none
WPF中如何定义包含自定义数据结构的直线类 RRS feed

  • 问题

  • System.Windows.Shapes中的Line不让派生。但是我的工程中需要绘制直线,直线需要记录两个端点的信息,这两个端点是两个同类型的自定义控件usercontrol1。如何才能定义一个能够显示在canvas中、又能向它添加字段的类?比如这里就需要一个类,可以像Line那样在canvas显示,同时还需要有两个字段usercontrol1 node1和usercontrol1 node2。
    2015年7月9日 10:37

答案

  • 其实你的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
    2015年7月10日 0:15

全部回复

  • 思路:自定义一个控件,在控件中绘制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也是需要考虑的。

    2015年7月9日 14:23
  • 非常感谢你的回答。你给的代码我运行了但是不知道怎么用这个来满足需求。

    我才接触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,然后再移除关系。

    所以想请教下有没有什么更好的方法。

    2015年7月9日 21:16
  • 其实你的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
    2015年7月10日 0:15
  • 明白你的思路了,非常感谢!


    2015年7月10日 16:13