在请求Canvas.GetLeft时,ItemsControl子项返回NAN

时间:2013-05-24 17:33:34

标签: wpf wpf-controls drawing dependency-properties

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

蓝色方块为ItemsControl,红色圆圈为Controls

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

所有都适用于canvas直接子(容器),但如果我想连接节点,它不起作用。似乎如果我没有明确地致电Canvas.SetLeft()Canvas.SetTop(),那么Canvas.GetLeft()Canvas.GetTop()会返回NAN。

我该怎么办?

  • 我应该实现一种机制来将所有对象放在我的画布上,所以我总是可以计算Canvas.GetLeft()所有对象吗?
  • 我应该以其他方式继续吗?

源代码和截图

enter image description here

这是示例的源代码。您可以找到here完整示例:

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

1 个答案:

答案 0 :(得分:3)

一切都是错的,如果不解决问题,我就无法回答这个问题。

  1. 您的节点和容器不应该是使用OnRender的控件。在WPF中有很多期望,一个期望是你使用他们的控件。如果你深入研究Microsoft代码,他们会为他们的类编写很多硬编码的东西。
  2. 您应该拥有包含Connections的Node和Container的数据对象。容器应该有一个子节点列表。
  3. 您将使用DataTemplate或Style来实际实现UI。这就是你进行绑定的地方,但不要使用多重绑定。只需绑定到各个值本身。如果需要进行求值,则可以创建为您执行这些计算的ViewModel对象。您没有在转换器中执行构造代码。
  4. 因为你正在使用绑定来连接东西,而你的“可连接”并没有描述它是一个节点还是一个容器,或两者兼而有之,我会假设它可以同时存在。 例如:

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

    然后,您可以将与Model类匹配的属性绑定到ViewModel类。然后操纵Model类中的数据将更新View。

    你的容器和节点的样式将决定如何绘制它们,所以有一天你决定一个Node应该看起来像一个矩形......你改变一个样式而不必挖掘OnRender代码。< / p>

    这就是你设计WPF程序的方法。

    其他好处。

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