开发者

dependency properties gathered in separate class

开发者 https://www.devze.com 2023-03-30 06:51 出处:网络
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 suc

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.


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;

}  


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!

0

精彩评论

暂无评论...
验证码 换一张
取 消