1
votes

I'm trying to implement an owner drawn ListView because the base control eats the tab character which I need to align values within a column.

Using an example from MSDN as a base I was able get close. The only problem I still have is that the periods of ellipsis used when the text doesn't fit in the column is much more closely spaced together than in the default text rendering; the the point that if the font is bold the periods run together into an underscore.

The program below demonstrates the problem. It has 4 ListViews: The two on the top are drawn using the default rendering. The two on the bottom are ownerdrawn, and the pair in the right side are bolded. For length reasons I removed everything I didn't need in order to demonstrate the problem, which is why the ownerdawn ListViews don't have column headers.

Looking at a zoomed in screenshot the periods of the ellipsis in the owner drawn ListViews are spaced one pixel apart; those in the default drawing have two pixels of spacing. When bolding widens the periods to two pixels the owner drawn ones merge together into a solid mass that looks like an underscore.

There are other minor differences in the text rendering as well; but the ellipsis is the only one that's readily apparent without zooming. These differences do however make me suspect the problem is a more general issue. Possibly GDI vs GDI+ rendering? Except I thought that could only vary at the application level. Apparently not, toggling Application.SetCompatibleTextRenderingDefault() didn't affect anything.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class Form1 : Form
    {
        private void ListViewDrawSubItem(object sender, 
                                         DrawListViewSubItemEventArgs e)
        {
            ListView listView = sender as ListView;
            using (StringFormat sf = new StringFormat())
            {
                // Draw the standard background.
                e.DrawBackground();
                sf.SetTabStops(0, new float[] {12, 12, 12, 12, 12});
                sf.FormatFlags = sf.FormatFlags | StringFormatFlags.NoWrap;
                sf.Trimming = StringTrimming.EllipsisCharacter;

                // Draw the header text.
                // passing the controls font directly causes an 
                // ArguementException);
                using (Font headerFont = new Font(listView.Font.Name, 
                                                  listView.Font.Size, 
                                                  listView.Font.Style))
                {
                    e.Graphics.DrawString(e.SubItem.Text, headerFont, 
                                          Brushes.Black, e.Bounds, sf);
                }
            }
        }

        public Form1()
        {
            InitializeComponent();
            LoadData(listView1);
            LoadData(listView2);
            LoadData(listView3);
            LoadData(listView4);
        }

        private void LoadData(ListView listView)
        {
            listView.Columns.Add("first", 35);
            listView.Columns.Add("second", 75);

            for (int i = 0; i < 5; i++)
            {
                listView.Items.Add("test");
                listView.Items[i].SubItems.Add("test test test test");
            }
        }

        #region from Form1.Designer
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be 
        /// disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.listView1 = new System.Windows.Forms.ListView();
            this.listView2 = new System.Windows.Forms.ListView();
            this.listView3 = new System.Windows.Forms.ListView();
            this.listView4 = new System.Windows.Forms.ListView();
            this.SuspendLayout();
            // 
            // listView1
            // 
            this.listView1.Location = new System.Drawing.Point(12, 12);
            this.listView1.Name = "listView1";
            this.listView1.Size = new System.Drawing.Size(121, 116);
            this.listView1.TabIndex = 0;
            this.listView1.UseCompatibleStateImageBehavior = false;
            this.listView1.View = System.Windows.Forms.View.Details;
            // 
            // listView2
            // 
            this.listView2.Font = new System.Drawing.Font("Microsoft Sans Serif", 
                                       8.25F, System.Drawing.FontStyle.Bold,
                                       System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.listView2.Location = new System.Drawing.Point(151, 12);
            this.listView2.Name = "listView2";
            this.listView2.Size = new System.Drawing.Size(121, 116);
            this.listView2.TabIndex = 1;
            this.listView2.UseCompatibleStateImageBehavior = false;
            this.listView2.View = System.Windows.Forms.View.Details;
            // 
            // listView3
            // 
            this.listView3.Location = new System.Drawing.Point(12, 134);
            this.listView3.Name = "listView3";
            this.listView3.OwnerDraw = true;
            this.listView3.Size = new System.Drawing.Size(121, 116);
            this.listView3.TabIndex = 2;
            this.listView3.UseCompatibleStateImageBehavior = false;
            this.listView3.View = System.Windows.Forms.View.Details;
            this.listView3.DrawSubItem += new 
                              System.Windows.Forms.DrawListViewSubItemEventHandler(
                              this.ListViewDrawSubItem);
            // 
            // listView4
            // 
            this.listView4.Font = new System.Drawing.Font("Microsoft Sans Serif", 
                                       8.25F, System.Drawing.FontStyle.Bold,
                                       System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.listView4.Location = new System.Drawing.Point(151, 134);
            this.listView4.Name = "listView4";
            this.listView4.OwnerDraw = true;
            this.listView4.Size = new System.Drawing.Size(121, 116);
            this.listView4.TabIndex = 3;
            this.listView4.UseCompatibleStateImageBehavior = false;
            this.listView4.View = System.Windows.Forms.View.Details;
            this.listView4.DrawSubItem += new 
                              System.Windows.Forms.DrawListViewSubItemEventHandler(
                              this.ListViewDrawSubItem);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 262);
            this.Controls.Add(this.listView4);
            this.Controls.Add(this.listView3);
            this.Controls.Add(this.listView2);
            this.Controls.Add(this.listView1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.ListView listView1;
        private System.Windows.Forms.ListView listView2;
        private System.Windows.Forms.ListView listView3;
        private System.Windows.Forms.ListView listView4;
        #endregion

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
1
You need to align values in a column? Have you considered adding a column? - Hans Passant
@HansPassant I have. I'm displaying family data; one row/family. The number of kids is arbitrary; in most cases low but there're a few with a bunch in the dataset. There's a desire to avoid having large numbers of columns and the resulting horizontal scrollbar for something only needed for a few percent of the rows. Since the default tooltip for a cell that's to narrow to avoid being truncated is the full text of the cell it gives an alternate way to see all of an outlier without scrolling. It seemed like a reasonable compromise at the time. Kid 1...Kid N columns are the fallback option. - Dan Is Fiddling By Firelight

1 Answers

9
votes

I've found a contingent implementation for the draw sub item method. The main caveats I have are that the tab size is fixed (although I could drop to win32 if necessary to change it); and that the combination of flags that I need while working on my machine is reported to be mutually incompatible in MSDN.

private void ListViewDrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
    //toggle colors if the item is highlighted 
    if (e.Item.Selected && e.Item.ListView.Focused)
    {
        e.SubItem.BackColor = SystemColors.Highlight;
        e.SubItem.ForeColor = e.Item.ListView.BackColor;
    }
    else if (e.Item.Selected && !e.Item.ListView.Focused)
    {
        e.SubItem.BackColor = SystemColors.Control;
        e.SubItem.ForeColor = e.Item.ListView.ForeColor;
    }
    else
    {
        e.SubItem.BackColor = e.Item.ListView.BackColor;
        e.SubItem.ForeColor = e.Item.ListView.ForeColor;
    }

    // Draw the standard header background.
    e.DrawBackground();
        
    //add a 2 pixel buffer the match default behavior
    Rectangle rec = new Rectangle(e.Bounds.X + 2, e.Bounds.Y+2, e.Bounds.Width - 4, 
                                  e.Bounds.Height-4);

    //TODO  Confirm combination of TextFormatFlags.EndEllipsis and 
    //TextFormatFlags.ExpandTabs works on all systems.  MSDN claims they're exclusive 
    //but on Win7-64 they work.
    TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | 
                            TextFormatFlags.ExpandTabs | TextFormatFlags.SingleLine;

    //If a different tab stop than the default is needed, will have to p/invoke 
    //DrawTextEx from win32.
    TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.Item.ListView.Font, rec, 
                          e.SubItem.ForeColor, flags);
}