Cyotek Development Bloghttps://devblog.cyotek.com/tag/winforms/atom.xml2022-03-07T19:21:27ZPainting the borders of a custom control using WM_NCPAINTurn:uuid:ff756232-014e-47c0-bfa6-238d258eeaa82022-03-07T19:21:27Z2022-03-07T19:21:27Z<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/ncpaint-01.png" class="gallery" title="A demonstration program showing borders painted via WM_NCPAINT" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ncpaint-01.png" alt="A demonstration program showing borders painted via WM_NCPAINT" decoding="async" loading="lazy" /></a><figcaption>A demonstration program showing borders painted via WM_NCPAINT</figcaption></figure>
<p>Over the years I've created a number of controls that require
borders. Sometimes, I'll draw the borders manually as part of
the normal user paint sequence. Other times I'll apply the
<code>WS_EX_CLIENTEDGE</code> or <code>WS_BORDER</code> styles and let Windows handle
it for me.</p>
<p>The advantage of the latter approach is that that means there is
nothing I need to do; the borders will automatically paint, and
as they are excluded from the normal client region of the
control, I don't need to account for them when performing my own
painting or positioning of child controls such as scroll bars.</p>
<p>The disadvantage is that these borders will be painted in the
classic Windows 95 style without any theming.</p>
<p>While working on a control recently, I went with applying window
styles for ease, but then decided I wanted to draw themed
borders. This time I decided to try something new, and have
Windows still manage the borders, but I would override its
default painting with my own, using the
<a href="https://docs.microsoft.com/en-us/windows/win32/gdi/wm-ncpaint" rel="external nofollow noopener"><code>WM_NCPAINT</code></a> message.</p>
<h2 id="sidequest-applying-window-styles">Sidequest: Applying window styles</h2>
<p>If your custom controls use <code>UserControl</code> as a base, this
already has a <code>BorderStyle</code> property which creates a non-client
frame. Most of the controls I create don't need the extra
functionality of <code>UserControl</code> and so I mostly inherit from
<code>Control</code>. By default this does not create a frame; the code
below adds a <code>BorderStyle</code> property and sets the appropriate
style when the window handle is created.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> WS_BORDER <span class="symbol">=</span> <span class="number">0x00800000</span><span class="symbol">;</span>

<span class="keyword">const</span> <span class="keyword">int</span> WS_EX_CLIENTEDGE <span class="symbol">=</span> <span class="number">0x00000200</span><span class="symbol">;</span>

<span class="keyword">private</span> BorderStyle _borderStyle<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>
<span class="symbol">[</span>DefaultValue<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>BorderStyle<span class="symbol">)</span><span class="symbol">,</span> <span class="string">&quot;Fixed3D&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> BorderStyle BorderStyle
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">=&gt;</span> _borderStyle<span class="symbol">;</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_borderStyle <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 _borderStyle <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>UpdateStyles<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> CreateParams CreateParams
<span class="symbol">{</span>
 <span class="keyword">get</span>
 <span class="symbol">{</span>
 CreateParams createParams<span class="symbol">;</span>

 createParams <span class="symbol">=</span> <span class="keyword">base</span><span class="symbol">.</span>CreateParams<span class="symbol">;</span>
 createParams<span class="symbol">.</span>ExStyle <span class="symbol">&amp;=</span> <span class="symbol">~</span>WS_EX_CLIENTEDGE<span class="symbol">;</span>
 createParams<span class="symbol">.</span>Style <span class="symbol">&amp;=</span> <span class="symbol">~</span>WS_BORDER<span class="symbol">;</span>

 <span class="keyword">switch</span> <span class="symbol">(</span>_borderStyle<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> BorderStyle<span class="symbol">.</span>Fixed<span class="number">3</span>D<span class="symbol">:</span>
 createParams<span class="symbol">.</span>ExStyle <span class="symbol">|=</span> WS_EX_CLIENTEDGE<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">case</span> BorderStyle<span class="symbol">.</span>FixedSingle<span class="symbol">:</span>
 createParams<span class="symbol">.</span>Style <span class="symbol">|=</span> WS_BORDER<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> createParams<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Here we have a fairly basic property definition, the only aspect
you might not normally see is the call to <code>UpdateStyles</code> - this
will cause the window styles to be reapplied via our overridden
<code>CreateParams</code> method.</p>
<p>The <code>CreateParams</code> property is also something you might not see
or need to use very often, and is used to set the underlying
Win32 attributes of the window (remember that as far as Windows
is concerned, your forms, controls, etc are all &quot;windows&quot;). I'm
using it here to set the border styles, but you could also use
it to specify the name of an existing class such as <code>EDIT</code>,
although that doesn't come up as often - a topic for another
day, perhaps.</p>
<p>In our override we first <em>remove</em> any existing border styles.
There shouldn't be any set, but better to be sure. We then apply
either a basic or an extended style depending on our property
value. And with that done, Windows will create an appropriate
frame and paint it for us, allowing me to move on with the rest
of this article.</p>
<h2 id="introducing-wm_ncpaint">Introducing WM_NCPAINT</h2>
<p>The <code>WM_NCPAINT</code> message is sent to a window when its frame must
be painted. We can intercept this message via the <code>WndProc</code>
method of our control and then perform the desired painting.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> WM_NCPAINT <span class="symbol">=</span> <span class="number">0x0085</span><span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_NCPAINT<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WmNcPaint<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<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> WmNcPaint<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span> 
 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// Just going back to Windows, for now</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="getting-the-device-context">Getting the device context</h3>
<p>Regardless of if we're going to using managed or unmanaged
painting we need to start by getting the device context (DC).
According to the documentation for <code>WM_NCPAINT</code>, I should have
been able to use <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdcex" rel="external nofollow noopener"><code>GetDCEx</code></a> to do this, but in
practice I found it always returned a null handle <a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>.
Normally, I might use <code>GetDC</code> but this returns a DC for the
client, and we need it for the non-client area. For this
technique, I will instead call <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdc" rel="external nofollow noopener"><code>GetWindowDC</code></a> -
the DC returned by this API will allow painting in both the
client and non-client areas. Once we have finished with a DC, it
needs to be released via <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-releasedc" rel="external nofollow noopener"><code>ReleaseDC</code></a>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr GetWindowDC<span class="symbol">(</span>IntPtr hWnd<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> ReleaseDC<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> IntPtr hDc<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> WmNcPaint<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span> 
 IntPtr hdc<span class="symbol">;</span>

 hdc <span class="symbol">=</span> GetWindowDC<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// TODO: Paint something</span>

 ReleaseDC<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> hdc<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="painting-in-a-mostly-managed-way">Painting in a mostly-managed way</h3>
<p>Once we've got our DC, we can begin to paint. The easiest way is
use use the managed API, e.g. the <code>Graphics</code> object. We can
create an instance of a <code>Graphics</code> instance from a Win32 DC via
the static <code>FromHdc</code> method.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> Graphics<span class="symbol">.</span>FromHdc<span class="symbol">(</span>hdc<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// TODO: Paint</span>
 <span class="symbol">}</span>
</pre>
</figure>
<p>If we immediately start to paint here however, we'll run into
two issues - firstly, the DC is for the entire window, so we
could accidentally paint over the client area. This shouldn't
matter too much as client painting will follow but if that
itself is only partial, artefacts may be left behind (plus in my
testing there was obvious flicker). The second issue is that
properties such as <code>Control.Size</code> may not have been update at
the point this message is received and so could return
inaccurate values.</p>
<p>To resolve these issues we need to do a little more work to both
determine the correct client region, and also to exclude this
region from painting.</p>
<p>First, we use <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect" rel="external nofollow noopener"><code>GetClientRect</code></a> to get a
<a href="https://docs.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect" rel="external nofollow noopener"><code>RECT</code></a> describing the client. Next, we will get the
window rectangle via <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect" rel="external nofollow noopener"><code>GetWindowRect</code></a>. Note that
the former always has a location of zero, whilst the latter
includes the position of the window. Also note that unlike the
.NET <code>Rectangle</code> structure, the Win32 rect is comprised of
<em>left</em>, <em>top</em>, <em>right</em> (left + width) and <em>bottom</em> (top +
height) values, <strong>not</strong> the more convenient <em>width</em> and <em>height</em>
you may be used to from working solely with .NET.</p>
<p>With these values in hand, I calculate the position of the
client area with the assumption that the horizontal and vertical
margins are equidistant. Save storing the actual values
retrieved or calculated via <code>WM_NCCALCSIZE</code> (which I will
briefly cover later), I don't actually know how you'd get them
another way<a id="fnref:2" href="#fn:2" class="footnote-ref"><sup>2</sup></a>.</p>
<p>The new painting implementation is a little longer, but more
robust.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">,</span> SetLastError <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> GetClientRect<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">out</span> RECT lpRect<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">,</span> SetLastError <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> GetWindowRect<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">out</span> RECT lpRect<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>StructLayout<span class="symbol">(</span>LayoutKind<span class="symbol">.</span>Sequential<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">struct</span> RECT
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">int</span> left<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> top<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> right<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> bottom<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> WmNcPaint<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span> 
 <span class="keyword">int</span> w<span class="symbol">;</span>
 <span class="keyword">int</span> h<span class="symbol">;</span>
 Rectangle clip<span class="symbol">;</span>
 IntPtr hdc<span class="symbol">;</span>

 GetClientRect<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">out</span> RECT clientRect<span class="symbol">)</span><span class="symbol">;</span>
 GetWindowRect<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">out</span> RECT windowRect<span class="symbol">)</span><span class="symbol">;</span>

 w <span class="symbol">=</span> windowRect<span class="symbol">.</span>right <span class="symbol">-</span> windowRect<span class="symbol">.</span>left<span class="symbol">;</span>
 h <span class="symbol">=</span> windowRect<span class="symbol">.</span>bottom <span class="symbol">-</span> windowRect<span class="symbol">.</span>top<span class="symbol">;</span>

 clip <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span><span class="symbol">(</span>w <span class="symbol">-</span> clientRect<span class="symbol">.</span>right<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">,</span> <span class="symbol">(</span>h <span class="symbol">-</span> clientRect<span class="symbol">.</span>bottom<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">,</span> clientRect<span class="symbol">.</span>right<span class="symbol">,</span> clientRect<span class="symbol">.</span>bottom<span class="symbol">)</span><span class="symbol">;</span>

 hdc <span class="symbol">=</span> GetWindowDC<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> Graphics<span class="symbol">.</span>FromHdc<span class="symbol">(</span>hdc<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 g<span class="symbol">.</span>SetClip<span class="symbol">(</span>clip<span class="symbol">,</span> CombineMode<span class="symbol">.</span>Exclude<span class="symbol">)</span><span class="symbol">;</span>

 g<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>Brushes<span class="symbol">.</span>SeaGreen<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> w<span class="symbol">,</span> h<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 ReleaseDC<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> hdc<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>And with this in place, we now have a control that has a green
border.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/ncpaint-02.png" class="gallery" title="A default paint example, the non-client area is green" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ncpaint-02.png" alt="A default paint example, the non-client area is green" decoding="async" loading="lazy" /></a><figcaption>A default paint example, the non-client area is green</figcaption></figure><h3 id="sidequest-regions">Sidequest - Regions</h3>
<p>In the above code, I calculated the clip region manually.
However, Windows does provide a paint region as part of the
message. The <em>wParam</em> parameter is a handle to an update region.
This doesn't always seem to be the case - the first call always
seems to be <code>1</code> which isn't a valid handle. When it isn't <code>1</code>,
you can use <code>Region.FromHrgn</code> to convert this handle into a
managed object, leaving you with code something similar to the
below.</p>
<p>While trying to work find out what the seemly undocumented <code>1</code>
meant, I came across this <a href="https://stackoverflow.com/a/50135792/148962" rel="external nofollow noopener">Stack Overflow answer</a>
and in turn this <a href="https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/a407591a-4b1e-4adc-ab0b-3c8b3aec3153/the-evil-wmncpaint" rel="external nofollow noopener">MSDN post</a> which first made me
realise why the calls to <code>GetDCEx</code> were failing but also made me
realise I should probably ignore that parameter and continue
doing it the way I was. As a bonus it also pointed me in the
direction of the <code>MapWindowPoints</code> call which may be the missing
piece I needed for offsetting the client rectangle, something to
investigate another day now though. (It also made me question if
I <em>really</em> should be using <code>WM_NCPAINT</code> or if I should just do
it all manually, but I'm halfway through the article now so I
may as well finish it!).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="comment">// this works but needs more investigation and probably shouldn&#39;t be used</span>
<span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> Graphics<span class="symbol">.</span>FromHdc<span class="symbol">(</span>hdc<span class="symbol">)</span><span class="symbol">)</span>
<span class="keyword">using</span> <span class="symbol">(</span>Region region <span class="symbol">=</span> m<span class="symbol">.</span>WParam <span class="symbol">!=</span> <span class="keyword">new</span> IntPtr<span class="symbol">(</span><span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">?</span> Region<span class="symbol">.</span>FromHrgn<span class="symbol">(</span>m<span class="symbol">.</span>WParam<span class="symbol">)</span>
 <span class="symbol">:</span> <span class="keyword">new</span> Region<span class="symbol">(</span>clip<span class="symbol">)</span><span class="symbol">)</span>
<span class="symbol">{</span>
 g<span class="symbol">.</span>SetClip<span class="symbol">(</span>region<span class="symbol">,</span> CombineMode<span class="symbol">.</span>Exclude<span class="symbol">)</span><span class="symbol">;</span>

 g<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>Brushes<span class="symbol">.</span>SeaGreen<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> w<span class="symbol">,</span> h<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="painting-using-the-themes-api">Painting using the themes API</h3>
<p>Although I could probably just use the built in Visual Styles,
using the native theme API is a nice way of demonstrating the
pure unmanaged approach.</p>
<p>It starts of similar to the previous code, except I manipulate
the results of <code>GetWindowRect</code> to be client based, otherwise the
painting would done at the wrong location. In this example, I'm
using the themes for the <code>EDIT</code> control, e.g. a <code>TextBox</code>.</p>
<p>As I don't have a <code>Graphics</code> object to call <code>SetClip</code> on, I call
<a href="https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-excludecliprect" rel="external nofollow noopener"><code>ExcludeClipRect</code></a> instead.</p>
<p>For the actual painting, first I get a handle to the theme via
<a href="https://docs.microsoft.com/en-us/windows/win32/api/uxtheme/nf-uxtheme-openthemedata" rel="external nofollow noopener"><code>OpenThemeData</code></a>. Next I check to see if any the
theme is partially transparent via
<a href="https://docs.microsoft.com/en-us/windows/win32/api/uxtheme/nf-uxtheme-isthemebackgroundpartiallytransparent" rel="external nofollow noopener"><code>IsThemeBackgroundPartiallyTransparent</code></a>
and if so I draw the background via
<a href="https://docs.microsoft.com/en-us/windows/win32/api/uxtheme/nf-uxtheme-drawthemeparentbackground" rel="external nofollow noopener"><code>DrawThemeParentBackground</code></a>. In
this case it isn't transparent, but I suppose it is good
practice to do these checks. After which I draw the theme
background via <a href="https://docs.microsoft.com/en-us/windows/win32/api/uxtheme/nf-uxtheme-drawthemebackground" rel="external nofollow noopener"><code>DrawThemeBackground</code></a>
which will give me my nice themed borders. And to wrap it up, I
close the handle I opened earlier using
<a href="https://docs.microsoft.com/en-us/windows/win32/api/uxtheme/nf-uxtheme-closethemedata" rel="external nofollow noopener"><code>CloseThemeData</code></a>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> EP_EDITTEXT <span class="symbol">=</span> <span class="number">1</span><span class="symbol">;</span>
<span class="keyword">const</span> <span class="keyword">int</span> ETS_NORMAL <span class="symbol">=</span> <span class="number">1</span><span class="symbol">;</span>
<span class="keyword">const</span> <span class="keyword">int</span> ETS_DISABLED <span class="symbol">=</span> <span class="number">4</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;uxtheme.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">int</span> CloseThemeData<span class="symbol">(</span>IntPtr hTheme<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;uxtheme.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">int</span> DrawThemeBackground<span class="symbol">(</span>IntPtr hTheme<span class="symbol">,</span> IntPtr hdc<span class="symbol">,</span> <span class="keyword">int</span> iPartId<span class="symbol">,</span> <span class="keyword">int</span> iStateId<span class="symbol">,</span> <span class="keyword">ref</span> RECT pRect<span class="symbol">,</span> IntPtr pClipRect<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;uxtheme.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">int</span> DrawThemeParentBackground<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> IntPtr hdc<span class="symbol">,</span> <span class="keyword">ref</span> RECT pRect<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;gdi32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">int</span> ExcludeClipRect<span class="symbol">(</span>IntPtr hdc<span class="symbol">,</span> <span class="keyword">int</span> nLeftRect<span class="symbol">,</span> <span class="keyword">int</span> nTopRect<span class="symbol">,</span> <span class="keyword">int</span> nRightRect<span class="symbol">,</span> <span class="keyword">int</span> nBottomRect<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;uxtheme.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">int</span> IsThemeBackgroundPartiallyTransparent<span class="symbol">(</span>IntPtr hTheme<span class="symbol">,</span> <span class="keyword">int</span> iPartId<span class="symbol">,</span> <span class="keyword">int</span> iStateId<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;uxtheme.dll&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Unicode<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr OpenThemeData<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">string</span> classList<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> WmNcPaint<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> w<span class="symbol">;</span>
 <span class="keyword">int</span> h<span class="symbol">;</span>
 IntPtr hdc<span class="symbol">;</span>
 IntPtr hTheme<span class="symbol">;</span>
 <span class="keyword">int</span> partId<span class="symbol">;</span>
 <span class="keyword">int</span> stateId<span class="symbol">;</span>

 GetClientRect<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">out</span> RECT clientRect<span class="symbol">)</span><span class="symbol">;</span>
 GetWindowRect<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">out</span> RECT windowRect<span class="symbol">)</span><span class="symbol">;</span>

 w <span class="symbol">=</span> windowRect<span class="symbol">.</span>right <span class="symbol">-</span> windowRect<span class="symbol">.</span>left<span class="symbol">;</span>
 h <span class="symbol">=</span> windowRect<span class="symbol">.</span>bottom <span class="symbol">-</span> windowRect<span class="symbol">.</span>top<span class="symbol">;</span>

 windowRect<span class="symbol">.</span>right <span class="symbol">=</span> w<span class="symbol">;</span>
 windowRect<span class="symbol">.</span>bottom <span class="symbol">=</span> h<span class="symbol">;</span>
 windowRect<span class="symbol">.</span>left <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 windowRect<span class="symbol">.</span>top <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>

 hdc <span class="symbol">=</span> GetWindowDC<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">)</span><span class="symbol">;</span>
 hTheme <span class="symbol">=</span> OpenThemeData<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="string">&quot;EDIT&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 partId <span class="symbol">=</span> EP_EDITTEXT<span class="symbol">;</span>

 stateId <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Enabled
 <span class="symbol">?</span> ETS_NORMAL
 <span class="symbol">:</span> ETS_DISABLED<span class="symbol">;</span>

 ExcludeClipRect<span class="symbol">(</span>hdc<span class="symbol">,</span> <span class="symbol">(</span>w <span class="symbol">-</span> clientRect<span class="symbol">.</span>right<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">,</span> <span class="symbol">(</span>h <span class="symbol">-</span> clientRect<span class="symbol">.</span>bottom<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">,</span> clientRect<span class="symbol">.</span>right<span class="symbol">,</span> clientRect<span class="symbol">.</span>bottom<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>IsThemeBackgroundPartiallyTransparent<span class="symbol">(</span>hTheme<span class="symbol">,</span> partId<span class="symbol">,</span> stateId<span class="symbol">)</span> <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 DrawThemeParentBackground<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> hdc<span class="symbol">,</span> <span class="keyword">ref</span> windowRect<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 DrawThemeBackground<span class="symbol">(</span>hTheme<span class="symbol">,</span> hdc<span class="symbol">,</span> partId<span class="symbol">,</span> stateId<span class="symbol">,</span> <span class="keyword">ref</span> windowRect<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span><span class="symbol">;</span>

 CloseThemeData<span class="symbol">(</span>hTheme<span class="symbol">)</span><span class="symbol">;</span>

 ReleaseDC<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> hdc<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/ncpaint-03.png" class="gallery" title="An example of painting using the themes API" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ncpaint-03.png" alt="An example of painting using the themes API" decoding="async" loading="lazy" /></a><figcaption>An example of painting using the themes API</figcaption></figure>
<p>Although it is probably much more concise to use the built-in
<code>VisualStyleRenderer</code> with a <code>Graphics</code> instance than the above,
this serves the purpose and will give us a control with a nice
themed border.</p>
<blockquote>
<p>Themes shouldn't be used blindly, they might be disabled by
the operating system or by the current process. You should
always check to see if themes are enabled (for example via
<code>Application.RenderWithVisualStyles</code>) before attempting to
render them, and fall back to another paint mode if not.</p>
</blockquote>
<h2 id="bonus-chatter-the-wm_nccalcsize-message">Bonus Chatter - the WM_NCCALCSIZE message</h2>
<p>When I started this article, I intended to end it with the
preceding section. However, given that themes don't have to
follow expected sizing conventions I thought I ought not do a
half job and should include some details on how you can define
the size of the non-client area yourself.</p>
<p>The <a href="https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize" rel="external nofollow noopener"><code>WM_NCCALCSIZE</code></a> message is sent when the
size and position of a window's client area must be calculated.
If you are just relying on painting over standard borders
without using themes then you may not need to use this message,
but if you are using themes them it is probably better to handle
it manually.</p>
<p>This message is slightly peculiar in my experience as it
contains different data at different times. If <em>wParam</em> is
<strong>TRUE</strong>, then <em>lParam</em> points to a
<a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-nccalcsize_params" rel="external nofollow noopener"><code>NCCALCSIZE_PARAMS</code></a> structure, otherwise it
points to a <code>RECT</code> instead. This makes the code slightly more
complicated, but not excessively.</p>
<blockquote>
<p>In my testing, it seemed a <code>RECT</code> value only happened once
when first created, thereafter a <code>NCCALCSIZE_PARAMS</code> value was
always provided.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> WM_NCCALCSIZE <span class="symbol">=</span> <span class="number">0x0083</span><span class="symbol">;</span>

<span class="symbol">[</span>StructLayout<span class="symbol">(</span>LayoutKind<span class="symbol">.</span>Sequential<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">struct</span> NCCALCSIZE_PARAMS
<span class="symbol">{</span>
 <span class="symbol">[</span>MarshalAs<span class="symbol">(</span>UnmanagedType<span class="symbol">.</span>ByValArray<span class="symbol">,</span> SizeConst <span class="symbol">=</span> <span class="number">3</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> RECT<span class="symbol">[</span><span class="symbol">]</span> rgrc<span class="symbol">;</span>
 <span class="keyword">public</span> WINDOWPOS lppos<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="symbol">[</span>StructLayout<span class="symbol">(</span>LayoutKind<span class="symbol">.</span>Sequential<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">struct</span> WINDOWPOS
<span class="symbol">{</span>
 <span class="keyword">public</span> IntPtr hwnd<span class="symbol">;</span>
 <span class="keyword">public</span> IntPtr hwndInsertAfter<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> y<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> cx<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> cy<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">uint</span> flags<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_NCPAINT<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WmNcPaint<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_NCCALCSIZE<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WmNcCalcSize<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<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> WmNcCalcSize<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>WParam <span class="symbol">==</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span>
 <span class="symbol">{</span>
 RECT clientRect<span class="symbol">;</span>

 clientRect <span class="symbol">=</span> <span class="symbol">(</span>RECT<span class="symbol">)</span>Marshal<span class="symbol">.</span>PtrToStructure<span class="symbol">(</span>m<span class="symbol">.</span>LParam<span class="symbol">,</span> <span class="keyword">typeof</span><span class="symbol">(</span>RECT<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 clientRect<span class="symbol">.</span>left <span class="symbol">+=</span> leftOffset<span class="symbol">;</span>
 clientRect<span class="symbol">.</span>top <span class="symbol">+=</span> topOffset<span class="symbol">;</span>
 clientRect<span class="symbol">.</span>right <span class="symbol">-=</span> rightOffset<span class="symbol">;</span>
 clientRect<span class="symbol">.</span>bottom <span class="symbol">-=</span> bottomOffset<span class="symbol">;</span>

 Marshal<span class="symbol">.</span>StructureToPtr<span class="symbol">(</span>clientRect<span class="symbol">,</span> m<span class="symbol">.</span>LParam<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>

 _clientRect <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>leftOffset<span class="symbol">,</span> topOffset<span class="symbol">,</span> clientRect<span class="symbol">.</span>right <span class="symbol">-</span> clientRect<span class="symbol">.</span>left<span class="symbol">,</span> clientRect<span class="symbol">.</span>bottom <span class="symbol">-</span> clientRect<span class="symbol">.</span>top<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 NCCALCSIZE_PARAMS parameters<span class="symbol">;</span>
 RECT clientRect<span class="symbol">;</span>

 parameters <span class="symbol">=</span> <span class="symbol">(</span>NCCALCSIZE_PARAMS<span class="symbol">)</span>Marshal<span class="symbol">.</span>PtrToStructure<span class="symbol">(</span>m<span class="symbol">.</span>LParam<span class="symbol">,</span> <span class="keyword">typeof</span><span class="symbol">(</span>NCCALCSIZE_PARAMS<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 clientRect <span class="symbol">=</span> parameters<span class="symbol">.</span>rgrc<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">;</span>
 clientRect<span class="symbol">.</span>left <span class="symbol">+=</span> leftOffset<span class="symbol">;</span>
 clientRect<span class="symbol">.</span>top <span class="symbol">+=</span> topOffset<span class="symbol">;</span>
 clientRect<span class="symbol">.</span>right <span class="symbol">-=</span> rightOffset<span class="symbol">;</span>
 clientRect<span class="symbol">.</span>bottom <span class="symbol">-=</span> bottomOffset<span class="symbol">;</span>

 parameters<span class="symbol">.</span>rgrc<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span> <span class="symbol">=</span> clientRect<span class="symbol">;</span>
 Marshal<span class="symbol">.</span>StructureToPtr<span class="symbol">(</span>parameters<span class="symbol">,</span> m<span class="symbol">.</span>LParam<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>

 _clientRect <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>leftOffset<span class="symbol">,</span> topOffset<span class="symbol">,</span> clientRect<span class="symbol">.</span>right <span class="symbol">-</span> clientRect<span class="symbol">.</span>left<span class="symbol">,</span> clientRect<span class="symbol">.</span>bottom <span class="symbol">-</span> clientRect<span class="symbol">.</span>top<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The first thing I do is check if <em>wParam</em> is <code>IntPtr.Zero</code>, and
if so I extract the <code>RECT</code> structure from <em>lParam</em> using
<code>Marshal.PtrToStructure</code>. I then adjust this rectangle
accordingly by increasing <em>left</em> and <em>top</em>, and reducing <em>right</em>
and <em>bottom</em> to account for how large I want the client area to
be. I then store the modified rectangle back into <em>lParam</em> using
<code>Marshal.StructureToPtr</code>. Finally, I also store the new
rectangle for future use in painting.</p>
<p>If <em>wParam</em> is non-zero, I instead extract a <code>NCCALCSIZE_PARAMS</code>
structure from <em>lParam</em>. This has two members, a <code>WINDOWPOS</code>
describing the window position and an array containing 3 <code>RECT</code>
values. The first rectangle in this array is the rectangle is
the one we want to modify as per the previous paragraph. Once
we've replaced the first value in the array with our modified
version, we store the entire structure back into <em>lParam</em>, again
using <code>Marshal.StructureToPtr</code>.</p>
<p>When painting in response to <code>WM_NCPAINT</code>, I use the
<code>_clientRectangle</code> value captured earlier to define the clipping
region.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/ncpaint-04.png" class="gallery" title="Using WM_NCCALCSIZE to specify a non-equidistant client rectangle" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ncpaint-04.png" alt="Using WM_NCCALCSIZE to specify a non-equidistant client rectangle" decoding="async" loading="lazy" /></a><figcaption>Using WM_NCCALCSIZE to specify a non-equidistant client rectangle</figcaption></figure>
<p>Processing this message in order to handle visual styles is
reasonably straight forward - as with painting, we need to open
a DC, open a theme, and then use
<a href="https://docs.microsoft.com/en-us/windows/win32/api/uxtheme/nf-uxtheme-getthemebackgroundcontentrect" rel="external nofollow noopener"><code>GetThemeBackgroundContentRect</code></a>
to get the client rectangle instead of calculating it ourselves.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;uxtheme.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">extern</span> <span class="keyword">static</span> <span class="keyword">int</span> GetThemeBackgroundContentRect<span class="symbol">(</span>IntPtr hTheme<span class="symbol">,</span> IntPtr hdc<span class="symbol">,</span> <span class="keyword">int</span> iPartId<span class="symbol">,</span> <span class="keyword">int</span> iStateId<span class="symbol">,</span> <span class="keyword">ref</span> RECT pBoundingRect<span class="symbol">,</span> <span class="keyword">out</span> RECT pContentRect<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> WmNcCalcSize<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 IntPtr hdc<span class="symbol">;</span>
 IntPtr hTheme<span class="symbol">;</span>
 <span class="keyword">int</span> partId<span class="symbol">;</span>
 <span class="keyword">int</span> stateId<span class="symbol">;</span>

 hdc <span class="symbol">=</span> GetWindowDC<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">)</span><span class="symbol">;</span>
 hTheme <span class="symbol">=</span> OpenThemeData<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="string">&quot;EDIT&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 partId <span class="symbol">=</span> EP_EDITTEXT<span class="symbol">;</span>

 stateId <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Enabled
 <span class="symbol">?</span> ETS_NORMAL
 <span class="symbol">:</span> ETS_DISABLED<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>WParam <span class="symbol">==</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span>
 <span class="symbol">{</span>
 RECT clientRect<span class="symbol">;</span>

 clientRect <span class="symbol">=</span> <span class="symbol">(</span>RECT<span class="symbol">)</span>Marshal<span class="symbol">.</span>PtrToStructure<span class="symbol">(</span>m<span class="symbol">.</span>LParam<span class="symbol">,</span> <span class="keyword">typeof</span><span class="symbol">(</span>RECT<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 GetThemeBackgroundContentRect<span class="symbol">(</span>hTheme<span class="symbol">,</span> hdc<span class="symbol">,</span> partId<span class="symbol">,</span> stateId<span class="symbol">,</span> <span class="keyword">ref</span> clientRect<span class="symbol">,</span> <span class="keyword">out</span> RECT adjustedClientRect<span class="symbol">)</span><span class="symbol">;</span>

 Marshal<span class="symbol">.</span>StructureToPtr<span class="symbol">(</span>adjustedClientRect<span class="symbol">,</span> m<span class="symbol">.</span>LParam<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>

 _clientRect <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>left <span class="symbol">-</span> clientRect<span class="symbol">.</span>left<span class="symbol">,</span> adjustedClientRect<span class="symbol">.</span>top <span class="symbol">-</span> clientRect<span class="symbol">.</span>top<span class="symbol">,</span> <span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>right <span class="symbol">-</span> adjustedClientRect<span class="symbol">.</span>left<span class="symbol">)</span> <span class="symbol">-</span> <span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>left <span class="symbol">-</span> clientRect<span class="symbol">.</span>left<span class="symbol">)</span><span class="symbol">,</span> <span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>bottom <span class="symbol">-</span> adjustedClientRect<span class="symbol">.</span>top<span class="symbol">)</span> <span class="symbol">-</span> <span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>top <span class="symbol">-</span> clientRect<span class="symbol">.</span>top<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 NCCALCSIZE_PARAMS parameters<span class="symbol">;</span>
 RECT clientRect<span class="symbol">;</span>

 parameters <span class="symbol">=</span> <span class="symbol">(</span>NCCALCSIZE_PARAMS<span class="symbol">)</span>Marshal<span class="symbol">.</span>PtrToStructure<span class="symbol">(</span>m<span class="symbol">.</span>LParam<span class="symbol">,</span> <span class="keyword">typeof</span><span class="symbol">(</span>NCCALCSIZE_PARAMS<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 clientRect <span class="symbol">=</span> parameters<span class="symbol">.</span>rgrc<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">;</span>

 GetThemeBackgroundContentRect<span class="symbol">(</span>hTheme<span class="symbol">,</span> hdc<span class="symbol">,</span> partId<span class="symbol">,</span> stateId<span class="symbol">,</span> <span class="keyword">ref</span> clientRect<span class="symbol">,</span> <span class="keyword">out</span> RECT adjustedClientRect<span class="symbol">)</span><span class="symbol">;</span>

 parameters<span class="symbol">.</span>rgrc<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span> <span class="symbol">=</span> adjustedClientRect<span class="symbol">;</span>
 Marshal<span class="symbol">.</span>StructureToPtr<span class="symbol">(</span>parameters<span class="symbol">,</span> m<span class="symbol">.</span>LParam<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>

 _clientRect <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>left <span class="symbol">-</span> clientRect<span class="symbol">.</span>left<span class="symbol">,</span> adjustedClientRect<span class="symbol">.</span>top <span class="symbol">-</span> clientRect<span class="symbol">.</span>top<span class="symbol">,</span> <span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>right <span class="symbol">-</span> adjustedClientRect<span class="symbol">.</span>left<span class="symbol">)</span> <span class="symbol">-</span> <span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>left <span class="symbol">-</span> clientRect<span class="symbol">.</span>left<span class="symbol">)</span><span class="symbol">,</span> <span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>bottom <span class="symbol">-</span> adjustedClientRect<span class="symbol">.</span>top<span class="symbol">)</span> <span class="symbol">-</span> <span class="symbol">(</span>adjustedClientRect<span class="symbol">.</span>top <span class="symbol">-</span> clientRect<span class="symbol">.</span>top<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 CloseThemeData<span class="symbol">(</span>hTheme<span class="symbol">)</span><span class="symbol">;</span>

 ReleaseDC<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> hdc<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/ncpaint-05.png" class="gallery" title="Although the border style is set to the 3D style that is two pixels in size, the painted area is only a single pixel, matching the theme setting" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ncpaint-05.png" alt="Although the border style is set to the 3D style that is two pixels in size, the painted area is only a single pixel, matching the theme setting" decoding="async" loading="lazy" /></a><figcaption>Although the border style is set to the 3D style that is two pixels in size, the painted area is only a single pixel, matching the theme setting</figcaption></figure><h2 id="bonus-chatter-forcing-a-redraw-of-the-non-client-area">Bonus Chatter - Forcing a redraw of the non-client area</h2>
<p>If you use this approach, you will find the <code>WM_NCPAINT</code> message
isn't called all that often - you'll have a lot more <code>WM_PAINT</code>
messages for painting the actual client area. But what happens
if you <em>need</em> to refresh the non-client area? For example, you
might have design time properties that control the appearance,
or perhaps at runtime you want to change the border when the
control has focus.</p>
<p>Built in methods like <code>Control.Invalidate</code> or <code>Control.Refresh</code>
won't have any impact as they only invalidate the client area.</p>
<p>The solution is to use the <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-redrawwindow" rel="external nofollow noopener"><code>RedrawWindow</code></a> API,
which allows us to control which aspects of the window are
refreshed. By using the <code>RDW_FRAME</code> flag, we tell Windows to
send a <code>WM_NCPAINT</code> message as appropriate, and the
<code>RDW_INVALIDATE</code> flag to repaint the window. By not including a
<code>RECT</code> or <code>HRGN</code> describing the area to repaint, it will paint
the full window.</p>
<blockquote>
<p>It would probably be better to try to define a region that
includes the non-client area and excludes the client area, but
this is an area I haven't touched for some time (if ever) so
I'm fuzzy on the details - I may post a follow up if I find it
becomes necessary.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> RDW_FRAME <span class="symbol">=</span> <span class="number">0x400</span><span class="symbol">;</span>
<span class="keyword">const</span> <span class="keyword">int</span> RDW_INVALIDATE <span class="symbol">=</span> <span class="number">0x1</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> RedrawWindow<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> IntPtr lprcUpdate<span class="symbol">,</span> IntPtr hrgnUpdate<span class="symbol">,</span> <span class="keyword">int</span> flags<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> InvalidateAll<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 RedrawWindow<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">,</span> RDW_FRAME <span class="symbol">|</span> RDW_INVALIDATE<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnEnter<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnEnter<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>InvalidateAll<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnLeave<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnLeave<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>InvalidateAll<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>This article turned out a little longer than I was expecting. As
is often the case, I wrote the article in tandem with creating
the demonstration and jumped back and forth whilst exploring
different ideas or encountering issues. As a result of this it's
possible that there are errors in the code embedded within the
article or with the article content itself. If you spot any,
please let me know!</p>
<p>A sample project can be downloaded from our <a href="https://github.com/cyotek/ncpaintDemo" rel="external nofollow noopener">GitHub</a>
page.</p>
<p>Would you follow this approach or would you do something
different?</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>The reason this call was failing appears to be two-fold.
Firstly, I was passing in an invalid HRGN whenever <em>wParam</em>
was <code>1</code>. In addition, when calling in response to
<code>WM_NCPAINT</code> apparently you need to use an undefined
<code>DCX_USESTYLE</code> (<code>0x00010000</code>) flag too.<a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>Potentially we can use
<a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapwindowpoints" rel="external nofollow noopener"><code>MapWindowPoints</code></a> for this, but at the
time of writing this article I have not investigated this
further.<a href="#fnref:2" class="footnote-back-ref">&#8617;</a></p>
</li>
</ol>
</div>

<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/painting-the-borders-of-a-custom-control-using-wm-ncpaint .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a custom single-axis scrolling control in WinFormsurn:uuid:fd062dc5-b1d0-4bb7-a436-a5441249e2542020-12-28T13:22:50Z2020-12-28T13:22:50Z<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/scroll-demo-many-items.png" class="gallery" title="Neither hot nor cold, but just right" ><img src="https://images.cyotek.com/image/thumbnail/devblog/scroll-demo-many-items.png" alt="Neither hot nor cold, but just right" decoding="async" loading="lazy" /></a><figcaption>Neither hot nor cold, but just right</figcaption></figure>
<p>Over the years, I have written several custom controls that
supporting scrolling. However, almost invariably they have some
flaw that meant scrolling was sub-optimal. For example, in our
<a href="https://www.cyotek.com/cyotek-gif-animator">Gif Animator</a> software, the frame reel can cut off the
last frame. In other programs, scrolling goes beyond visible
items and thus presents an empty control at worst or a
smattering of items at best.</p>
<p>As it seems each control had its own different flaws, I created
a dedicated demonstration program to iron out scrolling issues
in my code, concentrating on single axis scrolling as most of my
controls only scroll in one direction. This article covers the
key points in creating a custom scroll control.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/scroll-demo-under-scroll.png" class="gallery" title="Exhibit A: Here, the scrollbar is as far along as it allows, but the last frame is only partially visible" ><img src="https://images.cyotek.com/image/thumbnail/devblog/scroll-demo-under-scroll.png" alt="Exhibit A: Here, the scrollbar is as far along as it allows, but the last frame is only partially visible" decoding="async" loading="lazy" /></a><figcaption>Exhibit A: Here, the scrollbar is as far along as it allows, but the last frame is only partially visible</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/scroll-demo-over-scroll.png" class="gallery" title="Exhibit B: In this example, the maximum value of the scrollbar allows over scroll for an almost empty display" ><img src="https://images.cyotek.com/image/thumbnail/devblog/scroll-demo-over-scroll.png" alt="Exhibit B: In this example, the maximum value of the scrollbar allows over scroll for an almost empty display" decoding="async" loading="lazy" /></a><figcaption>Exhibit B: In this example, the maximum value of the scrollbar allows over scroll for an almost empty display</figcaption></figure><h2 id="setting-the-scene">Setting the scene</h2>
<p>I'm making the assumption that the scrolling will be of rows of
tiles, either where each tile is the full width of the control
(a list), or where there are multiple columns of a fixed size
that are either related (a grid) or not (a multi column list).</p>
<p>The <code>DemoScrollControl</code> featured in this sample probably isn't
directly usable in your projects but should make an excellent
starting point.</p>
<p>I've added <code>ItemCount</code>, <code>ItemHeight</code> and <code>Columns</code> properties
which can be used to simulate a list or grid. In a real control
you might have <code>Items</code> or <code>Columns</code> collections, but having a
simple count property allows me to simulate different control
types for testing. It is also good for creating virtual lists,
although that is a topic for another post.</p>
<p>The <code>Padding</code> property influences the client area of the control
(i.e. the part where the list contents are drawn) and there is
also a <code>Gap</code> property to control spacing between items, again
mostly for simulation purposes.</p>
<p>I'm using a <code>VScrollBar</code> control to provide the scrolling
interface as this is far simpler than applying the <code>WS_VSCROLL</code>
style and going native.</p>
<p>As this is a demonstration control, it simply paints the index
of each &quot;item&quot;. In addition, this article is about scrolling so
I'm not going to covering painting, hit testing or anything
unrelated to the scrolling aspects. The <a href="https://github.com/cyotek/ScrollDemo" rel="external nofollow noopener">source code
example</a> demonstrates a complete implementation.</p>
<h2 id="defining-the-number-of-rows">Defining the number of rows</h2>
<p>When the contents of control changes, we need to calculate the
number of rows, as this directly influences the scrollbar. For a
list or grid, that would be a simple item count. For a
multi-column list, it would be the number of items divided by
the column count.</p>
<p>We also need to determine how many rows are at least partially
visible in the control, which we use for painting and hit
testing. Finally, the number of <em>fully</em> visible rows is used to
define the page size.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> DefineRows<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_itemCount <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> _columns <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> height<span class="symbol">;</span>

 _rows <span class="symbol">=</span> _itemCount <span class="symbol">/</span> _columns<span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_itemCount <span class="symbol">%</span> _columns <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _rows<span class="symbol">++</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 height <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InnerClient<span class="symbol">.</span>Height<span class="symbol">;</span>

 _fullyVisibleRows <span class="symbol">=</span> height <span class="symbol">/</span> <span class="symbol">(</span>_itemHeight <span class="symbol">+</span> _gap<span class="symbol">)</span><span class="symbol">;</span>
 _visibleRows <span class="symbol">=</span> _fullyVisibleRows<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_fullyVisibleRows <span class="symbol">==</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// always make sure there is at least one row, otherwise you can&#39;t scroll</span>
 _fullyVisibleRows <span class="symbol">=</span> <span class="number">1</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_rows <span class="symbol">&gt;</span> _visibleRows <span class="symbol">&amp;&amp;</span> height <span class="symbol">%</span> <span class="symbol">(</span>_itemHeight <span class="symbol">+</span> _gap<span class="symbol">)</span> <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// account for a partially visible row</span>
 _visibleRows<span class="symbol">++</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>We calculate these values whenever a property changes that could
affect the display, for example <code>Size</code>, <code>Font</code> and <code>Padding</code> for
built-in properties, and <code>ItemCount</code>, <code>ItemHeight</code>, <code>Gap</code> and
<code>Columns</code> from the custom.</p>
<h2 id="updating-the-scrollbar-properties">Updating the scrollbar properties</h2>
<p>With our row count and visible row count defined, we can now
update our scrollbar by setting the <code>Maximum</code> and <code>LargeChange</code>
properties respectively.</p>
<blockquote>
<p>This is one of the mistakes I kept making, as I would always
set <code>LargeChange</code> to be an arbitrary value for the number of
items to scroll, but I think I was getting thrown by the naming
of the property (perhaps it is named LargeChange for
compatibility with ancient VB6?). Remember that the scrollbar
control wraps a Win32 scroll control, and the <code>SCROLLINFO</code>
structure describes <code>LargeChange</code> as <code>Page</code>. Thinking of it in
these terms let me realise I should be setting this to the
number of visible items and solved an overflow issue.</p>
</blockquote>
<p>If all items can fit without the need for scrolling, I disable
and hide the scrollbar.</p>
<p>The <code>SetScrollValue</code> helper function updates the value of a
scrollbar, ensuring that it fits within the minimum and maximum
range. However, it also adjusts the range to be the <code>Maximum</code>
minus the <code>LargeChange</code> which prevents another overflow issue
when using the mouse wheel.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> DefineRows<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_itemCount <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> _columns <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// snip</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_scrollBar <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _scrollBar<span class="symbol">.</span>LargeChange <span class="symbol">=</span> _fullyVisibleRows<span class="symbol">;</span>
 _scrollBar<span class="symbol">.</span>Maximum <span class="symbol">=</span> _rows <span class="symbol">-</span> <span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>SetScrollValue<span class="symbol">(</span>_scrollBar<span class="symbol">.</span>Value<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_scrollBar <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _scrollBar<span class="symbol">.</span>Enabled <span class="symbol">=</span> _rows <span class="symbol">&gt;</span> _fullyVisibleRows<span class="symbol">;</span>
 _scrollBar<span class="symbol">.</span>Visible <span class="symbol">=</span> _rows <span class="symbol">&gt;</span> _fullyVisibleRows<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> SetScrollValue<span class="symbol">(</span><span class="keyword">int</span> value<span class="symbol">)</span>
<span class="symbol">{</span>
 value <span class="symbol">=</span> Math<span class="symbol">.</span>Min<span class="symbol">(</span>value<span class="symbol">,</span> _scrollBar<span class="symbol">.</span>Maximum <span class="symbol">-</span> <span class="symbol">(</span>_scrollBar<span class="symbol">.</span>LargeChange <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>value <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 value <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _scrollBar<span class="symbol">.</span>Value <span class="symbol">=</span> value<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Note that this means the control can host a maximum of
<code>2,147,483,647</code> rows. If you need more than this then you'd
need to rethink all scrollbar interactions, or your entire UI
for that matter... I don't think I'd want to use such an
interface!</p>
<h2 id="setting-the-first-visible-item">Setting the first visible item</h2>
<p>I choose not to use &quot;smooth scrolling&quot; in most of my controls as
it is much easier to always have a partially displayed item at
the bottom than at the top. Being able to get and set the first,
or &quot;top&quot;, item is a core part of making scrolling work.</p>
<p>With the control set up the way it is, the first item is the
current scrollbar position multiplied by the number of columns.
This also means that when <em>setting</em> the first item, we set the
scrollbar position to be the new value divided by the column
count. You only need to do this for multi column lists, for
lists or grids you can simply apply the value as is.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="symbol">[</span>DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">int</span> TopItem
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _topItem<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>value <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 value <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>value <span class="symbol">&gt;</span> _itemCount<span class="symbol">)</span>
 <span class="symbol">{</span>
 value <span class="symbol">=</span> _itemCount<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_topItem <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 _topItem <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_columns <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>SetScrollValue<span class="symbol">(</span>value <span class="symbol">/</span> _columns<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnTopItemChanged<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Knowing the first item allows us to easily perform hit testing
and painting without having to store bounds information.</p>
<h2 id="scrolling-with-the-keyboard">Scrolling with the keyboard</h2>
<p>As the <code>SetScrollValue</code> method keeps the new value within the
range of the scrollbar, this time around I tried something new
and <em>all</em> scroll actions were performed by line count. Usually I
have a special case for the start and end of the list, but if I
tell it to scroll by the negative item count and positive item
count, then I can achieve the same result without special cases.</p>
<p>Due to using the arrow keys for scrolling, first I need to
intercept <code>IsInputKey</code> so that I can tell our control we want to
process them, and I include the other keys I'll use for
scrolling for good measure.</p>
<p>Then, in <code>OnKeyDown</code>, I call our <code>ProcessScrollKeys</code> method
which looks at the incoming key and scrolls the control
accordingly.</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Rows to scroll</th>
</tr>
</thead>
<tbody>
<tr>
<td>Up</td>
<td>-1</td>
</tr>
<tr>
<td>Down</td>
<td>1</td>
</tr>
<tr>
<td>Home</td>
<td>-item count</td>
</tr>
<tr>
<td>End</td>
<td>item count</td>
</tr>
<tr>
<td>Page Up</td>
<td>-visible rows</td>
</tr>
<tr>
<td>Page Down</td>
<td>visible rows</td>
</tr>
</tbody>
</table>
<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">bool</span> IsInputKey<span class="symbol">(</span>Keys keyData<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> keyData <span class="symbol">==</span> Keys<span class="symbol">.</span>Up
 <span class="symbol">||</span> keyData <span class="symbol">==</span> Keys<span class="symbol">.</span>Down
 <span class="symbol">||</span> keyData <span class="symbol">==</span> Keys<span class="symbol">.</span>Home
 <span class="symbol">||</span> keyData <span class="symbol">==</span> Keys<span class="symbol">.</span>End
 <span class="symbol">||</span> keyData <span class="symbol">==</span> Keys<span class="symbol">.</span>PageUp
 <span class="symbol">||</span> keyData <span class="symbol">==</span> Keys<span class="symbol">.</span>PageDown
 <span class="symbol">||</span> <span class="keyword">base</span><span class="symbol">.</span>IsInputKey<span class="symbol">(</span>keyData<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnKeyDown<span class="symbol">(</span>KeyEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnKeyDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>e<span class="symbol">.</span>Handled<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ProcessScrollKeys<span class="symbol">(</span>e<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> ProcessScrollKeys<span class="symbol">(</span>KeyEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">switch</span> <span class="symbol">(</span>e<span class="symbol">.</span>KeyCode<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> Keys<span class="symbol">.</span>Up<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>ScrollControl<span class="symbol">(</span><span class="symbol">-</span><span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">case</span> Keys<span class="symbol">.</span>Down<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>ScrollControl<span class="symbol">(</span><span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">case</span> Keys<span class="symbol">.</span>PageUp<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>ScrollControl<span class="symbol">(</span><span class="symbol">-</span>_fullyVisibleRows<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">case</span> Keys<span class="symbol">.</span>PageDown<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>ScrollControl<span class="symbol">(</span>_fullyVisibleRows<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">case</span> Keys<span class="symbol">.</span>Home<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>ScrollControl<span class="symbol">(</span><span class="symbol">-</span>_itemCount<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">case</span> Keys<span class="symbol">.</span>End<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>ScrollControl<span class="symbol">(</span>_itemCount<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> ScrollControl<span class="symbol">(</span><span class="keyword">int</span> lines<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> value<span class="symbol">;</span>

 <span class="keyword">try</span>
 <span class="symbol">{</span>
 value <span class="symbol">=</span> <span class="keyword">checked</span><span class="symbol">(</span>_scrollBar<span class="symbol">.</span>Value <span class="symbol">+</span> lines<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">catch</span> <span class="symbol">(</span>OverflowException<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>lines <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 value <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 value <span class="symbol">=</span> _itemCount<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetScrollValue<span class="symbol">(</span>value<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Note: While writing this article, I found that jumping to the
end of the list didn't work correctly if the sum of the
current position plus the increment was above <code>int.MaxValue</code>
due to integer overflow. I changed the <code>ScrollControl</code> method
to wrap the increment in a <code>checked</code> statement to throw in
this scenario, then choose a new min/max accordingly. This
should be a pretty rare scenario so you can always remove the
<code>try</code> ... <code>catch</code> block and the <code>checked</code> statement.</p>
</blockquote>
<h2 id="scrolling-with-the-mouse-wheel">Scrolling with the mouse wheel</h2>
<p>In previous controls, I might have done something similar to the
below. While this works, I have received <a href="https://github.com/cyotek/Cyotek.Windows.Forms.ImageBox/issues/8" rel="external nofollow noopener">reports</a>
that this isn't always reliable but it is something I have never
been able to reproduce.</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> OnMouseWheel<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// naive implementation</span>
 <span class="keyword">this</span><span class="symbol">.</span>ScrollControl<span class="symbol">(</span><span class="symbol">-</span><span class="symbol">(</span>e<span class="symbol">.</span>Delta <span class="symbol">/</span> SystemInformation<span class="symbol">.</span>MouseWheelScrollDelta<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnMouseWheel<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>This time, I decided to use a different solution. Martin Mitáš
wrote <a href="https://www.codeproject.com/articles/1042516/custom-controls-in-win-api-scrolling" rel="external nofollow noopener">Custom Controls in Win32 API: Scrolling</a> on
Code Project which has a helper function for accumulating wheel
deltas. Unfortunately I still don't have a mouse that reports a
non-standard delta so I am unable to test that this resolves
those issues, but certainly the code works well with my
hardware.</p>
<p>I converted the original C++ code into a C# class that I can
reuse with other projects.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">static</span> <span class="keyword">class</span> WheelHelper
<span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> <span class="keyword">int</span><span class="symbol">[</span><span class="symbol">]</span> _accumulator <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">int</span><span class="symbol">[</span><span class="number">2</span><span class="symbol">]</span><span class="symbol">;</span>

 <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> <span class="keyword">uint</span><span class="symbol">[</span><span class="symbol">]</span> _lastActivity <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">uint</span><span class="symbol">[</span><span class="number">2</span><span class="symbol">]</span><span class="symbol">;</span>

 <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> <span class="keyword">object</span> _lock <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">object</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">private</span> <span class="keyword">static</span> IntPtr _hwndCurrent <span class="symbol">=</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">;</span>

 <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> WheelScrollLines<span class="symbol">(</span>IntPtr hwnd<span class="symbol">,</span> <span class="keyword">int</span> delta<span class="symbol">,</span> <span class="keyword">int</span> pageSize<span class="symbol">,</span> <span class="keyword">bool</span> isVertical<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">uint</span> now<span class="symbol">;</span>
 <span class="keyword">int</span> scrollSysParam<span class="symbol">;</span>
 <span class="keyword">int</span> linesPerWheelDelta<span class="symbol">;</span>
 <span class="keyword">int</span> dirIndex <span class="symbol">=</span> isVertical <span class="symbol">?</span> <span class="number">0</span> <span class="symbol">:</span> <span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">int</span> lines<span class="symbol">;</span>

 now <span class="symbol">=</span> GetTickCount<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>pageSize <span class="symbol">&lt;</span> <span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 pageSize <span class="symbol">=</span> <span class="number">1</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 scrollSysParam <span class="symbol">=</span> isVertical
 <span class="symbol">?</span> SPI_GETWHEELSCROLLLINES
 <span class="symbol">:</span> SPI_GETWHEELSCROLLCHARS<span class="symbol">;</span>

 linesPerWheelDelta <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>SystemParametersInfo<span class="symbol">(</span>scrollSysParam<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="keyword">ref</span> linesPerWheelDelta<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 linesPerWheelDelta <span class="symbol">=</span> <span class="number">3</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>linesPerWheelDelta <span class="symbol">==</span> WHEEL_PAGESCROLL<span class="symbol">)</span>
 <span class="symbol">{</span>
 linesPerWheelDelta <span class="symbol">=</span> pageSize<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>linesPerWheelDelta <span class="symbol">&gt;</span> pageSize<span class="symbol">)</span>
 <span class="symbol">{</span>
 linesPerWheelDelta <span class="symbol">=</span> pageSize<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">lock</span> <span class="symbol">(</span>_lock<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>hwnd <span class="symbol">!=</span> _hwndCurrent<span class="symbol">)</span>
 <span class="symbol">{</span>
 _hwndCurrent <span class="symbol">=</span> hwnd<span class="symbol">;</span>
 _accumulator<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 _accumulator<span class="symbol">[</span><span class="number">1</span><span class="symbol">]</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>now <span class="symbol">-</span> _lastActivity<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">&gt;</span> SystemInformation<span class="symbol">.</span>DoubleClickTime <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _accumulator<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">(</span>_accumulator<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span> <span class="symbol">==</span> <span class="symbol">(</span>delta <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _accumulator<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>linesPerWheelDelta <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _accumulator<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">+=</span> delta<span class="symbol">;</span>

 lines <span class="symbol">=</span> _accumulator<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">*</span> linesPerWheelDelta <span class="symbol">/</span> WHEEL_DELTA<span class="symbol">;</span>

 _accumulator<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">-=</span> lines <span class="symbol">*</span> WHEEL_DELTA <span class="symbol">/</span> linesPerWheelDelta<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 lines <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 _accumulator<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _lastActivity<span class="symbol">[</span>dirIndex<span class="symbol">]</span> <span class="symbol">=</span> now<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> isVertical <span class="symbol">?</span> <span class="symbol">-</span>lines <span class="symbol">:</span> lines<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Our <code>OnMouseWheel</code> override now asks the helper class how many
lines to scroll by and acts accordingly.</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> OnMouseWheel<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseWheel<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_fullyVisibleRows <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ScrollControl<span class="symbol">(</span>WheelHelper<span class="symbol">.</span>WheelScrollLines<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> e<span class="symbol">.</span>Delta<span class="symbol">,</span> _fullyVisibleRows<span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="mouse-wheel-scrolling-on-older-versions-of-windows">Mouse wheel scrolling on older versions of Windows</h2>
<p>In versions of Windows prior to Windows 10, the <code>WM_MOUSEWHEEL</code>
and <code>WM_MOUSEHWHEEL</code> messages were only sent to the window with
focus. Windows 10 (or at least recent versions of it) changed
this behaviour so the wheel messages would be sent even if the
window didn't have focus.</p>
<p>Therefore, if you want your control to be scrollable via the
mouse wheel regardless of if it has focus or not on older
versions of Windows, you'd need to intercept the messages
yourself. The easiest way of doing this is via a <a href="/post/using-message-filters-in-windows-forms-applications">message
filter</a>.</p>
<p>The following class can be used to intercept the mouse wheel
messages and forward them to the control under the mouse. We do
this by checking for the <code>WM_MOUSEWHEEL</code> and <code>WM_MOUSEHWHEEL</code>
and on receiving these, if the window under the mouse is an
instance of our control we forward the message onto the control
and prevent it from being sent to the original window. For all
other cases, we let the message pass though and be handled
normally.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> MouseWheelMessageFilter<span class="symbol">&lt;</span>T<span class="symbol">&gt;</span> <span class="symbol">:</span> IMessageFilter
 <span class="keyword">where</span> T <span class="symbol">:</span> Control
<span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">bool</span> _enabled<span class="symbol">;</span>

 <span class="keyword">private</span> <span class="keyword">static</span> MouseWheelMessageFilter<span class="symbol">&lt;</span>T<span class="symbol">&gt;</span> _instance<span class="symbol">;</span>

 <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">bool</span> Enabled
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _enabled<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_enabled <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 _enabled <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_enabled<span class="symbol">)</span>
 <span class="symbol">{</span>
 Interlocked<span class="symbol">.</span>CompareExchange<span class="symbol">(</span><span class="keyword">ref</span> _instance<span class="symbol">,</span> <span class="keyword">new</span> MouseWheelMessageFilter<span class="symbol">&lt;</span>T<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">)</span><span class="symbol">;</span>

 Application<span class="symbol">.</span>AddMessageFilter<span class="symbol">(</span>_instance<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>_instance <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Application<span class="symbol">.</span>RemoveMessageFilter<span class="symbol">(</span>_instance<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">bool</span> IMessageFilter<span class="symbol">.</span>PreFilterMessage<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>

 result <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_MOUSEWHEEL <span class="symbol">||</span> m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_MOUSEHWHEEL<span class="symbol">)</span>
 <span class="symbol">{</span>
 IntPtr hControlUnderMouse<span class="symbol">;</span>

 hControlUnderMouse <span class="symbol">=</span> WindowFromPoint<span class="symbol">(</span><span class="keyword">new</span> Point<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>m<span class="symbol">.</span>LParam<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>hControlUnderMouse <span class="symbol">!=</span> m<span class="symbol">.</span>HWnd <span class="symbol">&amp;&amp;</span> Control<span class="symbol">.</span>FromHandle<span class="symbol">(</span>hControlUnderMouse<span class="symbol">)</span> <span class="keyword">is</span> T<span class="symbol">)</span>
 <span class="symbol">{</span>
 SendMessage<span class="symbol">(</span>hControlUnderMouse<span class="symbol">,</span> m<span class="symbol">.</span>Msg<span class="symbol">,</span> m<span class="symbol">.</span>WParam<span class="symbol">,</span> m<span class="symbol">.</span>LParam<span class="symbol">)</span><span class="symbol">;</span>

 result <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>While the above filter will work just as well on newer versions
of Windows (for example the <a href="https://github.com/cyotek/Cyotek.Windows.Forms.ImageBox" rel="external nofollow noopener">ImageBox</a> control still
currently always applies it), there is no point in running extra
code if the OS will handle it, so consider not enabling it for
Windows 10 or above.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">static</span> DemoScrollControl<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 OperatingSystem os<span class="symbol">;</span>

 os <span class="symbol">=</span> Environment<span class="symbol">.</span>OSVersion<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>os<span class="symbol">.</span>Platform <span class="symbol">==</span> PlatformID<span class="symbol">.</span>Win<span class="number">32</span>NT <span class="symbol">&amp;&amp;</span> os<span class="symbol">.</span>Version<span class="symbol">.</span>Major <span class="symbol">&lt;</span> <span class="number">10</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 MouseWheelMessageFilter<span class="symbol">&lt;</span>DemoScrollControl<span class="symbol">&gt;.</span>Enabled <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Important! If the application using your control does not
include a manifest that is explicit about which Windows
versions it supports, it highly likely that Windows will lie
about the version and report an earlier version</p>
</blockquote>
<h2 id="final-words">Final words</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/scroll-demo-columns.png" class="gallery" title="Scrolling with multiple columns" ><img src="https://images.cyotek.com/image/thumbnail/devblog/scroll-demo-columns.png" alt="Scrolling with multiple columns" decoding="async" loading="lazy" /></a><figcaption>Scrolling with multiple columns</figcaption></figure>
<p>Sitting down and properly thinking about the issues in a sample
dedicated purely to that one aspect certainly worked, and
hopefully scroll issues will be a thing of the past in my
programs. Or at least once I update them.</p>
<h2 id="getting-the-source">Getting the source</h2>
<p>The demonstration control is available from our <a href="https://github.com/cyotek/ScrollDemo" rel="external nofollow noopener">GitHub
repository</a>.</p>

<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-custom-single-axis-scrolling-control-in-winforms .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comSetting tab stops in a Windows Forms TextBox controlurn:uuid:6e9805c3-c921-40d9-bd90-b4d7267222f82019-05-25T08:14:37Z2019-05-25T00:44:48Z<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/textbox-tabstops-1b.png" class="gallery" title="An example of a TextBox control using custom tab stops" ><img src="https://images.cyotek.com/image/thumbnail/devblog/textbox-tabstops-1b.png" alt="An example of a TextBox control using custom tab stops" decoding="async" loading="lazy" /></a><figcaption>An example of a TextBox control using custom tab stops</figcaption></figure>
<p>I was adding a Wizard to one of my applications, and the final
screen of this Wizard was a summary of the user's choices. I
wanted the user to be able to copy this to the Clipboard if
required, and so I'd used a <code>TextBox</code> rather than the <code>ListView</code>
I might have otherwise used. However, this presented a minor
issue as I'd chosen to use tabs to delimit the information, and
the varying length of text meant that this wasn't aligned as
expected.</p>
<p>Previously I have &quot;dealt&quot; with this issue by cheating - I'd just
add extra tabs to force everything to line up. However, I do
plan on fully localising this application at some point, not to
mention that even using a different font could potentially
trigger the text to misalign once more. And so I decided to do
it properly this time.</p>
<p>I knew that Win32 <code>Edit</code> controls (of which the Windows Forms
<code>TextBox</code> wraps) supported customising tab stops, but this was
functionality the Framework developers did not expose.
Similarly, the <code>RichTextBox</code> offers the ability to customise tab
stops, but at a selection level and I really couldn't be fussed
with that approach. So I decided to directly invoke the Win32
API to set the tab stops in a <code>TextBox</code> control. How hard could
it be?</p>
<h2 id="introducing-em_settabstops">Introducing EM_SETTABSTOPS</h2>
<p>I already knew in principle how to do this, by using the
<code>SendMessage</code> API call and the <code>EM_SETTABSTOPS</code> message,
although I didn't know the specifics. On checking the
<a href="https://docs.microsoft.com/en-us/windows/desktop/controls/em-settabstops" rel="external nofollow noopener">documentation</a> for the message, it stated that when calling
<code>SendMessage</code> with this message, <code>wParam</code> is used to define how
the tab stops are set, whilst <code>lParam</code> has the tab stop values.
The following operations are available</p>
<table>
<thead>
<tr>
<th>wParam Value</th>
<th>lParam Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0</td>
<td>Resets tab stops to default, which is every 32 dialog units</td>
</tr>
<tr>
<td>1</td>
<td><em>integer</em></td>
<td>Sets all tab stops to be every <em>integer</em> dialog units</td>
</tr>
<tr>
<td>2 or more</td>
<td><em>integer[]</em></td>
<td>Sets custom tab stops (in dialog units) using each value in <em>integer[]</em>. Although not explicitly documented, the last value in the array is used for any additional tabs the user enters into the control</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Incidentally, there's an odd omission. Almost invariably with
the Win32 API, if there's a SET message, there's usually a
corresponding GET as well, e.g. <code>WM_SETTEXT</code> and <code>WM_GETTEXT</code>.
For some reason though, there is no <code>EM_GETTABSTOPS</code> - as far
as I can tell, there isn't a way of getting tab stop
information.</p>
</blockquote>
<h2 id="getting-started">Getting Started</h2>
<p>As the <code>EM_SETTABSTOPS</code> can be called several ways, I'm going to
define 3 different overloads of <code>SendMessage</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">static</span> <span class="keyword">class</span> NativeMethods
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">const</span> <span class="keyword">int</span> EM_SETTABSTOPS <span class="symbol">=</span> <span class="number">0x00CB</span><span class="symbol">;</span>

 <span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Auto<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">int</span> SendMessage<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">int</span> msg<span class="symbol">,</span> <span class="keyword">int</span> wParam<span class="symbol">,</span> <span class="keyword">int</span> lParam<span class="symbol">)</span><span class="symbol">;</span>

 <span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Auto<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">int</span> SendMessage<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">int</span> msg<span class="symbol">,</span> <span class="keyword">int</span> wParam<span class="symbol">,</span> <span class="keyword">int</span><span class="symbol">[</span><span class="symbol">]</span> lParam<span class="symbol">)</span><span class="symbol">;</span>

 <span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Auto<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">int</span> SendMessage<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">int</span> msg<span class="symbol">,</span> <span class="keyword">int</span> wParam<span class="symbol">,</span> <span class="keyword">ref</span> <span class="keyword">int</span> lParam<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>The documentation for the message states that the
<code>EM_SETTABSTOPS</code> message doesn't cause the <code>Edit</code> control to
be refreshed, although in my testing this didn't seem to be
the case - the control was always repainted. My assumption is
the <code>TextBox</code> control is refreshing itself when receiving
certain messages, however I have chosen to also explicitly
request a repaint by calling <code>Invalidate</code>.</p>
</blockquote>
<h2 id="setting-all-tab-stops-to-be-the-same-fixed-value">Setting all tab stops to be the same fixed value</h2>
<p>If you want all tab stops to be the same fixed value with no
variations, you send <code>EM_SETTABSTOPS</code> with <code>wParam</code> set to <code>1</code>
and <code>lParam</code> to the new tab size.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
NativeMethods<span class="symbol">.</span>SendMessage<span class="symbol">(</span>textBox<span class="symbol">.</span>Handle<span class="symbol">,</span> NativeMethods<span class="symbol">.</span>EM_SETTABSTOPS<span class="symbol">,</span> <span class="number">1</span><span class="symbol">,</span> tabSize<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="setting-custom-tab-stops">Setting custom tab stops</h2>
<p>To specify tab stops of different sizes, you send
<code>EM_SETTABSTOPS</code> with <code>wParam</code> with a value greater than one,
and <code>lParam</code> with an array of tab stop positions.</p>
<blockquote>
<p>Although the documentation states that this should be greater
than one, I observed (on Windows 10 1809) that unless <code>wParam</code>
was equal to the length of the array I was passing in, the
control didn't behave exactly as expected.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
NativeMethods<span class="symbol">.</span>SendMessage<span class="symbol">(</span>textBox<span class="symbol">.</span>Handle<span class="symbol">,</span> NativeMethods<span class="symbol">.</span>EM_SETTABSTOPS<span class="symbol">,</span> tabStops<span class="symbol">.</span>Length<span class="symbol">,</span> tabStops<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<blockquote>
<p>Important! The array of tab stops is specified as absolute
positions, not the size of each stop, e.g each item in the
array should be the sum of all previous entries plus the size
of the tab stop. So for example, if you wanted tab sizes of
<code>100</code>, <code>60</code> and <code>60</code> dialog units, the values to send would be
<code>100</code>, <code>160</code> and <code>220</code>.</p>
</blockquote>
<h2 id="resetting-tab-stops">Resetting tab stops</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/textbox-tabstops-1a.png" class="gallery" title="An example of a TextBox control using default tab stops" ><img src="https://images.cyotek.com/image/thumbnail/devblog/textbox-tabstops-1a.png" alt="An example of a TextBox control using default tab stops" decoding="async" loading="lazy" /></a><figcaption>An example of a TextBox control using default tab stops</figcaption></figure>
<p>To reset tab stops, we send the <code>EM_SETTABSTOPS</code> message to our
<code>TextBox</code> with a <code>wParam</code> value of <code>0</code>. <code>lParam</code> is unused in
this case so I send <code>0</code> as well.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
NativeMethods<span class="symbol">.</span>SendMessage<span class="symbol">(</span>textBox<span class="symbol">.</span>Handle<span class="symbol">,</span> NativeMethods<span class="symbol">.</span>EM_SETTABSTOPS<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="introducing-dialog-units">Introducing Dialog Units</h2>
<p>I mentioned above that tab stops are expressed in terms of
<em>dialog units.</em> But what are these? I certainly wasn't familiar
with them, pretty much all API calls I've ever used express
these types of values in plain pixels.</p>
<p>I can't actually find a dedicated topic in Microsoft's
documentation, but essentially a dialog unit is a device
independent way of specifying position and size information for
dialog controls. I believe they are mostly used in resource
templates, but as these are generally the province of C++
applications they aren't actually something I've used before.</p>
<p>As to the actual values of a dialog unit, they are equal to the
average width, in pixels, of the characters in the font used by
the dialog; the vertical base unit is equal to the height, in
pixels, of the font.</p>
<p>There are also a set of base units, which are the same
calculations but for the system font. I wonder how many modern
Windows developers have seen the system font, given it hasn't
been in widespread used since Windows 3.0!</p>
<h2 id="converting-from-dialog-unit-to-pixels">Converting from Dialog Unit to Pixels</h2>
<p>In order to convert from dialog units to pixels, you are
supposed to be able to use the <a href="https://docs.microsoft.com/en-gb/windows/desktop/api/winuser/nf-winuser-mapdialogrect" rel="external nofollow noopener"><code>MapDialogRect</code></a> function, by
passing in the dialog handle and a <code>RECT</code> structure populated
with the values to convert. The function will modify the <code>RECT</code>
with the converted values, at least in theory. Unfortunately
this is where things get complicated as the documentation states
<em>this function accepts only handles returned by one of the
dialog box creation functions; handles for other windows are not
valid.</em> However, as Windows Forms doesn't use the dialog
functions for creating windows, I was not able to get this API
call to perform a conversion.</p>
<p>The <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getdialogbaseunits" rel="external nofollow noopener">documentation</a> for <code>GetDialogBaseUnits</code> notes that you
can use <code>GetTextMetrics</code> to calculate the values for a given
font. I'd already <a href="/post/retrieving-font-and-text-metrics-using-csharp">written</a> about this some time ago and so I
did test this but the results were inconclusive, so I wonder if
<code>MapDialogRect</code> is doing additional actions rather than just
returning the average.</p>
<p>(In a move that doesn't surprise me in the slightest given my
experiences with this functionality so far, there isn't a
function to take pixel values and convert them into dialog
units.)</p>
<p>Fortunately, it doesn't really matter<sup>1</sup> as I suppose
you don't really want pixel perfect tab stops. I certainly
didn't, I just wanted rough values so &quot;columns&quot; would line up,
and we can do a naive characters to dialog units conversion by
multiplying the number of characters by 4. Not perfect, but good
enough.</p>
<h2 id="auto-detecting-tab-stops">Auto detecting tab stops</h2>
<figure class="screenshot" ><a href="https://www.cyotek.com/files/articleimages/textbox-tabstops-1c.png" class="gallery" title="An example of a TextBox control where tab stops have been auto-detected based on the contents of the control" ><img src="https://www.cyotek.com/files/articleimages/textbox-tabstops-1c-thumbnail.png" alt="An example of a TextBox control where tab stops have been auto-detected based on the contents of the control" decoding="async" loading="lazy" /></a><figcaption>An example of a TextBox control where tab stops have been auto-detected based on the contents of the control</figcaption></figure>
<p>I mentioned in the article preamble that I wanted to reformat
summary text so that columns would align. Below is a function
that will calculate rough tab stop positions based on a text
string.</p>
<blockquote>
<p>Note: This is rough and ready code and is doing a lot of
string manipulation via <code>string.Split</code> so isn't very efficient
at all. Originally I was going to count characters from tabs
to avoid any type of string processing at all, but I've more
than ran out of the time I'd allocated for this article.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span><span class="symbol">[</span><span class="symbol">]</span> AutoDetectTabStops<span class="symbol">(</span><span class="keyword">string</span> text<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span><span class="symbol">[</span><span class="symbol">]</span> result<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>text<span class="symbol">)</span> <span class="symbol">&amp;&amp;</span> text<span class="symbol">.</span>IndexOf<span class="symbol">(</span><span class="string">&#39;\t&#39;</span><span class="symbol">)</span> <span class="symbol">!=</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 List<span class="symbol">&lt;</span><span class="keyword">int</span><span class="symbol">&gt;</span> tabStops<span class="symbol">;</span>
 <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> lines<span class="symbol">;</span>

 tabStops <span class="symbol">=</span> <span class="keyword">new</span> List<span class="symbol">&lt;</span><span class="keyword">int</span><span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 lines <span class="symbol">=</span> text<span class="symbol">.</span>Split<span class="symbol">(</span>_lineSeparators<span class="symbol">,</span> StringSplitOptions<span class="symbol">.</span>RemoveEmptyEntries<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> lines<span class="symbol">.</span>Length<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">string</span><span class="symbol">[</span><span class="symbol">]</span> cells<span class="symbol">;</span>

 cells <span class="symbol">=</span> lines<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">.</span>Split<span class="symbol">(</span>_cellSeparators<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> j <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> j <span class="symbol">&lt;</span> cells<span class="symbol">.</span>Length<span class="symbol">;</span> j<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> estimatedDialogUnits<span class="symbol">;</span>

 estimatedDialogUnits <span class="symbol">=</span> cells<span class="symbol">[</span>j<span class="symbol">]</span><span class="symbol">.</span>Length <span class="symbol">*</span> <span class="number">4</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>tabStops<span class="symbol">.</span>Count <span class="symbol">&lt;=</span> j<span class="symbol">)</span>
 <span class="symbol">{</span>
 tabStops<span class="symbol">.</span>Add<span class="symbol">(</span>estimatedDialogUnits<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>estimatedDialogUnits <span class="symbol">&gt;</span> tabStops<span class="symbol">[</span>j<span class="symbol">]</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 tabStops<span class="symbol">[</span>j<span class="symbol">]</span> <span class="symbol">=</span> estimatedDialogUnits<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">1</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> tabStops<span class="symbol">.</span>Count<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 tabStops<span class="symbol">[</span>i<span class="symbol">]</span> <span class="symbol">+=</span> tabStops<span class="symbol">[</span>i <span class="symbol">-</span> <span class="number">1</span><span class="symbol">]</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 result <span class="symbol">=</span> tabStops<span class="symbol">.</span>ToArray<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> <span class="keyword">new</span><span class="symbol">[</span><span class="symbol">]</span> <span class="symbol">{</span> <span class="number">32</span> <span class="symbol">}</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">bool</span> AutoDetectTabStops<span class="symbol">(</span><span class="keyword">this</span> TextBoxBase textBox<span class="symbol">,</span> <span class="keyword">string</span> text<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>textBox <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException<span class="symbol">(</span>nameof<span class="symbol">(</span>textBox<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> textBox<span class="symbol">.</span>SetTabStops<span class="symbol">(</span>AutoDetectTabStops<span class="symbol">(</span>text<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">bool</span> AutoDetectTabStops<span class="symbol">(</span><span class="keyword">this</span> TextBoxBase textBox<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> textBox<span class="symbol">.</span>AutoDetectTabStops<span class="symbol">(</span>textBox<span class="symbol">.</span>Text<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="demonstration-project">Demonstration project</h2>
<p>As usual, a demonstration program is available for the link
below, including a helper class for adding some useful tab stop
related extension methods to the <code>TextBox</code> and <code>RichTextBox</code>
controls.</p>
<hr />
<p><sup>1.</sup> <small>Also, I lied. I suppose if you were
displaying a ruler for a word processor then you'd very much
want pixel perfect tab stops. I did try doing a basic ruler for
this demonstration, but ended up having to cheat the
calculations as I could get <code>MapDialogRect</code> to work, I didn't
have time to do things like add scrolling support and at the end
of the day it didn't really add much to the demo. </small></p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2019-05-25 - First published</li>
<li>2020-11-22 - 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/setting-tab-stops-in-a-windows-forms-textbox-control .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comUsing message filters in Windows Forms applicationsurn:uuid:b0298816-c2f9-463e-8969-3753c00445a02019-01-01T19:36:52Z2019-01-01T19:36:52Z<p>For a utility application, I wanted to add an item in the system
menu. It's been quite a long time since I last did this (and was
in VB6), so I decided to find some ready made source code. <a href="https://github.com/ygoe/FieldLog/blob/master/LogSubmit/Unclassified/UI/SystemMenu.cs" rel="external nofollow noopener">This
class</a> provides a nice little helper for wrapping the system
menu to add new commands to it, but it requires the owner form
to hook into its window procedure and forward messages on which
I felt was an awkward design.</p>
<p>The code snippets below should illustrate my point - first we
initialise the instance of the <code>SystemMenu</code> class, but in order
for custom commands to be processed we have to override to
override the form's <code>WndProc</code> and pass any messages received
into the <code>SystemMenu</code> class.</p>
<p>Form:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> SystemMenu _menu<span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnLoad<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnLoad<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 _menu <span class="symbol">=</span> <span class="keyword">new</span> SystemMenu<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">)</span><span class="symbol">;</span>
 _menu<span class="symbol">.</span>AddCommand<span class="symbol">(</span><span class="string">&quot;&amp;Defaults...&quot;</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ShowDefaultsDialog<span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>
 _menu<span class="symbol">.</span>AddCommand<span class="symbol">(</span><span class="string">&quot;&amp;Properties...&quot;</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ShowPropertiesDialog<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
 _menu<span class="symbol">.</span>AddCommand<span class="symbol">(</span><span class="string">&quot;&amp;About...&quot;</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ShowAboutDialog<span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_menu <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _menu<span class="symbol">.</span>HandleMessage<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>SystemMenu class:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">void</span> HandleMessage<span class="symbol">(</span><span class="keyword">ref</span> Message msg<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>msg<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_SYSCOMMAND<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnSysCommandMessage<span class="symbol">(</span><span class="keyword">ref</span> msg<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> OnSysCommandMessage<span class="symbol">(</span><span class="keyword">ref</span> Message msg<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">long</span><span class="symbol">)</span>msg<span class="symbol">.</span>WParam <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> <span class="symbol">(</span><span class="keyword">long</span><span class="symbol">)</span>msg<span class="symbol">.</span>WParam <span class="symbol">&lt;=</span> lastId<span class="symbol">)</span>
 <span class="symbol">{</span>
 actions<span class="symbol">[</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>msg<span class="symbol">.</span>WParam <span class="symbol">-</span> <span class="number">1</span><span class="symbol">]</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>This definitely isn't an ideal situation! As <code>WndProc</code> is
protected and there is no equivalent event, perhaps the original
author of this code thought this was the only solution.
Fortunately there is a (little used?) feature of Windows Forms
that can inspect and manipulate source messages at an
application level.</p>
<h2 id="introducing-message-filters">Introducing Message Filters</h2>
<p>The static <code>Application</code> class has the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.application.addmessagefilter?view=netframework-4.7.2" rel="external nofollow noopener"><code>AddMessageFilter</code></a>
and <a href="https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.application.removemessagefilter?view=netframework-4.7.2" rel="external nofollow noopener"><code>RemoveMessageFilter</code></a> methods, both of which accept a
single parameter - an object implementing the
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.imessagefilter?view=netframework-4.7.2" rel="external nofollow noopener"><code>IMessageFilter</code></a> interface. This interface has a single
method, <code>PreFilterMessage</code>, allowing you to inspect a message
and then either allow it to be dispatched or blocked (&quot;eaten&quot;).</p>
<blockquote>
<p>Warning! Caution is advised when dealing with message filters.
The MSDN documentation notes that adding filters can degrade
application performance but you can also adversely effect your
application if you accidentally block messages you shouldn't.</p>
</blockquote>
<h2 id="implementing-a-message-filter">Implementing a message filter</h2>
<p>Lets take the <code>SystemMenu</code> class from the start of the article.
I've stripped out as much as the code as possible to try and
focus only on message filtering.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> SystemMenu
<span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> WM_SYSCOMMAND <span class="symbol">=</span> <span class="number">0x112</span><span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> MF_STRING <span class="symbol">=</span> <span class="number">0x0</span><span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> MF_SEPARATOR <span class="symbol">=</span> <span class="number">0x800</span><span class="symbol">;</span>

 <span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Auto<span class="symbol">,</span> SetLastError <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">extern</span> IntPtr GetSystemMenu<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">bool</span> bRevert<span class="symbol">)</span><span class="symbol">;</span>

 <span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Auto<span class="symbol">,</span> SetLastError <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> AppendMenu<span class="symbol">(</span>IntPtr hMenu<span class="symbol">,</span> <span class="keyword">int</span> uFlags<span class="symbol">,</span> <span class="keyword">int</span> uIDNewItem<span class="symbol">,</span> <span class="keyword">string</span> lpNewItem<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">private</span> Form _owner<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">int</span> _lastId <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="keyword">private</span> List<span class="symbol">&lt;</span>Action<span class="symbol">&gt;</span> _actions <span class="symbol">=</span> <span class="keyword">new</span> List<span class="symbol">&lt;</span>Action<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">public</span> SystemMenu<span class="symbol">(</span>Form owner<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>owner <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException<span class="symbol">(</span>nameof<span class="symbol">(</span>owner<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _owner <span class="symbol">=</span> owner<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">void</span> AddCommand<span class="symbol">(</span><span class="keyword">string</span> text<span class="symbol">,</span> Action action<span class="symbol">,</span> <span class="keyword">bool</span> separatorBeforeCommand<span class="symbol">)</span>
 <span class="symbol">{</span>
 IntPtr systemMenuHandle<span class="symbol">;</span>
 <span class="keyword">int</span> id<span class="symbol">;</span>

 systemMenuHandle <span class="symbol">=</span> GetSystemMenu<span class="symbol">(</span>_owner<span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>

 id <span class="symbol">=</span> <span class="symbol">++</span>_lastId<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>separatorBeforeCommand<span class="symbol">)</span>
 <span class="symbol">{</span>
 AppendMenu<span class="symbol">(</span>systemMenuHandle<span class="symbol">,</span> MF_SEPARATOR<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 AppendMenu<span class="symbol">(</span>systemMenuHandle<span class="symbol">,</span> MF_STRING<span class="symbol">,</span> id<span class="symbol">,</span> text<span class="symbol">)</span><span class="symbol">;</span>

 _actions<span class="symbol">.</span>Add<span class="symbol">(</span>action<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">void</span> HandleMessage<span class="symbol">(</span><span class="keyword">ref</span> Message msg<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>msg<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_SYSCOMMAND<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnSysCommandMessage<span class="symbol">(</span><span class="keyword">ref</span> msg<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> OnSysCommandMessage<span class="symbol">(</span><span class="keyword">ref</span> Message msg<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">long</span><span class="symbol">)</span>msg<span class="symbol">.</span>WParam <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> <span class="symbol">(</span><span class="keyword">long</span><span class="symbol">)</span>msg<span class="symbol">.</span>WParam <span class="symbol">&lt;=</span> lastId<span class="symbol">)</span>
 <span class="symbol">{</span>
 actions<span class="symbol">[</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>msg<span class="symbol">.</span>WParam <span class="symbol">-</span> <span class="number">1</span><span class="symbol">]</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="implementing-imessagefilter">Implementing IMessageFilter</h3>
<p>First of all, we need to implement the interface. I'm choosing
to explicitly implement it as it doesn't need a public surface.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> SystemMenu <span class="symbol">:</span> IMessageFilter
<span class="symbol">{</span>
 <span class="keyword">bool</span> IMessageFilter<span class="symbol">.</span>PreFilterMessage<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">false</span><span class="symbol">;</span> <span class="comment">// allow the message to be dispatched</span>
 <span class="symbol">}</span>
</pre>
</figure>
<p>This empty implementation returns <code>false</code> to ensure we don't eat
any messages. If instead we returned <code>true</code> then our application
would be completely broken - it wouldn't paint and you wouldn't
be able to interact with anything on it.</p>
<p>With that said, a message filter that doesn't do anything is a
bit of a waste, so I'll remove the public <code>HandleMessage</code> method
and wrap its code into <code>PreFilterMessage</code>. I also need to adjust
<code>OnSysCommandMessage</code> to return a result code as well - again,
if you swallow all <code>WM_SYSCOMMAND</code> messages then your custom
actions might work but the default ones like <strong>Close</strong>,
<strong>Maximize</strong>, etc won't - and as this is an <em>application</em> filter
it will break all of your application windows.</p>
<p>Also, I added a check to make sure that the <code>WM_SYSCOMMAND</code>
message is destined for our owner form - there's no point
intercepting it for other forms in our application.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">bool</span> OnSysCommandMessage<span class="symbol">(</span><span class="keyword">ref</span> Message msg<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>
 <span class="keyword">int</span> commandId<span class="symbol">;</span>

 commandId <span class="symbol">=</span> msg<span class="symbol">.</span>WParam<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 result <span class="symbol">=</span> commandId <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> commandId <span class="symbol">&lt;=</span> _lastId<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>result<span class="symbol">)</span>
 <span class="symbol">{</span>
 _actions<span class="symbol">[</span>commandId <span class="symbol">-</span> <span class="number">1</span><span class="symbol">]</span><span class="symbol">.</span>Invoke<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">bool</span> IMessageFilter<span class="symbol">.</span>PreFilterMessage<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_SYSCOMMAND <span class="symbol">&amp;&amp;</span> m<span class="symbol">.</span>HWnd <span class="symbol">==</span> _owner<span class="symbol">.</span>Handle<span class="symbol">)</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>OnSysCommandMessage<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span> <span class="comment">// allow the message to continue being processed</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The message filter is now complete.</p>
<h2 id="installing-the-filter">Installing the filter</h2>
<p>To install the filter we call <code>Application.AddMessageFilter</code> and
pass in our class instance. I choose to do this from the
constructor.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> SystemMenu<span class="symbol">(</span>Form owner<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>owner <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException<span class="symbol">(</span>nameof<span class="symbol">(</span>owner<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _owner <span class="symbol">=</span> owner<span class="symbol">;</span>

 Application<span class="symbol">.</span>AddMessageFilter<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>If we now run the application, we'll find that our <code>SystemMenu</code>
class is now self contained and working perfectly.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/messagefilters-1a.png" class="gallery" title="An example of system menu modifications using a message filter to intercept the WM_SYSCOMMAND message" ><img src="https://images.cyotek.com/image/thumbnail/devblog/messagefilters-1a.png" alt="An example of system menu modifications using a message filter to intercept the WM_SYSCOMMAND message" decoding="async" loading="lazy" /></a><figcaption>An example of system menu modifications using a message filter to intercept the WM_SYSCOMMAND message</figcaption></figure><h2 id="removing-the-filter">Removing the filter</h2>
<p>Once we've finished with the filter we should remove it - in
this example, once the form is closed there isn't much point in
waiting for messages that will never arrive. We could make the
class disposable via the <code>IDisposable</code> interface but as the
<code>Form</code> object has a <code>FormClosed</code> event we can use that and free
the caller of having to anything except &quot;fire and forget&quot;.</p>
<p>Once again, I modify the constructor, this time to wire up the
event, and then supply the event handler itself.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> SystemMenu<span class="symbol">(</span>Form owner<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>owner <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException<span class="symbol">(</span>nameof<span class="symbol">(</span>owner<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _owner <span class="symbol">=</span> owner<span class="symbol">;</span>

 owner<span class="symbol">.</span>FormClosed <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>FormClosedHandler<span class="symbol">;</span>

 Application<span class="symbol">.</span>AddMessageFilter<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> FormClosedHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> FormClosedEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 Application<span class="symbol">.</span>RemoveMessageFilter<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">)</span><span class="symbol">;</span>

 _actions <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>

 _owner<span class="symbol">.</span>FormClosed <span class="symbol">-=</span> <span class="keyword">this</span><span class="symbol">.</span>FormClosedHandler<span class="symbol">;</span>
 _owner <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>As well as removing the handler, I detach the event and free up
objects as well, on the assumption that nothing else is going to
be done with the class.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>Although you probably won't have much call to use them, message
filters can be useful when dealing with certain windows
messages. I originally became aware of them when I needed to
have mouse wheel scrolling working on a control without focus
and now again for this generic system menu class.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2019-01-01 - First published</li>
<li>2020-11-22 - 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/using-message-filters-in-windows-forms-applications .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comDisplaying text in an empty ListBoxurn:uuid:93c73c30-05a2-41e9-8411-f5370adb35732018-04-29T21:41:23Z2018-04-28T14:02:05Z<p>While looking at ways of improving the UI of a dialog in an
application, I wanted to display some status text in a <code>ListBox</code>
control that was empty. The default Windows Forms <code>ListBox</code>
(which uses the underlying native Win32 control) doesn't support
this, but with a little effort we can extend the control.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/listbox-emptytext.gif" class="gallery" title="A demonstration of the sample project" ><img src="https://images.cyotek.com/image/devblog/listbox-emptytext.gif" alt="A demonstration of the sample project" decoding="async" loading="lazy" /></a><figcaption>A demonstration of the sample project</figcaption></figure><h2 id="a-brief-primer-on-painting-in-windows-forms">A brief primer on painting in Windows Forms</h2>
<p>When a <code>Control</code> receives either the <code>WM_PAINT</code> or
<code>WM_ERASEBKGND</code> messages, it will check to see if the
<code>ControlStyles.UserPaint</code> style is set. If set then the
<code>WM_PAINT</code> message will cause the <code>Paint</code> event to be raised,
and for <code>WM_ERASEBKGND</code> the <code>PaintBackground</code> event - but only
if the the <code>AllPaintingInWmPaint</code> style is not set.</p>
<p>For both messages, if the <code>UserPaint</code> style is not set, then the
control will call the default window procedure allowing that to
handle the message.</p>
<p>This is important to note, as for certain controls (such as
<code>ListBox</code> which wrap a native window) the <code>UserPaint</code> style is
not set, meaning the paint events are never raised. If you try
and set the flag yourself, then you will find the paint events
work again - but the native control will stop painting correctly
due to the default window procedure not being called.</p>
<p>Unfortunately, while you can manually call the default window
procedure via the <code>DefWndProc</code> method, you won't have access to
the original message data to pass to it.</p>
<h2 id="capturing-wm_paint">Capturing WM_PAINT</h2>
<p>Based on the above primer, we now know that we can't easily use
<code>OnPaint</code> to provide our custom drawing. Instead, we'll
intercept the <code>WM_PAINT</code> message when it arrives for our control
and initiate painting manually.</p>
<blockquote>
<p>Although the <code>Control</code> class offers many events for easily
hooking into various actions, it isn't possible to hook into
window procedures in this manner. The simplest solution is to
create an inherited class and then override the <code>WndProc</code>
method.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> WM_PAINT <span class="symbol">=</span> <span class="number">15</span><span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// make sure we call existing procedures!</span>
 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_PAINT<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// perform some custom painting</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="painting-our-custom-message">Painting our custom message</h2>
<p>Even though we're very slightly going outside the box to
intercept windows messages, we don't need to actually use any
Win32 calls. Instead we call <code>CreateGraphics</code> to get a
<code>Graphics</code> instance for our control and paint away as we
normally would.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> DrawText<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">==</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>_emptyText<span class="symbol">)</span> <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>DesignMode<span class="symbol">)</span>
 <span class="symbol">{</span>
 TextFormatFlags flags<span class="symbol">;</span>

 flags <span class="symbol">=</span> TextFormatFlags<span class="symbol">.</span>ExpandTabs <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>HorizontalCenter <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>NoPrefix <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>WordBreak <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>WordEllipsis <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>VerticalCenter<span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>CreateGraphics<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 TextRenderer<span class="symbol">.</span>DrawText<span class="symbol">(</span>g<span class="symbol">,</span> _emptyText<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Font<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ForeColor<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>BackColor<span class="symbol">,</span> flags<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/listbox-emptytext-1a.png" class="gallery" title="Displaying a message in an empty ListBox" ><img src="https://images.cyotek.com/image/thumbnail/devblog/listbox-emptytext-1a.png" alt="Displaying a message in an empty ListBox" decoding="async" loading="lazy" /></a><figcaption>Displaying a message in an empty ListBox</figcaption></figure>
<p>In this example it will print the message centred in the middle
of the list with word wrapping enabled.</p>
<h2 id="clearing-up-after-messy-resizing">Clearing up after messy resizing</h2>
<p>There's just one flaw with the above code - as soon as you
resize the control, it will paint the text again without
clearing the existing content, which can result in a bit of a
mess. As I discussed above, Windows uses the <code>WM_ERASEBKGND</code> to
notify a window that it should erase its background and so if we
adjust our <code>WndProc</code> to intercept this message we can clean up
after ourselves.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/listbox-emptytext-1b.png" class="gallery" title="Messy output after resizing the window" ><img src="https://images.cyotek.com/image/thumbnail/devblog/listbox-emptytext-1b.png" alt="Messy output after resizing the window" decoding="async" loading="lazy" /></a><figcaption>Messy output after resizing the window</figcaption></figure><figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> WM_ERASEBKGND <span class="symbol">=</span> <span class="number">20</span><span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_PAINT<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnWmPaint<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_ERASEBKGND <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>ShouldDrawEmptyText<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ClearBackground<span class="symbol">(</span><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> ClearBackground<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>CreateGraphics<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 g<span class="symbol">.</span>Clear<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>BackColor<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p><del>This time I'm simply instructing the control to draw
itself, which will cause the underlying native window to repaint
its background ready for our re-positioned text to be
drawn.</del></p>
<p>In the original posting of this article, I'd accidentally
defined <code>WM_ERASEBKGND</code> as <code>14</code> which is actually
<code>WM_GETTEXTLENGTH</code>. So the example managed to work only by
chance. Calling <code>Invalidate</code> from <code>WM_ERASEBKGND</code> is the wrong
approach as it leads to mass flicker. In the revised version, I
just manually erase the background.</p>
<p>And that is pretty much it, short and sweet - the associated
download includes an updated fully functional demonstration
project.</p>
<h2 id="adding-empty-text-support-to-other-controls">Adding empty text support to other controls</h2>
<p>While this article describes extending the <code>ListBox</code> control, it
should be possible to use in other controls too. For example, I
use the exact same technique to add empty text support to the
<code>ListView</code> control.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2018-04-28 - First published</li>
<li>2020-11-22 - 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/displaying-text-in-an-empty-listbox .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comDisplaying multi-page tiff files using the ImageBox control and C#urn:uuid:3c3b9f09-50a4-4410-9a1e-3cb3543b71ed2016-07-30T08:25:51Z2016-07-30T08:25:51Z<p>Earlier this week I received a support request from a user
wanting to know if it was possible to display multi-page tiff
files using the <code>ImageBox</code> control. As I haven't wrote anything
about this control for a while, it seemed a good opportunity for
a short blog post.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/tiffview-1a.png" class="gallery" title="Viewing pages in a multi-page file" ><img src="https://images.cyotek.com/image/thumbnail/devblog/tiffview-1a.png" alt="Viewing pages in a multi-page file" decoding="async" loading="lazy" /></a><figcaption>Viewing pages in a multi-page file</figcaption></figure><h2 id="getting-the-number-of-pages-in-a-tiff-file">Getting the number of pages in a TIFF file</h2>
<p>One you have obtained an <code>Image</code> instance containing your tiff
graphic, you can use the <code>GetFrameCount</code> method in conjunction
with a predefined <code>FrameDimension</code> object in order to determine
how many pages there are in the image.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">int</span> GetPageCount<span class="symbol">(</span>Image image<span class="symbol">)</span>
<span class="symbol">{</span>
 FrameDimension dimension<span class="symbol">;</span>

 dimension <span class="symbol">=</span> FrameDimension<span class="symbol">.</span>Page<span class="symbol">;</span>

 <span class="keyword">return</span> image<span class="symbol">.</span>GetFrameCount<span class="symbol">(</span>dimension<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>I have tested this code on several images, and even types
which don't support pages (such as standard bitmaps) have
always return a valid value. However, I have no way of knowing
if this will always be the case (I have experienced first hand
differences in how GDI+ handles actions between different
versions of Windows). The <code>Image</code> object does offer a
<code>FrameDimensionsList</code> property which returns a list of GUID's
for the dimensions supported by the image, so you can always
check the contents of this property first if you want to be
extra sure.</p>
</blockquote>
<h2 id="selecting-a-page">Selecting a page</h2>
<p>To change the active page the <code>Image</code> object represents, you can
its <code>SelectActiveFrame</code> method, passing in a <code>FrameDimension</code>
object and the zero-based page index. Again, we can use the
predefined <code>FrameDimension.Page</code> property, similar to the
following</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
image<span class="symbol">.</span>SelectActiveFrame<span class="symbol">(</span>FrameDimension<span class="symbol">.</span>Page<span class="symbol">,</span> page <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>After which, we need to instruct our <code>ImageBox</code> control (or
whatever control we have bound the image to) to repaint to pick
up the new image data.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
imageBox<span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<blockquote>
<p>You don't need to reassign the image (which probably won't
work anyway if the control does an equality check), simply
instructing it to repaint via <code>Invalidate</code> or <code>Refresh</code> ought
to be sufficient.</p>
</blockquote>
<h2 id="a-sample-multi-page-tiff-file">A sample multi-page tiff file</h2>
<p>As multi-page tiffs aren't exactly common images to be found in
plenty on the internet, I've prepared a sample image based on a
Newton's Cradle animation from
<a href="https://en.wikipedia.org/wiki/Newton%27s_cradle" rel="external nofollow noopener">Wikipedia</a>.</p>
<p><a href="https://images.cyotek.com/image/devblog/NewtonsCradle.tif">Download NewtonsCradle.tif</a> (4MB)</p>
<h2 id="short-and-sweet">Short and sweet</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/tiffview-1a.gif" class="gallery" title="The sample application in action" ><img src="https://images.cyotek.com/image/thumbnail/devblog/tiffview-1a.gif" alt="The sample application in action" decoding="async" loading="lazy" /></a><figcaption>The sample application in action</figcaption></figure>
<p>That is all the information we need to create a viewer - you can
download the project shown in the above animation from the links
below.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2016-07-30 - 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/displaying-multi-page-tiff-files-using-the-imagebox-control-and-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comAligning Windows Forms custom controls to text baselines using C#urn:uuid:19cf598d-70e5-4253-bb82-5acd9f4e63d52016-07-21T18:18:00Z2016-07-21T18:18:00Z<p>One of the nice things about the Visual Studio WinForms
designers are the guidelines it draws onto design surfaces,
aiding you in perfectly positioning your controls. These
guidelines are known internally as <a href="https://msdn.microsoft.com/en-us/library/system.windows.forms.design.behavior.snapline(v=vs.110).aspx" rel="external nofollow noopener">snap lines</a>, and by
default each visual component inheriting from <code>Control</code> gets
four of these, representing the values of the control's <code>Margin</code>
property.</p>
<p>A problem arises when you have multiple controls that have
different heights, and contain a display string - in this case
aligning along one edge isn't going to work and will probably
look pretty ugly. Instead, you more than likely want to align
the different controls so that the text appears on the same
line.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/snaplines-1a.png" class="gallery" title="Aligning everything along one edge just doesn't look right" ><img src="https://images.cyotek.com/image/devblog/snaplines-1a.png" alt="Aligning everything along one edge just doesn't look right" decoding="async" loading="lazy" /></a><figcaption>Aligning everything along one edge just doesn't look right</figcaption></figure>
<p>Fortunately for us developers, the designers do include this
functionality - just not by default. After all, while all
controls have a <code>Text</code> property, not all of them use it, and how
could the default designers know where your owner-draw control
is going to paint text?</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/snaplines-1b.png" class="gallery" title="Aligning the controls so all text is at the same level looks much better" ><img src="https://images.cyotek.com/image/devblog/snaplines-1b.png" alt="Aligning the controls so all text is at the same level looks much better" decoding="async" loading="lazy" /></a><figcaption>Aligning the controls so all text is at the same level looks much better</figcaption></figure>
<p>The image above shows a <code>Label</code>, <code>ComboBox</code> and <code>Button</code> control
all aligned along the text baseline (the magenta line). We can
achieve the same thing by creating a custom designer.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/snaplines-custom-v2.gif.png" class="gallery" title="Aligning a custom control with other controls using the text baseline" ><img src="https://images.cyotek.com/image/thumbnail/devblog/snaplines-custom-v2.gif" alt="Aligning a custom control with other controls using the text baseline" decoding="async" loading="lazy" /></a><figcaption>Aligning a custom control with other controls using the text baseline</figcaption></figure><h2 id="creating-the-designer">Creating the designer</h2>
<p>The first thing therefore is to create a new class and inherit
from <code>System.Windows.Forms.Design.ControlDesigner</code>. You may also
need to add a reference to <code>System.Design</code> to your project
(which rules out <em>Client Profile</em> targets).</p>
<blockquote>
<p>.NET conventions generally recommend that you put these types
of classes in a sub-namespace called Design.</p>
</blockquote>
<p>So, assuming I had a control named <code>BetterTextBox</code>, then the
associated designer would probably look similar to the
following.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> System<span class="symbol">.</span>Windows<span class="symbol">.</span>Forms<span class="symbol">.</span>Design<span class="symbol">;</span>

<span class="keyword">namespace</span> DesignerSnapLinesDemo<span class="symbol">.</span>Design
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">class</span> BetterTextBoxDesigner <span class="symbol">:</span> ControlDesigner
 <span class="symbol">{</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>If you use a tool such as Resharper to fill in namespaces,
note that by default it will try and use
<code>System.Web.UI.Design.ControlDesigner</code> which unsurprisingly
won't work for WinForms controls.</p>
</blockquote>
<h2 id="adding-a-snap-line">Adding a snap line</h2>
<p>To add or remove snap lines, we override the <code>SnapLines</code>
property and manipulate the list it returns. There are only a
few snap lines available, the one we want to add is <code>Baseline</code></p>
<p>For the baseline, you'll need to calculate where the control
will draw the text, taking into consideration padding, borders,
text alignments and of course the font. My previous article
<a href="http://www.cyotek.com/blog/retrieving-font-and-text-metrics-using-csharp">retrieving font and text metrics using C#</a> describes how to
do this.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">override</span> IList SnapLines
<span class="symbol">{</span>
 <span class="keyword">get</span>
 <span class="symbol">{</span>
 IList snapLines<span class="symbol">;</span>
 <span class="keyword">int</span> textBaseline<span class="symbol">;</span>
 SnapLine snapLine<span class="symbol">;</span>

 snapLines <span class="symbol">=</span> <span class="keyword">base</span><span class="symbol">.</span>SnapLines<span class="symbol">;</span>
 
 textBaseline <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetTextBaseline<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// Font ascent</span>

 <span class="comment">// TODO: Increase textBaseline by anything else that affects where your text is rendered, such as</span>
 <span class="comment">// * The value of the Padding.Top property</span>
 <span class="comment">// * If your control has a BorderStyle</span>
 <span class="comment">// * If you reposition the text vertically for centering etc</span>
 
 snapLine <span class="symbol">=</span> <span class="keyword">new</span> SnapLine<span class="symbol">(</span>SnapLineType<span class="symbol">.</span>Baseline<span class="symbol">,</span> textBaseline<span class="symbol">,</span> SnapLinePriority<span class="symbol">.</span>Medium<span class="symbol">)</span><span class="symbol">;</span>

 snapLines<span class="symbol">.</span>Add<span class="symbol">(</span>snapLine<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> snapLines<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Note: Resharper seems to think the <code>SnapLines</code> property can
return a null object. At least for the base WinForms
<code>ControlDesigner</code>, this is not true and it will <em>always</em>
return a list containing every possible snapline <em>except</em> for
<code>BaseLine</code></p>
</blockquote>
<h2 id="linking-the-designer-to-your-control">Linking the designer to your control</h2>
<p>You can link your custom control to your designer by decorating
your class with the <code>System.ComponentModel.DesignerAttribute</code>.
If your designer type is in the same assembly as the control (or
is referenced), then you can call it with the direct type as
with the following example.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Designer<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>BetterTextBoxDesigner<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">class</span> BetterTextBox <span class="symbol">:</span> Control
<span class="symbol">{</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>However, if the designer isn't directly available to your
control, all is not lost - the <code>DesignerAttribute</code> can also take
a string value that contains the assembly qualified designer
type name. Visual Studio will then figure out how to load the
type if it can.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Designer<span class="symbol">(</span><span class="string">&quot;DesignerSnapLinesDemo.Design.BetterTextBoxDesigner, DesignerSnapLinesDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">class</span> BetterTextBox <span class="symbol">:</span> Control
<span class="symbol">{</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>After rebuilding the project, you'll find that your control now
uses your designer rather than the default.</p>
<blockquote>
<p>I seem to recall that when using older versions of Visual
Studio once the IDE had loaded my custom designer contained in
a source code project it seemed to cache it. This meant that
if I then changed the designer code and recompiled, it
wouldn't be picked up unless I restarted Visual Studio. I
haven't noticed that happening in VS2015, so either I'm
imagining the whole thing, or it was fixed. Regardless, if you
get odd behaviour in older versions of VS, a restart of the
IDE might be just what you need.</p>
</blockquote>
<p>The following image shows a zoomed version of the
<code>BetterTextbox</code> (which is just a garishly painted demo control
and so is several lies for the price of one) showing all three
controls are perfectly aligned to the magenta <code>BaseLine</code>
guideline.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/snaplines-1c.png" class="gallery" title="Aligning a custom control via its text baseline" ><img src="https://images.cyotek.com/image/devblog/snaplines-1c.png" alt="Aligning a custom control via its text baseline" decoding="async" loading="lazy" /></a><figcaption>Aligning a custom control via its text baseline</figcaption></figure><h2 id="bonus-chatter-locking-down-how-the-control-is-sized">Bonus Chatter: Locking down how the control is sized</h2>
<p>The default <code>ControlDesigner</code> allows controls to be resized
along any edge at will. If your control automatically sets its
height or width to fit its contents, then this behaviour can be
undesirable. By overriding the <a href="https://msdn.microsoft.com/en-us/library/system.windows.forms.design.controldesigner.selectionrules(v=vs.110).aspx" rel="external nofollow noopener"><code>SelectionRules</code></a> property,
you can define how the control can be processed. The following
code snippet shows an example which prevents the control from
being resized vertically, useful for single-line text box style
controls.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">override</span> SelectionRules SelectionRules
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> SelectionRules<span class="symbol">.</span>Visible <span class="symbol">|</span> SelectionRules<span class="symbol">.</span>Moveable <span class="symbol">|</span> SelectionRules<span class="symbol">.</span>LeftSizeable <span class="symbol">|</span> SelectionRules<span class="symbol">.</span>RightSizeable<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="update-history">Update History</h2>
<ul>
<li>2016-07-21 - 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/aligning-windows-forms-custom-controls-to-text-baselines-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comAdding keyboard accelerators and visual cues to a WinForms controlurn:uuid:d0dff781-581a-43c1-ad3b-96180ebc20e32016-06-03T22:18:28Z2016-06-03T22:18:28Z<style>
.syntax {max-height: 25em;}
</style>
<p>Some weeks ago I was trying to make parts of <a href="https://cyotek.com/cyotek-webcopy">WebCopy's</a> UI a little bit simpler via the expedient of hiding some of the more advanced (and consequently less used) options. And to do this, I created a basic toggle panel control. This worked rather nicely, and while I was writing it I also thought I'd write a short article on adding keyboard support to WinForm controls - controls that are mouse only are a particular annoyance of mine.</p>
<h2 id="a-demonstration-control">A demonstration control</h2>
<p>Below is an fairly simple (but functional) button control that works - as long as you're a mouse user. The rest of the article will discuss how to extend the control to more thoroughly support keyboard users, and you what I describe below in your own controls.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/kbd-mouseonly.gif" class="gallery" title="A button control that currently only supports the mouse" ><img src="https://images.cyotek.com/image/thumbnail/devblog/kbd-mouseonly.gif" alt="A button control that currently only supports the mouse" decoding="async" loading="lazy" /></a><figcaption>A button control that currently only supports the mouse</figcaption></figure><figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> Button <span class="symbol">:</span> Control<span class="symbol">,</span> IButtonControl
<span class="symbol">{</span>
 <span class="keyword">#region</span> Constants

 <span class="keyword">private</span> <span class="keyword">const</span> TextFormatFlags _defaultFlags <span class="symbol">=</span> TextFormatFlags<span class="symbol">.</span>NoPadding <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>SingleLine <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>HorizontalCenter <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>VerticalCenter <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>EndEllipsis<span class="symbol">;</span>

 <span class="keyword">private</span> <span class="keyword">bool</span> _isDefault<span class="symbol">;</span>

 <span class="keyword">private</span> ButtonState _state<span class="symbol">;</span>

 <span class="keyword">public</span> Button<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>SetStyle<span class="symbol">(</span>ControlStyles<span class="symbol">.</span>AllPaintingInWmPaint <span class="symbol">|</span> ControlStyles<span class="symbol">.</span>OptimizedDoubleBuffer <span class="symbol">|</span> ControlStyles<span class="symbol">.</span>ResizeRedraw<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>SetStyle<span class="symbol">(</span>ControlStyles<span class="symbol">.</span>StandardDoubleClick<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
 _state <span class="symbol">=</span> ButtonState<span class="symbol">.</span>Normal<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="symbol">[</span>DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">new</span> <span class="keyword">event</span> EventHandler DoubleClick
 <span class="symbol">{</span>
 add <span class="symbol">{</span> <span class="keyword">base</span><span class="symbol">.</span>DoubleClick <span class="symbol">+=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 remove <span class="symbol">{</span> <span class="keyword">base</span><span class="symbol">.</span>DoubleClick <span class="symbol">-=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="symbol">[</span>DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">new</span> <span class="keyword">event</span> MouseEventHandler MouseDoubleClick
 <span class="symbol">{</span>
 add <span class="symbol">{</span> <span class="keyword">base</span><span class="symbol">.</span>MouseDoubleClick <span class="symbol">+=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 remove <span class="symbol">{</span> <span class="keyword">base</span><span class="symbol">.</span>MouseDoubleClick <span class="symbol">-=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnBackColorChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnBackColorChanged<span class="symbol">(</span>e<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="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnEnabledChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnEnabledChanged<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetState<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Enabled <span class="symbol">?</span> ButtonState<span class="symbol">.</span>Normal <span class="symbol">:</span> ButtonState<span class="symbol">.</span>Inactive<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnFontChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnFontChanged<span class="symbol">(</span>e<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="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnForeColorChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnForeColorChanged<span class="symbol">(</span>e<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="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseDown<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetState<span class="symbol">(</span>ButtonState<span class="symbol">.</span>Pushed<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseUp<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseUp<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetState<span class="symbol">(</span>ButtonState<span class="symbol">.</span>Normal<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <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>
 Graphics g<span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnPaint<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 g <span class="symbol">=</span> e<span class="symbol">.</span>Graphics<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>PaintButton<span class="symbol">(</span>g<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>PaintText<span class="symbol">(</span>g<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnTextChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnTextChanged<span class="symbol">(</span>e<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="keyword">private</span> <span class="keyword">void</span> PaintButton<span class="symbol">(</span>Graphics g<span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle bounds<span class="symbol">;</span>

 bounds <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_isDefault<span class="symbol">)</span>
 <span class="symbol">{</span>
 g<span class="symbol">.</span>DrawRectangle<span class="symbol">(</span>SystemPens<span class="symbol">.</span>WindowFrame<span class="symbol">,</span> bounds<span class="symbol">.</span>X<span class="symbol">,</span> bounds<span class="symbol">.</span>Y<span class="symbol">,</span> bounds<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="number">1</span><span class="symbol">,</span> bounds<span class="symbol">.</span>Height <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 bounds<span class="symbol">.</span>Inflate<span class="symbol">(</span><span class="symbol">-</span><span class="number">1</span><span class="symbol">,</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 ControlPaint<span class="symbol">.</span>DrawButton<span class="symbol">(</span>g<span class="symbol">,</span> bounds<span class="symbol">,</span> _state<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> PaintText<span class="symbol">(</span>Graphics g<span class="symbol">)</span>
 <span class="symbol">{</span>
 Color textColor<span class="symbol">;</span>
 Rectangle textBounds<span class="symbol">;</span>
 Size size<span class="symbol">;</span>

 size <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ClientSize<span class="symbol">;</span>
 textColor <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Enabled <span class="symbol">?</span> <span class="keyword">this</span><span class="symbol">.</span>ForeColor <span class="symbol">:</span> SystemColors<span class="symbol">.</span>GrayText<span class="symbol">;</span>
 textBounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span><span class="number">3</span><span class="symbol">,</span> <span class="number">3</span><span class="symbol">,</span> size<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="number">6</span><span class="symbol">,</span> size<span class="symbol">.</span>Height <span class="symbol">-</span> <span class="number">6</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_state <span class="symbol">==</span> ButtonState<span class="symbol">.</span>Pushed<span class="symbol">)</span>
 <span class="symbol">{</span>
 textBounds<span class="symbol">.</span>X<span class="symbol">++</span><span class="symbol">;</span>
 textBounds<span class="symbol">.</span>Y<span class="symbol">++</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 TextRenderer<span class="symbol">.</span>DrawText<span class="symbol">(</span>g<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> textBounds<span class="symbol">,</span> textColor<span class="symbol">,</span> _defaultFlags<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> SetState<span class="symbol">(</span>ButtonState state<span class="symbol">)</span>
 <span class="symbol">{</span>
 _state <span class="symbol">=</span> state<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="keyword">public</span> <span class="keyword">void</span> NotifyDefault<span class="symbol">(</span><span class="keyword">bool</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 _isDefault <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="keyword">public</span> <span class="keyword">void</span> PerformClick<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnClick<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<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;Behavior&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="symbol">[</span>DefaultValue<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>DialogResult<span class="symbol">)</span><span class="symbol">,</span> <span class="string">&quot;None&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> DialogResult DialogResult <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="about-mnemonic-characters">About mnemonic characters</h2>
<p>I'm fairly sure most developers would know about mnemonic
characters / keyboard accelerators, but I'll quickly outline
regardless. When attached to a UI element, the mnemonic
character tells users what key (usually combined with
<kbd>Alt</kbd>) to press in order to activate it. Windows shows
the mnemonic character with an underline, and this is known as
a keyboard cue.</p>
<p>For example, <strong><u>F</u>ile</strong> would mean press
<kbd>Alt</kbd>+<kbd>F</kbd>.</p>
<h2 id="specifying-the-keyboard-accelerator">Specifying the keyboard accelerator</h2>
<p>In Windows programming, you generally use the <code>&amp;</code> character to
denote the mnemonic in a string. So for example, <code>&amp;Demo</code> means
the <code>d</code> character is the mnemonic. If you actually wanted to
display the <code>&amp;</code> character, then you'd just double them up, e.g.
<code>Hello &amp;&amp; Goodbye</code>.</p>
<p>While the underlying Win32 API uses the <code>&amp;</code> character, and most
other platforms such as classic Visual Basic or Windows Forms do
the same, WPF uses the <code>_</code> character instead. Which pretty much
sums up all of my knowledge of WPF in that one little fact.</p>
<h2 id="painting-keyboard-cues">Painting keyboard cues</h2>
<p>If you use<code>TextRenderer.DrawText</code> to render text in your
controls (which produces <a href="https://theartofdev.com/2014/04/21/text-rendering-methods-comparison-or-gdi-vs-gdi-revised/" rel="external nofollow noopener">better output</a> than
<code>Graphics.DrawString</code>) then by default it will render keyboard
cues.</p>
<p>Older versions of Windows used to always render these cues.
However, at some point (with Window 2000 if I remember
correctly) Microsoft changed the rules so that applications
would only render cues after the user had first pressed the
<kbd>Alt</kbd> character. In practice, this means you need to
check to see if cues should be rendered and act accordingly.
There used to be an option to specify if they should always be
shown or not, but that seems to have disappeared with the march
towards dumbing the OS down to mobile-esque levels.</p>
<p>The first order of business then is to update our <code>PaintText</code>
method to include or exclude keyboard cues as necessary.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">const</span> TextFormatFlags _defaultFlags <span class="symbol">=</span> TextFormatFlags<span class="symbol">.</span>NoPadding <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>SingleLine <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>HorizontalCenter <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>VerticalCenter <span class="symbol">|</span> TextFormatFlags<span class="symbol">.</span>EndEllipsis<span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> PaintText<span class="symbol">(</span>Graphics g<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// .. snip ..</span>
 
 TextRenderer<span class="symbol">.</span>DrawText<span class="symbol">(</span>g<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> textBounds<span class="symbol">,</span> textColor<span class="symbol">,</span> _defaultFlags<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p><code>TextRenderer.DrawText</code> is a managed wrapper around the
<a href="https://msdn.microsoft.com/en-us/library/dd162499(v=vs.85).aspx" rel="external nofollow noopener"><code>DrawTextEx</code></a> Win32 API, and most of the members of
<code>TextFormatFlags</code> map to various <code>DT_*</code> constants. (Except for
<code>NoPadding</code>... I really don't know why <code>TextRenderer</code> adds left
and right padding by default but it's really annoying - I always
set <code>NoPadding</code> (when I'm not directly calling GDI via p/invoke)</p>
<p>As I noted the default behaviour is to <em>draw</em> the cues, so we
need to detect when cues should not be displayed and instruct
our paint code to skip them. To determine whether or not to
display keyboard cues, we can check the <code>ShowKeyboardCues</code>
property of the <code>Control</code> class. To stop <code>DrawText</code> from
painting the underline, we use the <code>TextFormatFlags.HidePrefix</code>
flag (<code>DT_HIDEPREFIX</code>).</p>
<p>So we can update our <code>PaintText</code> method accordingly</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> PaintText<span class="symbol">(</span>Graphics g<span class="symbol">)</span>
<span class="symbol">{</span>
 TextFormatFlags flags<span class="symbol">;</span>
 
 <span class="comment">// .. snip ..</span>

 flags <span class="symbol">=</span> _defaultFlags<span class="symbol">;</span>
 
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>ShowKeyboardCues<span class="symbol">)</span>
 <span class="symbol">{</span>
 flags <span class="symbol">|=</span> TextFormatFlags<span class="symbol">.</span>HidePrefix<span class="symbol">;</span>
 <span class="symbol">}</span>

 TextRenderer<span class="symbol">.</span>DrawText<span class="symbol">(</span>g<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> textBounds<span class="symbol">,</span> textColor<span class="symbol">,</span> flags<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Now our button will now hide and show accelerators based on how
the end user is working.</p>
<p>If for some reason you <em>want</em> to use <code>Graphics.DrawString</code>, then
you can use something similar to the below - just set the
<code>HotkeyPrefix</code> property of a <code>StringFormat</code> object to be
<code>HotkeyPrefix.Show</code> or <code>HotkeyPrefix.Hide</code>. Note that the
default <code>StringFormat</code> object <em>doesn't</em> show prefixes, in a nice
contradiction to <code>TextRenderer</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> <span class="symbol">(</span>StringFormat format <span class="symbol">=</span> <span class="keyword">new</span> StringFormat<span class="symbol">(</span>StringFormat<span class="symbol">.</span>GenericDefault<span class="symbol">)</span>
<span class="symbol">{</span>
 HotkeyPrefix <span class="symbol">=</span> HotkeyPrefix<span class="symbol">.</span>Show<span class="symbol">,</span>
 Alignment <span class="symbol">=</span> StringAlignment<span class="symbol">.</span>Center<span class="symbol">,</span>
 LineAlignment <span class="symbol">=</span>StringAlignment<span class="symbol">.</span>Center<span class="symbol">,</span>
 Trimming <span class="symbol">=</span> StringTrimming<span class="symbol">.</span>EllipsisCharacter
<span class="symbol">}</span><span class="symbol">)</span>
<span class="symbol">{</span>
 g<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> SystemBrushes<span class="symbol">.</span>ControlText<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> format<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/kbd-keyboardcue.gif" class="gallery" title="The button control now reacts to keyboard cues" ><img src="https://images.cyotek.com/image/thumbnail/devblog/kbd-keyboardcue.gif" alt="The button control now reacts to keyboard cues" decoding="async" loading="lazy" /></a><figcaption>The button control now reacts to keyboard cues</figcaption></figure>
<p>As the above animation is just a GIF file, there's no audio -
but when I ran that demo, pressing <kbd>Alt</kbd>+<kbd>D</kbd>
triggered a beep sound as there was nothing on the form that
could handle the accelerator.</p>
<h2 id="painting-focus-cues">Painting focus cues</h2>
<p>Focus cues are highlights that show which element has the
keyboard focus. Traditionally Windows would draw a dotted
outline around the text of an element that performs a single
action (such as a button or checkbox), or draws an item using
both a different background and foreground colours for an
element that has multiple items (such as a listbox or a menu).
Normally (for single action controls at least) focus cues only
appear after the <kbd>Tab</kbd> key has been pressed, memory
fails me as to whether this has always been the case or if
Windows use to always show a focus cue.</p>
<p>You can use the <code>Focused</code> property of a <code>Control</code> to determine
if it currently has keyboard focus and the <code>ShowFocusCues</code>
property to see if the focus state should be rendered.</p>
<p>After that, the simplest way of drawing a focus rectangle would
be to use the <code>ControlPaint.DrawFocusRectangle</code>. However, this
draws using fixed colours. Old-school focus rectangles inverted
the pixels by drawing with a dotted XOR pen, meaning you could
erase the focus rectangle by simply drawing it again - this was
great for rubber banding (or dancing ants if you prefer). If you
want <em>that</em> type of effect then you can use the
<a href="https://msdn.microsoft.com/en-us/library/dd162479(v=vs.85).aspx" rel="external nofollow noopener"><code>DrawFocusRect</code></a>
Win32 API.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> PaintButton<span class="symbol">(</span>Graphics g<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// .. snip ..</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ShowFocusCues <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>Focused<span class="symbol">)</span>
 <span class="symbol">{</span>
 bounds<span class="symbol">.</span>Inflate<span class="symbol">(</span><span class="symbol">-</span><span class="number">3</span><span class="symbol">,</span> <span class="symbol">-</span><span class="number">3</span><span class="symbol">)</span><span class="symbol">;</span>

 ControlPaint<span class="symbol">.</span>DrawFocusRectangle<span class="symbol">(</span>g<span class="symbol">,</span> bounds<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/kbd-focuscue.gif" class="gallery" title="The button control showing focus cues as focus is cycled with the tab key" ><img src="https://images.cyotek.com/image/thumbnail/devblog/kbd-focuscue.gif" alt="The button control showing focus cues as focus is cycled with the tab key" decoding="async" loading="lazy" /></a><figcaption>The button control showing focus cues as focus is cycled with the tab key</figcaption></figure>
<p>Notice in the demo above how focus cues and keyboard cues are
independent from each other.</p>
<h2 id="so-about-those-accelerators">So, about those accelerators</h2>
<p>Now that we've covered painting our control to show focus /
keyboard cues as appropriate, it's time to actually handle
accelerators. Once again, the <code>Control</code> class has everything we
need built right into it.</p>
<p>To start with, we override the <code>ProcessMnemonic</code> method. This
method is automatically called by .NET when a user presses an
<kbd>Alt</kbd> key combination and it is up to your component to
determine if it should process it or not. If the component can't
handle the accelerator, then it should return <code>false</code>. If it
can, then it should perform the action and return <code>true</code>. The
method includes a <code>char</code> argument that contains the accelerator
key (e.g. just the character code, not the alt modifier).</p>
<p>So how do you know if your component can handle it? Luckily the
<code>Control</code> class offers a static <code>IsMnemonic</code> method that takes a
<code>char</code> and a <code>string</code> as arguments. It will return <code>true</code> if the
source string contains a mnemonic matching the passed character.
Note that it expects the <code>&amp;</code> character is used to identify the
mnemonic. I assume WPF has a matching version of this method,
but I don't know where.</p>
<p>We can now implement the accelerator handling quite simply using
the following snippet</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">bool</span> ProcessMnemonic<span class="symbol">(</span><span class="keyword">char</span> charCode<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> processed<span class="symbol">;</span>

 processed <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>CanFocus <span class="symbol">&amp;&amp;</span> IsMnemonic<span class="symbol">(</span>charCode<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Text<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>processed<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Focus<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>PerformClick<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> processed<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>We check to make sure the control can be focused in addition to
checking if our control has a match for the incoming mnemonic,
and if both are true then we set focus to the control and raise
the <code>Click</code> event. If you don't need (or want) to set focus to
the control, then you can skip the <code>CanFocus</code> check and <code>Focus</code>
call.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/kbd-accelerator.gif" class="gallery" title="In this final demonstration, we see pressing Alt+D triggering the Click event of the button. Mission accomplished!" ><img src="https://images.cyotek.com/image/thumbnail/devblog/kbd-accelerator.gif" alt="In this final demonstration, we see pressing Alt+D triggering the Click event of the button. Mission accomplished!" decoding="async" loading="lazy" /></a><figcaption>In this final demonstration, we see pressing Alt+D triggering the Click event of the button. Mission accomplished!</figcaption></figure><h2 id="bonus-points-other-keys">Bonus Points: Other Keys</h2>
<p>Some controls accept other keyboard conventions. For example, a
button accepts the <kbd>Enter</kbd> or <kbd>Space</kbd> keys to
click the button (the former acting as an accelerator, the
latter acting as though the mouse were being pressed and
released), combo boxes accept <kbd>F4</kbd> to display drop
downs and so on. If your control mimics any standard controls,
it's always worthwhile adding support for these conventions too.
And don't forget about focus!</p>
<p>For example, in the sample button, I modify <code>OnMouseDown</code> to set
focus to the control if it isn't already set</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> OnMouseDown<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>CanFocus<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Focus<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetState<span class="symbol">(</span>ButtonState<span class="symbol">.</span>Pushed<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>I also add overrides for <code>OnKeyDown</code> and <code>OnKeyUp</code> to mimic the
button being pushed and then released when the user presses and
releases the space bar</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> OnKeyDown<span class="symbol">(</span>KeyEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnKeyDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span><span class="symbol">(</span>e<span class="symbol">.</span>KeyCode <span class="symbol">==</span> Keys<span class="symbol">.</span>Space <span class="symbol">&amp;&amp;</span> e<span class="symbol">.</span>Modifiers <span class="symbol">==</span> Keys<span class="symbol">.</span>None<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>SetState<span class="symbol">(</span>ButtonState<span class="symbol">.</span>Pushed<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnKeyUp<span class="symbol">(</span>KeyEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnKeyUp<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span><span class="symbol">(</span><span class="symbol">(</span>e<span class="symbol">.</span>KeyCode <span class="symbol">&amp;</span> Keys<span class="symbol">.</span>Space<span class="symbol">)</span> <span class="symbol">==</span> Keys<span class="symbol">.</span>Space<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>SetState<span class="symbol">(</span>ButtonState<span class="symbol">.</span>Normal<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>PerformClick<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>However, I'm not adding anything to handle the enter key. This
is because I don't need to - in this example, the <code>Button</code>
control implements the <code>IButtonControl</code> interface and so it's
handled for me without any special actions. For non-button
controls, I would need to explicitly handle enter key presses if
appropriate.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2016-06-03 - 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/adding-keyboard-accelerators-and-visual-cues-to-a-winforms-control .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comHosting a ColorGrid control in a ToolStripurn:uuid:ead00749-49e1-4b1f-bf44-81d222f6c89e2015-02-28T17:49:06Z2015-02-28T17:49:06Z<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/toolstrip-colorgrid-1b.png" class="gallery" title="Displaying a ColorGrid control in the drop down of a ToolStrip" ><img src="https://images.cyotek.com/image/thumbnail/devblog/toolstrip-colorgrid-1b.png" alt="Displaying a ColorGrid control in the drop down of a ToolStrip" decoding="async" loading="lazy" /></a><figcaption>Displaying a ColorGrid control in the drop down of a ToolStrip</figcaption></figure>
<p>The <code>ColorGrid</code> control is a fairly useful control for selecting
from a predefined list of colours. However, it can take up quite
a bit of screen real estate depending on how many colours it
contains. This article describes how you can host a <code>ColorGrid</code>
in a standard <code>ToolStrip</code> control, providing access to both the
<code>ColorGrid</code> and the <code>ColorPickerDialog</code>.</p>
<p>The <code>ToolStrip</code> control makes this surprisingly easy to
accomplish. First, we're going to need a component to host the
<code>ColorGrid</code> which we can ably achieve by inheriting from
<code>ToolStripDropDown</code>. So lets get started!</p>
<h2 id="the-drop-down">The Drop Down</h2>
<p>The <code>ToolStripDropDown</code> class &quot;<em>represents a control that allows
the user to select a single item from a list that is displayed
when the user clicks a ToolStripDropDownButton</em>&quot; and is just
what we need to save use reinventing at least one wheel. This
class will essentially manage the interactions to the
<code>ColorGrid</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">class</span> ToolStripColorPickerDropDown <span class="symbol">:</span> ToolStripDropDown
<span class="symbol">{</span>
 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="symbol">[</span>DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> ColorGrid Host <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">private</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> ToolStripColorPickerDropDown<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Host <span class="symbol">=</span> <span class="keyword">new</span> ColorGrid
 <span class="symbol">{</span>
 AutoSize <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">,</span>
 Columns <span class="symbol">=</span> <span class="number">10</span><span class="symbol">,</span>
 Palette <span class="symbol">=</span> ColorPalette<span class="symbol">.</span>Office<span class="number">2010</span>
 <span class="symbol">}</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Host<span class="symbol">.</span>MouseClick <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>HostMouseClickHandler<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Host<span class="symbol">.</span>KeyDown <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>HostKeyDownHandler<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">new</span> ToolStripControlHost<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Host<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>When the <code>ToolStripColorPickerDropDown</code> is created we
automatically create a <code>ColorGrid</code> control, set some default
properties and then add it to the <code>ToolStripItemCollection</code> of
the <code>ToolStripDropDown</code>.</p>
<p>If we simply bound the <code>ColorChanged</code> event of the <code>ColorGrid</code>
to select a colour, then you'd probably have great difficulty in
using the control properly - keyboard support is immediately out
of the question, and even some mouse support would be affected.</p>
<p>For this reason, I'm binding the <code>MouseClick</code> and <code>KeyDown</code>
events to allow for a nicer editing experience. I'll also add a
<code>Color</code> property so that I can track color independently of the
<code>ColorGrid</code>, to enable cancel support.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> HostKeyDownHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> KeyEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">switch</span> <span class="symbol">(</span>e<span class="symbol">.</span>KeyCode<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> Keys<span class="symbol">.</span>Enter<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>Close<span class="symbol">(</span>ToolStripDropDownCloseReason<span class="symbol">.</span>Keyboard<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Color <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Host<span class="symbol">.</span>Color<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> Keys<span class="symbol">.</span>Escape<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>Close<span class="symbol">(</span>ToolStripDropDownCloseReason<span class="symbol">.</span>Keyboard<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In the key handler, I'm closing the drop down if either the
<kbd>Enter</kbd> or <kbd>Escape</kbd> keys are pressed. If it's
the former, we update our true <code>Color</code> property. If the latter,
we don't. This way a user can cancel the drop down without
updating anything.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> HostMouseClickHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 ColorHitTestInfo info<span class="symbol">;</span>

 info <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Host<span class="symbol">.</span>HitTest<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>info<span class="symbol">.</span>Index <span class="symbol">!=</span> ColorGrid<span class="symbol">.</span>InvalidIndex<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Close<span class="symbol">(</span>ToolStripDropDownCloseReason<span class="symbol">.</span>ItemClicked<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Color <span class="symbol">=</span> info<span class="symbol">.</span>Color<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The mouse handling is fairly similar, with the exception we
don't cover a cancel case. If the user clicks outside the bounds
of the drop down it will be automatically closed.</p>
<p>Here we do a hit test, and if a colour was clicked, we close the
drop down and update the internal colour.</p>
<blockquote>
<p>Notice that I close the drop down <em>before</em> setting the colour.
This is deliberate, as originally I had it the other way
around (as would seem more logical). The problem with that is
that change events will be raised for the modified colour -
but the drop down palette is still visible on the screen which
I found a hindrance while debugging.</p>
</blockquote>
<p>I also noted that when the drop down opened, the <code>ColorGrid</code> did
not have focus. That was easy enough to resolve by overriding
<code>OnOpened</code>.</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> OnOpened<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnOpened<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Host<span class="symbol">.</span>Focus<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Now that the drop down is handled, we need a new <code>ToolStripItem</code>
to interact with it.</p>
<h2 id="a-custom-toolstripsplitbutton">A custom ToolStripSplitButton</h2>
<p>For the actual button, I choose to inherit from
<code>ToolStripSplitButton</code>. This gives me two interactions, a drop
down, and a button. We will display the <code>ColorGrid</code> via the drop
down, and the <code>ColorPickerDialog</code> via the button, giving the
user both a simple and an advanced way of choosing a colour.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DefaultProperty<span class="symbol">(</span><span class="string">&quot;Color&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="symbol">[</span>DefaultEvent<span class="symbol">(</span><span class="string">&quot;ColorChanged&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="symbol">[</span>ToolStripItemDesignerAvailability<span class="symbol">(</span>ToolStripItemDesignerAvailability<span class="symbol">.</span>ToolStrip <span class="symbol">|</span> ToolStripItemDesignerAvailability<span class="symbol">.</span>StatusStrip<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">class</span> ToolStripColorPickerSplitButton <span class="symbol">:</span> ToolStripSplitButton
<span class="symbol">{</span>
 <span class="keyword">public</span> ToolStripColorPickerSplitButton<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Color <span class="symbol">=</span> Color<span class="symbol">.</span>Black<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Category<span class="symbol">(</span><span class="string">&quot;Data&quot;</span><span class="symbol">)</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;Black&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">virtual</span> Color Color
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _color<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Color <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 _color <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnColorChanged<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>As with the <code>ToolStripColorPickerDropDown</code> class, our new
<code>ToolStripColorPickerSplitButton</code> also has a dedicated colour
property. The reason for this is I don't want to create the drop
down component unless it's actually going to be used. After all,
why waste resources creating objects we're not going to need?</p>
<p>The <code>ToolStripSplitButton</code> class calls <code>CreateDefaultDropDown</code>
in order to set the <code>DropDown</code> property if it doesn't have a
value. We'll override this to create our custom drop down.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> ToolStripColorPickerDropDown _dropDown<span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> ToolStripDropDown CreateDefaultDropDown<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>EnsureDropDownIsCreated<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> _dropDown<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> EnsureDropDownIsCreated<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_dropDown <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _dropDown <span class="symbol">=</span> <span class="keyword">new</span> ToolStripColorPickerDropDown<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 _dropDown<span class="symbol">.</span>ColorChanged <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>DropDownColorChangedHandler<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In order to allow the developer to customise the <code>ColorGrid</code> if
required, we need to expose the control so they can access it.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="symbol">[</span>DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> ColorGrid Host
<span class="symbol">{</span>
 <span class="keyword">get</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>EnsureDropDownIsCreated<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> _dropDown<span class="symbol">.</span>Host<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>Browsable</code> attribute prevents it from appearing in property
grids, while <code>DesignerSerializationVisibility</code> prevents the
property from being serialized.</p>
<p>Both the <code>Host</code> property and the <code>CreateDefaultDropDown</code>method
make use of the private <code>EnsureDropDownIsCreated</code> method, so
that the drop down is created on demand.</p>
<p>This means you can only customise the control from actual code
(such as from your forms <code>Load</code> event, not by setting properties
on the designer.</p>
<h2 id="toolstrip-designer-support">ToolStrip Designer Support</h2>
<p>As long as the <code>ToolStripColorPickerSplitButton</code> is <code>public</code>,
the existing designers will automatically detect it and allow
you to add them to your <code>ToolStrip</code> or <code>StatusStrip</code> controls.
(Although interestingly it seems to automatically remove the
&quot;ToolStrip&quot; prefix).</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/toolstrip-colorgrid-1c.png" class="gallery" title="Designer support is handled for you" ><img src="https://images.cyotek.com/image/thumbnail/devblog/toolstrip-colorgrid-1c.png" alt="Designer support is handled for you" decoding="async" loading="lazy" /></a><figcaption>Designer support is handled for you</figcaption></figure>
<p>There is a caveat however - the
<code>ToolStripColorPickerSplitButton</code> class must be <code>public</code>.
Originally I had it as <code>internal</code> (as it is part of a
non-library project) but then it never showed up in designers.</p>
<blockquote>
<p>If you display the drop down at design time, you'll find that
you can continue to add items to the drop down underneath the
hosted <code>ColorGrid</code>. I couldn't find a way to disable this,
unless I created a new designer myself.</p>
</blockquote>
<h2 id="displaying-the-colorpickerdialog">Displaying the ColorPickerDialog</h2>
<p>Once the <code>DropDown</code> property of a <code>ToolStripSplitButton</code> has
been set, it will take care of the details of showing it, so
there's nothing more for us to do there. However, we do need to
add some code to display the <code>ColorPickerDialog</code> if a user
clicks the main body of the button. This can be done by
overriding <code>OnButtonClick</code>.</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> OnButtonClick<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnButtonClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>ColorPickerDialog dialog <span class="symbol">=</span> <span class="keyword">new</span> ColorPickerDialog<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 dialog<span class="symbol">.</span>Color <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Color<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>dialog<span class="symbol">.</span>ShowDialog<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>GetCurrentParent<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span> <span class="symbol">==</span> DialogResult<span class="symbol">.</span>OK<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Color <span class="symbol">=</span> dialog<span class="symbol">.</span>Color<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="custom-painting">Custom Painting</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/toolstrip-colorgrid-1a.png" class="gallery" title="A series of ToolStripColorPickerSplitButton's in different styles to demonstrate the custom painting" ><img src="https://images.cyotek.com/image/thumbnail/devblog/toolstrip-colorgrid-1a.png" alt="A series of ToolStripColorPickerSplitButton's in different styles to demonstrate the custom painting" decoding="async" loading="lazy" /></a><figcaption>A series of ToolStripColorPickerSplitButton's in different styles to demonstrate the custom painting</figcaption></figure>
<p>Typically, buttons which display an editor for a colour also
display a preview of the active colour as a thick band
underneath the buttons icon. Although the <code>ToolStripSplitButton</code>
makes this a little harder than it should, we can add this to
our <code>ToolStripColorPickerSplitButton</code> class by overriding the
<code>OnPaint</code> method.</p>
<p>The difficulty comes from the fact that the class doesn't give
us access to its internal layout information, so we have to
guess where the image is in order to draw our line. As there are
quite a few display styles for these items, it can be a little
tricky.</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>
 Rectangle underline<span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnPaint<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 underline <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetUnderlineRectangle<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Brush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Color<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> underline<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">private</span> Rectangle GetUnderlineRectangle<span class="symbol">(</span>Graphics g<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>
 <span class="keyword">int</span> w<span class="symbol">;</span>
 <span class="keyword">int</span> h<span class="symbol">;</span>

 <span class="comment">// TODO: These are approximate values and may not work with different font sizes or image sizes etc</span>

 h <span class="symbol">=</span> <span class="number">4</span><span class="symbol">;</span> <span class="comment">// static height!</span>
 x <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ContentRectangle<span class="symbol">.</span>Left<span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ContentRectangle<span class="symbol">.</span>Bottom <span class="symbol">-</span> <span class="symbol">(</span>h <span class="symbol">+</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DisplayStyle <span class="symbol">==</span> ToolStripItemDisplayStyle<span class="symbol">.</span>ImageAndText <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Text<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> innerHeight<span class="symbol">;</span>

 innerHeight <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height <span class="symbol">-</span> h<span class="symbol">;</span>

 <span class="comment">// got both an image and some text to deal with</span>
 w <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Top <span class="symbol">+</span> innerHeight <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Height <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">switch</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>TextImageRelation<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> TextImageRelation<span class="symbol">.</span>TextBeforeImage<span class="symbol">:</span>
 x <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Right <span class="symbol">-</span> <span class="symbol">(</span>w <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> TextImageRelation<span class="symbol">.</span>ImageAboveText<span class="symbol">:</span>
 x <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Top <span class="symbol">+</span> innerHeight <span class="symbol">+</span> <span class="number">2</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> TextImageRelation<span class="symbol">.</span>TextAboveImage<span class="symbol">:</span>
 x <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ContentRectangle<span class="symbol">.</span>Bottom <span class="symbol">-</span> h<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> TextImageRelation<span class="symbol">.</span>Overlay<span class="symbol">:</span>
 x <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Top <span class="symbol">+</span> innerHeight <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Height <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DisplayStyle <span class="symbol">==</span> ToolStripItemDisplayStyle<span class="symbol">.</span>Image <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// just the image</span>
 w <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DisplayStyle <span class="symbol">==</span> ToolStripItemDisplayStyle<span class="symbol">.</span>Text <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Text<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// just the text</span>
 w <span class="symbol">=</span> TextRenderer<span class="symbol">.</span>MeasureText<span class="symbol">(</span>g<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>Width<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="comment">// who knows, use what we have</span>
 <span class="comment">// TODO: ButtonBounds (and SplitterBounds for that matter) seem to return the wrong</span>
 <span class="comment">// values when painting first occurs, so the line is too narrow until after you </span>
 <span class="comment">// hover the mouse over the button</span>
 w <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ButtonBounds<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ContentRectangle<span class="symbol">.</span>Left <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">,</span> w<span class="symbol">,</span> h<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>GetUnderlineRectangle</code> method show above does a decent job
of guessing where the image should be and should work without
much in the way of tinkering.</p>
<blockquote>
<p>If you are drawing a custom underline, you should make sure
the bottom four pixels of your image are blank, as any details
in these will be covered over by the image.</p>
</blockquote>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/toolstrip-colorgrid-1d.png" class="gallery" title="Keep the bottom pixels of the image clear to avoid loosing details" ><img src="https://images.cyotek.com/image/thumbnail/devblog/toolstrip-colorgrid-1d.png" alt="Keep the bottom pixels of the image clear to avoid loosing details" decoding="async" loading="lazy" /></a><figcaption>Keep the bottom pixels of the image clear to avoid loosing details</figcaption></figure><h2 id="downloading-the-full-source">Downloading the full source</h2>
<p>The full source code can be found in the demonstration program
for the ColorPicker controls on <a href="https://github.com/cyotek/Cyotek.Windows.Forms.ColorPicker" rel="external nofollow noopener">GitHub</a>. Just add the
<code>ToolStripColorPickerDropDown.cs</code> and
<code>ToolStripColorPickerSplitButton.cs</code> files to your project and
you should be good to go!</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2015-02-28 - 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/hosting-a-colorgrid-control-in-a-toolstrip .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comAdding Double Click support to the ComboBox controlurn:uuid:ea23b0c5-bc2a-40ba-9fb2-ca1533294f222014-10-11T07:30:31Z2014-10-11T07:30:31Z<p>I was recently using a <code>ComboBox</code> control with the
<code>DropDownStyle</code> set to <code>Simple</code>, effectively turning into a
combined text box and list box.</p>
<p>However, when I wanted an action to occur on double clicking an
item in the list I found that the control doesn't actually offer
double click support. I suppose I should have just ripped out
the combo box at that point and went with dedicated controls but
instead I decided to extend <code>ComboBox</code> to support double clicks.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/combobox-double-click.png" class="gallery" title="Double click events from a simple mode ComboBox control" ><img src="https://images.cyotek.com/image/thumbnail/devblog/combobox-double-click.png" alt="Double click events from a simple mode ComboBox control" decoding="async" loading="lazy" /></a><figcaption>Double click events from a simple mode ComboBox control</figcaption></figure><h2 id="hmm-no-wm_lbuttondblclk-message">Hmm, no WM_LBUTTONDBLCLK message?</h2>
<p>I had assumed I could simply get the handle of the list
component, set the <code>CS_DBLCLKS</code> style, and start receiving
<code>WM_LBUTTONDBLCLK</code> messages. Unfortunately I couldn't get this
to work. Something to revisit another day perhaps.</p>
<h2 id="fine-lets-fake-it-with-wm_lbuttonup-instead">Fine, lets fake it with WM_LBUTTONUP instead</h2>
<p>So plan A was a bust. Not to worry, I had another idea. In a
<a href="/post/getting-the-hwnd-of-the-edit-component-within-a-combobox-control">previous post</a> I described how to use the <code>GetComboBoxInfo</code>
Win32 API call to obtain the handles to the integrated controls.
We'll use this along with a <code>NativeWindow</code> to watch for
<code>WM_LBUTTONUP</code> messages and handle our double clicks that way.</p>
<h2 id="what-is-nativewindow">What is NativeWindow?</h2>
<p>I haven't described <code>NativeWindow</code> in any previous post, so I'll
briefly cover it now. <code>NativeWindow</code> is a managed wrapper around
a Win32 window handle, and allows you to easily hook into it's
window procedure (WndProc) in order to capture and process
messages sent to the window. Very tidy. The most important class
members are</p>
<ul>
<li><code>AssignHandle</code> - attaches the class to a window</li>
<li><code>ReleaseHandle</code> - detaches the handle once you're finished
with it</li>
<li><code>WndProc</code> - allows you to process messages, otherwise there's
not really much point in using the class!</li>
</ul>
<p>One final point, in most cases you're probably going to want to
subclass <code>NativeWindow</code> as <code>WndProc</code> is protected. And that's
what we'll do here, using a new <code>ListBoxNativeWindow</code> class.</p>
<h2 id="attaching-the-handle">Attaching the handle</h2>
<p>As I mentioned above, you have to explicitly attached your
<code>NativeWindow</code> implementation to a window. For this
demonstration control we'll do it when the control handle is
created, and when the drop down list style is changed. I'll also
add a <code>AllowDoubleClick</code> property to control the new behaviour,
so we'll also set it from there.</p>
<blockquote>
<p><code>NativeWindow</code> doesn't implement <code>IDisposable</code> so for best
practice you should make sure you manually clean up by calling
<code>ReleaseHandle</code> when you are done.</p>
</blockquote>
<p>As I've previously covered the <code>COMBOBOXINFO</code> structure and
<code>GetComboBoxInfo</code> call I won't go over these again - please
refer to my <a href="/post/getting-the-hwnd-of-the-edit-component-within-a-combobox-control">previous post</a> if you need more info.</p>
<p>Assuming we successfully obtain the combo box information, we
instantiate a new instance of our <code>ListBoxNativeWindow</code> and
attach it to the handle of the list box.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> ListBoxNativeWindow _listBoxWindow<span class="symbol">;</span>

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

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsHandleCreated <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>AllowDoubleClick <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>DropDownStyle <span class="symbol">==</span> ComboBoxStyle<span class="symbol">.</span>Simple<span class="symbol">)</span>
 <span class="symbol">{</span>
 COMBOBOXINFO info<span class="symbol">;</span>

 info <span class="symbol">=</span> <span class="keyword">new</span> COMBOBOXINFO<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 info<span class="symbol">.</span>cbSize <span class="symbol">=</span> Marshal<span class="symbol">.</span>SizeOf<span class="symbol">(</span>info<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>GetComboBoxInfo<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">ref</span> info<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 IntPtr hWnd<span class="symbol">;</span>

 hWnd <span class="symbol">=</span> info<span class="symbol">.</span>hwndList<span class="symbol">;</span>

 _listBoxWindow <span class="symbol">=</span> <span class="keyword">new</span> ListBoxNativeWindow<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">)</span><span class="symbol">;</span>
 _listBoxWindow<span class="symbol">.</span>AssignHandle<span class="symbol">(</span>hWnd<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Our new class is also storing a reference to the owner
<code>ComboBox</code> control so that we can raise events as appropriate
later on.</p>
<p>As we should clean up behind ourselves, there's a helper method
to release any existing handles which we will call when
assigning a new handle, or when disposing of the control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> ReleaseHandle<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_listBoxWindow <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _listBoxWindow<span class="symbol">.</span>ReleaseHandle<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 _listBoxWindow <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Now it's time to watch for some messages.</p>
<h2 id="intercepting-messages">Intercepting messages</h2>
<p>Intercepting messages in a <code>NativeWindow</code> is no different to
that of a normal control - just override <code>WndProc</code> and wait for
something interesting.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> WM_LBUTTONUP <span class="symbol">=</span> <span class="number">0x0202</span><span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_LBUTTONUP<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// do stuff!</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="double-clicks">Double clicks</h2>
<p>A double click is a pretty simple thing - it is the second click
to occur within a defined interval and with the cursor within
the region of the first click. These system values are
configurable by the end user so we shouldn't hard code our own
values.</p>
<p>The <code>DoubleClickSize</code> and <code>DoubleClickTime</code> properties of the
<code>SystemInformation</code> class provide managed access to these system
values, and so we can now populate our <code>WndProc</code> template with
some real code.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> NativeMethods<span class="symbol">.</span>WM_LBUTTONUP<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">long</span> previousMessageTime<span class="symbol">;</span>
 <span class="keyword">long</span> currentMessageTime<span class="symbol">;</span>
 Point currentLocation<span class="symbol">;</span>

 previousMessageTime <span class="symbol">=</span> _lastMessageTime<span class="symbol">;</span>
 currentMessageTime <span class="symbol">=</span> DateTime<span class="symbol">.</span>Now<span class="symbol">.</span>Ticks<span class="symbol">;</span>
 currentLocation <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetPoint<span class="symbol">(</span>m<span class="symbol">.</span>LParam<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_lastMessageTime <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle doubleClickBounds<span class="symbol">;</span>
 Size doubleClickSize<span class="symbol">;</span>

 doubleClickSize <span class="symbol">=</span> SystemInformation<span class="symbol">.</span>DoubleClickSize<span class="symbol">;</span>
 doubleClickBounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>_lastMousePosition<span class="symbol">.</span>X <span class="symbol">-</span> <span class="symbol">(</span>doubleClickSize<span class="symbol">.</span>Width <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> _lastMousePosition<span class="symbol">.</span>Y <span class="symbol">-</span> <span class="symbol">(</span>doubleClickSize<span class="symbol">.</span>Height <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> doubleClickSize<span class="symbol">.</span>Width<span class="symbol">,</span> doubleClickSize<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>previousMessageTime <span class="symbol">+</span> <span class="symbol">(</span>SystemInformation<span class="symbol">.</span>DoubleClickTime <span class="symbol">*</span> TimeSpan<span class="symbol">.</span>TicksPerMillisecond<span class="symbol">)</span> <span class="symbol">&gt;</span> currentMessageTime <span class="symbol">&amp;&amp;</span> doubleClickBounds<span class="symbol">.</span>Contains<span class="symbol">(</span>currentLocation<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 MouseEventArgs e<span class="symbol">;</span>

 e <span class="symbol">=</span> <span class="keyword">new</span> MouseEventArgs<span class="symbol">(</span>MouseButtons<span class="symbol">.</span>Left<span class="symbol">,</span> <span class="number">2</span><span class="symbol">,</span> currentLocation<span class="symbol">.</span>X<span class="symbol">,</span> currentLocation<span class="symbol">.</span>Y<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>

 _owner<span class="symbol">.</span>RaiseDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 _lastMessageTime <span class="symbol">=</span> currentMessageTime<span class="symbol">;</span>
 _lastMousePosition <span class="symbol">=</span> currentLocation<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Although it might look a little complicated at first glance, it
should be straight forward.</p>
<ul>
<li>The very first time you click with the left mouse button, we
record the current time and the cursor location</li>
<li>Each subsequent click then
<ul>
<li>Compares the current cursor position against a rectangle
centered on the previous position</li>
<li>Compares the previous click time with the current time
subtracted from the interval</li>
<li>If both the interval since the last click has not elapsed
and the cursor is in the same general area, then we have our
double click</li>
<li>Regards of if an event is to be raised or not, we then
update the time and position for the next click</li>
</ul>
</li>
</ul>
<h2 id="raising-the-event">Raising the event</h2>
<p>Although I'd like to do the &quot;right thing&quot; and trigger a
<code>WM_LBUTTONDBLCLK </code> message, the control doesn't support it and
there's not really much point in adding it when it's not going
to have any real value. So we'll manually do it.</p>
<p>I start by adding an internal method to our <code>ComboBox</code> control -
I tend to avoid internals where possible but I don't really see
a need to expose this publicly.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">void</span> RaiseDoubleClick<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnDoubleClick<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnMouseDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Short and to the point, it simply raises the two different
events .NET controls have for double clicks.</p>
<p>And back in our <code>WndProc</code>, we construct a new <code>MouseEventArgs</code>
object and then call the new method.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
MouseEventArgs e<span class="symbol">;</span>

e <span class="symbol">=</span> <span class="keyword">new</span> MouseEventArgs<span class="symbol">(</span>MouseButtons<span class="symbol">.</span>Left<span class="symbol">,</span> <span class="number">2</span><span class="symbol">,</span> currentLocation<span class="symbol">.</span>X<span class="symbol">,</span> currentLocation<span class="symbol">.</span>Y<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>

_owner<span class="symbol">.</span>RaiseDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>It's worth pointing out the fudge in this - the magic number <code>2</code>
which represents the number of times the button was clicked. The
<code>0</code>, while still magic, represents a mouse wheel delta which is
not appropriate for this event.</p>
<p>And with that code in place, this slightly long winded article
has gotten to the point and you now have fully working events.</p>
<h2 id="really-i-cant-see-them">Really? I can't see them</h2>
<p>Oh of course. As the <code>ComboBox</code> control doesn't support the
<code>DoubleClick</code> and <code>MouseDoubleClick</code> events, the <code>DoubleClick</code>
event has been hidden (but not <code>MouseDoubleClick</code> for some
reason). Easy enough to bring it back - just redefine
<code>DoubleClick</code> with the <code>new</code> keyword set the <code>EditorBrowsable</code>
and <code>Browsable</code> attributes so it will appear in designers.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Always<span class="symbol">)</span><span class="symbol">]</span>
<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">new</span> <span class="keyword">event</span> EventHandler DoubleClick
<span class="symbol">{</span>
 add <span class="symbol">{</span> <span class="keyword">base</span><span class="symbol">.</span>DoubleClick <span class="symbol">+=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 remove <span class="symbol">{</span> <span class="keyword">base</span><span class="symbol">.</span>DoubleClick <span class="symbol">-=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="always-a-catch">Always a catch</h2>
<p>This was yet another blog post that was written in a hurry after
writing some code in a hurry. I'm positive there must be a
better way using normal window styles and messages rather than
the manual approach I've taken.</p>
<p>There's also a flaw in the code - if you triple click (or more)
then you'll get two (or more) double click events. I don't know
of too many people who spam double clicks so I'm going to ignore
this for now. Possibly at some point I'll be bored enough to
take another look at this and see where I went wrong with the
pure API approach.</p>
<p>Finally, given the hurry with which both of these items were
written, it hasn't had any robust testing, and so may be a
flawed piece of work.</p>
<p>As always, a demonstration project accompanies this article.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2014-10-11 - 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/adding-double-click-support-to-the-combobox-control .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comDragging items in a ListBox control with visual insertion guidesurn:uuid:120dddbf-cf89-43e7-892b-e0a6453d2a3e2014-07-27T20:31:50Z2014-07-27T20:31:50Z<p>In my last post, I described how to <a href="/post/dragging-items-in-a-listview-control-with-visual-insertion-guides">drag and drop items to
reorder a <code>ListView</code> control</a>. This time I'm going to
describe the exact same technique, but this time for the more
humble <code>ListBox</code>.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/listbox-item-drag.gif" class="gallery" title="The demonstration project in action" ><img src="https://images.cyotek.com/image/thumbnail/devblog/listbox-item-drag.gif" alt="The demonstration project in action" decoding="async" loading="lazy" /></a><figcaption>The demonstration project in action</figcaption></figure><h2 id="getting-started">Getting Started</h2>
<p>The code below assumes you are working in a new class named
<code>ListBox</code> that inherits from <code>System.Windows.Forms.ListBox</code>.</p>
<p>As it's only implementation details that are different between
the two versions, I'll include the pertinent code and point out
the differences but that's about it. As always a full example
project is available from the link at the end of the article.</p>
<blockquote>
<p>As with the previous article, you must set <code>AllowDrop</code> to
<code>true</code> on any <code>ListBox</code> you wish to make use of this
functionality.</p>
</blockquote>
<h2 id="drawing-on-a-listbox">Drawing on a ListBox</h2>
<p>Just like the <code>ListView</code>, the <code>ListBox</code> control is a native
control that is drawn by the operating system and so overriding
<code>OnPaint</code> doesn't work. The <code>ListBox</code> also has a unique
behaviour of built in owner draw support, so you have to make
sure your painting works with all modes.</p>
<p>Fortunately, the exact same method of painting I used with the
<code>ListView</code> works fine here too - that is, I capture <code>WM_PAINT</code>
messages and use <code>Graphics.FromControl</code> to get something I can
work with.</p>
<p>The only real difference is getting the boundaries of the item
to draw due to the differences in the API's of the two controls
- the <code>ListView</code> uses <code>ListViewItem.GetBounds</code> whilst the
<code>ListBox</code> version is <code>ListView.GetItemRectangle</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> DrawInsertionLine<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">!=</span> InvalidIndex<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> index<span class="symbol">;</span>

 index <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>index <span class="symbol">&gt;=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> index <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count<span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle bounds<span class="symbol">;</span>
 <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>
 <span class="keyword">int</span> width<span class="symbol">;</span>

 bounds <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetItemRectangle<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">)</span><span class="symbol">;</span>
 x <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> <span class="comment">// aways fit the line to the client area, regardless of how the user is scrolling</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">==</span> InsertionMode<span class="symbol">.</span>Before <span class="symbol">?</span> bounds<span class="symbol">.</span>Top <span class="symbol">:</span> bounds<span class="symbol">.</span>Bottom<span class="symbol">;</span>
 width <span class="symbol">=</span> Math<span class="symbol">.</span>Min<span class="symbol">(</span>bounds<span class="symbol">.</span>Width <span class="symbol">-</span> bounds<span class="symbol">.</span>Left<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientSize<span class="symbol">.</span>Width<span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// again, make sure the full width fits in the client area</span>

 <span class="keyword">this</span><span class="symbol">.</span>DrawInsertionLine<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">,</span> width<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="flicker-flicker-flicker">Flicker flicker flicker</h2>
<p>The <code>ListBox</code> is a flickery old beast when owner draw is being
used. Unlike the <code>ListView</code> control where I just invalidate the
entire control and trust the double buffering, unfortunately
setting double buffering on the <code>ListBox</code> seems to have no
effect and it flickers like crazy as you drag things around.</p>
<p>To help combat this, I've added a custom <code>Invalidate</code> method
that accepts the index of a single item to redraw. It also
checks if an insertion mode is set, and if so adjusts the bounds
of the rectangle to include the next/previous item (otherwise,
bits of the insertion guides will be left behind as it tries to
flicker free paint). It will then invalidate only that specific
rectangle and reduce overall flickering. It's not perfect but
it's a lot better than invalidating the whole control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">void</span> Invalidate<span class="symbol">(</span><span class="keyword">int</span> index<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>index <span class="symbol">!=</span> InvalidIndex<span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle bounds<span class="symbol">;</span>

 bounds <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetItemRectangle<span class="symbol">(</span>index<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">==</span> InsertionMode<span class="symbol">.</span>Before <span class="symbol">&amp;&amp;</span> index <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 bounds <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Union<span class="symbol">(</span>bounds<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetItemRectangle<span class="symbol">(</span>index <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">==</span> InsertionMode<span class="symbol">.</span>After <span class="symbol">&amp;&amp;</span> index <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 bounds <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Union<span class="symbol">(</span>bounds<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetItemRectangle<span class="symbol">(</span>index <span class="symbol">+</span> <span class="number">1</span><span class="symbol">)</span><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>bounds<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>When you call <code>Control.Invalidate</code> it <em>does not</em> trigger an
immediate repaint. Instead it sends a <code>WM_PAINT</code> message to
the control to do a paint when next possible. This means
multiple calls to <code>Invalidate</code> with custom rectangles will
more than likely have them all combined into a single large
rectangle, thus repainting more of the control that you might
anticipate.</p>
</blockquote>
<h2 id="initiating-a-drag-operation">Initiating a drag operation</h2>
<p>Unlike the<code>ListView</code> control and its <code>ItemDrag</code> event, the
<code>ListBox</code> doesn't have one. So we'll roll our own <a href="/post/adding-drag-handles-to-an-imagebox-to-allow-resizing-of-selection-regions">using similar
techniques to those I've described before</a>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">int</span> DragIndex <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

<span class="keyword">protected</span> Point DragOrigin <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseDown<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragOrigin <span class="symbol">=</span> e<span class="symbol">.</span>Location<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragIndex <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>IndexFromPoint<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragOrigin <span class="symbol">=</span> Point<span class="symbol">.</span>Empty<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragIndex <span class="symbol">=</span> InvalidIndex<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>```

When the user first presses a button<span class="symbol">,</span> I record both the position of the cursor and which item <span class="keyword">is</span> under it<span class="symbol">.</span>

```csharp
<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseMove<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AllowItemDrag <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>IsDragging <span class="symbol">&amp;&amp;</span> e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>IsOutsideDragZone<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsDragging <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DoDragDrop<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DragIndex<span class="symbol">,</span> DragDropEffects<span class="symbol">.</span>Move<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnMouseMove<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">bool</span> IsOutsideDragZone<span class="symbol">(</span>Point location<span class="symbol">)</span>
<span class="symbol">{</span>
 Rectangle dragZone<span class="symbol">;</span>
 <span class="keyword">int</span> dragWidth<span class="symbol">;</span>
 <span class="keyword">int</span> dragHeight<span class="symbol">;</span>

 dragWidth <span class="symbol">=</span> SystemInformation<span class="symbol">.</span>DragSize<span class="symbol">.</span>Width<span class="symbol">;</span>
 dragHeight <span class="symbol">=</span> SystemInformation<span class="symbol">.</span>DragSize<span class="symbol">.</span>Height<span class="symbol">;</span>
 dragZone <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DragOrigin<span class="symbol">.</span>X <span class="symbol">-</span> <span class="symbol">(</span>dragWidth <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragOrigin<span class="symbol">.</span>Y <span class="symbol">-</span> <span class="symbol">(</span>dragHeight <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> dragWidth<span class="symbol">,</span> dragHeight<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> <span class="symbol">!</span>dragZone<span class="symbol">.</span>Contains<span class="symbol">(</span>location<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>As it would be somewhat confusing to the user (not to mention
rude) if we suddenly initiated drag events whenever they click
the control and their mouse wiggles during it, we check to see
if the mouse cursor has moved sufficient pixels away from the
drag origin using metrics obtained from <code>SystemInformation</code>.</p>
<p>If the user has dragged the mouse outside this region, then we
call <code>DoDragDrop</code> to initialize the drag and drop operation.</p>
<h2 id="updating-the-insertion-index">Updating the insertion index</h2>
<p>In exactly the same way as with the <code>ListView</code> version, we can
use the <code>DragOver</code> event to determine which item the mouse is
hovered over, and from there calculate if this is a &quot;before&quot; or
&quot;after&quot; action.</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> OnDragOver<span class="symbol">(</span>DragEventArgs drgevent<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsDragging<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> insertionIndex<span class="symbol">;</span>
 InsertionMode insertionMode<span class="symbol">;</span>
 Point clientPoint<span class="symbol">;</span>

 clientPoint <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PointToClient<span class="symbol">(</span><span class="keyword">new</span> Point<span class="symbol">(</span>drgevent<span class="symbol">.</span>X<span class="symbol">,</span> drgevent<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 insertionIndex <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>IndexFromPoint<span class="symbol">(</span>clientPoint<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>insertionIndex <span class="symbol">!=</span> InvalidIndex<span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle bounds<span class="symbol">;</span>

 bounds <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetItemRectangle<span class="symbol">(</span>insertionIndex<span class="symbol">)</span><span class="symbol">;</span>
 insertionMode <span class="symbol">=</span> clientPoint<span class="symbol">.</span>Y <span class="symbol">&lt;</span> bounds<span class="symbol">.</span>Top <span class="symbol">+</span> <span class="symbol">(</span>bounds<span class="symbol">.</span>Height <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span> <span class="symbol">?</span> InsertionMode<span class="symbol">.</span>Before <span class="symbol">:</span> InsertionMode<span class="symbol">.</span>After<span class="symbol">;</span>

 drgevent<span class="symbol">.</span>Effect <span class="symbol">=</span> DragDropEffects<span class="symbol">.</span>Move<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 insertionIndex <span class="symbol">=</span> InvalidIndex<span class="symbol">;</span>
 insertionMode <span class="symbol">=</span> InsertionMode<span class="symbol">.</span>None<span class="symbol">;</span>

 drgevent<span class="symbol">.</span>Effect <span class="symbol">=</span> DragDropEffects<span class="symbol">.</span>None<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>insertionIndex <span class="symbol">!=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">||</span> insertionMode <span class="symbol">!=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionMode<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// clear the previous item</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">=</span> insertionMode<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">=</span> insertionIndex<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// draw the new item</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnDragOver<span class="symbol">(</span>drgevent<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The logic is the same, just the implementation differences in
getting the hovered item (use <code>ListBox.IndexFromPoint</code> and the
item bounds). I've also added a dedicated <code>InsertionMode.None</code>
option this time, which is mainly so I don't unnecessarily
invalidate larger regions that I wanted as described in &quot;Flicker
flicker flicker&quot; above.</p>
<p>If the mouse leaves the confines of the control, then we use the
<code>DragLeave</code> event to reset the insertion status. Again no
differences per se, I set the insertion mode now, and I also
call <code>Invalidate</code> first with the current index before resetting
it.</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> OnDragLeave<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">=</span> InvalidIndex<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">=</span> InsertionMode<span class="symbol">.</span>None<span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnDragLeave<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="handling-the-drop">Handling the drop</h2>
<p>When the user releases the mouse, the <code>DragDrop</code> event is
raised. Here, we'll do the actual removal and re-insertion of
the source item.</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> OnDragDrop<span class="symbol">(</span>DragEventArgs drgevent<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsDragging<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">try</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">!=</span> InvalidIndex<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> dragIndex<span class="symbol">;</span>
 <span class="keyword">int</span> dropIndex<span class="symbol">;</span>

 dragIndex <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>drgevent<span class="symbol">.</span>Data<span class="symbol">.</span>GetData<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 dropIndex <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>dragIndex <span class="symbol">&lt;</span> dropIndex<span class="symbol">)</span>
 <span class="symbol">{</span>
 dropIndex<span class="symbol">--</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">==</span> InsertionMode<span class="symbol">.</span>After <span class="symbol">&amp;&amp;</span> dragIndex <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 dropIndex<span class="symbol">++</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>dropIndex <span class="symbol">!=</span> dragIndex<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">object</span> dragItem<span class="symbol">;</span>

 dragItem <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">[</span>dragIndex<span class="symbol">]</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Remove<span class="symbol">(</span>dragItem<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Insert<span class="symbol">(</span>dropIndex<span class="symbol">,</span> dragItem<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>SelectedItem <span class="symbol">=</span> dragItem<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">finally</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Invalidate<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">=</span> InvalidIndex<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">=</span> InsertionMode<span class="symbol">.</span>None<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsDragging <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnDragDrop<span class="symbol">(</span>drgevent<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Just as simple as the <code>ListView</code> version!</p>
<h2 id="sample-project">Sample Project</h2>
<p>An example demonstration project with an extended version of the
above code is available for download from the link below.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2014-07-27 - 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/dragging-items-in-a-listbox-control-with-visual-insertion-guides .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comDragging items in a ListView control with visual insertion guidesurn:uuid:2f61c77c-d1ca-4ce4-af2e-4932323fcd152014-07-27T14:47:09Z2014-07-27T14:47:09Z<p>I can't remember when it was I first saw something being dragged
with an insertion mark for guidance. Whenever it was, it was a
long long time ago and I'm just catching up now.</p>
<p>This article describes how to extend a <code>ListView</code> control to
allow the items within it to be reordered, using insertion
guides.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/listview-item-drag.gif" class="gallery" title="The demonstration project in action" ><img src="https://images.cyotek.com/image/thumbnail/devblog/listview-item-drag.gif" alt="The demonstration project in action" decoding="async" loading="lazy" /></a><figcaption>The demonstration project in action</figcaption></figure><h2 id="drag-drop-vs-mouse-events">Drag Drop vs Mouse Events</h2>
<p>When I first decided that one of my applications needed the
ability to move items around in a list, I knocked together some
quick code by using the <code>MouseDown</code>, <code>MouseMove</code> and <code>MouseUp</code>
events. It worked nicely but I ended up not using it, for the
simple reason I couldn't work out how to change the cursor one
of the standard drag/drop icons - such as <code>Move</code>, <code>Scroll</code>, or
<code>Link</code>. So if anyone knows how to do this, I'd be happy to hear
how (note I don't mean assigning a custom cursor, I mean using
the true OS cursor). As it turns out, apart from the cursor
business (and the incidental fact it looks... awful... if you
<a href="/post/enabling-shell-styles-for-the-listview-and-treeview-controls-in-csharp">enable shell styles</a>), it's also reinventing a fairly large
wheel as the <code>ListView</code> control already provides most of what we
need.</p>
<h2 id="an-elephant-never-forgets-the-allowdrop-property">An Elephant Never Forgets: The AllowDrop Property</h2>
<blockquote>
<p>I should probably have this tattooed upon my forehead as I
think that whenever I try and add drag and drop to anything
and it doesn't work, it's invariably because I didn't set the
<code>AllowDrop</code> property of the respective object to <code>true</code>. And
invariably it takes forever until I remember that that has to
be done.</p>
<p>So... don't forget to set it!</p>
</blockquote>
<h2 id="getting-started">Getting Started</h2>
<p>The code below assumes you are working in a new class named
<code>ListView</code> that inherits from <code>System.Windows.Forms.ListView</code>.
You could do most of this by hooking into the events of an
existing control, but that way leads to madness (and more
complicated code). Or at least duplicate code, and more than
likely duplicate bugs.</p>
<h2 id="drawing-insertion-marks">Drawing insertion marks</h2>
<p>I originally started writing this article with the drag sections
at the start, followed by the sections on drawing.
Unfortunately, it was somewhat confusing to read as the drag is
so heavily dependant on the drawing and insertion bits. So I'll
talk about that first instead.</p>
<p>In order to draw our guides, and to know what to do when the
drag is completed, we need to store some extra information - the
index of the item where the item is to be inserted, and whether
the item is to be inserted before or after the insertion item.
Leaving behind the question on if that sentence even makes
sense, on with some code!</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">enum</span> InsertionMode
<span class="symbol">{</span>
 Before<span class="symbol">,</span>

 After
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">int</span> InsertionIndex <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

<span class="keyword">protected</span> InsertionMode InsertionMode <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">bool</span> IsRowDragInProgress <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
</pre>
</figure>
<p>As we'll be drawing a nice guide so the user is clear on what is
happening, we'll also provide the property to configure the
colour of said guide.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Category<span class="symbol">(</span><span class="string">&quot;Appearance&quot;</span><span class="symbol">)</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;Red&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">virtual</span> Color InsertionLineColor
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _insertionLineColor<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span> <span class="symbol">{</span> _insertionLineColor <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>We'll also need to initialize default values for these.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> ListView<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>DoubleBuffered <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionLineColor <span class="symbol">=</span> Color<span class="symbol">.</span>Red<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">=</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Notice the call to set the <code>DoubleBuffered</code> property? This has
to be done, otherwise your drag operation will be an epic
exercise of Major Flickering. In my library code I use the
<code>LVS_EX_DOUBLEBUFFER</code> style when creating the window, but in
this example <code>DoubleBuffered</code> has worked just as well and is
much easier to do.</p>
<h2 id="drawing-on-a-listview">Drawing on a ListView</h2>
<p>The <code>ListView</code> control is a native control that is drawn by the
operating system. In otherwords, overriding <code>OnPaint</code> isn't
working to work.</p>
<p>So how do you draw on it? Well, you could always go even more
old school than using Windows Forms in the first place, and use
the Win32 to do some custom painting. However, it's a touch
overkill and we can get around it for the most part.</p>
<p>Instead, we'll hook into <code>WndProc</code>, watch for the <code>WM_PAINT</code>
message and then use the <code>Control.CreateGraphics</code> method to get
a <code>Graphics</code> object bound to the window and do our painting that
way.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> WM_PAINT <span class="symbol">=</span> <span class="number">0xF</span><span class="symbol">;</span>

<span class="symbol">[</span>DebuggerStepThrough<span class="symbol">]</span>
<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">switch</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> WM_PAINT<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>DrawInsertionLine<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>I'm not really sure that using <code>CreateGraphics</code> is the best way
to approach this, but it seems to work and it was quicker than
trying to recall all the Win32 GDI work I've done in the past.</p>
<blockquote>
<p><em>Tip:</em> The <code>DebuggerStepThrough</code> is useful for stopping the
debugger from stepping into a method (including any manual
breakpoints you have created). <code>WndProc</code> can be called
thousands of times in a &quot;busy&quot; control, and if you're trying
to debug and suddenly end up in here, it can be a pain.</p>
</blockquote>
<p>The code for actually drawing the insertion line is in itself
simple enough - we just draw a horizontal line with arrow heads
at either side. We adjust the start and end of the line to
ensure it always fits within the client area of the control,
regardless of if the control is horizontally scrolled or the
total width of the item.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> DrawInsertionLine<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">!=</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> index<span class="symbol">;</span>

 index <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>index <span class="symbol">&gt;=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> index <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count<span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle bounds<span class="symbol">;</span>
 <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>
 <span class="keyword">int</span> width<span class="symbol">;</span>

 bounds <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">[</span>index<span class="symbol">]</span><span class="symbol">.</span>GetBounds<span class="symbol">(</span>ItemBoundsPortion<span class="symbol">.</span>Entire<span class="symbol">)</span><span class="symbol">;</span>
 x <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> <span class="comment">// aways fit the line to the client area, regardless of how the user is scrolling</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">==</span> InsertionMode<span class="symbol">.</span>Before <span class="symbol">?</span> bounds<span class="symbol">.</span>Top <span class="symbol">:</span> bounds<span class="symbol">.</span>Bottom<span class="symbol">;</span>
 width <span class="symbol">=</span> Math<span class="symbol">.</span>Min<span class="symbol">(</span>bounds<span class="symbol">.</span>Width <span class="symbol">-</span> bounds<span class="symbol">.</span>Left<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientSize<span class="symbol">.</span>Width<span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// again, make sure the full width fits in the client area</span>

 <span class="keyword">this</span><span class="symbol">.</span>DrawInsertionLine<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">,</span> width<span class="symbol">)</span><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> DrawInsertionLine<span class="symbol">(</span><span class="keyword">int</span> x<span class="number">1</span><span class="symbol">,</span> <span class="keyword">int</span> y<span class="symbol">,</span> <span class="keyword">int</span> width<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>CreateGraphics<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Point<span class="symbol">[</span><span class="symbol">]</span> leftArrowHead<span class="symbol">;</span>
 Point<span class="symbol">[</span><span class="symbol">]</span> rightArrowHead<span class="symbol">;</span>
 <span class="keyword">int</span> arrowHeadSize<span class="symbol">;</span>
 <span class="keyword">int</span> x<span class="number">2</span><span class="symbol">;</span>

 x<span class="number">2</span> <span class="symbol">=</span> x<span class="number">1</span> <span class="symbol">+</span> width<span class="symbol">;</span>
 arrowHeadSize <span class="symbol">=</span> <span class="number">7</span><span class="symbol">;</span>
 leftArrowHead <span class="symbol">=</span> <span class="keyword">new</span><span class="symbol">[</span><span class="symbol">]</span>
 <span class="symbol">{</span>
 <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="number">1</span><span class="symbol">,</span> y <span class="symbol">-</span> <span class="symbol">(</span>arrowHeadSize <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="number">1</span> <span class="symbol">+</span> arrowHeadSize<span class="symbol">,</span> y<span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="number">1</span><span class="symbol">,</span> y <span class="symbol">+</span> <span class="symbol">(</span>arrowHeadSize <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">}</span><span class="symbol">;</span>
 rightArrowHead <span class="symbol">=</span> <span class="keyword">new</span><span class="symbol">[</span><span class="symbol">]</span>
 <span class="symbol">{</span>
 <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="number">2</span><span class="symbol">,</span> y <span class="symbol">-</span> <span class="symbol">(</span>arrowHeadSize <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="number">2</span> <span class="symbol">-</span> arrowHeadSize<span class="symbol">,</span> y<span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="number">2</span><span class="symbol">,</span> y <span class="symbol">+</span> <span class="symbol">(</span>arrowHeadSize <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">}</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Pen pen <span class="symbol">=</span> <span class="keyword">new</span> Pen<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionLineColor<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 g<span class="symbol">.</span>DrawLine<span class="symbol">(</span>pen<span class="symbol">,</span> x<span class="number">1</span><span class="symbol">,</span> y<span class="symbol">,</span> x<span class="number">2</span> <span class="symbol">-</span> <span class="number">1</span><span class="symbol">,</span> y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Brush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionLineColor<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 g<span class="symbol">.</span>FillPolygon<span class="symbol">(</span>brush<span class="symbol">,</span> leftArrowHead<span class="symbol">)</span><span class="symbol">;</span>
 g<span class="symbol">.</span>FillPolygon<span class="symbol">(</span>brush<span class="symbol">,</span> rightArrowHead<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>And that's all there is to that part of the code. Don't forget
to ensure the control is double buffered!</p>
<h2 id="initiating-a-drag-operation">Initiating a drag operation</h2>
<p>The <code>ListView</code> control has an <code>ItemDrag</code> event that is
automatically raised when the user tries to drag an item. We'll
use this to initiate our own drag and drop operation.</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> OnItemDrag<span class="symbol">(</span>ItemDragEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">&gt;</span> <span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsRowDragInProgress <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DoDragDrop<span class="symbol">(</span>e<span class="symbol">.</span>Item<span class="symbol">,</span> DragDropEffects<span class="symbol">.</span>Move<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnItemDrag<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Note: The code snippets in this article are kept concise to
show only the basics of the technique. In most cases, I've
expanded upon this to include extra support (in this case for
raising an event allowing the operation to be cancelled),
please download the sample project for the full class.</p>
</blockquote>
<p>When the <code>DroDragDrop</code> is called, execution will halt at that
point until the drag is complete or cancelled. You can use the
<code>DragEnter</code>, <code>DragOver</code>, <code>DragLeave</code>, <code>DragDrop</code> and
<code>GiveFeedback</code> events to control the drag, for example to
specify the action that is currently occurring, and to handle
what happens when the user releases the mouse cursor.</p>
<h2 id="updating-the-insertion-index">Updating the insertion index</h2>
<p>We can use the <code>DragOver</code> event to determine which item the
mouse is hovered over, and from there calculate if this is a
&quot;before&quot; or &quot;after&quot; action.</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> OnDragOver<span class="symbol">(</span>DragEventArgs drgevent<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsRowDragInProgress<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> insertionIndex<span class="symbol">;</span>
 InsertionMode insertionMode<span class="symbol">;</span>
 ListViewItem dropItem<span class="symbol">;</span>
 Point clientPoint<span class="symbol">;</span>

 clientPoint <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PointToClient<span class="symbol">(</span><span class="keyword">new</span> Point<span class="symbol">(</span>drgevent<span class="symbol">.</span>X<span class="symbol">,</span> drgevent<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 dropItem <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetItemAt<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> Math<span class="symbol">.</span>Min<span class="symbol">(</span>clientPoint<span class="symbol">.</span>Y<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">[</span><span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">-</span> <span class="number">1</span><span class="symbol">]</span><span class="symbol">.</span>GetBounds<span class="symbol">(</span>ItemBoundsPortion<span class="symbol">.</span>Entire<span class="symbol">)</span><span class="symbol">.</span>Bottom <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>dropItem <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle bounds<span class="symbol">;</span>

 bounds <span class="symbol">=</span> dropItem<span class="symbol">.</span>GetBounds<span class="symbol">(</span>ItemBoundsPortion<span class="symbol">.</span>Entire<span class="symbol">)</span><span class="symbol">;</span>
 insertionIndex <span class="symbol">=</span> dropItem<span class="symbol">.</span>Index<span class="symbol">;</span>
 insertionMode <span class="symbol">=</span> clientPoint<span class="symbol">.</span>Y <span class="symbol">&lt;</span> bounds<span class="symbol">.</span>Top <span class="symbol">+</span> <span class="symbol">(</span>bounds<span class="symbol">.</span>Height <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span> <span class="symbol">?</span> InsertionMode<span class="symbol">.</span>Before <span class="symbol">:</span> InsertionMode<span class="symbol">.</span>After<span class="symbol">;</span>

 drgevent<span class="symbol">.</span>Effect <span class="symbol">=</span> DragDropEffects<span class="symbol">.</span>Move<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 insertionIndex <span class="symbol">=</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">;</span>
 insertionMode <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionMode<span class="symbol">;</span>

 drgevent<span class="symbol">.</span>Effect <span class="symbol">=</span> DragDropEffects<span class="symbol">.</span>None<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>insertionIndex <span class="symbol">!=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">||</span> insertionMode <span class="symbol">!=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionMode<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">=</span> insertionMode<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">=</span> insertionIndex<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="keyword">base</span><span class="symbol">.</span>OnDragOver<span class="symbol">(</span>drgevent<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The code is a little long, but simple enough. We get the
<code>ListViewItem</code> underneath the cursor. If there isn't one, we
clear any existing insertion data. If we do have one, we check
if the cursor is above or below half of the total height of the
item in order to decide &quot;before&quot; or &quot;after&quot; status.</p>
<p>We also inform the underlying drag operation so that the
appropriate cursor &quot;Move&quot; or &quot;No Drag&quot; is displayed.</p>
<p>Finally, we issue a call to <code>Invalidate</code> to force the control to
repaint so that the new indicator is drawn (or the existing
indicator cleared).</p>
<p>If the mouse leaves the confines of the control, then we use the
<code>DragLeave</code> event to reset the insertion status. We don't need
to use <code>DragEnter</code> as <code>DragOver</code> covers us in this case.</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> OnDragLeave<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">=</span> <span class="symbol">-</span><span class="number">1</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="keyword">base</span><span class="symbol">.</span>OnDragLeave<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="handling-the-drop">Handling the drop</h2>
<p>When the user releases the mouse, the <code>DragDrop</code> event is
raised. Here, we'll do the actual removal and re-insertion of
the source item.</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> OnDragDrop<span class="symbol">(</span>DragEventArgs drgevent<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsRowDragInProgress<span class="symbol">)</span>
 <span class="symbol">{</span>
 ListViewItem dropItem<span class="symbol">;</span>

 dropItem <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">!=</span> <span class="symbol">-</span><span class="number">1</span> <span class="symbol">?</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">[</span><span class="keyword">this</span><span class="symbol">.</span>InsertionIndex<span class="symbol">]</span> <span class="symbol">:</span> <span class="keyword">null</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>dropItem <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 ListViewItem dragItem<span class="symbol">;</span>
 <span class="keyword">int</span> dropIndex<span class="symbol">;</span>

 dragItem <span class="symbol">=</span> <span class="symbol">(</span>ListViewItem<span class="symbol">)</span>drgevent<span class="symbol">.</span>Data<span class="symbol">.</span>GetData<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>ListViewItem<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 dropIndex <span class="symbol">=</span> dropItem<span class="symbol">.</span>Index<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>dragItem<span class="symbol">.</span>Index <span class="symbol">&lt;</span> dropIndex<span class="symbol">)</span>
 <span class="symbol">{</span>
 dropIndex<span class="symbol">--</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>InsertionMode <span class="symbol">==</span> InsertionMode<span class="symbol">.</span>After <span class="symbol">&amp;&amp;</span> dragItem<span class="symbol">.</span>Index <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 dropIndex<span class="symbol">++</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>dropIndex <span class="symbol">!=</span> dragItem<span class="symbol">.</span>Index<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Remove<span class="symbol">(</span>dragItem<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Insert<span class="symbol">(</span>dropIndex<span class="symbol">,</span> dragItem<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>SelectedItem <span class="symbol">=</span> dragItem<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>InsertionIndex <span class="symbol">=</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsRowDragInProgress <span class="symbol">=</span> <span class="keyword">false</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="keyword">base</span><span class="symbol">.</span>OnDragDrop<span class="symbol">(</span>drgevent<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>We reuse the <code>InsertionIndex</code> and <code>InsertionMode</code> values we
calculated in <code>OnDragOver</code> and then determine the index of the
new item from these. Remove the source item, reinsert it at the
new index, then clear the insertion values, force and repaint
and we're done. Easy!</p>
<h2 id="sample-project">Sample Project</h2>
<p>An example demonstration project with an extended version of the
above code is available for download from the link below.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2014-07-27 - 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/dragging-items-in-a-listview-control-with-visual-insertion-guides .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comBatch Syntax Highlighting in the DigitalRune Text Editor Controlurn:uuid:b060a717-827a-4506-a2d2-20f85e2e0b4b2014-06-23T20:00:26Z2014-06-23T20:00:26Z<p>In a <a href="/post/css-syntax-highlighting-in-the-digitalrune-text-editor-control">previous article</a> I described how to create a CSS
syntax highlighting definition file for use with the open source
<a href="http://www.digitalrune.com/Products/TextEditorControl/Overview.aspx" rel="external nofollow noopener">DigitalRune Text Editor Control</a>.</p>
<p>Today I was testing events in one of our myriad of prototypes,
which triggered an sample <code>TextEditorControl</code> to apply the BAT
syntax definition found in <code>BAT-Mode.xshd</code>.</p>
<p>While it validated the particular piece of code I was working on
rather nicely, I was less then enamoured with the highlighting.
The image below shows an example of this.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/bat-syntax-original.png" class="gallery" title="My eyes! The original (and illegible) BAT syntax highlighting" ><img src="https://images.cyotek.com/image/thumbnail/devblog/bat-syntax-original.png" alt="My eyes! The original (and illegible) BAT syntax highlighting" decoding="async" loading="lazy" /></a><figcaption>My eyes! The original (and illegible) BAT syntax highlighting</figcaption></figure>
<p>Pretty ugly isn't it? However, I was actually doing a vaguely
real-world test as I did want to see some simple batch file
syntax highlighting. Perfect timing for a distraction to go look
at another problem.</p>
<p>The following screenshot shows my new and improved syntax
highlighting file, based on my experiences creating the CSS
version. I couldn't do everything I tried/wanted, and I suspect
this will be limitations of the <code>TextEditorControl</code>, but fixing
that is &quot;rainy day&quot; stuff. Otherwise, simple as this was, it was
a nice distraction with immediate benefits!</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/bat-syntax-new.png" class="gallery" title="New and improved Joker products! Or, at least new and improved syntax highlighting" ><img src="https://images.cyotek.com/image/thumbnail/devblog/bat-syntax-new.png" alt="New and improved Joker products! Or, at least new and improved syntax highlighting" decoding="async" loading="lazy" /></a><figcaption>New and improved Joker products! Or, at least new and improved syntax highlighting</figcaption></figure><h2 id="the-xml-definition">The XML Definition</h2>
<p>The definition is very straightforward, as it's basically just
keyword tokens and a single highlighting span for environment
variables.</p>
<p>I was trying for something similar to how Notepad++ highlights
batch files, in which if the first &quot;word&quot; on a line isn't a
command, it's assumed to be a program and highlighted
accordingly, but I couldn't get that working correctly so it's
commented out in the definition - if you know of a trick or code
patch to make it work, please let me know!</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;?</span><span class="name">xml</span> <span class="name">version</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">1.0</span><span class="symbol">&quot;</span> <span class="name">encoding</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">utf-8</span><span class="symbol">&quot;</span><span class="symbol">?&gt;</span>

<span class="comment">&lt;!-- Batch File syntax highlighting for DigitalRune TextEditor. Created by Cyotek (http://cyotek.com/) --&gt;</span>

<span class="symbol">&lt;</span><span class="name">SyntaxDefinition</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Batch</span><span class="symbol">&quot;</span> <span class="name">extensions</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">*.bat;*.cmd</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>

 <span class="symbol">&lt;</span><span class="name">Environment</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Default</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#000000</span><span class="symbol">&quot;</span> <span class="name">bgcolor</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#ffffff</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Environment</span><span class="symbol">&gt;</span>

 <span class="symbol">&lt;</span><span class="name">RuleSets</span><span class="symbol">&gt;</span>

 <span class="symbol">&lt;</span><span class="name">RuleSet</span> <span class="name">ignorecase</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>

 <span class="comment">&lt;!-- Adding @ as a delimiter means the &quot;ECHO&quot; in &quot;@ECHO OFF&quot; will be highlighted. --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Delimiters</span><span class="symbol">&gt;</span>@:<span class="symbol">&lt;/</span><span class="name">Delimiters</span><span class="symbol">&gt;</span> 

 <span class="comment">&lt;!-- REM comment --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">LineComment</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#008000</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span><span class="symbol">&gt;</span>REM<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>

 <span class="comment">&lt;!-- :: style comment --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">LineComment2</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#008000</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span><span class="symbol">&gt;</span>::<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>

 <span class="comment">&lt;!-- label --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Label</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#FF0000</span><span class="symbol">&quot;</span> <span class="name">bgcolor</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#FFFF80</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span> <span class="name">startofline</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>:<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>

 <span class="comment">&lt;!-- Environment Variable --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Variable</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#FF8000</span><span class="symbol">&quot;</span> <span class="name">bgcolor</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#FCFFF0</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span><span class="symbol">&gt;</span>%<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">End</span><span class="symbol">&gt;</span>%<span class="symbol">&lt;/</span><span class="name">End</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>

 <span class="comment">&lt;!-- Programs --&gt;</span>
 <span class="comment">&lt;!-- The idea here is the first word on a line that isn&#39;t a keyword is a program. 
 Doesn&#39;t work with the default TextEditorControl though. --&gt;</span>
 <span class="comment">&lt;!--&lt;Span name=&quot;Program&quot; bold=&quot;false&quot; italic=&quot;true&quot; color=&quot;Red&quot; bgcolor=&quot;#FCFFF0&quot; stopateol=&quot;true&quot;&gt;
 &lt;Begin startofline=&quot;true&quot; singleword=&quot;true&quot;&gt;&lt;/Begin&gt;
 &lt;End&gt; &lt;/End&gt;
 &lt;/Span&gt;--&gt;</span>

 <span class="comment">&lt;!-- Operators --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">KeyWords</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Punctuation</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#FF0000</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">+</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">-</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">*</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">&amp;amp;lt;</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">&amp;amp;gt;</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">=</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">@</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">KeyWords</span><span class="symbol">&gt;</span>

 <span class="comment">&lt;!-- Standard command.com keywords --&gt;</span>
 <span class="comment">&lt;!-- http://en.wikipedia.org/wiki/COMMAND.COM --&gt;</span>

 <span class="symbol">&lt;</span><span class="name">KeyWords</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">command.com-internalcommands</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#0000FF</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">break</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">chcp</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">chdir</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">cd</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">cls</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">copy</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">ctty</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">date</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">del</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">erase</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">dir</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">echo</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">exit</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lfnfor</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">loadhigh</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lh</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lock</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">move</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">mkdir</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">md</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">path</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">prompt</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">ren</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">rename</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">rmdir</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">rd</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">set</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">time</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">truename</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">type</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">unlock</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">ver</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">verify</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">vol</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="comment">&lt;!-- http://ss64.com/nt/ --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">color</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">endlocal</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">ftype</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">mklink</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">popd</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">pushd</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">setlocal</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">start</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">title</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">KeyWords</span><span class="symbol">&gt;</span>

 <span class="symbol">&lt;</span><span class="name">KeyWords</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">command.com-commands</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#0000FF</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">call</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">for</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">goto</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">if</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">in</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">do</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">pause</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">rem</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">shift</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">KeyWords</span><span class="symbol">&gt;</span>

 <span class="comment">&lt;!-- Redirects --&gt;</span>
 <span class="symbol">&lt;</span><span class="name">KeyWords</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">command.com-redirects</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">#0000FF</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">nul</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">con</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">prn</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">aux</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">clock$</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com0</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com1</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com2</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com3</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com4</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com5</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com6</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com7</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com8</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">com9</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt0</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt1</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt2</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt3</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt4</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt5</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt6</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt7</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt8</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">lpt9</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">KeyWords</span><span class="symbol">&gt;</span>

 <span class="symbol">&lt;/</span><span class="name">RuleSet</span><span class="symbol">&gt;</span>

 <span class="symbol">&lt;/</span><span class="name">RuleSets</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">SyntaxDefinition</span><span class="symbol">&gt;</span>
</pre>
</figure>
<h2 id="a-sample-project">A sample project</h2>
<p>I've attached a basic sample project, which shows how to dynamic
load in the definition file. And yes, that is a (stripped down)
version of one of the build scripts used by Spriter. I'm still
an old school guy at heart, and so I use <code>msbuild</code> to compile a
solution, but for anything else I still tend to turn to batch
scripts and custom console apps first rather than MSBuild tasks
or PowerShell cmdlets.</p>
<p>Although the example project loads in the definition from an
external file, I would recommend that you build it into the
<code>TextEditorControl</code> assembly itself to make deployment easier. I
covered this in the <strong>Compiling the definition into the
assembly</strong> section of the <a href="/post/css-syntax-highlighting-in-the-digitalrune-text-editor-control">previous article</a> so I won't
replicate that here.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2014-06-23 - 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/batch-syntax-highlighting-in-the-digitalrune-text-editor-control .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comColorPicker Controls 1.0.4.0 Updateurn:uuid:54c0656a-d653-421f-bd59-b8bb0a12286c2014-04-13T09:48:03Z2014-04-13T09:48:03Z<p>The <a href="https://github.com/cyotek/Cyotek.Windows.Forms.ColorPicker" rel="external nofollow noopener">ColorPicker Controls</a> have been updated to version
1.0.4.0.</p>
<p>This is a fairly substantial update, with quite a few bug fixes
and enhancements to the code.</p>
<h2 id="whats-next-for-the-library">What's next for the library?</h2>
<p>I feel the code is starting to get a little bloated as the
library is trying to serve two purposes - the primary purpose is
for the selection of colours. However, it also provides the
ability to load and save colour swatches in various formats,
some of which has been the subject of posts on this blog.</p>
<p>Internally, we link to the individual files from the library in
our core product assemblies - all the UI stuff is in
<code>Cyotek.Windows.Forms</code>, and everything else is in
<code>Cyotek.Drawing</code>. I think it would probably be a good idea to
properly split up this library too. If you just want the UI, use
one library, if you want the extended palette serialisation
support, use the other. There is some overlap though so this
will need to be considered a bit first.</p>
<p>Of course splitting the library is a massive breaking change. I
think future versions of our open source libraries will change
to use <a href="http://semver.org/spec/v2.0.0.html" rel="external nofollow noopener">Semantic Versioning</a> so that it will be much clearer
when things are being broken. Of course, it would be preferable
if breaking changes <em>weren't</em> introduced all the time! This is
also why I haven't added a NuGet package yet, when you update a
package you don't expect to have to change your source code too.</p>
<h2 id="changes-and-new-features">Changes and new features</h2>
<ul>
<li>Added new <code>AdobePhotoShopColorSwatchSerializer</code> serializer for
reading and writing Adobe PhotoShop colour swatches (both
version 1 and version 2)</li>
<li>You can now set the <code>Columns</code> property of a <code>ColorGrid</code>
control to <code>0</code>, which will then internally calculate columns
based on the size of the control, the cell size, and the
spacing. A new read-only <code>ActualColumns</code> property has been
added which will allow you to get the real number of columns
if required. The <code>AutoSize</code> behaviour has been changed so that
only the vertical height of the control is adjusted when
<code>Columns</code> is zero</li>
<li><strong>Save Palette</strong> button in the <code>ColorPickerDialog</code> now obtains
the serializer to use based on the selected filter index,
allowing correct saving if multiple serializers use the same
extension.</li>
<li>Added <code>CanReadFrom</code> method to <code>IPaletteSerializer</code>.</li>
<li><code>PaletteSerializer.GetSerializer</code> now makes use of the above
new method to access the relevant serializer rather than just
matching extensions. This means if you have two serializers
that support different .pal formatted files, these can now be
loaded successfully, instead of one loading and one failing.</li>
<li>Added new <code>RawPaletteSerializer</code> which reads and writes
palettes that are simply RGB triplets in byte form</li>
<li>Added new <code>ShowAlphaChannel</code> property to <code>ColorEditor</code> and
<code>ColorPickerDialog</code>. This property allows the alpha channel
editing controls to be hidden, for when working with 8-bit
colours.</li>
<li>The rendering of the selected cell in a <code>ColorGrid</code> control
who's <code>SelectedCellStyle</code> is <code>Zoomed</code> now uses <code>Padding.Left</code>
and <code>Padding.Top</code> to determine the size of the zoom box,
avoiding massive boxes the larger the <code>CellSize</code> gets.</li>
<li>Added a new standard 256 colour palette. You can use this in
the <code>ColorGrid</code> by setting the <code>Palette</code> property to
<code>ColorPalette.Standard256</code> or obtain the array of colours by
calling <code>ColorPalettes.StandardPalette</code></li>
<li><code>ColorGrid</code> and <code>RgbaColorSlider</code> controls now only create
transparency brushes when required. A new virtual method
<code>SupportsTransparentBackColor</code> allows inheritors to create
their own brushes if required.</li>
<li>Added <code>EditingColor</code> event to <code>ColorGrid</code>, allowing the edit
colour action to be cancelled, or replaced with a custom
editor</li>
<li>Added <code>CurrentCell</code> property and <code>GetCellOffset</code> methods to
the <code>ColorGrid</code>.</li>
<li><code>ColorCollection</code> now implements <code>IEquatable</code></li>
<li>Added more tests</li>
<li>Added new <code>Navigate</code> method to <code>ColorGrid</code> for easier moving
within the cells of the grid</li>
</ul>
<h2 id="bug-fixes">Bug Fixes</h2>
<ul>
<li>The <code>ColorGrid</code> control now tries to be smarter with painting,
and only paints cells that intersect with the clip rectangle.
In addition, where possible only individual cells are
invalidated rather than the entire control.</li>
<li>Corrected invalid error messages from the <strong>Save Palette</strong>
button in the <code>ColorPickerDialog</code>.</li>
<li><strong>Load Palette</strong> and <strong>Save Palette</strong> buttons in the
<code>ColorPickerDialog</code> now check the <code>CanRead</code> and <code>CanWrite</code>
properties of the serializer.</li>
<li>Double clicking with any button other than the left in
<code>ColorGrid</code> control no longer attempts to initiate colour
editing</li>
<li>Setting the <code>Color</code> property of the <code>ColorGrid</code> control to
<code>Color.Empty</code> no longer treats the value as a valid colour</li>
<li>The <code>ColorGrid</code> control no longer defines custom colour
regions when the <code>ShowCustomColors</code> property was <code>false</code>. This
manifested in hover and selection effects working if you moved
your mouse over the bottom of a resized grid.</li>
<li>Clicking &quot;white space&quot; areas of a <code>ColorWheel</code> control will no
longer incorrectly set the colour to the closest matching
point on the wheel itself. However, starting to select a
colour within the wheel and then moving outside the bounds
will continue to select the closest match as usual.</li>
<li>Fixed a crash that occurred when creating controls that
inherited from <code>ColorGrid</code> or <code>RgbaColorSlider</code></li>
<li>When the <code>AutoAddColors</code> and <code>ShowCustomColors</code> properties are
<code>false</code>, unmatched colours will no longer be silently added to
the <code>ColorGrid</code> custom palette unexpectedly. This also
resolves various crashes after the colour regions fix above
was applied.</li>
<li>The <code>ColorWheel</code> control now makes use of
<code>ButtonRenderer.DrawParentBackground</code> to draw itself, to avoid
ugly blocks of solid colours when hosted in containers such as
the <code>TabControl</code></li>
<li>The <code>ColorEditorManager</code> control's <code>ColorChanged</code> event has
now been marked as the default event, so when you double click
the component in the designer, a code window now correctly
opens.</li>
<li>If the underlying entry in a <code>ColorCollection</code> bound to a
<code>ColorGrid</code> control was modified, and this particular entry
was the selected colour, the <code>ColorGrid</code> would not keep its
<code>Color</code> property in sync and would clear the selected index.</li>
<li>Attempting to set the <code>Columns</code> property to less than zero now
throws an <code>ArgumentOutOfRange</code> exception rather than setting
it, then crashing later on</li>
<li>Double clicking a colour in the grid of the
<code>ColorPickerDialog</code> no longer opens another copy of the
<code>ColorPickerDialog</code></li>
<li>Fixed problems in the <code>ColorGrid</code> with keyboard navigation and
initial focus if no valid colour index was set.</li>
<li>The <code>ColorCollection.Find</code> method now correctly works when
adding named colours (e.g. <code>Color.CornflowerBlue</code>) to the
collection, but searching by ARGB value (e.g.
<code>Color.FromArgb(100, 149, 237)</code>)</li>
<li>Fixed an issue where if the internal dictionary lookup in
<code>ColorCollection</code> class had been created and the collection
was then updated, in some cases the lookup wasn't correctly
modified.</li>
</ul>
<h2 id="update-history">Update History</h2>
<ul>
<li>2014-04-13 - 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/colorpicker-controls-1-0-4-0-update .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comAdding drag handles to an ImageBox to allow resizing of selection regionsurn:uuid:f4104c69-7c2f-4db2-9206-246115c2af482014-02-13T20:12:19Z2014-02-13T20:12:19Z<p>The <code>ImageBox</code> control is already a versatile little control and
I use it for all sorts of tasks. One of the features I recently
wanted was to allow users to be able to select a source region,
then adjust this as needed. The control already allows you to
draw a selection region, but if you need to adjust that ...
well, you can't. You can only draw a new region.</p>
<p>This article describes how to extend the <code>ImageBox</code> to include
the ability to resize the selection region. A older
demonstration which shows how to drag the selection around has
also been incorporated, in a more tidy fashion than the demo.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imageboxresize-1e.png" class="gallery" title="The control in action - and yes, you can resize even when zoomed in or out" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imageboxresize-1e.png" alt="The control in action - and yes, you can resize even when zoomed in or out" decoding="async" loading="lazy" /></a><figcaption>The control in action - and yes, you can resize even when zoomed in or out</figcaption></figure>
<blockquote>
<p>Note: The code presented in this article has not been added to
the core <code>ImageBox</code> control. Mostly this is because I don't
want to clutter the control with bloat (something users of the
old <code>PropertiesList</code> control might wish I'd done!) and partly
because I don't want to add changes to the control that I'll
regret down the line - I don't need another mess like the
<a href="https://github.com/cyotek/Cyotek.Windows.Forms.ColorPicker" rel="external nofollow noopener">Color Picker Controls</a> where every update seems to be a
breaking change! It most likely will be added to the core
control after it's been dog-fooded for a while with different
scenarios.</p>
</blockquote>
<h2 id="getting-started">Getting Started</h2>
<p>As I mentioned above, this isn't part of the core control (yet)
and so has been added to a new <code>ImageBoxEx</code> control. Not the
most imaginative of names, but with it's current status of
internal demonstration code, it matters not.</p>
<p>In addition to this new sub-classed control, we also need some
helper classes. First amongst these is a new enum to describe
the drag handle anchors, so we know which edges to resize.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">enum</span> DragHandleAnchor
<span class="symbol">{</span>
 None<span class="symbol">,</span>
 TopLeft<span class="symbol">,</span>
 TopCenter<span class="symbol">,</span>
 TopRight<span class="symbol">,</span>
 MiddleLeft<span class="symbol">,</span>
 MiddleRight<span class="symbol">,</span>
 BottomLeft<span class="symbol">,</span>
 BottomCenter<span class="symbol">,</span>
 BottomRight
<span class="symbol">}</span>
</pre>
</figure>
<p>Next we have the class that describes an individual drag handle
- nothing special here, although I have added <code>Enabled</code> and
<code>Visible</code> properties to allow for more advanced scenarios, such
as locking an edge, or only showing some handles.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">class</span> DragHandle
<span class="symbol">{</span>
 <span class="keyword">public</span> DragHandle<span class="symbol">(</span>DragHandleAnchor anchor<span class="symbol">)</span>
 <span class="symbol">:</span> <span class="keyword">this</span><span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Anchor <span class="symbol">=</span> anchor<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> DragHandle<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Enabled <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Visible <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> DragHandleAnchor Anchor <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">protected</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> Rectangle Bounds <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">bool</span> Enabled <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">bool</span> Visible <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</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/imageboxresize-1b.png" class="gallery" title="While you probably wouldn't do this, hiding one or two of the drag handles could be useful for some scenarios" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imageboxresize-1b.png" alt="While you probably wouldn't do this, hiding one or two of the drag handles could be useful for some scenarios" decoding="async" loading="lazy" /></a><figcaption>While you probably wouldn't do this, hiding one or two of the drag handles could be useful for some scenarios</figcaption></figure>
<p>The final support class is a collection for our drag handle
objects - we could just use a <code>List&lt;&gt;</code> or some other generic
collection but as a rule it's best not to expose these in a
public API (and this code will be just that eventually) so we'll
create a dedicated read-only collection.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">class</span> DragHandleCollection <span class="symbol">:</span> IEnumerable<span class="symbol">&lt;</span>DragHandle<span class="symbol">&gt;</span>
<span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">readonly</span> IDictionary<span class="symbol">&lt;</span>DragHandleAnchor<span class="symbol">,</span> DragHandle<span class="symbol">&gt;</span> _items<span class="symbol">;</span>

 <span class="keyword">public</span> DragHandleCollection<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _items <span class="symbol">=</span> <span class="keyword">new</span> Dictionary<span class="symbol">&lt;</span>DragHandleAnchor<span class="symbol">,</span> DragHandle<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 _items<span class="symbol">.</span>Add<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>TopLeft<span class="symbol">,</span> <span class="keyword">new</span> DragHandle<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>TopLeft<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 _items<span class="symbol">.</span>Add<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>TopCenter<span class="symbol">,</span> <span class="keyword">new</span> DragHandle<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>TopCenter<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 _items<span class="symbol">.</span>Add<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>TopRight<span class="symbol">,</span> <span class="keyword">new</span> DragHandle<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>TopRight<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 _items<span class="symbol">.</span>Add<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>MiddleLeft<span class="symbol">,</span> <span class="keyword">new</span> DragHandle<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>MiddleLeft<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 _items<span class="symbol">.</span>Add<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>MiddleRight<span class="symbol">,</span> <span class="keyword">new</span> DragHandle<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>MiddleRight<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 _items<span class="symbol">.</span>Add<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>BottomLeft<span class="symbol">,</span> <span class="keyword">new</span> DragHandle<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>BottomLeft<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 _items<span class="symbol">.</span>Add<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>BottomCenter<span class="symbol">,</span> <span class="keyword">new</span> DragHandle<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>BottomCenter<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 _items<span class="symbol">.</span>Add<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>BottomRight<span class="symbol">,</span> <span class="keyword">new</span> DragHandle<span class="symbol">(</span>DragHandleAnchor<span class="symbol">.</span>BottomRight<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">int</span> Count
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _items<span class="symbol">.</span>Count<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> DragHandle <span class="keyword">this</span><span class="symbol">[</span>DragHandleAnchor index<span class="symbol">]</span>
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _items<span class="symbol">[</span>index<span class="symbol">]</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> IEnumerator<span class="symbol">&lt;</span>DragHandle<span class="symbol">&gt;</span> GetEnumerator<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">return</span> _items<span class="symbol">.</span>Values<span class="symbol">.</span>GetEnumerator<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> DragHandleAnchor HitTest<span class="symbol">(</span>Point point<span class="symbol">)</span>
 <span class="symbol">{</span>
 DragHandleAnchor result<span class="symbol">;</span>

 result <span class="symbol">=</span> DragHandleAnchor<span class="symbol">.</span>None<span class="symbol">;</span>

 <span class="keyword">foreach</span> <span class="symbol">(</span>DragHandle handle <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>handle<span class="symbol">.</span>Visible <span class="symbol">&amp;&amp;</span> handle<span class="symbol">.</span>Bounds<span class="symbol">.</span>Contains<span class="symbol">(</span>point<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> handle<span class="symbol">.</span>Anchor<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
 <span class="symbol">}</span>

 IEnumerator IEnumerable<span class="symbol">.</span>GetEnumerator<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>GetEnumerator<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Again, there's not much special about this class. As it is a
custom class it does give us more flexibility, such as
initializing the required drag handles, and providing a
convenient <code>HitTest</code> method so we can check if a given point is
within the bounds of a <code>DragHandle</code>.</p>
<h2 id="positioning-drag-handles-around-the-selection-region">Positioning drag handles around the selection region</h2>
<p>The <code>ImageBox</code> control includes a nice bunch of helper methods,
such as <code>PointToImage</code>, <code>GetOffsetRectangle</code> and more, which are
very useful for adding scalable elements to an <code>ImageBox</code>
instance. Unfortunately, they are all virtually useless for the
drag handle code due to the fact that the handles themselves
must not scale - the positions of course must update and
resizing must be accurate whether at 100% zoom or not, but the
size must not. This means we can't rely on the built in methods
and must manually recalculate the handles whenever the control
changes.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> PositionDragHandles<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DragHandles <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>IsEmpty<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span>DragHandle handle <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">)</span>
 <span class="symbol">{</span>
 handle<span class="symbol">.</span>Bounds <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Empty<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> left<span class="symbol">;</span>
 <span class="keyword">int</span> top<span class="symbol">;</span>
 <span class="keyword">int</span> right<span class="symbol">;</span>
 <span class="keyword">int</span> bottom<span class="symbol">;</span>
 <span class="keyword">int</span> halfWidth<span class="symbol">;</span>
 <span class="keyword">int</span> halfHeight<span class="symbol">;</span>
 <span class="keyword">int</span> halfDragHandleSize<span class="symbol">;</span>
 Rectangle viewport<span class="symbol">;</span>
 <span class="keyword">int</span> offsetX<span class="symbol">;</span>
 <span class="keyword">int</span> offsetY<span class="symbol">;</span>

 viewport <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetX <span class="symbol">=</span> viewport<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>X<span class="symbol">;</span>
 offsetY <span class="symbol">=</span> viewport<span class="symbol">.</span>Top <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="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>Y<span class="symbol">;</span>
 halfDragHandleSize <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize <span class="symbol">/</span> <span class="number">2</span><span class="symbol">;</span>
 left <span class="symbol">=</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Left <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span> <span class="symbol">+</span> offsetX<span class="symbol">)</span><span class="symbol">;</span>
 top <span class="symbol">=</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Top <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span> <span class="symbol">+</span> offsetY<span class="symbol">)</span><span class="symbol">;</span>
 right <span class="symbol">=</span> left <span class="symbol">+</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Width <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">;</span>
 bottom <span class="symbol">=</span> top <span class="symbol">+</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Height <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">;</span>
 halfWidth <span class="symbol">=</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Width <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">;</span>
 halfHeight <span class="symbol">=</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Height <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>DragHandleAnchor<span class="symbol">.</span>TopLeft<span class="symbol">]</span><span class="symbol">.</span>Bounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>left <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> top <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>DragHandleAnchor<span class="symbol">.</span>TopCenter<span class="symbol">]</span><span class="symbol">.</span>Bounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>left <span class="symbol">+</span> halfWidth <span class="symbol">-</span> halfDragHandleSize<span class="symbol">,</span> top <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>DragHandleAnchor<span class="symbol">.</span>TopRight<span class="symbol">]</span><span class="symbol">.</span>Bounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>right<span class="symbol">,</span> top <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>DragHandleAnchor<span class="symbol">.</span>MiddleLeft<span class="symbol">]</span><span class="symbol">.</span>Bounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>left <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> top <span class="symbol">+</span> halfHeight <span class="symbol">-</span> halfDragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>DragHandleAnchor<span class="symbol">.</span>MiddleRight<span class="symbol">]</span><span class="symbol">.</span>Bounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>right<span class="symbol">,</span> top <span class="symbol">+</span> halfHeight <span class="symbol">-</span> halfDragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>DragHandleAnchor<span class="symbol">.</span>BottomLeft<span class="symbol">]</span><span class="symbol">.</span>Bounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>left <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> bottom<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>DragHandleAnchor<span class="symbol">.</span>BottomCenter<span class="symbol">]</span><span class="symbol">.</span>Bounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>left <span class="symbol">+</span> halfWidth <span class="symbol">-</span> halfDragHandleSize<span class="symbol">,</span> bottom<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>DragHandleAnchor<span class="symbol">.</span>BottomRight<span class="symbol">]</span><span class="symbol">.</span>Bounds <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>right<span class="symbol">,</span> bottom<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandleSize<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The code is fairly straightforward, but we need to call it from
a few places, so we have a bunch of overrides similar to the
below.</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> OnScroll<span class="symbol">(</span>ScrollEventArgs se<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnScroll<span class="symbol">(</span>se<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>PositionDragHandles<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>We call <code>PositionDragHandles</code> from the constructor, and the
<code>Scroll</code>, <code>SelectionRegionChanged</code>, <code>ZoomChanged</code> and <code>Resize</code>
events.</p>
<h2 id="painting-the-drag-handles">Painting the drag handles</h2>
<p>Painting the handles is simple enough - after normal painting
has occurred, we draw our handles on top.</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>
 <span class="keyword">base</span><span class="symbol">.</span>OnPaint<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AllowPainting <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>IsEmpty<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span>DragHandle handle <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>handle<span class="symbol">.</span>Visible<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>DrawDragHandle<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">,</span> handle<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> DrawDragHandle<span class="symbol">(</span>Graphics graphics<span class="symbol">,</span> DragHandle handle<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> left<span class="symbol">;</span>
 <span class="keyword">int</span> top<span class="symbol">;</span>
 <span class="keyword">int</span> width<span class="symbol">;</span>
 <span class="keyword">int</span> height<span class="symbol">;</span>
 Pen outerPen<span class="symbol">;</span>
 Brush innerBrush<span class="symbol">;</span>

 left <span class="symbol">=</span> handle<span class="symbol">.</span>Bounds<span class="symbol">.</span>Left<span class="symbol">;</span>
 top <span class="symbol">=</span> handle<span class="symbol">.</span>Bounds<span class="symbol">.</span>Top<span class="symbol">;</span>
 width <span class="symbol">=</span> handle<span class="symbol">.</span>Bounds<span class="symbol">.</span>Width<span class="symbol">;</span>
 height <span class="symbol">=</span> handle<span class="symbol">.</span>Bounds<span class="symbol">.</span>Height<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>handle<span class="symbol">.</span>Enabled<span class="symbol">)</span>
 <span class="symbol">{</span>
 outerPen <span class="symbol">=</span> SystemPens<span class="symbol">.</span>WindowFrame<span class="symbol">;</span>
 innerBrush <span class="symbol">=</span> SystemBrushes<span class="symbol">.</span>Window<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 outerPen <span class="symbol">=</span> SystemPens<span class="symbol">.</span>ControlDark<span class="symbol">;</span>
 innerBrush <span class="symbol">=</span> SystemBrushes<span class="symbol">.</span>Control<span class="symbol">;</span>
 <span class="symbol">}</span>

 graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>innerBrush<span class="symbol">,</span> left <span class="symbol">+</span> <span class="number">1</span><span class="symbol">,</span> top <span class="symbol">+</span> <span class="number">1</span><span class="symbol">,</span> width <span class="symbol">-</span> <span class="number">2</span><span class="symbol">,</span> height <span class="symbol">-</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 graphics<span class="symbol">.</span>DrawLine<span class="symbol">(</span>outerPen<span class="symbol">,</span> left <span class="symbol">+</span> <span class="number">1</span><span class="symbol">,</span> top<span class="symbol">,</span> left <span class="symbol">+</span> width <span class="symbol">-</span> <span class="number">2</span><span class="symbol">,</span> top<span class="symbol">)</span><span class="symbol">;</span>
 graphics<span class="symbol">.</span>DrawLine<span class="symbol">(</span>outerPen<span class="symbol">,</span> left<span class="symbol">,</span> top <span class="symbol">+</span> <span class="number">1</span><span class="symbol">,</span> left<span class="symbol">,</span> top <span class="symbol">+</span> height <span class="symbol">-</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 graphics<span class="symbol">.</span>DrawLine<span class="symbol">(</span>outerPen<span class="symbol">,</span> left <span class="symbol">+</span> <span class="number">1</span><span class="symbol">,</span> top <span class="symbol">+</span> height <span class="symbol">-</span> <span class="number">1</span><span class="symbol">,</span> left <span class="symbol">+</span> width <span class="symbol">-</span> <span class="number">2</span><span class="symbol">,</span> top <span class="symbol">+</span> height <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 graphics<span class="symbol">.</span>DrawLine<span class="symbol">(</span>outerPen<span class="symbol">,</span> left <span class="symbol">+</span> width <span class="symbol">-</span> <span class="number">1</span><span class="symbol">,</span> top <span class="symbol">+</span> <span class="number">1</span><span class="symbol">,</span> left <span class="symbol">+</span> width <span class="symbol">-</span> <span class="number">1</span><span class="symbol">,</span> top <span class="symbol">+</span> height <span class="symbol">-</span> <span class="number">2</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/imageboxresize-1a.png" class="gallery" title="Disabled drag handles are painted using different colors" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imageboxresize-1a.png" alt="Disabled drag handles are painted using different colors" decoding="async" loading="lazy" /></a><figcaption>Disabled drag handles are painted using different colors</figcaption></figure><h2 id="updating-the-cursor">Updating the cursor</h2>
<p>As the mouse travels across the control, we need to adjust the
cursor accordingly - either to change it to one of the four
resize cursors if the mouse is over an enabled handle, or to the
drag cursor if it's within the bounds of the selection region.
Of course, we also need to reset it if none of these conditions
are true.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> SetCursor<span class="symbol">(</span>Point point<span class="symbol">)</span>
<span class="symbol">{</span>
 Cursor cursor<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsSelecting<span class="symbol">)</span>
 <span class="symbol">{</span>
 cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>Default<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 DragHandleAnchor handleAnchor<span class="symbol">;</span>

 handleAnchor <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>IsResizing <span class="symbol">?</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">:</span> <span class="keyword">this</span><span class="symbol">.</span>HitTest<span class="symbol">(</span>point<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>handleAnchor <span class="symbol">!=</span> DragHandleAnchor<span class="symbol">.</span>None <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>handleAnchor<span class="symbol">]</span><span class="symbol">.</span>Enabled<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">switch</span> <span class="symbol">(</span>handleAnchor<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> DragHandleAnchor<span class="symbol">.</span>TopLeft<span class="symbol">:</span>
 <span class="keyword">case</span> DragHandleAnchor<span class="symbol">.</span>BottomRight<span class="symbol">:</span>
 cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>SizeNWSE<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> DragHandleAnchor<span class="symbol">.</span>TopCenter<span class="symbol">:</span>
 <span class="keyword">case</span> DragHandleAnchor<span class="symbol">.</span>BottomCenter<span class="symbol">:</span>
 cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>SizeNS<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> DragHandleAnchor<span class="symbol">.</span>TopRight<span class="symbol">:</span>
 <span class="keyword">case</span> DragHandleAnchor<span class="symbol">.</span>BottomLeft<span class="symbol">:</span>
 cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>SizeNESW<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> DragHandleAnchor<span class="symbol">.</span>MiddleLeft<span class="symbol">:</span>
 <span class="keyword">case</span> DragHandleAnchor<span class="symbol">.</span>MiddleRight<span class="symbol">:</span>
 cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>SizeWE<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">default</span><span class="symbol">:</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentOutOfRangeException<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsMoving <span class="symbol">||</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Contains<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>PointToImage<span class="symbol">(</span>point<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>SizeAll<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>Default<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>Cursor <span class="symbol">=</span> cursor<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="initializing-a-move-or-a-drag">Initializing a move or a drag</h2>
<p>When the user first presses the left mouse button, check to see
if the cursor is within the bounds of the selection region, or
any visible drag handle. If so, we record the location of the
cursor, and it's offset to the upper left corner of the
selection region.</p>
<p>The original cursor location will be used as the origin, so once
the mouse starts moving, we use this to determine if a move
should occur, or a resize, or nothing.</p>
<p>The offset is used purely for moving, so that we reposition the
selection relative to the cursor position - otherwise it would
snap to the cursor which would look pretty awful.</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> OnMouseDown<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 Point imagePoint<span class="symbol">;</span>

 imagePoint <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PointToImage<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left <span class="symbol">&amp;&amp;</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Contains<span class="symbol">(</span>imagePoint<span class="symbol">)</span> <span class="symbol">||</span> <span class="keyword">this</span><span class="symbol">.</span>HitTest<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span> <span class="symbol">!=</span> DragHandleAnchor<span class="symbol">.</span>None<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragOrigin <span class="symbol">=</span> e<span class="symbol">.</span>Location<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragOriginOffset <span class="symbol">=</span> <span class="keyword">new</span> Point<span class="symbol">(</span>imagePoint<span class="symbol">.</span>X <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>X<span class="symbol">,</span> imagePoint<span class="symbol">.</span>Y <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragOriginOffset <span class="symbol">=</span> Point<span class="symbol">.</span>Empty<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>DragOrigin <span class="symbol">=</span> Point<span class="symbol">.</span>Empty<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnMouseDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Even if the user immediately moves the mouse, we don't want to
trigger a move or a resize - the mouse may have just twitched.
Instead, we wait until it moves beyond an area centred around
the drag origin - once it has, then we trigger the action.</p>
<p>This drag rectangle is determined via the
<code>SystemInformation.DragSize</code> (<a href="http://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&amp;l=EN-US&amp;k=k(System.Windows.Forms.SystemInformation.DragSize)%3bk(TargetFrameworkMoniker-.NETFramework%2cVersion%3dv2.0)%3bk(DevLang-csharp)&amp;rd=true" rel="external nofollow noopener">MSDN</a>) property.</p>
<p>During a mouse move, as well as triggering a move or resize, we
also need to <em>process</em> any in-progress action, as well as update
the cursor as described in the previous section.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">bool</span> IsOutsideDragZone<span class="symbol">(</span>Point location<span class="symbol">)</span>
<span class="symbol">{</span>
 Rectangle dragZone<span class="symbol">;</span>
 <span class="keyword">int</span> dragWidth<span class="symbol">;</span>
 <span class="keyword">int</span> dragHeight<span class="symbol">;</span>

 dragWidth <span class="symbol">=</span> SystemInformation<span class="symbol">.</span>DragSize<span class="symbol">.</span>Width<span class="symbol">;</span>
 dragHeight <span class="symbol">=</span> SystemInformation<span class="symbol">.</span>DragSize<span class="symbol">.</span>Height<span class="symbol">;</span>
 dragZone <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DragOrigin<span class="symbol">.</span>X <span class="symbol">-</span> <span class="symbol">(</span>dragWidth <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>DragOrigin<span class="symbol">.</span>Y <span class="symbol">-</span> <span class="symbol">(</span>dragHeight <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> dragWidth<span class="symbol">,</span> dragHeight<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> <span class="symbol">!</span>dragZone<span class="symbol">.</span>Contains<span class="symbol">(</span>location<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseMove<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// start either a move or a resize operation</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>IsSelecting <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>IsMoving <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>IsResizing <span class="symbol">&amp;&amp;</span> e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>DragOrigin<span class="symbol">.</span>IsEmpty <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>IsOutsideDragZone<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 DragHandleAnchor anchor<span class="symbol">;</span>

 anchor <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>HitTest<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DragOrigin<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>anchor <span class="symbol">==</span> DragHandleAnchor<span class="symbol">.</span>None<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// move</span>
 <span class="keyword">this</span><span class="symbol">.</span>StartMove<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>anchor<span class="symbol">]</span><span class="symbol">.</span>Enabled <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>DragHandles<span class="symbol">[</span>anchor<span class="symbol">]</span><span class="symbol">.</span>Visible<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// resize</span>
 <span class="keyword">this</span><span class="symbol">.</span>StartResize<span class="symbol">(</span>anchor<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="comment">// set the cursor</span>
 <span class="keyword">this</span><span class="symbol">.</span>SetCursor<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// perform operations</span>
 <span class="keyword">this</span><span class="symbol">.</span>ProcessSelectionMove<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>ProcessSelectionResize<span class="symbol">(</span>e<span class="symbol">.</span>Location<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnMouseMove<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Although I'm not going to include the code here as this article
is already very code heavy, the <code>StartMove</code> and <code>StartResize</code>
methods simply set some internal flags describing the control
state, and store a copy of the <code>SelectionRegion</code> property - I'll
explain why towards the end of the article. They also raise
events, both to allow the actions to be cancelled, or to allow
the application to update the user interface in some fashion.</p>
<h2 id="performing-the-move">Performing the move</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imageboxresize-1c.gif" class="gallery" title="Moving the selection around" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imageboxresize-1c.gif" alt="Moving the selection around" decoding="async" loading="lazy" /></a><figcaption>Moving the selection around</figcaption></figure>
<p>Performing the move is simple - we calculate the new position of
the selection region according to the cursor position, and
including the offset from the original drag for a smooth move.</p>
<p>We also check to ensure that the full bounds of the selection
region fit within the controls client area, preventing the user
from dragging out outside the bounds of the underlying
image/virtual size.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> ProcessSelectionMove<span class="symbol">(</span>Point cursorPosition<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsMoving<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>
 Point imagePoint<span class="symbol">;</span>

 imagePoint <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PointToImage<span class="symbol">(</span>cursorPosition<span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>

 x <span class="symbol">=</span> Math<span class="symbol">.</span>Max<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> imagePoint<span class="symbol">.</span>X <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>DragOriginOffset<span class="symbol">.</span>X<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>x <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Width <span class="symbol">&gt;=</span> <span class="keyword">this</span><span class="symbol">.</span>ViewSize<span class="symbol">.</span>Width<span class="symbol">)</span>
 <span class="symbol">{</span>
 x <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ViewSize<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Width<span class="symbol">;</span>
 <span class="symbol">}</span>

 y <span class="symbol">=</span> Math<span class="symbol">.</span>Max<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> imagePoint<span class="symbol">.</span>Y <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>DragOriginOffset<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>y <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Height <span class="symbol">&gt;=</span> <span class="keyword">this</span><span class="symbol">.</span>ViewSize<span class="symbol">.</span>Height<span class="symbol">)</span>
 <span class="symbol">{</span>
 y <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ViewSize<span class="symbol">.</span>Height <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Height<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> <span class="keyword">new</span> RectangleF<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Width<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="performing-the-resize">Performing the resize</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imageboxresize-1d.gif" class="gallery" title="Resizing the selection" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imageboxresize-1d.gif" alt="Resizing the selection" decoding="async" loading="lazy" /></a><figcaption>Resizing the selection</figcaption></figure>
<p>The resize code is also reasonably straight forward. We decide
which edges of the selection region we're going to adjust based
on the drag handle. Next, we get the position of the cursor
within the underlying view - snapped to fit within the bounds,
so that you can't size the region outside the view.</p>
<p>The we just update the edges based on this calculation. However,
we also ensure that the selection region is above a minimum
size. Apart from the fact that if the drag handles overlap it's
going to be impossible to size properly, you probably want to
force some minimum size constraints.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> ProcessSelectionResize<span class="symbol">(</span>Point cursorPosition<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsResizing<span class="symbol">)</span>
 <span class="symbol">{</span>
 Point imagePosition<span class="symbol">;</span>
 <span class="keyword">float</span> left<span class="symbol">;</span>
 <span class="keyword">float</span> top<span class="symbol">;</span>
 <span class="keyword">float</span> right<span class="symbol">;</span>
 <span class="keyword">float</span> bottom<span class="symbol">;</span>
 <span class="keyword">bool</span> resizingTopEdge<span class="symbol">;</span>
 <span class="keyword">bool</span> resizingBottomEdge<span class="symbol">;</span>
 <span class="keyword">bool</span> resizingLeftEdge<span class="symbol">;</span>
 <span class="keyword">bool</span> resizingRightEdge<span class="symbol">;</span>

 imagePosition <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PointToImage<span class="symbol">(</span>cursorPosition<span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// get the current selection</span>
 left <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Left<span class="symbol">;</span>
 top <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Top<span class="symbol">;</span>
 right <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Right<span class="symbol">;</span>
 bottom <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Bottom<span class="symbol">;</span>

 <span class="comment">// decide which edges we&#39;re resizing</span>
 resizingTopEdge <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">&gt;=</span> DragHandleAnchor<span class="symbol">.</span>TopLeft <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">&lt;=</span> DragHandleAnchor<span class="symbol">.</span>TopRight<span class="symbol">;</span>
 resizingBottomEdge <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">&gt;=</span> DragHandleAnchor<span class="symbol">.</span>BottomLeft <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">&lt;=</span> DragHandleAnchor<span class="symbol">.</span>BottomRight<span class="symbol">;</span>
 resizingLeftEdge <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">==</span> DragHandleAnchor<span class="symbol">.</span>TopLeft <span class="symbol">||</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">==</span> DragHandleAnchor<span class="symbol">.</span>MiddleLeft <span class="symbol">||</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">==</span> DragHandleAnchor<span class="symbol">.</span>BottomLeft<span class="symbol">;</span>
 resizingRightEdge <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">==</span> DragHandleAnchor<span class="symbol">.</span>TopRight <span class="symbol">||</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">==</span> DragHandleAnchor<span class="symbol">.</span>MiddleRight <span class="symbol">||</span> <span class="keyword">this</span><span class="symbol">.</span>ResizeAnchor <span class="symbol">==</span> DragHandleAnchor<span class="symbol">.</span>BottomRight<span class="symbol">;</span>

 <span class="comment">// and resize!</span>
 <span class="keyword">if</span> <span class="symbol">(</span>resizingTopEdge<span class="symbol">)</span>
 <span class="symbol">{</span>
 top <span class="symbol">=</span> imagePosition<span class="symbol">.</span>Y<span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>bottom <span class="symbol">-</span> top <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>MinimumSelectionSize<span class="symbol">.</span>Height<span class="symbol">)</span>
 <span class="symbol">{</span>
 top <span class="symbol">=</span> bottom <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>MinimumSelectionSize<span class="symbol">.</span>Height<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>resizingBottomEdge<span class="symbol">)</span>
 <span class="symbol">{</span>
 bottom <span class="symbol">=</span> imagePosition<span class="symbol">.</span>Y<span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>bottom <span class="symbol">-</span> top <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>MinimumSelectionSize<span class="symbol">.</span>Height<span class="symbol">)</span>
 <span class="symbol">{</span>
 bottom <span class="symbol">=</span> top <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>MinimumSelectionSize<span class="symbol">.</span>Height<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>resizingLeftEdge<span class="symbol">)</span>
 <span class="symbol">{</span>
 left <span class="symbol">=</span> imagePosition<span class="symbol">.</span>X<span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>right <span class="symbol">-</span> left <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>MinimumSelectionSize<span class="symbol">.</span>Width<span class="symbol">)</span>
 <span class="symbol">{</span>
 left <span class="symbol">=</span> right <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>MinimumSelectionSize<span class="symbol">.</span>Width<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>resizingRightEdge<span class="symbol">)</span>
 <span class="symbol">{</span>
 right <span class="symbol">=</span> imagePosition<span class="symbol">.</span>X<span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>right <span class="symbol">-</span> left <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>MinimumSelectionSize<span class="symbol">.</span>Width<span class="symbol">)</span>
 <span class="symbol">{</span>
 right <span class="symbol">=</span> left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>MinimumSelectionSize<span class="symbol">.</span>Width<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> <span class="keyword">new</span> RectangleF<span class="symbol">(</span>left<span class="symbol">,</span> top<span class="symbol">,</span> right <span class="symbol">-</span> left<span class="symbol">,</span> bottom <span class="symbol">-</span> top<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="finalizing-the-moveresize-operations">Finalizing the move/resize operations</h2>
<p>So far, we've used the <code>MouseDown</code> and <code>MouseMove</code> events to
control the initializing and processing of the actions. Now,
we've use the <code>MouseUp</code> event to finish things off - to reset
flags that describe the control state, and to raise events.</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> OnMouseUp<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsMoving<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CompleteMove<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsResizing<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CompleteResize<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnMouseUp<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="cancelling-a-move-or-resize-operation">Cancelling a move or resize operation</h2>
<p>Assuming the user has started moving the region or resizes it,
and then changes their mind. How to cancel? The easiest way is
to press the <code>Escape</code> key - and so that's what we'll implement.</p>
<p>We can do this by overriding <code>ProcessDialogKey</code>, checking for
<code>Escape</code> and then resetting the control state, and restoring the
<code>SelectionRegion</code> property using the copy we started at the
start of the operation.</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">bool</span> ProcessDialogKey<span class="symbol">(</span>Keys keyData<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>keyData <span class="symbol">==</span> Keys<span class="symbol">.</span>Escape <span class="symbol">&amp;&amp;</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsResizing <span class="symbol">||</span> <span class="keyword">this</span><span class="symbol">.</span>IsMoving<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsResizing<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CancelResize<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CancelMove<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 result <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> <span class="keyword">base</span><span class="symbol">.</span>ProcessDialogKey<span class="symbol">(</span>keyData<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="wrapping-up">Wrapping up</h2>
<p>That covers most of the important code for making these
techniques work, although it's incomplete, so please download
the latest version for the full source. And I hope you find this
addition to the <code>ImageBox</code> component useful!</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2014-02-13 - 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/adding-drag-handles-to-an-imagebox-to-allow-resizing-of-selection-regions .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comHow to be notified when your application is activated and deactivatedurn:uuid:108e9c03-80dc-4e9c-9c2e-0ea57ccf9cb62013-12-29T13:19:24Z2013-12-29T13:19:24Z<p>I recently had a requirement where a user was able to perform an
action externally to my application, and my application then had
to detect this for processing.</p>
<p>I could of course just had a poller running away in the
background to check, but as the requirement also needed user
input, why not just wait until the user switched back to my
application, then check and deal with accordingly?</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/appactivation.png" class="gallery" title="A simple demonstration of application activation and deactivation" ><img src="https://images.cyotek.com/image/thumbnail/devblog/appactivation.png" alt="A simple demonstration of application activation and deactivation" decoding="async" loading="lazy" /></a><figcaption>A simple demonstration of application activation and deactivation</figcaption></figure><h2 id="the-activated-and-deactivate-events">The Activated and Deactivate events</h2>
<p>Your standard <code>Form</code> object has an <code>Activated</code> event and a
<code>Deactivate</code> event that (very logically!) are fired when your
form is activated and deactivated.</p>
<p>Brilliant! Well, it would be - if your application is a single
form application that doesn't show any window <em>ever</em>. Including
message boxes. Still, you could do it that way, if your
processing was absolute and always done there and then. In my
case, I had to give the user a choice - if they said no, then I
would simply ask them again later (ie the next time the
application was activated). But if I stuck with the <code>Activated</code>
event it would mean they would get prompted after displaying
another dialog in my application... probably not the sort of
behaviour you want to exhibit.</p>
<h2 id="the-wm_activateapp-message">The WM_ACTIVATEAPP message</h2>
<p>So how does Windows do it? It does it via the use of the
<code>WM_ACTIVATEAPP</code> (<a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms632614(v=vs.85).aspx" rel="external nofollow noopener">MSDN reference</a>). Windows sends this
message when a window belonging to a different application than
the active window is about to be activated. It's also good
enough enough to send it to the window about to be deactivated
too, so this one message can cover both activation and
deactivation.</p>
<h2 id="overriding-wndproc">Overriding WndProc</h2>
<p>Even after all these years of being a C# developer, I still
marvel at just how easy this stuff is. In my VB6 days, I could
(and did) do exactly the same thing, but it was difficult to
implement and fun to debug.</p>
<p>To handle Windows Messages in C# we just need to override the
<code>WndProc</code> method on your <code>Form</code>. Then we can check for the
<code>WM_ACTIVATEAPP</code> message arriving and handle it accordingly.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> WM_ACTIVATEAPP <span class="symbol">=</span> <span class="number">0x1C</span><span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> WM_ACTIVATEAPP<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>WParam <span class="symbol">!=</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// the application is getting activated</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="comment">// the application is getting deactivated</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>According to the MSDN docs, <code>lParam</code> contains the thread
identifier, however we can safely ignore this. <code>wParam</code> (the
<code>WParam</code> property on the <code>Message</code> struct) is the important bit.
It's a simple true or false value (true for activation, false
otherwise) and so we just need to check it isn't zero and we're
good to go.</p>
<h2 id="add-some-events">Add some events</h2>
<p>You could just stick that override in your applications form and
use it like that, but as always we should think a little bit
about the future and promote some code re-use! In this case, I'm
going to create a pair of events and add them (along with the
override) to a base class, so next time I want this
functionality the events are sitting there waiting.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">class</span> BaseApplicationForm <span class="symbol">:</span> Form
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">event</span> EventHandler ApplicationActivated<span class="symbol">;</span>

 <span class="keyword">public</span> <span class="keyword">event</span> EventHandler ApplicationDeactivated<span class="symbol">;</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnApplicationActivated<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 EventHandler handler<span class="symbol">;</span>

 handler <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ApplicationActivated<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>handler <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 handler<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnApplicationDeactivated<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 EventHandler handler<span class="symbol">;</span>

 handler <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ApplicationDeactivated<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>handler <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 handler<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg <span class="symbol">==</span> NativeMethods<span class="symbol">.</span>WM_ACTIVATEAPP<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>m<span class="symbol">.</span>WParam <span class="symbol">!=</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnApplicationActivated<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnApplicationDeactivated<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="a-simple-demonstration">A simple demonstration</h2>
<p>You can download the demonstration code from the end of this
post, but here's a simple example of the code in use:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">partial</span> <span class="keyword">class</span> MainForm <span class="symbol">:</span> BaseApplicationForm
<span class="symbol">{</span>
 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnActivated<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnActivated<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>LogEvent<span class="symbol">(</span><span class="string">&quot;Activated&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnDeactivate<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnDeactivate<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>LogEvent<span class="symbol">(</span><span class="string">&quot;Deactivate&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnApplicationActivated<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnApplicationActivated<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>LogEvent<span class="symbol">(</span><span class="string">&quot;ApplicationActivated&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnApplicationDeactivated<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnApplicationDeactivated<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>LogEvent<span class="symbol">(</span><span class="string">&quot;ApplicationDeactivated&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> LogEvent<span class="symbol">(</span><span class="keyword">string</span> text<span class="symbol">)</span>
 <span class="symbol">{</span>
 logTextBox<span class="symbol">.</span>AppendText<span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;{0}\t{1}\n\n&quot;</span><span class="symbol">,</span> DateTime<span class="symbol">.</span>UtcNow<span class="symbol">.</span>ToString<span class="symbol">(</span><span class="string">&quot;hh:mm:ss&quot;</span><span class="symbol">)</span><span class="symbol">,</span> text<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>Activated</code>, <code>Deactivate</code>, <code>ApplicationActivated</code> and
<code>ApplicationDeactivated</code> events are all being used (well, their
overrides are) to log information. If you run the example, you
will see that <code>Activated</code> and <code>Deactivate</code> are called whenever
the form itself is processed, ie from opening a dialog or
displaying a message box, whilst the <code>ApplicationActivated</code> and
<code>ApplicationDeactivated</code> events are only called when I switch to
another application.</p>
<h2 id="closing-thoughts">Closing Thoughts</h2>
<p>In the above example, the <code>ApplicationActivated</code> and
<code>ApplicationDeactivated</code> events are raised as expected -
including if you already have a modal dialog open in your
application. Just something to keep in mind if you use this sort
of functionality to prompt the user for an action. If your
events are showing their own modal dialog, that's fine, but if
they are trying to do something else, such as set focus to a
control, or open a floating window - then you're likely to run
into problems.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2013-12-29 - 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/how-to-be-notified-when-your-application-is-activated-and-deactivated .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comExtending the LabelEdit functionality of a TreeView to include validationurn:uuid:9f45ec5f-59c3-4fff-8fca-511e794277912013-10-28T09:14:14Z2013-10-28T09:14:14Z<p>In my <a href="/post/specifying-custom-text-when-using-the-labeledit-functionality-of-a-treeview">last post</a> I described how to extend the default label
edit functionality of a <code>TreeView</code> control to be somewhat more
flexible, by allowing you to specify custom text other than
blindly using the text of the <code>TreeNode</code> being edited.</p>
<p>This post will extend the original code to include custom
validation. For example, you may wish to restrict the available
characters, or check to see if the value entered doesn't match
an existing value.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/treeview-labeledit-validation.gif" class="gallery" title="An example project showing the use of validation in node editing, and preserving the entered text between errors" ><img src="https://images.cyotek.com/image/thumbnail/devblog/treeview-labeledit-validation.gif" alt="An example project showing the use of validation in node editing, and preserving the entered text between errors" decoding="async" loading="lazy" /></a><figcaption>An example project showing the use of validation in node editing, and preserving the entered text between errors</figcaption></figure><h2 id="getting-started">Getting started</h2>
<p>The code in this article assumes you have a base class that
includes the enhancements from the <a href="/post/specifying-custom-text-when-using-the-labeledit-functionality-of-a-treeview">previous post</a>, or you
can download the complete example from the link at the end of
the article.</p>
<p>Firstly we need to add a new event that will present the
proposed change and allow the implementer to validate it. As
this is event won't allow the label to be modified, we can use
the original <code>NodeLabelEditEventArgs</code> class rather than the
custom class we created in the previous post.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Category<span class="symbol">(</span><span class="string">&quot;Behavior&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">event</span> EventHandler<span class="symbol">&lt;</span>NodeLabelEditEventArgs<span class="symbol">&gt;</span> ValidateLabelEdit<span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnValidateLabelEdit<span class="symbol">(</span>NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 EventHandler<span class="symbol">&lt;</span>NodeLabelEditEventArgs<span class="symbol">&gt;</span> handler<span class="symbol">;</span>

 handler <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ValidateLabelEdit<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>handler <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 handler<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>We also need a backing variable to store the current text string
in the event of a validation error in order to correctly
re-initialize the edit field.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">string</span> _postEditText<span class="symbol">;</span>
</pre>
</figure>
<h2 id="raising-the-validation-event">Raising the validation event</h2>
<p>In our extended <code>TreeView</code> component, we had overridden
<code>OnAfterLabelEdit</code> in order to obtain the new display text after
a successful edit. We're going to modify this override slightly
in order to handle validation.</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> OnAfterLabelEdit<span class="symbol">(</span>NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Label <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span> <span class="comment">// if the user cancelled the edit this event is still raised, just with a null label</span>
 <span class="symbol">{</span>
 NodeLabelEditEventArgs validateEventArgs<span class="symbol">;</span>

 e<span class="symbol">.</span>CancelEdit <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span> <span class="comment">// cancel the built in operation so we can substitute our own</span>

 validateEventArgs <span class="symbol">=</span> <span class="keyword">new</span> NodeLabelEditEventArgs<span class="symbol">(</span>e<span class="symbol">.</span>Node<span class="symbol">,</span> e<span class="symbol">.</span>Label<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnValidateLabelEdit<span class="symbol">(</span>validateEventArgs<span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// validate the users input</span>

 <span class="keyword">if</span> <span class="symbol">(</span>validateEventArgs<span class="symbol">.</span>CancelEdit<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// if the users input was invalid, enter edit mode again using the previously entered text to give them the chance to correct it</span>
 _postEditText <span class="symbol">=</span> e<span class="symbol">.</span>Label<span class="symbol">;</span>
 e<span class="symbol">.</span>Node<span class="symbol">.</span>BeginEdit<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="comment">// -- snip --</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnAfterLabelEdit<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Here, we automatically cancel the default handling of the label
edit, as regardless of whether validation passes or not, we'll
be updating node text manually.</p>
<p>First we raise our <code>ValidateLabelEdit</code> event, passing in the
<code>TreeNode</code> to be edited, and the proposed label text. If the
<code>CancelEdit</code> property of the passed <code>NodeLabelEditEventArgs</code> is
set to <code>true</code>, then validation has failed.</p>
<p>If validation does fail, we update the <code>_postEditText</code> variable
we defined earlier with the current label text, then
automatically switch the control back into label editing mode.</p>
<h2 id="changing-how-label-edits-are-initialized">Changing how label edits are initialized</h2>
<p>There's just one thing left to change. As with <code>OnAfterLabelEdit</code>, we had also overridden <code>OnBeforeLabelEdit</code> in order to modify the text displayed in the edit field. We'll need to modify this to provide the current label value if a validation error occurs, otherwise the text will reset to whatever the original value was before editing started. Of course, in the event of a validation error you want he user to be able to retry with the modified value to allow correction of the error. To do this, we'll modify the block of code that obtained the text to display to use the new <code>_postEditText</code> variable and to skip raising the <code>RequestEditText</code> event if its set. We'll also reset the <code>_postEditText</code> to <code>null</code> so that the next time an edit is started, it reverts to the original behaviour. Unless it's another validation error for the current edit operation of course!</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> OnBeforeLabelEdit<span class="symbol">(</span>NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 NodeRequestTextEventArgs editTextArgs<span class="symbol">;</span>

 <span class="comment">// get the text to apply to the label</span>
 editTextArgs <span class="symbol">=</span> <span class="keyword">new</span> NodeRequestTextEventArgs<span class="symbol">(</span>e<span class="symbol">.</span>Node<span class="symbol">,</span> _postEditText <span class="symbol">??</span> e<span class="symbol">.</span>Node<span class="symbol">.</span>Text<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_postEditText <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnRequestEditText<span class="symbol">(</span>editTextArgs<span class="symbol">)</span><span class="symbol">;</span>
 _postEditText <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>

 <span class="comment">// -- snip --</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnBeforeLabelEdit<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>And that is it. Extremely simple, but very useful if you need to
validate this sort of input!</p>
<h2 id="sample-application">Sample application</h2>
<p>The sample project available with this article demonstrates
validation, as shown in the following snippet.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> subclassedTreeView_ValidateLabelEdit<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 TreeNode<span class="symbol">[</span><span class="symbol">]</span> matchingNodes<span class="symbol">;</span>

 <span class="comment">// Check to make sure the value the user enters isn&#39;t used by any other node than the current.</span>
 <span class="comment">// This code assumes that all names in the tree are unique, regardless of level</span>
 matchingNodes <span class="symbol">=</span> subclassedTreeView<span class="symbol">.</span>Nodes<span class="symbol">.</span>Find<span class="symbol">(</span>e<span class="symbol">.</span>Label<span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>matchingNodes<span class="symbol">.</span>Length <span class="symbol">!=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> matchingNodes<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span> <span class="symbol">!=</span> e<span class="symbol">.</span>Node<span class="symbol">)</span>
 <span class="symbol">{</span>
 MessageBox<span class="symbol">.</span>Show<span class="symbol">(</span><span class="string">&quot;You must enter a unique value.&quot;</span><span class="symbol">,</span> <span class="string">&quot;Validation Error&quot;</span><span class="symbol">,</span> MessageBoxButtons<span class="symbol">.</span>OK<span class="symbol">,</span> MessageBoxIcon<span class="symbol">.</span>Exclamation<span class="symbol">)</span><span class="symbol">;</span>
 e<span class="symbol">.</span>CancelEdit <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="further-improvements">Further Improvements</h2>
<p>As can be seen from the simple animation at the start of the
article, the edit field is hidden and the original node text
displayed, validation occurs, then editing restarts in the event
of an error. This means, if you display a message box for
example, the original tree state is displayed. It also means
that the cursor and selection state of the edit field is lost.
Ideally, it would be preferable to do validation without causing
the edit field to vanish first, but that would require some more
p-invoke, and probably isn't necessary for most cases - this
method keeps the users entered text which is the important bit.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2013-10-28 - First published</li>
<li>2020-11-21 - Updated formatting, corrected misspellings</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/extending-the-labeledit-functionality-of-a-treeview-to-include-validation .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comSpecifying custom text when using the LabelEdit functionality of a TreeViewurn:uuid:b7a03afe-5f8b-4cdf-a09b-58eb19ca575e2013-10-28T09:09:46Z2013-10-28T09:09:46Z<p>Recently I was updating a support tool that displays documents
in raw form and allows editing of them. This tool is centred
around a <code>TreeView</code>, and the <code>Text</code> property of each <code>TreeNode</code>
is a concatenation of a name and one or more values.</p>
<p>The problem with this approach is if you wish to allow the nodes
to be edited using the built in functionality you're in trouble
as by default you can't actually influence the text that appears
in the in-line editor. In other applications of a similar nature
I used owner-drawn trees as I was using different styles for the
name and the value. In this case, I just wanted the standard
look.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/treeview-labeledit.gif" class="gallery" title="An example project showing the different techniques described in this article" ><img src="https://images.cyotek.com/image/thumbnail/devblog/treeview-labeledit.gif" alt="An example project showing the different techniques described in this article" decoding="async" loading="lazy" /></a><figcaption>An example project showing the different techniques described in this article</figcaption></figure><h2 id="how-you-would-expect-it-to-work">How you would expect it to work</h2>
<p>Ideally, you'd expect that by hooking into the <code>BeforeLabelEdit</code>
event (or overriding <code>OnBeforeLabelEdit</code>) then you could
manipulate the <code>NodeLabelEditEventArgs.Label</code> property. Except
this property is read only.</p>
<p>Scratch that then. What about setting the <code>TreeNode.Text</code>
property to something else in this event, then resetting it
afterwards? Nope, doesn't work either.</p>
<p>Therefore, using just the managed code of the <code>TreeView</code> it's
not possible to do what we want. Lets get slightly outside the
black box with a little Win32 API. We'll get the handle of the
<code>edit</code> control the <code>TreeView</code> is using and directly set it's
text.</p>
<h2 id="getting-the-handle-to-the-edit-control">Getting the handle to the EDIT control</h2>
<p>In order to manipulate the edit control, we first need to get a
handle to it. We can do this succinctly by overriding
<code>OnBeforeLabelEdit</code> (or hooking the <code>BeforeLabelEdit</code> event
although the former is preferable) and using the
<code>TVM_GETEDITCONTROL</code> message.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;USER32&quot;</span><span class="symbol">,</span> EntryPoint <span class="symbol">=</span> <span class="string">&quot;SendMessage&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Auto<span class="symbol">,</span> SetLastError <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> IntPtr SendMessage<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">int</span> msg<span class="symbol">,</span> IntPtr wParam<span class="symbol">,</span> IntPtr lParam<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">public</span> <span class="keyword">const</span> <span class="keyword">int</span> TVM_GETEDITCONTROL <span class="symbol">=</span> <span class="number">0x110F</span><span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnBeforeLabelEdit<span class="symbol">(</span>NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 IntPtr editHandle<span class="symbol">;</span>

 editHandle <span class="symbol">=</span> SendMessage<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> TVM_GETEDITCONTROL<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>editHandle <span class="symbol">!=</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// we have a handle, lets use it!</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnBeforeLabelEdit<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="setting-the-text-of-the-edit-control">Setting the text of the EDIT control</h2>
<p>Now that we have a handle, we can painlessly use <code>WM_SETTEXT</code> to
change the text of the edit control</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;USER32&quot;</span><span class="symbol">,</span> EntryPoint <span class="symbol">=</span> <span class="string">&quot;SendMessage&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Auto<span class="symbol">,</span> SetLastError <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> IntPtr SendMessage<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">int</span> msg<span class="symbol">,</span> IntPtr wParam<span class="symbol">,</span> <span class="keyword">string</span> lParam<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">public</span> <span class="keyword">const</span> <span class="keyword">int</span> WM_SETTEXT <span class="symbol">=</span> <span class="number">0xC</span><span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnBeforeLabelEdit<span class="symbol">(</span>NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// -- snip --</span>

 <span class="keyword">if</span> <span class="symbol">(</span>editHandle <span class="symbol">!=</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span>
 SendMessage<span class="symbol">(</span>editHandle<span class="symbol">,</span> WM_SETTEXT<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">,</span> <span class="string">&quot;Magic String&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// -- snip --</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="ok-but-what-about-specifying-the-real-text">OK, but what about specifying the real text?</h2>
<p>If you were hooking into the <code>BeforeLabelEdit</code> event, then you
can just have your own logic in that event to determine the text
to apply. If however you're overriding <code>OnBeforeEdit</code> in order
to make a nice reusable component, you need another way of
allowing implementers to specify the value. For this, I added a
new event to the control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Category<span class="symbol">(</span><span class="string">&quot;Behavior&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">event</span> EventHandler<span class="symbol">&lt;</span>NodeRequestTextEventArgs<span class="symbol">&gt;</span> RequestEditText<span class="symbol">;</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnRequestEditText<span class="symbol">(</span>NodeRequestTextEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 EventHandler<span class="symbol">&lt;</span>NodeRequestTextEventArgs<span class="symbol">&gt;</span> handler<span class="symbol">;</span>

 handler <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>RequestEditText<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>handler <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 handler<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>NodeRequestTextEventArgs</code> class is essentially a clone of
<code>NodeLabelEditEventArgs</code> except with a writeable <code>Label</code>
property. I also decided to allow you to cancel the node edit
from this event, so that implementers don't have to hook both
events unless necessary.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">class</span> NodeRequestTextEventArgs <span class="symbol">:</span> CancelEventArgs
<span class="symbol">{</span>
 <span class="keyword">public</span> NodeRequestTextEventArgs<span class="symbol">(</span>TreeNode node<span class="symbol">,</span> <span class="keyword">string</span> label<span class="symbol">)</span>
 <span class="symbol">:</span> <span class="keyword">this</span><span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Node <span class="symbol">=</span> node<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Label <span class="symbol">=</span> label<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> NodeRequestTextEventArgs<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span> <span class="symbol">}</span>

 <span class="keyword">public</span> TreeNode Node <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">protected</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">string</span> Label <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Our final version now looks like this:</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> OnBeforeLabelEdit<span class="symbol">(</span>NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 NodeRequestEditTextEventArgs editTextArgs<span class="symbol">;</span>

 <span class="comment">// get the text to apply to the label</span>
 editTextArgs <span class="symbol">=</span> <span class="keyword">new</span> NodeRequestTextEventArgs<span class="symbol">(</span>e<span class="symbol">.</span>Node<span class="symbol">,</span> e<span class="symbol">.</span>Node<span class="symbol">.</span>Text<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnRequestEditText<span class="symbol">(</span>editTextArgs<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// cancel the edit if required</span>
 <span class="keyword">if</span> <span class="symbol">(</span>editTextArgs<span class="symbol">.</span>Cancel<span class="symbol">)</span>
 e<span class="symbol">.</span>CancelEdit <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>

 <span class="comment">// apply the text to the EDIT control</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>e<span class="symbol">.</span>CancelEdit<span class="symbol">)</span>
 <span class="symbol">{</span>
 IntPtr editHandle<span class="symbol">;</span>

 editHandle <span class="symbol">=</span> SendMessage<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> TVM_GETEDITCONTROL<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// Get the handle of the EDIT control</span>
 <span class="keyword">if</span> <span class="symbol">(</span>editHandle <span class="symbol">!=</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span>
 SendMessage<span class="symbol">(</span>editHandle<span class="symbol">,</span> WM_SETTEXT<span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">,</span> editTextArgs<span class="symbol">.</span>Label<span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// And apply the text. Simples.</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnBeforeLabelEdit<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>And an sample usage scenario from the demo application:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> subclassedTreeView_RequestEditText<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> NodeRequestEditTextEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 e<span class="symbol">.</span>Label <span class="symbol">=</span> e<span class="symbol">.</span>Node<span class="symbol">.</span>Name<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In this example, we are setting the edit text to be the value of
the <code>TreeNode</code>'s <code>Name</code> property, regardless of whatever its
<code>Text</code> is.</p>
<h2 id="updating-the-text-post-edit">Updating the text post-edit</h2>
<p>After the conclusion of the label editing, the node's text will
be set to the new label, and therefore we need to tinker that
logic to allow the implementer to specify the new value text.</p>
<p>You could just hook the <code>AfterLabelEdit</code> event and have your
custom logic in there (remembering to cancel the default edit),
as shown here:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> notifyTreeView_AfterLabelEdit<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Label <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 e<span class="symbol">.</span>CancelEdit <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>

 e<span class="symbol">.</span>Node<span class="symbol">.</span>Name <span class="symbol">=</span> e<span class="symbol">.</span>Label<span class="symbol">;</span>
 e<span class="symbol">.</span>Node<span class="symbol">.</span>Text <span class="symbol">=</span> <span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;{0}: {1}&quot;</span><span class="symbol">,</span> e<span class="symbol">.</span>Label<span class="symbol">,</span> e<span class="symbol">.</span>Node<span class="symbol">.</span>Tag<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>However, I didn't want to be having to do this type of code each
time I implemented this sort of behaviour in an application.
Rather than get fancy with subclassed <code>TreeNode</code> classes, I
choose to add a sister event for <code>RequestEditText</code>, named
<code>RequestDisplayText</code> and then handle this automatically. This is
the only aspect of this article that feels &quot;smelly&quot; to me -
ideally it would be nice if the control could handle this for
you without having to ask for more information. But, this should
do for the time being.</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> OnAfterLabelEdit<span class="symbol">(</span>NodeLabelEditEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Label <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span> <span class="comment">// if the user cancelled the edit this event is still raised, just with a null label</span>
 <span class="symbol">{</span>
 NodeRequestTextEventArgs displayTextArgs<span class="symbol">;</span>

 displayTextArgs <span class="symbol">=</span> <span class="keyword">new</span> NodeRequestTextEventArgs<span class="symbol">(</span>e<span class="symbol">.</span>Node<span class="symbol">,</span> e<span class="symbol">.</span>Label<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnRequestDisplayText<span class="symbol">(</span>displayTextArgs<span class="symbol">)</span><span class="symbol">;</span>

 e<span class="symbol">.</span>CancelEdit <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span> <span class="comment">// cancel the built in operation so we can substitute our own</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>displayTextArgs<span class="symbol">.</span>Cancel<span class="symbol">)</span>
 e<span class="symbol">.</span>Node<span class="symbol">.</span>Text <span class="symbol">=</span> displayTextArgs<span class="symbol">.</span>Label<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnAfterLabelEdit<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>And an example of 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> subclassedTreeView_RequestDisplayText<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> NodeRequestTextEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 e<span class="symbol">.</span>Label <span class="symbol">=</span> <span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;{0}: {1}&quot;</span><span class="symbol">,</span> e<span class="symbol">.</span>Label<span class="symbol">,</span> e<span class="symbol">.</span>Node<span class="symbol">.</span>Tag<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The demonstration shows both of these approaches - the
<code>TreeViewEx</code> control favours the <code>RequestDisplayText</code> event, and
the <code>TreeViewExNotify</code> control leaves it entirely to the
implementer to deal with.</p>
<h2 id="closing-notes">Closing notes</h2>
<p>And that's it. I've seen some implementations of this sort of
functionality in various places across the internet, and some of
them are pretty awful, having to override all sorts of methods,
store and restore various states. The above solution is pretty
simple and works regardless of if you are calling
<code>TreeNode.BeginEdit</code> or using the &quot;click and hover&quot; approach on
a node.</p>
<p>In addition, it's trivially easy to expand this to support
validation as well, I'll cover that in the next article.</p>
<h2 id="bonus-chatter">Bonus Chatter</h2>
<p>I originally tried two different approaches to modifying the
value, both of these involved capturing the <code>TVN_BEGINLABELEDIT</code>
notification. The first approach used a <code>NativeWindow</code> bound to
the <code>TreeView</code> control's parent watching for the <code>WM_NOTIFY</code>
message. The second approach did the same thing, but used MFC's
<a href="http://msdn.microsoft.com/en-us/library/eeah46xd.aspx" rel="external nofollow noopener">Message Reflection</a> via <code>WM_REFLECT</code> to intercept the
notification message on the tree view itself. Both of these
solutions worked, and yet were still overkill as overriding
<code>OnBeforeLabelEdit</code> is sufficient.</p>
<p>Although I'm not going to describe that approach here as it'll
just clutter the article, I did include an implementation of the
<code>WM_REFLECT</code> solution in the demonstration project as I think it
is a neat technique and potentially useful for other
notifications.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2013-10-28 - 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/specifying-custom-text-when-using-the-labeledit-functionality-of-a-treeview .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comGetting the hWnd of the edit component within a ComboBox controlurn:uuid:ab103b9b-0e7d-4d13-8f7f-1621542657f92013-09-29T15:03:10Z2013-09-29T15:03:10Z<p>I'm currently in the process of dusting off the bugs from a tool
written back in 2011. One of the bugs involves an editable
<code>ComboBox</code> control paired with a separate <code>Button</code> control. When
the button is clicked a popup menu is shown, and when an item in
this menu is clicked, the text of the <code>ComboBox</code> is updated.</p>
<p>The problem with this scenario is by default, as soon as the
<code>ComboBox</code> loses focus, the <code>SelectionStart</code>, <code>SelectionLength</code>
and <code>SelectedText</code> properties are reset, thus preventing my
little menu from replacing the selected text. While this article
doesn't solve this problem (I'll save that for the next
article!), it does describe how you can get the <code>hWnd</code>, or
window handle (in .NET terms the <code>Handle</code>) of the edit
component, thus opening the door for a little Win32 API fun.</p>
<p>There are various approaches to doing this - you could use
<code>FindWindow</code> and search for a child window with a class of
<code>edit</code>, or use <code>SendMessage</code> with the <code>CB_GETCOMBOBOXINFO</code>
message. Or, easiest of all, we can use the <code>GetComboBoxInfo</code>
function.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/getcomboboxinfo1a.png" class="gallery" title="A simple demonstration application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/getcomboboxinfo1a.png" alt="A simple demonstration application" decoding="async" loading="lazy" /></a><figcaption>A simple demonstration application</figcaption></figure><h2 id="interop-declarations">Interop Declarations</h2>
<p>Using this API is very simple, as there's one method and two
simple structs.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> GetComboBoxInfo<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">ref</span> COMBOBOXINFO pcbi<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>StructLayout<span class="symbol">(</span>LayoutKind<span class="symbol">.</span>Sequential<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">struct</span> COMBOBOXINFO
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">int</span> cbSize<span class="symbol">;</span>
 <span class="keyword">public</span> RECT rcItem<span class="symbol">;</span>
 <span class="keyword">public</span> RECT rcButton<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> stateButton<span class="symbol">;</span>
 <span class="keyword">public</span> IntPtr hwndCombo<span class="symbol">;</span>
 <span class="keyword">public</span> IntPtr hwndEdit<span class="symbol">;</span>
 <span class="keyword">public</span> IntPtr hwndList<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="symbol">[</span>StructLayout<span class="symbol">(</span>LayoutKind<span class="symbol">.</span>Sequential<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">struct</span> RECT
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">int</span> left<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> top<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> right<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> bottom<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="calling-the-api">Calling the API</h2>
<p>There is one (well, many, but lets start simple!) caveat which
you may fall foul of if you are new to Win32 API programming -
often the contents of structs need to be initialized with their
size first. But how to you know how big a structure is? Lucky
for you, you don't need to calculate it manually - the
<code>Marshal.SizeOf</code> function will handle this for you.</p>
<p>If you forget to set the size, then the structure simply won't
be populated.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
NativeMethods<span class="symbol">.</span>COMBOBOXINFO info<span class="symbol">;</span>

info <span class="symbol">=</span> <span class="keyword">new</span> NativeMethods<span class="symbol">.</span>COMBOBOXINFO<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
info<span class="symbol">.</span>cbSize <span class="symbol">=</span> Marshal<span class="symbol">.</span>SizeOf<span class="symbol">(</span>info<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">if</span> <span class="symbol">(</span>NativeMethods<span class="symbol">.</span>GetComboBoxInfo<span class="symbol">(</span>control<span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">ref</span> info<span class="symbol">)</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// the info structure is now populated, go wild!</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="getting-the-edit-control-handle">Getting the edit control handle</h2>
<p>With the above in place, then getting the handle of the edit
component is very straightforward.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> IntPtr GetEditHandle<span class="symbol">(</span>ComboBox control<span class="symbol">)</span>
<span class="symbol">{</span>
 NativeMethods<span class="symbol">.</span>COMBOBOXINFO info<span class="symbol">;</span>

 info <span class="symbol">=</span> <span class="keyword">new</span> NativeMethods<span class="symbol">.</span>COMBOBOXINFO<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 info<span class="symbol">.</span>cbSize <span class="symbol">=</span> Marshal<span class="symbol">.</span>SizeOf<span class="symbol">(</span>info<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> NativeMethods<span class="symbol">.</span>GetComboBoxInfo<span class="symbol">(</span>control<span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="keyword">ref</span> info<span class="symbol">)</span> <span class="symbol">?</span> info<span class="symbol">.</span>hwndEdit <span class="symbol">:</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Bear in mind that the <code>edit</code> control is a Win32 control - not a
managed .NET control. In order to do anything with this
therefore, you need to use additional Win32 API methods, or
perhaps bind it to a <code>NativeWindow</code> class for easy subclassing.
I'll briefly cover some of this in a future article.</p>
<h2 id="other-goodies">Other goodies</h2>
<p>The <code>COMBOBOXINFO</code> structure has other information, such as the
handle of the <code>list</code> control, which you see if you set the
<code>DropDownStyle</code> property of the <code>ComboBox</code> to <code>Simple</code> and the
state of the dropdown button. You can view the <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/bb775798(v=vs.85).aspx" rel="external nofollow noopener">MSDN
Documentation</a> to learn more about the structure.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2013-09-29 - 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/getting-the-hwnd-of-the-edit-component-within-a-combobox-control .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating long running Windows Forms applications without a start-up formurn:uuid:1cfe3223-0f99-4870-a7d5-7e915e627eb22013-08-26T18:59:30Z2013-08-26T18:59:30Z<p>Sometimes you may wish to create an application that sits
running in the background but doesn't actually display an
initial user interface. However, the user can interact with the
application and so therefore its not appropriate to be a
service. Often such applications are accessible from a system
tray icon. Another viable requirement might be for multiple top
level windows, for example recent versions of Microsoft Word,
where each document has its own application window.</p>
<p>By default however, a normal Windows Form application displays a
single start-up form which definitely isn't desirable when you
want to have a hidden UI, especially as hiding this form isn't
as straightforward as you might expect. Fortunately however, the
framework provides us with the <code>ApplicationContext</code> class that
we can use to create a different approach to managing the
application.</p>
<h2 id="getting-started">Getting Started</h2>
<p>If you look in <code>Program.Main</code>, you'll see code similar to the
following:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Application<span class="symbol">.</span>EnableVisualStyles<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
Application<span class="symbol">.</span>SetCompatibleTextRenderingDefault<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
Application<span class="symbol">.</span>Run<span class="symbol">(</span><span class="keyword">new</span> MainForm<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>The <code>Application.Run</code> statement is the critical aspect, and it
operates by creating a new instance of your start-up form which,
when closed, causes the application to end. The <code>Run</code> method
also allows you to pass in a custom <code>ApplicationContext</code> class
instead which can be used for more flexibility. In order to exit
the application when using this class, you can either call the
static <code>Application.ExitThread</code> or the <code>ExitThread</code> method of
the <code>ApplicationContext</code> class. In additional,
<code>Application.Exit</code> seems to work just as well.</p>
<p>To start with, we're going to create a basic class that inherits
from <code>ApplicationContent</code> and provides system tray icon and
context menu support. To that end, we'll create a class named
<code>TrayIconApplicationContext</code>.</p>
<p>The first thing we need to do is hook into the <code>ApplicationExit</code>
event. We'll use this for clean-up purposes no matter if the
application is shut down via our new class, or other code
calling <code>ExitThread</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> TrayIconApplicationContext <span class="symbol">:</span> ApplicationContext
<span class="symbol">{</span>
 <span class="keyword">protected</span> TrayIconApplicationContext<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Application<span class="symbol">.</span>ApplicationExit <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>ApplicationExitHandler<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnApplicationExit<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>

 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> ApplicationExitHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnApplicationExit<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>When the event handler is triggered, it calls the
<code>OnApplicationExit</code> virtual method. This makes it easier for
inheritors of this class to provide their own clean up behaviour
without hooking into events. It seems a shame there isn't an
existing method to override in the first place without the the
initial hooking of events, but it's a minor thing.</p>
<h2 id="adding-the-tray-icon">Adding the tray icon</h2>
<p>Now that we have the basic infrastructure in place, we can add
our tray icon. To do this we'll create an instance of the
<code>NotifyIcon</code> component, accessible via an protected property.
We'll also automatically hook into the <code>Click</code> and <code>DoubleClick</code>
events of the icon and provide virtual methods for inheritors.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">readonly</span> NotifyIcon _notifyIcon<span class="symbol">;</span>

<span class="keyword">protected</span> TrayIconApplicationContext<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 Application<span class="symbol">.</span>ApplicationExit <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>ApplicationExitHandler<span class="symbol">;</span>

 _notifyIcon <span class="symbol">=</span> <span class="keyword">new</span> NotifyIcon
 <span class="symbol">{</span>
 Text <span class="symbol">=</span> Application<span class="symbol">.</span>ProductName<span class="symbol">,</span>
 Visible <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>TrayIcon<span class="symbol">.</span>MouseDoubleClick <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>TrayIconDoubleClickHandler<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>TrayIcon<span class="symbol">.</span>MouseClick <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>TrayIconClickHandler<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> NotifyIcon TrayIcon
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _notifyIcon<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
 
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnTrayIconClick<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span> <span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnTrayIconDoubleClick<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span> <span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> TrayIconClickHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnTrayIconClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> TrayIconDoubleClickHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnTrayIconDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>We'll also update <code>OnApplicationExit</code> to clear up the icon:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">if</span> <span class="symbol">(</span>_notifyIcon <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
<span class="symbol">{</span>
 _notifyIcon<span class="symbol">.</span>Visible <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
 _notifyIcon<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Even though we are setting the icon's <code>Visible</code> property to
<code>true</code>, nothing will happen as you need to assign the <code>Icon</code>
property first.</p>
</blockquote>
<h2 id="adding-a-context-menu">Adding a context menu</h2>
<p>Having a tray icon is very nice, but if the only interaction
possible with our application is double clicking the icon, it's
a bit of a limited application! We'll solve this by adding a
<code>ContextMenuStrip</code> to the class, which will be bound to the
icon. Inheritors can then populate the menu according to their
requirements.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">readonly</span> ContextMenuStrip _contextMenu<span class="symbol">;</span>

<span class="keyword">protected</span> TrayIconApplicationContext<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 _contextMenu <span class="symbol">=</span> <span class="keyword">new</span> ContextMenuStrip<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 Application<span class="symbol">.</span>ApplicationExit <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>ApplicationExitHandler<span class="symbol">;</span>

 _notifyIcon <span class="symbol">=</span> <span class="keyword">new</span> NotifyIcon
 <span class="symbol">{</span>
 ContextMenuStrip <span class="symbol">=</span> _contextMenu<span class="symbol">,</span>
 Text <span class="symbol">=</span> Application<span class="symbol">.</span>ProductName<span class="symbol">,</span>
 Visible <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>TrayIcon<span class="symbol">.</span>DoubleClick <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>TrayIconDoubleClickHandler<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>TrayIcon<span class="symbol">.</span>Click <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>TrayIconClickHandler<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> ContextMenuStrip ContextMenu
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _contextMenu<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Again, we'll update the exit handler to dispose of the menu:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">if</span> <span class="symbol">(</span>_contextMenu <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _contextMenu<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="creating-the-application">Creating the application</h2>
<p>With our reusable application context class ready, we can now
create a custom application specific version. Of course, you
don't have to do this, you could just make the changes directly
to the original class, but it's better to promote resuse where
you can.</p>
<p>For example, a basic application which had a settings dialog and
an about dialog could look something like this:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">class</span> CustomApplicationContext <span class="symbol">:</span> TrayIconApplicationContext
<span class="symbol">{</span>
 <span class="keyword">public</span> ApplicationContext<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>TrayIcon<span class="symbol">.</span>Icon <span class="symbol">=</span> Resources<span class="symbol">.</span>SmallIcon<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>ContextMenu<span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;&amp;Settings...&quot;</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>SettingsContextMenuClickHandler<span class="symbol">)</span><span class="symbol">.</span>Font <span class="symbol">=</span> <span class="keyword">new</span> Font<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ContextMenu<span class="symbol">.</span>Font<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Bold<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>ContextMenu<span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;-&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>ContextMenu<span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;&amp;About...&quot;</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>AboutContextMenuClickHandler<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>ContextMenu<span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;-&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>ContextMenu<span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;E&amp;xit&quot;</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ExitContextMenuClickHandler<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnTrayIconDoubleClick<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ShowSettings<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnTrayIconDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> AboutContextMenuClickHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> EventArgs eventArgs<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Form dialog <span class="symbol">=</span> <span class="keyword">new</span> AboutDialog<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 dialog<span class="symbol">.</span>ShowDialog<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> ExitContextMenuClickHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> EventArgs eventArgs<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ExitThread<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> SettingsContextMenuClickHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> EventArgs eventArgs<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ShowSettings<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> ShowSettings<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Form dialog <span class="symbol">=</span> <span class="keyword">new</span> SettingsDialog<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 dialog<span class="symbol">.</span>ShowDialog<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>This sample creates a context menu with 3 items; two dialogs and
a way to exit the program. Double clicking the icon also
displays a dialog. Convention usually suggests that for the
context menu, you display the primary item in bold - so in this
example the bold item opens the settings dialog, matching the
double click action.</p>
<p>Finally, we need to modify the entry point of our application to
use the new class.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>STAThread<span class="symbol">]</span>
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> Main<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 Application<span class="symbol">.</span>EnableVisualStyles<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 Application<span class="symbol">.</span>SetCompatibleTextRenderingDefault<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
 Application<span class="symbol">.</span>Run<span class="symbol">(</span><span class="keyword">new</span> CustomApplicationContext<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>And that's how simple it is to set up an application with no
start up form!</p>
<h2 id="notes">Notes</h2>
<p>While writing a real utility program that made use of this
technique for an application accessed via the system I had the
following observations:</p>
<ul>
<li>Dialogs opened from this class are not modal. You can see this
by double clicking the tray icon several times - if you call
<code>ShowDialog</code>, they aren't modal and you can can therefore open
multiple dialogs by accessing the menu again etc. It's
probably better to have instance variables for such forms, and
then create them on first use, and activate the existing
instance on subsequent calls. The full source code download
available below shows examples of this.</li>
<li>Mixing the <code>MouseClick</code> and <code>MouseDoubleClick</code> events to show
windows doesn't really work as shown in the example project.
Perhaps this can be worked around by using the <code>MouseUp</code> event
instead and the<code>SystemInformation.DoubleClickSize</code> /
<code>SystemInformation.DoubleClickTime</code> properties but that's
beyond the scope of this article.</li>
<li>As there is no top level main window to appear in the taskbar,
you should probably ensure any window that can be opened
directly from the tray icon has its <code>Icon</code>, <code>ShowIcon</code> and
<code>ShowInTaskbar</code> properties set.</li>
<li>Opened dialogs were frequently displayed behind existing
windows of other applications. I didn't observe this while
debugging the project, but only when running the program
outside the IDE. The simplest way I found to work around this
issue was to call <code>this.Activate()</code> from the <code>Shown</code> event</li>
</ul>
<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> OnShown<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnShown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Activate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>As usual an example project is available from the link below
containing a demonstration of this technique.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2013-08-26 - 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-long-running-windows-forms-applications-without-a-start-up-form .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comColorPicker Controls Update 1.0.2.0urn:uuid:2cd07bf2-c75a-4edf-a79f-8f501f9ab5f12013-07-14T08:48:08Z2013-07-13T18:06:56Z<p>I've been pretty busy recently pushing out updates to
<a href="https://www.cyotek.com/cyotek-webcopy">WebCopy</a>, a pending update to <a href="https://www.cyotek.com/cyotek-spriter">Spriter</a> and working on a
game project so blog posts have suffered a bit. While I work to
correct that, we've just pushed an update to the ColorPicker
controls.</p>
<h2 id="important">Important</h2>
<p>This update contains breaking changes due to a number of renamed
classes and enum members.</p>
<h2 id="so-whats-new">So what's new?</h2>
<ul>
<li>All the <code>*PaletteReader</code> classes have been replaced with
<code>*PaletteSerializer</code>. As the new names imply, palettes can now
be written as well as read. This is a breaking change and may
require some reworking of any code that used to use the old
readers.</li>
<li>The <code>ColorEditor</code> now supports selecting of named colors as
the hex editor is now a dropdown list. As well as being able
to select named colors from the list, you can now also type
names directly into the hex editor and they will be processed
accordingly.</li>
<li>The <code>ColorPickerDialog</code> now can load and save palette files</li>
<li>Palette support has been reworked to allow the saving of
palettes as well as loading. Unfortunately due to the initial
names of these classes this is a breaking change, albeit a
minor one.</li>
<li>Added a bit more documentation</li>
<li>Corrected some grammatical errors in existing documentation
and headers</li>
<li>Added additional tests to ensure palettes are written
correctly</li>
</ul>
<h2 id="new-from-1.0.1.0">New from 1.0.1.0</h2>
<ul>
<li>GIMP Palette support</li>
<li>Some unit tests to make sure palettes are read correctly</li>
</ul>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cp-1020-editor.png" class="gallery" title="Named color support" ><img src="https://images.cyotek.com/image/thumbnail/devblog/cp-1020-editor.png" alt="Named color support" decoding="async" loading="lazy" /></a><figcaption>Named color support</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cp-1020-dialog.png" class="gallery" title="Support for loading and saving palettes" ><img src="https://images.cyotek.com/image/thumbnail/devblog/cp-1020-dialog.png" alt="Support for loading and saving palettes" decoding="async" loading="lazy" /></a><figcaption>Support for loading and saving palettes</figcaption></figure><h2 id="downloads">Downloads</h2>
<p>Grab the update from the link below or from the <a href="https://www.cyotek.com/cyotek-spriter">GitHub</a>
page.</p>
<p>We hope you enjoy these improvements!</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2013-07-13 - 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/colorpicker-controls-update-1-0-2-0 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comColorPicker Controls for Windows Formsurn:uuid:25ad5918-b209-45fa-b2a2-5d19e328ef372013-07-13T18:10:06Z2013-04-04T16:09:52Z<p>Back at the start of the new millennium, I had a publishing
agreement with another company to publish our components under
their branding. The first of these components was the
ColorPicker ActiveX control. Roll on 13 years later and that
publishing agreement is long expired, ActiveX is dead, and yet
here I am again writing a color picker control. Except this time
losing money rather than making it. There's probably a life
lesson buried in there somewhere.</p>
<p>All of our current products ask for a color at least once
(mostly buried in an options dialog), and some of the prototype
products we are working on ask for more. Currently, we just wrap
around the <code>System.Drawing.Design.ColorEditor</code> class, which
overtime has identified a few problems:</p>
<ul>
<li>Keyboard support is iffy (this is more to do with how it's
implemented at our end I suspect)</li>
<li>You can't choose alpha channels, or enter custom values</li>
<li>The dependency on <code>System.Design</code> prevents us from targeting
the Client Profile, and indeed will cause a crash if deployed
to a system with only the Client Profile installed</li>
</ul>
<p>The other option briefly considered was just to replace with the
standard color common dialog. But this dialog looks and operates
the same as it did back in Windows 3, and while that is pretty
good from a consistent UI standpoint, it does mean it hasn't
kept up with changing times - such as entering colors as hex
values. I took a look at some other third party libraries but
none of them were flexible enough for what I wanted.</p>
<p>In the end I went with writing my own set of C# based color
picker controls which we're providing here as open source - we
hope you find them useful. As there's quite a lot of code, I'm
not going to describe them line by line as I've done in the
past, but just provide an overview of each component.</p>
<h2 id="colorgrid-control">ColorGrid Control</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cp-colorgridcontrol.png" class="gallery" title="ColorGrid control demonstration" ><img src="https://images.cyotek.com/image/thumbnail/devblog/cp-colorgridcontrol.png" alt="ColorGrid control demonstration" decoding="async" loading="lazy" /></a><figcaption>ColorGrid control demonstration</figcaption></figure>
<p>This control displays a grid of colors, and supports both a
primary palette, and a custom color palette. Several properties
are available for configuring the appearing of the control, and
there are behaviour options too, such as built in editing of
colors.</p>
<h2 id="colorwheel-control">ColorWheel Control</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cp-colorwheelcontrol.png" class="gallery" title="ColorWheel control demonstration" ><img src="https://images.cyotek.com/image/thumbnail/devblog/cp-colorwheelcontrol.png" alt="ColorWheel control demonstration" decoding="async" loading="lazy" /></a><figcaption>ColorWheel control demonstration</figcaption></figure>
<p>This control displays a radial wheel of colors and allows
selection from any point in the wheel. Not much in the way of
customisation for this control!</p>
<h2 id="colorslider-controls">ColorSlider Controls</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cp-colorslidercontrols.png" class="gallery" title="ColorSlider controls demonstration" ><img src="https://images.cyotek.com/image/thumbnail/devblog/cp-colorslidercontrols.png" alt="ColorSlider controls demonstration" decoding="async" loading="lazy" /></a><figcaption>ColorSlider controls demonstration</figcaption></figure>
<p>A bunch of controls (inheriting from a single base) that allow
selection of values via a colourful bar. Similar to the
<code>TrackBar</code> control you have a few options for specifying the
drag handle's position and bar orientation.</p>
<h2 id="coloreditor-control">ColorEditor Control</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cp-coloreditorcontrol.png" class="gallery" title="ColorEditor control demonstration" ><img src="https://images.cyotek.com/image/thumbnail/devblog/cp-coloreditorcontrol.png" alt="ColorEditor control demonstration" decoding="async" loading="lazy" /></a><figcaption>ColorEditor control demonstration</figcaption></figure>
<p>This control allows you enter/select a color using RGB/HSL or
hex formats.</p>
<h2 id="screencolorpicker-control">ScreenColorPicker Control</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cp-screencolorpickercontrol.png" class="gallery" title="ScreenColorPicker control demonstration" ><img src="https://images.cyotek.com/image/thumbnail/devblog/cp-screencolorpickercontrol.png" alt="ScreenColorPicker control demonstration" decoding="async" loading="lazy" /></a><figcaption>ScreenColorPicker control demonstration</figcaption></figure>
<p>This control allows the user to pick a color from any pixel
displayed on the screen.</p>
<h2 id="colorpickerdialog-form">ColorPickerDialog Form</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cp-colorpickerdialog.png" class="gallery" title="ColorPickerDialog demonstration" ><img src="https://images.cyotek.com/image/thumbnail/devblog/cp-colorpickerdialog.png" alt="ColorPickerDialog demonstration" decoding="async" loading="lazy" /></a><figcaption>ColorPickerDialog demonstration</figcaption></figure>
<p>This form puts together the previous controls in a ready to use
dialog.</p>
<h2 id="coloreditormanager">ColorEditorManager</h2>
<p>This is a non-GUI component that you can drop onto a form, and
bind to other controls in this library. When the <code>Color</code>
property of one control changes, it is reflected in the others
without having to lift a finger. Useful if you're creating
composite displays from multiple controls.</p>
<h2 id="color-palettes">Color Palettes</h2>
<p>The <code>ColorGrid</code> control has <code>Colors</code> and <code>CustomColors</code>
properties which return a <code>ColorCollection</code>. These two
properties make it easier as a developer to keep separate a
primary palette whilst having the flexibility of custom colors,
although it does complicate the control's internal logic a bit!
The grid will automatically populate custom colors if you try
and set the control's <code>Color</code> to a value not currently defined.</p>
<p>As well as manually populating <code>ColorCollection</code> instances, you
can also load in external palette files. Paint.NET and the age
old JASC 1.0 formats are currently supported.</p>
<h2 id="keyboard-support">Keyboard Support</h2>
<p>All GUI components, with the exception of the
<code>ScreenColorPicker</code> include full keyboard/focus support. Many
controls support <code>SmallChange</code> and <code>LargeChange</code> properties
which influence how navigation keys are processed. Although in
the case of the <code>ColorWheel</code> it's not really a bonus... but
that's what the <code>ColorEditor</code> control is best suited for!</p>
<h2 id="known-issues">Known Issues</h2>
<ul>
<li>XML documentation comments are incomplete</li>
<li>The <code>ColorEditorManager</code> control currently allows you to bind
to the <code>LightnessColorSlider</code> control, but doesn't fully
support it yet</li>
</ul>
<h2 id="acknowledgements">Acknowledgements</h2>
<ul>
<li>Inspiration (and some code!) was taken from <a href="http://www.codeproject.com/Articles/21965/Color-Picker-with-Color-Wheel-and-Eye-Dropper" rel="external nofollow noopener">Color Picker with
Color Wheel and Eye Dropper</a></li>
<li>The icon used by the demonstration is from the [Crystal
Project Icons<a href="http://www.iconfinder.com/icondetails/17937/128/color_color_scheme_icons_renk_icon" rel="external nofollow noopener">2</a></li>
<li>The eye dropper png graphic is from the <a href="http://www.iconfinder.com/icondetails/84569/32/eyedropper_icon" rel="external nofollow noopener">Momentum Glossy
Icons</a></li>
</ul>
<h2 id="source-code">Source Code</h2>
<p>Download the source code from the link below, or from the
<a href="https://github.com/cyotek/Cyotek.Windows.Forms.ColorPicker/" rel="external nofollow noopener">GitHub</a> page.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2013-04-04 - First published</li>
<li>2020-11-21 - Updated formatting, corrected some spelling
errors</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/colorpicker-controls-for-windows-forms .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comImageBox and TabList update's - virtual mode, pixel grid, bug fixes and more!urn:uuid:090ccddd-473e-47f7-ab27-b623ab52a7372013-08-10T20:32:56Z2012-12-31T19:53:27Z<p>Our last post before the new year and some new material is an
update to the <code>ImageBox</code> (now at version 1.1.2.0) and <code>TabList</code>
(at version 1.0.0.2) controls. You can grab the updated source
from the links at the end of the post, or from the <a href="https://github.com/cyotek" rel="external nofollow noopener">GitHub</a>
page.</p>
<h2 id="imagebox">ImageBox</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-6a.png" class="gallery" title="Virtual mode allows you to use the ImageBox control without a backing image" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-6a.png" alt="Virtual mode allows you to use the ImageBox control without a backing image" decoding="async" loading="lazy" /></a><figcaption>Virtual mode allows you to use the ImageBox control without a backing image</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-6b.png" class="gallery" title="The pixel grid allows you to show a grid when zoomed in" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-6b.png" alt="The pixel grid allows you to show a grid when zoomed in" decoding="async" loading="lazy" /></a><figcaption>The pixel grid allows you to show a grid when zoomed in</figcaption></figure><h3 id="changes-and-new-features">Changes and new features</h3>
<ul>
<li>Added <code>IsPointInImage</code> method. This function returns if a
given point is within the image viewport, and is useful for
combining with <code>PointToImage</code>.</li>
<li>Added <code>ImageBorderColor</code> property, allowing you to customize
the color of the image border</li>
<li>Added a new <code>ImageBoxBorderStyle</code>, <code>FixedSingleGlowShadow</code>.
This style allows for a more smoother outer glow shadow
instead of the existing clunky drop shadow.</li>
<li>Added <code>ShowPixelGrid</code> and <code>PixelGridColor</code> properties. When
set, a dotted grid is displayed around pixels when zooming in
on an image.</li>
<li>Added new overload to <code>PointToImage</code> which allows you to
specify if the function should map the given point to the
nearest available edge(s) if the point is outside the image
boundaries</li>
<li>Added <code>AllowDoubleClick</code> property. When set, the normal double
click events and overrides work as expected.</li>
<li>Added <code>VirtualMode</code> and <code>VirtualSize</code> properties. These new
properties allow you to use all functionality of the ImageBox
control without having to set the <code>Image</code> property. You can
also use the new <code>VirtualDraw</code> event to provide custom drawing
without having to override existing drawing functionality.</li>
<li>Additional documentation added via XML comments</li>
</ul>
<h3 id="fixed">Fixed</h3>
<ul>
<li>If the <code>GridDisplayMode</code> property is set to <code>Image</code> an
explicit image border is no longer drawn, instead the
<code>ImageBorder</code> property is correctly honoured.</li>
<li>Fixes a problem where half the pixels of the first row/column
were lost when zooming. Thanks to Rotem for the fix.</li>
<li>The <code>GetImageViewport</code> method now correctly returns a width
and height that accounts for control size, padding and zoom
levels.</li>
<li>Fixed incorrect attributes on <code>AutoSize</code> property</li>
<li>Fixed the image viewport sometimes being the incorrect size
when zoomed in. Thanks to WMJ for the fix.</li>
<li>Fixes &quot;see also&quot; documentation errors for events</li>
</ul>
<h2 id="tablist">TabList</h2>
<h3 id="changes-and-new-features-1">Changes and new features</h3>
<ul>
<li>Added <code>ShowTabList</code> property. When set to <code>False</code>, the list of
tabs is no longer displayed, and navigation can only occur via
code.</li>
<li>Added <code>AllowTabSelection</code> property. When set to <code>False</code>, the
control can no longer gain focus, mouse hover effects are not
displayed, and navigation can only occur via code. This allows
you to disable navigation whilst still having the tabs
visible.</li>
</ul>
<h3 id="fixed-1">Fixed</h3>
<ul>
<li>Fixed the <code>HoverIndex</code> property always defaulting to zero.</li>
</ul>
<p>Happy New Year all!</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2012-12-31 - 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/imagebox-and-tablist-updates-virtual-mode-pixel-grid-bug-fixes-and-more .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comImageBox update, version 1.1.0.0urn:uuid:24d152df-a604-4f8a-a7eb-7b149ac194be2012-08-30T21:53:50Z2012-08-30T21:53:50Z<p>The <code>ImageBox</code> control has had quite a big update, you can
download the source from the link below, or from our <a href="https://github.com/cyotek" rel="external nofollow noopener">GitHub</a>
page.</p>
<p>Listed below are the changes made during this update, we hope
you enjoy them!</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-1-1-0-0.png" class="gallery" title="The updated ImageBox demonstration application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-1-1-0-0.png" alt="The updated ImageBox demonstration application" decoding="async" loading="lazy" /></a><figcaption>The updated ImageBox demonstration application</figcaption></figure><h2 id="changes-and-new-features">Changes and new features</h2>
<ul>
<li>Zooming with the mouse is now smoother, and the control
attempts to keep the area under the mouse before the zoom in
the same area after the zoon.</li>
<li>Added a <code>ZoomLevels</code> property which allows you to configure
the different zoom levels supported by the control. Now
instead of the control trying to guess the next zoom level, it
cycles appropriately through the defined levels. <em>Currently
ZoomLevels (apart from the default series) can only be set at
runtime.</em></li>
<li>The <code>ZoomIncrement</code> property has been removed due to the
introduction of the new zoom levels.</li>
<li>New <code>CenterAt</code> and <code>ScrollTo</code> methods allow you to scroll to a
given location in the source image.</li>
<li>Split shortcut handling into two methods
<code>ProcessScrollingShortcuts</code> for handling arrow keys and
<code>ProcessImageShortcuts</code> for handling pretty much anything
else.</li>
<li>Added <code>EnableShortcuts</code> property, allowing the built in
keyboard support to be disabled. When this property is true,
<code>ProcessImageShortcuts</code> is not called, allowing the control to
still be scrolled via the keyboard, but not zoomed etc.</li>
<li>Zooming can now be performed by the -/+ keys (<code>OemMinus</code> and
<code>Oemplus</code>).</li>
<li>When zooming (except via mouse action), if the <code>AutoCenter</code>
property is set, the control will always center the image even
when scrollbars are present.</li>
<li>Nestable <code>BeginUpdate</code> and <code>EndUpdate</code> methods allow you to
disable and enable painting of the control, for example when
changing multiple properties at once.</li>
<li>Added a new <code>GetSelectedImage</code> method which creates a new
<code>Bitmap</code> based on the current selection.</li>
<li>Added new <code>FitRectangle</code> method which takes a given rectangle
and ensure it fits within the image boundaries</li>
<li>The <code>AllowClickZoom</code> property now defaults to <code>false</code>.</li>
<li>The <code>PointToImage</code> function no longer adds +1 to the result of
the function.</li>
<li>Added a new <code>ZoomToRegion</code> method. This will caculate and
appropriate zoom level and scrollbar positions to fit a given
rectangle.</li>
<li>Added new <code>SelectionMode.Zoom</code>. When this mode is selected,
drawing a region will automatically zoom and position the
control to fit the region, after which the region is
automatically cleared.</li>
</ul>
<h2 id="bug-fixes">Bug fixes</h2>
<ul>
<li>Panning no longer tries to activate if no scrollbars are
visible</li>
<li>A new base class, <code>VirtualScrollableControl</code> is now used
instead of <code>ScrollableControl</code>. This removes completely the
flicker issues present in previous versions of the control.</li>
<li>The BorderStyle property has been moved to the <code>ScrollControl</code>
class, so that borders now correctly surround the control
(including scrollbars) rather than just the client area.</li>
<li>If the <code>AllowZoomClick</code> property is <code>true</code>, the control no
longer magically zooms after panning or selecting a region.
Code previously in the <code>OnMouseClick</code> override is now in
<code>OnMouseUp</code>.</li>
<li>If both <code>AutoPan</code> and a valid <code>SelectionMode</code> are set, only
selections are processed, instead of the control tying to do
both. As a result of this fix, setting the <code>SelectionMode</code>
property no longer resets <code>AutoPan</code></li>
<li>With the introduction of the <code>VirtualScrollableControl</code>, the
<code>MouseWheel</code> event is now raised as expected.</li>
</ul>
<h2 id="known-issues">Known issues</h2>
<ul>
<li>The <code>ScrollProperties</code> class hasn't been fully integrated with
the <code>ScrollControl</code>, setting properties on this class won't
update the owner control.</li>
</ul>
<h2 id="update-history">Update History</h2>
<ul>
<li>2012-08-30 - 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/imagebox-update-version-1-1-0-0 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a multi-paged container control with design time supporturn:uuid:d569d6ce-22aa-418f-9f61-d564fe58eacd2012-08-20T16:54:07Z2012-08-19T18:02:02Z<p>This article describes adding design time support for a
<code>TabControl</code>-like component which renders the same way the
Project Properties in Visual Studio 2012.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/tablist.png" class="gallery" title="The TabList demonstration application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/tablist.png" alt="The TabList demonstration application" decoding="async" loading="lazy" /></a><figcaption>The TabList demonstration application</figcaption></figure>
<blockquote>
<p>This is the first time I've tried to make more advanced use of
component designers so there are going to be areas that I'm
not aware of or have not implemented correctly. The component
seems to be working fine, but it's entirely possible that bugs
exist, which could cause problems. Caveat emptor!</p>
</blockquote>
<h2 id="overview-of-the-control">Overview of the control</h2>
<p>For this article, I'm not going to delve into how the control
itself was put together as I want to focus on the design time
support, so I'm just going to provide a quick overview.</p>
<ul>
<li><code>TabList</code> - the main control</li>
<li><code>TabListPage</code> - these are hosted by the <code>TabList</code> to provided
multi-paged support</li>
<li><code>TabListControlCollection</code> - a custom <code>ControlCollection</code> that
handles <code>TabListPages</code>, and prevents adding other controls
directly onto the <code>TabList</code></li>
<li><code>TabListPageCollection</code> - a strongly typed wrapper for
<code>TabListPage</code> objects</li>
</ul>
<p>The basics of these four classes are all based on the
<code>TabControl</code>. If you know how to use that, then you know how to
use the <code>TabList</code> control, some property names have changed but
otherwise it's pretty similar.</p>
<p>For rendering support, we use these classes:</p>
<ul>
<li><code>ITabListPageRenderer</code> - interface to be implemented by
rendering classes</li>
<li><code>TabListPageRenderer</code> - base class to inherit for render
support, and also provides a default renderer property</li>
<li><code>TabListPageState</code> - flags which describe the state of a
<code>TabListPage</code></li>
<li><code>DefaultTabListPageRenderer</code> - simple renderer which draws a
header in a Visual Studio 2012-esque style.</li>
</ul>
<p>And finally, we have the two designers which this article will
concentrate on:</p>
<ul>
<li><code>TabListDesigner</code> - designer class for the <code>TabList</code> control</li>
<li><code>TabListPageDesigner</code> - designer class for the <code>TabListPage</code>
control</li>
</ul>
<h2 id="implementing-the-tablistdesigner">Implementing the TabListDesigner</h2>
<p>As the <code>TabList</code> control is a container control, we can't use
the base <code>ControlDesigner</code>. Instead, we'll use
<code>ParentControlDesigner</code> which has a bunch of extra functionality
we need.</p>
<h3 id="initializing-a-new-control">Initializing a new control</h3>
<p>Normally, I initialize a component via the constructor of the
control. This is fine when you're initializing properties to
default values, but what about adding child items? Consider for
example the <code>TabControl</code>. When add one of these to a form, it
generates two hosted pages. If you remove these, they don't come
back. If you've ever looked at the designer generated code for a
control, you'll see it will <em>add</em> items to a collection, but
doesn't <em>clear</em> the collection first so creating items via the
initialization method of a component would be problematic.</p>
<p>Fortunately for us, the designer has two methods you can
override. <code>InitializeNewComponent</code> is called when you create a
new instance of the designed type. <code>InitializeExistingComponent</code>
can be used to modify an existing component. There's also a
third override, <code>InitializeNonDefault</code> although I'm not sure
when this is called.</p>
<p>For our purposes, overriding the <code>InitializeNewComponent</code> method
is enough:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> InitializeNewComponent<span class="symbol">(</span>IDictionary defaultValues<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>InitializeNewComponent<span class="symbol">(</span>defaultValues<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// add two default pages to each new control and reset the selected index</span>
 <span class="keyword">this</span><span class="symbol">.</span>AddTabListPage<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>AddTabListPage<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>TabListControl<span class="symbol">.</span>SelectedIndex <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Now, whenever you add a <code>TabList</code> control onto a designer
surface such as a <code>Form</code>, it'll get two shiny new
<code>TabListPages</code>.</p>
<h3 id="hooking-up-events">Hooking up events</h3>
<p>For our designer, we need to know when certain actions occur so
we can act accordingly - for example, to disable the Remove verb
if there's nothing to remove. We'll set these up by overriding
the <code>Initialize</code> method.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> Initialize<span class="symbol">(</span>IComponent component<span class="symbol">)</span>
<span class="symbol">{</span>
 TabList control<span class="symbol">;</span>
 ISelectionService selectionService<span class="symbol">;</span>
 IComponentChangeService changeService<span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>Initialize<span class="symbol">(</span>component<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// attach an event so we can be notified when the selected components in the host change</span>
 selectionService <span class="symbol">=</span> <span class="symbol">(</span>ISelectionService<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>GetService<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>ISelectionService<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>selectionService <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 selectionService<span class="symbol">.</span>SelectionChanged <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>OnSelectionChanged<span class="symbol">;</span>

 <span class="comment">// attach an event to notify us of when a component has been modified</span>
 changeService <span class="symbol">=</span> <span class="symbol">(</span>IComponentChangeService<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>GetService<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>IComponentChangeService<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>changeService <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 changeService<span class="symbol">.</span>ComponentChanged <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>OnComponentChanged<span class="symbol">;</span>

 <span class="comment">// attach an event so we can tell when the SelectedIndex of the TabList control changes</span>
 control <span class="symbol">=</span> component <span class="keyword">as</span> TabList<span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>control <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 control<span class="symbol">.</span>SelectedIndexChanged <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>OnSelectedIndexChanged<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h4 id="onselectionchanged">OnSelectionChanged</h4>
<p>The first event we attached as
<code>ISelectionService.SelectionChanged</code>. This event is raised when
the selected components change. We'll use this event to
automatically activate a given <code>TabListPage</code> if a control hosted
upon it is selected.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> OnSelectionChanged<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 ISelectionService service<span class="symbol">;</span>

 service <span class="symbol">=</span> <span class="symbol">(</span>ISelectionService<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>GetService<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>ISelectionService<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>service <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 TabList control<span class="symbol">;</span>

 control <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>TabListControl<span class="symbol">;</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span><span class="keyword">object</span> component <span class="keyword">in</span> service<span class="symbol">.</span>GetSelectedComponents<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 TabListPage ownedPage<span class="symbol">;</span>

 <span class="comment">// check to see if one of the selected controls is hosted on a TabListPage. If it is</span>
 <span class="comment">// activate the page. This means, if for example, you select a control via the</span>
 <span class="comment">// IDE&#39;s properties window, the relavent TabListPage will be activated</span>

 ownedPage <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetComponentOwner<span class="symbol">(</span>component<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>ownedPage <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> ownedPage<span class="symbol">.</span>Parent <span class="symbol">==</span> control<span class="symbol">)</span>
 <span class="symbol">{</span>
 control<span class="symbol">.</span>SelectedPage <span class="symbol">=</span> ownedPage<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h4 id="oncomponentchanged">OnComponentChanged</h4>
<p>The second event <code>IComponentChangeService.ComponentChanged</code> is
raised when the <code>RaiseComponentChanged</code> method is called. We'll
describe how this method works a bit further on, but for now, we
use the event to determine if there are any tab pages in the
control - if there are, the remove command is enabled, otherwise
it's disabled. (We'll also describe the verbs further down too!)</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> OnComponentChanged<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> ComponentChangedEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// disable the Remove command if we dont&#39; have anything we can actually remove</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_removeVerb <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _removeVerb<span class="symbol">.</span>Enabled <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>TabListControl<span class="symbol">.</span>TabListPageCount <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h4 id="onselectedindexchanged">OnSelectedIndexChanged</h4>
<p>The final event, <code>TabList.SelectedIndexChanged</code> is on the
<code>TabList</code> control itself. We use this event to select the
<code>TabList</code> component for designing due to how component selection
seems to work when you mix runtime and design time
functionality.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> OnSelectedIndexChanged<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 ISelectionService service<span class="symbol">;</span>

 service <span class="symbol">=</span> <span class="symbol">(</span>ISelectionService<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>GetService<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>ISelectionService<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>service <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// set the TabList control as the selected object. We need to do this as if the control is selected as a result</span>
 <span class="comment">// of GetHitTest returning true, normal designer actions don&#39;t seem to take place</span>
 <span class="comment">// Alternatively, we could select the selected TabListPage instead but might as well stick with the standard behaviour</span>
 service<span class="symbol">.</span>SetSelectedComponents<span class="symbol">(</span><span class="keyword">new</span> <span class="keyword">object</span><span class="symbol">[</span><span class="symbol">]</span> <span class="symbol">{</span> <span class="keyword">this</span><span class="symbol">.</span>Control <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="verbs">Verbs</h3>
<p>I mentioned verbs above, but just what are they? Well, they are
commands you attach to the context and tasks menu of controls.
To do this, override the <code>Verbs</code> property of your designer and
create a verbs collection.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">override</span> DesignerVerbCollection Verbs
<span class="symbol">{</span>
 <span class="keyword">get</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_verbs <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _verbs <span class="symbol">=</span> <span class="keyword">new</span> DesignerVerbCollection<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 _addVerb <span class="symbol">=</span> <span class="keyword">new</span> DesignerVerb<span class="symbol">(</span><span class="string">&quot;Add TabListPage&quot;</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>AddVerbHandler<span class="symbol">)</span> <span class="symbol">{</span> Description <span class="symbol">=</span> <span class="string">&quot;Add a new TabListPage to the parent control.&quot;</span> <span class="symbol">}</span><span class="symbol">;</span>
 _removeVerb <span class="symbol">=</span> <span class="keyword">new</span> DesignerVerb<span class="symbol">(</span><span class="string">&quot;Remove TabListPage&quot;</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>RemoveVerbHandler<span class="symbol">)</span> <span class="symbol">{</span> Description <span class="symbol">=</span> <span class="string">&quot;Remove the currently selected TabListPage from the parent control.&quot;</span> <span class="symbol">}</span><span class="symbol">;</span>

 _verbs<span class="symbol">.</span>Add<span class="symbol">(</span>_addVerb<span class="symbol">)</span><span class="symbol">;</span>
 _verbs<span class="symbol">.</span>Add<span class="symbol">(</span>_removeVerb<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> _verbs<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Each verb binds to an event handler. For our purposes the events
are simple and just pass through into other methods.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> AddVerbHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>AddTabListPage<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> RemoveVerbHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>RemoveSelectedTabListPage<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>I suppose you could just use an anonymous delegate instead.</p>
<h3 id="modifying-a-component-with-undo-support">Modifying a component with undo support</h3>
<p>If you are making multiple changes a control, and one of these
goes wrong, the IDE won't automatically undo the changes for you
and you will need to handle this yourself. Fortunately, the IDE
does provide the facility via designer transactions. In
additional to providing a single undo for a number of
operations, using transactions can also be good for performance
as UI updates are delayed until the transaction is complete.</p>
<p>The code below is called by the <strong>Add</strong> verb and adds a new
<code>TabListPage</code> to the control.</p>
<p>These are the basic steps for making changes:</p>
<ul>
<li>Create a transaction via <code>IDesignerHost.CreateTransaction</code></li>
<li>Notify the designer of impending changes via the
<code>RaiseComponentChanging</code> method</li>
<li>Make the change</li>
<li>Notify the designer that the change has been made via the
<code>RaiseComponentChanged</code> method. This will raise the
<code>IComponentChangeService.ComponentChanged</code> event mentioned
above.</li>
<li>Either <code>Commit</code> or <code>Cancel</code> the transaction</li>
</ul>
<p>In this case, despite wrapping the transaction in a <code>using</code>
statement, I've got got an explicit <code>try</code> <code>catch</code> block to
cancel the transaction in the event of an error. I'm not sure if
this is strictly necessary however.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> AddTabListPage<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 TabList control<span class="symbol">;</span>
 IDesignerHost host<span class="symbol">;</span>

 control <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>TabListControl<span class="symbol">;</span>
 host <span class="symbol">=</span> <span class="symbol">(</span>IDesignerHost<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>GetService<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>IDesignerHost<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>host <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>DesignerTransaction transaction <span class="symbol">=</span> host<span class="symbol">.</span>CreateTransaction<span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;Add TabListPage to &#39;{0}&#39;&quot;</span><span class="symbol">,</span> control<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">try</span>
 <span class="symbol">{</span>
 TabListPage page<span class="symbol">;</span>
 MemberDescriptor controlsProperty<span class="symbol">;</span>

 page <span class="symbol">=</span> <span class="symbol">(</span>TabListPage<span class="symbol">)</span>host<span class="symbol">.</span>CreateComponent<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>TabListPage<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 controlsProperty <span class="symbol">=</span> TypeDescriptor<span class="symbol">.</span>GetProperties<span class="symbol">(</span>control<span class="symbol">)</span><span class="symbol">[</span><span class="string">&quot;Controls&quot;</span><span class="symbol">]</span><span class="symbol">;</span>

 <span class="comment">// tell the designer we&#39;re about to start making changes</span>
 <span class="keyword">this</span><span class="symbol">.</span>RaiseComponentChanging<span class="symbol">(</span>controlsProperty<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// set the text to match the name</span>
 page<span class="symbol">.</span>Text <span class="symbol">=</span> page<span class="symbol">.</span>Name<span class="symbol">;</span>

 <span class="comment">// add the new control to the parent, and set it to be the active page</span>
 control<span class="symbol">.</span>Controls<span class="symbol">.</span>Add<span class="symbol">(</span>page<span class="symbol">)</span><span class="symbol">;</span>
 control<span class="symbol">.</span>SelectedIndex <span class="symbol">=</span> control<span class="symbol">.</span>TabListPageCount <span class="symbol">-</span> <span class="number">1</span><span class="symbol">;</span>

 <span class="comment">// inform the designer we&#39;re finished making changes</span>
 <span class="keyword">this</span><span class="symbol">.</span>RaiseComponentChanged<span class="symbol">(</span>controlsProperty<span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// commit the transaction</span>
 transaction<span class="symbol">.</span>Commit<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">catch</span>
 <span class="symbol">{</span>
 transaction<span class="symbol">.</span>Cancel<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">throw</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The handler for the remove verb does pretty much the same thing,
except we use <code>IDesignerHost.DestroyComponent</code> to remove the
selected <code>TabListPage</code> control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> RemoveSelectedTabListPage<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 TabList control<span class="symbol">;</span>

 control <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>TabListControl<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>control <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> control<span class="symbol">.</span>TabListPageCount <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 IDesignerHost host<span class="symbol">;</span>

 host <span class="symbol">=</span> <span class="symbol">(</span>IDesignerHost<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>GetService<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>IDesignerHost<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>host <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>DesignerTransaction transaction <span class="symbol">=</span> host<span class="symbol">.</span>CreateTransaction<span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;Remove TabListPage from &#39;{0}&#39;&quot;</span><span class="symbol">,</span> control<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">try</span>
 <span class="symbol">{</span>
 MemberDescriptor controlsProperty<span class="symbol">;</span>

 controlsProperty <span class="symbol">=</span> TypeDescriptor<span class="symbol">.</span>GetProperties<span class="symbol">(</span>control<span class="symbol">)</span><span class="symbol">[</span><span class="string">&quot;Controls&quot;</span><span class="symbol">]</span><span class="symbol">;</span>

 <span class="comment">// inform the designer we&#39;re about to make changes</span>
 <span class="keyword">this</span><span class="symbol">.</span>RaiseComponentChanging<span class="symbol">(</span>controlsProperty<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// remove the tab page</span>
 host<span class="symbol">.</span>DestroyComponent<span class="symbol">(</span>control<span class="symbol">.</span>SelectedPage<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// tell the designer we&#39;re finished making changes</span>
 <span class="keyword">this</span><span class="symbol">.</span>RaiseComponentChanged<span class="symbol">(</span>controlsProperty<span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// commit the transaction</span>
 transaction<span class="symbol">.</span>Commit<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">catch</span>
 <span class="symbol">{</span>
 transaction<span class="symbol">.</span>Cancel<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">throw</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="adding-controls-to-the-selected-tablistpage">Adding controls to the selected TabListPage</h3>
<p>If the <code>TabList</code> control is selected and you try to drag a
control on it, you'll get an error stating that only
<code>TabListPage</code> controls can be hosted. By overriding the
<code>CreateToolCore</code> method, we can intercept the control creation,
and forward it onto the current <code>TabListPage</code> via the
<code>InvokeCreateTool</code> method.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">override</span> IComponent<span class="symbol">[</span><span class="symbol">]</span> CreateToolCore<span class="symbol">(</span>ToolboxItem tool<span class="symbol">,</span> <span class="keyword">int</span> x<span class="symbol">,</span> <span class="keyword">int</span> y<span class="symbol">,</span> <span class="keyword">int</span> width<span class="symbol">,</span> <span class="keyword">int</span> height<span class="symbol">,</span> <span class="keyword">bool</span> hasLocation<span class="symbol">,</span> <span class="keyword">bool</span> hasSize<span class="symbol">)</span>
<span class="symbol">{</span>
 TabList control<span class="symbol">;</span>
 IDesignerHost host<span class="symbol">;</span>

 control <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>TabListControl<span class="symbol">;</span>

 <span class="comment">// prevent controls from being created directly on the TabList</span>
 <span class="keyword">if</span> <span class="symbol">(</span>control<span class="symbol">.</span>SelectedPage <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentException<span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;Cannot add control &#39;{0}&#39;, no page is selected.&quot;</span><span class="symbol">,</span> tool<span class="symbol">.</span>DisplayName<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 host <span class="symbol">=</span> <span class="symbol">(</span>IDesignerHost<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>GetService<span class="symbol">(</span><span class="keyword">typeof</span><span class="symbol">(</span>IDesignerHost<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>host <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 ParentControlDesigner childDesigner<span class="symbol">;</span>

 childDesigner <span class="symbol">=</span> <span class="symbol">(</span>ParentControlDesigner<span class="symbol">)</span>host<span class="symbol">.</span>GetDesigner<span class="symbol">(</span>control<span class="symbol">.</span>SelectedPage<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// add controls onto the TabListPage control instead of the TabList</span>
 ParentControlDesigner<span class="symbol">.</span>InvokeCreateTool<span class="symbol">(</span>childDesigner<span class="symbol">,</span> tool<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> <span class="keyword">null</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Returning <code>null</code> via <code>CreateToolCore</code> prevents the control from
being created on the <code>TabList</code>. The reminder of the logic
forwards the call onto the selected <code>TabListPage</code>, if one is
available.</p>
<h3 id="allowing-tablistpage-selection-at-design-time">Allowing TabListPage selection at design-time</h3>
<p>As you'll have noticed, most controls can't be used at design
time - when you click a control it just selects it. This default
behaviour is a serious problem for our component as if you can't
active other pages, how can you add controls to them?
Fortunately, this is extremely easy to implement as the designer
provides a <code>GetHitTest</code> method which you can override. If you
return <code>true</code> from this method, then mouse clicks will be
processed by the underlying control instead of the designer.</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">bool</span> GetHitTest<span class="symbol">(</span>Point point<span class="symbol">)</span>
<span class="symbol">{</span>
 TabList control<span class="symbol">;</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>
 Point location<span class="symbol">;</span>

 <span class="comment">// return true if the mouse is located over a TabListPage header</span>
 <span class="comment">// this allows you to switch pages at design time with the mouse</span>
 <span class="comment">// rather than just selecting the control as it would otherwise</span>

 control <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>TabListControl<span class="symbol">;</span>
 location <span class="symbol">=</span> control<span class="symbol">.</span>PointToClient<span class="symbol">(</span>point<span class="symbol">)</span><span class="symbol">;</span>
 result <span class="symbol">=</span> control<span class="symbol">.</span>HitTest<span class="symbol">(</span>location<span class="symbol">)</span> <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">;</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In the above code, we translate the provided mouse co-ordinates
into the client co-ordinates, then test to see if they are on
the header of a <code>TabListPage</code>. If they are, we return <code>true</code> and
the call will then be forward onto the <code>TabList</code> control which
will then selected that page.</p>
<p>There is one side effect of this behaviour. As we have
essentially intercepted the mouse call, that means the <code>TabList</code>
control isn't selected. This behaviour is inconsistent with
standard behaviour and this is why when the designer was
initialized we hooked into the <code>SelectedIndexChanged</code> event of
the <code>TabList</code> control. With this hooked, as soon as the
<code>SelectedIndex</code> property is changed we can manually select the
<code>TabList</code> control. Of course, if you'd rather, you could change
that code to select the active <code>TabListPage</code> instead, but again
that's inconsistent with standard behaviour.</p>
<p>Unfortunately there's also another side effect I discovered -
the context menu no longer works if you right click on an area
where you allow mouse clicks to pass through. Again, this is
fairly straightforward to work around by overriding <code>WndProc</code>
and intercepting the <code>WM_CONTEXTMENU</code> message.</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> WndProc<span class="symbol">(</span><span class="keyword">ref</span> Message m<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">switch</span> <span class="symbol">(</span>m<span class="symbol">.</span>Msg<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> <span class="number">0x7b</span><span class="symbol">:</span> <span class="comment">// WM_CONTEXTMENU</span>
 Point position<span class="symbol">;</span>

 <span class="comment">// For some reason the context menu is no longer displayed when right clicking the control</span>
 <span class="comment">// By hooking into the WM_CONTEXTMENU context message we can display the menu ourselves</span>

 position <span class="symbol">=</span> Cursor<span class="symbol">.</span>Position<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnContextMenu<span class="symbol">(</span>position<span class="symbol">.</span>X<span class="symbol">,</span> position<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">default</span><span class="symbol">:</span>
 <span class="keyword">base</span><span class="symbol">.</span>WndProc<span class="symbol">(</span><span class="keyword">ref</span> m<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Note: Normally I wouldn't use &quot;magic numbers&quot; as I have here.
But at the same time, I don't want to define WM_CONTEXTMENU in
this class - for my internal projects, I link to an assembly
I've created which contains all the Win32 API functionality
that I use. Linking that to this not possible for this example
and I don't want to create a <code>Native</code> class for a just a
single member. So this time I'll cheat and leave an inline
magic number.</p>
</blockquote>
<p>The final side effect I've found is double clicking to open the
default event handler doesn't work either.</p>
<h3 id="design-time-control-paining">Design time control paining</h3>
<p>The final section of the <code>TabListDesigner</code> class I want to
discuss is design time painting. Normally, in the <code>OnPaint</code>
overriding of my control, I would have a block similar to the
below.</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>
 <span class="keyword">base</span><span class="symbol">.</span>OnPaint<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</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>
 <span class="comment">// Design time painting here</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>While there's nothing wrong with this approach, if you are using
a designer than you have another option, which saves you having
to do design time checks each time your contain is painted at
runtime. The designer has an <code>OnPaintAdornments</code> method, just
override this to perform your design time drawing.</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> OnPaintAdornments<span class="symbol">(</span>PaintEventArgs pe<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnPaintAdornments<span class="symbol">(</span>pe<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// outline the control at design time as we don&#39;t have any borders</span>
 ControlPaint<span class="symbol">.</span>DrawFocusRectangle<span class="symbol">(</span>pe<span class="symbol">.</span>Graphics<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Control<span class="symbol">.</span>ClientRectangle<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>As the <code>TabList</code> doesn't have a border property, I draw a dotted
line around the control using <code>ControlPaint.DrawFocusRectangle</code>.</p>
<h2 id="implementing-the-tablistpage-designer">Implementing the TabListPage designer</h2>
<p>Although the <code>TabListPage</code> control is basically a <code>Panel</code>
control with a bunch of properties and events hidden, it still
needs a designer to override some functionality. For the
<code>TabListPageDesigner</code> class, we'll inherit from
<code>ScrollableControlDesigner</code>.</p>
<h3 id="removing-sizing-and-moving-handles">Removing sizing and moving handles</h3>
<p>As the <code>TabList</code> control takes care of sizing its child
<code>TabListPage</code> controls, we don't really want the user to be able
to resize or move them at design time. By overriding the
<code>SelectionRules</code> property, you can define exactly which handles
are displayed. As I don't want the control to be moved or sized,
I get rid of everything via the <code>Locked</code> flag.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">override</span> SelectionRules SelectionRules
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> SelectionRules<span class="symbol">.</span>Locked<span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>
</pre>
</figure>
<h3 id="preventing-the-component-from-being-re-parented">Preventing the component from being re-parented</h3>
<p>The <code>CanBeParentedTo</code> method is used to determine if a component
can be hosted by another control. I'm overriding this to make
sure that they can only be parented on another <code>TabList</code>
control. Although, as I've disabled the dragging of
<code>TabListPage</code> controls with selection rules above, you can't
drag them to reparent anyway.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">bool</span> CanBeParentedTo<span class="symbol">(</span>IDesigner parentDesigner<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> parentDesigner <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> parentDesigner<span class="symbol">.</span>Component <span class="keyword">is</span> TabList<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="known-issues">Known Issues</h2>
<ul>
<li>As described above, if you double click one of the
<code>TabListPage</code> headers nothing happens. Normally, you'd expect
a code window to be opened at the default event handler for
the control. While it should be possible to trap the
<code>WM_LBUTTONDBLCLK</code> message, I don't know how to open a code
window, or create a default event handler is one is missing.</li>
<li>Another issue I spotted is that I can't Cut (or Copy) a
<code>TagListPage</code> from one <code>TabList</code> control to another. Not sure
why yet, but I'll update the source on GitHub when I fix it.</li>
</ul>
<h2 id="the-source">The source</h2>
<p>Get the source code from the link below. I've also uploaded it
to <a href="https://github.com/cyotek/Cyotek.Windows.Forms.TabList" rel="external nofollow noopener">GitHub</a>, feel free to fork and make pull requests to make
this component even better!</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2012-08-19 - 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-multi-paged-container-control-with-design-time-support .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comArcade explosion generatorurn:uuid:c44b7b56-f7ca-4f7d-9c20-82619ff939202012-06-05T09:47:03Z2012-06-03T19:57:59Z<p>Over the past few weeks I've been messing around creating a
unique graphics for our <a href="http://binaryrealms.co.uk/jewel-rush" rel="external nofollow noopener">Jewel Rush</a> game. One of the things
I was experimenting with was explosion animations. Although
tools exist for <a href="http://www.positech.co.uk/content/explosion/explosiongenerator.html" rel="external nofollow noopener">generating explosions</a> the problem with most
of these is that they create large sprites which don't shrink
well, and the output is a bit more realistic than what I was
looking for.</p>
<p>And while I'm competent enough to do application graphics (more
or less!), gaming graphics are a completely different kettle of
fish!</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/aeg1a.png" class="gallery" title="A screenshot from Missile Command" ><img src="https://images.cyotek.com/image/thumbnail/devblog/aeg1a.png" alt="A screenshot from Missile Command" decoding="async" loading="lazy" /></a><figcaption>A screenshot from Missile Command</figcaption></figure>
<p>Above is a screenshot from Missile Command, a classic from
Atari. That's the sort of explosions I wanted to create, so I
wrote a small tool that would create these sort of graphics in a
random (but reproducible) fashion and export them to images for
use in other tools such as <a href="https://cyotek.com/cyotek-spriter">Spriter</a>. As it turned out, the
graphics it produces didn't end up quite that way (I was having
problems with the intersection stuff) but it's usable enough for
the purposes I want.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/aeg1b.png" class="gallery" title="A sample of the default output" ><img src="https://images.cyotek.com/image/thumbnail/devblog/aeg1b.png" alt="A sample of the default output" decoding="async" loading="lazy" /></a><figcaption>A sample of the default output</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/aeg1c.png" class="gallery" title="Another sample using some custom settings" ><img src="https://images.cyotek.com/image/thumbnail/devblog/aeg1c.png" alt="Another sample using some custom settings" decoding="async" loading="lazy" /></a><figcaption>Another sample using some custom settings</figcaption></figure>
<p>The application was thrown together over the weekend so it's
probably not hugely robust and may contain a small army of bugs.
But it works and is possibly an interesting starting point for
other projects. There's some interesting bits of code here and
there, although I'm not writing about the implementation of the
code.</p>
<h2 id="application-features">Application Features</h2>
<ul>
<li>Configuration settings can be saved and reloaded for tweaking
of favoured settings. Uses basic Reflection serialization as
<code>XmlSerializer</code> can't handle colors without having to create
duplicate color properties in string format.</li>
<li>Can export either a complete sprite sheet, or the individual
images</li>
<li>Copy the sprite sheet to the clipboard (although I noticed
that transparent doesn't work if you do, something to look at
later)</li>
<li>Uses the ImageBox (of course!) for displaying previews</li>
<li>The <code>TrackBar</code> control embedded in the <code>ToolStrip</code> is a custom
component inheriting from <code>ToolStripControlHost</code> which can be
reused. And once you understand the principles, it's so easy
to host other controls.</li>
</ul>
<h2 id="graphic-settings">Graphic Settings</h2>
<ul>
<li>Either specify a seed to always recreate the same explosion,
or use a random seed each time. (If you find a seed you like,
clicking the seed number in the status bar will apply it to
your configuration settings).</li>
<li>Specify the number of animation frames that will be generated,
and the size of the frames</li>
<li>Specify the maximum number of explosion booms available at
once. There's also an option to automatically remove and
recreate &quot;expired&quot; blooms.</li>
<li>Choose the colors used to render the blooms</li>
<li>Specify the percentage by which blooms grow (and shrink), and
how many growth states there are. Once a bloom has shrunk to
its minimum size, it is expired and no longer draw.</li>
<li>Anti alias options, useful if you don't want pixel graphics</li>
<li>Border size and growth</li>
<li>Set a random order, in which newly created blooms will be
inserted randomly in the list.</li>
<li>An experimental mask mode which was supposed to enable me to
create those Missile Command style XOR drawing. However, it
doesn't really work and I'll probably have another go at it at
some point.</li>
</ul>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/aeg1d.png" class="gallery" title="An example generated by the project" ><img src="https://images.cyotek.com/image/devblog/aeg1d.png" alt="An example generated by the project" decoding="async" loading="lazy" /></a><figcaption>An example generated by the project</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/aeg1e.png" class="gallery" title="Another example of generated output" ><img src="https://images.cyotek.com/image/devblog/aeg1e.png" alt="Another example of generated output" decoding="async" loading="lazy" /></a><figcaption>Another example of generated output</figcaption></figure><h2 id="room-for-improvement">Room for improvement</h2>
<p>Everything can be improved, one of the ideas I'd had for this
tool was greater control over blooms, allowing you configure
their locations etc with better precision but it wasn't
necessary for the graphic I was creating. As mentioned above,
the masking doesn't work as expected, it would have been nice if
it did. Some better rendering would be a plus too, at the moment
the &quot;explosions&quot; are simple rings of color. Some noise or other
minor particle effects to make them a little less uniform would
probably look interesting.</p>
<h2 id="source-code">Source Code</h2>
<p>Source code, and optionally pre-compiled binaries, are available
from the link below. The code has been compiled against the .NET
3.5 Client Profile. Due to some minor use of Linq and auto
generated properties a small amount of work would be needed to
compile against .NET 2.0. I'm afraid comments are somewhat
lacking as well, I wasn't planning on releasing this publicly
originally.</p>
<p>If anyone creates any interesting graphics or improves upon the
code, we'd <a href="https://cyotek.com/contact">love to hear</a> from you.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2012-06-03 - 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/arcade-explosion-generator .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating an image viewer in C# Part 5: Selecting part of an imageurn:uuid:e2670884-1a55-450f-9831-417fe9e335852012-11-25T09:20:27Z2012-05-30T17:09:14Z<p>Part 4 of this series (by far the most popular article on
cyotek.com) was supposed to be the end, but recently I was asked
if was possible to select part of an image for saving it to a
file. After implementing the new functionality and lacking ideas
for a new post on other matters, here we are with a new part!</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-5a.png" class="gallery" title="The demonstration program showing the selection functionality" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-5a.png" alt="The demonstration program showing the selection functionality" decoding="async" loading="lazy" /></a><figcaption>The demonstration program showing the selection functionality</figcaption></figure><h2 id="getting-started">Getting Started</h2>
<p>If you aren't already familiar with the <code>ImageBox</code> component,
you may wish to view parts <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-1">1</a>, <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-2">2</a>, <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-3">3</a> and <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-4">4</a> for
the original background and specification of the control.</p>
<p>First thing is to add some new properties, along with backing
events. These are:</p>
<ul>
<li><code>SelectionMode</code> - Determines if selection is available within
the control</li>
<li><code>SelectionColor</code> - Primary color for drawing the selection
region</li>
<li><code>SelectionRegion</code> - The currently selected region.</li>
<li><code>LimitSelectionToImage</code> - This property allows you to control
if the selection region can be drawn outside the image
boundaries.</li>
<li><code>IsSelecting</code> - This property returns if a selection operation
is in progress</li>
</ul>
<p>If the <code>SelectionMode</code> property is set, then the <code>AutoPan</code> and
<code>AllowClickZoom</code> properties will both be set to <code>false</code> to avoid
conflicting actions.</p>
<p>We also need a couple of new events not directly tried to
properties.</p>
<ul>
<li><code>Selecting</code> - Occurs when the user starts to draw a selection
region and can be used to cancel the action.</li>
<li><code>Selected</code> - Occurs when the user completes drawing a
selection region</li>
</ul>
<p>These events are called when setting the <code>IsSelecting</code> property:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">bool</span> IsSelecting
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _isSelecting<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">protected</span> <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_isSelecting <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 CancelEventArgs args<span class="symbol">;</span>

 args <span class="symbol">=</span> <span class="keyword">new</span> CancelEventArgs<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>value<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnSelecting<span class="symbol">(</span>args<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnSelected<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>args<span class="symbol">.</span>Cancel<span class="symbol">)</span>
 _isSelecting <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="drawing-the-selection-highlight">Drawing the selection highlight</h2>
<p>Before adding support for defining the selection region, we'll
add the code to draw it - that way we'll know the code to define
the region works! To do this, we'll modify the existing
<code>OnPaint</code> override, and insert a call to a new method named
<code>DrawSelection</code>:</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>
 <span class="comment">/* Snipped existing code for brevity */</span>

 <span class="comment">// draw the selection</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">!=</span> Rectangle<span class="symbol">.</span>Empty<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>DrawSelection<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnPaint<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>DrawSelection</code> method itself is very straightforward. First
it fills the region with a translucent variant of the
<code>SelectionColor</code> property, then draws a solid outline around
this. A clip region is also applied to avoid overwriting the
controls borders.</p>
<p>As with most of the methods and properties in the <code>ImageBox</code>
control, it has been marked as <code>virtual</code> to allow you to
override it and provide your own drawing implementation if
required, without needing to redraw all of the control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> DrawSelection<span class="symbol">(</span>PaintEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 RectangleF rect<span class="symbol">;</span>

 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>SetClip<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>GetInsideViewPort<span class="symbol">(</span><span class="keyword">true</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 rect <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetOffsetRectangle<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionRegion<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Brush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span>Color<span class="symbol">.</span>FromArgb<span class="symbol">(</span><span class="number">128</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionColor<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> rect<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Pen pen <span class="symbol">=</span> <span class="keyword">new</span> Pen<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionColor<span class="symbol">)</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawRectangle<span class="symbol">(</span>pen<span class="symbol">,</span> rect<span class="symbol">.</span>X<span class="symbol">,</span> rect<span class="symbol">.</span>Y<span class="symbol">,</span> rect<span class="symbol">.</span>Width<span class="symbol">,</span> rect<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>ResetClip<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The <code>GetOffsetRectangle</code> method will be described a little
further down this article.</p>
<h2 id="defining-the-selection-region">Defining the selection region</h2>
<p>Currently the selection region can only be defined via the
mouse; there is no keyboard support. To do this, we'll do the
usual overriding of <code>MouseDown</code>, <code>MouseMove</code> and <code>MouseUp</code></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> OnMouseDown<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">/* Snipped existing code for brevity */</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>SelectionMode <span class="symbol">!=</span> ImageBoxSelectionMode<span class="symbol">.</span>None<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Empty<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseMove<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseMove<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">/* Snipped existing code for brevity */</span>
 
 <span class="keyword">this</span><span class="symbol">.</span>ProcessSelection<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseUp<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseUp<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsPanning<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsPanning <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsSelecting<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsSelecting <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p><code>OnMouseDown</code> and <code>OnMouseUp</code> aren't being used for much in this
case, the former is used to clear an existing selection region,
the later to notify that the selection is no longer being
defined. <code>OnMouseMove</code> calls the <code>ProcessSelection</code> method which
is where all the action happens.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> ProcessSelection<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SelectionMode <span class="symbol">!=</span> ImageBoxSelectionMode<span class="symbol">.</span>None<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>IsSelecting<span class="symbol">)</span>
 <span class="symbol">{</span>
 _startMousePosition <span class="symbol">=</span> e<span class="symbol">.</span>Location<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsSelecting <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>
</pre>
</figure>
<p>First, we check to make sure a valid selection mode is set.
Then, if a selection operation hasn't been initiated, we attempt
to set the <code>IsSelecting</code> property. As noted above, this property
will call the <code>Selecting</code> event allowing the selection to be
cancelled if required by the implementing application.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsSelecting<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">float</span> x<span class="symbol">;</span>
 <span class="keyword">float</span> y<span class="symbol">;</span>
 <span class="keyword">float</span> w<span class="symbol">;</span>
 <span class="keyword">float</span> h<span class="symbol">;</span>
 Point imageOffset<span class="symbol">;</span>

 imageOffset <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">.</span>Location<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>X <span class="symbol">&lt;</span> _startMousePosition<span class="symbol">.</span>X<span class="symbol">)</span>
 <span class="symbol">{</span>
 x <span class="symbol">=</span> e<span class="symbol">.</span>X<span class="symbol">;</span>
 w <span class="symbol">=</span> _startMousePosition<span class="symbol">.</span>X <span class="symbol">-</span> e<span class="symbol">.</span>X<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 x <span class="symbol">=</span> _startMousePosition<span class="symbol">.</span>X<span class="symbol">;</span>
 w <span class="symbol">=</span> e<span class="symbol">.</span>X <span class="symbol">-</span> _startMousePosition<span class="symbol">.</span>X<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Y <span class="symbol">&lt;</span> _startMousePosition<span class="symbol">.</span>Y<span class="symbol">)</span>
 <span class="symbol">{</span>
 y <span class="symbol">=</span> e<span class="symbol">.</span>Y<span class="symbol">;</span>
 h <span class="symbol">=</span> _startMousePosition<span class="symbol">.</span>Y <span class="symbol">-</span> e<span class="symbol">.</span>Y<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 y <span class="symbol">=</span> _startMousePosition<span class="symbol">.</span>Y<span class="symbol">;</span>
 h <span class="symbol">=</span> e<span class="symbol">.</span>Y <span class="symbol">-</span> _startMousePosition<span class="symbol">.</span>Y<span class="symbol">;</span>
 <span class="symbol">}</span>

 x <span class="symbol">=</span> x <span class="symbol">-</span> imageOffset<span class="symbol">.</span>X <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>X<span class="symbol">;</span>
 y <span class="symbol">=</span> y <span class="symbol">-</span> imageOffset<span class="symbol">.</span>Y <span class="symbol">-</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>Y<span class="symbol">;</span>
</pre>
</figure>
<p>If selection was allowed, we construct the co-ordinates for a
rectangle, automatically switching values around to ensure that
the rectangle will always have a positive width and height.
We'll also offset the co-ordinates if the image has been
scrolled or if it has been centred (or both!).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 x <span class="symbol">=</span> x <span class="symbol">/</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">;</span>
 y <span class="symbol">=</span> y <span class="symbol">/</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">;</span>
 w <span class="symbol">=</span> w <span class="symbol">/</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">;</span>
 h <span class="symbol">=</span> h <span class="symbol">/</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">;</span>
</pre>
</figure>
<p>As this is the <em>zoomable</em> scrolling image control, we also need
to rescale the rectangle according to the current zoom level.
This ensures the <code>SelectionRegion</code> property always returns a
rectangle that describes the selection at 100% zoom.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>LimitSelectionToImage<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>x <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 x <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>y <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 y <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>x <span class="symbol">+</span> w <span class="symbol">&gt;</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">)</span>
 w <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width <span class="symbol">-</span> x<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>y <span class="symbol">+</span> h <span class="symbol">&gt;</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height<span class="symbol">)</span>
 h <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height <span class="symbol">-</span> y<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> <span class="keyword">new</span> RectangleF<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">,</span> w<span class="symbol">,</span> h<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The final step is to constrain the rectangle to the image size
if the <code>LimitSelectionToImage</code> property is set, before assigning
the final rectangle to the <code>SelectionRegion</code> property.</p>
<p>And that's pretty much all there is to it.</p>
<h2 id="scaling-and-offsetting">Scaling and offsetting</h2>
<p>When using the control in our own products, it's very rarely to
display a single image, but rather to display multiple items, be
it sprites in a sprite sheet or tiles in a map. These
implementations therefore often require the ability to get a
single item, for example to display hover effects. This can be
tricky with a control that scrolls, zooms and centres the image.
Rather than repeat <code>ZoomFactor</code> calculations (and worse
<code>AutoScrollPosition</code>) everywhere, we added a number of helper
methods named <code>GetOffset*</code> and <code>GetScaled*</code>. Calling these with
a &quot;normal&quot; value, will return that value repositioned and
rescaled according to the current state of the control. An
example of this is the <code>DrawSelection</code> method described above
which needs ensure the current selection region is rendered
correctly.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">virtual</span> RectangleF GetScaledRectangle<span class="symbol">(</span>RectangleF source<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">new</span> RectangleF
 <span class="symbol">(</span>
 <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>source<span class="symbol">.</span>Left <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">,</span>
 <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>source<span class="symbol">.</span>Top <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">,</span>
 <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>source<span class="symbol">.</span>Width <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">,</span>
 <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>source<span class="symbol">.</span>Height <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span>
 <span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">virtual</span> RectangleF GetOffsetRectangle<span class="symbol">(</span>RectangleF source<span class="symbol">)</span>
<span class="symbol">{</span>
 RectangleF viewport<span class="symbol">;</span>
 RectangleF scaled<span class="symbol">;</span>
 <span class="keyword">float</span> offsetX<span class="symbol">;</span>
 <span class="keyword">float</span> offsetY<span class="symbol">;</span>

 viewport <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 scaled <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetScaledRectangle<span class="symbol">(</span>source<span class="symbol">)</span><span class="symbol">;</span>
 offsetX <span class="symbol">=</span> viewport<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>X<span class="symbol">;</span>
 offsetY <span class="symbol">=</span> viewport<span class="symbol">.</span>Top <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="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>Y<span class="symbol">;</span>

 <span class="keyword">return</span> <span class="keyword">new</span> RectangleF<span class="symbol">(</span><span class="keyword">new</span> PointF<span class="symbol">(</span>scaled<span class="symbol">.</span>Left <span class="symbol">+</span> offsetX<span class="symbol">,</span> scaled<span class="symbol">.</span>Top <span class="symbol">+</span> offsetY<span class="symbol">)</span><span class="symbol">,</span> scaled<span class="symbol">.</span>Size<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Versions of these methods exist for the following structures:</p>
<ul>
<li><code>Point</code></li>
<li><code>PointF</code></li>
<li><code>Size</code></li>
<li><code>SizeF</code></li>
<li><code>Rectangle</code></li>
<li><code>RectangleF</code></li>
</ul>
<p>These methods can come in extremely useful depending on how you
are using the control!</p>
<h2 id="cropping-an-image">Cropping an image</h2>
<p>The demonstration program displays two <code>ImageBox</code> controls, the
first allows you to select part of an image, and the second
displays the cropped selection. I didn't add any sort of crop
functionality to the control itself, but the following snippets
shows how the demonstration program creates the cropped version.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Rectangle rect<span class="symbol">;</span>

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

rect <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>imageBox<span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>X<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>imageBox<span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Y<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>imageBox<span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Width<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>imageBox<span class="symbol">.</span>SelectionRegion<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

_previewImage <span class="symbol">=</span> <span class="keyword">new</span> Bitmap<span class="symbol">(</span>rect<span class="symbol">.</span>Width<span class="symbol">,</span> rect<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> Graphics<span class="symbol">.</span>FromImage<span class="symbol">(</span>_previewImage<span class="symbol">)</span><span class="symbol">)</span>
 g<span class="symbol">.</span>DrawImage<span class="symbol">(</span>imageBox<span class="symbol">.</span>Image<span class="symbol">,</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>Point<span class="symbol">.</span>Empty<span class="symbol">,</span> rect<span class="symbol">.</span>Size<span class="symbol">)</span><span class="symbol">,</span> rect<span class="symbol">,</span> GraphicsUnit<span class="symbol">.</span>Pixel<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

previewImageBox<span class="symbol">.</span>Image <span class="symbol">=</span> _previewImage<span class="symbol">;</span>
</pre>
</figure>
<h2 id="finishing-touches">Finishing touches</h2>
<p>We'll finish off by adding a couple of helper methods that
implementers can call:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">void</span> SelectAll<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException<span class="symbol">(</span><span class="string">&quot;No image set&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> <span class="keyword">new</span> RectangleF<span class="symbol">(</span>PointF<span class="symbol">.</span>Empty<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Size<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">void</span> SelectNone<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>SelectionRegion <span class="symbol">=</span> RectangleF<span class="symbol">.</span>Empty<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="known-issues">Known issues</h2>
<p>Currently, if you try and draw the selection bigger than the
visible area of the control, it will work, but it will not
scroll the control for you. I also was going to add the ability
to move or modify the selection but ran out of time for this
particular post.</p>
<p>As always, if you have any comments or questions, please contact
us!</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2012-05-30 - 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-an-image-viewer-in-csharp-part-5-selecting-part-of-an-image .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comExtending the ImageBox component to display the contents of a PDF file using C#urn:uuid:6127dd18-25af-4467-ac03-cf9ca52236512011-09-04T16:48:55Z2011-09-04T16:48:55Z<p>In this article, I'll describe how to extend the <a href="/tag/imagebox">ImageBox</a>
control discussed in earlier articles to be able to display PDF
files with the help of the <a href="http://www.ghostscript.com/" rel="external nofollow noopener">GhostScript library</a> and the
conversion library described in the <a href="/post/convert-a-pdf-into-a-series-of-images-using-csharp-and-ghostscript">previous article</a>.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/pdfimagebox.png" class="gallery" title="A sample application demonstrating displaying a PDF file in the ImageBox control" ><img src="https://images.cyotek.com/image/thumbnail/devblog/pdfimagebox.png" alt="A sample application demonstrating displaying a PDF file in the ImageBox control" decoding="async" loading="lazy" /></a><figcaption>A sample application demonstrating displaying a PDF file in the ImageBox control</figcaption></figure><h2 id="getting-started">Getting Started</h2>
<p>You can download the source code used in this article from the
links below, these are:</p>
<ul>
<li><strong>Cyotek.GhostScript</strong> - core library providing GhostScript
integration support</li>
<li><strong>Cyotek.GhostScript.PdfConversion</strong> - support library for
converting a PDF document into images</li>
<li><strong>PdfImageBoxSample</strong> - sample project containing an updated
<code>ImageBox</code> control, and the extended <code>PdfImageBox</code>.</li>
</ul>
<blockquote>
<p>Please note that the native GhostScript DLL is not included in
these downloads, you will need to obtain that from the
<a href="http://www.ghostscript.com/" rel="external nofollow noopener">GhostScript project page</a>.</p>
</blockquote>
<h2 id="extending-the-imagebox">Extending the ImageBox</h2>
<p>To start extending the <code>ImageBox</code>, create a new class and
inherit the <code>ImageBox</code> control. I also decided to override some
of the default properties, so I added a constructor which sets
the new values.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> PdfImageBox<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// override some of the original ImageBox defaults</span>
 <span class="keyword">this</span><span class="symbol">.</span>GridDisplayMode <span class="symbol">=</span> ImageBoxGridDisplayMode<span class="symbol">.</span>None<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>BackColor <span class="symbol">=</span> SystemColors<span class="symbol">.</span>AppWorkspace<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>ImageBorderStyle <span class="symbol">=</span> ImageBoxBorderStyle<span class="symbol">.</span>FixedSingleDropShadow<span class="symbol">;</span>

 <span class="comment">// new pdf conversion settings</span>
 <span class="keyword">this</span><span class="symbol">.</span>Settings <span class="symbol">=</span> <span class="keyword">new</span> Pdf<span class="number">2</span>ImageSettings<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>To ensure correct designer support, override versions of the
properties with new <code>DefaultValue</code> attributes were added. With
this done, it's time to add the new properties that will support
viewing PDF files. The new properties are:</p>
<ul>
<li><code>PdfFileName</code> - the filename of the PDF to view</li>
<li><code>PdfPassword</code> - specifies the password of the PDF file if one
is required to open it (<em>note, I haven't actually tested that
this works!</em>)</li>
<li><code>Settings</code> - uses the <code>Pdf2ImageSettings</code> class <a href="/post/convert-a-pdf-into-a-series-of-images-using-csharp-and-ghostscript">discussed
earlier</a> to control quality settings for the converted
document.</li>
<li><code>PageCache</code> - an internal dictionary which stores a <code>Bitmap</code>
against a page number to cache pages after these have loaded.</li>
</ul>
<p>With the exception of <code>PageCache</code>, each of these properties also
has backing event for change notifications, and as
<code>Pdf2ImageSettings</code> implements <code>INotifyPropertyChanged</code> we'll
also bind an event detect when the individual setting properties
are modified.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<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>Pdf<span class="number">2</span>ImageSettings<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> <span class="keyword">virtual</span> Pdf<span class="number">2</span>ImageSettings Settings
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _settings<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Settings <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_settings <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _settings<span class="symbol">.</span>PropertyChanged <span class="symbol">-=</span> SettingsPropertyChangedHandler<span class="symbol">;</span>

 _settings <span class="symbol">=</span> value<span class="symbol">;</span>
 _settings<span class="symbol">.</span>PropertyChanged <span class="symbol">+=</span> SettingsPropertyChangedHandler<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnSettingsChanged<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><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> SettingsPropertyChangedHandler<span class="symbol">(</span><span class="keyword">object</span> sender<span class="symbol">,</span> PropertyChangedEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnSettingsChanged<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnSettingsChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OpenPDF<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SettingsChanged <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>SettingsChanged<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="navigation-support">Navigation support</h2>
<p>Although the <code>PdfImageBox</code> doesn't supply a user interface for
navigating to different pages, we want to make it easy for the
hosting application to provide one. To support this, a new
<code>CurrentPage</code> property will be added for allowing the active
page to retrieved or set, and also a number of readonly
<code>CanMove*</code> properties. These properties allow the host to query
which navigation options are applicable in order to present the
correct UI.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">int</span> PageCount
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _converter <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">?</span> _converter<span class="symbol">.</span>PageCount <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>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="number">1</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">int</span> CurrentPage
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _currentPage<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>CurrentPage <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>value <span class="symbol">&lt;</span> <span class="number">1</span> <span class="symbol">||</span> value <span class="symbol">&gt;</span> <span class="keyword">this</span><span class="symbol">.</span>PageCount<span class="symbol">)</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentException<span class="symbol">(</span><span class="string">&quot;Page number is out of bounds&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 _currentPage <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnCurrentPageChanged<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">bool</span> CanMoveFirst
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>PageCount <span class="symbol">!=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>CurrentPage <span class="symbol">!=</span> <span class="number">1</span><span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>

<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">bool</span> CanMoveLast
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>PageCount <span class="symbol">!=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>CurrentPage <span class="symbol">!=</span> <span class="keyword">this</span><span class="symbol">.</span>PageCount<span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>

<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">bool</span> CanMoveNext
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>PageCount <span class="symbol">!=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>CurrentPage <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>PageCount<span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>

<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">bool</span> CanMovePrevious
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>PageCount <span class="symbol">!=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>CurrentPage <span class="symbol">&gt;</span> <span class="number">1</span><span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>
</pre>
</figure>
<p>Again, to make it easier for the host to connect to the control,
we also add some helper navigation methods.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">void</span> FirstPage<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CurrentPage <span class="symbol">=</span> <span class="number">1</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">void</span> LastPage<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CurrentPage <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PageCount<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">void</span> NextPage<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CurrentPage<span class="symbol">++</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">void</span> PreviousPage<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CurrentPage<span class="symbol">--</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Finally, it can sometimes take a few seconds to convert a page
in a PDF file. To allow the host to provide a busy notification,
such as setting the wait cursor or displaying a status bar
message, we'll add a pair of events which will be called before
and after a page is converted.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">event</span> EventHandler LoadingPage<span class="symbol">;</span>

<span class="keyword">public</span> <span class="keyword">event</span> EventHandler LoadedPage<span class="symbol">;</span>
</pre>
</figure>
<h4 id="opening-the-pdf-file">Opening the PDF file</h4>
<p>Each of the property changed handlers in turn call the <code>OpenPDF</code>
method. This method first clears any existing image cache and
then initializes the conversion class based on the current PDF
file name and quality settings. If the specified file is a valid
PDF, the first page is converted, cached, and displayed.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">void</span> OpenPDF<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>CleanUp<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>DesignMode<span class="symbol">)</span>
 <span class="symbol">{</span>
 _converter <span class="symbol">=</span> <span class="keyword">new</span> Pdf<span class="number">2</span>Image<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 PdfFileName <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PdfFileName<span class="symbol">,</span>
 PdfPassword <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PdfPassword<span class="symbol">,</span>
 Settings <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Settings
 <span class="symbol">}</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>PageCache<span class="symbol">=</span> <span class="keyword">new</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">int</span><span class="symbol">,</span> Bitmap<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 _currentPage <span class="symbol">=</span> <span class="number">1</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>PageCount <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _currentPage <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>CurrentPage <span class="symbol">=</span> <span class="number">1</span><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> CleanUp<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// release bitmaps</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>PageCache <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span>KeyValuePair<span class="symbol">&lt;</span><span class="keyword">int</span><span class="symbol">,</span> Bitmap<span class="symbol">&gt;</span> pair <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">.</span>PageCache<span class="symbol">)</span>
 pair<span class="symbol">.</span>Value<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>PageCache <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h4 id="displaying-the-image">Displaying the image</h4>
<p>Each time the <code>CurrentPage</code> property is changed, it calls the
<code>SetPageImage</code> method. This method first checks to ensure the
specified page is present in the cache. If it is not, it will
load the page in. Once the page is in the cache, it is then
displayed in the <code>ImageBox</code>, and the user can then pan and zoom
as with any other image.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> SetPageImage<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>DesignMode <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>PageCache <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">lock</span> <span class="symbol">(</span>_lock<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>PageCache<span class="symbol">.</span>ContainsKey<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>CurrentPage<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnLoadingPage<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>PageCache<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>CurrentPage<span class="symbol">,</span> _converter<span class="symbol">.</span>GetImage<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>CurrentPage<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnLoadedPage<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>PageCache<span class="symbol">[</span><span class="keyword">this</span><span class="symbol">.</span>CurrentPage<span class="symbol">]</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Note that we operate a lock during the execution of this method,
to ensure that you can't try and load the same page twice.</p>
<p>With this method in place, the control is complete and ready to
be used as a basic PDF viewer. In order to keep the article down
to a reasonable size, I've excluded some of the definitions,
overloads and helper methods; these can all be found in the
sample download below.</p>
<p>The sample project demonstrates all the features described above
and provides an example setting up a user interface for
navigating a PDF document.</p>
<h2 id="future-changes">Future changes</h2>
<p>At the moment, the <code>PdfImageBox</code> control processes on page at a
time and caches the results. This means that navigation through
already viewed pages is fast, but displaying new pages can be
less than ideal. A possible enhancement would be to make the
control multithreaded, and continue to load pages on a
background thread.</p>
<p>Another issue is that as the control is caching the converted
images in memory, it may use a lot of memory in order to display
large PDF files. Not quite sure on the best approach to resolve
this one, either to &quot;expire&quot; older pages, or to keep only a
fixed number in memory. Or even save each page to a temporary
disk file.</p>
<p>Finally, I haven't put in any handling at all for if the
converter fails to convert a given page... I'll add this to a
future update, and hopefully get the code hosted on an SVN
server for interested parties.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2011-09-04 - 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/extending-the-imagebox-component-to-display-the-contents-of-a-pdf-file-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCSS Syntax Highlighting in the DigitalRune Text Editor Controlurn:uuid:364dffb1-7e3a-4d8c-896a-219c539503af2011-07-08T19:21:26Z2011-07-08T19:21:26Z<p>For projects where I need some form of syntax highlighting, I
tend to use the open source <a href="http://www.digitalrune.com/Products/TextEditorControl/Overview.aspx" rel="external nofollow noopener">DigitalRune Text Editor Control</a>
which is a modified version of the text editor used in
<a href="http://sharpdevelop.net/OpenSource/SD/Default.aspx" rel="external nofollow noopener">SharpDevelop</a>. While it has a number of syntax definitions
built in, the one it didn't have was for CSS formatting.</p>
<p>After doing a quick search on the internet and finding pretty
much nothing, I created my own. This article describes that
process, along with how to embed the definition directly in a
custom version of the control, or loading it into the vendor
supplied control.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/css-syntax.png" class="gallery" title="A sample application demonstrating CSS syntax highlighting" ><img src="https://images.cyotek.com/image/thumbnail/devblog/css-syntax.png" alt="A sample application demonstrating CSS syntax highlighting" decoding="async" loading="lazy" /></a><figcaption>A sample application demonstrating CSS syntax highlighting</figcaption></figure><h2 id="creating-the-rule-set">Creating the rule set</h2>
<p>Each definition is an XML document which contains various
sections describing how to syntax highlight a document. An XSD
schema is available, named <em>Mode.xsd</em> and located in the
<em>/Resources</em> directory in the control's source code.</p>
<p>Here's an example of an (almost) empty definition - I've filled
in the definition name and the list of file extensions it will
support:</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;?</span><span class="name">xml</span> <span class="name">version</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">1.0</span><span class="symbol">&quot;</span> <span class="name">encoding</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">utf-8</span><span class="symbol">&quot;</span><span class="symbol">?&gt;</span>
<span class="symbol">&lt;</span><span class="name">SyntaxDefinition</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CSS</span><span class="symbol">&quot;</span> <span class="name">extensions</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">*.css</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">RuleSets</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">RuleSets</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">SyntaxDefinition</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>The <code>RuleSets</code> element contains one of more <code>RuleSet</code> elements
which in turn describe formatting. I'm not sure how the control
decides to process these, but in my example I started with an
unnamed rule which references a named rule, and in turn that
references another - seems to work fine.</p>
<p>There are two key constructs we'll be using for highlighting -
first is <strong>span</strong> highlighting, where an block of text which
starts and ends with given symbols is highlighted. The second is
<strong>keywords</strong>, where distinct words are highlighted. From having
a quick look through the source code to figure out problems,
there appears to be one or two other constructs available, but
I'll ignore these for now.</p>
<p>First, I need to add a rule for comments, which should be quite
straight forward - look for a <code>/*</code> and end with <code>*/</code>:</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">RuleSet</span> <span class="name">ignorecase</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Comment</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Green</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span><span class="symbol">&gt;</span>/*<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">End</span><span class="symbol">&gt;</span>*/<span class="symbol">&lt;/</span><span class="name">End</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">RuleSet</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>The <code>Span</code> tag creates a span highlighting construct. The
<code>Begin</code> and <code>End</code> tags describe the phrase that marks the
beginning and end of the text to match. The <code>stopateol</code>
attribute determines if the line breaks should stop at the end
of a line. The formatting properties should be evident!</p>
<p>Next, I added another span rule to process the highlighting of
the actual CSS rules - so anything between <code>{</code> and <code>}</code>.</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CssClass</span><span class="symbol">&quot;</span> <span class="name">rule</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CssClass</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Black</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span><span class="symbol">&gt;</span>{<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">End</span><span class="symbol">&gt;</span>}<span class="symbol">&lt;/</span><span class="name">End</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>Note this time the <code>rule</code> attribute - this is pointing to a new
ruleset (more on that below). Without this attribute, I found
that I was unable to style keywords and values inside the CSS
rule, as the span above always took precedence. The new ruleset
looks similar to this, although in this example I have stripped
out most of the CSS property names. (The list of which came from
<a href="http://www.w3schools.com/cssref/default.asp" rel="external nofollow noopener">w3schools</a>)</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">RuleSet</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CssClass</span><span class="symbol">&quot;</span> <span class="name">ignorecase</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Value</span><span class="symbol">&quot;</span> <span class="name">rule</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">ValueRules</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Blue</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Black</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>:<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">End</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Black</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>;<span class="symbol">&lt;/</span><span class="name">End</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">KeyWords</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CSSLevel1PropertyNames</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Red</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">background</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">background-attachment</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 (snip)
 <span class="symbol">&lt;/</span><span class="name">KeyWords</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">KeyWords</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CSSLevel2PropertyNames</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Red</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">border-collapse</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">border-spacing</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 (snip)
 <span class="symbol">&lt;/</span><span class="name">KeyWords</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">KeyWords</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CSSLevel3PropertyNames</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Red</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">@font-face</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">@keyframes</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 (snip)
 <span class="symbol">&lt;/</span><span class="name">KeyWords</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">RuleSet</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>First is a new span to highlight attribute values (found between
the <code>:</code> and <code>;</code> characters in blue, and then 3 sets of a new
construct - <code>KeyWords</code>. This basically matches a given word and
formats it appropriately. In this example, I have split each of
the 3 major CSS versions into separate sections, on the off
chance you want to reconfigure the file to only support a
subset, for example CSS1 and CSS2. Also note that I haven't
included any vendor prefixes.</p>
<p>One thing to note, in the <code>Value</code> span above, the begin and end
tags have <code>color</code> attributes. This overrides the overall span
color (blue) and colors those individual colors with the
override (black). Again, from checking the scheme it looks like
this can be done for most elements, and supports the <code>color</code>,
<code>bold</code> and <code>italic</code> attributes, plus a <code>bgcolor</code> attribute that
I haven't used yet.</p>
<p>The span in the above ruleset references a final ruleset, as
follows:</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">RuleSet</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">ValueRules</span><span class="symbol">&quot;</span> <span class="name">ignorecase</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Comment</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Green</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span><span class="symbol">&gt;</span>/*<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">End</span><span class="symbol">&gt;</span>*/<span class="symbol">&lt;/</span><span class="name">End</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>
 &lt;<span class="symbol">&lt;</span><span class="name">pan</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">String</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">BlueViolet</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span><span class="symbol">&gt;</span>&quot;<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">End</span><span class="symbol">&gt;</span>&quot;<span class="symbol">&lt;/</span><span class="name">End</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Span</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Char</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">BlueViolet</span><span class="symbol">&quot;</span> <span class="name">stopateol</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Begin</span><span class="symbol">&gt;</span>&#39;<span class="symbol">&lt;/</span><span class="name">Begin</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">End</span><span class="symbol">&gt;</span>&#39;<span class="symbol">&lt;/</span><span class="name">End</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">Span</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">KeyWords</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Flags</span><span class="symbol">&quot;</span> <span class="name">bold</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">true</span><span class="symbol">&quot;</span> <span class="name">italic</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">false</span><span class="symbol">&quot;</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">BlueViolet</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Key</span> <span class="name">word</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">!important</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
 <span class="symbol">&lt;/</span><span class="name">KeyWords</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">RuleSet</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>This ruleset has 3 spans, and one keyword. I had to duplicate
the comment span from the first ruleset, I couldn't comment
highlighting to work inside <code>{ }</code> blocks otherwise - probably
some subtlety of the definition format that I'm missing. This is
followed by two spans which highlight strings (depending on
whether single or double quoted). Finally, we have a keyword
rule for formatting <code>!important</code>. (Of course, ideally you
wouldn't be using this keyword at all, but you never know!)</p>
<p>Put together, this definition nicely highlights CSS. Except for
one thing - everything outside a comment or style block is
black. And I want it to be something else! Initially I tried
just setting the <code>ForeColor</code> property of the control itself, but
this was blatantly ignored when it drew itself. Fortunately a
scan of the schema gave the answer - you can add an
<code>Environment</code> tag and set up a large bunch of colors. Or one, in
this case.</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">Environment</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Default</span> <span class="name">color</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Maroon</span><span class="symbol">&quot;</span> <span class="name">bgcolor</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">White</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
<span class="symbol">&lt;/</span><span class="name">Environment</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>Now save the file somewhere with the <code>.xshd</code> extension - in
keeping with the convention of the existing definitions, I named
it <code>CSS-Mode.xshd</code>.</p>
<h2 id="loading-the-definition-into-the-text-editor-control">Loading the definition into the Text Editor control</h2>
<p>This is where I was a little bit stumped - as I didn't have a
clue how to get the definition in. Fortunately, DigitalRune's
technical support were able to help.</p>
<p>If you are using a custom version of the source code, you can
add the definition directly into the source and have it
available with the compiled assembly. However, if you are using
the vendor supplied assembly, you'll need to include the
definition with your application in order to load it in.</p>
<h2 id="compiling-the-definition-into-the-assembly">Compiling the definition into the assembly</h2>
<p>This is quite straight forward, and easily recommended if you
have a custom version.</p>
<ol>
<li><p>Copy the definition file into the <em>Resources</em> folder of the
control's project</p>
</li>
<li><p>Set the <strong>Build Action</strong> to be <em>Embedded Resource</em></p>
</li>
<li><p>Open <code>SyntaxModes.xml</code> located in the same folder and add a
mode tag which points to your definition, for
example</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">Mode</span> <span class="name">file</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CSS-Mode.xshd</span><span class="symbol">&quot;</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CSS</span><span class="symbol">&quot;</span> <span class="name">extensions</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">.css</span><span class="symbol">&quot;</span><span class="symbol">/&gt;</span>
</pre>
</figure>
<p>While I haven't checked to see if it is
enforced, common sense would suggest you ensure the <code>name</code>
and <code>extensions</code> attributes match in both the syntax
definition and the ruleset definition.</p>
</li>
<li><p>Compile the solution.</p>
</li>
</ol>
<p>With that done, your definition is now available for use!</p>
<h2 id="loading-the-definitions-externally">Loading the definitions externally</h2>
<p>You don't need to compile the definitions into the control
assembly, but can load them externally. To do this, you need to
have the definition file and the syntax mode file available for
loading.</p>
<ol>
<li><p>Add a new folder to your project and copy into this folder
your <code>.xshd</code> file and set the <strong>Copy to Output Directory</strong>
property to <em>Copy always</em>.</p>
</li>
<li><p>Create a file named <code>SyntaxMode.xml</code> in the folder, and paste
in the definition below. You'll also need to set the copy to
output directory attribute.</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;?</span><span class="name">xml</span> <span class="name">version</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">1.0</span><span class="symbol">&quot;</span> <span class="name">encoding</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">utf-8</span><span class="symbol">&quot;</span><span class="symbol">?&gt;</span>
<span class="symbol">&lt;</span><span class="name">SyntaxModes</span> <span class="name">version</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">1.0</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
 <span class="symbol">&lt;</span><span class="name">Mode</span> <span class="name">file</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CSS-Mode.xshd</span><span class="symbol">&quot;</span> <span class="name">name</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">CSS</span><span class="symbol">&quot;</span> <span class="name">extensions</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">.css</span><span class="symbol">&quot;</span> <span class="symbol">/&gt;</span>
<span class="symbol">&lt;/</span><span class="name">SyntaxModes</span><span class="symbol">&gt;</span>
</pre>
</figure>
</li>
<li><p>The following line of code will load the definition file into
the text editor control:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
HighlightingManager<span class="symbol">.</span>Manager<span class="symbol">.</span>AddSyntaxModeFileProvider<span class="symbol">(</span><span class="keyword">new</span> FileSyntaxModeProvider<span class="symbol">(</span>definitionsFolder<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
</li>
</ol>
<h2 id="setting-up-the-text-editor-control">Setting up the Text Editor Control</h2>
<p>To instruct instances of the Text Editor control to use CSS
syntax highlighting, add the following line of code to your
application (replacing <code>CSS</code> with the name of your definition
if you called it something different):</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
textEditorControl<span class="symbol">.</span>Document<span class="symbol">.</span>HighlightingStrategy <span class="symbol">=</span> HighlightingManager<span class="symbol">.</span>Manager<span class="symbol">.</span>FindHighlighter<span class="symbol">(</span><span class="string">&quot;CSS&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="syntax-highlighting-isnt-appearing-what-went-wrong">Syntax highlighting isn't appearing, what went wrong?</h2>
<p>Rather frustratingly, the control doesn't raise an error if a
definition file is invalid, it just silently ignores it and uses
a default highlighting scheme. Use the source code for the
control so you can catch the exceptions being raised by the
<code>HighlightingDefinitionParser</code> class in order to determine any
problems. Remember the definition you create is implicitly
linked to the schema and so must conform to it.</p>
<h2 id="sharpdevelop">SharpDevelop?</h2>
<p>As the DigitalRune control is derived from the original
SharpDevelop editing component, I believe this article and
sample code will work in exactly the same way for the
SharpDevelop control. However, I don't have this installed and
so this remains untested - let me know if it works for you!</p>
<h2 id="sample-application">Sample application</h2>
<p>The download available below includes the CSS definition file
and a sample application which will load in the definition
files. Note that no binaries are included in the archive, you'll
need to add a reference to a copy of the DigitalRune Text Editor
control installed on your own system.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2011-07-08 - 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/css-syntax-highlighting-in-the-digitalrune-text-editor-control .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comEnabling shell styles for the ListView and TreeView controls in C#urn:uuid:28e73b1b-08b7-4f14-9a1f-a17f42eeccc42011-04-16T17:42:53Z2011-04-16T17:25:02Z<p>For those who remember the Common Controls OCX's featured in
Visual Basic 5 and 6, there was one peculiarity of these. In
Visual Basic 5, the Common Controls were linked directly to
their shell counterparts. As the shell was updated, so did the
look of any VB app using these. However, for Visual Basic 6,
this behaviour was changed and they didn't use the shell for
drawing.</p>
<p>Curiously enough, history repeats itself in a limited way with
Visual Studio .NET. If you use the <code>ListView</code> or <code>TreeView</code>
controls on Windows Vista or higher, you'll find they are
somewhat drawn according to the &quot;classic&quot; Windows style - no
gradients on selection highlights, column separators (ListView)
or alternate +/- glyphs (TreeView).</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/shellcontrols1.png" class="gallery" title="Examples of the default TreeView and ListView controls in Windows 7" ><img src="https://images.cyotek.com/image/thumbnail/devblog/shellcontrols1.png" alt="Examples of the default TreeView and ListView controls in Windows 7" decoding="async" loading="lazy" /></a><figcaption>Examples of the default TreeView and ListView controls in Windows 7</figcaption></figure>
<p>Fortunately however, it is quite simple to enable this with a
single call to the <code>SetWindowTheme</code> API when creating the
control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;uxtheme.dll&quot;</span><span class="symbol">,</span> CharSet <span class="symbol">=</span> CharSet<span class="symbol">.</span>Unicode<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">extern</span> <span class="keyword">static</span> <span class="keyword">int</span> SetWindowTheme<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> <span class="keyword">string</span> pszSubAppName<span class="symbol">,</span> <span class="keyword">string</span> pszSubIdList<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>In the sample application (available for download from the link
below), we create two new <code>ListView</code> and <code>TreeView</code> classes
which inherit from their <code>System.Windows.Forms</code> counterparts.</p>
<p>In each class, override the <code>OnHandleCreated</code> method, and check
to see what OS is being run - if you try to call
<code>SetWindowTheme</code> on an unsupported OS, you'll get a crash. In
this case, I'm checking for Windows Vista or higher.</p>
<p>If the version is fine, call <code>SetWindowTheme</code> with the handle of
the control, and the name of the shell style - <strong>explorer</strong> in
this case.</p>
<p>It's as simple as that - now when you run the application, the
controls will be drawn using whatever shell styles are in use.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> System<span class="symbol">;</span>

<span class="keyword">namespace</span> ShellControlsExample
<span class="symbol">{</span>
 <span class="keyword">class</span> TreeView <span class="symbol">:</span> System<span class="symbol">.</span>Windows<span class="symbol">.</span>Forms<span class="symbol">.</span>TreeView
 <span class="symbol">{</span>
 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnHandleCreated<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnHandleCreated<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>DesignMode <span class="symbol">&amp;&amp;</span> Environment<span class="symbol">.</span>OSVersion<span class="symbol">.</span>Platform <span class="symbol">==</span> PlatformID<span class="symbol">.</span>Win<span class="number">32</span>NT <span class="symbol">&amp;&amp;</span> Environment<span class="symbol">.</span>OSVersion<span class="symbol">.</span>Version<span class="symbol">.</span>Major <span class="symbol">&gt;=</span> <span class="number">6</span><span class="symbol">)</span>
 NativeMethods<span class="symbol">.</span>SetWindowTheme<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> <span class="string">&quot;explorer&quot;</span><span class="symbol">,</span> <span class="keyword">null</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>For the <code>TreeView</code> control, I'd also recommend setting the
<code>ShowLines</code> property to <code>false</code> as it will look odd otherwise.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/shellcontrols2.png" class="gallery" title="Examples of the TreeView and ListView controls in Windows 7 after using the SetWindowTheme API" ><img src="https://images.cyotek.com/image/thumbnail/devblog/shellcontrols2.png" alt="Examples of the TreeView and ListView controls in Windows 7 after using the SetWindowTheme API" decoding="async" loading="lazy" /></a><figcaption>Examples of the TreeView and ListView controls in Windows 7 after using the SetWindowTheme API</figcaption></figure><h2 id="update-history">Update History</h2>
<ul>
<li>2011-04-16 - 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/enabling-shell-styles-for-the-listview-and-treeview-controls-in-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a WYSIWYG font ComboBox using C#urn:uuid:33ca6c15-e6c6-46e9-a7bb-3dfb282339b82011-02-19T20:05:31Z2011-02-19T20:05:31Z<p>This article shows how to use the built in ownerdraw
functionality of a standard Windows Forms ComboBox control to
display a WYSIWYG font list.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/font-combobox.png" class="gallery" title="The FontComboBox control in a sample application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/font-combobox.png" alt="The FontComboBox control in a sample application" decoding="async" loading="lazy" /></a><figcaption>The FontComboBox control in a sample application</figcaption></figure><h2 id="setting-up-the-control">Setting up the control</h2>
<p>To start, we'll create a new class, and inherit this from the
<code>ComboBox</code> control.</p>
<p>We are going to use variable ownerdraw for this sample, as it
gives us a little more flexibility without having to mess around
with the <code>ItemHeight</code> property. We'll add a constructor, and set
the ownerdraw mode here. Also, we'll add a new version of the
<code>DrawMode</code> property, which we'll both hide and disable the value
persistence. We always want the font list to be sorted, so for
now we'll do the same with the <code>Sorted</code> property.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> FontComboBox<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>DrawMode <span class="symbol">=</span> DrawMode<span class="symbol">.</span>OwnerDrawVariable<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Sorted <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">,</span> EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Never<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">new</span> DrawMode DrawMode
<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>DrawMode<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>DrawMode <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">,</span> EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Never<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">new</span> <span class="keyword">bool</span> Sorted
<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>Sorted<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>Sorted <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="caching-font-objects">Caching Font objects</h2>
<p>In order to avoid continuously creating and destroying font
objects, we'll create a internal cache of fonts. When it's time
to draw the control, the cache will be queried - if the
requested font exists, it will be returned, otherwise the font
will be created and added to the cache. This will be done via
the <code>GetFont</code> method below.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> Font GetFont<span class="symbol">(</span><span class="keyword">string</span> fontFamilyName<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">lock</span> <span class="symbol">(</span>_fontCache<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>_fontCache<span class="symbol">.</span>ContainsKey<span class="symbol">(</span>fontFamilyName<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Font font<span class="symbol">;</span>

 font <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Regular<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>font <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 font <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Bold<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>font <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 font <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Italic<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>font <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 font <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Bold <span class="symbol">|</span> FontStyle<span class="symbol">.</span>Italic<span class="symbol">)</span><span class="symbol">;</span>

 _fontCache<span class="symbol">.</span>Add<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> font<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> _fontCache<span class="symbol">[</span>fontFamilyName<span class="symbol">]</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> Font GetFont<span class="symbol">(</span><span class="keyword">string</span> fontFamilyName<span class="symbol">,</span> FontStyle fontStyle<span class="symbol">)</span>
<span class="symbol">{</span>
 Font font<span class="symbol">;</span>

 <span class="keyword">try</span>
 <span class="symbol">{</span>
 font <span class="symbol">=</span> <span class="keyword">new</span> Font<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>PreviewFontSize<span class="symbol">,</span> fontStyle<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">catch</span>
 <span class="symbol">{</span>
 font <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> font<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Note: Whilst testing the control, I discovered that some of
the fonts installed on the development system only had bold or
italic styles. The original version of this method, which
always attempts to get the normal style would cause a crash.</p>
</blockquote>
<p>Due to this, I changed the method to try and access the normal
style, and if that failed, to try the other styles. Perhaps
there is a better way of doing this, but I leave that as an
exercise for the future.</p>
<p>As we don't want the font size of the dropdown list to
necessarily match that of the display/edit portion, we'll add a
new property named `PreviewFontSize*.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">event</span> EventHandler PreviewFontSizeChanged<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="number">12</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">int</span> PreviewFontSize
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _previewFontSize<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 _previewFontSize <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnPreviewFontSizeChanged<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnPreviewFontSizeChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>PreviewFontSizeChanged <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 PreviewFontSizeChanged<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CalculateLayout<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>When certain actions occur, such as this property changing, we
want to calculate the height of items in the dropdown list.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> CalculateLayout<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ClearFontCache<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Font font <span class="symbol">=</span> <span class="keyword">new</span> Font<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Font<span class="symbol">.</span>FontFamily<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>PreviewFontSize<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Size textSize<span class="symbol">;</span>

 textSize <span class="symbol">=</span> TextRenderer<span class="symbol">.</span>MeasureText<span class="symbol">(</span><span class="string">&quot;yY&quot;</span><span class="symbol">,</span> font<span class="symbol">)</span><span class="symbol">;</span>
 _itemHeight <span class="symbol">=</span> textSize<span class="symbol">.</span>Height <span class="symbol">+</span> <span class="number">2</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="loading-the-list-of-font-families">Loading the list of font families</h2>
<p>In order to avoid slowing the control down without reason, we'll
delay loading the list of font families until there is a reason,
either when the control's text has changed, or when the control
gets focus.</p>
<p>This will be done by creating a <code>LoadFontFamilies</code> method which
will be called by overriding <code>OnGotFocus</code> and <code>OnTextChanged</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">void</span> LoadFontFamilies<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">==</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Cursor<span class="symbol">.</span>Current <span class="symbol">=</span> Cursors<span class="symbol">.</span>WaitCursor<span class="symbol">;</span>

 <span class="keyword">foreach</span> <span class="symbol">(</span>FontFamily fontFamily <span class="keyword">in</span> FontFamily<span class="symbol">.</span>Families<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span>fontFamily<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">;</span>

 Cursor<span class="symbol">.</span>Current <span class="symbol">=</span> Cursors<span class="symbol">.</span>Default<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnGotFocus<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>LoadFontFamilies<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnGotFocus<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnTextChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnTextChanged<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">==</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> selectedIndex<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>LoadFontFamilies<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 selectedIndex <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>FindStringExact<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Text<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>selectedIndex <span class="symbol">!=</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>SelectedIndex <span class="symbol">=</span> selectedIndex<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="drawing-the-items">Drawing the items</h2>
<p>Drawing an overdraw ComboBox is done by overriding the
<code>OnDrawItem</code> method. However, as we have told the control we are
doing variable sized ownerdraw, we also need to override
<code>OnMeasureItem</code>. This method allows us to define the size for
each item, or in the case of this control to set the height of
each item to match the pixel height calculated for the value of
the <code>PreviewFontSize</code> property.</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> OnMeasureItem<span class="symbol">(</span>MeasureItemEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMeasureItem<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Index <span class="symbol">&gt;</span> <span class="symbol">-</span><span class="number">1</span> <span class="symbol">&amp;&amp;</span> e<span class="symbol">.</span>Index <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count<span class="symbol">)</span>
 <span class="symbol">{</span>
 e<span class="symbol">.</span>ItemHeight <span class="symbol">=</span> _itemHeight<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnDrawItem<span class="symbol">(</span>DrawItemEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnDrawItem<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Index <span class="symbol">&gt;</span> <span class="symbol">-</span><span class="number">1</span> <span class="symbol">&amp;&amp;</span> e<span class="symbol">.</span>Index <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count<span class="symbol">)</span>
 <span class="symbol">{</span>
 e<span class="symbol">.</span>DrawBackground<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">(</span>e<span class="symbol">.</span>State <span class="symbol">&amp;</span> DrawItemState<span class="symbol">.</span>Focus<span class="symbol">)</span> <span class="symbol">==</span> DrawItemState<span class="symbol">.</span>Focus<span class="symbol">)</span>
 e<span class="symbol">.</span>DrawFocusRectangle<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>SolidBrush textBrush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span>e<span class="symbol">.</span>ForeColor<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">string</span> fontFamilyName<span class="symbol">;</span>

 fontFamilyName <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">[</span>e<span class="symbol">.</span>Index<span class="symbol">]</span><span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawString<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">)</span><span class="symbol">,</span> textBrush<span class="symbol">,</span> e<span class="symbol">.</span>Bounds<span class="symbol">,</span> _stringFormat<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The actual drawing is very simple - we use the built in drawing
for the background and focus rectangle, and then use the
<code>Graphics</code> object to draw the text using the <code>GetFont</code> method
explained above.</p>
<p>You might notice that the above code is referencing a previously
defined <code>StringFormat</code> object. This is created using the below
method.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> CreateStringFormat<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_stringFormat <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _stringFormat<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 _stringFormat <span class="symbol">=</span> <span class="keyword">new</span> StringFormat<span class="symbol">(</span>StringFormatFlags<span class="symbol">.</span>NoWrap<span class="symbol">)</span><span class="symbol">;</span>
 _stringFormat<span class="symbol">.</span>Trimming <span class="symbol">=</span> StringTrimming<span class="symbol">.</span>EllipsisCharacter<span class="symbol">;</span>
 _stringFormat<span class="symbol">.</span>HotkeyPrefix <span class="symbol">=</span> HotkeyPrefix<span class="symbol">.</span>None<span class="symbol">;</span>
 _stringFormat<span class="symbol">.</span>Alignment <span class="symbol">=</span> StringAlignment<span class="symbol">.</span>Near<span class="symbol">;</span>
 _stringFormat<span class="symbol">.</span>LineAlignment <span class="symbol">=</span> StringAlignment<span class="symbol">.</span>Center<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsUsingRTL<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">)</span><span class="symbol">)</span>
 _stringFormat<span class="symbol">.</span>FormatFlags <span class="symbol">|=</span> StringFormatFlags<span class="symbol">.</span>DirectionRightToLeft<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">bool</span> IsUsingRTL<span class="symbol">(</span>Control control<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>control<span class="symbol">.</span>RightToLeft <span class="symbol">==</span> RightToLeft<span class="symbol">.</span>Yes<span class="symbol">)</span>
 result <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>control<span class="symbol">.</span>RightToLeft <span class="symbol">==</span> RightToLeft<span class="symbol">.</span>Inherit <span class="symbol">&amp;&amp;</span> control<span class="symbol">.</span>Parent <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 result <span class="symbol">=</span> IsUsingRTL<span class="symbol">(</span>control<span class="symbol">.</span>Parent<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 result <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="cleaning-up">Cleaning up</h2>
<p>As we are creating a large number of objects, we need to clean
these up in the controls <code>Dispose</code> method.</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> Dispose<span class="symbol">(</span><span class="keyword">bool</span> disposing<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ClearFontCache<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

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

 <span class="keyword">base</span><span class="symbol">.</span>Dispose<span class="symbol">(</span>disposing<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> ClearFontCache<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_fontCache <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span><span class="keyword">string</span> key <span class="keyword">in</span> _fontCache<span class="symbol">.</span>Keys<span class="symbol">)</span>
 _fontCache<span class="symbol">[</span>key<span class="symbol">]</span><span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 _fontCache<span class="symbol">.</span>Clear<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="suggestions-for-improvement">Suggestions for improvement</h3>
<p>The control as it stands is a basic example, and depending on
your application's needs, it could be further expanded. For
example:</p>
<ul>
<li>Currently each instance of the control will use its own font
cache. By making the cache and access methods static, a single
cache could be used by all instances</li>
<li>When you select a font in Word, this is added to a kind of
&quot;recently used&quot; list at the top of Word's own font picker. The
same sort of functionality could be quite easily added to this
control.</li>
<li>Currently the font text is displayed on a single line. If the
control isn't wide enough, the text is trimmed and therefore
it may not be always possible to tell the full name of a font.
Either tooltip support or drawing across multiple lines could
help with this, or by resizing the dropdown component to be
the minimum width required to display all font names without
trimming.</li>
</ul>
<h3 id="full-source">Full source</h3>
<p>The full source of the class is below.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> System<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>Collections<span class="symbol">.</span>Generic<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>ComponentModel<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>Drawing<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>Drawing<span class="symbol">.</span>Text<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>Windows<span class="symbol">.</span>Forms<span class="symbol">;</span>

<span class="keyword">namespace</span> Cyotek<span class="symbol">.</span>Windows<span class="symbol">.</span>Forms
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">class</span> FontComboBox <span class="symbol">:</span> ComboBox
 <span class="symbol">{</span>
 <span class="keyword">#region</span>&#160;&#160;Private&#160;Member&#160;Declarations&#160;&#160;

 <span class="keyword">private</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Font<span class="symbol">&gt;</span> _fontCache<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">int</span> _itemHeight<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">int</span> _previewFontSize<span class="symbol">;</span>
 <span class="keyword">private</span> StringFormat _stringFormat<span class="symbol">;</span>

 <span class="keyword">public</span> FontComboBox<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _fontCache <span class="symbol">=</span> <span class="keyword">new</span> Dictionary<span class="symbol">&lt;</span><span class="keyword">string</span><span class="symbol">,</span> Font<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>DrawMode <span class="symbol">=</span> DrawMode<span class="symbol">.</span>OwnerDrawVariable<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Sorted <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>PreviewFontSize <span class="symbol">=</span> <span class="number">12</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CalculateLayout<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>CreateStringFormat<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">event</span> EventHandler PreviewFontSizeChanged<span class="symbol">;</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> Dispose<span class="symbol">(</span><span class="keyword">bool</span> disposing<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ClearFontCache<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

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

 <span class="keyword">base</span><span class="symbol">.</span>Dispose<span class="symbol">(</span>disposing<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnDrawItem<span class="symbol">(</span>DrawItemEventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnDrawItem<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Index <span class="symbol">&gt;</span> <span class="symbol">-</span><span class="number">1</span> <span class="symbol">&amp;&amp;</span> e<span class="symbol">.</span>Index <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count<span class="symbol">)</span>
 <span class="symbol">{</span>
 e<span class="symbol">.</span>DrawBackground<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">(</span>e<span class="symbol">.</span>State <span class="symbol">&amp;</span> DrawItemState<span class="symbol">.</span>Focus<span class="symbol">)</span> <span class="symbol">==</span> DrawItemState<span class="symbol">.</span>Focus<span class="symbol">)</span>
 e<span class="symbol">.</span>DrawFocusRectangle<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>SolidBrush textBrush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span>e<span class="symbol">.</span>ForeColor<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">string</span> fontFamilyName<span class="symbol">;</span>

 fontFamilyName <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">[</span>e<span class="symbol">.</span>Index<span class="symbol">]</span><span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawString<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">)</span><span class="symbol">,</span> textBrush<span class="symbol">,</span> e<span class="symbol">.</span>Bounds<span class="symbol">,</span> _stringFormat<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnFontChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnFontChanged<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CalculateLayout<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnGotFocus<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>LoadFontFamilies<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnGotFocus<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMeasureItem<span class="symbol">(</span>MeasureItemEventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMeasureItem<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Index <span class="symbol">&gt;</span> <span class="symbol">-</span><span class="number">1</span> <span class="symbol">&amp;&amp;</span> e<span class="symbol">.</span>Index <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count<span class="symbol">)</span>
 <span class="symbol">{</span>
 e<span class="symbol">.</span>ItemHeight <span class="symbol">=</span> _itemHeight<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnRightToLeftChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnRightToLeftChanged<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CreateStringFormat<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnTextChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnTextChanged<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">==</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> selectedIndex<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>LoadFontFamilies<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 selectedIndex <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>FindStringExact<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Text<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>selectedIndex <span class="symbol">!=</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>SelectedIndex <span class="symbol">=</span> selectedIndex<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">void</span> LoadFontFamilies<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Count <span class="symbol">==</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Cursor<span class="symbol">.</span>Current <span class="symbol">=</span> Cursors<span class="symbol">.</span>WaitCursor<span class="symbol">;</span>

 <span class="keyword">foreach</span> <span class="symbol">(</span>FontFamily fontFamily <span class="keyword">in</span> FontFamily<span class="symbol">.</span>Families<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span>fontFamily<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">;</span>

 Cursor<span class="symbol">.</span>Current <span class="symbol">=</span> Cursors<span class="symbol">.</span>Default<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">,</span> EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Never<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">new</span> DrawMode DrawMode
 <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>DrawMode<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>DrawMode <span class="symbol">=</span> value<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="number">12</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">int</span> PreviewFontSize
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _previewFontSize<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 _previewFontSize <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>OnPreviewFontSizeChanged<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">,</span> EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Never<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">new</span> <span class="keyword">bool</span> Sorted
 <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>Sorted<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>Sorted <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

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

 <span class="keyword">using</span> <span class="symbol">(</span>Font font <span class="symbol">=</span> <span class="keyword">new</span> Font<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Font<span class="symbol">.</span>FontFamily<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>PreviewFontSize<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Size textSize<span class="symbol">;</span>

 textSize <span class="symbol">=</span> TextRenderer<span class="symbol">.</span>MeasureText<span class="symbol">(</span><span class="string">&quot;yY&quot;</span><span class="symbol">,</span> font<span class="symbol">)</span><span class="symbol">;</span>
 _itemHeight <span class="symbol">=</span> textSize<span class="symbol">.</span>Height <span class="symbol">+</span> <span class="number">2</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">bool</span> IsUsingRTL<span class="symbol">(</span>Control control<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>control<span class="symbol">.</span>RightToLeft <span class="symbol">==</span> RightToLeft<span class="symbol">.</span>Yes<span class="symbol">)</span>
 result <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>control<span class="symbol">.</span>RightToLeft <span class="symbol">==</span> RightToLeft<span class="symbol">.</span>Inherit <span class="symbol">&amp;&amp;</span> control<span class="symbol">.</span>Parent <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 result <span class="symbol">=</span> IsUsingRTL<span class="symbol">(</span>control<span class="symbol">.</span>Parent<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 result <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> ClearFontCache<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_fontCache <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span><span class="keyword">string</span> key <span class="keyword">in</span> _fontCache<span class="symbol">.</span>Keys<span class="symbol">)</span>
 _fontCache<span class="symbol">[</span>key<span class="symbol">]</span><span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 _fontCache<span class="symbol">.</span>Clear<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> CreateStringFormat<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_stringFormat <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 _stringFormat<span class="symbol">.</span>Dispose<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 _stringFormat <span class="symbol">=</span> <span class="keyword">new</span> StringFormat<span class="symbol">(</span>StringFormatFlags<span class="symbol">.</span>NoWrap<span class="symbol">)</span><span class="symbol">;</span>
 _stringFormat<span class="symbol">.</span>Trimming <span class="symbol">=</span> StringTrimming<span class="symbol">.</span>EllipsisCharacter<span class="symbol">;</span>
 _stringFormat<span class="symbol">.</span>HotkeyPrefix <span class="symbol">=</span> HotkeyPrefix<span class="symbol">.</span>None<span class="symbol">;</span>
 _stringFormat<span class="symbol">.</span>Alignment <span class="symbol">=</span> StringAlignment<span class="symbol">.</span>Near<span class="symbol">;</span>
 _stringFormat<span class="symbol">.</span>LineAlignment <span class="symbol">=</span> StringAlignment<span class="symbol">.</span>Center<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsUsingRTL<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">)</span><span class="symbol">)</span>
 _stringFormat<span class="symbol">.</span>FormatFlags <span class="symbol">|=</span> StringFormatFlags<span class="symbol">.</span>DirectionRightToLeft<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> Font GetFont<span class="symbol">(</span><span class="keyword">string</span> fontFamilyName<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">lock</span> <span class="symbol">(</span>_fontCache<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>_fontCache<span class="symbol">.</span>ContainsKey<span class="symbol">(</span>fontFamilyName<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Font font<span class="symbol">;</span>

 font <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Regular<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>font <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 font <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Bold<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>font <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 font <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Italic<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>font <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 font <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetFont<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> FontStyle<span class="symbol">.</span>Bold <span class="symbol">|</span> FontStyle<span class="symbol">.</span>Italic<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>font <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 font <span class="symbol">=</span> <span class="symbol">(</span>Font<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>Font<span class="symbol">.</span>Clone<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 _fontCache<span class="symbol">.</span>Add<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> font<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> _fontCache<span class="symbol">[</span>fontFamilyName<span class="symbol">]</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> Font GetFont<span class="symbol">(</span><span class="keyword">string</span> fontFamilyName<span class="symbol">,</span> FontStyle fontStyle<span class="symbol">)</span>
 <span class="symbol">{</span>
 Font font<span class="symbol">;</span>

 <span class="keyword">try</span>
 <span class="symbol">{</span>
 font <span class="symbol">=</span> <span class="keyword">new</span> Font<span class="symbol">(</span>fontFamilyName<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>PreviewFontSize<span class="symbol">,</span> fontStyle<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">catch</span>
 <span class="symbol">{</span>
 font <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> font<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> OnPreviewFontSizeChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>PreviewFontSizeChanged <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 PreviewFontSizeChanged<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CalculateLayout<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="update-history">Update History</h2>
<ul>
<li>2011-02-19 - 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-wysiwyg-font-combobox-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a scrollable and zoomable image viewer in C# Part 4urn:uuid:6344e9eb-fd6a-42d7-9a0d-3d09d3883bfa2012-11-25T09:20:14Z2010-08-28T15:49:10Z<p>In the conclusion to our series on building a scrollable and
zoomable image viewer, we'll add support for zooming, auto
centering, size to fit and some display optimizations and
enhancements.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-4a.png" class="gallery" title="The ImageBox sample application showing a zoomed in image." ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-4a.png" alt="The ImageBox sample application showing a zoomed in image." decoding="async" loading="lazy" /></a><figcaption>The ImageBox sample application showing a zoomed in image.</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-4b.png" class="gallery" title="The ImageBox sample application showing a zoomed out image, with auto centering and the transparency grid only displayed behind the image." ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-4b.png" alt="The ImageBox sample application showing a zoomed out image, with auto centering and the transparency grid only displayed behind the image." decoding="async" loading="lazy" /></a><figcaption>The ImageBox sample application showing a zoomed out image, with auto centering and the transparency grid only displayed behind the image.</figcaption></figure><h2 id="getting-started">Getting Started</h2>
<p>Unlike parts 2 and 3, we're actually adding quite a lot of new
functionality, some of it more complicated than others.</p>
<p>First, we're going to remove the <code>ShowGrid</code> property. This
originally was a simple on/off flag, but we want more control
this time.</p>
<p>We've also got a number of new properties and backing events to
add:</p>
<ul>
<li><code>AutoCenter</code> - controls if the image is automatically centered
in the display area if the image isn't scrolled.</li>
<li><code>SizeToFit</code> - if this property is set, the image will
automatically zoom to the maximum size for displaying the
entire image.</li>
<li><code>GridDisplayMode</code> - this property, which replaces <code>ShowGrid</code>
will determine how the background grid is to be drawn.</li>
<li><code>InterpolationMode</code> - determines how the zoomed image will be
rendered.</li>
<li><code>Zoom</code> - allows you to specify the zoom level.</li>
<li><code>ZoomIncrement</code> - specifies how much the zoom is increased or
decreased using the scroll wheel.</li>
<li><code>ZoomFactor</code> - this protected property returns the current
zoom as used internally for scaling.</li>
<li><code>ScaledImageWidth</code> and <code>ScaledImageHeight</code> - these protected
properties return the size of the image adjusted for the
current zoom.</li>
</ul>
<p>Usually the properties are simple assignments, which compare the
values before assignment and raise an event. The zoom property
is slightly different as it will ensure that the new value fits
within a given range before setting it.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> <span class="keyword">int</span> MinZoom <span class="symbol">=</span> <span class="number">10</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> <span class="keyword">int</span> MaxZoom <span class="symbol">=</span> <span class="number">3500</span><span class="symbol">;</span>

<span class="symbol">[</span>DefaultValue<span class="symbol">(</span><span class="number">100</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>
<span class="keyword">public</span> <span class="keyword">int</span> Zoom
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _zoom<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>value <span class="symbol">&lt;</span> ImageBox<span class="symbol">.</span>MinZoom<span class="symbol">)</span>
 value <span class="symbol">=</span> ImageBox<span class="symbol">.</span>MinZoom<span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>value <span class="symbol">&gt;</span> ImageBox<span class="symbol">.</span>MaxZoom<span class="symbol">)</span>
 value <span class="symbol">=</span> ImageBox<span class="symbol">.</span>MaxZoom<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_zoom <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 _zoom <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnZoomChanged<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Using the <code>MinZoom</code> and <code>MaxZoom</code> constants we are specifying a
minimum value of 10% and a maximum of 3500%. The values you are
assign are more or less down to your own personal preferences -
I don't have any indications of what a &quot;best&quot; maximum value
would be.</p>
<p>Setting the <code>SizeToFit</code> property should disable the <code>AutoPan</code>
property and vice versa.</p>
<h2 id="layout-updates">Layout Updates</h2>
<p>Several parts of the component work from the image size, however
as these now need to account for any zoom level, all such calls
now use the <code>ScaledImageWidth</code> and <code>ScaledImageHeight</code>
properties.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">int</span> ScaledImageHeight
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">?</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Size<span class="symbol">.</span>Height <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span> <span class="symbol">:</span> <span class="number">0</span><span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">int</span> ScaledImageWidth
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">?</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Size<span class="symbol">.</span>Width <span class="symbol">*</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span> <span class="symbol">:</span> <span class="number">0</span><span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">double</span> ZoomFactor
<span class="symbol">{</span> <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">/</span> <span class="number">100</span><span class="symbol">;</span> <span class="symbol">}</span> <span class="symbol">}</span>
</pre>
</figure>
<p>The <code>AdjustLayout</code> method which determines the appropriate
course of action when certain properties are changed has been
updated to support the size to fit functionality by calling the
new <code>ZoomToFit</code> method.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> AdjustLayout<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AutoSize<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>AdjustSize<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>SizeToFit<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>ZoomToFit<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AutoScroll<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>AdjustViewPort<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="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">void</span> ZoomToFit<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle innerRectangle<span class="symbol">;</span>
 <span class="keyword">double</span> zoom<span class="symbol">;</span>
 <span class="keyword">double</span> aspectRatio<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>AutoScrollMinSize <span class="symbol">=</span> Size<span class="symbol">.</span>Empty<span class="symbol">;</span>

 innerRectangle <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetInsideViewPort<span class="symbol">(</span><span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width <span class="symbol">&gt;</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height<span class="symbol">)</span>
 <span class="symbol">{</span>
 aspectRatio <span class="symbol">=</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span>innerRectangle<span class="symbol">.</span>Width<span class="symbol">)</span> <span class="symbol">/</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">)</span><span class="symbol">;</span>
 zoom <span class="symbol">=</span> aspectRatio <span class="symbol">*</span> <span class="number">100.0</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>innerRectangle<span class="symbol">.</span>Height <span class="symbol">&lt;</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height <span class="symbol">*</span> zoom<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">100.0</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 aspectRatio <span class="symbol">=</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span>innerRectangle<span class="symbol">.</span>Height<span class="symbol">)</span> <span class="symbol">/</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>
 zoom <span class="symbol">=</span> aspectRatio <span class="symbol">*</span> <span class="number">100.0</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 aspectRatio <span class="symbol">=</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span>innerRectangle<span class="symbol">.</span>Height<span class="symbol">)</span> <span class="symbol">/</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>
 zoom <span class="symbol">=</span> aspectRatio <span class="symbol">*</span> <span class="number">100.0</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>innerRectangle<span class="symbol">.</span>Width <span class="symbol">&lt;</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width <span class="symbol">*</span> zoom<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">100.0</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 aspectRatio <span class="symbol">=</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span>innerRectangle<span class="symbol">.</span>Width<span class="symbol">)</span> <span class="symbol">/</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Width<span class="symbol">)</span><span class="symbol">;</span>
 zoom <span class="symbol">=</span> aspectRatio <span class="symbol">*</span> <span class="number">100.0</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>Math<span class="symbol">.</span>Round<span class="symbol">(</span>Math<span class="symbol">.</span>Floor<span class="symbol">(</span>zoom<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Due to the additional complexity in positioning and sizing,
we're also adding functions to return the different regions in
use by the control.</p>
<ul>
<li><code>GetImageViewPort</code> - returns a rectangle representing the size
of the drawn image.</li>
<li><code>GetInsideViewPort</code> - returns a rectangle representing the
client area of the control, offset by the current border
style, and optionally padding.</li>
<li><code>GetSourceImageRegion</code> - returns a rectangle representing the
area of the source image that will be drawn onto the control.</li>
</ul>
<p>The sample project has been updated to be able to display the
results of the <code>GetImageViewPort</code> and <code>GetSourceImageRegion</code>
functions.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">virtual</span> Rectangle GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 Rectangle viewPort<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Rectangle innerRectangle<span class="symbol">;</span>
 Point offset<span class="symbol">;</span>

 innerRectangle <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetInsideViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AutoCenter<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>

 x <span class="symbol">=</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>HScroll <span class="symbol">?</span> <span class="symbol">(</span>innerRectangle<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ScaledImageWidth <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Horizontal<span class="symbol">)</span><span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span> <span class="symbol">:</span> <span class="number">0</span><span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>VScroll <span class="symbol">?</span> <span class="symbol">(</span>innerRectangle<span class="symbol">.</span>Height <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ScaledImageHeight <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Vertical<span class="symbol">)</span><span class="symbol">)</span> <span class="symbol">/</span> <span class="number">2</span> <span class="symbol">:</span> <span class="number">0</span><span class="symbol">;</span>

 offset <span class="symbol">=</span> <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 offset <span class="symbol">=</span> Point<span class="symbol">.</span>Empty<span class="symbol">;</span>

 viewPort <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>offset<span class="symbol">.</span>X <span class="symbol">+</span> innerRectangle<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Left<span class="symbol">,</span> offset<span class="symbol">.</span>Y <span class="symbol">+</span> innerRectangle<span class="symbol">.</span>Top <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Top<span class="symbol">,</span> innerRectangle<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Horizontal <span class="symbol">+</span> <span class="symbol">(</span>offset<span class="symbol">.</span>X <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">,</span> innerRectangle<span class="symbol">.</span>Height <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Vertical <span class="symbol">+</span> <span class="symbol">(</span>offset<span class="symbol">.</span>Y <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 viewPort <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Empty<span class="symbol">;</span>

 <span class="keyword">return</span> viewPort<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> Rectangle GetInsideViewPort<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>GetInsideViewPort<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">virtual</span> Rectangle GetInsideViewPort<span class="symbol">(</span><span class="keyword">bool</span> includePadding<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> left<span class="symbol">;</span>
 <span class="keyword">int</span> top<span class="symbol">;</span>
 <span class="keyword">int</span> width<span class="symbol">;</span>
 <span class="keyword">int</span> height<span class="symbol">;</span>
 <span class="keyword">int</span> borderOffset<span class="symbol">;</span>

 borderOffset <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetBorderOffset<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 left <span class="symbol">=</span> borderOffset<span class="symbol">;</span>
 top <span class="symbol">=</span> borderOffset<span class="symbol">;</span>
 width <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ClientSize<span class="symbol">.</span>Width <span class="symbol">-</span> <span class="symbol">(</span>borderOffset <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 height <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ClientSize<span class="symbol">.</span>Height <span class="symbol">-</span> <span class="symbol">(</span>borderOffset <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>includePadding<span class="symbol">)</span>
 <span class="symbol">{</span>
 left <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Left<span class="symbol">;</span>
 top <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Top<span class="symbol">;</span>
 width <span class="symbol">-=</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Horizontal<span class="symbol">;</span>
 height <span class="symbol">-=</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Vertical<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>left<span class="symbol">,</span> top<span class="symbol">,</span> width<span class="symbol">,</span> height<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">virtual</span> Rectangle GetSourceImageRegion<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> sourceLeft<span class="symbol">;</span>
 <span class="keyword">int</span> sourceTop<span class="symbol">;</span>
 <span class="keyword">int</span> sourceWidth<span class="symbol">;</span>
 <span class="keyword">int</span> sourceHeight<span class="symbol">;</span>
 Rectangle viewPort<span class="symbol">;</span>
 Rectangle region<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 viewPort <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 sourceLeft <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">-</span><span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>X <span class="symbol">/</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">;</span>
 sourceTop <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">-</span><span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>Y <span class="symbol">/</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">;</span>
 sourceWidth <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span>viewPort<span class="symbol">.</span>Width <span class="symbol">/</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">;</span>
 sourceHeight <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span>viewPort<span class="symbol">.</span>Height <span class="symbol">/</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomFactor<span class="symbol">)</span><span class="symbol">;</span>

 region <span class="symbol">=</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>sourceLeft<span class="symbol">,</span> sourceTop<span class="symbol">,</span> sourceWidth<span class="symbol">,</span> sourceHeight<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 region <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Empty<span class="symbol">;</span>

 <span class="keyword">return</span> region<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="drawing-the-control">Drawing the control</h2>
<p>As with the previous versions, the control is drawn by
overriding <code>OnPaint</code>, this time we are not using clip regions or
drawing the entire image even if only a portion of it is
visible.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="comment">// draw the borders</span>
<span class="keyword">switch</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>BorderStyle<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">case</span> BorderStyle<span class="symbol">.</span>FixedSingle<span class="symbol">:</span>
 ControlPaint<span class="symbol">.</span>DrawBorder<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ForeColor<span class="symbol">,</span> ButtonBorderStyle<span class="symbol">.</span>Solid<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> BorderStyle<span class="symbol">.</span>Fixed<span class="number">3</span>D<span class="symbol">:</span>
 ControlPaint<span class="symbol">.</span>DrawBorder<span class="number">3</span>D<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> Border<span class="number">3</span>DStyle<span class="symbol">.</span>Sunken<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Depending on the value of the <code>GridDisplayMode</code> property, the
background tile grid will either not be displayed, will be
displayed to fill the client area of the control, or new for
this update, to only fill the area behind the image. The
remainder of the control is filled with the background color.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Rectangle innerRectangle<span class="symbol">;</span>

innerRectangle <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetInsideViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

<span class="comment">// draw the background</span>
<span class="keyword">using</span> <span class="symbol">(</span>SolidBrush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>BackColor<span class="symbol">)</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> innerRectangle<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">if</span> <span class="symbol">(</span>_texture <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>GridDisplayMode <span class="symbol">!=</span> ImageBoxGridDisplayMode<span class="symbol">.</span>None<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">switch</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>GridDisplayMode<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> ImageBoxGridDisplayMode<span class="symbol">.</span>Image<span class="symbol">:</span>
 Rectangle fillRectangle<span class="symbol">;</span>

 fillRectangle <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>_texture<span class="symbol">,</span> fillRectangle<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>fillRectangle<span class="symbol">.</span>Equals<span class="symbol">(</span>innerRectangle<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 fillRectangle<span class="symbol">.</span>Inflate<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>
 ControlPaint<span class="symbol">.</span>DrawBorder<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">,</span> fillRectangle<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ForeColor<span class="symbol">,</span> ButtonBorderStyle<span class="symbol">.</span>Solid<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> ImageBoxGridDisplayMode<span class="symbol">.</span>Client<span class="symbol">:</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>_texture<span class="symbol">,</span> innerRectangle<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Previous versions of the control drew the entire image using the
<code>DrawImageUnscaled</code> method of the <code>Graphics</code> object. In this
final version, we're going to be a little more intelligent and
only draw the visible area, removing the need for the previous
clip region. The <code>InterpolationMode</code> is used to determine how
the image is drawn when it is zoomed in or out.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="comment">// draw the image</span>
g<span class="symbol">.</span>InterpolationMode <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>InterpolationMode<span class="symbol">;</span>
g<span class="symbol">.</span>DrawImage<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetImageViewPort<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetSourceImageRegion<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">,</span> GraphicsUnit<span class="symbol">.</span>Pixel<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="zooming-support">Zooming Support</h2>
<p>With the control now all set up and fully supporting zoom, it's
time to allow the end user to be able to change the zoom.</p>
<p>The first step is to disable the ability to double click the
control, by modifying the control styles in the constructor.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">this</span><span class="symbol">.</span>SetStyle<span class="symbol">(</span>ControlStyles<span class="symbol">.</span>StandardDoubleClick<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>We're going to allow the zoom to be changed two ways - by either
scrolling the mouse wheel, or left/right clicking the control.</p>
<p>By overriding <code>OnMouseWheel</code>, we can be notified when the user
spins the wheel, and in which direction. We then adjust the zoom
using the value of the <code>ZoomIncrement</code> property. If a modifier
key such as Shift or Control is pressed, then we'll modify the
zoom by five times the increment.</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> OnMouseWheel<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>SizeToFit<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> increment<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>Control<span class="symbol">.</span>ModifierKeys <span class="symbol">==</span> Keys<span class="symbol">.</span>None<span class="symbol">)</span>
 increment <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomIncrement<span class="symbol">;</span>
 <span class="keyword">else</span>
 increment <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ZoomIncrement <span class="symbol">*</span> <span class="number">5</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Delta <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 increment <span class="symbol">=</span> <span class="symbol">-</span>increment<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">+=</span> increment<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Normally, whenever we override a method, we always call it's
base implementation. However, in this case we will not; the
<code>ScrollbableControl</code> that we inherit from uses the mouse wheel
to scroll the viewport and there doesn't seem to be a way to
disable this undesirable behaviour.</p>
<p>As we also want to allow the user to be able to click the
control with the left mouse button to zoom in, and either the
right mouse button or left button holding a modifier key to zoom
out, we'll also override <code>OnMouseClick</code>.</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> OnMouseClick<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>IsPanning <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>SizeToFit<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left <span class="symbol">&amp;&amp;</span> Control<span class="symbol">.</span>ModifierKeys <span class="symbol">==</span> Keys<span class="symbol">.</span>None<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">&gt;=</span> <span class="number">100</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>Math<span class="symbol">.</span>Round<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">+</span> <span class="number">100</span><span class="symbol">)</span> <span class="symbol">/</span> <span class="number">100</span><span class="symbol">)</span> <span class="symbol">*</span> <span class="number">100</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">&gt;=</span> <span class="number">75</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">=</span> <span class="number">100</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">/</span> <span class="number">0.75</span>F<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Right <span class="symbol">||</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left <span class="symbol">&amp;&amp;</span> Control<span class="symbol">.</span>ModifierKeys <span class="symbol">!=</span> Keys<span class="symbol">.</span>None<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">&gt;</span> <span class="number">100</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">&lt;=</span> <span class="number">125</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">=</span> <span class="number">100</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">&gt;</span> <span class="number">100</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>Math<span class="symbol">.</span>Round<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">double</span><span class="symbol">)</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">-</span> <span class="number">100</span><span class="symbol">)</span> <span class="symbol">/</span> <span class="number">100</span><span class="symbol">)</span> <span class="symbol">*</span> <span class="number">100</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 <span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Zoom <span class="symbol">*</span> <span class="number">0.75</span>F<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>OnMouseClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Unlike with the mouse wheel and it's fixed increment, we want to
use a different approach with clicking. If zooming out and the
percentage is more than 100, then the zoom level will be set to
the current zoom level + 100, but rounded to the nearest 100,
and the same in reverse for zooming in.</p>
<p>If the current zoom is less than 100, then the new value will +-
75% of the current zoom, or reset to 100 if the new value falls
between 75 and 125.</p>
<p>This results in a nicer zoom experience then just using a fixed
value.</p>
<h2 id="sample-project">Sample Project</h2>
<p>You can download the final sample project from the link below.</p>
<h2 id="whats-next">What's next?</h2>
<p>One of the really annoying issues with this control that has
plagued me during writing this series is scrolling the
component. During scrolling there is an annoying flicker as the
original contents are moved, then the new contents are drawn. At
present I don't have a solution for this, I've tried overriding
various <code>WM_*</code> messages but without success. A future update to
this component will either fix this issue, or do it's own
scrollbar support without inheriting from <code>ScrollableControl</code>,
although I'd like to avoid this latter solution.</p>
<p>If anyone knows of a solution please let us know!</p>
<p>Another enhancement would be intelligent use of the
interpolation mode. Currently the control uses a fixed value,
but some values are better when zoomed in, and some better when
zoomed out. The ability for the control to automatically select
the most appropriate mode would be useful.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2010-08-28 - 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-scrollable-and-zoomable-image-viewer-in-csharp-part-4 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a scrollable and zoomable image viewer in C# Part 3urn:uuid:4e72fcb8-5729-4038-a693-087d0e9e8f592012-11-25T09:21:03Z2010-08-23T18:08:01Z<p>After part 2 added scrolling support, we are now going to extend
this to support keyboard scrolling and panning with the mouse.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-3.png" class="gallery" title="The ImageBox component, demonstrated in a sample application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-3.png" alt="The ImageBox component, demonstrated in a sample application" decoding="async" loading="lazy" /></a><figcaption>The ImageBox component, demonstrated in a sample application</figcaption></figure><h2 id="design-support">Design support</h2>
<p>In order to enable panning, we're going to add three new
properties. The <code>AutoPan</code> property will control if the user can
click and drag the image with the mouse in order to scroll.
Also, we'll add an <code>InvertMouse</code> property to control how the
scrolling works. Finally the <code>IsPanning</code> property; however it
can only be read publicly, not set.</p>
<p>As well as the backing events for the above properties, we'll
also add extra events - <code>PanStart</code> and <code>PanEnd</code> The normal
<code>Scroll</code> event will be utilized while panning is in progress
rather than a custom event.</p>
<h2 id="mouse-panning">Mouse Panning</h2>
<p>To pan with the mouse, the user needs to &quot;grab&quot; the control by
clicking and holding down the left mouse button. As they move
the mouse, the control should automatically scroll in the
opposite direction the mouse is moving (or if <code>InvertMouse</code> is
set, in the same direction). Once the button is released,
scrolling should stop.</p>
<p>We'll implement this by overriding <code>OnMouseMove</code> and
<code>OnMouseUp</code>, shown below.</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> OnMouseMove<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseMove<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>e<span class="symbol">.</span>Button <span class="symbol">==</span> MouseButtons<span class="symbol">.</span>Left <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>AutoPan <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>IsPanning<span class="symbol">)</span>
 <span class="symbol">{</span>
 _startMousePosition <span class="symbol">=</span> e<span class="symbol">.</span>Location<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsPanning <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsPanning<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>
 Point position<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">this</span><span class="symbol">.</span>InvertMouse<span class="symbol">)</span>
 <span class="symbol">{</span>
 x <span class="symbol">=</span> <span class="symbol">-</span>_startScrollPosition<span class="symbol">.</span>X <span class="symbol">+</span> <span class="symbol">(</span>_startMousePosition<span class="symbol">.</span>X <span class="symbol">-</span> e<span class="symbol">.</span>Location<span class="symbol">.</span>X<span class="symbol">)</span><span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="symbol">-</span>_startScrollPosition<span class="symbol">.</span>Y <span class="symbol">+</span> <span class="symbol">(</span>_startMousePosition<span class="symbol">.</span>Y <span class="symbol">-</span> e<span class="symbol">.</span>Location<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 x <span class="symbol">=</span> <span class="symbol">-</span><span class="symbol">(</span>_startScrollPosition<span class="symbol">.</span>X <span class="symbol">+</span> <span class="symbol">(</span>_startMousePosition<span class="symbol">.</span>X <span class="symbol">-</span> e<span class="symbol">.</span>Location<span class="symbol">.</span>X<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 y <span class="symbol">=</span> <span class="symbol">-</span><span class="symbol">(</span>_startScrollPosition<span class="symbol">.</span>Y <span class="symbol">+</span> <span class="symbol">(</span>_startMousePosition<span class="symbol">.</span>Y <span class="symbol">-</span> e<span class="symbol">.</span>Location<span class="symbol">.</span>Y<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 position <span class="symbol">=</span> <span class="keyword">new</span> Point<span class="symbol">(</span>x<span class="symbol">,</span> y<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>UpdateScrollPosition<span class="symbol">(</span>position<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseUp<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseUp<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>IsPanning<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>IsPanning <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> UpdateScrollPosition<span class="symbol">(</span>Point position<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition <span class="symbol">=</span> position<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="keyword">this</span><span class="symbol">.</span>OnScroll<span class="symbol">(</span><span class="keyword">new</span> ScrollEventArgs<span class="symbol">(</span>ScrollEventType<span class="symbol">.</span>ThumbPosition<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>
</pre>
</figure>
<p><code>UpdateScrollPosition</code> is a common method to set the viewport
and refresh the control. The <code>IsPanning</code> property is used to
notify the control internally that a pan operation has been
started. It will also set a semi-appropriate cursor (we'll look
at custom cursors another time), and raise either the <code>PanStart</code>
or <code>PanEnd</code> events.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DefaultValue<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">,</span> Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">bool</span> IsPanning
<span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _isPanning<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">protected</span> <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_isPanning <span class="symbol">!=</span> value<span class="symbol">)</span>
 <span class="symbol">{</span>
 _isPanning <span class="symbol">=</span> value<span class="symbol">;</span>
 _startScrollPosition <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>value<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>SizeAll<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnPanStart<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Cursor <span class="symbol">=</span> Cursors<span class="symbol">.</span>Default<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>OnPanEnd<span class="symbol">(</span>EventArgs<span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="keyboard-scrolling">Keyboard Scrolling</h2>
<p>The first two versions of this component effectively disabled
keyboard support via the <code>ControlStyles.Selectable</code> control
style and <code>TabStop</code> property. However, we now want to allow
keyboard support. So the first thing we do is remove the call to
disable the selectable style and resetting of the tab stop
property from the constructor. We also remove the custom
<code>TabStop</code> property we had implemented for attribute overriding.</p>
<p>With this done, we can now add some keyboard support. As the
<code>ScrollableControl</code> doesn't natively support this, we'll do it
ourselves by overriding <code>OnKeyDown</code>. One of the initial
drawbacks is that it won't always capture special keys, such as
the arrow keys.</p>
<p>In order for it to do so we need to let the control know that
such keys are required by overriding <code>IsInputKey</code> - if this
returns <code>true</code>, then the specified key is required and will be
captured in <code>OnKeyDown</code>.</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">bool</span> IsInputKey<span class="symbol">(</span>Keys keyData<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">(</span>keyData <span class="symbol">&amp;</span> Keys<span class="symbol">.</span>Right<span class="symbol">)</span> <span class="symbol">==</span> Keys<span class="symbol">.</span>Right <span class="symbol">|</span> <span class="symbol">(</span>keyData <span class="symbol">&amp;</span> Keys<span class="symbol">.</span>Left<span class="symbol">)</span> <span class="symbol">==</span> Keys<span class="symbol">.</span>Left <span class="symbol">|</span> <span class="symbol">(</span>keyData <span class="symbol">&amp;</span> Keys<span class="symbol">.</span>Up<span class="symbol">)</span> <span class="symbol">==</span> Keys<span class="symbol">.</span>Up <span class="symbol">|</span> <span class="symbol">(</span>keyData <span class="symbol">&amp;</span> Keys<span class="symbol">.</span>Down<span class="symbol">)</span> <span class="symbol">==</span> Keys<span class="symbol">.</span>Down<span class="symbol">)</span>
 result <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 result <span class="symbol">=</span> <span class="keyword">base</span><span class="symbol">.</span>IsInputKey<span class="symbol">(</span>keyData<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnKeyDown<span class="symbol">(</span>KeyEventArgs e<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnKeyDown<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">switch</span> <span class="symbol">(</span>e<span class="symbol">.</span>KeyCode<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> Keys<span class="symbol">.</span>Left<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>AdjustScroll<span class="symbol">(</span><span class="symbol">-</span><span class="symbol">(</span>e<span class="symbol">.</span>Modifiers <span class="symbol">==</span> Keys<span class="symbol">.</span>None <span class="symbol">?</span> <span class="keyword">this</span><span class="symbol">.</span>HorizontalScroll<span class="symbol">.</span>SmallChange <span class="symbol">:</span> <span class="keyword">this</span><span class="symbol">.</span>HorizontalScroll<span class="symbol">.</span>LargeChange<span class="symbol">)</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> Keys<span class="symbol">.</span>Right<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>AdjustScroll<span class="symbol">(</span>e<span class="symbol">.</span>Modifiers <span class="symbol">==</span> Keys<span class="symbol">.</span>None <span class="symbol">?</span> <span class="keyword">this</span><span class="symbol">.</span>HorizontalScroll<span class="symbol">.</span>SmallChange <span class="symbol">:</span> <span class="keyword">this</span><span class="symbol">.</span>HorizontalScroll<span class="symbol">.</span>LargeChange<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> Keys<span class="symbol">.</span>Up<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>AdjustScroll<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> <span class="symbol">-</span><span class="symbol">(</span>e<span class="symbol">.</span>Modifiers <span class="symbol">==</span> Keys<span class="symbol">.</span>None <span class="symbol">?</span> <span class="keyword">this</span><span class="symbol">.</span>VerticalScroll<span class="symbol">.</span>SmallChange <span class="symbol">:</span> <span class="keyword">this</span><span class="symbol">.</span>VerticalScroll<span class="symbol">.</span>LargeChange<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> Keys<span class="symbol">.</span>Down<span class="symbol">:</span>
 <span class="keyword">this</span><span class="symbol">.</span>AdjustScroll<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> e<span class="symbol">.</span>Modifiers <span class="symbol">==</span> Keys<span class="symbol">.</span>None <span class="symbol">?</span> <span class="keyword">this</span><span class="symbol">.</span>VerticalScroll<span class="symbol">.</span>SmallChange <span class="symbol">:</span> <span class="keyword">this</span><span class="symbol">.</span>VerticalScroll<span class="symbol">.</span>LargeChange<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> AdjustScroll<span class="symbol">(</span><span class="keyword">int</span> x<span class="symbol">,</span> <span class="keyword">int</span> y<span class="symbol">)</span>
<span class="symbol">{</span>
 Point scrollPosition<span class="symbol">;</span>

 scrollPosition <span class="symbol">=</span> <span class="keyword">new</span> Point<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>HorizontalScroll<span class="symbol">.</span>Value <span class="symbol">+</span> x<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>VerticalScroll<span class="symbol">.</span>Value <span class="symbol">+</span> y<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>UpdateScrollPosition<span class="symbol">(</span>scrollPosition<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>When the left, right, up or down arrow keys are pressed, the
control checks to see if a modifier such as shift or control is
active. If not, then the control is scrolled either horizontally
or vertically using the &quot;small change&quot; value of the appropriate
scrollbar. If a modifier was set, then the scroll is made using
the &quot;large change&quot; value.</p>
<p>The <code>AdjustScroll</code> method is used to &quot;nudge&quot; the scrollbars in
the given direction, using values read from the
<code>HorizontalScroll</code> and <code>VerticalScroll</code> - reading the
<code>AutoScrollPosition</code> property didn't return appropriate results
in our testing.</p>
<h4 id="sample-project">Sample Project</h4>
<p>You can download the third sample project from the links below.
The final article in the series will add autofit, centering and
of course, zoom support.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2010-08-23 - 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-scrollable-and-zoomable-image-viewer-in-csharp-part-3 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a scrollable and zoomable image viewer in C# Part 2urn:uuid:16fc4d3c-b9d4-469f-88b2-3b920b63e7e92012-11-25T09:20:51Z2010-08-13T20:22:52Z<p>In the second part of our <a href="/post/creating-a-scrollable-and-zoomable-image-viewer-in-csharp-part-1">Creating a scrollable and zoomable
image viewer in C#</a> series we will update our component to
support automatic scrolling when auto size is disabled and the
image is larger than the client area of the control.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox-2.png" class="gallery" title="The ImageBox component, demonstrated in a sample application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox-2.png" alt="The ImageBox component, demonstrated in a sample application" decoding="async" loading="lazy" /></a><figcaption>The ImageBox component, demonstrated in a sample application</figcaption></figure><h2 id="setting-up-auto-scrolling">Setting up auto scrolling</h2>
<p>Originally we inherited from <code>Control</code>, however this does not
support automatic scrolling. Rather than reinventing the wheel
at this point, we'll change the control to inherit from
<code>ScrollableControl</code> instead. This will expose a number of new
members, the ones we need are:</p>
<ul>
<li><code>AutoScroll</code> - Enables or disables automatic scrolling</li>
<li><code>AutoScrollMinSize</code> - Specifies the minimum size before
scrollbars appear</li>
<li><code>AutoScrollPosition</code> - Specifies the current scroll position</li>
<li><code>OnScroll</code> - Raised when the scroll position is changed</li>
</ul>
<p>Using the above we can now offer full scrolling.</p>
<p>As the control will take care of the scrolling behaviour, we
don't want the <code>AutoScrollMinSize</code> property to be available, so
we'll declare a new version of it and hide it with attributes.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Never<span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">new</span> Size AutoScrollMainSize
<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>AutoScrollMinSize<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>AutoScrollMinSize <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Initially the component only offered auto sizing and so we had
defined an <code>AdjustSize</code> method which was called in response to
various events and property changes. As we now need to set up
the scrolling area if <code>AutoScroll</code> is enabled, this method is no
longer as suitable. Instead, we add a pair of new methods,
<code>AdjustLayout</code> and <code>AdjustScrolling</code>. Existing calls to
<code>AdjustSize</code> are changed to call <code>AdjustLayout</code> instead, and
this method now calls either <code>AdjustScrolling</code> or <code>AdjustSize</code>
depending on the state of the <code>AutoSize</code> and <code>AutoScroll</code>
properties.</p>
<p>The <code>AdjustScrolling</code> method is used to set the
<code>AutoScrollMainSize</code> property. When this is correctly set, the
<code>ScrollableControl</code> will automatically take care of displaying
scrollbars.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> AdjustLayout<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AutoSize<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>AdjustSize<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AutoScroll<span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>AdjustScrolling<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> AdjustScrolling<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AutoScroll <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>AutoScrollMinSize <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">.</span>Size<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="reacting-to-scroll-changes">Reacting to scroll changes</h2>
<p>By overriding the <code>OnScroll</code> event we get notifications whenever
the user scrolls the control, and can therefore redraw the
image.</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> OnScroll<span class="symbol">(</span>ScrollEventArgs se<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="keyword">base</span><span class="symbol">.</span>OnScroll<span class="symbol">(</span>se<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="painting-adjustments">Painting adjustments</h2>
<p>The initial version of our <code>ImageBox</code> tiled a bitmap across the
client area of the control. In this new version, when we create
the background tile, we now create a new <code>TextureBrush</code>. During
drawing we can call <code>FillRectangle</code> and pass in the new brush
and it will be tiled for us.</p>
<p>Another shortcoming of the first version was the borders. These
were painted last, so that if the image was larger than the
controls client area, the image wouldn't be painted on top of
the borders. Now, the borders are drawn first and a clip region
applied to prevent any overlap.</p>
<p>Finally of course, the position of the drawn image needs to
reflect any scrollbar offset.</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>
 <span class="keyword">int</span> borderOffset<span class="symbol">;</span>
 Rectangle innerRectangle<span class="symbol">;</span>

 borderOffset <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetBorderOffset<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>borderOffset <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// draw the borders</span>
 <span class="keyword">switch</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>BorderStyle<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> BorderStyle<span class="symbol">.</span>FixedSingle<span class="symbol">:</span>
 ControlPaint<span class="symbol">.</span>DrawBorder<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ForeColor<span class="symbol">,</span> ButtonBorderStyle<span class="symbol">.</span>Solid<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> BorderStyle<span class="symbol">.</span>Fixed<span class="number">3</span>D<span class="symbol">:</span>
 ControlPaint<span class="symbol">.</span>DrawBorder<span class="number">3</span>D<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> Border<span class="number">3</span>DStyle<span class="symbol">.</span>Sunken<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// clip the background so we don&#39;t overwrite the border</span>
 innerRectangle <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Inflate<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> <span class="symbol">-</span>borderOffset<span class="symbol">,</span> <span class="symbol">-</span>borderOffset<span class="symbol">)</span><span class="symbol">;</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>SetClip<span class="symbol">(</span>innerRectangle<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 innerRectangle <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">;</span>

 <span class="comment">// draw the background</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_texture <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>ShowGrid<span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>_texture<span class="symbol">,</span> innerRectangle<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>SolidBrush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>BackColor<span class="symbol">)</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> innerRectangle<span class="symbol">)</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="keyword">this</span><span class="symbol">.</span>Image <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> left<span class="symbol">;</span>
 <span class="keyword">int</span> top<span class="symbol">;</span>

 left <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Left <span class="symbol">+</span> borderOffset<span class="symbol">;</span>
 top <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Top <span class="symbol">+</span> borderOffset<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>AutoScroll<span class="symbol">)</span>
 <span class="symbol">{</span>
 left <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>X<span class="symbol">;</span>
 top <span class="symbol">+=</span> <span class="keyword">this</span><span class="symbol">.</span>AutoScrollPosition<span class="symbol">.</span>Y<span class="symbol">;</span>
 <span class="symbol">}</span>

 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawImageUnscaled<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">,</span> <span class="keyword">new</span> Point<span class="symbol">(</span>left<span class="symbol">,</span> top<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// reset the clipping</span>
 <span class="keyword">if</span> <span class="symbol">(</span>borderOffset <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>ResetClip<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="sample-project">Sample Project</h2>
<p>You can download the second sample project from the link below.
The next article in the series will look at panning the image
using the mouse within the client area of the image control.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2010-08-13 - 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-scrollable-and-zoomable-image-viewer-in-csharp-part-2 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a scrollable and zoomable image viewer in C# Part 1urn:uuid:02fd4283-ddf1-4672-9a64-39b46b02c3f72012-11-25T09:20:38Z2010-08-12T20:14:30Z<p>This is the first part in a series of articles that will result
in a component for viewing an image. The final component will
support zooming and scrolling.</p>
<p>In this first part, we're going to create a basic image viewer,
without the scrolling and zooming. Rather than having a plain
background however, we're going to create a two tone checker box
effect which is often used for showing transparent images. We'll
also allow this to be disabled and a solid colour used instead.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/imgbox.png" class="gallery" title="The sample project in action." ><img src="https://images.cyotek.com/image/thumbnail/devblog/imgbox.png" alt="The sample project in action." decoding="async" loading="lazy" /></a><figcaption>The sample project in action.</figcaption></figure><h2 id="creating-the-component">Creating the component</h2>
<p>The component inherits from <code>Control</code> rather than something
like <code>PictureBox</code> or <code>Panel</code> as we want to provide a lot of
our own behaviour.</p>
<p>The first thing we'll do is override some properties - to hide
the ones we won't be using such as <code>Text</code> and <code>Font</code>, and to
modify others, such as making <code>AutoSize</code> visible, and changing
the default value of <code>BackColor</code>.</p>
<p>Next is to add some new properties. We'll create the following
properties and respective change events:</p>
<ul>
<li><code>BorderStyle</code> - A standard border style.</li>
<li><code>GridCellSize</code> - The basic cell size.</li>
<li><code>GridColor</code> and <code>GridColorAlternate</code> - The colors used to
create the checkerboard style background.</li>
<li><code>GridScale</code> - A property for scaling the <code>GridCellSize</code> for
user interface options.</li>
<li><code>Image</code> - The image to be displayed.</li>
<li><code>ShowGrid</code> - Flag to determine if the checkerboard background
should be displayed.</li>
</ul>
<p>As we are offering auto size support, we also override some
existing events so we can resize when certain actions occur,
such as changing the control's padding or parent.</p>
<h2 id="setting-control-styles">Setting control styles</h2>
<p>As well as setting up default property values, the component's
constructor also adjusts several control styles.</p>
<ul>
<li><code>AllPaintingInWmPaint</code> - We don't need a separate
<code>OnPaintBackground</code> and <code>OnPaint</code> mechanism, <code>OnPaint</code> will do
fine.</li>
<li><code>UserPaint</code> - As we are doing entirely our own painting, we
disable the base <strong>Control</strong>'s painting.</li>
<li><code>OptimizedDoubleBuffer</code> - Double buffering means the painting
will occur in a memory buffer before being transferred to the
screen, reducing flicker.</li>
<li><code>ResizeRedraw</code> - Automatically redraw the component if it is
resized.</li>
<li><code>Selectable</code> - We disable this flag as we don't want the
control to be receiving focus.</li>
</ul>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> ImageBox<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 InitializeComponent<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetStyle<span class="symbol">(</span>ControlStyles<span class="symbol">.</span>AllPaintingInWmPaint <span class="symbol">|</span> ControlStyles<span class="symbol">.</span>UserPaint <span class="symbol">|</span> ControlStyles<span class="symbol">.</span>OptimizedDoubleBuffer<span class="symbol">|</span> ControlStyles<span class="symbol">.</span>ResizeRedraw<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>SetStyle<span class="symbol">(</span>ControlStyles<span class="symbol">.</span>Selectable<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>UpdateStyles<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>BackColor <span class="symbol">=</span> Color<span class="symbol">.</span>White<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>TabStop <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>AutoSize <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>GridScale <span class="symbol">=</span> ImageBoxGridScale<span class="symbol">.</span>Small<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>ShowGrid <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>GridColor <span class="symbol">=</span> Color<span class="symbol">.</span>Gainsboro<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>GridColorAlternate <span class="symbol">=</span> Color<span class="symbol">.</span>White<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>GridCellSize <span class="symbol">=</span> <span class="number">8</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>BorderStyle <span class="symbol">=</span> BorderStyle<span class="symbol">.</span>FixedSingle<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="creating-the-background">Creating the background</h2>
<p>The <code>CreateGridTileImage</code> method creates a tile of a 2x2 grid
using many of the properties listed above which is then tiled
across the background of the control.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">virtual</span> Bitmap CreateGridTileImage<span class="symbol">(</span><span class="keyword">int</span> cellSize<span class="symbol">,</span> Color firstColor<span class="symbol">,</span> Color secondColor<span class="symbol">)</span>
<span class="symbol">{</span>
 Bitmap result<span class="symbol">;</span>
 <span class="keyword">int</span> width<span class="symbol">;</span>
 <span class="keyword">int</span> height<span class="symbol">;</span>
 <span class="keyword">float</span> scale<span class="symbol">;</span>

 <span class="comment">// rescale the cell size</span>
 <span class="keyword">switch</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>GridScale<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> ImageBoxGridScale<span class="symbol">.</span>Medium<span class="symbol">:</span>
 scale <span class="symbol">=</span> <span class="number">1.5</span>F<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> ImageBoxGridScale<span class="symbol">.</span>Large<span class="symbol">:</span>
 scale <span class="symbol">=</span> <span class="number">2</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">default</span><span class="symbol">:</span>
 scale <span class="symbol">=</span> <span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 cellSize <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span>cellSize <span class="symbol">*</span> scale<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// draw the tile</span>
 width <span class="symbol">=</span> cellSize <span class="symbol">*</span> <span class="number">2</span><span class="symbol">;</span>
 height <span class="symbol">=</span> cellSize <span class="symbol">*</span> <span class="number">2</span><span class="symbol">;</span>
 result <span class="symbol">=</span> <span class="keyword">new</span> Bitmap<span class="symbol">(</span>width<span class="symbol">,</span> height<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Graphics g <span class="symbol">=</span> Graphics<span class="symbol">.</span>FromImage<span class="symbol">(</span>result<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>SolidBrush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span>firstColor<span class="symbol">)</span><span class="symbol">)</span>
 g<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> <span class="keyword">new</span> Rectangle<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> height<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>SolidBrush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span>secondColor<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 g<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> cellSize<span class="symbol">,</span> cellSize<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 g<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> <span class="keyword">new</span> Rectangle<span class="symbol">(</span>cellSize<span class="symbol">,</span> cellSize<span class="symbol">,</span> cellSize<span class="symbol">,</span> cellSize<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="painting-the-control">Painting the control</h2>
<p>As described above, we've disabled all default painting, so we
simply need to override <code>OnPaint</code> and do our custom painting
here.</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>
 <span class="keyword">if</span> <span class="symbol">(</span>_gridTile <span class="symbol">!=</span> <span class="keyword">null</span> <span class="symbol">&amp;&amp;</span> <span class="keyword">this</span><span class="symbol">.</span>ShowGrid<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// draw the background</span>
 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> x <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> x <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>ClientSize<span class="symbol">.</span>Width<span class="symbol">;</span> x <span class="symbol">+=</span> _gridTile<span class="symbol">.</span>Size<span class="symbol">.</span>Width<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> y <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> y <span class="symbol">&lt;</span> <span class="keyword">this</span><span class="symbol">.</span>ClientSize<span class="symbol">.</span>Height<span class="symbol">;</span> y <span class="symbol">+=</span> _gridTile<span class="symbol">.</span>Size<span class="symbol">.</span>Height<span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>DrawImageUnscaled<span class="symbol">(</span>_gridTile<span class="symbol">,</span> x<span class="symbol">,</span> y<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>SolidBrush brush <span class="symbol">=</span> <span class="keyword">new</span> SolidBrush<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>BackColor<span class="symbol">)</span><span class="symbol">)</span>
 e<span class="symbol">.</span>Graphics<span class="symbol">.</span>FillRectangle<span class="symbol">(</span>brush<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">)</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="keyword">this</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>DrawImageUnscaled<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Image<span class="symbol">,</span> <span class="keyword">new</span> Point<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Padding<span class="symbol">.</span>Left <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>GetBorderOffset<span class="symbol">(</span><span class="symbol">)</span><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="keyword">this</span><span class="symbol">.</span>GetBorderOffset<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// draw the borders</span>
 <span class="keyword">switch</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>BorderStyle<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> BorderStyle<span class="symbol">.</span>FixedSingle<span class="symbol">:</span>
 ControlPaint<span class="symbol">.</span>DrawBorder<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ForeColor<span class="symbol">,</span> ButtonBorderStyle<span class="symbol">.</span>Solid<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> BorderStyle<span class="symbol">.</span>Fixed<span class="number">3</span>D<span class="symbol">:</span>
 ControlPaint<span class="symbol">.</span>DrawBorder<span class="number">3</span>D<span class="symbol">(</span>e<span class="symbol">.</span>Graphics<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>ClientRectangle<span class="symbol">,</span> Border<span class="number">3</span>DStyle<span class="symbol">.</span>Sunken<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>First, we either draw a solid background using the <code>BackColor</code>
property if <code>ShowGrid</code> is <code>false</code>, otherwise we tile the grid
image created earlier.</p>
<p>Next we draw the actual image, if one has been set. The image is
offset based on the border style and padding.</p>
<p>Finally we draw the border style to ensure it appears on top of
the image if autosize is disabled and the control is too small.</p>
<h4 id="sample-project">Sample Project</h4>
<p>You can download the first sample project from the links below.
The next article in the series will look at implementing
scrolling for when the image is larger than the display area of
the control.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2010-08-12 - 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-scrollable-and-zoomable-image-viewer-in-csharp-part-1 .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comAdding a horizontal scrollbar to a ComboBox using C#urn:uuid:15bcc036-d651-472f-8556-48a77261dae62010-07-13T17:29:48Z2010-07-13T17:29:48Z<p>In our <a href="https://cyotek.com/cyotek-webcopy">WebCopy</a> application we decided to
update the User Agent configuration to allow selection from a
predefined list of common agents, but still allow the user to
enter their own custom agent if required.</p>
<p>Rather than use two separate fields, we choose to use a
<code>ComboBox</code> in <strong>simple</strong> mode, which is both a <code>TextBox</code> and a
<code>ListBox</code> in a single control. This mode seems somewhat out of
fashion, I think the only place I see it used is in the Font
common dialog, virtually unchanged since Windows 3.1.</p>
<p>The problem was immediately apparent however on firing up
WebCopy and going to select a user agent - the agent strings can
be very long, far longer than the width of the control.</p>
<p>Unfortunately however, the .NET ComboBox doesn't allow you to
directly enable horizontal scrolling. So we'll do it the old
fashioned way using the Windows API.</p>
<p>In order for a window to support horizontal scrolling, it needs
to have the <code>WS_HSCROLL</code> style applied to it. And to setup the
horizontal scrollbar, we need to call the <code>SendMessage</code> API with
the <code>CB_SETHORIZONTALEXTENT</code> message.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/cbhscroll.png" class="gallery" title="A sample project showing a horizontal scrollbar attached to each display type of a ComboBox." ><img src="https://images.cyotek.com/image/thumbnail/devblog/cbhscroll.png" alt="A sample project showing a horizontal scrollbar attached to each display type of a ComboBox." decoding="async" loading="lazy" /></a><figcaption>A sample project showing a horizontal scrollbar attached to each display type of a ComboBox.</figcaption></figure>
<p>As usual, we'll be starting off by creating a new <em>Component</em>,
which we'll inherit from <code>ComboBox</code>.</p>
<p>Traditionally, you would call <code>GetWindowLong</code> and
<code>SetWindowLong</code> API's with the <code>GWL_STYLE</code> or <code>GWL_EXSTYLE</code>
flags. However, we can more simply override the <code>CreateParams</code>
property of our component and set the new style when the control
is created.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> <span class="keyword">override</span> CreateParams CreateParams
<span class="symbol">{</span>
 <span class="keyword">get</span>
 <span class="symbol">{</span>
 CreateParams createParams<span class="symbol">;</span>

 createParams <span class="symbol">=</span> <span class="keyword">base</span><span class="symbol">.</span>CreateParams<span class="symbol">;</span>
 createParams<span class="symbol">.</span>Style <span class="symbol">|=</span> WS_HSCROLL<span class="symbol">;</span>

 <span class="keyword">return</span> createParams<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>With that done, we can now inform Windows of the size of the
horizontal scroll area, and it will automatically add the
scrollbar if required. To do this, I'll add two new methods to
the component. The first will set the horizontal extent to a
given value. The second will calculate the length of the longest
piece of text in the control and then set the extent to match.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">void</span> SetHorizontalExtent<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> maxWith<span class="symbol">;</span>

 maxWith <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span><span class="keyword">object</span> item <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">.</span>Items<span class="symbol">)</span>
 <span class="symbol">{</span>
 Size textSize<span class="symbol">;</span>

 textSize <span class="symbol">=</span> TextRenderer<span class="symbol">.</span>MeasureText<span class="symbol">(</span>item<span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Font<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>textSize<span class="symbol">.</span>Width <span class="symbol">&gt;</span> maxWith<span class="symbol">)</span>
 maxWith <span class="symbol">=</span> textSize<span class="symbol">.</span>Width<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetHorizontalExtent<span class="symbol">(</span>maxWith<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">void</span> SetHorizontalExtent<span class="symbol">(</span><span class="keyword">int</span> width<span class="symbol">)</span>
<span class="symbol">{</span>
 SendMessage<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Handle<span class="symbol">,</span> CB_SETHORIZONTALEXTENT<span class="symbol">,</span> <span class="keyword">new</span> IntPtr<span class="symbol">(</span>width<span class="symbol">)</span><span class="symbol">,</span> IntPtr<span class="symbol">.</span>Zero<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The first overload of <code>SetHorizontalExtent</code> iterates through all
the items in the control and uses the <code>TextRenderer</code> object to
measure the size of the text. Once it has found the largest
piece of text, it calls the second overload with the size.</p>
<p>The second overload does the actual work of notifying Windows
using the <code>SendMessage</code> call, <code>CB_SETHORIZONTALEXTENT</code> message
and the given width. <code>SendMessage</code> takes two configuration
parameters per message, but <code>CB_SETHORIZONTALEXTENT</code> only
requires one, and so we send <code>0</code> for the second.</p>
<p>The above function works with all display modes of the ComboBox.</p>
<p>For completeness, here are the API declarations we are using:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> WS_HSCROLL <span class="symbol">=</span> <span class="number">0x100000</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> CB_SETHORIZONTALEXTENT <span class="symbol">=</span> <span class="number">0x015E</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">extern</span> IntPtr SendMessage<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> UInt<span class="number">32</span> msg<span class="symbol">,</span> IntPtr wParam<span class="symbol">,</span> IntPtr lParam<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>As usual, a demonstration project is available from the link
below.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2010-07-13 - 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/adding-a-horizontal-scrollbar-to-a-combobox-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a Windows Forms RadioButton that supports the double click eventurn:uuid:a65a8ab3-ac96-4875-8bb3-b0c4a80ed8232010-06-25T19:32:07Z2010-06-25T18:56:38Z<p>Another of the peculiarities of Windows Forms is that the
<code>RadioButton</code> control doesn't support double clicking. Granted,
it is not often you require the functionality but it's a little
odd it's not supported.</p>
<p>As an example, one of our earlier products which never made it
to production uses a popup dialog to select a zoom level for a
<code>RichTexBox</code>. Common zoom levels are provided via a list of
radio buttons. Rather than the user having to first click a zoom
level and then click the <strong>OK</strong> button, we wanted the user to be
able to simply double click an option to have it selected and
the dialog close.</p>
<p>However, once again with a simple bit of overriding magic we can
enable this functionality.</p>
<p>Create a new component and paste in the code below (<code>using</code> and
<code>namespace</code> statements omitted for clarity).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">partial</span> <span class="keyword">class</span> RadioButton <span class="symbol">:</span> System<span class="symbol">.</span>Windows<span class="symbol">.</span>Forms<span class="symbol">.</span>RadioButton
<span class="symbol">{</span>
 <span class="keyword">public</span> RadioButton<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 InitializeComponent<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>SetStyle<span class="symbol">(</span>ControlStyles<span class="symbol">.</span>StandardClick <span class="symbol">|</span> ControlStyles<span class="symbol">.</span>StandardDoubleClick<span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Always<span class="symbol">)</span><span class="symbol">,</span> Browsable<span class="symbol">(</span><span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">new</span> <span class="keyword">event</span> MouseEventHandler MouseDoubleClick<span class="symbol">;</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnMouseDoubleClick<span class="symbol">(</span>MouseEventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnMouseDoubleClick<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// raise the event</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>MouseDoubleClick <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">this</span><span class="symbol">.</span>MouseDoubleClick<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> e<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>This new component inherits from the standard <code>RadioButton</code>
control and unlocks the functionality we need.</p>
<p>The first thing we do in the constructor is to modify the
components control styles to enable the <code>StandardDoubleClick</code>
style. At the same time we also set the <code>StandardClick</code> style as
the MSDN documentation states that <code>StandardDoubleClick</code> will be
ignored if <code>StandardClick</code> is not set.</p>
<p>As you can't override an event, we declare a new version of the
<code>MouseDoubleClick</code> event using the <code>new</code> keyword. To this new
definition we add the <code>EditorBrowsable</code> and <code>Browsable</code>
attributes so that the event appears in the IDE property
inspectors and intellisense.</p>
<p>Finally, we override the <code>OnMouseDoubleClick</code> method and invoke
the <code>MouseDoubleClick</code> event whenever this method is called.</p>
<p>And there we have it. Three short steps and we now have a radio
button that you can double click.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2010-06-25 - 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-windows-forms-radiobutton-that-supports-the-double-click-event .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating a Windows Forms Label that wraps with C#urn:uuid:8470ef98-31a6-4c60-81e2-1a43f55aa3622012-01-24T16:52:14Z2010-05-21T23:26:06Z<p>One of the few annoyances I occasionally get with C# is the lack
of a word wrap facility for the standard <code>Label</code> control.</p>
<p>Instead, if the <code>AutoSize</code> property is set to <code>true</code>, the label
will just get wider and wider. In order to wrap it, you have to
disable auto resize then manually ensure the height of the label
is sufficient.</p>
<p>The base <code>Control</code> class has method named <code>GetPreferredSize</code>
which is overridden by derived classes. This method will
calculate the size of a control based on a suggested value. By
calling this method and overriding the <code>OnTextChanged</code> and
<code>OnResize</code> methods, we can very easily create a custom label
that automatically wraps and resizes itself vertically to fit
its contents.</p>
<p>Paste in the following code into a new <strong>Component</strong> to have a
read-to-run wrappable label.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> System<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>ComponentModel<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>Drawing<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>Windows<span class="symbol">.</span>Forms<span class="symbol">;</span>

<span class="keyword">namespace</span> Cyotek<span class="symbol">.</span>Windows<span class="symbol">.</span>Forms
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">partial</span> <span class="keyword">class</span> WrapLabel <span class="symbol">:</span> Label
 <span class="symbol">{</span>
 <span class="keyword">public</span> WrapLabel<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>AutoSize <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnResize<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnResize<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>FitToContents<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnTextChanged<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnTextChanged<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>FitToContents<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">protected</span> <span class="keyword">virtual</span> <span class="keyword">void</span> FitToContents<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Size size<span class="symbol">;</span>

 size <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetPreferredSize<span class="symbol">(</span><span class="keyword">new</span> Size<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Width<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>Height <span class="symbol">=</span> size<span class="symbol">.</span>Height<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="symbol">[</span>DefaultValue<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> Browsable<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">,</span> EditorBrowsable<span class="symbol">(</span>EditorBrowsableState<span class="symbol">.</span>Never<span class="symbol">)</span><span class="symbol">,</span> DesignerSerializationVisibility<span class="symbol">(</span>DesignerSerializationVisibility<span class="symbol">.</span>Hidden<span class="symbol">)</span><span class="symbol">]</span>
 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">bool</span> AutoSize
 <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>AutoSize<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>AutoSize <span class="symbol">=</span> value<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>So, what is the code doing? It's very straightforward.</p>
<p>In the constructor, we are disabling the built in auto resize
functionality, otherwise you won't be able to resize the control
in the designer.</p>
<p>Next, we want to override the <code>OnTextChanged</code> and <code>OnResize</code>
methods to call our new resize functionality. By overriding
these, we can ensure that the control will correctly resize as
required.</p>
<p>Now to implement the actual resize functionality. The
<code>FitToContents</code> method calls the label's <code>GetPreferredSize</code>
method, passing in the width of the control. This method returns
a <code>Size</code> structure which is large enough to hold the entire
contents of the control. We take the Height of this (but not the
width) and apply it to the label to make it resize vertically.</p>
<p>When calling <code>GetPreferredSize</code>, the size we passed in only
had the width specified, which will be the maximum width
returning. As we passed in zero for the height, the method
defines its own maximum height.</p>
<p>Finally, you'll note that we have overridden the <code>AutoSize</code>
property itself and added a number of attributes to it to make
sure it doesn't appear in any property or code windows, and to
prevent its value from being serialized.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2010-05-21 - 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-windows-forms-label-that-wraps-with-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comCreating 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