未通知父项的派生UserControl更改了DependencyProperty

时间:2014-11-14 12:26:11

标签: c# wpf xaml inheritance dependency-properties

我创建了一个基础ClassA,其中包含DependencyProperty MyText。我从中派生了一个ClassB。此ClassB已嵌入MyCustomButton中。在MouseHover / MousePressed上,我更改了嵌入式ClassB的MyText-Value。但是,当父ClassA触发PropertyChanged-Event时,派生的ClassB永远不会更改Property。

通知派生的ClassB更改DependencyProperty的正确方法是什么?我已经尝试了“Base class DependencyProperty value change in WPF”中提到的解决方案,但无法使其工作。

我的代码:

主窗口只包含一个包含ClassB-Instance的MyCustomButton:

<Window.Resources>
    <cc:ClassB x:Key="MyClassB" MyText="MyClassBText"/>
</Window.Resources>

<Grid>
    <StackPanel>
        <cc:MyCustomButton MyClassA="{StaticResource MyClassB}" Width="100" Height="100"/>
    </StackPanel>
</Grid> 

MyCustomButton拥有ClassA的实例(实际上是派生ClassB的一个实例):

class MyCustomButton : Button
{
    public ClassA MyClassA
    {
        get { return (ClassA)GetValue(MyClassAProperty); }
        set { SetValue(MyClassAProperty, value); }
    }
    public static readonly DependencyProperty MyClassAProperty =
        DependencyProperty.Register("MyClassA", typeof(ClassA), typeof(MyCustomButton));
}

<Style TargetType="{x:Type cc:MyCustomButton}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type cc:MyCustomButton}">

                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                    <StackPanel Background="Transparent">
                        <cc:ClassA x:Name="ButtonClassA" Content="{Binding MyClassA, RelativeSource={RelativeSource TemplatedParent}}" />
                    </StackPanel>
                </Border>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter TargetName="ButtonClassA" Property="MyText" Value="HOVER"/>
                    </Trigger>
                    <Trigger Property="IsPressed" Value="true">
                        <Setter TargetName="ButtonClassA" Property="MyText" Value="PRESSED"/>
                    </Trigger>
                </ControlTemplate.Triggers>

            </ControlTemplate>

        </Setter.Value>
    </Setter>
</Style>

ClassA拥有DependencyProperty:

public class ClassA : UserControl
{
    public string MyText
    {
        get { return (string)GetValue(MyTextProperty); }
        set { SetValue(MyTextProperty, value); }
    }
    public static readonly DependencyProperty MyTextProperty =
        DependencyProperty.Register("MyText", typeof(string), typeof(ClassA), new PropertyMetadata("ClassA Default Text", MyTextPropertyChanged));

    private static void MyTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Console.WriteLine("ClassA.MyTextPropertyChanged: " + e.NewValue);
    }

    public ClassA()
    {
    }
}

ClassB派生自ClassA

static ClassB()
{
    MyTextProperty.AddOwner(typeof(ClassB), new PropertyMetadata(MyTextPropertyChanged));
}

private static void MyTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    Console.WriteLine("ClassB.MyTextPropertyChanged: " + e.NewValue); // This is just reached once at startup!
}

public ClassB()
{
    InitializeComponent();            
}

<local:ClassA 
         xmlns:local="clr-namespace:CustomControlPlayground.CustomControls"
         x:Class="CustomControlPlayground.CustomControls.ClassB"
         x:Name="ClassBXAML">

    <Grid>
        <TextBlock Text="{Binding MyText, ElementName=ClassBXAML}"/>
    </Grid>
</local:ClassA> 

更新

到目前为止我得到的所有答案似乎都是正确的,但对我的问题没有帮助。我认为问题可能在于我绑定派生类的方式。可能是这样,我的ClassB MyClassB总是被转换为ClassA吗?

在我的MainWindow.xaml:

<Window.Resources>
    <cc:ClassB x:Key="MyClassB" MyText="MyClassBText"/>
</Window.Resources>
<Grid>
    <cc:MyCustomButton MyClassA="{StaticResource MyClassB}" Width="100" Height="100"/>
</Grid> 

在MyButton风格中:

<StackPanel Background="Transparent">
    <cc:ClassA x:Name="ButtonClassA" Content="{Binding MyClassA, RelativeSource={RelativeSource TemplatedParent}}" />
</StackPanel>

3 个答案:

答案 0 :(得分:0)

我看到的第一件事是,如果没有指定其他内容,依赖属性将设置为BindsTwoWayByDefault=false。因此,您只有从源到目标的绑定。要启用依赖项属性允许从目标到源的绑定,您必须在依赖项属性创建时声明:

public static readonly DependencyProperty MyTextPropertyChanged =
    DependencyProperty.Register
    (
            "MyTextProperty",
            typeof(ClassA), 
            typeof(string),
            new FrameworkPropertyMetadata
            (
                    default(string), 
                    //the next line enables the binding back to source:
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    MyTextPropertyChanged
            )
    );

public static void MyTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs)
{
    //handling changed event
}

检查您的示例项目后,发现您遗漏了style和/或ClassA的{​​{1}}。只需删除ClassB的ClassB,只需使用C#代码并在XAML中添加以下行即可:

App.xaml

从ClassB中删除<Application.Resources> <Style TargetType="{x:Type customControls:ClassA}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type customControls:ClassA}"> <TextBlock Text="{Binding MyText, RelativeSource={RelativeSource TemplatedParent}}"/> </ControlTemplate> </Setter.Value> </Setter> </Style> </Application.Resources> - 文件,只使用C#代码。

最后,我建议你在ClassA中写下以下的ClassB:

XAML

答案 1 :(得分:0)

您的触发器未更新MyText实例的ClassB属性;它正在更新ClassA实例。看看你的模板:

<ControlTemplate TargetType="{x:Type l:MyCustomButton}">
  <Border BorderBrush="{TemplateBinding BorderBrush}"
          BorderThickness="{TemplateBinding BorderThickness}">
    <StackPanel Background="Transparent">
      <l:ClassA x:Name="ButtonClassA"
                Content="{Binding MyClassA,
                                  RelativeSource={RelativeSource TemplatedParent}}" />
    </StackPanel>
  </Border>

  <ControlTemplate.Triggers>
    <Trigger Property="IsMouseOver"
              Value="true">
      <Setter TargetName="ButtonClassA"
              Property="MyText"
              Value="HOVER" />
    </Trigger>
    <Trigger Property="IsPressed"
              Value="true">
      <Setter TargetName="ButtonClassA"
              Property="MyText"
              Value="PRESSED" />
    </Trigger>
  </ControlTemplate.Triggers>
</ControlTemplate>

您的触发器会更新MyText的{​​{1}}属性,这是ButtonClassA个实例。您的ClassA实例是ClassB Content ,但您永远不会更改其属性。由于您永远不会更改ButtonClassA实例上的MyText,因此只会调用一次更改处理程序:实例化ClassB资源时。

请注意,您确实不应将MyClassB用作共享资源,因为UIElement只能加载到单个可视树中;它不能拥有两个所有者。要查看问题的实际效果,请向UIElementClassA实例添加第二个相同的StackPanel实例。


作为挑剔者,当您在MyClassB中添加回调时,应使用OverrideMetadata代替AddOwner。如果要覆盖祖先类声明的属性的元数据,请使用ClassB。如果要重新使用由基类层次结构之外的类声明的属性,请使用OverrideMetadata。例如,AddOwnerPanel位于不同的层次结构中,但它们共享相同的Control属性,因为Background重新使用Control声明的属性:< / p>

Panel

您不需要使用public static readonly DependencyProperty BackgroundProperty = Panel.BackgroundProperty.AddOwner( typeof(Control), new FrameworkPropertyMetadata( Panel.BackgroundProperty.DefaultMetadata.DefaultValue, FrameworkPropertyMetadataOptions.None)); ,因为您没有将该属性附加到新类;您已经从基类继承了该属性,因此您只需要使用AddOwner

答案 2 :(得分:0)

据我所知,这应该可以解决问题:

static ClassB()
{
    MyTextProperty.OverrideMetadata(
        typeof(ClassB),
        new PropertyMetadata("ClassB Default Text", MyTextPropertyChanged));
}

在此,您使用指定MyTextProperty的新元数据作为属性更改处理程序,覆盖ClassB类型的ClassB.MyTextPropertyChanged元数据。只需确保在两种情况下(ClassAClassB)元数据的类型相同,无论是PropertyMetadataFrameworkPropertyMetadata还是任何其他类型。

请注意,只要您使用ClassA.MyTextPropertyChanged的实例,此解决方案就会停止调用ClassB。如果你想添加新的处理程序而不是覆盖旧的处理程序,你可以这样做:

public ClassB()
{
    DependencyPropertyDescriptor.FromProperty(MyTextProperty, typeof(ClassB))
        .AddValueChanged(this, MyHandler);
}

private void MyHandler(object sender, EventArgs e)
{
    //your handler code
}