WPF Style DataTrigger based on DependencyProperty binding - wpf

I'm working on a WPF form designer where you can drag and drop controls like Labels, TextBox, ComboBox to a design surface, then through a Property Grid user can set data bindings for each control. I have a requirement to show a red background for those controls that don't have a Binding set for a given property.
My original idea was to create a HasBindingConverter that would take the calling element itself and examine if it has a binding to a certain property. In this case TextBox.TextProperty
public class HasBindingConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
FrameworkElement fe = value as FrameworkElement;
if(fe != null)
{
Binding binding = BindingOperations.GetBinding(fe, TextBox.TextProperty);
return binding != null;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then I added a Style associated to TextBox control type to the Resources section of my form which is a UserControl:
<UserControl.Resources>
<Style TargetType="TextBox">
<Style.Resources>
<Converters:HasBindingConverter x:Key="HasBindingConv"/>
</Style.Resources>
<Style.Triggers>
<DataTrigger
Binding="{Binding,
RelativeSource={RelativeSource Self},
Converter={StaticResource HasBindingConv}}"
Value="False">
<Setter Property="TextBox.Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource Self},
Converter={StaticResource HasBindingConv}}"
Value="True">
<Setter Property="TextBox.Background" Value="White" />
</Style.Triggers>
</Style>
So if TextBox does not have a Data Binding set for the TextBox.TextProperty then it will set its background to red. This part works fine, the problem is that when user sets the TextBox.TextProperty binding for this control my Converter is never invoked again so the background remains red.
Anyone knows how to invoke the trigger after setting the binding for this control? Or any other suggestions, I may be approaching the problem in a wrong way.
Thanks!

The reason why this happens, is the Binding won't be invoked again as the source i.e. the TextBox itself is never changed once it is created!
Assume you are focused into a TextBox.
So have the Tag property of the Container (Window / UserControl) set to the current control.
myWindow.Tag = FocusManager.GetFocusedElement(myWindow);
Change the trigger with this Binding.
<DataTrigger
Binding="{Binding,
Path=Tag,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource HasBindingConv}}" .. >
Refresh Tag property of Window.
myWindow.Tag = null;
myWindow.Tag = FocusManager.GetFocusedElement(myWindow);

I found another way to solve this problem.
I created a ControlHasBindingBehavior Attached property setting it initially to false.
public class ControlHasBindingBehavior
{
#region DependencyProperty HasBinding
/// <summary>
/// Registers a dependency property as backing store for the HasBinding property
/// Very important to set default value to 'false'
/// </summary>
public static readonly DependencyProperty HasBindingProperty =
DependencyProperty.RegisterAttached("HasBinding", typeof(bool), typeof(ControlHasBindingBehavior),
new FrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// Gets or sets the HasBinding.
/// </summary>
/// <value>The HasBinding.</value>
public static bool GetHasBinding(DependencyObject d)
{
return (bool)d.GetValue(HasBindingProperty);
}
public static void SetHasBinding(DependencyObject d, bool value)
{
d.SetValue(HasBindingProperty, value);
}
#endregion
}
Then in my FormDesigner View I created a style and Trigger for all TextBoxes so when the Textbox 'HasBinding' attached property is false then it turns background to red:
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Behaviors:ControlHasBindingBehavior.HasBinding" Value="False">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
Finally, when User sets the binding successfully for a given control I set the Attached Property to 'True':
ControlHasBindingBehavior.SetHasBinding(SelectedObject,true);
When this happens my TextBox.Background turns to white again :)
Initially I wanted to apply the style trigger to all UIElements, FrameworkElements or Controls in a generic way but seems like this is not possible according to this thread
Hope someone finds it useful

Related

Cancel binding depending on object type

In my WPF application I have a label which is bound to the length of a Text-property:
<Label Content="{Binding Editor.Text.Length}"/>
The Editor-object may be either a textbox or a checkbox. The textbox does have a Text-property whereas the checkbox does not.
When the Label is bound to a "checkbox-editor" it produces a warning in Visual Studio:
BindingExpression path error: 'Text' property not found on 'object'...
This is expected and I would like to know if there is any way to tell the binding engine not to try to bind this value unless the Editor-object is a textbox?
Is the Editor property of your viewmodel a control? I hope not, but anyway.
You could write a valueconverter that returns the type of the value, and then set the Label's Content via a series of triggers in a Style. If type of Editor is {x:Type TextBox}, set it to the binding you've got above. If it's {x:Type CheckBox}, make it `{Binding Editor.IsChecked}'.
XAML
<Label>
<Label.Style>
<Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=Editor, Converter={local:GetTypeConverter}}"
Value="{x:Type TextBox}"
>
<Setter
Property="Content"
Value="{Binding Text.Length, ElementName=Editor}"
/>
</DataTrigger>
<DataTrigger
Binding="{Binding ElementName=Editor, Converter={local:GetTypeConverter}}"
Value="{x:Type CheckBox}"
>
<Setter
Property="Content"
Value="{Binding IsChecked, ElementName=Editor}"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
C#
public class GetTypeConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Alternatively, your viewmodel could have a readonly property which returns whatever should be in that label, since the viewmodel has Editor and knows what it is. Call it EditorLabelValue for the time being. Presumably Editor is bound to a string property or a bool property, depending on which editor it is. So both of those setters would raise PropertyChanged for "EditorLabelValue", which would return the appropriate value.
I tried to do this in pure XAML by making Editor the Content of a ContentControl and then playing with DataTemplates, but I couldn't find a way to make that work without getting an exception for reparenting Editor.

Wpf bind multitrigger condition null value

What I have is a custom window. Added a bool dependencyproperty. I want to use this dependency property as a condition for my triggers. A way to get around my triggers so to speak. Unfortunately I have propery non-null value exception being thrown. Banging my head with this one. I also tested the dependency property before the binding on the triggers. It never hits the dependency property wrapper. No errors thrown/shown when I do that.
DependencyProperty setup:
/// <summary>
/// The override visibility property
/// </summary>
public static readonly DependencyProperty OverrideVisibilityProperty = DependencyProperty.Register(
"OverrideVisibility", typeof(bool), typeof(MyWindow), new PropertyMetadata(false));
/// <summary>
/// Gets or sets the override visibility.
/// </summary>
/// <value>The override visibility.</value>
public bool OverrideVisibility
{
get
{
return (bool)this.GetValue(OverrideVisibilityProperty);
}
set
{
this.SetValue(OverrideVisibilityProperty, value);
}
}
Trigger setup in style
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="WindowStyle" Value="None" />
<Condition Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=OverrideVisibility}" Value="false" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter TargetName="WindowCloseButton" Property="Visibility" Value="Visible" />
</MultiTrigger.Setters>
</MultiTrigger>
</ControlTemplate.Triggers>
Form xaml Setup:
<local:MyWindow x:Class="MyForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500"
Height="500"
OverrideVisibility="True">
Your error is on this line:
<Condition Binding="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=OverrideVisibility}" Value="false" />
Specifically, this is your error:
AncestorType={x:Type Window}
You probably have an error in your Output Window in Visual Studio that says something like:
Error: No OverrideVisibility property found on object Window
Instead of that Binding, use the name/type of your custom Window... something like this:
AncestorType={x:Type YourPrefix:YourCustomWindow}
Additionally, you said this:
It never hits the dependency property wrapper
It wouldn't... they are just for your use... they're not used by the Framework. If you want to monitor what values are going through a DependencyProperty, then you need to register a PropertyChangedCallback event handler. You can find out more from the Custom Dependency Properties page on MSDN.
UPDATE >>>
Ah, I just noticed the comments. You might still be able to do it if you can declare an Attached Property in an assembly that both your Style and your view have access to. If that's a possibility, then take a look at the Attached Properties Overview page on MSDN to find out how to do that.
Finally, you can bind to an Attached Property like this:
<animation Storyboard.TargetProperty="(ownerType.propertyName)" .../>
This example was from the Property Path Syntax page on MSDN.

UserControl update image based on datatype

I have a program that displays either customer, company or employee info. I would like to display an icon next to this image with the icon changing based on the type of info I am displaying (customer, company or employee).
I have the following setup in my resource dictionary to specify the images:
<ImageSource x:Key="CompanyIcon">../Images/companies_32.png</ImageSource>
<ImageSource x:Key="EmployeeIcon">../Images/employee_32.png</ImageSource>
<ImageSource x:Key="CustomerIcon">../Images/customer_32.png</ImageSource>
In my viewmodel I would like to assign the image based on what data type I am working with. For instance if I am viewing a company's info (a DBContext of type 'Company' using EF 4.5) I want to set the image to that of 'CompanyIcon'.
How would I assign the image with the viewmodel (and change it as I change between a 'Company', 'Employee' or 'Customer' DBContext type) and then bind this image to a placeholder in the view (it will be displayed within a grid column).
I would use a DataTrigger that sets the Image.Source based on the object type, and use a Converter that returns the typeof(value) for getting the type
<Style x:Key="MyStyle" TargetType="{x:Type Image}">
<!-- Default Value -->
<Setter Property="Source" Value="{StaticResource CompanyIcon}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}}"
Value="{x:Type local:Employee}">
<Setter Property="Source" Value="{StaticResource EmployeeIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}}"
Value="{x:Type local:Customer}">
<Setter Property="Source" Value="{StaticResource CustomerIcon}" />
</DataTrigger>
</Style.Triggers>
</Style>
The converter I usually use just looks like this:
public class ObjectToTypeConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return null;
return value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
What I did was to have a string property in the VM pointing to the image location (don't know if it's the best approach, but it worked quite well for me):
private string _imageSource;
public string ImageSource
{
get
{
return _imageSource;
}
set
{
_imageSource = value;
NotifyPropertyChanged(() => ImageSource);
}
}
public void SetImage()
{
If (customer)
ImageSource = "../Images/companies_32.png";
...
}
in XAML:
<Image Source="{Binding ImageSource}" .../>

Select control style by binding value

I have an editor view that can be used for multiple edited objects. The view model for multiple objects provides a property like Field1Multiple of type bool for each field that needs to be handled. In this case, it's only ComboBox controls for now. Whenever multiple differing values shall be indicated for that field, a certain style should be applied to that control which is defined in App.xaml. That style changes the background of the control to visualise that there is no single value that can be displayed here.
I've tried with this XAML code:
<ComboBox
ItemsSource="{Binding Project.Field1Values}" DisplayMemberPath="DisplayName"
SelectedItem="{Binding Field1}">
<ComboBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Field1Multiple}" Value="true">
<Setter
Property="ComboBox.Style"
Value="{StaticResource MultiValueCombo}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
But it doesn't work because I cannot set the Style property from inside a Style. If I use triggers directly on the control, there may only be EventTriggers, no DataTriggers, the compiler says.
How can I set the control's style based on a binding value? Or, how can I set a certain style for a control if a binding value is true?
(EDIT to full solution)
You can use converter:
public class AnyIsMultipleToStyle : IValueConverter
{
public Style NormalStyle { get; set; }
public Style MultiStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
IList<SampleClass> list= value as IList<SampleClass>;
if (list!=null)
{
if (list.Any(i => i.Multi))
return MultiStyle;
}
}
return NormalStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in your xaml:(You indicate normal style and multistyle to converter)
<Window.Resources>
<Style x:Key="MultiValueCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Olive" />
</Style>
<Style x:Key="NormalCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Red" />
</Style>
<my:AnyIsMultipleToStyle x:Key="AnyIsMultipleToStyle1" MultiStyle="{StaticResource MultiValueCombo}" NormalStyle="{StaticResource NormalCombo }" />
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Items, ElementName=root}" >
<ComboBox.Style>
<Binding Converter="{StaticResource AnyIsMultipleToStyle1}" Path="Items" ElementName="root" >
</Binding>
</ComboBox.Style>
</ComboBox>
</Grid>

WPF/XAML - compare the "SelectedIndex" of two comboboxes (DataTrigger?)

i've got two comboboxes with the same content. the user should not be allowed to choose the same item twice. therefore the comboboxes' contents (= selectedindex?) should never be equal.
my first attempt was to comapare the selectedindex with a datatrigger to show/hide a button:
<DataTrigger Binding="{Binding ElementName=comboBox1, Path=SelectedIndex}" Value="{Binding ElementName=comboBox2, Path=SelectedIndex}">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
it seems that it is not possible to use Value={Binding}. is there any other way (if possible without using a converter)? thanks in advance!
Option 1
You could use ValidationRules -- it can also be done in XAML, and will work for a one-off situation. It would be extremely localized and not something I would recommend since the rule would not be reusable. Maybe somebody else can come up with a generic rule to encompass different inputs. Try this out.
<ComboBox>
<ComboBox.SelectedValue>
<Binding Path="Whatever" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:ComparisonValidationRule />
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedValue>
</ComboBox>
And maybe the ComparisonRule looks like this, and it would have to be in the code-behind for that rule to see the controls in the visual tree.
public class ComparisonValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (this.firstComboBox.SelectedIndex == this.secondComboBox.SelectedIndex)
return new ValidationResult(false, "These two comboboxes must supply different values.");
else return new ValidationResult(true, null);
}
}
OR you could definitely do it with a trigger if you wanted to set some interesting things outside of an error template.
Option 2
Use a trigger & converter. It's really not too difficult. Here's how I would do it.
<Style x:Key="{x:Type ComboBox}" TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualsConverter}">
<Binding ElementName="cbOne" Path="SelectedIndex"/>
<Binding ElementName="cbTwo" Path="SelectedIndex"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
and the converter in code-behind
public class EqualsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
if (values[0] is int && values[1] is int && values[0] == values[1])
return true;
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
If the ComboBoxes share the same itemsource, set a flag on the underlying data object when an item is selected via the first ComboBox.
In the datatemplate for the data object displayed in the second combobox write a datatrigger that binds to that property and does something appropriate.
Ensure your binding is TwoWay for the first ComboBox.

Resources