如何在运行时调整网格上的WPF控件(保持纵横比)

Luk*_*ler 6 c# wpf events resize

我目前正在开发一个C#WPF应用程序,它包含一个Grid带有控件的全屏,这些控件在运行时动态添加到它.我有代码允许用户在Grid鼠标事件中移动这些控件.我现在想要做的是允许用户也在运行时调整控件的大小(同时保持宽高比).我已经看过各种教程,描述如何使用Canvas(和Thumb控件),但没有Grid.由于我无法Canvas在我的应用程序中使用a ,有没有一种有效的方法在网格上实现它?为了让您了解我的代码是什么样的,我将鼠标事件放在下面:[编辑如下]

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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" />
Run Code Online (Sandbox Code Playgroud)

编辑:我发现使用TranslateTransform不会更改控件的边距.我需要保证金才能适当改变才能使其发挥作用.

编辑2:现在我添加了[修改]代码,用于调整大小的装饰器(在此处找到).它几乎真的有效.问题是,我正在经历与装饰者的奇怪行为.在我的一个控件上(左上角),所有4个控件都显示在相应的角落中.但我的其他控件只能获得1-2个装饰,并且它们没有正确间隔.这些行为也很陌生.是的,我意识到这是很多代码,但我怀疑这个问题会出现在ResizingAdorner课堂上.

编辑3:为想要复制和粘贴的人添加了"样板"代码.它应该编译没有问题.如果您有任何问题,请告诉我.

编辑4(1/10/2018):仍然没有好的答案.Thumb如果控件的Margin值为0,0 ,则装饰器上的控件似乎只能正确对齐.从该位置移动时,装饰者会从元素中移出.

编辑5(1/15/2018):adorner类最初是为a Canvas而设计的,并且运行它Grid可能会导致问题.我最好的猜测是,这个ArrangeOverride方法因为它而搞砸了(这就是拇指放在它们身上的地方UIElement).

Luk*_*ler 0

今天,经过一番尝试和错误,我找到了修复该Adorner错误的方法。正如我所发布的,用于调整元素大小的角拇指未与控件正确对齐。原始代码旨在用于Canvas而不是Grid我正在使用的容器上。错误在于方法double中的某些值ArrangeOverride(安排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....
Run Code Online (Sandbox Code Playgroud)

...我修改为:

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));
Run Code Online (Sandbox Code Playgroud)

似乎专门用于排列s 的desiredWidth和double 变量没有提供控件的实际尺寸。使用带有此更改的代码是有效的,但对于左上角、右上角和左下角装饰器来说仍然很麻烦。我相信我可以调整自己。感谢所有提供反馈的人。desiredHeightThumb

链接到原始(画布)代码