如何在触发器运行时重新计算WPF资源?

时间:2013-06-22 17:51:52

标签: c# wpf styles templatebinding dynamicresource

简而言之:我有Style。它使用TemplateBinding一点点来使其参数化,而不是一遍又一遍地重复自己。但是,当使用该样式的触发器并且在该触发器中的setter中使用资源时,它就不会显示出来!甚至没有显示默认值。这是一个复制这个问题的小程序:

TestDictionary.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:lcl="clr-namespace:MyNamespace">
    <Style TargetType="Button" x:Key="BtnTest">
        <Style.Resources>
            <Label Content="{TemplateBinding lcl:TestClass.String}" x:Key="innerLabel"/>
        </Style.Resources>
        <Style.Triggers>
            <Trigger Property="IsEnabled" Value="True">
                <Setter Property="Content" Value="{DynamicResource innerLabel}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

MainWindow.xaml

<Window x:Class="MyNamespace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lcl="clr-namespace:MyNamespace"
        Title="Test" Width="500" Height="350">
    <Window.Resources>
        <ResourceDictionary Source="TestDictionary.xaml"/>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Content="Enable/Disable" Click="Click"/>
        <Button Grid.Column="1" x:Name="btn" Style="{DynamicResource BtnTest}" lcl:TestClass.String="TESTING"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace MyNamespace
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void Click(object sender, RoutedEventArgs e)
        {
            btn.IsEnabled = !btn.IsEnabled;
        }
    }
    public class TestClass
    {
        public static string GetString(DependencyObject obj)
        {
            return (string)obj.GetValue(StringProperty);
        }

        public static void SetString(DependencyObject obj, string value)
        {
            obj.SetValue(StringProperty, value);
        }
        public static readonly DependencyProperty StringProperty =
            DependencyProperty.RegisterAttached("String", typeof(string), typeof(TestClass), new PropertyMetadata("Default!"));
    }
}

我没有使用TemplateBinding,而是尝试了这个:

{Binding Path=lcl:TestClass.String, RelativeSource={RelativeSource AncestorType={x:Type Button}}}

它仍然无效。 我知道我可能做错了什么,但问题是:它是什么?

3 个答案:

答案 0 :(得分:1)

现在我看到了细节。在相对来源之前你应该写的是:

Binding Path=(lcl:TestClass.String)

不要忘记添加括号。

答案 1 :(得分:1)

您真正需要做的就是在绑定中使用RelativeSource。由于您在Button上设置附加属性,在您的样式触发器中,您只需绑定到self上的附加属性:

<Style TargetType="Button" x:Key="BtnTest">
    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="True">
            <Setter Property="Content" 
                    Value="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource Self}}"/>
        </Trigger>
    </Style.Triggers>
</Style>

使用您的方法很酷,因为ButtonContentControl,您附加的属性可以是任何对象,而不仅仅是字符串。

并澄清你之前的方法出了什么问题 -

  • 正如其他人所说,TemplateBinding仅适用于ControlTemplates。只有在为您创建模板的类上定义DependencyProperty时,它才有效(例如,您永远不能TemplateBindingGrid.Row
  • 当绑定到附加属性时,整个事物需要在括号中,否则WPF将尝试绑定到属性的属性。否则你的RelativeSource绑定很接近!
  • 我想如果你想在Label里面有一个Button作为内容,它可能有用(我没有测试过),但这似乎不是最好的主意,因为您的Button可以托管您想要的任何对象。

编辑更复杂的例子

因此,如果您需要显示多个动态属性,我建议您使用DataTemplate

<Style TargetType="Button" x:Key="BtnTest">
    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="True">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Label Content="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
                    </DataTemplate>
                </Setter.Value>
            </Setter> 
        </Trigger>
    </Style.Triggers>
</Style>

此外,我想指出,如果您有多个不同的标准来更改内容的外观,DataTemplateSelector可能更适用。

答案 2 :(得分:0)

您的示例不起作用,因为TemplateBinding仅适用于ControlTemplate。要获得类似于TemplateBinding Resources的内容,您需要做其他事情。 Here's an example.

为了让TemplateBinding起作用,你需要稍微修改一下代码(这只是一个没有资源的例子):

<Style x:Key="BtnTest" TargetType="{x:Type Button}">
    <Setter Property="MinHeight" Value="100" />
    <Setter Property="MinWidth" Value="200" />
    <Setter Property="BorderThickness" Value="2" />
    <Setter Property="BorderBrush" Value="Blue" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2" Background="{TemplateBinding Background}">
                    <ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding lcl:TestClass.String}" HorizontalAlignment="Center" VerticalAlignment="Center" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Opacity" Value="0.5" />
        </Trigger>
    </Style.Triggers>
</Style>

有关此主题的有用链接:Here, and here too.

修改

您也可以使用应用程序设置而不是TestClass。打开“项目 - &gt;属性:MyNamespace ... - &gt;设置”并添加您的设置:

命名 --------的类型 --------的范围 ------- - 的

LabelText的--- --------串用户----------默认

在代码中设置LabelText的值。例如:

    public MainWindow()
    {
        InitializeComponent();

        MyNamespace.Properties.Settings.Default.LabelText = "Testing";
    }

并使用此ResourceDictionary:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:properties="clr-namespace:MyNamespace.Properties"
                xmlns:lcl="clr-namespace:MyNamespace">

<Style TargetType="Button" x:Key="BtnTest">
    <Style.Resources>
        <Label x:Key="innerLabel" Content="{Binding Source={x:Static properties:Settings.Default}, Path=LabelText, Mode=TwoWay}" />
    </Style.Resources>

    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="True">
            <Setter Property="Content" Value="{DynamicResource innerLabel}"/>
        </Trigger>
    </Style.Triggers>
</Style>