ListView SubItem OwnerDraw Bold Part of the Text

主宰稳场 提交于 2020-08-10 19:16:12

问题


To finish off my adventure of owner drawing C# controls, my last problem is in a ListView. I have a ListView that will always be in the Detail view mode with two columns - Name and Value. For the Name column, I'll always use DefaultDraw, and for the Value column, I want to bold every match of a search term.

My TreeView question and my ComboBox question about owner drawing helped shape the code so far. I don't need any background or border drawing so it is simpler than some of the other questions I've seen people ask, but I have problem that I'm noticing so far.

When I shrink a column and expect a ..., if I've rendered multiple string parts (because of a match from the search term), each string has its own ... based on when it is longer than the column's width. If there is no presence of the search term and I render the e.SubItem.Text as one normal string, it behaves as expected.

In the series below, Address 1 is a complete string. The other two items have strings of 110 M, aple, and Avenue.

Is there a way to make the entire string act as a single entity with regards to ...? In my brief testing, I haven't found any other issues yet, but happy to take some advice if needed.

Before Resize

First Resize - Only Address 1 shows ...

Second Resize - Avenue Shows ...

Third Resize - 110 M Shows ...

Final Resize - Every 'string' Shows ...

Code

    TextFormatFlags subItemFlags = TextFormatFlags.Left | TextFormatFlags.Bottom | TextFormatFlags.EndEllipsis | TextFormatFlags.NoPadding;

    private void InitializeListView()
    {
        listView.OwnerDraw = true;
        listView.DrawColumnHeader += listView_DrawColumnHeader;
        listView.DrawSubItem += listView_DrawSubItem;
        listView.Font = new Font( "Microsoft YaHei UI", 10F, FontStyle.Regular, GraphicsUnit.Point, 0 );
    }

    private void listView_DrawColumnHeader( object sender, DrawListViewColumnHeaderEventArgs e ) => e.DrawDefault = true;
    private void listView_DrawSubItem( object sender, DrawListViewSubItemEventArgs e )
    {
        if ( e.ColumnIndex == 0)
        {
            e.DrawDefault = true;
        }
        else
        {
            var textPadding = 2;

            using ( var boldFont = new Font( listView.Font, FontStyle.Bold ) )
            {
                var stringParts = BuildDrawingString( e.SubItem.Text, e.Graphics, e.Bounds.Size, fieldSearch.Text, listView.Font, boldFont, subItemFlags ).ToArray();
                var color = listView.ForeColor;
                var point = new Point( e.SubItem.Bounds.X + textPadding, e.SubItem.Bounds.Y );

                foreach ( var part in stringParts )
                {
                    var font = part.Selected ? boldFont : listView.Font;
                    DrawText( part.Text, e.Graphics, e.SubItem.Bounds.Size, font, point, color, e.SubItem.BackColor, subItemFlags );
                    point.Offset( part.Width, 0 );
                }
            }
        }
    }
    private void DrawText( string text, Graphics graphics, Size size, Font font, Point offset, Color foreColor, Color backColor, TextFormatFlags formatFlags )
    {
        var rect = new Rectangle( offset, size );
        TextRenderer.DrawText( graphics, text, font, rect, foreColor, backColor, formatFlags );
    }

    private IEnumerable<(string Text, bool Selected, int Width)> BuildDrawingString( string textToRender, Graphics graphics, Size proposedSize, string pattern, Font normalFont, Font boldFont, TextFormatFlags formatFlags )
    {
        int measureText( string t, bool s ) => TextRenderer.MeasureText( graphics, t, s ? boldFont : normalFont, proposedSize, formatFlags ).Width;

        if ( pattern.Length == 0 )
        {
            yield return (textToRender, false, measureText( textToRender, false ));
        }
        else
        {
            var matches = Regex.Split( textToRender, $"(?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 = textToRender.Substring( currentCharacter, patternLength );
                    yield return (
                        matchText,
                        true,
                        measureText( matchText, true )
                    );

                    currentCharacter += patternLength;
                }
            }
        }
    }

回答1:


So after thinking about this a bit, I realized that each text part (bold and non-bold) were being measured against the 'entire subitem' Bounds.Size. I had to instead keep decreasing the allowed size for TextRenderer.MeasureText so it knew when to put in the ... and then immediately stop processing additional strings as soon as any item was rendered with .... So my BuildDrawingsString had to account for this, and it also had to return an AllowedWidth for each part as well, so that the actual call to TextRenderer.DrawText would use the correct Size each time as well.

    private void listView_DrawSubItem( object sender, DrawListViewSubItemEventArgs e )
    {
        if ( e.ColumnIndex == 0)
        {
            e.DrawDefault = true;
        }
        else
        {
            var textPadding = 2;

            using ( var boldFont = new Font( listView.Font, FontStyle.Bold ) )
            {
                var stringParts = BuildDrawingString( e.SubItem.Text, e.Graphics, e.SubItem.Bounds.Size, fieldSearch.Text, listView.Font, boldFont, subItemFlags, true );
                var color = listView.ForeColor;
                var point = new Point( e.SubItem.Bounds.X + textPadding, e.SubItem.Bounds.Y );

                foreach ( var part in stringParts )
                {
                    var font = part.Selected ? boldFont : listView.Font;
                    // System.Diagnostics.Trace.WriteLine( e.SubItem.Bounds.Size + ", " + part.Width );
                    DrawText( part.Text, e.Graphics, new Size( part.AllowedWidth, e.SubItem.Bounds.Size.Height ), font, point, color, e.SubItem.BackColor, subItemFlags );
                    point.Offset( part.Width, 0 );
                }
            }
        }
    }

    private IEnumerable<(string Text, bool Selected, int Width, int AllowedWidth)> BuildDrawingString( string textToRender, Graphics graphics, Size proposedSize, string pattern, Font normalFont, Font boldFont, TextFormatFlags formatFlags, bool isListView )
    {
        var totalWidth = 0;
        (int width, int allowedWidth, bool isTruncated) measureText( string t, bool s )
        {
            var size = new Size( proposedSize.Width - totalWidth, proposedSize.Height );
            var width = TextRenderer.MeasureText( graphics, t, s ? boldFont : normalFont, size, formatFlags ).Width;
            var truncated = isListView && TextRenderer.MeasureText( graphics, t, s ? boldFont : normalFont, size, formatFlags & ~TextFormatFlags.EndEllipsis ).Width != width;

            return ( width, size.Width, truncated );
        }

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

            for ( int i = 0; i < matches.Length; i++ )
            {
                if ( matches[ i ].Length >= 0 )
                {
                    var measureInfo = measureText( matches[ i ], false );
                    totalWidth += measureInfo.width;

                    yield return (
                        matches[ i ],
                        false,
                        measureInfo.width,
                        measureInfo.allowedWidth
                    );

                    currentCharacter += matches[ i ].Length;

                    if ( measureInfo.isTruncated )
                    {
                        yield break;
                    }
                }

                if ( i < matches.Length - 1 )
                {
                    var matchText = textToRender.Substring( currentCharacter, patternLength );
                    var measureInfo = measureText( matchText, true );
                    totalWidth += measureInfo.width;

                    yield return (
                        matchText,
                        true,
                        measureInfo.width,
                        measureInfo.allowedWidth
                    );

                    currentCharacter += patternLength;

                    if ( measureInfo.isTruncated )
                    {
                        yield break;
                    }
                }
            }
        }
    }


来源:https://stackoverflow.com/questions/63146058/listview-subitem-ownerdraw-bold-part-of-the-text

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!