Creating a scrollable and zoomable image viewer in C# Part 3
After part 2 added scrolling support, we are now going to extend this to support keyboard scrolling and panning with the mouse.
Design support
In order to enable panning, we're going to add three new
properties. The AutoPan
property will control if the user can
click and drag the image with the mouse in order to scroll.
Also, we'll add an InvertMouse
property to control how the
scrolling works. Finally the IsPanning
property; however it
can only be read publicly, not set.
As well as the backing events for the above properties, we'll
also add extra events - PanStart
and PanEnd
The normal
Scroll
event will be utilized while panning is in progress
rather than a custom event.
Mouse Panning
To pan with the mouse, the user needs to "grab" 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 InvertMouse
is
set, in the same direction). Once the button is released,
scrolling should stop.
We'll implement this by overriding OnMouseMove
and
OnMouseUp
, shown below.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left && this.AutoPan && this.Image != null)
{
if (!this.IsPanning)
{
_startMousePosition = e.Location;
this.IsPanning = true;
}
if (this.IsPanning)
{
int x;
int y;
Point position;
if (!this.InvertMouse)
{
x = -_startScrollPosition.X + (_startMousePosition.X - e.Location.X);
y = -_startScrollPosition.Y + (_startMousePosition.Y - e.Location.Y);
}
else
{
x = -(_startScrollPosition.X + (_startMousePosition.X - e.Location.X));
y = -(_startScrollPosition.Y + (_startMousePosition.Y - e.Location.Y));
}
position = new Point(x, y);
this.UpdateScrollPosition(position);
}
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (this.IsPanning)
this.IsPanning = false;
}
protected virtual void UpdateScrollPosition(Point position)
{
this.AutoScrollPosition = position;
this.Invalidate();
this.OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, 0));
}
UpdateScrollPosition
is a common method to set the viewport
and refresh the control. The IsPanning
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 PanStart
or PanEnd
events.
[DefaultValue(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
public bool IsPanning
{
get { return _isPanning; }
protected set
{
if (_isPanning != value)
{
_isPanning = value;
_startScrollPosition = this.AutoScrollPosition;
if (value)
{
this.Cursor = Cursors.SizeAll;
this.OnPanStart(EventArgs.Empty);
}
else
{
this.Cursor = Cursors.Default;
this.OnPanEnd(EventArgs.Empty);
}
}
}
}
Keyboard Scrolling
The first two versions of this component effectively disabled
keyboard support via the ControlStyles.Selectable
control
style and TabStop
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
TabStop
property we had implemented for attribute overriding.
With this done, we can now add some keyboard support. As the
ScrollableControl
doesn't natively support this, we'll do it
ourselves by overriding OnKeyDown
. One of the initial
drawbacks is that it won't always capture special keys, such as
the arrow keys.
In order for it to do so we need to let the control know that
such keys are required by overriding IsInputKey
- if this
returns true
, then the specified key is required and will be
captured in OnKeyDown
.
protected override bool IsInputKey(Keys keyData)
{
bool result;
if ((keyData & Keys.Right) == Keys.Right | (keyData & Keys.Left) == Keys.Left | (keyData & Keys.Up) == Keys.Up | (keyData & Keys.Down) == Keys.Down)
result = true;
else
result = base.IsInputKey(keyData);
return result;
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
switch (e.KeyCode)
{
case Keys.Left:
this.AdjustScroll(-(e.Modifiers == Keys.None ? this.HorizontalScroll.SmallChange : this.HorizontalScroll.LargeChange), 0);
break;
case Keys.Right:
this.AdjustScroll(e.Modifiers == Keys.None ? this.HorizontalScroll.SmallChange : this.HorizontalScroll.LargeChange, 0);
break;
case Keys.Up:
this.AdjustScroll(0, -(e.Modifiers == Keys.None ? this.VerticalScroll.SmallChange : this.VerticalScroll.LargeChange));
break;
case Keys.Down:
this.AdjustScroll(0, e.Modifiers == Keys.None ? this.VerticalScroll.SmallChange : this.VerticalScroll.LargeChange);
break;
}
}
protected virtual void AdjustScroll(int x, int y)
{
Point scrollPosition;
scrollPosition = new Point(this.HorizontalScroll.Value + x, this.VerticalScroll.Value + y);
this.UpdateScrollPosition(scrollPosition);
}
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 "small change" value of the appropriate scrollbar. If a modifier was set, then the scroll is made using the "large change" value.
The AdjustScroll
method is used to "nudge" the scrollbars in
the given direction, using values read from the
HorizontalScroll
and VerticalScroll
- reading the
AutoScrollPosition
property didn't return appropriate results
in our testing.
Sample Project
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.
Update History
- 2010-08-23 - First published
- 2020-11-21 - Updated formatting
Related articles you may be interested in
- Displaying multi-page tiff files using the ImageBox control and C#
- Adding drag handles to an ImageBox to allow resizing of selection regions
- ImageBox 1.1.4.0 update
- ImageBox and TabList update's - virtual mode, pixel grid, bug fixes and more!
- ImageBox update, version 1.1.0.0
- Zooming to fit a region in a ScrollableControl
- Zooming into a fixed point on a ScrollableControl
- Arcade explosion generator
- Creating an image viewer in C# Part 5: Selecting part of an image
- Extending the ImageBox component to display the contents of a PDF file using C#
- Creating a scrollable and zoomable image viewer in C# Part 4
- Creating a scrollable and zoomable image viewer in C# Part 2
- Creating a scrollable and zoomable image viewer in C# Part 1
Downloads
Filename | Description | Version | Release Date | |
---|---|---|---|---|
imageboxsample-part3.zip
|
Sample project showing how to create an image viewer that supports scrolling with the mouse and keyboard, and panning with the mouse. |
23/08/2010 | Download |
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
gyurisc
#
This controll is supercool! Thanks for taking the time and writing the control and articles.
I have one question though. If I click on the control, how can I get the coordinates of the click on the original image?
Richard Moss
#
gyurisc,
Thanks for the comment. I was working on a small update to the control, but as I haven't got one of the bugs fixed yet I'll just post the function you need in this comment.
[csharp] public virtual Point PointToImage(Point point) { Rectangle viewport; int x; int y;
[/csharp]
Add this to your copy of ImageBox, then call it as you would the PointToClient and PointToScreen methods, for example:
[csharp] private void imageBox_MouseMove(object sender, MouseEventArgs e) { Point point;
[/csharp]
Note that this function always returns 1 more than the actual point, as Point.Empty returns true for 0,0 and I can't return null for a structure. A bit annoying...
Hope this helps!
Regards; Richard Moss