3

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?

3 Answers 3

1
+50

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 in 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)
  • 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 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.

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.

Sign up to request clarification or add additional context in comments.

10 Comments

I prefer this one over the other solution, not only because this also provides me a simpler RelativeSource, but also actually tells me it outside of the comments.
However, I've got another problem. I need to do it on the label (because in reality it's a Label in a VisualBrush in a TextBox, with more than one property needing to be dynamic). This kind of works on the label, but it only shows the default value. I've got everything as a DynamicResource. Thanks for your help though!
Okay, well, I thought that might be the case. I think you will probably want to do something with a DataTemplate (maybe even DataTemplateSelector). I'll edit the answer to elaborate.
Few questions: what's a DataTemplateSelector? And yeah, maybe a DataTemplate's what's right.
A DataTemplateSelector is a class you write that is given the item to template, and the container for it (in your case the button), and it returns a DataTemplate. These can be defined in XAML and referenced, or built in code (a pain in the butt). You can use whatever criteria you need to select a template, making it more powerful than what can be done in XAML alone. Learn more here: msdn.microsoft.com/en-us/library/…
|
1

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.

6 Comments

I copied your code to a new project, replaced your binding code and it worked. If you do not mind you can send me a small project. Saying "Does not work" is easy but does not help solving the problem, right?
It is. However it was tried and it didn't work. I'll try to clean and rebuild.
Let's clear what you expected first. In your code, the attached String is set to "Default!" initially. When UI shows up, Label shows "TESTING" since you gave it the value in initialization of the button. When you click the button, the Label shows nothing since you do not give it a content outside the trigger. Am I correct?
Not really, it was just to see if either of them showed up. Also, cleaned and rebuilt the solution. No luck.
I shared my project here: skydrive.live.com/redir?resid=99DD12AC5EE8E440!236 You can take a look to see whether it is what you expect.
|
0

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> 

7 Comments

Not only does that stop the button from having the usual animations (and a weird border too), it also doesn't do what I want it to do...?
Ok, what do you exactly want? The resource can not just use a TemplateBinding, so you do not see the value. Use hacks to make Binding resource.
It's throwing build errors, is your second solution. Also, even if you fix it, that ruins the point of XAML: XAML's meant to be about separating the view and the model (MVVM).
For me, the second solution works, personally checked. As for separating the view and the model (MVVM) I completely agree... but in the example you want to use resources from the bindings, but it does not support the standard XAML because the resource is not part of the visual tree, or part of the template so you have to look for alternatives. One example of an alternative, and I'll show... there may be several. I can advise not to use Binding in resources, or to seek other alternatives if you do not like mine.
Sorry, my fault for forgetting to clean the solution. It works. However, I can't understand the rest of the comment... sorry.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.