依赖属性聚集在单独的类中

发布于 2024-12-01 12:53:09 字数 3420 浏览 1 评论 0原文

我的问题涉及 Silverlight(但我猜 WPF 也是如此)。

基本上我知道如何在用户控件中创建依赖属性以及如何使其工作。但我试图做但没有成功的是:在类中创建依赖属性(或多个),并且该类将成为我的用户控件的依赖属性。

换句话说:

// my UserControl
public class DPTest : UserControl
{
    // dependency property, which type is a class, and this class will be holding other dependency properties        
    public static readonly DependencyProperty GroupProperty =
        DependencyProperty.Register("Group", typeof(DPGroup), typeof(DPTest), new PropertyMetadata(new DPGroup(), OnPropertyChanged));

    public DPGroup Group
    {
        get { return (DPGroup)GetValue(GroupProperty); }
        set { SetValue(GroupProperty, value); }
    }    

    // this occurs only when property Group will change, but not when a member of property Group will change        
    static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPTest g = d as DPTest;
        // etc.     
    }
}

// a class, where I want to hold my dependency properties
public class DPGroup : DependencyObject
{

    public static readonly DependencyProperty MyProperty1Property =
        DependencyProperty.RegisterAttached("MyProperty1", typeof(int), typeof(DPGroup), new PropertyMetadata(1, OnPropertyChanged));

    public int MyProperty1
    {
        get { return (int)GetValue(MyProperty1Property); }
        set { SetValue(MyProperty1Property, value); }
    }

    // I would like to notify "the parent" (which means user control "DPTest" ), that member MyProperty1 has changed
    static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPTest g = d as DPTest;
        if (g != null) g.textBox1.Text = g.Group.MyProperty1.ToString();
    }
}

我想要实现的是通知(在XAML中的设计时)用户控件DPTest,即Group属性的成员(Group.MyProperty1)更改了它的值。我设法使其在运行时发生,例如通过使用 DPGroup 类中定义的事件处理程序,但这在 xaml 的设计时不起作用。

<Grid x:Name="LayoutRoot" Background="White">
    <local:DPTest>
        <local:DPTest.Group>
            <local:DPGroup MyProperty1="2"/>
        </local:DPTest.Group>
    </local:DPTest>
</Grid>

它可以工作,但只是第一次,在创建标记期间:

 <local:DPGroup MyProperty1="2"/>

此后,更改 MyProperty1 的值,不会触发 DPTest.OnPropertyChange。可能会触发DBGroup.OnPropertyChanged,但这当然不会通知用户控件DPTest那么如何让 DPTest 知道 Group.MyProperty1 已更改?

我不想从 MyProperty1 进行任何绑定 到用户控件 DPTest 内创建的各个属性(不要重复属性),重点是在单独的类中拥有一组属性,因此我可以多次使用该组,例如:

// my UserControl
public class DPTest : UserControl
{
    public DPGroup Group1 { ... } 
    public DPGroup Group2 { ... } 
}

我看到一些类比UIElement.RenderTransform (假设它是我的 Group 属性),例如 ScaleTransform

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.RenderTransform>
        <ScaleTransform ScaleX="0.4"/>
    </Grid.RenderTransform>      
</Grid>

ScaleX 是一个类比MyProperty1不同之处在于,改变 ScaleX 的值(在 XAML 中)将反映设计时的立即变化,而这正是我想要实现的。

我试图找到整个谷歌/堆栈溢出和其他问题的解决方案,但没有找到。到处都是在用户控件内创建依赖属性的示例。

感谢您抽出时间。 非常感谢任何帮助。

编辑:根据 Harlow Burgess 的回答,成功地在 Silverlight 中制作了一个工作示例。我将整个解决方案作为单独的答案放在下面。

My question concerns Silverlight (but I guess WPF as well).

Basically I know, how to create dependency property in a user control and how to make it work. But what i was trying to do, and didn't succeded is: to create dependency property (or more than one) in a class, and this class will become a dependency property for my user control.

With other words:

// my UserControl
public class DPTest : UserControl
{
    // dependency property, which type is a class, and this class will be holding other dependency properties        
    public static readonly DependencyProperty GroupProperty =
        DependencyProperty.Register("Group", typeof(DPGroup), typeof(DPTest), new PropertyMetadata(new DPGroup(), OnPropertyChanged));

    public DPGroup Group
    {
        get { return (DPGroup)GetValue(GroupProperty); }
        set { SetValue(GroupProperty, value); }
    }    

    // this occurs only when property Group will change, but not when a member of property Group will change        
    static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPTest g = d as DPTest;
        // etc.     
    }
}

// a class, where I want to hold my dependency properties
public class DPGroup : DependencyObject
{

    public static readonly DependencyProperty MyProperty1Property =
        DependencyProperty.RegisterAttached("MyProperty1", typeof(int), typeof(DPGroup), new PropertyMetadata(1, OnPropertyChanged));

    public int MyProperty1
    {
        get { return (int)GetValue(MyProperty1Property); }
        set { SetValue(MyProperty1Property, value); }
    }

    // I would like to notify "the parent" (which means user control "DPTest" ), that member MyProperty1 has changed
    static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPTest g = d as DPTest;
        if (g != null) g.textBox1.Text = g.Group.MyProperty1.ToString();
    }
}

What I want to achieve is to notify (in design time in XAML) a user control DPTest, that member of Group property (Group.MyProperty1) changed it's value. I managed to make it happen in a run-time, for example by using event handler defined in DPGroup class, but this doesn't work in design-time in xaml.

<Grid x:Name="LayoutRoot" Background="White">
    <local:DPTest>
        <local:DPTest.Group>
            <local:DPGroup MyProperty1="2"/>
        </local:DPTest.Group>
    </local:DPTest>
</Grid>

It works, but only first time, during creating tag:

 <local:DPGroup MyProperty1="2"/>

and after this, changing value of MyProperty1, does not fire DPTest.OnPropertyChange. Probably fires DBGroup.OnPropertyChanged, but this of course does not notify user control DPTest about it. So how to make DPTest know, that the Group.MyProperty1 has changed?

I don't want to make any bindings from MyProperty1 to respective property created inside user control DPTest (not to duplicate properties), the point is to have a group of properties in separate class, so i can use this group more than once, like:

// my UserControl
public class DPTest : UserControl
{
    public DPGroup Group1 { ... } 
    public DPGroup Group2 { ... } 
}

I see some analogy to UIElement.RenderTransform (let's say it is my Group property) which holds for example ScaleTransform

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.RenderTransform>
        <ScaleTransform ScaleX="0.4"/>
    </Grid.RenderTransform>      
</Grid>

ScaleX is an analogy to MyProperty1. The difference is, that changing value of ScaleX (in XAML) will reflect immediate changes in design-time, and exactly this I am trying to achieve.

I was trying to find a solution in entire google/stack overflow and others, but none found. Everywhere are just examples of creating dependency properties inside a user control.

Thank you for your time.
Any help much appreciated.

edit: based on Harlow Burgess answer, a managed to make a working example in Silverlight. I put the whole solution below as an separate answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

巷子口的你 2024-12-08 12:53:09

来自: http://msdn.microsoft.com/en-us/library /ms752914.aspx#setting_properties_data_binding

依赖属性或 DependencyObject 类,本身并不
支持 INotifyPropertyChanged 来生成通知
数据绑定的 DependencyObject 源属性值的更改次数
运营。有关如何创建使用属性的更多信息
在可以报告数据绑定目标更改的数据绑定中,请参阅
数据绑定概述。


设计一个在任何子属性(任何子属性、任何子属性……)的任何属性发生变化时通知整个对象图的系统是低效的。因此,当您需要在属性更改时执行某些操作时,或者如果您确实希望在任何子属性更改时收到通知,则应该使用数据绑定到特定属性,您应该实现INotifyPropertyChanged。

如何:实现属性更改通知

示例:

public class DPGroup : DependencyObject, INotifyPropertyChanged 
{      
    public static readonly DependencyProperty MyProperty1Property =
        DependencyProperty.RegisterAttached(
        "MyProperty1",
        typeof(int),
        typeof(DPGroup),
        new PropertyMetadata(1));

    public int MyProperty1
    {        
        get { return (int)GetValue(MyProperty1Property); }        
        set { SetValue(MyProperty1Property, value); }
    } 

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        NotifyPropertyChanged(e.Property.Name);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

public class DPTest : UserControl   
{     
    public static readonly DependencyProperty GroupProperty =         
        DependencyProperty.Register(
        "Group",
        typeof(DPGroup),
        typeof(DPTest),
        new PropertyMetadata(
            new DPGroup(),
            new PropertyChangedCallback(OnGroupPropertyChanged)
            )
        );

    public DPGroup Group     
    {
        get { return (DPGroup)GetValue(GroupProperty); }
        set { SetValue(GroupProperty, value);}     
    }

    static void OnGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPTest control = (DPTest)d;

        DPGroup oldGroup = e.OldValue as DPGroup;
        if (oldGroup != null)
        {
            oldGroup.PropertyChanged -=new PropertyChangedEventHandler(control.group_PropertyChanged);
        }

        DPGroup newGroup = e.NewValue as DPGroup;
        if (newGroup != null)
        {
            newGroup.PropertyChanged +=new PropertyChangedEventHandler(control.group_PropertyChanged);
        }

        control.UpdateTextBox();
    }

    private void group_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        this.UpdateTextBox();
    }

    private void UpdateTextBox()
    {
        this.textBox1.Text = this.Group.MyProperty1.ToString(); 
    }

    private TextBox textBox1;

}  

From: http://msdn.microsoft.com/en-us/library/ms752914.aspx#setting_properties_data_binding

Dependency properties, or the DependencyObject class, do not natively
support INotifyPropertyChanged
for purposes of producing notifications
of changes in DependencyObject source property value for data binding
operations. For more information on how to create properties for use
in data binding that can report changes to a data binding target, see
Data Binding Overview.

It would be inefficient to design a system that notifies an entire object graph anytime any property of any subproperty (of any subproperty, of any subproperty, ...) changes. So instead you should use Data Binding to specific properties when you need to do something when that property changes, or if you really want to be notified when any subproperty changes, you should implement INotifyPropertyChanged.

How to: Implement Property Change Notification

Example:

public class DPGroup : DependencyObject, INotifyPropertyChanged 
{      
    public static readonly DependencyProperty MyProperty1Property =
        DependencyProperty.RegisterAttached(
        "MyProperty1",
        typeof(int),
        typeof(DPGroup),
        new PropertyMetadata(1));

    public int MyProperty1
    {        
        get { return (int)GetValue(MyProperty1Property); }        
        set { SetValue(MyProperty1Property, value); }
    } 

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        NotifyPropertyChanged(e.Property.Name);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

public class DPTest : UserControl   
{     
    public static readonly DependencyProperty GroupProperty =         
        DependencyProperty.Register(
        "Group",
        typeof(DPGroup),
        typeof(DPTest),
        new PropertyMetadata(
            new DPGroup(),
            new PropertyChangedCallback(OnGroupPropertyChanged)
            )
        );

    public DPGroup Group     
    {
        get { return (DPGroup)GetValue(GroupProperty); }
        set { SetValue(GroupProperty, value);}     
    }

    static void OnGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPTest control = (DPTest)d;

        DPGroup oldGroup = e.OldValue as DPGroup;
        if (oldGroup != null)
        {
            oldGroup.PropertyChanged -=new PropertyChangedEventHandler(control.group_PropertyChanged);
        }

        DPGroup newGroup = e.NewValue as DPGroup;
        if (newGroup != null)
        {
            newGroup.PropertyChanged +=new PropertyChangedEventHandler(control.group_PropertyChanged);
        }

        control.UpdateTextBox();
    }

    private void group_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        this.UpdateTextBox();
    }

    private void UpdateTextBox()
    {
        this.textBox1.Text = this.Group.MyProperty1.ToString(); 
    }

    private TextBox textBox1;

}  
红尘作伴 2024-12-08 12:53:09

好的,根据 @Harlow Burgess 的回答,我成功地在 Silverlight 中制作了一个工作示例。

基本上区别在于,在 SL 中,DependencyObject 类没有 OnPropertyChanged 方法,因此在 DPGroup 类中我们无法重写它,但我们可以附加它此方法以另一种方式,通过:

new PropertyMetadata(1, OnPropertyChanged).

因此 DPGroup 类将如下所示:

public class DPGroup : DependencyObject, INotifyPropertyChanged
{
    public static readonly DependencyProperty MyProperty1Property =
        DependencyProperty.RegisterAttached(
        "MyProperty1",
        typeof(int),
        typeof(DPGroup),
        new PropertyMetadata(1, OnPropertyChanged));

    public int MyProperty1
    {
        get { return (int)GetValue(MyProperty1Property); }
        set { SetValue(MyProperty1Property, value); }
    }   

    // static method invoked when MyProperty1 has changed value
    static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPGroup g = d as DPGroup;
        if (g != null)
        {
            g.MyProperty1 = (int)e.NewValue;
            // invoking event handler, to notify parent class about changed value of DP
            if (g.PropertyChanged != null) g.PropertyChanged(g, null);                
        }
    }

    // event handler, for use in parent class
    public event PropertyChangedEventHandler PropertyChanged;             
}

父类,包含类型 DPGroup 的依赖属性:

public partial class DPTest : UserControl
{
    public static readonly DependencyProperty GroupProperty =
        DependencyProperty.Register(
        "Group",
        typeof(DPGroup),
        typeof(DPTest),
        new PropertyMetadata(
            new DPGroup(),
            new PropertyChangedCallback(OnGroupPropertyChanged)
            )
        );

    public DPGroup Group
    {
        get { return (DPGroup)GetValue(GroupProperty); }
        set { SetValue(GroupProperty, value); }
    }

    // static method invoked when Group property has changed value
    // here we need to attach event handler defined if DPGroup, so it will fire from inside Group property, 
    // when Group.MyProperty1 will change value
    static void OnGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPTest control = (DPTest)d;

        DPGroup oldGroup = e.OldValue as DPGroup;
        // removing event handler from prevoius instance of DBGroup
        if (oldGroup != null)            
            oldGroup.PropertyChanged -= new PropertyChangedEventHandler(control.group_PropertyChanged);

        DPGroup newGroup = e.NewValue as DPGroup;
        // adding event handler to new instance of DBGroup
        if (newGroup != null)            
            newGroup.PropertyChanged += new PropertyChangedEventHandler(control.group_PropertyChanged);

        DPTest g = d as DPTest;
        if (g != null)
            control.UpdateTextBox();            
    }

    private void group_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        UpdateTextBox();
    }

    // here you can do anything with changed value Group.MyProperty1
    private void UpdateTextBox()
    {
        this.textBox1.Text = this.Group.MyProperty1.ToString();
    }

    public DPTest()
    {
        InitializeComponent();

    }
}  

现在,DPGroup 类的 XAML 部分代码>DPTest:

<UserControl x:Class="Silverlight_Workbench_2.DPTest"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Silverlight_Workbench_2"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" >

    <Grid x:Name="LayoutRoot" Background="White">

        <TextBox Height="23" HorizontalAlignment="Left" Margin="76,61,0,0" 
                 x:Name="textBox1" VerticalAlignment="Top" Width="120" />

    </Grid>
</UserControl>

最后,我们可以将 DPTest 嵌入到任何控件的某些内容中,例如嵌入到另一个用户控件的 Grid 中:

<UserControl x:Class="Silverlight_Workbench_2.DPTestMain"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Silverlight_Workbench_2"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White">

        <local:DPTest>
            <local:DPTest.Group>
                <!--here we can change value, and it will be reflected in design window
                as a text in textBox1-->
                <local:DPGroup MyProperty1="8"/>
            </local:DPTest.Group>
        </local:DPTest>

    </Grid>
</UserControl>

仅此而已,再次感谢 Harlow Burgess 的帮助!

Ok, so based on @Harlow Burgess answer, a managed to make a working example in Silverlight.

Basically the difference is, that in SL, DependencyObject class has no OnPropertyChanged method, so in DPGroup class we cannot override it, but we can attach this method in another way, by:

new PropertyMetadata(1, OnPropertyChanged).

So the DPGroup class will look like this:

public class DPGroup : DependencyObject, INotifyPropertyChanged
{
    public static readonly DependencyProperty MyProperty1Property =
        DependencyProperty.RegisterAttached(
        "MyProperty1",
        typeof(int),
        typeof(DPGroup),
        new PropertyMetadata(1, OnPropertyChanged));

    public int MyProperty1
    {
        get { return (int)GetValue(MyProperty1Property); }
        set { SetValue(MyProperty1Property, value); }
    }   

    // static method invoked when MyProperty1 has changed value
    static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPGroup g = d as DPGroup;
        if (g != null)
        {
            g.MyProperty1 = (int)e.NewValue;
            // invoking event handler, to notify parent class about changed value of DP
            if (g.PropertyChanged != null) g.PropertyChanged(g, null);                
        }
    }

    // event handler, for use in parent class
    public event PropertyChangedEventHandler PropertyChanged;             
}

And the parent class, containing dependency property of type DPGroup:

public partial class DPTest : UserControl
{
    public static readonly DependencyProperty GroupProperty =
        DependencyProperty.Register(
        "Group",
        typeof(DPGroup),
        typeof(DPTest),
        new PropertyMetadata(
            new DPGroup(),
            new PropertyChangedCallback(OnGroupPropertyChanged)
            )
        );

    public DPGroup Group
    {
        get { return (DPGroup)GetValue(GroupProperty); }
        set { SetValue(GroupProperty, value); }
    }

    // static method invoked when Group property has changed value
    // here we need to attach event handler defined if DPGroup, so it will fire from inside Group property, 
    // when Group.MyProperty1 will change value
    static void OnGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DPTest control = (DPTest)d;

        DPGroup oldGroup = e.OldValue as DPGroup;
        // removing event handler from prevoius instance of DBGroup
        if (oldGroup != null)            
            oldGroup.PropertyChanged -= new PropertyChangedEventHandler(control.group_PropertyChanged);

        DPGroup newGroup = e.NewValue as DPGroup;
        // adding event handler to new instance of DBGroup
        if (newGroup != null)            
            newGroup.PropertyChanged += new PropertyChangedEventHandler(control.group_PropertyChanged);

        DPTest g = d as DPTest;
        if (g != null)
            control.UpdateTextBox();            
    }

    private void group_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        UpdateTextBox();
    }

    // here you can do anything with changed value Group.MyProperty1
    private void UpdateTextBox()
    {
        this.textBox1.Text = this.Group.MyProperty1.ToString();
    }

    public DPTest()
    {
        InitializeComponent();

    }
}  

Now, the XAML part for DPTest:

<UserControl x:Class="Silverlight_Workbench_2.DPTest"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Silverlight_Workbench_2"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" >

    <Grid x:Name="LayoutRoot" Background="White">

        <TextBox Height="23" HorizontalAlignment="Left" Margin="76,61,0,0" 
                 x:Name="textBox1" VerticalAlignment="Top" Width="120" />

    </Grid>
</UserControl>

Finally, we can embed our DPTest in some content of any control, for example in a Grid of another user control:

<UserControl x:Class="Silverlight_Workbench_2.DPTestMain"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Silverlight_Workbench_2"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White">

        <local:DPTest>
            <local:DPTest.Group>
                <!--here we can change value, and it will be reflected in design window
                as a text in textBox1-->
                <local:DPGroup MyProperty1="8"/>
            </local:DPTest.Group>
        </local:DPTest>

    </Grid>
</UserControl>

That is all, thanks again to the Harlow Burgess for the help!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文