在c#中缩放到一个点

发布于 2024-07-13 01:42:55 字数 4449 浏览 6 评论 0原文

我正在尝试编写一个自定义可滚动 C# 控件,该控件可缩放到图像上的特定点。 我面临的问题是,当启用双缓冲时,图像似乎会向左上角猛拉,然后正确缩放到单击鼠标的位置。 这似乎只在我设置 AutoScrollPosition 时发生。 我验证了我的 OnPaint 方法中没有发生这种情况。 这似乎是一些我无法追踪的内部行为。 有人解决这个问题了吗?

这是一些示例代码,演示了我想要完成的任务。 仅当图像相当大时,该问题似乎才会明显地呈现给用户。

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 PointZoom : ScrollableControl
    {
        #region Private Data
        private float _zoom = 1.0f;
        private PointF _origin = PointF.Empty;
        private Image _image;
        private Matrix _transform = new Matrix();
        #endregion

        public PointZoom() {
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, 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;

            _transform = new Matrix(_zoom, 0, 0, _zoom, dx, dy);
            g.Transform = _transform;

            DrawImage(g);
        }

        private void ClearBackground(Graphics g) {
            g.Clear(SystemColors.Window);
        }

        protected override void OnScroll(ScrollEventArgs se) {
            if (se.ScrollOrientation == ScrollOrientation.HorizontalScroll) {
                _origin.X += se.NewValue - se.OldValue;
            }
            else {
                _origin.Y += se.NewValue - se.OldValue;
            }
            Invalidate();
            base.OnScroll(se);
        }



        protected override void OnMouseClick(MouseEventArgs e) {
            ZoomToPoint(e.Location);
            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.AutoScrollPosition = position;
                this.AutoScrollMinSize = scrollSize;
            }
            else {
                this.AutoScrollMargin = this.Size;
            }

        }

        private void ZoomToPoint(Point viewPoint) {

            PointF modelPoint = ToModelPoint(viewPoint);

            // Increase the zoom 
            _zoom *= 1.25F;

            // 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);
        }
    }

}

I am attempting to write a custom scrollable c# control that zooms to a particular point on an image. The problem that I am facing is that when double buffering is enabled, the image seems to jerk towards the top left corner and then zooms properly to the point where the mouse was clicked. This seems to only happen when I set the AutoScrollPosition. I verified that it does not happen in my OnPaint method. It appears to be some internal behavior that I cannot track down. Has anyone resolved this issue?

Here's some example code that demonstrates what I am trying to accomplish. The problem only seems to present itself noticeably to the user when the image is fairly large.

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 PointZoom : ScrollableControl
    {
        #region Private Data
        private float _zoom = 1.0f;
        private PointF _origin = PointF.Empty;
        private Image _image;
        private Matrix _transform = new Matrix();
        #endregion

        public PointZoom() {
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, 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;

            _transform = new Matrix(_zoom, 0, 0, _zoom, dx, dy);
            g.Transform = _transform;

            DrawImage(g);
        }

        private void ClearBackground(Graphics g) {
            g.Clear(SystemColors.Window);
        }

        protected override void OnScroll(ScrollEventArgs se) {
            if (se.ScrollOrientation == ScrollOrientation.HorizontalScroll) {
                _origin.X += se.NewValue - se.OldValue;
            }
            else {
                _origin.Y += se.NewValue - se.OldValue;
            }
            Invalidate();
            base.OnScroll(se);
        }



        protected override void OnMouseClick(MouseEventArgs e) {
            ZoomToPoint(e.Location);
            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.AutoScrollPosition = position;
                this.AutoScrollMinSize = scrollSize;
            }
            else {
                this.AutoScrollMargin = this.Size;
            }

        }

        private void ZoomToPoint(Point viewPoint) {

            PointF modelPoint = ToModelPoint(viewPoint);

            // Increase the zoom 
            _zoom *= 1.25F;

            // 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);
        }
    }

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

情绪操控生活 2024-07-20 01:42:56

尝试以下操作。 这是我为在工作项目中使用而编写的。 我删除了一些额外的功能,但这里的功能超出了回答您的问题所需的功能。 特别值得注意的是 CenterOn 和 Zoom 方法。 另请注意,我没有清除背景,而是先绘制背景。 Clear 对我来说也有奇怪的副作用。 我还继承了面板,它也最适合我。
请随意将其转换为 C#。

Imports System.Drawing.Drawing2D
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Public Class ctlViewer
   Inherits Panel

   Protected Const C_SmallChangePercent As Integer = 2
   Protected Const C_LargeChangePercent As Integer = 10

   Protected mimgImage As Image
   Protected mintActiveFrame As Integer
   Protected mdecZoom As Decimal
   Protected mpntUpperLeft As New Point
   Protected mpntCenter As New Point
   Protected mblnDragging As Boolean = False
   Private mButtons As MouseButtons

#Region " Constructor"
   Public Sub New()
      MyBase.New()
      Me.SetStyle(ControlStyles.ContainerControl, False)
      Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
      Me.SetStyle(ControlStyles.UserPaint, True)
      Me.SetStyle(ControlStyles.ResizeRedraw, True)
      Me.SetStyle(ControlStyles.UserPaint, True)
      Me.SetStyle(ControlStyles.DoubleBuffer, True)
      ZoomFactor = 1.0
      Me.AutoScroll = True
      Me.BackColor = Color.FromKnownColor(KnownColor.ControlDark)
   End Sub
#End Region

#Region " Properties"

   ''' <summary>
   ''' Image object representing the TIFF image.
   ''' </summary>
   ''' <value></value>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Property Image() As Image
      Get
         Return mimgImage
      End Get
      Set(ByVal Value As Image)
         AutoScrollPosition = New Point(0, 0)
         mimgImage = Value
         RaiseEvent ImageLoaded(New ImageLoadedEventArgs(Value))
         UpdateScaleFactor()
         Invalidate()
      End Set
   End Property


   ''' <summary>
   ''' Viewing area of image
   ''' </summary>
   ''' <value></value>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public ReadOnly Property ViewPort() As Rectangle
      Get
         Dim r As New Rectangle
         Dim pul As Point = Me.CoordViewerToSrc(New Point(0, 0))
         Dim pbr As Point = Me.CoordViewerToSrc(New Point(Me.Width, Me.Height))
         r.Location = pul
         r.Width = pbr.X - pul.X
         r.Height = pbr.Y - pul.Y
         Return r
      End Get
   End Property

   ''' <summary>
   ''' Gets or sets the zoom / scale factor for the image being displayed.
   ''' </summary>
   ''' <value></value>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Property ZoomFactor() As Decimal
      Get
         Return mdecZoom
      End Get
      Set(ByVal Value As Decimal)
         If Value < 0 OrElse Value < 0.00001 Then
            Value = 0.00001F
         End If
         mdecZoom = Value
         UpdateScaleFactor()
         Invalidate()
         RaiseEvent ZoomChanged(New ImageViewerEventArgs(Me.Image))
      End Set
   End Property

#End Region

#Region " Event Signatures"
   Public Event ImageMouseDown(ByVal e As ImageMouseEventArgs)
   Public Event ImageMouseMove(ByVal e As ImageMouseEventArgs)
   Public Event ImageMouseUp(ByVal e As ImageMouseEventArgs)
   Public Event ImageLoaded(ByVal e As ImageLoadedEventArgs)
   Public Event ZoomChanged(ByVal e As ImageViewerEventArgs)
   Public Event ImageViewPortChanged(ByVal e As ImageViewerEventArgs)

   Public Event ViewerPaint(ByVal sender As Object, ByVal e As PaintEventArgs)
#End Region

#Region " Public Subs/Functions"

   ''' <summary>
   ''' Pans the viewer by X,Y up to the bounds of the image.
   ''' </summary>
   ''' <param name="x"></param>
   ''' <param name="y"></param>
   ''' <remarks></remarks>
   Public Sub Pan(ByVal x As Integer, ByVal y As Integer)
      Me.AutoScrollPosition = New Point(Math.Abs(Me.AutoScrollPosition.X) + x, Math.Abs(Me.AutoScrollPosition.Y) + y)
      Me.Invalidate()
   End Sub

   ''' <summary>
   ''' Zoom image
   ''' </summary>
   ''' <param name="decZoom"></param>
   ''' <remarks></remarks>
   Public Sub Zoom(ByVal decZoom As Decimal)
      ZoomFactor = decZoom
   End Sub

   ''' <summary>
   ''' Zoom image and scroll to rectangle coordinates.
   ''' </summary>
   ''' <param name="decZoomFactor"></param>
   ''' <param name="objRectangleToCenter"></param>
   ''' <remarks></remarks>
   Public Sub Zoom(ByVal decZoomFactor As Decimal, ByVal objRectangleToCenter As Rectangle)
      Dim intCenterX As Int32 = objRectangleToCenter.X + objRectangleToCenter.Width / 2
      Dim intCenterY As Int32 = objRectangleToCenter.Y + objRectangleToCenter.Height / 2
      Me.CenterOn(New Point(intCenterX, intCenterY))
      Me.ZoomFactor = decZoomFactor
   End Sub

   ''' <summary>
   ''' Zoom to fit image on screen.
   ''' </summary>
   ''' <param name="minZoom"></param>
   ''' <param name="maxZoom"></param>
   ''' <remarks></remarks>
   Public Sub ZoomToFit(ByVal minZoom As Decimal, ByVal maxZoom As Decimal)
      If Not Me.Image Is Nothing Then
         Dim ItoVh As Single = Me.Image.Height / (Me.Height - 2)
         Dim ItoVw As Single = Me.Image.Width / (Me.Width - 2)
         Dim zf As Single = 1 / Math.Max(ItoVh, ItoVw)
         If (((zf > minZoom) And minZoom <> 0) Or minZoom = 0) _
               And ((zf < maxZoom) And maxZoom <> 0) Or maxZoom = 0 Then
            Me.Zoom(zf)
         End If
      End If
   End Sub

   ''' <summary>
   ''' Zoom to fit width of image
   ''' </summary>
   ''' <param name="minZoom"></param>
   ''' <param name="maxZoom"></param>
   ''' <remarks></remarks>
   Public Sub ZoomToWidth(ByVal minZoom As Decimal, ByVal maxZoom As Decimal)
      If Image Is Nothing Then
         Me.AutoScrollMargin = Me.Size
         Me.AutoScrollMinSize = Me.Size

         mpntCenter = New Point(0, 0)
         mpntUpperLeft = New Point(0, 0)
         Exit Sub
      End If
      Dim intOff As Integer = 0
      If ScrollStateVScrollVisible Then
         intOff = ScrollStateVScrollVisible
      End If
      Dim ItoVw As Single = Me.Image.Width / (Me.Width - 2)
      Dim zf As Single = 1 / ItoVw
      If (Me.Image.Height * zf) >= Me.Height Then
         ItoVw = Me.Image.Width / (Me.Width - 22)
         zf = 1 / ItoVw
      End If
      If (((zf > minZoom) And minZoom <> 0) Or minZoom = 0) _
            And ((zf < maxZoom) And maxZoom <> 0) Or maxZoom = 0 Then
         Me.Zoom(zf)
      End If
   End Sub

   ''' <summary>
   ''' Adjust scrollbars to zoomed size of image
   ''' </summary>
   ''' <remarks></remarks>
   Protected Sub UpdateScaleFactor()
      If Image Is Nothing Then
         Me.AutoScrollMargin = Me.Size
         Me.AutoScrollMinSize = Me.Size

         mpntCenter = New Point(0, 0)
         mpntUpperLeft = New Point(0, 0)
      Else
         Me.AutoScrollMinSize = New Size(CInt(Me.Image.Width * ZoomFactor + 0.5F), CInt(Me.Image.Height * ZoomFactor + 0.5F))
      End If
      Me.HorizontalScroll.LargeChange = Me.Size.Width * (C_LargeChangePercent / 100)
      Me.VerticalScroll.LargeChange = Me.Size.Height * (C_LargeChangePercent / 100)
      Me.HorizontalScroll.SmallChange = Me.Size.Width * (C_SmallChangePercent / 100)
      Me.VerticalScroll.SmallChange = Me.Size.Height * (C_SmallChangePercent / 100)
   End Sub

   ''' <summary>
   ''' Convert a point of the original image to screen coordinates adjusted for zoom and pan.
   ''' </summary>
   ''' <param name="pntPoint"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Function CoordSrcToViewer(ByVal pntPoint As Point) As Point
      Dim pntResult As New Point
      pntResult.X = pntPoint.X * Me.ZoomFactor + Me.AutoScrollPosition.X
      pntResult.Y = pntPoint.Y * Me.ZoomFactor + Me.AutoScrollPosition.Y
      Return pntResult
   End Function

   ''' <summary>
   ''' Convert a screen point to the corrseponding coordinate of the original image.
   ''' </summary>
   ''' <param name="pntPoint"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Function CoordViewerToSrc(ByVal pntPoint As Point) As Point
      Dim pntResult As New Point
      pntResult.X = (pntPoint.X - Me.AutoScrollPosition.X) / Me.ZoomFactor
      pntResult.Y = (pntPoint.Y - Me.AutoScrollPosition.Y) / Me.ZoomFactor
      Return pntResult
   End Function

   ''' <summary>
   ''' Returns an offset needed to move the center point to make visible.
   ''' </summary>
   ''' <param name="imagePoint"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Friend Function PointIsVisible(ByVal imagePoint As Point) As Point
      Dim pntViewer As Point = Me.CoordSrcToViewer(imagePoint)
      Dim pntSize As New Point((pntViewer.X - Me.Width) / Me.ZoomFactor, (pntViewer.Y - Me.Height) / Me.ZoomFactor)
      If pntViewer.X > 0 And pntViewer.X < Me.Width Then
         pntSize.X = 0
      End If
      If pntViewer.Y > 0 And pntViewer.Y < Me.Height Then
         pntSize.Y = 0
      End If
      If pntViewer.X < 0 Then
         pntSize.X = pntViewer.X
      End If
      If pntViewer.Y < 0 Then
         pntSize.Y = pntViewer.Y
      End If
      Return pntSize
   End Function

   ''' <summary>
   ''' Centers view on coordinates of the original image.
   ''' </summary>
   ''' <param name="X"></param>
   ''' <param name="Y"></param>
   ''' <remarks></remarks>
   Public Sub CenterOn(ByVal X As Integer, ByVal Y As Integer)
      CenterOn(New Point(X, Y))
   End Sub

   ''' <summary>
   ''' Centers view on a point of the original image.
   ''' </summary>
   ''' <param name="pntCenter"></param>
   ''' <remarks></remarks>
   Public Sub CenterOn(ByVal pntCenter As Point)
      Dim midX As Integer = Me.Width / 2
      Dim midY As Integer = Me.Height / 2
      Dim intX As Integer = (pntCenter.X * ZoomFactor - midX)
      Dim intY As Integer = (pntCenter.Y * ZoomFactor - midY)
      Me.AutoScrollPosition = New Point(intX, intY)
      Me.Invalidate()
   End Sub

   ''' <summary>
   ''' Returns image coordinate which is centered in viewer.
   ''' </summary>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Function GetCenterPoint() As Point
      Dim pntResult As Point
      pntResult = CoordViewerToSrc(New Point(Me.Width / 2, Me.Height / 2))
      If pntResult.X > Me.Image.Width Or pntResult.Y > Image.Height Then
         pntResult = Nothing
      End If
      Return pntResult
   End Function

   ''' <summary>
   ''' Fire viewport changed event.
   ''' </summary>
   ''' <remarks></remarks>
   Private Sub FireViewPortChangedEvent()
      Dim e As New ImageViewerEventArgs(Me.Image)
      RaiseEvent ImageViewPortChanged(e)
   End Sub
   Private Sub FireViewerPaintEvent(ByVal e As PaintEventArgs)
      RaiseEvent ViewerPaint(Me, e)
   End Sub
#End Region

#Region " Overrides"

   ''' <summary>
   ''' Paint image in proper position and zoom.  All work is done with a Matrix object.
   ''' The coordinates of the graphics instance of the ctlViewer_OnPaint event 
   ''' are transformed.  This allows drawing on the "paper image" rather than "over the viewport"
   ''' </summary>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
      If mimgImage Is Nothing Then
         MyBase.OnPaintBackground(e)
         Return
      Else
         Debug.WriteLine("ctl painting")
         Dim mx As New Matrix
         e.Graphics.FillRectangle(New SolidBrush(Me.BackColor), 0, 0, Me.Width, Me.Height)
         mx.Translate(Me.AutoScrollPosition.X, Me.AutoScrollPosition.Y)
         mx.Scale(ZoomFactor, ZoomFactor)
         e.Graphics.SetClip(New Rectangle(0, 0, Me.Width, Me.Height))
         e.Graphics.InterpolationMode = InterpolationMode.Low
         e.Graphics.SmoothingMode = SmoothingMode.HighSpeed
         e.Graphics.Transform = mx
         Dim ia As New ImageAttributes
         e.Graphics.DrawImage(Image, _
             New Rectangle(-Me.AutoScrollPosition.X / ZoomFactor, _
             -Me.AutoScrollPosition.Y / ZoomFactor, _
              Me.Width / ZoomFactor, _
              Me.Height / ZoomFactor), _
              Me.ViewPort.Left, Me.ViewPort.Top, Me.ViewPort.Width, Me.ViewPort.Height, _
             GraphicsUnit.Pixel, ia)
         ia.Dispose()
      End If
      Me.mpntCenter = Me.GetCenterPoint
      FireViewPortChangedEvent()
      MyBase.OnPaint(e)
      e.Graphics.ResetClip()
      e.Graphics.ResetTransform()
      Me.FireViewerPaintEvent(e)

   End Sub

   ''' <summary>
   ''' Pan image and raise event.
   ''' </summary>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
      Me.mButtons = e.Button
      RaiseEvent ImageMouseDown(New ImageMouseEventArgs(e.Button, e.Clicks, e.X, e.Y, e.Delta, Me.ZoomFactor, Me.AutoScrollPosition))
      MyBase.OnMouseDown(e)
   End Sub

   ''' <summary>
   ''' Stop panning image and raise event.
   ''' </summary>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
      Me.Cursor = Cursors.Arrow
      MyBase.OnMouseUp(e)

      RaiseEvent ImageMouseUp(New ImageMouseEventArgs(e.Button, e.Clicks, e.X, e.Y, e.Delta, Me.ZoomFactor, Me.AutoScrollPosition))
      Me.mButtons = Windows.Forms.MouseButtons.None
   End Sub

   ''' <summary>
   ''' Pan image if PanOnMouseMove is True.  Fire the ImageMouseMove event.
   ''' </summary>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
      Static oldX As Integer
      Static oldy As Integer
      Try
         oldX = e.X
         oldy = e.Y
      Catch ex As Exception
         Throw ex
      Finally
         MyBase.OnMouseMove(e)
         RaiseEvent ImageMouseMove(New ImageMouseEventArgs(Me.mButtons, e.Clicks, e.X, e.Y, e.Delta, Me.ZoomFactor, Me.AutoScrollPosition))
      End Try
   End Sub

   ''' <summary>
   ''' Catch a panel scroll event.
   ''' </summary>
   ''' <param name="m"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
      Const WM_VSCROLL As Integer = 277 '115 hex
      Const WM_HSCROLL As Integer = 276 '0x114;
      MyBase.WndProc(m)
      If Not m.HWnd.Equals(Me.Handle) Then
         Return
      End If
      If m.Msg = WM_VSCROLL Or m.Msg = WM_HSCROLL Then
         Me.Invalidate()
      End If
   End Sub
#End Region
End Class

Try the following. This is one I wrote for use in a project at work. I stripped out some of the extra functionality, but there's more here than needs to be to answer your question. Of particular note for you are the CenterOn and Zoom methods. Also notice I'm not clearing the background, but painting a background first. Clear had weird side effects for me too. I'm also inheriting from Panel which worked best for me as well.
Feel free to convert it to C#.

Imports System.Drawing.Drawing2D
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Public Class ctlViewer
   Inherits Panel

   Protected Const C_SmallChangePercent As Integer = 2
   Protected Const C_LargeChangePercent As Integer = 10

   Protected mimgImage As Image
   Protected mintActiveFrame As Integer
   Protected mdecZoom As Decimal
   Protected mpntUpperLeft As New Point
   Protected mpntCenter As New Point
   Protected mblnDragging As Boolean = False
   Private mButtons As MouseButtons

#Region " Constructor"
   Public Sub New()
      MyBase.New()
      Me.SetStyle(ControlStyles.ContainerControl, False)
      Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
      Me.SetStyle(ControlStyles.UserPaint, True)
      Me.SetStyle(ControlStyles.ResizeRedraw, True)
      Me.SetStyle(ControlStyles.UserPaint, True)
      Me.SetStyle(ControlStyles.DoubleBuffer, True)
      ZoomFactor = 1.0
      Me.AutoScroll = True
      Me.BackColor = Color.FromKnownColor(KnownColor.ControlDark)
   End Sub
#End Region

#Region " Properties"

   ''' <summary>
   ''' Image object representing the TIFF image.
   ''' </summary>
   ''' <value></value>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Property Image() As Image
      Get
         Return mimgImage
      End Get
      Set(ByVal Value As Image)
         AutoScrollPosition = New Point(0, 0)
         mimgImage = Value
         RaiseEvent ImageLoaded(New ImageLoadedEventArgs(Value))
         UpdateScaleFactor()
         Invalidate()
      End Set
   End Property


   ''' <summary>
   ''' Viewing area of image
   ''' </summary>
   ''' <value></value>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public ReadOnly Property ViewPort() As Rectangle
      Get
         Dim r As New Rectangle
         Dim pul As Point = Me.CoordViewerToSrc(New Point(0, 0))
         Dim pbr As Point = Me.CoordViewerToSrc(New Point(Me.Width, Me.Height))
         r.Location = pul
         r.Width = pbr.X - pul.X
         r.Height = pbr.Y - pul.Y
         Return r
      End Get
   End Property

   ''' <summary>
   ''' Gets or sets the zoom / scale factor for the image being displayed.
   ''' </summary>
   ''' <value></value>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Property ZoomFactor() As Decimal
      Get
         Return mdecZoom
      End Get
      Set(ByVal Value As Decimal)
         If Value < 0 OrElse Value < 0.00001 Then
            Value = 0.00001F
         End If
         mdecZoom = Value
         UpdateScaleFactor()
         Invalidate()
         RaiseEvent ZoomChanged(New ImageViewerEventArgs(Me.Image))
      End Set
   End Property

#End Region

#Region " Event Signatures"
   Public Event ImageMouseDown(ByVal e As ImageMouseEventArgs)
   Public Event ImageMouseMove(ByVal e As ImageMouseEventArgs)
   Public Event ImageMouseUp(ByVal e As ImageMouseEventArgs)
   Public Event ImageLoaded(ByVal e As ImageLoadedEventArgs)
   Public Event ZoomChanged(ByVal e As ImageViewerEventArgs)
   Public Event ImageViewPortChanged(ByVal e As ImageViewerEventArgs)

   Public Event ViewerPaint(ByVal sender As Object, ByVal e As PaintEventArgs)
#End Region

#Region " Public Subs/Functions"

   ''' <summary>
   ''' Pans the viewer by X,Y up to the bounds of the image.
   ''' </summary>
   ''' <param name="x"></param>
   ''' <param name="y"></param>
   ''' <remarks></remarks>
   Public Sub Pan(ByVal x As Integer, ByVal y As Integer)
      Me.AutoScrollPosition = New Point(Math.Abs(Me.AutoScrollPosition.X) + x, Math.Abs(Me.AutoScrollPosition.Y) + y)
      Me.Invalidate()
   End Sub

   ''' <summary>
   ''' Zoom image
   ''' </summary>
   ''' <param name="decZoom"></param>
   ''' <remarks></remarks>
   Public Sub Zoom(ByVal decZoom As Decimal)
      ZoomFactor = decZoom
   End Sub

   ''' <summary>
   ''' Zoom image and scroll to rectangle coordinates.
   ''' </summary>
   ''' <param name="decZoomFactor"></param>
   ''' <param name="objRectangleToCenter"></param>
   ''' <remarks></remarks>
   Public Sub Zoom(ByVal decZoomFactor As Decimal, ByVal objRectangleToCenter As Rectangle)
      Dim intCenterX As Int32 = objRectangleToCenter.X + objRectangleToCenter.Width / 2
      Dim intCenterY As Int32 = objRectangleToCenter.Y + objRectangleToCenter.Height / 2
      Me.CenterOn(New Point(intCenterX, intCenterY))
      Me.ZoomFactor = decZoomFactor
   End Sub

   ''' <summary>
   ''' Zoom to fit image on screen.
   ''' </summary>
   ''' <param name="minZoom"></param>
   ''' <param name="maxZoom"></param>
   ''' <remarks></remarks>
   Public Sub ZoomToFit(ByVal minZoom As Decimal, ByVal maxZoom As Decimal)
      If Not Me.Image Is Nothing Then
         Dim ItoVh As Single = Me.Image.Height / (Me.Height - 2)
         Dim ItoVw As Single = Me.Image.Width / (Me.Width - 2)
         Dim zf As Single = 1 / Math.Max(ItoVh, ItoVw)
         If (((zf > minZoom) And minZoom <> 0) Or minZoom = 0) _
               And ((zf < maxZoom) And maxZoom <> 0) Or maxZoom = 0 Then
            Me.Zoom(zf)
         End If
      End If
   End Sub

   ''' <summary>
   ''' Zoom to fit width of image
   ''' </summary>
   ''' <param name="minZoom"></param>
   ''' <param name="maxZoom"></param>
   ''' <remarks></remarks>
   Public Sub ZoomToWidth(ByVal minZoom As Decimal, ByVal maxZoom As Decimal)
      If Image Is Nothing Then
         Me.AutoScrollMargin = Me.Size
         Me.AutoScrollMinSize = Me.Size

         mpntCenter = New Point(0, 0)
         mpntUpperLeft = New Point(0, 0)
         Exit Sub
      End If
      Dim intOff As Integer = 0
      If ScrollStateVScrollVisible Then
         intOff = ScrollStateVScrollVisible
      End If
      Dim ItoVw As Single = Me.Image.Width / (Me.Width - 2)
      Dim zf As Single = 1 / ItoVw
      If (Me.Image.Height * zf) >= Me.Height Then
         ItoVw = Me.Image.Width / (Me.Width - 22)
         zf = 1 / ItoVw
      End If
      If (((zf > minZoom) And minZoom <> 0) Or minZoom = 0) _
            And ((zf < maxZoom) And maxZoom <> 0) Or maxZoom = 0 Then
         Me.Zoom(zf)
      End If
   End Sub

   ''' <summary>
   ''' Adjust scrollbars to zoomed size of image
   ''' </summary>
   ''' <remarks></remarks>
   Protected Sub UpdateScaleFactor()
      If Image Is Nothing Then
         Me.AutoScrollMargin = Me.Size
         Me.AutoScrollMinSize = Me.Size

         mpntCenter = New Point(0, 0)
         mpntUpperLeft = New Point(0, 0)
      Else
         Me.AutoScrollMinSize = New Size(CInt(Me.Image.Width * ZoomFactor + 0.5F), CInt(Me.Image.Height * ZoomFactor + 0.5F))
      End If
      Me.HorizontalScroll.LargeChange = Me.Size.Width * (C_LargeChangePercent / 100)
      Me.VerticalScroll.LargeChange = Me.Size.Height * (C_LargeChangePercent / 100)
      Me.HorizontalScroll.SmallChange = Me.Size.Width * (C_SmallChangePercent / 100)
      Me.VerticalScroll.SmallChange = Me.Size.Height * (C_SmallChangePercent / 100)
   End Sub

   ''' <summary>
   ''' Convert a point of the original image to screen coordinates adjusted for zoom and pan.
   ''' </summary>
   ''' <param name="pntPoint"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Function CoordSrcToViewer(ByVal pntPoint As Point) As Point
      Dim pntResult As New Point
      pntResult.X = pntPoint.X * Me.ZoomFactor + Me.AutoScrollPosition.X
      pntResult.Y = pntPoint.Y * Me.ZoomFactor + Me.AutoScrollPosition.Y
      Return pntResult
   End Function

   ''' <summary>
   ''' Convert a screen point to the corrseponding coordinate of the original image.
   ''' </summary>
   ''' <param name="pntPoint"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Function CoordViewerToSrc(ByVal pntPoint As Point) As Point
      Dim pntResult As New Point
      pntResult.X = (pntPoint.X - Me.AutoScrollPosition.X) / Me.ZoomFactor
      pntResult.Y = (pntPoint.Y - Me.AutoScrollPosition.Y) / Me.ZoomFactor
      Return pntResult
   End Function

   ''' <summary>
   ''' Returns an offset needed to move the center point to make visible.
   ''' </summary>
   ''' <param name="imagePoint"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Friend Function PointIsVisible(ByVal imagePoint As Point) As Point
      Dim pntViewer As Point = Me.CoordSrcToViewer(imagePoint)
      Dim pntSize As New Point((pntViewer.X - Me.Width) / Me.ZoomFactor, (pntViewer.Y - Me.Height) / Me.ZoomFactor)
      If pntViewer.X > 0 And pntViewer.X < Me.Width Then
         pntSize.X = 0
      End If
      If pntViewer.Y > 0 And pntViewer.Y < Me.Height Then
         pntSize.Y = 0
      End If
      If pntViewer.X < 0 Then
         pntSize.X = pntViewer.X
      End If
      If pntViewer.Y < 0 Then
         pntSize.Y = pntViewer.Y
      End If
      Return pntSize
   End Function

   ''' <summary>
   ''' Centers view on coordinates of the original image.
   ''' </summary>
   ''' <param name="X"></param>
   ''' <param name="Y"></param>
   ''' <remarks></remarks>
   Public Sub CenterOn(ByVal X As Integer, ByVal Y As Integer)
      CenterOn(New Point(X, Y))
   End Sub

   ''' <summary>
   ''' Centers view on a point of the original image.
   ''' </summary>
   ''' <param name="pntCenter"></param>
   ''' <remarks></remarks>
   Public Sub CenterOn(ByVal pntCenter As Point)
      Dim midX As Integer = Me.Width / 2
      Dim midY As Integer = Me.Height / 2
      Dim intX As Integer = (pntCenter.X * ZoomFactor - midX)
      Dim intY As Integer = (pntCenter.Y * ZoomFactor - midY)
      Me.AutoScrollPosition = New Point(intX, intY)
      Me.Invalidate()
   End Sub

   ''' <summary>
   ''' Returns image coordinate which is centered in viewer.
   ''' </summary>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Function GetCenterPoint() As Point
      Dim pntResult As Point
      pntResult = CoordViewerToSrc(New Point(Me.Width / 2, Me.Height / 2))
      If pntResult.X > Me.Image.Width Or pntResult.Y > Image.Height Then
         pntResult = Nothing
      End If
      Return pntResult
   End Function

   ''' <summary>
   ''' Fire viewport changed event.
   ''' </summary>
   ''' <remarks></remarks>
   Private Sub FireViewPortChangedEvent()
      Dim e As New ImageViewerEventArgs(Me.Image)
      RaiseEvent ImageViewPortChanged(e)
   End Sub
   Private Sub FireViewerPaintEvent(ByVal e As PaintEventArgs)
      RaiseEvent ViewerPaint(Me, e)
   End Sub
#End Region

#Region " Overrides"

   ''' <summary>
   ''' Paint image in proper position and zoom.  All work is done with a Matrix object.
   ''' The coordinates of the graphics instance of the ctlViewer_OnPaint event 
   ''' are transformed.  This allows drawing on the "paper image" rather than "over the viewport"
   ''' </summary>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
      If mimgImage Is Nothing Then
         MyBase.OnPaintBackground(e)
         Return
      Else
         Debug.WriteLine("ctl painting")
         Dim mx As New Matrix
         e.Graphics.FillRectangle(New SolidBrush(Me.BackColor), 0, 0, Me.Width, Me.Height)
         mx.Translate(Me.AutoScrollPosition.X, Me.AutoScrollPosition.Y)
         mx.Scale(ZoomFactor, ZoomFactor)
         e.Graphics.SetClip(New Rectangle(0, 0, Me.Width, Me.Height))
         e.Graphics.InterpolationMode = InterpolationMode.Low
         e.Graphics.SmoothingMode = SmoothingMode.HighSpeed
         e.Graphics.Transform = mx
         Dim ia As New ImageAttributes
         e.Graphics.DrawImage(Image, _
             New Rectangle(-Me.AutoScrollPosition.X / ZoomFactor, _
             -Me.AutoScrollPosition.Y / ZoomFactor, _
              Me.Width / ZoomFactor, _
              Me.Height / ZoomFactor), _
              Me.ViewPort.Left, Me.ViewPort.Top, Me.ViewPort.Width, Me.ViewPort.Height, _
             GraphicsUnit.Pixel, ia)
         ia.Dispose()
      End If
      Me.mpntCenter = Me.GetCenterPoint
      FireViewPortChangedEvent()
      MyBase.OnPaint(e)
      e.Graphics.ResetClip()
      e.Graphics.ResetTransform()
      Me.FireViewerPaintEvent(e)

   End Sub

   ''' <summary>
   ''' Pan image and raise event.
   ''' </summary>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
      Me.mButtons = e.Button
      RaiseEvent ImageMouseDown(New ImageMouseEventArgs(e.Button, e.Clicks, e.X, e.Y, e.Delta, Me.ZoomFactor, Me.AutoScrollPosition))
      MyBase.OnMouseDown(e)
   End Sub

   ''' <summary>
   ''' Stop panning image and raise event.
   ''' </summary>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
      Me.Cursor = Cursors.Arrow
      MyBase.OnMouseUp(e)

      RaiseEvent ImageMouseUp(New ImageMouseEventArgs(e.Button, e.Clicks, e.X, e.Y, e.Delta, Me.ZoomFactor, Me.AutoScrollPosition))
      Me.mButtons = Windows.Forms.MouseButtons.None
   End Sub

   ''' <summary>
   ''' Pan image if PanOnMouseMove is True.  Fire the ImageMouseMove event.
   ''' </summary>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
      Static oldX As Integer
      Static oldy As Integer
      Try
         oldX = e.X
         oldy = e.Y
      Catch ex As Exception
         Throw ex
      Finally
         MyBase.OnMouseMove(e)
         RaiseEvent ImageMouseMove(New ImageMouseEventArgs(Me.mButtons, e.Clicks, e.X, e.Y, e.Delta, Me.ZoomFactor, Me.AutoScrollPosition))
      End Try
   End Sub

   ''' <summary>
   ''' Catch a panel scroll event.
   ''' </summary>
   ''' <param name="m"></param>
   ''' <remarks></remarks>
   Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
      Const WM_VSCROLL As Integer = 277 '115 hex
      Const WM_HSCROLL As Integer = 276 '0x114;
      MyBase.WndProc(m)
      If Not m.HWnd.Equals(Me.Handle) Then
         Return
      End If
      If m.Msg = WM_VSCROLL Or m.Msg = WM_HSCROLL Then
         Me.Invalidate()
      End If
   End Sub
#End Region
End Class
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文