0

I have a WinForms app for .NET Framework in which the text of items in context menus based on the the ContextMenuStrip component are drawn using Graphics.DrawString() to provide consistent look with other interface elements. The core part of the custom renderer looks like this:

private class CustomContextMenuRenderer : ToolStripProfessionalRenderer
{
    private static StringFormat fStringFormatLeft = new StringFormat()
    { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Center};
    private static StringFormat fStringFormatRight = new StringFormat()
    { Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Center };
        
    protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
    {
        StringFormat sf = (e.TextFormat & TextFormatFlags.Right) == 0 ? fStringFormatLeft : fStringFormatRight;
        using (SolidBrush brush = new SolidBrush(e.Item.ForeColor))
        {
            e.Graphics.DrawString(e.Text, e.TextFont, brush, e.TextRectangle, sf);
        }
    }
}

For some fonts, the height of menu items calculated by the ContextMenuStrip component is not enough to display item text. Most likely, this happens because the standard implementation is based on the TextRenderer class to output item texts. Is there a way to measure and tell the component the expected size of the item if we use GDI+ to render item texts?

TecMan
  • 2,743
  • 2
  • 30
  • 64
  • In `OnRenderItemText()` you can redefine both `e.Item.Height` and `e.TextRectangle`, but this assumes that the Items have `AutoSize = false`. -- Note that you can set this in the override, as `e.Item.AutoSize = false;` (i.e., doesn't need to be set in the Designer) -- I usually add a public Property in the custom `ToolStripProfessionalRenderer`, as shown here: [Change space between Image and Text in ContextMenuStrip](https://stackoverflow.com/a/64130479/7444103) (slightly different language, should be clear enough) – Jimi Jun 17 '22 at 14:55
  • As a note, if you measure the text in the override (instead of pre-calcuating the text height, using the setter of a custom public Property, for example), remember to call `e.Graphics.MeasureString()` **only if** `e.Text` is not null or empty (otherwise you may end up measuring the separators, which may cause *weird effects*) – Jimi Jun 17 '22 at 15:28
  • @Jimi, I've examined the source code of the `ToolStripDropDownMenu` class and related classes to understand your idea, but I couldn't. Are we eligible to change `e.Item.Height` and other item parameters in the overridden `OnRenderItemText()`? What if we do not call `OnRenderItemText()` from the base class at all? – TecMan Jun 17 '22 at 15:34
  • What does *What if we do not call `OnRenderItemText()`* mean? You're not *calling* it, the base class does and you override it, to change its behavior. Here, you're not calling `base.OnRenderItemText(e);` to avoid double-rendering. -- You just need, e.g., `var textSize = e.Graphics.MeasureString(e.Text, e.TextFont, e.Item.Size, StringFormat.GenericTypographic); e.Item.AutoSize = false; var textRect = e.TextRectangle; textRect.Height = (int)textSize.Height + e.Item.Padding.Size.Height + 4; e.Item.Height = textRect.Height; e.TextRectangle = textRect;`, then draw your text. – Jimi Jun 17 '22 at 15:49
  • Of course, as mentioned, also add a first line: `if (string.IsNullOrEmpty(e.Text)) return;`, since you cannot measure separators here. That's done in `OnRenderSeparator()`, eventually. -- This `e.Item.Padding.Size.Height + 4` (current Item padding + `DrawString()` padding) depends on how you configure the StringFormat. – Jimi Jun 17 '22 at 15:50
  • If your ContextMenu also contains items that are not just text and separators (e.g., a TextBox or ComboBox), you have to call base when `e.Text` is empty. – Jimi Jun 17 '22 at 16:21

0 Answers0