2017-12-09 160 views
6

Aktualnie pracuję nad aplikacją C# WPF, która zawiera pełny ekran Grid z kontrolkami, które są dynamicznie dodawane do niej w czasie wykonywania. Mam kod na miejscu, aby umożliwić użytkownikowi przeniesienie tych elementów sterujących poprzez Grid za pomocą zdarzeń myszy. Teraz chciałbym pozwolić użytkownikowi na zmianę rozmiaru kontrolek (przy zachowaniu proporcji) również w czasie wykonywania. Widziałem różne samouczki opisujące, jak to zrobić przy użyciu formantów Canvas (i Thumb), ale żaden na Grid. Ponieważ nie mogę użyć aplikacji Canvas w mojej aplikacji, czy istnieje skuteczny sposób implementacji tego na siatce? Aby dać wyobrażenie o tym, co mój kod wygląda, umieściłem moje zdarzeń myszy poniżej: [Edytowane poniżej]Jak zmienić rozmiar elementów sterujących WPF na siatce w czasie wykonywania (zachowując współczynnik proporcji)?

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 (przykład):

<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" /> 

Edit: znalazłem, że za pomocą TranslateTransform nie zmienia marginesu formantu użytkownika. Potrzebuję marginesu, aby odpowiednio się zmienić, aby to zadziałało.

Edycja 2: Teraz dodałem kod [zmodyfikowany] dla adoratora zmiany rozmiaru (znaleziono here). To prawie działa. Problem polega na tym, że doświadczam dziwnych zachowań u adoratorów. Na jednej z moich kontrolek (w lewym górnym rogu) wszystkie 4 pojawiają się w odpowiednich rogach. Ale reszta kontrolek ma tylko 1-2 adorners i nie są one poprawnie rozmieszczone. Zachowanie z nimi jest również dziwniejsze. Tak, zdaję sobie sprawę, że to dużo kodu, ale podejrzewam, że problem byłby w klasie ResizingAdorner.

Edycja 3: Dodano kod "boilerplate" dla tych, którzy chcą skopiować pastę &. Powinien skompilować się bez żadnych problemów. Daj mi znać, jeśli masz jakiekolwiek problemy.

Edytuj 4 (1/10/2018): Nadal nie ma dobrej odpowiedzi. Wygląda na to, że kontrolki na adornerze Thumb wyrównują się tylko poprawnie, jeśli wartość kontrolna Margin wynosi 0,0. Po przejściu z tej pozycji, adoratorzy odgradzają się od elementu.

Edytuj 5 (1/15/2018): Klasa adornera została pierwotnie zaprojektowana dla wersji Canvas, a jej uruchomienie na serwerze Grid może przyczynić się do rozwiązania problemu. Domyślam się, że metoda ArrangeOverride została z tego powodu pomieszana (tutaj kciuki są umieszczane na ich UIElement).

+0

Twoje pytanie jest zbyt szerokie. Spróbuj coś zaimplementować i daj nam znać, jeśli napotkasz jakieś ** szczególne ** problemy. – dymanoid

+0

@dymanoid Zadałem to pytanie, ponieważ przeszedłem badania i nie znalazłem żadnego rozwiązania do wdrożenia w ogóle. Podałem kod dla moich wydarzeń, a każdy, kto wie, co robi, może wskazać mi właściwy kierunek. –

+0

Dlaczego nie możesz używać Canvas? To dobry panel do takich zadań. – Netstep

Odpowiedz

0

Dziś, po pewnym badaniu i erro r, udało mi się dowiedzieć, jak naprawić błąd Adorner. Jak napisałem, narożne Kciuki, które są używane do zmiany rozmiaru elementów, nie dostosowywały się właściwie do elementów sterujących.Oryginalny kod był przeznaczony do użytku na kontenerze Canvas, a nie na kontenerze Grid, którego używam. Błąd był w niektórych wartościach double w metodzie ArrangeOverride (która organizuje Thumbs). Oryginalny kod był:

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.... 

... co ja zmodyfikowane w:

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

Wydaje zmienne podwójne desiredWidth i desiredHeight wykorzystywane specjalnie zorganizować Thumb s nie zapewniając rzeczywiste wymiary dla kontrola. Używanie kodu z tą zmianą jest funkcjonalne, ale nadal dość uciążliwe dla lewych górnych, górnych prawych i dolnych lewych adorujących. To jest coś, co moim zdaniem mogę poprawić. Dziękujemy wszystkim, którzy udzielili informacji zwrotnej.

Link to original (canvas) code

1

Resize:

Może to rozrusznik ...

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> 

Code Behind:

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

Funkcja TranslateTransform nie zmienia marginesu z jakiegoś powodu (który jest mi potrzebny). Nie wiem, co jeszcze można użyć. Również przenoszenie działa dobrze. Potrzebuję tylko rozwiązania do zmiany rozmiaru. –

+0

Przepraszam, że nie czytałem pytania o zmianę rozmiaru. Właśnie obejrzałem kod. Ale zmiana rozmiaru nie powinna być inna na płótnie niż na siatce. Potrzebujesz Adornera (Thumb) lub obszaru, w którym chcesz kliknąć i zmienić rozmiar UCI, a następnie ustawić nową wysokość i szerokość zachowując oryginalny współczynnik proporcji ... coś takiego powinno również działać dla Grid Parent: https://denisvuyka.wordpress.com/2007/10/15/wpf-simple-adorner-usage-with-drag-and-resize-operations/ – Markus

+0

To było moje wrażenie, że adorners były używane tylko na Canvases. Czy masz kod do siatki? –