2011-06-15 83 views
4

我试图执行拖放方法来创建图表中的关系,直接与图表工具SQL Server Management Studio一致。例如,在下图中,用户将从User实体拖动CustomerIDCustomer实体,并在两者之间创建外键关系。在WPF中拖放的绘制图圆弧

关键的预期功能是当用户按照鼠标执行拖动操作时,绘制临时弧路径。移动的实体或关系一旦创建不是我遇到的问题。

Entity–relationship diagram

对应于实体上图上的一些参考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(); 
     } 
    } 

Canvas被绑定到视图模型,并且Canvas上的实体和关系又被绑定到相应的视图模型。与整体框图一些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> 

DataTemplate S为的entites和关系:

<!-- 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 s更改基于IsDragging的CursorAdorner可见性,并且可以使用DragItem进行放置操作。

(但是,我期待奖项的另一个有趣的方法赏金请评论,如果需要更多的信息或代码来澄清这个问题。)

编辑:较低的优先级,但是我仍然在寻找更好的解决方案来实现拖放图表方法。希望在开源Mo+ Solution Builder中实施更好的方法。

回答

2

如上所述,我目前的做法是不直接使用拖放,而是使用DependencyProperties的组合和处理鼠标事件来模仿拖放。

在父图控制的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); 
    } 
} 

IsDraggingDependencyProperty用于触发当拖动正在发生,诸如光标变化:

<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,我将IsDragging = trueDragItem设置为拖动的源项目d。

在上鼠标离开,连接器装饰器,其绘制在拖动期间电弧的实体控制被使能,如:

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

该图控制还必须处理“滴”在鼠标向上事件(和它必须找出哪些实体正被上根据鼠标位置丢弃),如:

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

我仍在寻找一种更好的解决方案,在拖动操作发生时在图上绘制临时弧(跟随鼠标)。

1

我想你会想看看WPF的拇指控制。它将这些功能包装在一个便利的包装中。

这里的MSDN文档:

http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.thumb.aspx

下面是一个例子:

http://denisvuyka.wordpress.com/2007/10/13/wpf-draggable-objects-and-simple-shape-connectors/

不幸的是我没有很多这方面的经验,但我不认为这是你在找什么。祝你好运!

+0

谢谢亚当。我确实使用了Thumb控件来移动实体和关系,并在渲染后调整实体的大小。在这种情况下,我不认为我可以使用大拇指,因为在拖动过程中正在绘制弧路径,然后将其作为控件放置。除非你知道一个窍门! – 2011-06-16 04:22:05

2

这是一个相当复杂的答案。让我知道它的任何部分是不是很清楚。

我目前正在尝试解决类似的问题。在我的情况下,我想将我的ListBox ItemsSource绑定到一个集合,然后将该集合中的每个项目表示为节点即可拖动对象或连接即在节点被拖动时重绘自身的节点之间的一条线。我将向您展示我的代码和详细信息,我认为您可能需要对其进行更改以适应您的需求。

拖动

拖动通过设定由Dragger类拥有附加属性来实现的。在我看来,这比使用MoveThumb执行拖动的优势在于使对象可拖动不涉及更改其控件模板。我的第一个实现实际上在控件模板中使用MoveThumb来实现拖动,但是我发现这样做使得我的应用程序变得非常脆弱(添加新功能常常会导致拖动)。下面是牵引机代码:

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要求对象是在CanvasCustomCanvas,但没有任何好的理由,除了lazyness,这一点。您可以轻松修改它以适用于任何面板。 (这是在我的积压!)。

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标记中设置Dragger.IsDraggable = true即可。或者,您可以注册到Dragger.IsDragging事件,该事件从正在拖动的元素中冒出来,以执行您可能需要的任何处理。

更新连接位置

我的通知,它需要重新绘制的连接机制是一点不马虎,绝对需要重新寻址。

Connection包含两个类型为FrameworkElement的DependencyProperties:Start和End。在PropertyChangedCallbacks中,我尝试将它们转换为DragAwareListBoxItems(我需要将其设置为更好的可重用性的接口)。如果演员成功,我会注册DragAwareListBoxItem.ConnectionDragging事件。 (坏名字,不是我的!)。当该事件触发时,连接重新绘制其路径。

DragAwareListBoxItem实际上并不知道它何时被拖动,所以有人必须告诉它。由于ListBoxItem在我的可视树中的位置,它从不会听到Dragger.IsDragging事件。因此,要告诉它它被拖动,ListBox监听事件并通知相应的DragAwareListBoxItem。

准备发布ConnectionDragAwareListBoxItemListBox_IsDragging的代码,但我认为在这里可读的方式太多了。您可以通过http://code.google.com/p/pavilion/source/browse/#hg%2FPavilionDesignerTool%2FPavilion.NodeDesigner 查看该项目,或使用hg克隆https://code.google.com/p/pavilion/克隆该仓库。它是MIT许可证下的开源项目,因此您可以根据自己的需要进行调整。作为警告,没有稳定的版本,所以它可以随时更改。

连接性

与连接更新一样,我不会粘贴代码。相反,我会告诉你项目中哪些类需要检查以及每个类需要查找哪些类。

从用户的角度来看,创建连接的方式如下。用户右键单击一个节点。这将弹出一个用户选择“创建新连接”的上下文菜单。该选项创建一条直线,其起点根源于所选节点,并且其终点跟随鼠标。如果用户点击另一个节点,则在两者之间建立连接。如果用户点击其他任何地方,则不会创建连接并且线路消失。

这个过程涉及两个类。 ConnectionManager(实际上并不管理任何连接)包含附加属性。消费控件将ConnectionManager.IsConnectable属性设置为true,并将ConnectionManager.MenuItemInvoker属性设置为应启动该过程的菜单项。此外,可视树中的某些控件必须侦听ConnectionPending路由事件。这是实际创建连接的地方。

当选择菜单项时,ConnectionManager会创建一个LineAdorner。 ConnectionManager侦听LineAdorner LeftClick事件。当这个事件被解雇时,我会执行命中测试来找到所选的控件。然后,我提出ConnectionPending事件,传入事件args我想创建两个控件之间的连接。实际完成这项工作取决于活动的用户。

+0

对于有趣的拖拽方法,我会在明天更深入地观察你的链接项目。例如,假设您正在从节点A拖到节点B(而不是移动任一节点),并且您在拖放上创建了连接(并且此时的连接已添加到ItemsSource中)。你能否在这里添加你的帖子,并解释在你从A拖到B时以及如何在拖放上创建连接(连接A到B)时你如何绘制圆弧?谢谢。 – 2011-08-14 22:53:08

+0

当然,我会努力完成。也许今晚晚些时候。 – Vish 2011-08-15 17:46:56

+0

+50为答案尝试。如果您可以覆盖在拖动时绘制临时弧的关键问题场景,我可以接受答案。 – 2011-08-18 01:09:03