问题
In short: I've got a Style
. It uses TemplateBinding
a fair bit to make it parametrized instead of repeating myself over and over again. 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! Not even the default value gets shown. Here's a small program that replicates this issue:
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!"));
}
}
Instead of using a TemplateBinding
, I also tried this:
{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?
回答1:
All you really need to make this work is to use RelativeSource
in your binding. Since you are setting the attached property on the Button
, in your style trigger, you can just bind to the attached property on 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.
And to clarify what went wrong in your previous approach -
- As others have said,
TemplateBinding
only works inControlTemplates
. It also only works when theDependencyProperty
is defined on the class you are creating the template for (so you can never do aTemplateBinding
toGrid.Row
for example) - 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. Otherwise your
RelativeSource
binding was close! - I think if you want to have a
Label
inside theButton
as the content, it may work (I didn't test that), but it doesn't seem like the best idea, as yourButton
can host any object you want.
EDIT for more complex example
So, if you need to display more than one dynamic property, I would recommend using a 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.
回答2:
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.
回答3:
Your example does not work because TemplateBinding
only works in a ControlTemplate
. To achieve something akin to a TemplateBinding
in Resources
you need to do other stuff. 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):
<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
. Open "Project -> Properties: MyNamespace... -> Settings" and add your settings:
Name--------Type--------Scope--------Value
LabelText---string--------User----------Default
Set the your value for the LabelText
in code. For example:
public MainWindow()
{
InitializeComponent();
MyNamespace.Properties.Settings.Default.LabelText = "Testing";
}
And use this 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>
来源:https://stackoverflow.com/questions/17253650/how-to-make-a-wpf-resource-be-recalculated-when-a-triggers-run