Hello,
I am attempting to develop a custom control that allows the user to
zoom to a specific point on an image, typically a map. The problem
that I am having is when I attempt to adjust the scrollbar position,
the update of the image is very jerky. For example, in the code below,
when the user selects a point on the image, the image is zoomed in on
the point and the scroll bar position is updated to the new origin of
the viewport. When the image is updated, it appears to be drawn at its
original position and scale, then the control's OnPaint method is
called and the image is drawn correctly. This gives the appearance of
the image moving left and up before snapping to the zoomed point. The
problem seems to be exacerbated when double buffering is enabled. Has
anyone else experienced this?
Here's the example code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing;
namespace Zoom
{
public class ZoomPoint : ScrollableControl
{
enum ZoomDirection
{
In,
Out
}
#region Private Data
private float _zoom = 1.0f;
private PointF _origin = new PointF(0, 0);
private Image _image = null;
#endregion
public ZoomPoint() {
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
this.AutoScroll = true;
UpdateScroll();
}
public Image Image {
get {
return _image;
}
set {
_image = value;
_origin = PointF.Empty;
_zoom = 1.0F;
UpdateScroll();
Invalidate();
}
}
protected override void OnPaintBackground(PaintEventArgs e) {
// don't allow the background to be painted
}
protected override void OnPaint(PaintEventArgs e) {
Graphics g = e.Graphics;
ClearBackground(g);
float dx = -_origin.X;
float dy = -_origin.Y;
g.Transform = new Matrix(_zoom, 0, 0, _zoom, dx, dy);
g.FillRectangle(Brushes.Blue, 100, 100, 5, 5);
DrawImage(g);
}
private void ClearBackground(Graphics g) {
g.Clear(SystemColors.Window);
}
protected override void OnScroll(ScrollEventArgs se) {
base.OnScroll(se);
if (se.ScrollOrientation ==
ScrollOrientation.HorizontalScroll) {
_origin.X += se.NewValue - se.OldValue;
}
else {
_origin.Y += se.NewValue - se.OldValue;
}
Invalidate();
}
protected override void OnMouseWheel(MouseEventArgs e) {
if (e.Delta > 0) {
ZoomToPoint(e.Location, ZoomDirection.In);
}
else {
ZoomToPoint(e.Location, ZoomDirection.Out);
}
Invalidate();
}
protected override void OnMouseClick(MouseEventArgs e) {
ZoomToPoint(e.Location, ZoomDirection.In);
Invalidate();
}
private void UpdateScroll() {
if (_image != null) {
Size scrollSize = new Size(
(int)Math.Round(_image.Width * _zoom),
(int)Math.Round(_image.Height * _zoom));
Point position = new Point(
(int)Math.Round(_origin.X),
(int)Math.Round(_origin.Y));
this.AutoScrollMinSize = scrollSize;
this.AutoScrollPosition = position;
}
else {
this.AutoScrollMargin = this.Size;
}
}
private void ZoomToPoint(Point viewPoint, ZoomDirection
direction) {
// get the model point
PointF modelPoint = ToModelPoint(viewPoint);
if (direction == ZoomDirection.In) {
// Increase the zoom
_zoom *= 1.25F;
}
else {
// decrease the zoom
_zoom *= .75F;
}
// calculate the new origin
_origin.X = (modelPoint.X * _zoom) - viewPoint.X;
_origin.Y = (modelPoint.Y * _zoom) - viewPoint.Y;
UpdateScroll();
}
private PointF ToModelPoint(Point viewPoint) {
PointF modelPoint = new PointF();
modelPoint.X = (_origin.X + viewPoint.X) / _zoom;
modelPoint.Y = (_origin.Y + viewPoint.Y) / _zoom;
return modelPoint;
}
private void DrawImage(Graphics g) {
if (null != _image) {
// set the transparency color for the image
ImageAttributes attr = new ImageAttributes();
attr.SetColorKey(Color.White, Color.White);
Rectangle destRect = new Rectangle(0, 0, _image.Width,
_image.Height);
g.DrawImage(_image, destRect, 0, 0, _image.Width,
_image.Height, GraphicsUnit.Pixel, attr);
}
}
protected override void Dispose(bool disposing) {
if (disposing) {
if (null != _image) {
_image.Dispose();
_image = null;
}
}
base.Dispose(disposing);
}
}