简体   繁体   English

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

[英]How to make a WPF resource be recalculated when a trigger's run?

In short: I've got a Style . 简而言之:我有一个Style It uses TemplateBinding a fair bit to make it parametrized instead of repeating myself over and over again. 它使用TemplateBinding使其参数化而不是一遍又一遍地重复自己。 However, when a trigger for that style gets used and a resource gets used in a setter in that trigger, it just doesn't show up! 但是,当使用该样式的触发器并且在该触发器中的setter中使用资源时,它就不会显示出来! Not even the default value gets shown. 甚至没有显示默认值。 Here's a small program that replicates this issue: 这是一个复制此问题的小程序:

TestDictionary.xaml 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 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 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!"));
    }
}

Instead of using a TemplateBinding , I also tried this: 我没有使用TemplateBinding ,而是尝试了这个:

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

It still didn't work. 它仍然无法正常工作。 I know I'm probably doing something wrong, but the question is: what is it? 我知道我可能做错了什么,但问题是:它是什么?

Now I see the details. 现在我看到了细节。 What you should write before relative source is like: 在相对来源之前你应该写的是:

Binding Path=(lcl:TestClass.String)

Do not forget to add parenthesis. 别忘了添加括号。

All you really need to make this work is to use RelativeSource in your binding. 您真正需要做的就是在绑定中使用RelativeSource Since you are setting the attached property on the Button , in your style trigger, you can just bind to the attached property on self: 由于您在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>

One cool thing about using your approach, since Button is a ContentControl , you're attached property can be any object, not just strings. 关于使用你的方法的一个很酷的事情,因为Button是一个ContentControl ,你附加的属性可以是任何对象,而不仅仅是字符串。

And to clarify what went wrong in your previous approach - 并澄清你以前的方法出了什么问题 -

  • As others have said, TemplateBinding only works in ControlTemplates . 正如其他人所说, TemplateBinding仅适用于ControlTemplates It also only works when the DependencyProperty is defined on the class you are creating the template for (so you can never do a TemplateBinding to Grid.Row for example) 它也适用于在为您创建模板的类上定义DependencyProperty时(例如,您永远不能对Grid.Row执行TemplateBinding
  • When binding to an attached property, the whole thing needs to be in parentheses, otherwise WPF will try to bind to a property of a property. 绑定到附加属性时,整个事物需要在括号中,否则WPF将尝试绑定到属性的属性。 Otherwise your RelativeSource binding was close! 否则你的RelativeSource绑定很接近!
  • I think if you want to have a Label inside the Button as the content, it may work (I didn't test that), but it doesn't seem like the best idea, as your Button can host any object you want. 我想如果你想在Button里面有一个Label作为内容,它可能会起作用(我没有测试过),但这似乎不是最好的主意,因为你的Button可以托管你想要的任何对象。

EDIT for more complex example 编辑更复杂的例子

So, if you need to display more than one dynamic property, I would recommend using a DataTemplate : 因此,如果您需要显示多个动态属性,我建议您使用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>

Also, I want to point out that a DataTemplateSelector might be more applicable if you have multiple different criteria for changing the look of the content. 此外,我想指出,如果您有多个不同的标准来更改内容的外观, DataTemplateSelector可能更适用。

Your example does not work because TemplateBinding only works in a ControlTemplate . 您的示例不起作用,因为TemplateBinding仅适用于ControlTemplate To achieve something akin to a TemplateBinding in Resources you need to do other stuff. 要在Resources实现类似于TemplateBinding东西,你需要做其他的事情。 Here's an example. 这是一个例子。

In order for TemplateBinding to work, you need to fix the code a little bit (this is just an example with no resources): 为了使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>

Useful links about this topic: Here, and here too. 关于这个主题的有用链接: 这里, 也是这里。

EDIT: 编辑:

You can also use the application settings instead of TestClass . 您还可以使用应用程序设置而不是TestClass Open "Project -> Properties: MyNamespace... -> Settings" and add your settings: 打开“项目 - >属性:MyNamespace ... - >设置”并添加您的设置:

Name -------- Type -------- Scope -------- Value 名称 -------- 类型 -------- 范围 --------

LabelText---string--------User----------Default LabelText的---弦-------- ----------用户默认

Set the your value for the LabelText in code. 在代码中设置LabelText的值。 For example: 例如:

    public MainWindow()
    {
        InitializeComponent();

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

And use this ResourceDictionary: 并使用此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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM