Wpf bind multitrigger condition null value - wpf

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.

Related

How to set DataContext within a control's resources tag?a

I have a UserControl, where my DataContext is defined as an attribute within my tag. Anywhere outside of my UserControl's Resources, I can bind to properties within my DataContext. However, when I attempt to do this within my control's Resources, I encounter the following: System.InvalidOperationException: Must have non-null value for 'Binding'.
I suspect this is because the DataContext is not being applied within the scope (the Resource tag) I'm trying to use it in, not only because the Binding is null, but because the property I'm trying to bind to never gets called before the crash as well. My question is: how can I bind to a property within my DataContext within this specific scope? I've tried a few things at this point, such as giving the UserControl an x:Name and attempting to set a Source={x:Reference the_x:Name}, but to no avail.
Below, B and C are the properties within my DataContext with the aforementioned issue. A is fine, as that is just a property of my Style's TargetType.
<UserControl... (DataContext defined here)>
<UserControl.Resources...>
<Style...>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Property="A" Value="D"/>
<Condition>
<Condition.Binding>
<MultiBinding Converter="{StaticResource SomeIMultiValueConverter}">
<Binding Path="B"/>
<Binding Path="C"/>
</MultiBinding>
</Condition.Binding>
<Condition.Value>
<sys:Boolean>False</sys:Boolean>
</Condition.Value>
In the Style, the default Source for any bindings is going to be the DataContext of the target of the style, not that of the outermost element in the XAML file, or that of the owner of the Resources. If you apply a style a Button, for example, that'll be the DataContext of the Button.
However, that won't cause this exception anyway. A Binding with a null or wrong source is not a null reference to a Binding.
This code reproduces your exception. The problem is that when used in a MultiDataTrigger, the Condition must have a Binding.
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<!-- TextBlock has no property named A, so I'll use Text -->
<Condition Property="Text" Value="D"/>
</MultiDataTrigger.Conditions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
If you want a MultiTrigger on multiple regular properties of the target of the style, use a MultiTrigger.
If A is a property of the viewmodel rather than of the control, you don't want Property="A" anyway; you want Binding="{Binding A}":
<Condition Binding="{Binding A}" Value="D"/>
If A is a property of the UserControl, you want Binding="{Binding A, RelativeSource={RelativeSource AncestorType=UserControl}}".
<Condition
Binding="{Binding A, RelativeSource={RelativeSource AncestorType=UserControl}}"
Value="D"
/>

Change Dependency Property value on Textbox ValidationRule

I have implemented Custom Control inheriting from ContentControl,which has dependency property called "CanNavigate"(bool).
In a Window.xaml,I have a text box with some ValidationRule checking for Textbox emptiness.I want to set "CanNavigate" to true/false based on TextBox.Validation.HasError as shown below code:
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="{Binding CanNavigate}" Value="false"></Setter>
</Trigger>
</Style.Triggers>
</Style>
Having this code,gives an error "'Property' property cannot use markup extensions or property element syntax on Setter. Only the Value property can use markup extensions or property element syntax. Error at object 'System.Windows.Setter' in markup file"
Is there a way where I can set CanNavigate property based on TextBox.validationError.
Regards,
Patil
Concerning your first setter I think you should replace this setter with a Binding on the ToolTip property and use a ValueConverter when neccessary, triggers can not.
It's better to do it like this (note I haven't tested this piece of code I'm just trying to point to this way):
<TextBox ... ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
Concerning the second setter: In a setter when defining the Property you don't need to specify a Binding you just provide the property name just like this:
<Setter Property="CanNavigate" Value="False" />

WPF Style DataTrigger based on DependencyProperty binding

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

DataContext not binding in Style.Trigger

So I have some code similar to the following: (Forgive any typos-- I tried to simplify in the SO editor for the post)
<my:CustomContentControl>
<my:CustomContentControl.Style>
<Style TargetType="{x:Type my:CustomContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentView}" Value="MyCustomView">
<Setter Property="Content">
<Setter.Value>
<my:CustomView DataContext="{Binding DataContextForMyCustomView"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</m:CustomContentControl.Style>
</my:CustomContentControl>
The problem is that whenever the DataTrigger occurs, the setter does set the Content property to my:CustomView, but it does not bind DataContext. If I move the same code outside of the trigger the DataContext binding works just fine.
Any ideas? If this is a limitation of some sorts, is there any work around?
Update:
I received the following error in the output window:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=DataContextForMyCustomView; DataItem=null; target element is 'CustomView' (Name='customView'); target property is 'DataContext' (type 'Object')
The error you posted makes it sound like your custom control is in an object that doesn't have a DataContext, such as a DataGridColumn.Header.
To get around that, you can create a Freezeable object in your .Resources containing the binding you're looking for, then bind your my:CustomView.DataContext to that object
<my:CustomContentControl.Resources>
<local:BindingProxy x:Key="proxy"
Data="{Binding DataContextForMyCustomView, ElementName=MyControl}" />
</my:CustomContentControl.Resources>
...
<my:CustomView DataContext="{Binding Source={StaticResource proxy}}"/>
Here's the code for a sample Freezable object copied from here:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object),
typeof(BindingProxy), new UIPropertyMetadata(null));
}
Also, you really should use ContentTemplate instead of Content to avoid an exception if more than one object applies that style :)
I solved a similar problem by putting the UserControl into the resources and then changing the Content with that.
e.g. from my own code (different names, same concept)
<ContentControl Grid.Column="1"
Margin="7,0,7,0">
<ContentControl.Resources>
<mapping:Slide11x4MappingView x:Key="Slide11X4MappingView" DataContext="{Binding MappingViewModel}"/>
<mapping:MicrotubeMappingView x:Key="MicrotubeMappingView" DataContext="{Binding MappingViewModel}"/>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Acquirer.Sorter.TrayType}" Value="{x:Static mapping:TrayType.SLIDES11X4}">
<Setter Property="Content" Value="{StaticResource Slide11X4MappingView}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Acquirer.Sorter.TrayType}" Value="{x:Static mapping:TrayType.VIALS}">
<Setter Property="Content" Value="{StaticResource MicrotubeMappingView}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Using Validation in WPF With Dependency Property and Style Triggers

I am trying to use Validation in WPF. I created a NotNullOrEmptyValidationRule as shown below:
public class NotNullOrEmptyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (String.IsNullOrEmpty(value as String))
return new ValidationResult(false, "Value cannot be null or empty");
return new ValidationResult(true, null);
}
}
Now, I need to use it in my application. In my App.xaml file I declared the Style for the TextBox. Here is the declaration.
<Style x:Key="textBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="Green"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Now, I want to use it on my TextBox so I am using the following code:
<TextBox Style="{StaticResource textBoxStyle}">
<TextBox.Text>
<Binding>
<Binding.ValidationRules>
<NotNullOrEmptyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The error comes on the Tag NotNullOrEmptyValidationRule. The XAML syntax checker is not able to resolve the NotNullOrEmptyValidationRule. I have even tried putting the namespace but it does not seem to work.
You just need to add the xmlns to your Window, and use that to reference your ValidationRule.
In WPF, the object is perfectly fine to be used from the same assembly.
Since your rule isn't defined in the standard XAML namespace, you have to create a mapping to your clr namespace like so:
<Window ...
xmlns:local="clr-namespace:MyNamespaceName">
And then you would use it like so:
<Binding Path=".">
<Binding.ValidationRules>
<local:NotNullOrEmptyValidationRule />
</Binding.ValidationRules>
</Binding>
Edit
I added a Path statement to the Binding. You have to tell the Binding what to bind to :)
i see your binding on the TextBox is set to a path of 'Text' - is that a field on whatever the datacontext of this textbox is? is the textbox actually getting a value put into it? also, if you put a breakpoint in your validation method, is that ever getting fired?
you may want to lookup how to log failures in binding and review those as well..
You do not have this line in ur code behind
Public Sub New()
' This call is required by the Windows Form Designer.
InitializeComponent()
Me.**NameOfTextBox**.DataContext = Me
End Sub
There is a bug in Visual Studio and Expression Blend that causes this problem. What you need to do is make sure that the Validation rule is in a separately project/assembly that you can reference. This should resolve the problem.
However, you will have to add back the namespace in order for it to work.

Resources