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

Posted

技术标签:

【中文标题】ItemsControl 子项在请求 Canvas.GetLeft 时返回 NAN【英文标题】:ItemsControl children return NAN when asking for Canvas.GetLeft 【发布时间】:2013-05-24 17:33:34 【问题描述】:

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

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

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

所有的画布直接子(容器)都可以正常工作,但是如果我想连接节点,它就不起作用。看来,如果我不显式调用Canvas.SetLeft()Canvas.SetTop(),那么Canvas.GetLeft()Canvas.GetTop() 会返回NAN。

我应该如何进行?

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

源码和截图

这是示例的源代码。你可以找到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 号不要在程序代码中创建或操作 UI 元素。这就是 XAML 的用途。 2 号覆盖 OnRender() 闻起来太像 winforms。请不要这样做。第 3 位,如果您想进行一些 Nodes 可视化,请查看我的 MVVM Nodes Editor Sample 如果在画布中不设置top和left,控件都会在左上角。所以 NaN 很有意义。您应该手动设置顶部和左侧。你还有什么期待?你的多重绑定是垃圾。这些都没有任何意义。 【参考方案1】:

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

    您的节点和容器不应是使用 OnRender 的控件。 WPF 中有很多期望,其中一个期望是您使用他们的控件。如果您深入研究 Microsoft 代码,他们会为他们的课程硬编码很多东西。 您应该拥有具有连接的节点和容器的数据对象。容器应该有一个子节点列表。 您将使用 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 对象”放在容器上的某个位置,则改为绑定到它的位置。您可以使用 Grid 来对齐 ConnectionPointView,然后 ConnectionPoint 会自动更新。

【讨论】:

嗨,李,首先感谢您的回答。我对你的提议有一些问题。容器和容器视图有什么区别?当您使用 Left 和 Top 属性时,这些属性是在哪里定义的? IConnectable 接口是如何定义的?如何定义以及应该如何使用依赖属性?您的实现看起来不错,但我想我错过了一些部分。 我故意遗漏了一些部分。其余的对读者来说是一个练习。这不应该是关于 WPF 的完整教程,而是指出您应该如何在 WPF 中编码的正确方向。我强烈建议您研究 WPF MVVM,您的需求会从中受益匪浅。

以上是关于ItemsControl 子项在请求 Canvas.GetLeft 时返回 NAN的主要内容,如果未能解决你的问题,请参考以下文章

在 ItemsControl DataTemplate 中设置 Canvas 属性

如何捕捉鼠标点击事件到重叠的ItemsControl

如何在 TabNavigator 的 Canvas 子项上获取个人 creationPolicy?

Flex Canvas 子项随机调整大小

将 ItemsControl 与可拖动项目相结合 - Element.parent 始终为空

容器的剪辑子项在滚动时不可见