积极答复者
WPF中如何定义包含自定义数据结构的直线类

问题
答案
-
其实你的Relation大概就能满足要求了,但既然你的需求是:模拟一个密封类Line并添加新属性和成员,那么你需要做的就是:
1.让你的Relation类和Line类具有相同的呈现,即他们放到容器中有相同的外观。想要达到这个目的,只能使用UserControl或CustomControl的方法,或者从其他元素派生也行,但是比较麻烦。
我的MyLine使用CustomControl实现了和Line一样的外观,它是通过在模板中绘制一条Line实现的。
2.为你的Relation类定义Line类所具有的关键属性,如X1、Y1、Stroke等。
在这里需要注意,如果想要在修改这些属性的时候,元素的呈现也会相应更新,那么你的属性必须满足其中一点:
a.是DependencyProperty,这要求Relation类继承DepencencyObejct(所有的Visual都满足这一点);
b.引发PropertyChanged事件,这要求Relation类实现INotifyPropertyChanged接口;
我的MyLine定义了X1、Y1等绘制Line所必需的属性,它们是依赖属性(DependencyProperty)。
3.绑定以实现状态和效果的更新,当然你也可以手动更新,就像你说的删掉旧的Line然后重新绘制一样,但使用绑定是比较方便的。
我的MyLine使用绑定,将模板中的Line的X1等属性和MyLine中的对应属性关联起来了,因此他们能够同步更新。
4.通用属性和事件的支持,我之所以建议使用UserControl或者CustomControl,是因为它们分别继承自UserControl和Control,而这两种元素和Line一样,都是间接继承自FrameworkElement的,因此具有通用的事件(如鼠标事件)和属性支持。如果FrameworkElement中并没有你想要的事件,那你可以自定义事件来达到目标。
其实依赖属性很简单的,只要按照套路定义好,然后跟普通属性一样用。如果实在不喜欢,那只能用 INotifyPropertyChanged 了,只有这样才能自动的实时更新状态。
模板不清楚的话,可以使用UserControl代替,也可以用绑定等功能。
- 已建议为答案 Lymim 2015年7月10日 9:31
- 已标记为答案 SerenaHaven 2015年7月10日 16:13
全部回复
-
思路:自定义一个控件,在控件中绘制line,可以使用用户控件(UserControl)或者自定义控件(CustomControl),我用的自定义控件。
因为想要模拟line,因此将其几个关键的属性都添加到控件中。
自定义控件类:
using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace WPF_Test { public class MyLine : Control { public static readonly DependencyProperty Y1Property = DependencyProperty.Register("Y1", typeof (double), typeof (MyLine), new PropertyMetadata(default(double))); public static readonly DependencyProperty Y2Property = DependencyProperty.Register("Y2", typeof (double), typeof (MyLine), new PropertyMetadata(default(double))); public static readonly DependencyProperty X1Property = DependencyProperty.Register("X1", typeof (double), typeof (MyLine), new PropertyMetadata(default(double))); public static readonly DependencyProperty X2Property = DependencyProperty.Register("X2", typeof (double), typeof (MyLine), new PropertyMetadata(default(double))); public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof (Brush), typeof (MyLine), new PropertyMetadata(default(Brush))); public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof (double), typeof (MyLine), new PropertyMetadata(default(double))); public static readonly DependencyProperty Node2ContentProperty = DependencyProperty.Register("Node2Content", typeof (object), typeof (MyLine), new PropertyMetadata(default(object))); public static readonly DependencyProperty Node1ContentProperty = DependencyProperty.Register("Node1Content", typeof (object), typeof (MyLine), new PropertyMetadata(default(object))); static MyLine() { DefaultStyleKeyProperty.OverrideMetadata(typeof (MyLine), new FrameworkPropertyMetadata(typeof (MyLine))); } public double Y1 { get { return (double) GetValue(Y1Property); } set { SetValue(Y1Property, value); } } public double Y2 { get { return (double) GetValue(Y2Property); } set { SetValue(Y2Property, value); } } public double X1 { get { return (double) GetValue(X1Property); } set { SetValue(X1Property, value); } } public double X2 { get { return (double) GetValue(X2Property); } set { SetValue(X2Property, value); } } public Brush Stroke { get { return (Brush) GetValue(StrokeProperty); } set { SetValue(StrokeProperty, value); } } public double StrokeThickness { get { return (double) GetValue(StrokeThicknessProperty); } set { SetValue(StrokeThicknessProperty, value); } } public object Node2Content { get { return GetValue(Node2ContentProperty); } set { SetValue(Node2ContentProperty, value); } } public object Node1Content { get { return GetValue(Node1ContentProperty); } set { SetValue(Node1ContentProperty, value); } } } }
默认样式和模板:
<Style TargetType="{x:Type local:MyLine}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:MyLine}"> <Canvas> <Line X1="{TemplateBinding X1}" Y1="{TemplateBinding Y1}" X2="{TemplateBinding X2}" Y2="{TemplateBinding Y2}" Stroke="{TemplateBinding Stroke}" StrokeThickness="{TemplateBinding StrokeThickness}" /> <ContentControl Content="{TemplateBinding Node1Content}" Canvas.Left="{TemplateBinding X1}" Canvas.Top="{TemplateBinding Y1}" /> <ContentControl Content="{TemplateBinding Node2Content}" Canvas.Left="{TemplateBinding X2}" Canvas.Top="{TemplateBinding Y2}" /> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style>
应用:
<Window x:Class="WPF_Test.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WPF_Test" Title="MainWindow" Width="525" Height="350"> <Grid x:Name="Grid"> <local:MyLine X1="10" Y1="10" X2="100" Y2="100" StrokeThickness="2" Stroke="Black"> <local:MyLine.Node1Content> <Button Opacity=".5">info</Button> </local:MyLine.Node1Content> <local:MyLine.Node2Content> <Button Opacity=".5">info</Button> </local:MyLine.Node2Content> </local:MyLine> <Line X1="10" Y1="10" X2="100" Y2="10" StrokeThickness="2" Stroke="Black" /> </Grid> </Window>
效果:
NodeContent的位置需要微调,ZIndex也是需要考虑的。
- 已建议为答案 Xavier Xie-MSFTModerator 2015年7月10日 5:07
-
非常感谢你的回答。你给的代码我运行了但是不知道怎么用这个来满足需求。
我才接触WPF不到一个星期,依赖属性、模板这些都不熟,这个不用依赖属性应该怎么做呢?
想要实现的直线类是要存储节点关系用的,大致描述如下:
有一个节点类Node,是一个用户控件,canvas中现在有两个Node的实例node1和node2。在执行确定node1和node2的关系的操作后,实例化一个RelationLine的对象,这个对象里有两个字段UpperNode和LowerNode,分别存储node1和node2,同时需要在canvas中、node1和node2之间绘制一条线段,这条线段需要支持点击事件(Shapes中的Line能满足除了存储Node实例以外的所有需求,所以先前尝试继承Line,但是Line是密封的)。
后来我实现了另一个类能实现上述功能,实现方法如下:
实现一个Relation类,包含一个upperNode和一个lowerNode字段以及一个Line的实例line。执行确立关系的操作后,为UpperNode和LoweNode赋值,同时根据这两个节点实例化一条线段存储至line。绑定这两个节点位置与line的两端。然后在canvas中显示这个line。
虽然说功能实现了,但是很别扭。因为按照正常逻辑,关系应该是线段的一部分,而不应该是如上所说的那种方式。并且每次增删关系后要更新显示时,都需要从canvas中移除关系中的line,然后再移除关系。
所以想请教下有没有什么更好的方法。
-
其实你的Relation大概就能满足要求了,但既然你的需求是:模拟一个密封类Line并添加新属性和成员,那么你需要做的就是:
1.让你的Relation类和Line类具有相同的呈现,即他们放到容器中有相同的外观。想要达到这个目的,只能使用UserControl或CustomControl的方法,或者从其他元素派生也行,但是比较麻烦。
我的MyLine使用CustomControl实现了和Line一样的外观,它是通过在模板中绘制一条Line实现的。
2.为你的Relation类定义Line类所具有的关键属性,如X1、Y1、Stroke等。
在这里需要注意,如果想要在修改这些属性的时候,元素的呈现也会相应更新,那么你的属性必须满足其中一点:
a.是DependencyProperty,这要求Relation类继承DepencencyObejct(所有的Visual都满足这一点);
b.引发PropertyChanged事件,这要求Relation类实现INotifyPropertyChanged接口;
我的MyLine定义了X1、Y1等绘制Line所必需的属性,它们是依赖属性(DependencyProperty)。
3.绑定以实现状态和效果的更新,当然你也可以手动更新,就像你说的删掉旧的Line然后重新绘制一样,但使用绑定是比较方便的。
我的MyLine使用绑定,将模板中的Line的X1等属性和MyLine中的对应属性关联起来了,因此他们能够同步更新。
4.通用属性和事件的支持,我之所以建议使用UserControl或者CustomControl,是因为它们分别继承自UserControl和Control,而这两种元素和Line一样,都是间接继承自FrameworkElement的,因此具有通用的事件(如鼠标事件)和属性支持。如果FrameworkElement中并没有你想要的事件,那你可以自定义事件来达到目标。
其实依赖属性很简单的,只要按照套路定义好,然后跟普通属性一样用。如果实在不喜欢,那只能用 INotifyPropertyChanged 了,只有这样才能自动的实时更新状态。
模板不清楚的话,可以使用UserControl代替,也可以用绑定等功能。
- 已建议为答案 Lymim 2015年7月10日 9:31
- 已标记为答案 SerenaHaven 2015年7月10日 16:13
-