在运行时如何调整WPF网格中控件的大小(保持长宽比)

6
我目前正在开发一个C# WPF应用程序,其中包含一个全屏的Grid,其中的控件在运行时动态添加。我已经编写了代码,允许用户使用鼠标事件将这些控件移动到Grid上。现在我想让用户在运行时也可以调整控件的大小(同时保持宽高比)。我看过一些使用Canvas(和Thumb控件)实现此功能的教程,但没有使用Grid的教程。由于我的应用程序中不能使用Canvas,是否有一种有效的方法在Grid上实现此功能?为了让您了解我的代码是什么样子的,我在下面放置了我的鼠标事件: [以下已编辑]

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    //Orientation variables:
    public static Point _anchorPoint;
    public static Point _currentPoint;
    private static double _originalTop;
    private static double _originalLeft;
    private static Point _startPoint;
    private static bool _isDown = false;
    private static bool _isInDrag = false;
    private static bool _isDragging = false;
    public static UIElement selectedElement = null;
    public static bool elementIsSelected = false;
    public static Dictionary<object, TranslateTransform> PointDict = new Dictionary<object, TranslateTransform>();
    public static AdornerLayer aLayer;

    public MainWindow()
    {
        InitializeComponent();
    }
    //Control events:
    public static void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) //HUD element left mouse button up
    {       
        if (_isInDrag)
        {
            var element = sender as FrameworkElement;
            element.ReleaseMouseCapture();
            _isInDrag = false;
            e.Handled = true;
            aLayer = AdornerLayer.GetAdornerLayer(selectedElement);
            aLayer.Add(new ResizingAdorner(selectedElement));
        }
    }
    public static void HUD_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (elementIsSelected)
            {
                elementIsSelected = false;
                _isDown = false;
                if (selectedElement != null)
                {
                    aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
                    selectedElement = null;
                }
            }
            if (e.Source != mw.PACSGrid)
            {
                _isDown = true;
                _startPoint = e.GetPosition(mw.PACSGrid);
                selectedElement = e.Source as UIElement;
                _originalLeft = VisualWorker.GetLeft(selectedElement);
                _originalTop = VisualWorker.GetTop(selectedElement);
                aLayer = AdornerLayer.GetAdornerLayer(selectedElement);
                aLayer.Add(new ResizingAdorner(selectedElement));
                elementIsSelected = true;
                e.Handled = true;
            }
    }
    public static void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) //HUD element left mouse button down
    {
        if (elementIsSelected)
            {
                aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
                selectedElement = sender as UIElement;
                var element = sender as FrameworkElement;
                _anchorPoint = e.GetPosition(null);
                element.CaptureMouse();
                _isInDrag = true;
                e.Handled = true;
            }
    }
    public static void Control_MouseMove(object sender, MouseEventArgs e) //Drag & drop HUD element
    {
        if (_isInDrag) // The user is currently dragging the HUD element...
        {
            _currentPoint = e.GetPosition(null);
            TranslateTransform tt = new TranslateTransform();
            bool isMoved = false;
            if (PointDict.ContainsKey(sender))
            {
                tt = PointDict[sender];
                isMoved = true;
            }
            tt.X += _currentPoint.X - _anchorPoint.X;
            tt.Y += (_currentPoint.Y - _anchorPoint.Y);
            _anchorPoint = _currentPoint;
            (sender as UIElement).RenderTransform = tt;
            if (isMoved)
            {
                PointDict.Remove(sender);
            }
            PointDict.Add(sender, tt);       
        }
    }
}
    // Adorner Class:
public class ResizingAdorner : Adorner
{
    Thumb topLeft, topRight, bottomLeft, bottomRight;

    // To store and manage the adorner's visual children.
    VisualCollection visualChildren;

    // Initialize the ResizingAdorner.
    public ResizingAdorner(UIElement adornedElement)
        : base(adornedElement)
    {
        visualChildren = new VisualCollection(this);

        // Call a helper method to initialize the Thumbs
        // with a customized cursors.
        BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);
        BuildAdornerCorner(ref topRight, Cursors.SizeNESW);
        BuildAdornerCorner(ref bottomLeft, Cursors.SizeNESW);
        BuildAdornerCorner(ref bottomRight, Cursors.SizeNWSE);

        // Add handlers for resizing.
        bottomLeft.DragDelta += new DragDeltaEventHandler(HandleBottomLeft);
        bottomRight.DragDelta += new DragDeltaEventHandler(HandleBottomRight);
        topLeft.DragDelta += new DragDeltaEventHandler(HandleTopLeft);
        topRight.DragDelta += new DragDeltaEventHandler(HandleTopRight);
    }

    // Handler for resizing from the bottom-right.
    void HandleBottomRight(object sender, DragDeltaEventArgs args)
    {
        FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
        Thumb hitThumb = sender as Thumb;

        if (adornedElement == null || hitThumb == null) return;
        FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;

        // Ensure that the Width and Height are properly initialized after the resize.
        EnforceSize(adornedElement);

        // Change the size by the amount the user drags the mouse, as long as it's larger 
        // than the width or height of an adorner, respectively.
        adornedElement.Width = Math.Max(adornedElement.Width + args.HorizontalChange, hitThumb.DesiredSize.Width);
        adornedElement.Height = Math.Max(args.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height);
    }

    // Handler for resizing from the top-right.
    void HandleTopRight(object sender, DragDeltaEventArgs args)
    {
        FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
        Thumb hitThumb = sender as Thumb;

        if (adornedElement == null || hitThumb == null) return;
        FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;

        // Ensure that the Width and Height are properly initialized after the resize.
        EnforceSize(adornedElement);

        // Change the size by the amount the user drags the mouse, as long as it's larger 
        // than the width or height of an adorner, respectively.
        adornedElement.Width = Math.Max(adornedElement.Width + args.HorizontalChange, hitThumb.DesiredSize.Width);
        //adornedElement.Height = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);

        double height_old = adornedElement.Height;
        double height_new = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
        double top_old = VisualWorker.GetTop(adornedElement);
        //double top_old = Canvas.GetTop(adornedElement);
        adornedElement.Height = height_new;
        //Canvas.SetTop(adornedElement, top_old - (height_new - height_old));
        VisualWorker.SetTop(adornedElement, top_old - (height_new - height_old));
    }

    // Handler for resizing from the top-left.
    void HandleTopLeft(object sender, DragDeltaEventArgs args)
    {
        FrameworkElement adornedElement = AdornedElement as FrameworkElement;
        Thumb hitThumb = sender as Thumb;

        if (adornedElement == null || hitThumb == null) return;

        // Ensure that the Width and Height are properly initialized after the resize.
        EnforceSize(adornedElement);

        // Change the size by the amount the user drags the mouse, as long as it's larger 
        // than the width or height of an adorner, respectively.
        //adornedElement.Width = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
        //adornedElement.Height = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);

        double width_old = adornedElement.Width;
        double width_new = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
        double left_old = VisualWorker.GetLeft(adornedElement);
        //double left_old = Canvas.GetLeft(adornedElement);
        adornedElement.Width = width_new;
        VisualWorker.SetLeft(adornedElement, left_old - (width_new - width_old));

        double height_old = adornedElement.Height;
        double height_new = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
        double top_old = VisualWorker.GetTop(adornedElement);
        //double top_old = Canvas.GetTop(adornedElement);
        adornedElement.Height = height_new;
        //Canvas.SetTop(adornedElement, top_old - (height_new - height_old));
        VisualWorker.SetTop(adornedElement, top_old - (height_new - height_old));
    }

    // Handler for resizing from the bottom-left.
    void HandleBottomLeft(object sender, DragDeltaEventArgs args)
    {
        FrameworkElement adornedElement = AdornedElement as FrameworkElement;
        Thumb hitThumb = sender as Thumb;

        if (adornedElement == null || hitThumb == null) return;

        // Ensure that the Width and Height are properly initialized after the resize.
        EnforceSize(adornedElement);

        // Change the size by the amount the user drags the mouse, as long as it's larger 
        // than the width or height of an adorner, respectively.
        //adornedElement.Width = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
        adornedElement.Height = Math.Max(args.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height);

        double width_old = adornedElement.Width;
        double width_new = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
        double left_old = VisualWorker.GetLeft(adornedElement);
        //double left_old = Canvas.GetLeft(adornedElement);
        adornedElement.Width = width_new;
        //Canvas.SetLeft(adornedElement, left_old - (width_new - width_old));
        VisualWorker.SetLeft(adornedElement, left_old - (width_new - width_old));
    }

    // Arrange the Adorners.
    protected override Size ArrangeOverride(Size finalSize)
    {
        // desiredWidth and desiredHeight are the width and height of the element that's being adorned.  
        // These will be used to place the ResizingAdorner at the corners of the adorned element.  
        double desiredWidth = AdornedElement.DesiredSize.Width;
        double desiredHeight = AdornedElement.DesiredSize.Height;
        // adornerWidth & adornerHeight are used for placement as well.
        double adornerWidth = this.DesiredSize.Width;
        double adornerHeight = this.DesiredSize.Height;

        topLeft.Arrange(new Rect(-adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
        topRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
        bottomLeft.Arrange(new Rect(-adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
        bottomRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));

        // Return the final size.
        return finalSize;
    }

    // Helper method to instantiate the corner Thumbs, set the Cursor property, 
    // set some appearance properties, and add the elements to the visual tree.
    void BuildAdornerCorner(ref Thumb cornerThumb, Cursor customizedCursor)
    {
        if (cornerThumb != null) return;

        cornerThumb = new Thumb();

        // Set some arbitrary visual characteristics.
        cornerThumb.Cursor = customizedCursor;
        cornerThumb.Height = cornerThumb.Width = 10;
        cornerThumb.Opacity = 1;
        cornerThumb.Background = new ImageBrush(new BitmapImage(new Uri(@"pack://application:,,,/Images/Thumb 1.jpg")));
        visualChildren.Add(cornerThumb);
    }

    // This method ensures that the Widths and Heights are initialized.  Sizing to content produces
    // Width and Height values of Double.NaN.  Because this Adorner explicitly resizes, the Width and Height
    // need to be set first.  It also sets the maximum size of the adorned element.
    void EnforceSize(FrameworkElement adornedElement)
    {
        if (adornedElement.Width.Equals(Double.NaN))
            adornedElement.Width = adornedElement.DesiredSize.Width;
        if (adornedElement.Height.Equals(Double.NaN))
            adornedElement.Height = adornedElement.DesiredSize.Height;

        FrameworkElement parent = adornedElement.Parent as FrameworkElement;
        if (parent != null)
        {
            adornedElement.MaxHeight = parent.ActualHeight;
            adornedElement.MaxWidth = parent.ActualWidth;
        }
    }
    // Override the VisualChildrenCount and GetVisualChild properties to interface with 
    // the adorner's visual collection.
    protected override int VisualChildrenCount { get { return visualChildren.Count; } }
    protected override Visual GetVisualChild(int index) { return visualChildren[index]; }
}
// Canvas alternative class:
public class VisualWorker
{
    public static void SetTop(UIElement uie, double top)
    {
        var frame = uie as FrameworkElement;
        frame.Margin = new Thickness(frame.Margin.Left, top, frame.Margin.Right, frame.Margin.Bottom);
    }
    public static void SetLeft(UIElement uie, double left)
    {
        var frame = uie as FrameworkElement;
        frame.Margin = new Thickness(left, frame.Margin.Top, frame.Margin.Right, frame.Margin.Bottom);
    }
    public static double GetTop(UIElement uie)
    {
        return (uie as FrameworkElement).Margin.Top;
    }
    public static double GetLeft(UIElement uie)
    {
        return (uie as FrameworkElement).Margin.Left;
    }
}

MainWindow.xaml(示例):

<Window x:Name="MW" x:Class="MyProgram.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyProgram"
mc:Ignorable="d"
Title="MyProgram" d:DesignHeight="1080" d:DesignWidth="1920" ResizeMode="NoResize" WindowState="Maximized" WindowStyle="None" MouseLeave="HUD_MouseLeave">

    <Grid x:Name="MyGrid MouseDown="HUD_MouseDown" />
         <Image x:Name="Image1" Source="pic.png" Margin="880,862,0,0" Height="164" Width="162" HorizontalAlignment="Left" VerticalAlignment="Top" MouseLeftButtonDown="Control_MouseLeftButtonDown" MouseLeftButtonUp="Control_MouseLeftButtonUp" MouseMove="Control_MouseMove" />
         <TextBox x:Name="Textbox1" Margin="440,560,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" MouseLeftButtonDown="Control_MouseLeftButtonDown" MouseLeftButtonUp="Control_MouseLeftButtonUp" MouseMove="Control_MouseMove" />

编辑:我发现使用TranslateTransform不会改变控件的边距。为了使其正常工作,我需要相应地更改边距。

编辑2:现在我已经添加了一个调整大小的装饰器的修改代码(在这里找到)。它几乎实际上可以工作。问题是我遇到了奇怪的装饰器行为。在我的一个控件中(在左上角),所有4个装饰器都出现在它们适当的角落。但是我的其他控件只有1-2个装饰器,并且它们的间距不正确。那些的行为也更奇怪。是的,我意识到这是很多代码,但我认为问题可能在ResizingAdorner类中。

编辑3:添加了“样板”代码,供那些想要复制和粘贴的人使用。它应该可以编译而没有任何问题。如果你有任何问题,请告诉我。

编辑4(2018年1月10日):仍然没有好的答案。看起来,装饰器上的Thumb控件只有在控件的Margin为0,0时才能正确对齐。当从该位置移动时,装饰器会与元素间隔开。

编辑5(2018年1月15日):装饰器类最初是为Canvas设计的,在Grid上运行可能会导致问题。我最好的猜测是由于此原因导致了ArrangeOverride方法出现问题(这是放置UIElement的拇指的位置)。


你的问题太过宽泛。请尝试实现一些东西,如果遇到了某些具体问题,请告诉我们。 - dymanoid
@dymanoid,我问这个问题是因为我已经做了研究,但没有找到任何可行的解决方案。我提供了我的事件代码,任何知道该怎么做的人都可以指导我正确的方向。 - Luke Dinkler
为什么不能使用Canvas?它是这种任务的好面板。 - Netstep
1
@Netstep 是的,但是我需要更具响应性和高性能的网格。 - Luke Dinkler
@LukeDinkler 你为什么认为网格比画布更具响应性? - Aman Seth
显示剩余2条评论
2个回答

1

调整大小:

也许这是一个入门...

XAML:

<Grid x:Name="Content">
<Border HorizontalAlignment="Left" VerticalAlignment="Top" Background="Blue" Width="20" Height="20" x:Name="BorderToResize"/>
<Border HorizontalAlignment="Left" VerticalAlignment="Top" Background="Red" Width="10" Height="10" MouseLeftButtonDown="OnLeftMouseButtonDown" MouseLeftButtonUp="OnLeftMouseButtonUp" MouseMove="OnMouseMove" x:Name="BorderThumb">
    <Border.RenderTransform>
        <TranslateTransform X="15" Y="15" />
    </Border.RenderTransform>
</Border>

代码后台:

   private void OnLeftMouseButtonDown(object sender, MouseButtonEventArgs e) {
        (sender as UIElement).CaptureMouse();
    }

    private void OnLeftMouseButtonUp(object sender, MouseButtonEventArgs e) {
        (sender as UIElement).ReleaseMouseCapture();
    }

    private void OnMouseMove(object sender, MouseEventArgs e) {
        if ((sender as UIElement).IsMouseCaptureWithin) {
            var pos = e.GetPosition(Content);
            BorderThumb.RenderTransform = new TranslateTransform(pos.X, pos.Y);
            BorderToResize.Height = pos.Y;
            BorderToResize.Width = pos.X;
        }
    }

TranslateTransform由于某些原因不会更改边距(我需要更改)。虽然不确定还有什么其他方法可用,但移动功能正常。我只需要一个调整大小的解决方案。 - Luke Dinkler
抱歉,我没有看到调整大小的问题,我只是看了代码。但在画布上调整大小与在网格上调整大小并没有什么不同。您需要一个装饰器(拇指)或一个区域来单击和调整大小UI元素,然后只需设置新的高度和宽度,保持原始纵横比恒定......像这样的东西也适用于网格父级:https://denisvuyka.wordpress.com/2007/10/15/wpf-simple-adorner-usage-with-drag-and-resize-operations/ - Markus
我的印象是装饰器只用于画布上。你有网格的代码吗? - Luke Dinkler
1
实际上,我也只在画布上使用了装饰器... 我更新了我的答案,用另一个边框作为拇指来调整边框的大小。 - Markus

0
今天经过一些尝试和错误,我终于找到了如何修复Adorner错误的方法。正如我所发布的那样,用于调整元素大小的角落拇指没有正确地对齐到控件上。原始代码是为在Canvas上使用而不是我正在使用的Grid容器而设计的。问题出在ArrangeOverride方法中的一些double值(该方法排列Thumbs)。原始代码如下:

double desiredWidth = AdornedElement.DesiredSize.Width;
double desiredHeight = AdornedElement.DesiredSize.Height;
// adornerWidth & adornerHeight are used for placement as well.
double adornerWidth = this.DesiredSize.Width;
double adornerHeight = this.DesiredSize.Height;
//Arrange method calls below....

...我修改成了:

double desiredWidth = (AdornedElement as FrameworkElement).ActualWidth;
double desiredHeight = (AdornedElement as FrameworkElement).ActualHeight;
// adornerWidth & adornerHeight are used for placement as well.
double adornerWidth = (AdornedElement as FrameworkElement).Width;
double adornerHeight = (AdornedElement as FrameworkElement).Height;
//Arrange the thumbs:
topLeft.Arrange(new Rect(-adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
topRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
bottomLeft.Arrange(new Rect(-adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
bottomRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));

似乎用来排列 ThumbdesiredWidthdesiredHeight 双变量并没有提供控件的实际尺寸。使用这个更改后的代码是可行的,但对于左上角、右上角和左下角的装饰器仍然非常有故障。我相信我可以自己调整这个问题。感谢所有提供反馈意见的人。

原始(canvas)代码链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接