2011-06-15 15 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