Cyotek Development Bloghttps://devblog.cyotek.com/tag/displayrectangle/atom.xml2010-04-03T12:42:56ZCreating a GroupBox containing an image and a custom display rectangleurn:uuid:d2b83d63-1dfb-4621-a5be-89c0662409fa2010-04-03T12:42:56Z2009-08-10T16:16:28Z<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/groupbox-1.png" class="gallery" title="An example of the GroupBox component in action" ><img src="https://images.cyotek.com/image/devblog/groupbox-1.png" alt="An example of the GroupBox component in action" decoding="async" loading="lazy" /></a><figcaption>An example of the GroupBox component in action</figcaption></figure>
<p>One of our applications required a GroupBox which was more like
the one featured in the Options dialog of Microsoft Outlook
2003. This article describes how to create a custom GroupBox
component which allows this type of user interface, and also a
neat trick on adjusting the client area so that when you drag
controls inside the GroupBox, the handy little margin guides
allow you to position without overlapping the icon.</p>
<p>Add a new <strong>Component</strong> class to your project, and inherit this
from the standard <code>GroupBox</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>ToolboxItem<span class="symbol">(</span><span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="symbol">[</span>DefaultEvent<span class="symbol">(</span><span class="string">&quot;Click&quot;</span><span class="symbol">)</span><span class="symbol">,</span> DefaultProperty<span class="symbol">(</span><span class="string">&quot;Text&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">partial</span> <span class="keyword">class</span> GroupBox <span class="symbol">:</span> System<span class="symbol">.</span>Windows<span class="symbol">.</span>Forms<span class="symbol">.</span>GroupBox
</pre>
</figure>
<p>I personally don't like assigning variables at the same time as
defining them, so I've added a default constructor to assign the
defaults and also to set-up the component as we need to set a
few <em>ControlStyles</em>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> GroupBox<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 _iconMargin <span class="symbol">=</span> <span class="keyword">new</span> Size<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> <span class="number">6</span><span class="symbol">)</span><span class="symbol">;</span>
 _lineColorBottom <span class="symbol">=</span> SystemColors<span class="symbol">.</span>ButtonHighlight<span class="symbol">;</span>
 _lineColorTop <span class="symbol">=</span> SystemColors<span class="symbol">.</span>ButtonShadow<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetStyle<span class="symbol">(</span>ControlStyles<span class="symbol">.</span>DoubleBuffer <span class="symbol">|</span> ControlStyles<span class="symbol">.</span>AllPaintingInWmPaint <span class="symbol">|</span> ControlStyles<span class="symbol">.</span>ResizeRedraw <span class="symbol">|</span>
 ControlStyles<span class="symbol">.</span>UserPaint <span class="symbol">|</span> ControlStyles<span class="symbol">.</span>SupportsTransparentBackColor<span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CreateResources<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Although this is a simple component, we need at the minimum an
<code>Image</code> property to specify the image. We're also adding color
properties in case we decide to use the component in a
non-standard interface later on.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> Size _iconMargin<span class="symbol">;</span>
<span class="keyword">private</span> Image _image<span class="symbol">;</span>
<span class="keyword">private</span> Color _lineColorBottom<span class="symbol">;</span>
<span class="keyword">private</span> Color _lineColorTop<span class="symbol">;</span>

<span class="symbol">[</span>Category<span class="symbol">(</span><span class="string">&quot;Appearance&quot;</span><span class="symbol">)</span><span class="symbol">,</span> DefaultValue<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>Size<span class="symbol">)</span><span class="symbol">,</span> <span class="string">&quot;0, 6&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> Size IconMargin
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _iconMargin<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 _iconMargin <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="symbol">[</span>Category<span class="symbol">(</span><span class="string">&quot;Appearance&quot;</span><span class="symbol">)</span><span class="symbol">,</span> DefaultValue<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>Image<span class="symbol">)</span><span class="symbol">,</span> <span class="string">&quot;&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> Image Image
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _image<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 _image <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="symbol">[</span>Category<span class="symbol">(</span><span class="string">&quot;Appearance&quot;</span><span class="symbol">)</span><span class="symbol">,</span> DefaultValue<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>Color<span class="symbol">)</span><span class="symbol">,</span> <span class="string">&quot;ButtonHighlight&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> Color LineColorBottom
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _lineColorBottom<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 _lineColorBottom <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>CreateResources<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="symbol">[</span>Category<span class="symbol">(</span><span class="string">&quot;Appearance&quot;</span><span class="symbol">)</span><span class="symbol">,</span> DefaultValue<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>Color<span class="symbol">)</span><span class="symbol">,</span> <span class="string">&quot;ButtonShadow&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> Color LineColorTop
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _lineColorTop<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 _lineColorTop <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>CreateResources<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="symbol">[</span>DefaultValue<span class="symbol">(</span><span class="string">&quot;&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">string</span> Text
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">base</span><span class="symbol">.</span>Text<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>Text <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>If you wanted you could create and destroy required GDI objects
every time the control is painted, but in this example I've
opted to create them once for the lifetime of the control.
Therefore I've added <code>CreateResources</code> and <code>CleanUpResources</code> to
create and destroy these. Although not demonstrated in this
in-line listing, <code>CleanUpResources</code> is also called from the
components <code>Dispose</code> method. You'll also notice
<code>CreateResources</code> is called whenever a property value changes,
and that it first releases resources in use.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> CleanUpResources<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_topPen <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _topPen<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_bottomPen <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _bottomPen<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_textBrush <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _textBrush<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> CreateResources<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CleanUpResources<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 _topPen <span class="symbol">=</span> <span class="keyword">new</span> Pen<span class="symbol">(</span>_lineColorTop<span class="symbol">)</span><span class="symbol">;</span>
 _bottomPen <span class="symbol">=</span> <span class="keyword">new</span> Pen<span class="symbol">(</span>_lineColorBottom<span class="symbol">)</span><span class="symbol">;</span>
 _textBrush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ForeColor<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Now that all the initialization is performed, we're going to add
our drawing routine which is to simply override the <code>OnPaint</code>
method.</p>
<p>Remember that as we are overriding an existing component, we
should override the base components methods whenever possible -
this means overriding <code>OnPaint</code> and <em>not</em> hooking into the
<code>Paint</code> event.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnPaint<span class="symbol">(</span>PaintEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 SizeF size<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>

 size <span class="symbol">=</span> e<span class="symbol">.</span>Graphics<span class="symbol">.</span>MeasureString<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Text<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Font<span class="symbol">)</span><span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span>size<span class="symbol">.</span>Height <span class="symbol">+</span> <span class="number">3</span><span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">;</span>

 <span class="comment">// draw the header text and line</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawString<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Text<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Font<span class="symbol">,</span> _textBrush<span class="symbol">,</span> <span class="number">1</span><span class="symbol">,</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawLine<span class="symbol">(</span>_topPen<span class="symbol">,</span> size<span class="symbol">.</span>Width <span class="symbol">+</span> <span class="number">3</span><span class="symbol">,</span> y<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Width <span class="symbol">-</span> <span class="number">5</span><span class="symbol">,</span> y<span class="symbol">)</span><span class="symbol">;</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawLine<span class="symbol">(</span>_bottomPen<span class="symbol">,</span> size<span class="symbol">.</span>Width <span class="symbol">+</span> <span class="number">3</span><span class="symbol">,</span> y <span class="symbol">+</span> <span class="number">1</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Width <span class="symbol">-</span> <span class="number">5</span><span class="symbol">,</span> y <span class="symbol">+</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// draw the image</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">(</span>_image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawImage<span class="symbol">(</span>_image<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Left <span class="symbol">+</span> _iconMargin<span class="symbol">.</span>Width<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Top <span class="symbol">+</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>size<span class="symbol">.</span>Height <span class="symbol">+</span> _iconMargin<span class="symbol">.</span>Height<span class="symbol">,</span> _image<span class="symbol">.</span>Width<span class="symbol">,</span> _image<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">//draw a designtime outline</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DesignMode<span class="symbol">)</span>
 <span class="symbol">{</span>
 Pen pen<span class="symbol">;</span>
 pen <span class="symbol">=</span> <span class="keyword">new</span> Pen<span class="symbol">(</span>SystemColors<span class="symbol">.</span>ButtonShadow<span class="symbol">)</span><span class="symbol">;</span>
 pen<span class="symbol">.</span>DashStyle <span class="symbol">=</span> DashStyle<span class="symbol">.</span>Dot<span class="symbol">;</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawRectangle<span class="symbol">(</span>pen<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> Width <span class="symbol">-</span> <span class="number">1</span><span class="symbol">,</span> Height <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 pen<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In the code above you'll also notice a block specifically for
design time. As this control only has borders at the top of the
control, at design time it may not be obvious where the
boundaries of the control are when laying out your interface.
This code adds a dotted outline to the control at design time,
and is ignored at runtime.</p>
<p>Another method we are overriding is <code>OnSystemColorsChanged</code>.
As our default colors are based on system colors, should these
change we need to recreate our objects and repaint the control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnSystemColorsChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnSystemColorsChanged<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CreateResources<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/groupbox-2.png" class="gallery" title="The default client area" ><img src="https://images.cyotek.com/image/devblog/groupbox-2.png" alt="The default client area" decoding="async" loading="lazy" /></a><figcaption>The default client area</figcaption></figure>
<p>The client area of a standard group box accounts for the text
header and the borders. Our component however, needs an
additional offset on the left to account for the icon. If you
try and place controls into the group box, you will see the
snapping guides appear in the &quot;wrong&quot; place.</p>
<p>Fortunately however, it is very easy for us to suggest our own
client area via the <code>DisplayRectangle</code> property. We just
override this and provide a new rectangle which includes
provisions for the width of the image.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">override</span> Rectangle DisplayRectangle
<span class="symbol">{</span>
 <span class="keyword">get</span>
 <span class="symbol">{</span>
 Size clientSize<span class="symbol">;</span>
 <span class="keyword">int</span> fontHeight<span class="symbol">;</span>
 <span class="keyword">int</span> imageSize<span class="symbol">;</span>

 clientSize <span class="symbol">=</span> <span class="keyword">base</span><span class="symbol">.</span>ClientSize<span class="symbol">;</span>
 fontHeight <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Font<span class="symbol">.</span>Height<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 imageSize <span class="symbol">=</span> _iconMargin<span class="symbol">.</span>Width <span class="symbol">+</span> _image<span class="symbol">.</span>Width <span class="symbol">+</span> <span class="number">3</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 imageSize <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>

 <span class="keyword">return</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span><span class="number">3</span> <span class="symbol">+</span> imageSize<span class="symbol">,</span> fontHeight <span class="symbol">+</span> <span class="number">3</span><span class="symbol">,</span> Math<span class="symbol">.</span>Max<span class="symbol">(</span>clientSize<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="symbol">(</span>imageSize <span class="symbol">+</span> <span class="number">6</span><span class="symbol">)</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">,</span> Math<span class="symbol">.</span>Max<span class="symbol">(</span><span class="symbol">(</span>clientSize<span class="symbol">.</span>Height <span class="symbol">-</span> fontHeight<span class="symbol">)</span> <span class="symbol">-</span> <span class="number">6</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/groupbox-3.png" class="gallery" title="The custom client area" ><img src="https://images.cyotek.com/image/devblog/groupbox-3.png" alt="The custom client area" decoding="async" loading="lazy" /></a><figcaption>The custom client area</figcaption></figure>
<p>Now as you can see the snapping guides suggest a suitable left
margin based on the current image width.</p>
<p>You can download the complete source for the <code>GroupBox</code>
component below.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2009-08-10 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/creating-a-groupbox-containing-an-image-and-a-custom-display-rectangle .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com