Positioning and highlighting of TreeView node text with OwnerDrawText mode

后端 未结 1 1588
Happy的楠姐
Happy的楠姐 2021-01-22 13:47

My Problem

I\'m trying to create a TreeView that will bold portions of node text that match a search term. My code was adopted from this question. I have

1条回答
  •  自闭症患者
    2021-01-22 14:22

    Part of the information found in the previous question didn't make the cut.

    • TextFormatFlags are important: these settings have a great impact on how the Text is rendered. Also, each Control has its own specific requirements, maybe the difference is small - as in this case - but we need to adapt anyway.
      The TreeView Control is better served when the Text is align to left and centered vertically.
    • TextRenderer is very precise, but we want to give it hand (as described before) always using Rectangles as the reference container to both measure and draw the Text. Possibly, don't use a Point, you'll notice that the results can change in similar situation when this simple reference is used. When drawing on a Control, we really don't want that.
    • You removed TextFormatFlags.NoClipping from the original code, not good, this is a keeper. Unless you actually want to clip the Text, but then you need to specify how to clip it. Other flags can be combined for that.

    Specific to this question:

    • e.State == TreeNodeStates.Selected is not enough, we also need to test TreeNodeStates.Focused, otherwise we have a weird difference in the Text rendering when a Node is selected or focused; these are different states: one Node can be selected and a different one focused, both must be rendered equally.
    • There's a subtle difference between the Graphics bound of the DrawTreeNodeEventArgs and the bounds of the Node item. When drawing the background, the former is used, to define the constraint of the Node text, the latter is used instead.
    • Using Font with different weights for the same section of Text, we must use the bounds of the Node as the starting position, use the measures returned by TextRenderer.MeasureText, sum these measures and offset the text position manually (as mentioned, counting on the precision of MeasureText).
    • Whether the Node has an image is not really important, we just need to account for the initial offset, equal to e.Node.Bounds.X. In code, it's stored in int drawingPosition = e.Node.Bounds.X;.

    Visual result:


    TextFormatFlags twFormat = TextFormatFlags.Left | TextFormatFlags.VerticalCenter | 
                               TextFormatFlags.NoClipping | TextFormatFlags.NoPadding;
    
    private void tree_DrawNode(object sender, DrawTreeNodeEventArgs e)
    {
        Color textColor = e.Node.ForeColor;
        Color backColor = e.Node.BackColor == Color.Empty ? tree.BackColor : e.Node.BackColor;
    
        if (e.State.HasFlag(TreeNodeStates.Selected) || e.State.HasFlag(TreeNodeStates.Focused)) {
            textColor = SystemColors.HighlightText;
            backColor = SystemColors.Highlight;
        }
        using (var brush = new SolidBrush(backColor)) {
            e.Graphics.FillRectangle(brush, e.Bounds);
        }
    
        string searchText = fieldSearch.Text;  // Search string from TextBox
        int drawingPosition = e.Node.Bounds.X;
        foreach (var part in BuildDrawingString(e.Node.Text, searchText)) {
            var style = part.Selected ? FontStyle.Bold : FontStyle.Regular;
            drawingPosition += RenderNodeText(part.Text, e, style, new Point(drawingPosition, e.Node.Bounds.Y), textColor).Width;
        }
    }
    
    private Size RenderNodeText(string text, DrawTreeNodeEventArgs e, FontStyle altStyle, Point offset, Color foreColor)
    {
        using (var font = new Font(tree.Font, altStyle)) {
            var size = e.Node.Bounds.Size;
            var textWidth = TextRenderer.MeasureText(e.Graphics, text, font, size, twFormat);
            var rect = new Rectangle(offset, size);
            TextRenderer.DrawText(e.Graphics, text, font, rect, foreColor, e.Node.BackColor, twFormat);
            return textWidth;
        }
    }
    
    private IEnumerable<(string Text, bool Selected)> BuildDrawingString(string itemContent, string pattern)
    {
        if (pattern.Length == 0) {
            yield return (itemContent, false);
        }
        else {
            var matches = Regex.Split(itemContent, $"(?i){pattern}");
            int pos = itemContent.IndexOf(pattern, StringComparison.CurrentCultureIgnoreCase);
            for (int i = 0; i < matches.Length; i++) {
                if (matches[i].Length == 0 && i < matches.Length - 1) {
                    yield return (itemContent.Substring(pos, pattern.Length), matches[i].Length > 0 ? false : true);
                }
                else {
                    yield return (matches[i], false);
                    if (i < matches.Length - 1) {
                        yield return (itemContent.Substring(pos, pattern.Length), true);
                    }
                }
            }
        }
    }
    

    0 讨论(0)
提交回复
热议问题