0

我有一个非常简单的 WPF 应用程序,可以在画布中呈现简单的形状:

蓝色方块是ItemsControl红色圆圈是Controls

我的应用程序中的以下步骤是在形状之间添加连接线。形状将被移动,我希望自动移动连接。我读到了如何添加连接绑定。

与画布直接子(容器)一起工作正常,但如果我想连接节点,它就不起作用。看来如果我不Canvas.SetLeft()打电话Canvas.SetTop()明确地Canvas.GetLeft(),然后Canvas.GetTop()返回 NAN。

我应该如何进行?

  • 我是否应该实现一种机制来让所有对象都放在我的画布中,这样我总是可以计算Canvas.GetLeft()
  • 我应该以其他方式进行吗?

源代码和截图

在此处输入图像描述

这是示例的源代码。你可以在这里找到完整的例子:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Container container1 = new Container() { Width = 100, Height = 100 };
        Node node1 = new Node() { Width = 50, Height = 50 };
        container1.Items.Add(node1);

        Container container2 = new Container() { Width = 100, Height = 100 };
        Node node2 = new Node() { Width = 50, Height = 50 };
        container2.Items.Add(node2);

        Canvas.SetLeft(container2, 200);

        myCanvas.Children.Add(container1);
        myCanvas.Children.Add(container2);
    }
}

class Container : ItemsControl
{
    protected override void OnRender(DrawingContext drawingContext)
    {
        drawingContext.DrawRectangle(
            Brushes.Blue, null, new Rect(0, 0, this.Width, this.Height));
    }
}

class Node : Control
{
    protected override void OnRender(DrawingContext drawingContext)
    {
        drawingContext.DrawEllipse(
            Brushes.Red, null,
            new Point(Width / 2, Height / 2), Width / 2, Height / 2);
    }
}

这就是我实现形状之间连接的方式:

    public Shape AddConnection(UIElement source, UIElement target)
    {
        Connector conn = new Connector();
        conn.SetBinding(Connector.StartPointProperty,
            CreateConnectorBinding(source));
        conn.SetBinding(Connector.EndPointProperty,
            CreateConnectorBinding(target));
        return conn;
    }

    private MultiBinding CreateConnectorBinding(UIElement connectable)
    {
        // Create a multibinding collection and assign an appropriate converter to it
        MultiBinding multiBinding = new MultiBinding();
        multiBinding.Converter = new ConnectorBindingConverter();

        // Create binging #1 to IConnectable to handle Left
        Binding binding = new Binding();
        binding.Source = connectable;
        binding.Path = new PropertyPath(Canvas.LeftProperty);
        multiBinding.Bindings.Add(binding);

        // Create binging #2 to IConnectable to handle Top
        binding = new Binding();
        binding.Source = connectable;
        binding.Path = new PropertyPath(Canvas.TopProperty);
        multiBinding.Bindings.Add(binding);

        // Create binging #3 to IConnectable to handle ActualWidth
        binding = new Binding();
        binding.Source = connectable;
        binding.Path = new PropertyPath(FrameworkElement.ActualWidthProperty);
        multiBinding.Bindings.Add(binding);

        // Create binging #4 to IConnectable to handle ActualHeight
        binding = new Binding();
        binding.Source = connectable;
        binding.Path = new PropertyPath(FrameworkElement.ActualHeightProperty);
        multiBinding.Bindings.Add(binding);

        return multiBinding;
    }

Connector对象非常简单。它有一个 LineGeometry 并公开两个 DependencyProperties 来计算起点和终点。

public static readonly DependencyProperty StartPointProperty =
    DependencyProperty.Register(
        "StartPoint",
        typeof(Point),
        typeof(Connector),
        new FrameworkPropertyMetadata(
            new Point(0, 0),
            FrameworkPropertyMetadataOptions.AffectsMeasure));

public static readonly DependencyProperty EndPointProperty =
    DependencyProperty.Register(
        "EndPoint",
        typeof(Point),
        typeof(Connector),
        new FrameworkPropertyMetadata(
            new Point(0, 0),
            FrameworkPropertyMetadataOptions.AffectsMeasure));
4

1 回答 1

3

一切都错了,如果不解决问题,我无法真正回答这个问题。

  1. 您的节点和容器不应是使用 OnRender 的控件。WPF 中有很多期望,其中一个期望是您使用他们的控件。如果您深入研究 Microsoft 代码,他们会为他们的课程硬编码很多东西。
  2. 您应该拥有具有连接的节点和容器的数据对象。容器应该有一个子节点列表。
  3. 您将使用 DataTemplate 或 Style 来实际实现 UI。那是您进行绑定的地方,但不要使用多重绑定。只需绑定到个人价值观本身。如果您需要评估,那么您可以创建 ViewModel 对象来为您执行这些计算。您不会在转换器中执行构建代码。

因为您使用绑定来连接事物,并且您的“可连接”没有描述它是节点还是容器或两者兼而有之,所以我假设它可以是两者。例如:

public interface IConnection
{
   IConnectable A { get; set; }
   IConnectable B { get; set; }
}

public class Connection : IConnection, Line
{
   DependencyProperty AProperty = ...;
   DependencyProperty BProperty = ...;
}

public class Node : IConnectable
{
   DependencyProperty ConnectionProperty = ...;
}

public class Container : IConnectable
{
   DependencyProperty ConnectionProperty = ...;
   ObservableCollection<IConnectable> Children = ...;
}


public class ContainerView : IConnectable
{
   DependencyProperty ConnectionPointProperty = ...;
   DependencyProperty ConnectionProperty = ...;

   void OnSizeChanged(...)
   {
      RecalcConnectionPoint();
   }
   void OnConnectionPointOtherChanged()
   {
      RecalcConnectionPoint();
   }
   void RecalcConnectionPoint()
   {
      if (Connection.A == this)
      {
         if (Connection.B.ConnectionPoint.Left < this.Left)
         {
            ConnectionPoint = new Point(Left, Top + Height/2);
         }
         else
         {
            ConnectionPoint = new Point(Right, Top + Height/2);
         }
      }
   }
}

然后,您会将与您的模型类匹配的属性绑定到您的 ViewModel 类。然后在您的模型类中操作数据将更新您的视图。

您的容器和节点的样式将决定如何绘制它们,因此假设有一天您决定节点应该看起来像一个矩形......您更改样式并且不必挖掘 OnRender 代码。

这就是您设计 WPF 程序的方式。

其他福利。

如果您要将“连接 UI 对象”放置在 Container 上的某个位置,那么您将绑定到它的位置。您可以使用 Grid 来对齐 ConnectionPointView,然后 ConnectionPoint 会自动更新。

于 2013-05-24T18:40:12.780 回答