Search code examples
c#.netwinformsdatagridviewellipsis

DataGridView TextBox Column - Show Right Part of Text when Text is Long


I have a DataGridView in windows forms which has a column that I don't want to set it to be auto-size to fit all text.

Instead I want to display the right part of the text when the text is long. Is this possible?

For example:

  • Text for a cell is: Some long text

  • Currently width is such that only this displays: Some long

  • I would like it to show the right most part of text at first: long text

Then to see the whole text, the user can resize the column width. It's opposite of default behavior. In default behavior, it shows the left part of text with ellipsis at the end of string, and to see the right part user have to change column width.

Thanks


Solution

  • The main idea is handling CellPainting event of DataGridView and measure length of string using TextRenderer.MeasureText and check if the string is longer than width of cell, draw string at right of cell otherwise, draw it at left side.

    Note: Please note, right-aligning the text is not the solution. If you use right-aligning, When the text is long, for example Some long text, it will display Some lo... while the OP need the text be shown as long text or ...ng text. The op need the right part of text be shown first. It's not about right-aligning.

    void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
    {
        if (e.RowIndex >= 0 && e.ColumnIndex == 1)
        {
            var rect = e.CellBounds;
            rect.Inflate(-1, 0);
            e.Paint(e.CellBounds, DataGridViewPaintParts.All
                & ~(DataGridViewPaintParts.ContentForeground));
            var text = string.Format("{0}", e.FormattedValue);
            var size = TextRenderer.MeasureText(text, e.CellStyle.Font);
            var flags = TextFormatFlags.Left;
            if (size.Width > rect.Width)
                flags = TextFormatFlags.Right;
            TextRenderer.DrawText(e.Graphics, text, e.CellStyle.Font,
                rect, e.CellStyle.ForeColor, flags | TextFormatFlags.VerticalCenter);
            e.Handled = true;
        }
    }
    

    Left image: For long strings, you see the right part of string in cell. Short string appearance is normal.

    Right Image: The column got wider so appearance is normal.

    enter image description here enter image description here

    Showing Ellipsis at beginning of string

    But the appearance is somehow confusing for users and they don't know which cell has more text and which one doesn't have more text. In such cases, showing ellipsis is really useful and based on your preferences, ellipsis should be shown at beginning of the string this way:

    enter image description here

    But the problem is drawing a string with ellipsis at the beginning of the string.

    There is a useful article written by Thomas Polaert. I made some small changes to the class which he shared to calculate a string with start ellipsis based on the string, available width, the font and graphics object which will draw the string. I've posted changed code at the end of this post.

    The only change which you need to make is calculating the text with ellipsis:

    var text = string.Format("{0}", e.FormattedValue);
    text = AutoEllipsis.Ellipsis.Compact(text, e.Graphics, e.CellBounds.Width,
        e.CellStyle.Font, AutoEllipsis.EllipsisFormat.Start);
    

    The rest of code is untouched and the result would be a string with ellipsis at the beginning despite the default behavior which shows ellipsis at the end.

    AutoEllipsis

    The original code relies on a Control. I changed it a bit to pass Graphics object, Font and Width which we want to use to calculate auto ellipis:

    using System;
    using System.Drawing;
    using System.IO;
    using System.Text.RegularExpressions;
    using System.Windows.Forms;
    namespace AutoEllipsis
    {
        /// <summary>
        /// Specifies ellipsis format and alignment.
        /// </summary>
        [Flags]
        public enum EllipsisFormat
        {
            /// <summary>
            /// Text is not modified.
            /// </summary>
            None = 0,
            /// <summary>
            /// Text is trimmed at the end of the string. An ellipsis (...) is drawn in place of remaining text.
            /// </summary>
            End = 1,
            /// <summary>
            /// Text is trimmed at the begining of the string. An ellipsis (...) is drawn in place of remaining text. 
            /// </summary>
            Start = 2,
            /// <summary>
            /// Text is trimmed in the middle of the string. An ellipsis (...) is drawn in place of remaining text.
            /// </summary>
            Middle = 3,
            /// <summary>
            /// Preserve as much as possible of the drive and filename information. Must be combined with alignment information.
            /// </summary>
            Path = 4,
            /// <summary>
            /// Text is trimmed at a word boundary. Must be combined with alignment information.
            /// </summary>
            Word = 8
        }
    
    
        public class Ellipsis
        {
            /// <summary>
            /// String used as a place holder for trimmed text.
            /// </summary>
            public static readonly string EllipsisChars = "...";
    
            private static Regex prevWord = new Regex(@"\W*\w*$");
            private static Regex nextWord = new Regex(@"\w*\W*");
    
            /// <summary>
            /// Truncates a text string to fit within a given control width by replacing trimmed text with ellipses. 
            /// </summary>
            /// <param name="text">String to be trimmed.</param>
            /// <param name="ctrl">text must fit within ctrl width.
            /// The ctrl's Font is used to measure the text string.</param>
            /// <param name="options">Format and alignment of ellipsis.</param>
            /// <returns>This function returns text trimmed to the specified witdh.</returns>
            public static string Compact(string text, Graphics g, int width, Font font, EllipsisFormat options)
            {
                if (string.IsNullOrEmpty(text))
                    return text;
    
                // no aligment information
                if ((EllipsisFormat.Middle & options) == 0)
                    return text;
    
                if (g == null)
                    throw new ArgumentNullException("g");
    
                Size s = TextRenderer.MeasureText(g, text, font);
    
                // control is large enough to display the whole text
                if (s.Width <= width)
                    return text;
    
                string pre = "";
                string mid = text;
                string post = "";
    
                bool isPath = (EllipsisFormat.Path & options) != 0;
    
                // split path string into <drive><directory><filename>
                if (isPath)
                {
                    pre = Path.GetPathRoot(text);
                    mid = Path.GetDirectoryName(text).Substring(pre.Length);
                    post = Path.GetFileName(text);
                }
    
                int len = 0;
                int seg = mid.Length;
                string fit = "";
    
                // find the longest string that fits into 
                // the control boundaries using bisection method
                while (seg > 1)
                {
                    seg -= seg / 2;
    
                    int left = len + seg;
                    int right = mid.Length;
    
                    if (left > right)
                        continue;
    
                    if ((EllipsisFormat.Middle & options) == EllipsisFormat.Middle)
                    {
                        right -= left / 2;
                        left -= left / 2;
                    }
                    else if ((EllipsisFormat.Start & options) != 0)
                    {
                        right -= left;
                        left = 0;
                    }
    
                    // trim at a word boundary using regular expressions
                    if ((EllipsisFormat.Word & options) != 0)
                    {
                        if ((EllipsisFormat.End & options) != 0)
                        {
                            left -= prevWord.Match(mid, 0, left).Length;
                        }
                        if ((EllipsisFormat.Start & options) != 0)
                        {
                            right += nextWord.Match(mid, right).Length;
                        }
                    }
    
                    // build and measure a candidate string with ellipsis
                    string tst = mid.Substring(0, left) + EllipsisChars + mid.Substring(right);
    
                    // restore path with <drive> and <filename>
                    if (isPath)
                    {
                        tst = Path.Combine(Path.Combine(pre, tst), post);
                    }
                    s = TextRenderer.MeasureText(g, tst, font);
    
                    // candidate string fits into control boundaries, try a longer string
                    // stop when seg <= 1
                    if (s.Width <= width)
                    {
                        len += seg;
                        fit = tst;
                    }
                }
    
                if (len == 0) // string can't fit into control
                {
                    // "path" mode is off, just return ellipsis characters
                    if (!isPath)
                        return EllipsisChars;
    
                    // <drive> and <directory> are empty, return <filename>
                    if (pre.Length == 0 && mid.Length == 0)
                        return post;
    
                    // measure "C:\...\filename.ext"
                    fit = Path.Combine(Path.Combine(pre, EllipsisChars), post);
    
                    s = TextRenderer.MeasureText(g, fit, font);
    
                    // if still not fit then return "...\filename.ext"
                    if (s.Width > width)
                        fit = Path.Combine(EllipsisChars, post);
                }
                return fit;
            }
        }
    }