свойства зависимостей, собранные в отдельный класс - PullRequest
1 голос
/ 25 августа 2011

Мой вопрос касается 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) будет отражать немедленные изменения во время разработки, и именно этого я и пытаюсь достичь.

Я пытался найти решение во всем переполнении Google / стека и других, но ничего не нашел. Везде только примеры создания свойств зависимостей внутри пользовательского элемента управления.

Спасибо за ваше время. Любая помощь высоко ценится.

edit: на основании ответа Харлоу Берджесса удалось создать рабочий пример в Silverlight. Я положил все решение ниже в качестве отдельного ответа.

Ответы [ 2 ]

2 голосов
/ 25 августа 2011

От: 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;

}  
0 голосов
/ 29 августа 2011

Хорошо, поэтому, основываясь на ответе @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();

    }
}  

Теперь часть 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 за помощь!

...