Welcome to the Axosoft Community, Sign in | Register | Help
in Search

Dan Suceava

Everything & Nothing

Nested GroupBox controls bug

Another bug that crept up in SP1 of the .Net 1.1 Framework is the nested group box caption problem.  The bug is that captions of nested group boxes are rendered in bold and in the space of the caption as if it wasn't bold, thus cutting off the text.  You've probably seen it in some application, if not here's a shot of the issue:

Microsoft does not currently have a fix for this problem, but there is a fairly simple solution to it.  You can place a panel inside the main group box and place the nested group box inside the panel and dock it.  This will make the problem go away.  But I don't particularly like this solution, it involves messing around with too many controls on the UI.  I was looking for a solution that involves sub-classing the group box control so I could just replace the type name in code.

Googling around I did not find much along these lines.  One post I'd found had some simple code that did not seem to work.  So I decided to do it myself.  The idea is simple, just overwrite the caption using the right font.  This basically involves clearing the background and redrawing the caption.  Simple enough, just use FillRectangle and DrawString and it's all done. 

 public class AxoGroupBox : GroupBox
 {
  private const int WM_PAINT = 0x000F;

  protected override void WndProc(ref Message m)
  {
   base.WndProc(ref m);

   if (m.Msg == WM_PAINT)
   {
    // get graphics device
    using (Graphics g = Graphics.FromHwnd(this.Handle))
    {
     if (g != null)
     {
      SizeF sz = g.MeasureString(this.Text, this.Font);
      Rectangle bounds = new Rectangle(10, 0, (int)sz.Width, (int)sz.Height);
      g.FillRectangle(new SolidBrush(this.BackColor), bounds);
      g.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), bounds.Left, bounds.Top);
     }
    }
   }
  }
 }

Not so fast!  This works fine and dandy unless your application uses visual styles, which is the case with OnTime.  This means we have to deal with the themes because group box captions are drawn in different colors.  It turns out to be a little easier than it sounds.

There's a set of API functions in the uxtheme.dll that allow us to deal with XP themes.  But first, we use the OSFeature class to find out if themes are supported on the current OS.  Next we make sure the application is themed (i.e. the user could be not using themes). Then we start doing whatever we need to do for the theme.  In my case I only care about getting the color of the group box caption and I'll use my previous code to re-draw it.  If you want to get more involved, you should probably use the DrawThemeText function and all the other Draw functions to have the system do it for you. I decided not to go that far, just needed a simple solution which works in my case.

So here's my sub-classed group box control:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Axosoft.WinControls
{
 public class AxoGroupBox : GroupBox
 {
  private const int WM_PAINT   = 0x000F;

  // part id
  private const int BP_GROUPBOX = 4;
  // state id
  private const int PBS_NORMAL = 1;
  // property id
  private const int TMT_TEXTCOLOR = 3803;

  [DllImport("uxtheme.dll", ExactSpelling=true, CharSet=CharSet.Unicode)]
  public static extern IntPtr OpenThemeData(IntPtr hWnd, String classList);
  [DllImport("uxtheme.dll", ExactSpelling=true)]
  public extern static Int32 CloseThemeData(IntPtr hTheme);
  [DllImport("uxtheme.dll", ExactSpelling=true)]
  public extern static int IsAppThemed();
  [DllImport("uxtheme.dll", ExactSpelling=true)]
  public extern static Int32 GetThemeColor(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out UInt32 pColor);


  protected override void WndProc(ref Message m)
  {
   base.WndProc(ref m);

   if (m.Msg == WM_PAINT)
   {
    // get graphics device
    using (Graphics g = Graphics.FromHwnd(this.Handle))
    {
     if (g != null)
     {
      Color groupText = this.ForeColor;
      try
      {
       // check to see if OS supports themes
       if (OSFeature.Feature.IsPresent(OSFeature.Themes))
       {
        // check to see if the app is using themes
        if (IsAppThemed() == 1)
        {
         // open theme data
         IntPtr td = OpenThemeData(this.Handle, "Button");
         if (td != IntPtr.Zero)
         {
          // get text color
          UInt32 clr = 0;
          if (GetThemeColor(td, BP_GROUPBOX, PBS_NORMAL, TMT_TEXTCOLOR, out clr) == 0)
          {
           // color is backwards (BGR) => adjust value
           int iclr = (int)clr;
           groupText = Color.FromArgb(iclr & 0xff, (iclr & 0xff00) >> 8, (iclr & 0xff0000) >> 16);
          }

          // close theme data
          CloseThemeData(td);
         }
        }
       }
      }
      catch {}

      SizeF sz = g.MeasureString(this.Text, this.Font);
      Rectangle bounds = new Rectangle(10, 0, (int)sz.Width, (int)sz.Height);
      g.FillRectangle(new SolidBrush(this.BackColor), bounds);
      g.DrawString(this.Text, this.Font, new SolidBrush(groupText), bounds.Left, bounds.Top);
     }
    }
   }
  }
 }
}

So here it is in action:

Published Thursday, June 30, 2005 7:50 AM by Dan Suceava

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Anonymous said:

One of the reason they left such a bug is probably because they don't recommend nesting GroupBoxes. I've just discovered the bug myself and after finding that it has such an easy workaround like the one with panels - it's not worth to do anything more. In fact - it's good, the bug is there, because it shows something is wrong with your UI. In your case - the topmost GroupBox groups everything on the form and just repeats the form's caption. The lower level one repeats the text already in the treeview. I've checked my eMail client (The Bat!) to see how it handles this kinda settings form and it turned out it just puts the current properties set on a white panel, which looks kind of nice. :]
IMHO - nested groupboxes don't look too well anyways.

Originally posted by:
Filip Skakun aka xyzzer
September 16, 2005 4:40 AM

Leave a Comment

(required) 
(optional)
(required) 
Submit



© 2002 - 2007, Axosoft, LLC. All Rights Reserved. | Privacy
Bug Tracking | Defect Tracking Videos | Help Desk Software