绘图在WPF中通过拖放而呈弧形

| 我正在尝试执行一种拖放方法来在图中创建关系,这直接类似于SQL Server Management Studio图表工具。例如,在下图中,用户将“ 0”从“ 1”实体拖动到“ 2”实体并在两者之间创建外键关系。 关键的期望功能是在用户跟随鼠标执行拖动操作时绘制一条临时弧形路径。创建实体后移动实体或关系不是我遇到的问题。 与上图中的实体相对应的一些参考XAML:
<!-- Entity diagram control -->
<Grid MinWidth=\"10\" MinHeight=\"10\" Margin=\"2\">
    <Grid.RowDefinitions>
        <RowDefinition Height=\"Auto\"></RowDefinition>
        <RowDefinition Height=\"*\" ></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width=\"*\"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid Grid.Row=\"0\" Grid.Column=\"0\" IsHitTestVisible=\"False\" Background=\"{StaticResource ControlDarkBackgroundBrush}\">
        <Label Grid.Row=\"0\" Grid.Column=\"0\" Style=\"{DynamicResource LabelDiagram}\" Content=\"{Binding DiagramHeader, Mode=OneWay}\" />
    </Grid>
    <ScrollViewer Grid.Row=\"1\" Grid.Column=\"0\" VerticalScrollBarVisibility=\"Auto\" HorizontalScrollBarVisibility=\"Auto\" Background=\"{StaticResource ControlBackgroundBrush}\" >
        <StackPanel VerticalAlignment=\"Top\">
            <uent:EntityDataPropertiesDiagramControl DataContext=\"{Binding EntityDataPropertiesFolder}\" />
            <uent:CollectionEntityPropertiesDiagramControl DataContext=\"{Binding CollectionEntityPropertiesFolder}\" />
            <uent:DerivedEntityDataPropertiesDiagramControl DataContext=\"{Binding DerivedEntityDataPropertiesFolder}\" />
            <uent:ReferenceEntityPropertiesDiagramControl DataContext=\"{Binding ReferenceEntityPropertiesFolder}\" />
            <uent:MethodsDiagramControl DataContext=\"{Binding MethodsFolder}\" />
        </StackPanel>
    </ScrollViewer>
    <Grid Grid.RowSpan=\"2\" Margin=\"-10\">
        <lib:Connector x:Name=\"LeftConnector\" Orientation=\"Left\" VerticalAlignment=\"Center\" HorizontalAlignment=\"Left\" Visibility=\"Collapsed\"/>
        <lib:Connector x:Name=\"TopConnector\" Orientation=\"Top\" VerticalAlignment=\"Top\" HorizontalAlignment=\"Center\" Visibility=\"Collapsed\"/>
        <lib:Connector x:Name=\"RightConnector\" Orientation=\"Right\" VerticalAlignment=\"Center\" HorizontalAlignment=\"Right\" Visibility=\"Collapsed\"/>
        <lib:Connector x:Name=\"BottomConnector\" Orientation=\"Bottom\" VerticalAlignment=\"Bottom\" HorizontalAlignment=\"Center\" Visibility=\"Collapsed\"/>
    </Grid>
</Grid>
我目前执行此操作的方法是: 1)在实体的子控件中启动拖动操作,例如:
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed)
    {
        dragStartPoint = null;
    }
    else if (dragStartPoint.HasValue)
    {
        Point? currentPosition = new Point?(e.GetPosition(this));
        if (currentPosition.HasValue && (Math.Abs(currentPosition.Value.X - dragStartPoint.Value.X) > 10 || Math.Abs(currentPosition.Value.Y - dragStartPoint.Value.Y) > 10))
        {
            DragDrop.DoDragDrop(this, DataContext, DragDropEffects.Link);
            e.Handled = true;
        }
    }
}
2)当拖动操作离开实体时,创建一个连接器装饰物,例如:
protected override void OnDragLeave(DragEventArgs e)
{
    base.OnDragLeave(e);
    if (ParentCanvas != null)
    {
        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(ParentCanvas);
        if (adornerLayer != null)
        {
            ConnectorAdorner adorner = new ConnectorAdorner(ParentCanvas, BestConnector);
            if (adorner != null)
            {
                adornerLayer.Add(adorner);
                e.Handled = true;
            }
        }
    }
}
3)在连接器装饰器中移动鼠标时绘制弧形路径,例如:
    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            if (!IsMouseCaptured) CaptureMouse();
            HitTesting(e.GetPosition(this));
            pathGeometry = GetPathGeometry(e.GetPosition(this));
            InvalidateVisual();
        }
        else
        {
            if (IsMouseCaptured) ReleaseMouseCapture();
        }
    }
图“ 7”绑定到视图模型,“ 7”上的实体和关系又绑定到相应的视图模型。一些与总体图有关的XAML:
<ItemsControl ItemsSource=\"{Binding Items, Mode=OneWay}\">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <lib:DesignerCanvas VirtualizingStackPanel.IsVirtualizing=\"True\" VirtualizingStackPanel.VirtualizationMode=\"Recycling\"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property=\"Canvas.Left\" Value=\"{Binding X}\"/>
            <Setter Property=\"Canvas.Top\" Value=\"{Binding Y}\"/>
            <Setter Property=\"Canvas.Width\" Value=\"{Binding Width}\"/>
            <Setter Property=\"Canvas.Height\" Value=\"{Binding Height}\"/>
            <Setter Property=\"Canvas.ZIndex\" Value=\"{Binding ZIndex}\"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>
实体和关系的价格为10英镑:
<!-- diagram relationship -->
<DataTemplate DataType=\"{x:Type dvm:DiagramRelationshipViewModel}\">
    <lib:Connection />
</DataTemplate>
<!-- diagram entity -->
<DataTemplate DataType=\"{x:Type dvm:DiagramEntityViewModel}\">
    <lib:DesignerItem>
        <lib:EntityDiagramControl />
    </lib:DesignerItem>
</DataTemplate>
问题:问题在于,一旦开始拖动操作,就不再跟踪鼠标的移动,并且连接器装饰器无法像在其他情况下那样绘制圆弧。如果释放鼠标并再次单击,则弧开始绘制,但是随后我丢失了源对象。我正在尝试找到一种结合鼠标移动来传递源对象的方法。 赏金:绕开这个问题,我目前不打算直接使用拖放操作来做到这一点。我目前计划为图表控件添加一个DragItem和IsDragging
DependencyProperty
,该控件将容纳要拖动的项目,并在发生拖动操作时进行标记。然后,我可以使用
DataTrigger
来基于IsDragging更改
Cursor
Adorner
可见性,并可以使用DragItem进行放置操作。 (但是,我希望奖励另一种有趣的方法。请发表评论,如果需要更多信息或代码来阐明此问题。) 编辑:较低的优先级,但我仍在寻找更好的拖放图表方法解决方案。希望在开源Mo + Solution Builder中实现更好的方法。     
已邀请:
这是一个相当复杂的答案。让我知道它的任何部分不清楚。 我目前正在尝试解决类似的问题。就我而言,我想将我的ListBox ItemsSource绑定到一个集合,然后将该集合中的每个项目表示为节点(即可拖动对象)或连接(即节点之间的线),该线在节点被拖动时重绘自身。我将向您展示我的代码和详细信息,我认为您可能需要进行更改以满足自己的需求。 拖曳 拖动是通过设置
Dragger
类拥有的附加属性来完成的。在我看来,这与使用ѭ17进行拖动相比具有优势,因为使对象可拖动不涉及更改其控制模板。我的第一个实现实际上是在控制模板中使用ѭ17来实现拖动,但是我发现这样做使我的应用程序非常脆弱(添加新功能通常会破坏拖动)。这是Dragger的代码:
public static class Dragger
    {
        private static FrameworkElement currentlyDraggedElement;
        private static FrameworkElement CurrentlyDraggedElement
        {
            get { return currentlyDraggedElement; } 
            set
            {
                currentlyDraggedElement = value;
                if (CurrentlyDraggedElement != null)
                {
                    CurrentlyDraggedElement.MouseMove += new MouseEventHandler(CurrentlyDraggedElement_MouseMove);
                    CurrentlyDraggedElement.MouseLeftButtonUp +=new MouseButtonEventHandler(CurrentlyDraggedElement_MouseLeftButtonUp);
                }
            }           
        }

        private static ItemPreviewAdorner adornerForDraggedItem;
        private static ItemPreviewAdorner AdornerForDraggedItem
        {
            get { return adornerForDraggedItem; }
            set { adornerForDraggedItem = value; }
        }

        #region IsDraggable

        public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.RegisterAttached(\"IsDraggable\", typeof(Boolean), typeof(Dragger),
            new FrameworkPropertyMetadata(IsDraggable_PropertyChanged));

        public static void SetIsDraggable(DependencyObject element, Boolean value)
        {
            element.SetValue(IsDraggableProperty, value);
        }
        public static Boolean GetIsDraggable(DependencyObject element)
        {
            return (Boolean)element.GetValue(IsDraggableProperty);
        }

        #endregion

        #region IsDraggingEvent

        public static readonly RoutedEvent IsDraggingEvent = EventManager.RegisterRoutedEvent(\"IsDragging\", RoutingStrategy.Bubble,
            typeof(RoutedEventHandler), typeof(Dragger));

        public static event RoutedEventHandler IsDragging;

        public static void AddIsDraggingHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement uie = d as UIElement;
            if (uie != null)
            {
                uie.AddHandler(Dragger.IsDraggingEvent, handler);
            }
        }

        public static void RemoveIsDraggingEventHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement uie = d as UIElement;
            if (uie != null)
            {
                uie.RemoveHandler(Dragger.IsDraggingEvent, handler);
            }
        }

        #endregion

        public static void IsDraggable_PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if ((bool)args.NewValue == true)
            {
                FrameworkElement element = (FrameworkElement)obj;
                element.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(itemToBeDragged_MouseLeftButtonDown);
            }
        }

        private static void itemToBeDragged_MouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            var element = sender as FrameworkElement;
            if (element != null)
            {                
                CurrentlyDraggedElement = element;
            }           
        }

        private static void CurrentlyDraggedElement_MouseMove(object sender, MouseEventArgs e)
        {
            var element = sender as FrameworkElement;
            if (element.IsEnabled == true)
            {
                element.CaptureMouse();
                //RaiseIsDraggingEvent();
                DragObject(sender, new Point(Mouse.GetPosition(PavilionVisualTreeHelper.GetAncestor(element, typeof(CustomCanvas)) as CustomCanvas).X,
                    Mouse.GetPosition(PavilionVisualTreeHelper.GetAncestor(element, typeof(CustomCanvas)) as CustomCanvas).Y));
            }         
        }

        private static void CurrentlyDraggedElement_MouseLeftButtonUp(object sender, MouseEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            element.MouseMove -= new MouseEventHandler(CurrentlyDraggedElement_MouseMove);
            element.ReleaseMouseCapture();
            CurrentlyDraggedElement = null;
        }

        private static void DragObject(object sender, Point startingPoint)
        {
            FrameworkElement item = sender as FrameworkElement;

            if (item != null)
            {
                var canvas = PavilionVisualTreeHelper.GetAncestor(item, typeof(CustomCanvas)) as CustomCanvas;

                double horizontalPosition = Mouse.GetPosition(canvas).X - item.ActualWidth/2;
                double verticalPosition = Mouse.GetPosition(canvas).Y - item.ActualHeight/2;

                item.RenderTransform = ReturnTransFormGroup(horizontalPosition, verticalPosition);
                item.RaiseEvent(new IsDraggingRoutedEventArgs(item, new Point(horizontalPosition, verticalPosition), IsDraggingEvent));
            }
        }

        private static TransformGroup ReturnTransFormGroup(double mouseX, double mouseY)
        {
            TransformGroup transformGroup = new TransformGroup();
            transformGroup.Children.Add(new TranslateTransform(mouseX, mouseY));
            return transformGroup;
        }
    }

    public class IsDraggingRoutedEventArgs : RoutedEventArgs
    {
        public Point LocationDraggedTo { get; set;}
        public FrameworkElement ElementBeingDragged { get; set; }

        public IsDraggingRoutedEventArgs(DependencyObject elementBeingDragged, Point locationDraggedTo, RoutedEvent routedEvent)
            : base(routedEvent)
        {
            this.ElementBeingDragged = elementBeingDragged as FrameworkElement;
            LocationDraggedTo = locationDraggedTo;            
        }
    }
我相信
Dragger
要求对象位于
Canvas
CustomCanvas
上,但是除了懒惰之外,没有任何其他充分的理由。您可以轻松地对其进行修改以适用于任何面板。 (这是我的待办事项!)。
Dragger
类也使用
PavilionVisualTreeHelper.GetAncestor()
辅助方法,该方法只是爬上可视树以查找适当的元素。下面的代码。
 /// <summary>
    /// Gets ancestor of starting element
    /// </summary>
    /// <param name=\"parentType\">Desired type of ancestor</param>
    public static DependencyObject GetAncestor(DependencyObject startingElement, Type parentType)
    {
        if (startingElement == null || startingElement.GetType() == parentType)
            return startingElement;
        else
            return GetAncestor(VisualTreeHelper.GetParent(startingElement), parentType);
    }
消费
Dragger
类非常简单。只需在相应控件的xaml标记中设置“ 27”即可。 (可选)您可以注册到
Dragger.IsDragging
事件(该事件从被拖动的元素起泡),以执行您可能需要的任何处理。 更新连接位置 我通知连接需要重新绘制的机制有些草率,而且绝对需要重新处理。 连接包含两个FrameworkElement类型的DependencyProperty:开始和结束。在PropertyChangedCallbacks中,我尝试将它们强制转换为DragAwareListBoxItems(我需要将其设置为一个接口,以提高可重用性)。如果转换成功,我将注册到
DragAwareListBoxItem.ConnectionDragging
事件。 (坏名字,不是我的!)。当该事件触发时,连接将重绘其路径。 DragAwareListBoxItem实际上并不知道何时拖动它,因此必须告诉别人。由于ListBoxItem在我的视觉树中的位置,因此它从未听见
Dragger.IsDragging
事件。为了告诉它正在被拖动,ListBox会侦听该事件并通知适当的DragAwareListBoxItem。 将会发布
Connection
DragAwareListBoxItem
ListBox_IsDragging
的代码,但我认为在这里难以理解。您可以在http://code.google.com/p/pavilion/source/browse/#hg%2FPavilionDesignerTool%2FPavilion.NodeDesigner中检出项目。 或使用hg clone https://code.google.com/p/pavilion/克隆存储库。这是MIT许可下的一个开源项目,因此您可以根据自己的需要进行调整。作为警告,没有稳定的版本,因此可以随时更改。 连接性 与连接更新一样,我不会粘贴代码。相反,我将告诉您项目中要检查的类以及在每个类中要查找的内容。 从用户的角度来看,这是创建连接的工作方式。用户在节点上单击鼠标右键。这将弹出一个上下文菜单,用户可以从中选择“创建新连接”。该选项将创建一条直线,其起点以选定节点为根,终点跟随鼠标。如果用户单击另一个节点,则会在两者之间创建连接。如果用户单击其他任何位置,则不会创建任何连接,该行也会消失。 此过程涉及两个类。
ConnectionManager
(实际上并不管理任何连接)存放附加属性。使用方控件将ConnectionManager.IsConnectable属性设置为true,并将ConnectionManager.MenuItemInvoker属性设置为应启动该过程的菜单项。此外,视觉树中的某些控件必须侦听ConnectionPending路由事件。这是实际创建连接的地方。 选择菜单项后,ConnectionManager将创建LineAdorner。 ConnectionManager监听LineAdorner LeftClick事件。触发该事件后,我将执行命中测试以找到所选的控件。然后,我引发ConnectionPending事件,将要在其之间创建连接的两个控件传递给事件args。活动的订阅者要实际完成这项工作。     
如上所述,我当前的方法是不直接使用拖放,而是结合使用ѭ35和处理鼠标事件来模拟拖放。 父图控件中的
DependencyProperties
是:
public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register(\"IsDragging\", typeof(bool), typeof(SolutionDiagramControl));
public bool IsDragging
{
    get
    {
        return (bool)GetValue(IsDraggingProperty);
    }
    set
    {
        SetValue(IsDraggingProperty, value);
    }
}

public static readonly DependencyProperty DragItemProperty = DependencyProperty.Register(\"DragItem\", typeof(IWorkspaceViewModel), typeof(SolutionDiagramControl));
public IWorkspaceViewModel DragItem
{
    get
    {
        return (IWorkspaceViewModel)GetValue(DragItemProperty);
    }
    set
    {
        SetValue(DragItemProperty, value);
    }
}
IsDragging
DependencyProperty
用于在发生拖动时触发光标更改,例如:
<Style TargetType=\"{x:Type lib:SolutionDiagramControl}\">
    <Style.Triggers>
        <Trigger Property=\"IsDragging\" Value=\"True\">
            <Setter Property=\"Cursor\" Value=\"Pen\" />
        </Trigger>
    </Style.Triggers>
</Style>
在需要执行
drag and drop
弧形绘制形式的任何地方,而不是调用
DragDrop.DoDragDrop
,我都将I43ѭ和
DragItem
设置为要拖动的源项目。 在鼠标离开时的实体控件中,启用了在拖动过程中绘制圆弧的连接器装饰器,例如:
protected override void OnMouseLeave(MouseEventArgs e)
{
    base.OnMouseLeave(e);
    if (ParentSolutionDiagramControl.DragItem != null)
    {
        CreateConnectorAdorner();
    }
}
图表控件必须在拖动过程中处理其他鼠标事件,例如:
protected override void OnMouseMove(MouseEventArgs e)
{
    base.OnMouseMove(e);
    if (e.LeftButton != MouseButtonState.Pressed)
    {
        IsDragging = false;
        DragItem = null;
    }
}
图表控件还必须在发生鼠标向上事件时处理\“ drop \”(并且它必须根据鼠标位置确定要放下哪个实体),例如:
protected override void OnMouseUp(MouseButtonEventArgs e)
{
    base.OnMouseUp(e);
    if (DragItem != null)
    {
        Point currentPosition = MouseUtilities.GetMousePosition(this);
        DiagramEntityViewModel diagramEntityView = GetMouseOverEntity(currentPosition );
        if (diagramEntityView != null)
        {
            // Perform the drop operations
        }
    }
    IsDragging = false;
    DragItem = null;
}
我仍在寻找更好的解决方案,以在进行拖动操作时在图表上绘制临时弧(跟随鼠标)。     
        我认为您将需要研究WPF Thumb控件。它将某些功能包装在一个方便的软件包中。 这是MSDN文档: http://msdn.microsoft.com/zh-CN/library/system.windows.controls.primitives.thumb.aspx 这是一个例子: http://denisvuyka.wordpress.com/2007/10/13/wpf-draggable-objects-and-simple-shape-connectors/ 不幸的是,我在这方面没有很多经验,但是我确实认为这是您想要的。祝好运!     

要回复问题请先登录注册