簡體   English   中英

如何在運行時調整網格上的WPF控件(保持縱橫比)

[英]How to resize WPF controls on a grid at runtime (keeping aspect ratio)

我目前正在開發一個C#WPF應用程序,它包含一個全屏Grid ,其中包含在運行時動態添加的控件。 我有代碼允許用戶使用鼠標事件在Grid上移動這些控件。 我現在想要做的是允許用戶也在運行時調整控件的大小 (同時保持寬高比)。 我已經看過各種教程,描述如何使用Canvas (和Thumb控件)執行此操作,但在Grid上沒有。 由於我無法在我的應用程序中使用Canvas ,有沒有一種有效的方法在網格上實現它? 為了讓您了解我的代碼是什么樣的,我將鼠標事件放在下面:[編輯如下]

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(1/10/2018):仍然沒有好的答案。 如果控件的Margin為0,0,則裝飾器上的Thumb控件似乎只能正確對齊。 從該位置移動時,裝飾者會從元素中移出。

編輯5(1/15/2018):adorner類最初是為Canvas設計的,在Grid上運行它可能會導致問題。 我最好的猜測是, ArrangeOverride方法由於它而搞砸了(這是拇指放在UIElement )。

調整:

也許這是一個首發......

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

今天,經過一些試驗和錯誤,我能夠弄清楚如何修復Adorner錯誤。 正如我發布的那樣,用於調整元素大小的角拇指沒有正確對齊控件。 原始代碼旨在用於Canvas而不是我正在使用的Grid容器。 ArrangeOverride方法(排列Thumbs )中,錯誤是一些double值。 原始代碼是:

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雙變量沒有提供控件的實際尺寸。 使用具有此更改的代碼是有效的,但對於左上角,右上角和左下角的裝飾器仍然非常小問題。 這是我相信我可以調整自己的東西。 感謝所有提供反饋的人。

鏈接到原始(畫布)代碼

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM