Cyotek Development Blog
https://devblog.cyotek.com/tag/drag/atom.xml
2014-02-13T20:12:19Z
Adding drag handles to an ImageBox to allow resizing of selection regions
urn:uuid:f4104c69-7c2f-4db2-9206-246115c2af48
2014-02-13T20:12:19Z
2014-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 Moss
https://www.cyotek.com/
richard.moss@cyotek.com