自定义 Winforms 设计器控件同时缩放和平移控件

Posted

技术标签:

【中文标题】自定义 Winforms 设计器控件同时缩放和平移控件【英文标题】:Zoom and Pan controls simultaneously for a custom Winforms designer control 【发布时间】:2021-10-15 14:59:00 【问题描述】:

我正在尝试在 winforms 中创建一个“设计师”。这将向用户呈现一个所见即所得的“画布”,用户可以将图像、文本和形状拖放到该画布上。然后,他们可以通过带有手柄的“选择器”框来选择、移动和调整它们的大小。用户也可以一次选择多个对象,因此需要多个选择器。您还必须能够缩放和平移画布。

必须通过winforms。我决定的方法是在Paint 事件中按程序将所有项目绘制到单个控件(PicturBox 作为画布)上。对于选择器,我将使用放置在 Canvas 顶部的控件作为拖动手柄角的指南。然后我会让这个控件不可见。

我制作了一个自定义的PictureBox(ScaledPictureBox),它是可扩展的,并将其放置在启用自动滚动的Panel 中。这很好用 - 实现了缩放和平移。然后我在上面放置了另一个ScaledPictureBox 作为选择器。

下面的代码可以很好地以 100% 平移。但是,当我在缩放时平移时,选择器的位置是关闭的。这是一个视频:

Video

代码如下:

Imports System.Drawing.Drawing2D

Partial Public Class ScaledPictureBox
    Inherits PictureBox

    Public Property ScaleM As Matrix
    Private Property Zoom As Single
    Private Property OriginalSize As Size

    Public Sub New()
        ScaleM = New Matrix()
        SizeMode = PictureBoxSizeMode.Zoom
    End Sub

    Public Sub InitImage()
        OriginalSize = Me.Size
        Size = OriginalSize
        SetZoom(100)
    End Sub

    Public Sub SetZoom(ByVal zoomfactor As Single)
        If zoomfactor <= 0 Then Throw New Exception("Zoom must be positive")
        Dim oldZoom As Single = Zoom
        Zoom = zoomfactor / 100.0F
        ScaleM.Reset()
        ScaleM.Scale(Zoom, Zoom)
        If OriginalSize <> Size.Empty Then Size = New Size(CInt((OriginalSize.Width * Zoom)), CInt((OriginalSize.Height * Zoom)))
    End Sub

    Public Function ScalePoint(ByVal pt As PointF) As PointF
        Return New PointF(pt.X / Zoom, pt.Y / Zoom)
    End Function

End Class

测试表格:

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Canvas.InitImage()
        Selector.InitImage()
    End Sub

    Private Sub TrackBar1_Scroll(sender As Object, e As EventArgs) Handles TrackBar1.Scroll

        Canvas.SetZoom(CInt(TrackBar1.Value))
        Selector.SetZoom(CInt(TrackBar1.Value))

        Canvas.Invalidate()
        Selector.Invalidate()

    End Sub

    Dim moving As Boolean = False
    Dim CanvasClickPoint As New Point
    Dim SelectorLocation As New Point

    Private Sub Canvas_MouseDown(sender As Object, e As MouseEventArgs) Handles Canvas.MouseDown

        If (ModifierKeys And Keys.Control) = Keys.Control Then
            moving = True
            CanvasClickPoint = e.Location
            SelectorLocation = Selector.Location
        End If

    End Sub

    Private Sub Canvas_MouseMove(sender As Object, e As MouseEventArgs) Handles Canvas.MouseMove

        If moving Then

            Dim CanvasNewPoint As Point = New Point(Canvas.Left + (e.Location.X - CanvasClickPoint.X),
                                                      Canvas.Top + (e.Location.Y - CanvasClickPoint.Y))

            SelectorLocation.X = SelectorLocation.X + (e.Location.X - CanvasClickPoint.X)
            SelectorLocation.Y = SelectorLocation.Y + (e.Location.Y - CanvasClickPoint.Y)

            Dim SelectorLocationPointf As PointF = SelectorLocation
            SelectorLocation = Point.Round(Selector.ScalePoint(SelectorLocationPointf))
            Selector.Location = SelectorLocation

            Canvas.Location = CanvasNewPoint

        End If
    End Sub

    Private Sub Canvas_MouseUp(sender As Object, e As MouseEventArgs) Handles Canvas.MouseUp
        moving = False
    End Sub

End Class

这是框架讨论的控件位置的测试设置:

我猜这与我对Selector.ScalePoint 的使用有关 - 选择器的位置需要应用一些缩放,就好像你不需要一样,它对于 100% 缩放以外的任何东西都是关闭的。然而,数学和技术超出了我的能力,因为我解除了ScaledPictureBox 的代码,虽然我可以大致理解ScalePoint 背后的想法,但我无法完全理解它。希望有人可以帮忙

Visual Studio 项目下载(小)HERE

【问题讨论】:

你为什么不简单地根据选择的比例调整图片框的大小(在SizeMode = Zoom,确定你不想拉伸你的图像,对吧?)?目前尚不清楚 PictureBox 的初始大小是什么以及是什么决定了它。另一个 PictureBox(无论是什么用途)可以具有始终相同或按较大控件的初始大小缩放的初始大小。父级是较小的 PictureBox 到较大的一个,因此当您移动图像时,较小的 PicureBox 跟随其父级,没有其他事情可做。 -- 我建议用这两个控件构建一个UserControl。 谢谢。 ScaledPictureBox 确实调整了图片框的大小。我很确定你建议它做什么。问题在于缩放例程。 我建议缩放图片框的大小。我这么说是因为您提到了 drawing 并且那里有一个 Matrix,即使在您发布的代码中的任何地方都没有绘制任何东西并且 Matrix 的使用是未知的。 -- 当您将缩放的Selector 设置为较大的 PictureBox 时,当另一个 PictureBox 被移动时,无需在代码中移动它,因为它是它的父级,并且无需任何进一步计算即可跟随。 -- 还提到,您应该构建一个包含全部功能的 UserControl。 @Jimi - 完美,谢谢 - 我错过了基础。将 Selector 与 Canvas 以及其他一些代码抖动一起育儿起到了作用(工作代码发布在下面)。它的附加好处本质上提供了一个“透明控件”,您在另一篇文章中也帮助了我。再次重写...... 【参考方案1】:

感谢 Jimi,找到答案。我错过了在 VS Designer 中向控件添加控件并不一定使其成为父控件的子控件。这在代码中与其他一些修复一起使它工作。可扩展控件:

Imports System.Drawing.Drawing2D

Partial Public Class ScaledPictureBox
    Inherits PictureBox

    Public Property ScaleM As Matrix
    Private Property OriginalSize As Size
    Private Property Zoom As Single

    Public Sub New()
        ScaleM = New Matrix()
        SizeMode = PictureBoxSizeMode.Zoom
    End Sub

    Public Sub InitImage()
        OriginalSize = Me.Size
        Size = OriginalSize
        SetZoom(100)
    End Sub

    Public Sub SetZoom(ByVal zoomfactor As Single)
        If zoomfactor <= 0 Then Throw New Exception("Zoom must be positive")
        Dim oldZoom As Single = Zoom
        Zoom = zoomfactor / 100.0F
        ScaleM.Reset()
        ScaleM.Scale(Zoom, Zoom)
        If OriginalSize <> Size.Empty Then Size = New Size(CInt((OriginalSize.Width * Zoom)), CInt((OriginalSize.Height * Zoom)))
    End Sub

    Public Function ScalePoint(ByVal pt As PointF) As PointF
        Return New PointF(pt.X / Zoom, pt.Y / Zoom)
    End Function

End Class

以及如何使用:

Public Class Form1

Dim moving As Boolean = False
Dim CanvasClickPoint As New Point
Dim SelectorLocation As New Point

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    Canvas.InitImage()
    Selector.InitImage()

    SelectorLocation = Selector.Location

    Canvas.Controls.Add(Selector)

End Sub

Private Sub TrackBar1_Scroll(sender As Object, e As EventArgs) Handles TrackBar1.Scroll

    Zoom(TrackBar1.Value)

End Sub

Private Sub Zoom(zoom As Integer)

    Canvas.SetZoom(CInt(zoom))
    Selector.SetZoom(CInt(zoom))

    Selector.Location = New Point(SelectorLocation.X * (zoom / 100), SelectorLocation.Y * (zoom / 100))

End Sub

Private Sub Canvas_MouseDown(sender As Object, e As MouseEventArgs) Handles Canvas.MouseDown

    If (ModifierKeys And Keys.Control) = Keys.Control Then
        moving = True
        CanvasClickPoint = e.Location
        SelectorLocation = Point.Round(Canvas.ScalePoint(New PointF(Selector.Location.X, Selector.Location.Y)))
    End If

End Sub

Private Sub Canvas_MouseMove(sender As Object, e As MouseEventArgs) Handles Canvas.MouseMove

    If moving Then

        Dim CanvasNewPoint As Point = New Point(Canvas.Left + (e.Location.X - CanvasClickPoint.X),
                                                  Canvas.Top + (e.Location.Y - CanvasClickPoint.Y))

        Canvas.Location = CanvasNewPoint

    End If
End Sub

Private Sub Canvas_MouseUp(sender As Object, e As MouseEventArgs) Handles Canvas.MouseUp
    moving = False
    Canvas.Invalidate()
End Sub

End Class

【讨论】:

以上是关于自定义 Winforms 设计器控件同时缩放和平移控件的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义控件篇 图片进行平移,缩放,旋转

WinForms 不同的 DPI 布局

Winforms:在设计器中创建对象并对其进行自定义

百度地图之控件使用

在 VS2017 Winforms 应用程序中存在 IDE 缩放问题

如何在winforms设计器中公开用户控件的整个子控件