WPF DataBinding Canvas带拖放功能

时间:2014-05-13 20:23:26

标签: wpf binding

我有一个项目,我有一组项目,我想将Canvas绑定到。画布是子类,以提供拖动和调整元素大小的功能。拖动和调整大小的代码工作正常,但问题是我无法绑定画布。我发现我能做到:

<ItemsControl x:Name="Items" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <controls:SuperCanvas Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}" />
                <Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>

画布正确绑定,并且画布上显示了项目。但是我的子类画布不再正常工作。 (调整大小并拖动不再起作用。)

以下是SuperCanvas的代码:

sing System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using Meld.Helpers;

namespace Meld.Controls
{
    public class SuperCanvas : Canvas
    {
        private AdornerLayer aLayer;

        private bool isDown;
        private bool isDragging;
        private double originalLeft;
        private double originalTop;
        private bool selected;
        private UIElement selectedElement;

        private Point startPoint;

        private bool locked;

        public SuperCanvas()
        {
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            MouseLeftButtonDown += WorkspaceWindow_MouseLeftButtonDown;
            MouseLeftButtonUp += DragFinishedMouseHandler;
            MouseMove += WorkspaceWindow_MouseMove;
            MouseLeave += Window1_MouseLeave;

            PreviewMouseLeftButtonDown += myCanvas_PreviewMouseLeftButtonDown;
            PreviewMouseLeftButtonUp += DragFinishedMouseHandler;
        }

        // Handler for drag stopping on leaving the window
        private void Window1_MouseLeave(object sender, MouseEventArgs e)
        {
            StopDragging();
            e.Handled = true;
        }

        // Handler for drag stopping on user choice
        private void DragFinishedMouseHandler(object sender, MouseButtonEventArgs e)
        {
            StopDragging();
            e.Handled = true;
        }

        // Method for stopping dragging
        private void StopDragging()
        {
            if (isDown)
            {
                isDown = false;
                isDragging = false;
            }
        }

        // Hanler for providing drag operation with selected element
        private void WorkspaceWindow_MouseMove(object sender, MouseEventArgs e)
        {
            if (locked) return;

            if (isDown)
            {
                if ((isDragging == false) &&
                    ((Math.Abs(e.GetPosition(this).X - startPoint.X) >
                      SystemParameters.MinimumHorizontalDragDistance) ||
                     (Math.Abs(e.GetPosition(this).Y - startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)))
                    isDragging = true;

                if (isDragging)
                {
                    Point position = Mouse.GetPosition(this);
                    SetTop(selectedElement, position.Y - (startPoint.Y - originalTop));
                    SetLeft(selectedElement, position.X - (startPoint.X - originalLeft));
                }
            }
        }

        // Handler for clearing element selection, adorner removal
        private void WorkspaceWindow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (locked) return;

            if (selected)
            {
                selected = false;
                if (selectedElement != null)
                {
                    aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
                    selectedElement = null;
                }
            }
        }

        // Handler for element selection on the canvas providing resizing adorner
        private void myCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            //add code to lock dragging and dropping things.
            if (locked)
            {
                e.Handled = true;
                return;
            }

            // Remove selection on clicking anywhere the window
            if (selected)
            {
                selected = false;
                if (selectedElement != null)
                {
                    // Remove the adorner from the selected element
                    aLayer.Remove(aLayer.GetAdorners(selectedElement)[0]);
                    selectedElement = null;
                }
            }

            // If any element except canvas is clicked, 
            // assign the selected element and add the adorner
            if (e.Source != this)
            {
                isDown = true;
                startPoint = e.GetPosition(this);

                selectedElement = e.Source as UIElement;

                originalLeft = GetLeft(selectedElement);
                originalTop = GetTop(selectedElement);

                aLayer = AdornerLayer.GetAdornerLayer(selectedElement);
                aLayer.Add(new ResizingAdorner(selectedElement));
                selected = true;
                e.Handled = true;
            }
        }
    }
}

编辑:忘记调整大小的装饰

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace Meld.Helpers
{
    internal class ResizingAdorner : Adorner
    {
        // Resizing adorner uses Thumbs for visual elements.  
        // The Thumbs have built-in mouse input handling.
        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 = Canvas.GetTop(adornedElement);
            adornedElement.Height = height_new;
            Canvas.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 = Canvas.GetLeft(adornedElement);
            adornedElement.Width = width_new;
            Canvas.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 = Canvas.GetTop(adornedElement);
            adornedElement.Height = height_new;
            Canvas.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 = Canvas.GetLeft(adornedElement);
            adornedElement.Width = width_new;            
            Canvas.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 = 5;
            cornerThumb.Opacity = 0.40;
            cornerThumb.Background = new SolidColorBrush(Colors.White);

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

/ END编辑

任何想法为什么在上面的ItemsControl中使用它时不起作用以及如何修复它?

1 个答案:

答案 0 :(得分:4)

ItemsControl中的每个项目都由ContentPresenter包装。这是在拖放操作期间需要定位的元素,而不是包含的实际元素。您可以在指定ItemContainerStyle时看到这种情况:

    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}" />
            <Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}" />
        </Style>
    </ItemsControl.ItemContainerStyle>

在当前代码中,使用selectedMlement(其附加属性Canvas.Left和Canvas.Top不用于定位元素)在PreviewMouseLeftButtonDown事件中初始化originalLeft和originalTop值。在MouseMove事件中,为selectedElement设置了left和top值,这些值无效,因为它是必须定位的ContentPresenter。

要解决此问题,PreviewMouseLeftButtonDown处理程序应该(类似于):

selectedElement = e.Source as UIElement;

selectedPresenter = System.Windows.Media.VisualTreeHelper.GetParent(selectedElement) as ContentPresenter;

originalLeft = GetLeft(selectedPresenter);
originalTop = GetTop(selectedPresenter);

在MouseMove处理程序中,您需要使用selectedPresenter而不是selectedElement:

SetTop(selectedPresenter, position.Y - (startPoint.Y - originalTop));
SetLeft(selectedPresenter, position.X - (startPoint.X - originalLeft));

根据您发布的代码,我无法告诉您如何填充ItemsControl或者填充它的内容。为了自己测试,我有一个ObservableCollection绑定到ItemsControl.ItemsSource属性和一个ItemsControl.ItemTemplate,如下所示:

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Rectangle Width="100" Height="100" Fill="Blue"></Rectangle>
        </DataTemplate>
    </ItemsControl.ItemTemplate>        

由于数据模板是具有Width和Height属性的FrameworkElement,因此调整大小工作正常。但是,我必须对拖放进行上述更改才能正常工作。