What I want:
I created a UserControl ImageButton, where the images can be set in WPF. Usually, I'm reusing the default image for all states but one, but have to configure the control in a very verbose way (see WPF snipped below). What I'd like to achieve is, that every image that is not set, just uses the default image.
What I have:
This is the way I'm describing the ImageButton at the moment:
<cc:ImageButton x:Name="cmdHideCustomWindowingArea"
DefaultImage="/Images/UI/ButtonDefault.png"
HoverImage="/Images/UI/ButtonDefault.png"
DownImage="/Images/UI/ButtonActive.png"
UpImage="/Images/UI/ButtonDefault.png"
DisabledImage="/Images/UI/ButtonDefault.png"/>
This is the style im using:
<Style TargetType="{x:Type cc:ImageButton}">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type cc:ImageButton}">
<Grid x:Name="Grid">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image x:Name="ButtonImage" Source="{Binding DefaultImage, RelativeSource={RelativeSource TemplatedParent}}"/>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" />
</StackPanel>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding HoverImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding DownImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding DisabledImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And this is the code:
public class ImageButton : Button
{
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton),
new FrameworkPropertyMetadata(typeof(ImageButton)));
}
public ImageSource DefaultImage
{
get { return (ImageSource)GetValue(DefaultImageProperty); }
set { SetValue(DefaultImageProperty, value); }
}
public static readonly DependencyProperty DefaultImageProperty =
DependencyProperty.Register("DefaultImage",
typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(null));
public ImageSource HoverImage
{
get { return (ImageSource)GetValue(HoverImageProperty); }
set { SetValue(HoverImageProperty, value); }
}
public static readonly DependencyProperty HoverImageProperty =
DependencyProperty.Register("HoverImage",
typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(null));
//
// ... and so on for every state (Down, Up, Disabled)
//
}
What I've tried so far:
1. I already tried checking if the image is set like this, which does not work and the code seems to never even reach the getter at all...
get { return (ImageSource)GetValue(HoverImageProperty) ?? DefaultImage; }
2. Also, setting a TargetNullValue like recommended here does not work ("The member "TargetNullValue" is not recognized or is not accessible."):
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="ButtonImage" Property="Source"
Value="{Binding DownImage, RelativeSource={RelativeSource TemplatedParent}}"
TargetNullValue="{Binding DefaultImage, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
3. The last thing I did was trying to set a PriorityBinding, like recommended here:
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ButtonImage" Property="Source">
<Setter.Value>
<PriorityBinding>
<Binding Path="{Binding HoverImage, RelativeSource={RelativeSource TemplatedParent}}"/>
<Binding Path="{Binding DefaultImage, RelativeSource={RelativeSource TemplatedParent}}"/>
</PriorityBinding>
</Setter.Value>
</Setter>
</Trigger>
But that returns the following error:
A 'Binding' cannot be set on the 'Path' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
What I'll have to do:
What would be the right way to fall back to a another DependencyProperty in case the one I'm looking for is not set?
It sounds as though you are looking for the PriorityBinding Class (despite your earlier attempt at using it). From the linked page, a PriorityBinding:
Describes a collection of Binding objects that is attached to a single binding target property, which receives its value from the first binding in the collection that produces a value successfully.
...
PriorityBinding lets you associate a binding target (target) property with a list of bindings. The first binding that returns a value successfully becomes the active binding.
A binding returns a value successfully if:
1.The path to the binding source resolves successfully.
2.The value converter, if any, is able to convert the resulting value.
3.The resulting value is valid for the target property.
You should rearrange your Binding Path in your PriorityBindings... try this instead:
<PriorityBinding>
<Binding Path="HoverImage" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="DefaultImage" RelativeSource="{RelativeSource TemplatedParent}" />
</PriorityBinding>
For further information, you can also read through the WPF Tutorial - Priority Bindings tutorial on the Tech Pro website.
UPDATE >>>
As you correctly pointed out, the PriorityBinding will indeed accept null as a valid, returned value. However, you can get around that problem by using a custom IValueConverter implementation that returns DependencyProperty.UnsetValue when the actual value is null.
Related
How can I get the SelectedItem of a ListBox to be highlighted after setting the SelectedItem in the ViewModel?
The ItemsSource is bound to an ObservableCollection of Bar (the collection is a member of a class Foo. A button is bound to a command that adds a new empty Bar instance to the collection and then also sets SelectedItem to the new empty instance.
After adding the instance to the collection, the ListBox is updated to show the new blank Bar. However, after setting the SelectedItem property in the ViewModel, the new instance is not highlighted in the ListBox but it is being set and the PropertyChanged event is raised (the SelectedItem is displayed elsewhere in the View).
Additional Details:
INotifyPropertyChanged is implemented in a base ViewModel class, and also implemented in the Foo and Bar classes.
The ListBox contains a custom ItemTemplate to display Bar members, and a custom ItemContainerStyle that modifies the Background for the IsMouseOver trigger.
simplified xaml:
<ListBox ItemsSource="{Binding Path=MyFoo.BarCollection}"
SelectedItem="{Binding Path=SelectedItem,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Button Content="Add New Bar"
Command="{Binding Path=AddBarCommand}"/>
simplified viewmodel:
private Foo _myFoo;
public Foo MyFoo
{
get { return _myFoo; }
set { _myFoo= value; OnPropertyChanged("MyFoo"); }
}
private Bar _selectedItem;
public Bar SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value; OnPropertyChanged("SelectedItem"); }
}
private void AddBar()
{
Bar newBar = new Bar();
MyFoo.BarCollection.Add(newBar);
SelectedItem = newBar ;
_unsavedChanges = true;
}
Your code worked perfectly for me.
Notice that "Bar 3" is selected, but when the listbox doesn't have focus, the default theme on Windows 7 works pretty hard to keep you from noticing. And Windows 7 isn't even the worst of the bunch. Our application uses WhiteSmoke as a default background, and we've had major issues with older users1 being unable to tell if listbox items are selected or not.
Fortunately, it's a trivial fix. These resources could just as easily be in Window.Resources, in a global ListBox style, or in App.xaml. It's up to you how widely you want to apply them.
<ListBox
ItemsSource="{Binding MyFoo.BarCollection}"
SelectedItem="{Binding SelectedItem}"
>
<ListBox.Resources>
<SolidColorBrush
x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
Color="{x:Static SystemColors.HighlightColor}"
Opacity="0.5"
/>
<SolidColorBrush
x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}"
Color="{x:Static SystemColors.HighlightTextColor}"
/>
</ListBox.Resources>
</ListBox>
1 "Older" meaning old enough to vote.
And if your version of .NET predates the existence of SystemColors.InactiveSelectionHighlightTextBrushKey, this could probably use some refinement, but it works:
<ListBox
>
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem"
BasedOn="{StaticResource {x:Type ListBoxItem}}"
>
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid
Background="{TemplateBinding Background}"
>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter
Property="Background"
Value="{StaticResource {x:Static SystemColors.HighlightBrushKey}}"
/>
<Setter
Property="Foreground"
Value="{StaticResource {x:Static SystemColors.HighlightTextBrushKey}}"
/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="IsEnabled" Value="False" />
</MultiTrigger.Conditions>
<Setter
Property="Background"
Value="{StaticResource {x:Static SystemColors.InactiveCaptionBrushKey}}"
/>
</MultiTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I'm having an issue getting the following to work. I have created a subclass of button with several DependencyProperties. and we are attempting to use these in the style.
We have the following c#
/// <summary>
/// Custom Is Mouse Over Colour.
/// </summary>
public static DependencyProperty LITIsMouseOverProperty =
DependencyProperty.RegisterAttached("LITIsMouseOver",
typeof(System.Windows.Media.LinearGradientBrush), typeof(Button));
public static System.Windows.Media.LinearGradientBrush GetLITIsMouseOver(DependencyObject target)
{
return (System.Windows.Media.LinearGradientBrush)target.GetValue(LITIsMouseOverProperty);
}
public static void SetLITIsMouseOver(DependencyObject target, System.Windows.Media.LinearGradientBrush value)
{
target.SetValue(LITIsMouseOverProperty, value);
}
and the following XAML:
<Style x:Key="StandardButton" TargetType="{x:Type Utils:LITCustomButton01}">
<Setter Property="LITIsMouseOver" Value="{StaticResource DarkBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Utils:LITCustomButton01}">
<Border x:Name="buttonBorder" CornerRadius="{TemplateBinding LITCornerRadius}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}">
<TextBlock Name="textBlock" Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{TemplateBinding Foreground}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{TemplateBinding LITIsMouseOver}" />
</Trigger>
</Style.Triggers>
</Style>
We have this same system working on corner radius, using the following:
/// <summary>
/// Custom Corner Radius
/// </summary>
public static DependencyProperty LITCornerRadiusProperty =
DependencyProperty.RegisterAttached("LITCornerRadius",
typeof(CornerRadius), typeof(Button));
public static CornerRadius GetLITCornerRadius(DependencyObject target)
{
return (CornerRadius)target.GetValue(LITCornerRadiusProperty);
}
public static void SetLITCornerRadius(DependencyObject target, CornerRadius value)
{
target.SetValue(LITCornerRadiusProperty, value);
}
However, the background throws the following error:
"member is not valid because it does not have a qualifying type name."
TemplateBinding can only be used in a ControlTemplate. The TemplateBinding shortcut does not work in triggers so you will have to use the RelativeSource binding. When overriding the ControlTemplate you will need to specify the target of the trigger. Something like this:
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background"
TargetName="buttonBorder"
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LITIsMouseOver}"/>
</Trigger>
</ControlTemplate.Triggers>
Also, you are creating an attached property which would allow you to do:
<Grid Utils:LITCustomButton01.LITIsMouseOver="Pink"/>
In this case, nothing will happen on MouseOver. You want to use Register as opposed to RegisterAttached.
I'm new to the styling part of WPF. What I want to do is to get the value of an attached property in a setter, e.g.:
<Trigger Property="SomeProperty" Value="SomeValue">
<Setter Property="SomeProperty"
Value="(My attached property, let's say lcl:MyClass.MyString)"/>
</Trigger>
I know that you can get something to this effect using a {TemplateBinding lcl:MyClass.MyString} in a ControlTemplate. My question is: can you do this in a style, without using a ControlTemplate?
You can try to use:
<Setter Property="SomeProperty" Value="{Binding Path=(lcl:MyClass.MyString), RelativeSource={RelativeSource self}}"/>
if your attached property applies to the element as your style. If not, you can use RelativeSource or ElementName to find the appropriate element.
I am not sure how you have done that since your code lacks of details. Below code works:
<UserControl.Resources>
<Style x:Key="LabelStyle" TargetType="{x:Type Label}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Content"
Value="{Binding Path=(TestWebBrowser:AttachP.ValueEditorState), RelativeSource={RelativeSource self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel>
<Label x:Name="label" TestWebBrowser:AttachP.ValueEditorState="HelloWorld" Style="{StaticResource LabelStyle}"/>
<Button Content="Disable Label" Click="Button_Click"/>
</StackPanel>
Button's click event handler will set Label's IsEnabled to false to trigger the trigger. And note that you have to use Path= with parenthesis in the binding.
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>
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>