Search code examples
c#winformsonpainttoolstripstatuslabel

Adding missing properties support to ToolStripStatusLabel which overrides OnPaint?


I am using the following class which inherits from ToolStripStatusLabel and adds the feature to ellipsis text which is too long to display in the control.

[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.StatusStrip)]
    public partial class SpringLabel : ToolStripStatusLabel
    {
        public SpringLabel()
        {
            this.Spring = true;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            var flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | TextFormatFlags.VerticalCenter;
            var bounds = new Rectangle(0, 0, this.Bounds.Width, this.Bounds.Height);
            TextRenderer.DrawText(e.Graphics, this.Text, this.Font, bounds, this.ForeColor, flags);
        }
    }

This works fine but is missing support for various properties of the parent control, such as BorderSides as well as any support for images and so on. Is there a way to get at the ToolStripStatusLabel code to try and copy or emulate some of its behavior? I really have no ideas on where to start looking.

Edited to add the solution as suggested by @Hans

[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.StatusStrip)]
    public partial class SpringLabel : ToolStripStatusLabel
    {
        private string? _sltext;
        public new string? Text
        {
            get => _sltext;
            set { _sltext = value; ToolTipText = _sltext; Invalidate(); }
        }
        public SpringLabel()
        {
            this.Spring = true;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            var flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | TextFormatFlags.VerticalCenter;
            var bounds = new Rectangle(0, 0, this.Bounds.Width, this.Bounds.Height);
            TextRenderer.DrawText(e.Graphics, _sltext, this.Font, bounds, this.ForeColor, flags);
        }
    }

This works just fine but likely doesn't support some of the properties such as images. Also the parent StatusStrip control seems to require the LayoutStyle be set to the default of Table.


Solution

  • You'll not get the desired result by creating a custom ToolStripStatusLabel unless you draw all parts of the label yourself. The image, text, borders ...etc. Each has a set of properties that the default renderer uses to draw them in its overridable OnRenderXXX methods. Which means, you can instead create a custom renderer, override the relevant methods to draw the different parts of the label the way you want or to adjust the properties of the specialized xxxEventArgs - if possible - and call the base method to do the rest.

    Take the following for example.

    public class TSRenderer : ToolStripProfessionalRenderer
    {
        protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
        {            
            if (e.Item is ToolStripStatusLabel lbl)
            {
                if (lbl.Image is not null)
                {
                    Rectangle r = e.TextRectangle;
                    Size scaleSize = lbl.Owner!.ImageScalingSize;
    
                    if (r.X < scaleSize.Width)
                    {
                        r.X += scaleSize.Width + 1;
                        r.Width -= r.X + 1;
                    }
    
                    e.TextRectangle = r;
                }
    
                e.TextFormat = TextFormatFlags.Left |
                    TextFormatFlags.VerticalCenter |
                    TextFormatFlags.EndEllipsis;                    
            }            
    
            base.OnRenderItemText(e);
        }
    
        protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e)
        {
            if (e.Item is ToolStripStatusLabel && e.Image is not null)
            {
                var r = e.ImageRectangle;
                r.Width = r.Height;
                r.X += 2;
                e.Graphics.DrawImage(e.Image, r);
            }
            else
            {
                base.OnRenderItemImage(e);
            }
        }
    }
    
    // Use it...
    
    public partial class SomeForm : Form
    {
        public SomeForm()
        {
            InitializeComponent();
            yourStatusStrip.Renderer = new TSRenderer();
            someToolStripStatusLabel.AutoSize = false; // Must do.
            someToolStripStatusLabel.Spring = true;
        }
    }
    

    ... and that's all.

    Note, when the text length exceeds the width of the available space, the base renderer does not render the image. It takes the width from the image space and appends it to the text space. Therefore, you need to override this by design behavior in the OnRenderItemImage method override and draw the image yourself regardless of the length of the text. This also requires overriding the OnRenderItemText method to - in addition to apply the required TextFormatFlags.EndEllipsis flag - fix the text rectangle so that it does not overlap the image rectangle since the renderer draws the image before drawing the text.

    One final note, set the ToolStripStatusLabel.AutoSize property to false to ensure that overridden methods are also called when the container Form is minimized and restored.