如何在网格 WPF 上的两个控件之间绘制连接线
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2823266/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to draw connecting lines between two controls on a grid WPF
提问by Scooby
I am creating controls (say button) on a grid. I want to create a connecting line between controls. Say you you do mousedown on one button and release mouse over another button. This should draw a line between these two buttons.
我正在网格上创建控件(比如按钮)。我想在控件之间创建连接线。假设您在一个按钮上执行 mousedown 并在另一个按钮上释放鼠标。这应该在这两个按钮之间画一条线。
Can some one help me or give me some ideas on how to do this?
有人可以帮助我或给我一些关于如何做到这一点的想法吗?
Thanks in advance!
提前致谢!
回答by Robert Fraser
I'm doing something similar; here's a quick summary of what I did:
我正在做类似的事情;这是我所做的快速总结:
Drag & Drop
拖放
For handling the drag-and-drop between controls there's quite a bit of literature on the web (just search WPF drag-and-drop). The default drag-and-drop implementation is overly complex, IMO, and we ended up using some attached DPs to make it easier (similar to these). Basically, you want a drag method that looks something like this:
为了处理控件之间的拖放,网上有很多文献(只需搜索 WPF 拖放)。默认的拖放实现过于复杂,IMO,我们最终使用了一些附加的 DP 以使其更容易(类似于这些)。基本上,您需要一个看起来像这样的拖动方法:
private void onMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UIElement element = sender as UIElement;
if (element == null)
return;
DragDrop.DoDragDrop(element, new DataObject(this), DragDropEffects.Move);
}
On the target, set AllowDrop to true, then add an event to Drop:
在目标上,将 AllowDrop 设置为 true,然后向 Drop 添加一个事件:
private void onDrop(object sender, DragEventArgs args)
{
FrameworkElement elem = sender as FrameworkElement;
if (null == elem)
return;
IDataObject data = args.Data;
if (!data.GetDataPresent(typeof(GraphNode))
return;
GraphNode node = data.GetData(typeof(GraphNode)) as GraphNode;
if(null == node)
return;
// ----- Actually do your stuff here -----
}
Drawing the Line
画线
Now for the tricky part! Each control exposes an AnchorPoint DependencyProperty. When the LayoutUpdated event is raised (i.e. when the control moves/resizes/etc), the control recalculates its AnchorPoint. When a connecting line is added, it binds to the DependencyProperties of both the source and destination's AnchorPoints. [EDIT: As Ray Burns pointed out in the comments the Canvas and grid just need to be in the same place; they don't need to be int the same hierarchy (though they may be)]
现在是棘手的部分!每个控件公开一个 AnchorPoint DependencyProperty。当 LayoutUpdated 事件引发时(即当控件移动/调整大小/等时),控件会重新计算其 AnchorPoint。添加连接线后,它会绑定到源和目标的 AnchorPoints 的 DependencyProperties。[编辑:正如 Ray Burns 在评论中指出的,画布和网格只需要在同一个地方;他们不需要在同一个层次结构中(尽管它们可能是)]
For updating the position DP:
更新位置 DP:
private void onLayoutUpdated(object sender, EventArgs e)
{
Size size = RenderSize;
Point ofs = new Point(size.Width / 2, isInput ? 0 : size.Height);
AnchorPoint = TransformToVisual(node.canvas).Transform(ofs);
}
For creating the line class (can be done in XAML, too):
用于创建线类(也可以在 XAML 中完成):
public sealed class GraphEdge : UserControl
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Point), typeof(GraphEdge), new FrameworkPropertyMetadata(default(Point)));
public Point Source { get { return (Point) this.GetValue(SourceProperty); } set { this.SetValue(SourceProperty, value); } }
public static readonly DependencyProperty DestinationProperty = DependencyProperty.Register("Destination", typeof(Point), typeof(GraphEdge), new FrameworkPropertyMetadata(default(Point)));
public Point Destination { get { return (Point) this.GetValue(DestinationProperty); } set { this.SetValue(DestinationProperty, value); } }
public GraphEdge()
{
LineSegment segment = new LineSegment(default(Point), true);
PathFigure figure = new PathFigure(default(Point), new[] { segment }, false);
PathGeometry geometry = new PathGeometry(new[] { figure });
BindingBase sourceBinding = new Binding {Source = this, Path = new PropertyPath(SourceProperty)};
BindingBase destinationBinding = new Binding { Source = this, Path = new PropertyPath(DestinationProperty) };
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, sourceBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, destinationBinding);
Content = new Path
{
Data = geometry,
StrokeThickness = 5,
Stroke = Brushes.White,
MinWidth = 1,
MinHeight = 1
};
}
}
If you want to get a lot fancier, you can use a MultiValueBinding on source and destination and add a converter which creates the PathGeometry. Here's an example from GraphSharp.Using this method, you could add arrows to the end of the line, use Bezier curves to make it look more natural, route the line around other controls (though this could be harder than it sounds), etc., etc.
如果你想变得更漂亮,你可以在源和目标上使用 MultiValueBinding 并添加一个创建 PathGeometry 的转换器。这是 GraphSharp 的一个例子。使用这种方法,您可以在线条的末尾添加箭头,使用贝塞尔曲线使其看起来更自然,将线条围绕其他控件(尽管这可能比听起来更难)等。
See also
也可以看看
- http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dd246675-bc4e-4d1f-8c04-0571ea51267b
- http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx
- http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
- http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part3.aspx
- http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part4.aspx
- http://www.syncfusion.com/products/user-interface-edition/wpf/diagram
- http://www.mindscape.co.nz/products/wpfflowdiagrams/
- http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dd246675-bc4e-4d1f-8c04-0571ea51267b
- http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx
- http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
- http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part3.aspx
- http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part4.aspx
- http://www.syncfusion.com/products/user-interface-edition/wpf/diagram
- http://www.mindscape.co.nz/products/wpfflowdiagrams/