使用OwnerDrawText模式定位和突出显示TreeView节点文本 [英] Positioning and highlighting of TreeView node text with OwnerDrawText mode

查看:67
本文介绍了使用OwnerDrawText模式定位和突出显示TreeView节点文本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个TreeView,该字体将使与搜索词匹配的节点文本部分变为粗体.我的代码来自此问题.无论是否使用ImageList,我都存在相同/相似的问题,但是我将在此处发布未使用的版本.渲染文本时,会得到类似这样的内容,其中文本的最后一部分被切除,但对于某些节点,.即 Version 看起来还不错,但是其余的文本从边界处截取的数量却不同.

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 the same/similar problem with or without an ImageList used, but I'll post the unused version here. When I render my text, I get something like this where the last part of the text is cut off, but only for some of the nodes. i.e. Version looks ok, but the rest have differing amounts of text cut off from the bounds.

我认为我的TextFormatFlags标志正在影响这一点,但是如果我在文本的度量/渲染"期间不传递这些标志,则该节点会在左侧断开.

I think my TextFormatFlags flags are affecting this, but if I don't pass those in during Measure/Render of the text, the node cuts off on the left.

如果我绘制粗体字,还会引入垂直间距问题.您可以看到当我使用 Plan 作为搜索词时,它比文本的其余部分要高一些.

If I draw my bold text, I additionally have vertical spacing issues introduced. You can see when I use Plan as the search term, it is a bit higher than the rest of the text.

如果我选择一个节点,您会看到我再次遇到水平间距问题.

If I select a node, you can see I then get horizonal spacing issues again.

  1. 不使用粗体显示的节点的水平调整问题
  2. 使用粗体部分渲染的节点的垂直尺寸问题
  3. 使用粗体显示的节点的水平尺寸调整问题.
  1. Horizontal sizing issues of nodes rendering without bold
  2. Vertical sizing issues of nodes rendering with bold portions
  3. Horizontal sizing issues of nodes rendering with bold and active.

更新代码

由于@jimi,我得以整理一些东西.当他回答时,我接近了,但由于他的建议,以下是我的更改.我所做的一些事情与他所做的有所不同.

Updated Code

Thanks to @jimi, I was able to firm up some stuff. I got close while he was answering, but below are my changes due to his suggestions. I did do a few things differently than he did.

  1. ( formClosing || e.Bounds.X == -1 )为true时,我立即退出tree_DrawNode,以避免出现某些图形故障.您可以在下面看到我的评论.

  1. I immediately exit tree_DrawNode when ( formClosing || e.Bounds.X == -1 ) is true to avoid some graphical glitches. You can see my comment below.

我喜欢BuildDrawingString清理代码的方式.我添加了计算所得的Width属性,此外还修复了有关返回匹配文本的错误.

I liked how BuildDrawingString cleaned up the code. I added a Width property that is calculated and additionally I fixed a bug about the return of matched text.

当一个节点具有焦点时,我绘制了突出显示的背景,而当它不能更好地模拟默认的TreeView行为时,我绘制了窗口的背景.尤其是在节点上向下移动,而在其他位置向上移动时.

I drew the highlight background when a node had focus and window background when it doesn't to better emulate default TreeView behavior. Especially when mousing down on a node, and mousing up somewhere else.

我不仅仅使用e.Bounds来绘制背景矩形,而是基于e.Node.Bounds x/y,渲染文本所需的宽度和一些填充来创建自己的矩形.

Instead of just using e.Bounds to draw background rectangle I created my own rectangle based on e.Node.Bounds x/y, the required width for rendered text, and a little padding.

private void tree_DrawNode( object sender, DrawTreeNodeEventArgs e )
{
    var textPadding = 2;

    // formClosing - don't need to redraw when shutting down, avoids seeing a little glitch with text offset
    // e.Bounds.X == -1 - when form loads, all *non-top level* nodes seem to draw on top of each other on first line
    //                    causing a big 'black blur' to happen when form loads b/c text is mashed together
    if ( formClosing || e.Bounds.X == -1 )
    {
        return;
    }

    using ( var boldFont = new Font( tree.Font, FontStyle.Bold ) )
    {
        var stringParts = BuildDrawingString( e, fieldSearch.Text, boldFont ).ToArray();

        // To better emulate default behavior, draw the 'selected' look only when focused, so if
        // you click down on item, originally selected item draws 'normal' and item clicking on is 'selected'
        // and if you let up on mouse outside of node, it reverts back to how it was.
        var isSelected = e.State.HasFlag( TreeNodeStates.Focused );
        var color = isSelected ? Color.White : tree.ForeColor;

        // Use e.NodeBounds X,Y and width of measured text with just a little bit of 
        // padding on left and right, e.Bounds was too wide.
        var nodeRectangle = new Rectangle(
                e.Node.Bounds.X,
                e.Node.Bounds.Y,
                stringParts.Sum( p => p.Width ) + textPadding * 2,
                e.Node.Bounds.Height
            );

        e.Graphics.FillRectangle( isSelected ? SystemBrushes.Highlight : SystemBrushes.Window, nodeRectangle );

        if ( isSelected )
        {
            using ( var focusPen = new Pen( Color.Black ) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dot } )
            {
                nodeRectangle.Size = new Size( nodeRectangle.Width - 1, nodeRectangle.Height - 1 );
                e.Graphics.DrawRectangle( focusPen, nodeRectangle );
            }
        }

        var point = new Point( e.Node.Bounds.X + textPadding, e.Node.Bounds.Y );

        foreach ( var part in stringParts )
        {
            var font = part.Selected ? boldFont : tree.Font;
            RenderNodeText( part.Text, e, font, point, color );
            point.Offset( part.Width, 0 );
        }
    }
}

private void RenderNodeText( string text, DrawTreeNodeEventArgs e, Font font, Point offset, Color color )
{
    var size = e.Node.Bounds.Size;
    var rect = new Rectangle( offset, size );
    TextRenderer.DrawText( e.Graphics, text, font, rect, color, e.Node.BackColor, treeFlags );
}

private IEnumerable<(string Text, bool Selected, int Width)> BuildDrawingString( DrawTreeNodeEventArgs e, string pattern, Font boldFont )
{
    var itemContent = e.Node.Text;

    int measureText( string t, bool s ) => TextRenderer.MeasureText( e.Graphics, t, s ? boldFont : tree.Font, e.Bounds.Size, treeFlags ).Width;

    if ( pattern.Length == 0 )
    {
        yield return (itemContent, false, measureText( itemContent, false ));
    }
    else
    {
        var matches = Regex.Split( itemContent, $"(?i){pattern}" );
        var currentCharacter = 0;
        var patternLength = pattern.Length;

        for ( int i = 0; i < matches.Length; i++ )
        {
            if ( matches[ i ].Length >= 0 )
            {
                yield return (
                    matches[ i ], 
                    false, 
                    measureText( matches[ i ], false ) 
                );

                currentCharacter += matches[ i ].Length;
            }

            if ( i < matches.Length - 1 )
            {
                var matchText = itemContent.Substring( currentCharacter, patternLength );
                yield return (
                    matchText,
                    true,
                    measureText( matchText, true )
                );

                currentCharacter += patternLength;
            }
        }
    }
}

新扭曲

我将此处创建的所有最终代码从WinForms应用程序移至VSTO Word AddIn项目/表单中的表单,并且由于某些原因字体呈现方式有所不同.

New Twist

I moved all the final code created here from a WinForms Application to a Form inside a VSTO Word AddIn project/form and the font rendering is different for some reason.

  1. 一般字体(普通字体)看起来越来越细.
  2. 粗体字体似乎比普通字体偏高一点.

在下图中,最上面的表单是Word中的一个,第二个表单(Form1的标题)是我的WinForms应用程序.作为VSTO加载项运行时是否存在一些兼容性问题?

In the image below, the top form is the one from Word and the second one (caption of Form1) is my WinForms application. Is there some compatability issue or something when running as a VSTO add-in?

    private void Form1_Load( object sender, EventArgs e )
    {
        tree.DrawMode = TreeViewDrawMode.OwnerDrawText;
        tree.DrawNode += tree_DrawNode;
        tree.Font = new Font( "Microsoft YaHei UI", 10F, FontStyle.Regular, GraphicsUnit.Point, 0 );
        // tree.ImageList = imageList;
        tree.Nodes.Add( "PlanInfo" );
        tree.Nodes[ 0 ].Nodes.Add( "Version" );
        tree.Nodes[ 0 ].Nodes.Add( "Plan Name" );
        tree.Nodes[ 0 ].Nodes.Add( "Plan Sponsor" );
    }

    TextFormatFlags treeFlags = TextFormatFlags.Top | TextFormatFlags.Left | TextFormatFlags.NoPadding;

    private void tree_DrawNode( object sender, DrawTreeNodeEventArgs e )
    {
        var currentX = 0;
        var searchText = e.Node.Text;
        var searchTerm = fieldSearch.Text;
        var matches = Regex.Split( searchText, "(?i)" + searchTerm );
        var point = new Point( e.Node.Bounds.X + currentX, e.Node.Bounds.Y );
        var isSelected = ( e.State & TreeNodeStates.Selected ) != 0;
        var color = isSelected ? Color.White : tree.ForeColor;

        if ( isSelected )
        {
            e.Graphics.FillRectangle( SystemBrushes.Highlight, e.Node.Bounds );
        }

        if ( !string.IsNullOrEmpty( searchTerm ) && matches != null )
        {
            var currentCharacter = 0;
            var currentMatch = 0;
            var keyLength = searchTerm.Length;

            foreach ( var m in matches )
            {
                if ( !string.IsNullOrEmpty( m ) )
                {
                    point.Offset(
                        RenderNodeText( m, e, FontStyle.Regular, point, color ).Width,
                        0
                    );

                    currentCharacter += m.Length;
                }

                currentMatch++;

                if ( currentMatch < matches.Length || ( string.IsNullOrEmpty( m ) && currentMatch == 1 ) )
                {
                    var boldText = searchText.Substring( currentCharacter, keyLength );

                    point.Offset(
                        RenderNodeText( boldText, e, FontStyle.Bold, point, color ).Width,
                        0
                    );

                    currentCharacter += keyLength;
                }
            }
        }
        else
        {
            RenderNodeText( e.Node.Text, e, FontStyle.Regular, point, color );
        }
    }

    private Size RenderNodeText( string text, DrawTreeNodeEventArgs e, FontStyle altStyle, Point offset, Color color )
    {
        using ( var font = new Font( tree.Font, altStyle ) )
        {
            var size = e.Node.Bounds.Size;
            var textWidth = TextRenderer.MeasureText( e.Graphics, text, font, size, treeFlags );
            var rect = new Rectangle( offset, size );
            TextRenderer.DrawText( e.Graphics, text, font, rect, color, Color.Transparent, treeFlags );
            return textWidth;
        }
    }

推荐答案

  • TextFormatFlags 很重要:这些设置对文本的呈现方式有很大的影响.另外,每个控件都有自己的特定要求,也许相差很小-在这种情况下-但无论如何我们都需要适应.
    当文本"向左对齐并垂直居中时,可以更好地使用TreeView控件.
  • TextRenderer 非常精确,但我们要始终使用Rectangles作为参考容器来测量和绘制文本(如前所述).可能的是,不要使用Point,使用该简单引用时,您会注意到在类似情况下结果可能会发生变化.在控件上绘图时,我们真的不希望这样做.
  • 您从原始代码中删除了TextFormatFlags.NoClipping,不好,这是管理员.除非您实际上想剪切文本,否则您需要指定如何剪切文本.可以为此组合其他标志.
    • 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.

    特定于此问题:

    • e.State == TreeNodeStates.Selected > 还不够,我们还需要测试 DrawTreeNodeEventArgs 和节点"项的边界.绘制背景时,使用前者来定义Node文本的约束,而使用后者来代替.
    • 对于文本的同一部分,使用具有不同权重的字体,我们必须使用Node的边界作为起始位置,并使用
    • 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;.

    视觉结果:

    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);
                    }
                }
            }
        }
    }
    

    这篇关于使用OwnerDrawText模式定位和突出显示TreeView节点文本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆