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 属性
如何在 TabNavigator 的 Canvas 子项上获取个人 creationPolicy?