Aim is to Reuse this MultiBinding in Various Places
<MultiTrigger.Conditions>
<Condition Value="True">
<Condition.Binding>
<MultiBinding Converter="{StaticResource ValidationBooleanConverter}">
<Binding X" />
<Binding Y" />
<Binding Z" />
</MultiBinding>
</Condition.Binding>
</Condition>
</MultiTrigger.Conditions>
Current not very DRY conditions (n+)
<Style x:Key="AnErrorTemplate" TargetType="FrameworkElement">
<Style.Triggers>
<MultiTrigger>
<!-- Repeating the whole MultiTrigger.Conditions block here --->
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource DirtyErrorControlTemplate}" />
</MultiTrigger>
</Style.Triggers>
<Style x:Key="AnotherStyle" TargetType="FrameworkElement">
<Style.Triggers>
<MultiTrigger>
<!-- Repeating the whole MultiTrigger.Conditions block here --->
<Setter Property="Other" Value="SomeValueIfTheSameConditionsAreTrue" />
</MultiTrigger>
</Style.Triggers>
In fact, the requirements are broader, because I also need to re-use these same conditions in ControlTemplates.
<ControlTemplate x:Key="InlineErrorControlTemplate" TargetType="{x:Type Control}">
<TextBlock Text="{Binding ElementName=InputView, Path=(Validation.Errors)[0].ErrorContent}" Foreground="Red"/>
<ControlTemplate.Triggers>
<MultiTrigger>
<!-- Repeating the whole MultiTrigger.Conditions block here --->
<Setter Property="Visibility" Value="SetMeIfTheseSameTriggerConditionsHold" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
An umbrella for easy maintenance?
Any ideas how I could specify the MultiTrigger.Conditions or the MultiBinding once only, and then use it in multiple styles and control templates?
In XAML only?
This is exactly why Style inheritance was supplied in WPF. Plus in your case I will recommend to use DataTrigger with MultiBinding instead of MultiTrigger with MultiBinding ...
To demonstrate this lets assume I am creating a style that checks if the Tooltip or Name assigned to any FrameworkElement is empty. If so, it will make the Foreground red if the FrameworkElement is a ComboBox or its Background yellow if the FrameworkElement is TextBox.
I am using the miscellaneous field Tag of FrameworkElement....
So an all XAML (plus a C# converter) solution to this is as follows...
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Height="300" Width="300">
<Window.Resources>
<local:AtleastOneEmptyConverter x:Key="AtleastOneEmptyConverter"/>
<Style x:Key="BaseFrameworkElementStyle"
TargetType="{x:Type FrameworkElement}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource AtleastOneEmptyConverter}">
<Binding Path="ToolTip"
RelativeSource="{RelativeSource Self}"/>
<Binding Path="Name"
RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Tag" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ApplyToComboStyle"
TargetType="{x:Type ComboBox}"
BasedOn="{StaticResource BaseFrameworkElementStyle}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Tag,
RelativeSource={RelativeSource Self}}" Value="1">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ApplyToTextBoxStyle"
TargetType="{x:Type TextBox}"
BasedOn="{StaticResource BaseFrameworkElementStyle}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Tag,
RelativeSource={RelativeSource Self}}" Value="1">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<ComboBox Style="{StaticResource ApplyToComboStyle}"
x:Name="NotRedComboBox"
ToolTip="You will not see red text here">
<ComboBoxItem IsSelected="True">I am not Red!</ComboBoxItem>
</ComboBox>
<ComboBox Style="{StaticResource ApplyToComboStyle}">
<ComboBoxItem IsSelected="True">I am Red!</ComboBoxItem>
</ComboBox>
<Separator Margin="5"/>
<TextBox Style="{StaticResource ApplyToTextBoxStyle}"
Text="I am yellow"
x:Name="YellowTextBox"/>
<TextBox Style="{StaticResource ApplyToTextBoxStyle}"
Text="I am not yellow"
x:Name="NotYellowTextBox"
ToolTip="You will not see yellow background here"/>
</StackPanel>
</Window>
C# Converter:
public class AtleastOneEmptyConverter : IMultiValueConverter
{
public object Convert
(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
return values.Cast<string>().Any(val => string.IsNullOrEmpty(val));
}
public object[] ConvertBack
(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I've been in this situation many times where I want to reuse commonly used Bindings that set Converters, Validation Rules within those Converters, Notifying on Source/Target update, etc. by inheriting from Binding and reusing them by specifying:
Foreground="{b:CustomColorBinding CustomDateTime}"
to reduce the amount of XAML that I need to type for bindings, but don't believe there's a way to keep it all in XAML. Trying to set the x:Key and use it as you would a Static or Dynamic Resource isn't possible, unfortunately. I've become so accustomed to using this convention that I've created a BindingCatalog for my projects to store my common (multi) binding scenarios for different controls that bind to different types.
I'm sure you have good reasons to want to avoid code-behind but if you can create your MultiBinding in code once and reuse it to DRY up your XAML -- then I think it more than justifies the (overly-demonized, IMHO) code that will be required to do so.
Hope that helps in deciding what to do!
An extended binding
namespace MyNamespace
{
public class CustomBinding : Binding
{
public CustomBinding(String path)
: base(path)
{
this.Converter = new CustomValueConverter();
this.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
this.ValidatesOnDataErrors = true;
}
}
}
Its XAML usage
<UserControl x:Class="MyNamespace.UserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace">
<StackPanel>
<TextBox Text="{local:CustomBinding MyViewModelProperty}"/>
</StackPanel>
</UserControl>
Related
I have TabControl which is used as a main element of my app:
Every tab content would have a title (duplicate to the menu name), separater and content. My XAML code which must correspond to this view should be like this:
<TabControl Style="{StaticResource MyCustomStyle}">
<TabItem Header="Menu 1">
<TabItem.Content>
...
</TabItem.Content>
</TabItem>
<TabItem Header="Menu 2">
<TabItem.Content>
<TextBlock>Content</TextBlock>
</TabItem.Content>
</TabItem>
...
</TabControl>
To avoid duplicating parts of the code, I decided to setup view in custom style.
MyCustomStyle for this:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="Content">
<Setter.Value>
<StackPanel Margin="10">
<Label FontSize="20" Content="..."/>
<Separator/>
<ContentPresenter ContentSource="..."/>
</StackPanel>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>
The only problem occurs with the modification of content. Label don't want to bind value to the TabItem's header. The same story for ContentPresenter
I tried to use RelativeSource for this, but this is not working:
<Label FontSize="20" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}"/>
If I understand correctly, you're very close -- you just want to give it a ContentTemplate instead of setting the Content in the style:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Margin="10">
<Label FontSize="20" Content="..."/>
<Separator/>
<ContentControl
Content="{Binding}"
/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>
Now, for the Label content. You can't get that via RelativeSource AncestorType because TabItem isn't in the visual tree: If you write a VisualTreeHelper.GetParent() loop, you find that the parent chain hits a bunch of random stuff like Grids and whatnot, then suddenly it's on TabControl.
So what we do instead, is we write a multi-value converter. We give it the DataContext for the DataTemplate -- that's the Content for the TabItem being ContentTemplated -- and the TabControl. Then we look through the TabControl's Items to find the TabItem that has the same Content.
I tried this as a regular valueconverter, just passing in {RelativeSource Self} from the Label, walking the visual tree inside the converter to find the TabControl, and using the Label's DataContext inside the converter to identify the TabItem I wanted. That didn't work because of (I think) virtualization: The DataTemplate is instantiated once and those controls are reused. Since the value of {RelativeSource Self} is the same each time, and I didn't tell the Binding anything about DataContext, the value converter was only invoked for the first TabItem that was ever selected. The multi-value converter fixes that problem by explicitly binding to ., the DataContext.
This breaks if you use ItemsSource to populate the TabControl. In fact, it breaks if you populate the TabControl with anything but TabItems just like you did. But then, you couldn't be setting their Header properties otherwise, and if you were using ItemsSource, you'd have all kinds of good things to bind to and you wouldn't be contemplating nutty expedients like this one.
TabItemHeaderConverter.cs
public class TabItemHeaderConverter : IMultiValueConverter
{
// This is pretty awful, but nobody promised life would be perfect.
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var tc = values[0] as TabControl;
var tabItemContent = values[1];
var tabItem = tc.Items.Cast<TabItem>().FirstOrDefault(ti => ti.Content == tabItemContent);
if (null != tabItem)
{
return tabItem.Header;
}
return "Unknown";
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And the new XAML:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<local:TabItemHeaderConverter x:Key="TabItemHeaderConverter" />
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Margin="10">
<Label
FontSize="20"
>
<Label.Content>
<MultiBinding Converter="{StaticResource TabItemHeaderConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=TabControl}" />
<Binding Path="." />
</MultiBinding>
</Label.Content>
</Label>
<Separator />
<ContentControl
Content="{Binding}"
/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>
I have a UserControl with a TextBox inside. I want to show a message at the UserControl level when there is an error at the TextBox Level. And show too an error message at TexBox level.
We are not using MVVM. This is a reusable control.
However, this is a reusable control, and the people using it, will be likely using MVVM themselves.
They would add it to their View like this:
<myUserControlNamespace:MyUserControl x:Name="control1" Value="{Binding Value}" OtherProperty="{Binding OtherValue}" />
My Control inside has a TexBox, which is binded to "Value". Value it is a Double, so when the user inputs a letter on the TexBox an error is thrown. My TextBox is binded similar to this:
<TextBox Grid.Column="1" x:Name="valueTextBox" Style="{DynamicResource ValidatingTextBox}"
Validation.ValidationAdornerSite="{Binding ElementName=valueTextBox}"
Validation.ValidationAdornerSiteFor="{Binding}" >
<TextBox.Text>
<MultiBinding Converter="{StaticResource MyConverter}"Mode="TwoWay" >
<Binding RelativeSource="{RelativeSource AncestorType={x:Type unitConversion:UnitConversionControl}}" UpdateSourceTrigger="PropertyChanged" Path="Value" Mode="TwoWay" />
<Binding RelativeSource="{RelativeSource AncestorType={x:Type unitConversion:UnitConversionControl}}" Path="OtherProperty"/>
</MultiBinding>
</TextBox.Text>
The Style ValidatingTexBox has something like this:
<Style x:Key="ValidatingTextBox" TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<AdornedElementPlaceholder />
<TextBlock Foreground="Red" Text="{Binding ErrorContent}" Height="16" Margin="2"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background"
Value="LightPink" />
<Setter Property="Foreground"
Value="Black" />
<Setter Property="Margin" Value="5,5,5,28"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
I would like the users of my client to use my control in a similar way that I use the TextBox. Setting MyUserControl style, and using the Validation.ErrorTemplate to show the errors.
When there is an error in the TexBox I would like my Control to have an error too, so the people using my control could handle it, for example by using an Validation.ErrorTemplate.
Is there a way to "cascade up" the errors?
I read this SO question, he is using INotifyDataErrorInfo, I can't because I am using .Net 4.0, but I read that I could instead use IDataErrorInfo. I would have to make my control implement IDataErrorInfo. Is this correct?
Let me know if I need to add some more detail.
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?
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.
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.
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>
EDIT: So, it turns out that it was a problem with the code in the VM (embarrassingly enough checking on a property that always returned true [after a refactoring session] ) - I'd kind of assumed that I'd buggered up the databinding as that's the usual suspect (for me at least)
Thank you for all the help, and apologies for wasting your time.
Hi, I'm trying to get this to simply change text colour to either Red or Green depending on a boolean Dependency Property in the viewmodel. The triggers are where the problem is... I think?
<TextBlock>
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsNegativeChange}" Value="true">
<Setter Property="TextBlock.Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding IsNegativeChange}" Value="false">
<Setter Property="TextBlock.Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="ReturnedData.Change" />
<Binding Path="ReturnedData.ChangePercentage" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The IsNegativeChange is a member of the ViewModel object itself and so it doesn't need the 'ReturnedData' qualification.
As it stands, the text always appears as green. The ViewModel is correctly returning true/false depending on input.. Help! Is there something stupid I'm missing?
[edited for formatting]
Edit, in the debug window it says:
BindingExpression:Path=IsNegativeChange; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'NoTarget' (type 'Object')
Isn't the target set by the ??
The triggers look fine to me, does the output window in Visual Studio show any binding errors?
If not maybe this is a case where the value of the trigger is overwritten, see this article about dependency property value precedence for more information. If you set the value explicitly to green somewhere the trigger will not do anything.
You can probably do away with the second trigger like so:
<Style>
<Setter Property="TextBlock.Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=DataContext.IsNegativeChange}" Value="false">
<Setter Property="TextBlock.Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
That doesn't explain why one works and the other doesn't though.
I think your problem might be having the Style inline with the element. The Binding error message in your Console indicates that the binding target is being obscured inside your Style. However, you mention that adding another Label element with a binding shows the correct value.
I would also consider converting to a known default in your style, instead of two opposing triggers.
Try defining the style outside the TextBlock --
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Setter Property="TextBlock.Foreground" Value="Green" />
<DataTrigger Binding="{Binding IsNegativeChange}" Value="True">
<Setter Property="TextBlock.Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="ReturnedData.Change" />
<Binding Path="ReturnedData.ChangePercentage" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
Hope that helps!
I have a collection of objects bound to a hierarchical data template, each of my objects have a property on them (lets call it Property "A") that is of a certain type. This type varies among each of the objects.
If the data template contains an image and some text, what would be the best way to change the image that is displayed in the template based on the type of property "A".
I know I could just stick this into a converter and do the binding translation manually in code, but with all the binding facilities available in WPF, I think theres probably a better way.
It's pretty simple to do this within your data template, if you create local data templates and use a ContentPresenter. This template presents objects of type MyObject, displaying an image whose source is determined by the type of the A property next to a TextBlock that displays the content of the Text property:
<DataTemplate DataType="{x:Type MyObject}">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type Thing1}">
<Image Source="thing1.png"/>
</DataTemplate>
<DataTemplate DataType="{x:Type Thing2}">
<Image Source="thing2.png"/>
</DataTemplate>
</StackPanel.Resources>
<ContentPresenter Content="{Binding A}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
If you want to use styles to do this instead, you're going to run into a problem, because data triggers want to look at property values, and the type of the A property is not, itself, exposed as a property.
Unless, of course, you implement one:
public Type AType { get { return A.GetType(); } }
(You'll also need to raise PropertyChanged for AType when the value of A changes.) Once you've done this, you should be able to implement a data trigger in a style, e.g.:
<Style TargetType="Image">
<Setter Property="Source" Value="default.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding AType}" Value="{x:Type Thing1}">
<Setter Property="Source" Value="thing1.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding AType}" Value="{x:Type Thing2}">
<Setter Property="Source" Value="thing2.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
I think You can do that with triggers.
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding TheProperty}" Value="TheValue">
<Setter Property="Source" Value="NewPath"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
DataTemplateSelector doesn't seem to be a good choice here since you have the same template for all values of A.
Use DataTriggers:
<DataTemplate>
<StackPanel>
<Image x:Name="image" />
<TextBlock>Your text</TextBlock>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=A}" Value="ValueToCheck1">
<DataTrigger.Setters>
<Setter Property="Source" Value="Image1.png" TargetName="image" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=A}" Value="ValueToCheck2">
<DataTrigger.Setters>
<Setter Property="Source" Value="Image2.png" TargetName="image" />
</DataTrigger.Setters>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Haven't tested it, but the idea is like that.