Cyotek Development Bloghttps://devblog.cyotek.com/tag/wm-erasebkgnd/atom.xml2018-04-29T21:41:23ZDisplaying 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.com