在WPF中通过拖放绘制工程图弧

Dav*_*mer 5 wpf drag-and-drop diagramming mvvm mo+

我正在尝试执行拖放方法以在图表中创建关系,这直接类似于SQL Server Management Studio图表工具。例如,在下面的说明中,用户将拖动CustomerIDUser实体到所述Customer实体和创建两者之间的外键关系。

关键的期望功能是在用户跟随鼠标执行拖动操作时绘制一条临时弧形路径。创建实体后移动实体或关系并不是我遇到的问题。

实体关系图

上图中与实体对应的一些参考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>
Run Code Online (Sandbox Code Playgroud)

我目前执行此操作的方法是:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

该图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>
Run Code Online (Sandbox Code Playgroud)

DataTemplate表示实体和关系:

<!-- 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>
Run Code Online (Sandbox Code Playgroud)

问题:问题在于,一旦开始拖动操作,就不再跟踪鼠标的移动,并且连接器装饰器无法像在其他情况下那样绘制弧线。如果释放鼠标并再次单击,则弧开始绘制,但随后丢失了源对象。我试图找到一种方法来结合鼠标移动传递源对象。

赏金:现在回过头来看,我目前不打算直接使用拖放操作来完成此任务。我目前计划DependencyProperty为图表控件添加一个DragItem和IsDragging ,以容纳要拖动的项目,并在发生拖动操作时进行标记。然后,我可以使用DataTriggers更改基于IsDragging 的CursorAdorner可见性,并可以使用DragItem进行放置操作。

(但是,我希望奖励另一种有趣的方法。请发表评论,如果需要更多信息或代码来阐明此问题。)

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

Dav*_*mer 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用于IsDragging DependencyProperty在发生拖动时触发光标变化,例如:

<Style TargetType="{x:Type lib:SolutionDiagramControl}">
    <Style.Triggers>
        <Trigger Property="IsDragging" Value="True">
            <Setter Property="Cursor" Value="Pen" />
        </Trigger>
    </Style.Triggers>
</Style>
Run Code Online (Sandbox Code Playgroud)

无论何时需要执行 的圆弧绘制形式,我都将和设置为被拖动的源项目,drag and drop而不是调用。DragDrop.DoDragDropIsDragging = trueDragItem

在鼠标离开的实体控件中,启用在拖动过程中绘制圆弧的连接器装饰器,例如:

protected override void OnMouseLeave(MouseEventArgs e)
{
    base.OnMouseLeave(e);
    if (ParentSolutionDiagramControl.DragItem != null)
    {
        CreateConnectorAdorner();
    }
}
Run Code Online (Sandbox Code Playgroud)

图表控件必须在拖动期间处理其他鼠标事件,例如:

protected override void OnMouseMove(MouseEventArgs e)
{
    base.OnMouseMove(e);
    if (e.LeftButton != MouseButtonState.Pressed)
    {
        IsDragging = false;
        DragItem = null;
    }
}
Run Code Online (Sandbox Code Playgroud)

图表控件还必须处理鼠标释放事件时的“放置”(并且它必须根据鼠标位置确定正在放置的实体),例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

我仍在寻找更好的解决方案,以便在进行拖动操作时在图表上绘制临时弧(跟随鼠标)。