Binding to attached property of property in style - wpf

Before I begin, my end goal here is to have a default ToolTip style for all controls which has a normal border that goes red when there's a validation error on the control for which the ToolTip is being shown.
So ToolTip has a PlacementTarget property which is the control over which the ToolTip is being shown. There is a Validation.HasError attached property that gets set on that PlacementTarget control when it has a databinding error. For instance I've got a TextBox, and in the TextBox style I can do this:
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Content="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.(Validation.Errors)[0].ErrorContent}"/>
Now, I can just make a style for the ToolTip with a red border and set it in that trigger above, but I'd rather not have to do this in every control's style triggers, so I want my ToolTip style to handle this functionality itself if possible.
I've tried various things, most recently this (the converter basically returns red if there's an error, black if not):
<Style TargetType="{x:Type ToolTip}">
<Setter Property="BorderBrush" Value="{Binding Path=PlacementTarget.Validation.HasError, RelativeSource={RelativeSource Self}, Converter={StaticResource ToolTipBorderConverter}}"/>
</Style>
But from the error that gives it looks like that's trying to get an actual property called Validation on the TextBox, as opposed to the attached property:
System.Windows.Data Error: 40 : BindingExpression path error: 'Validation' property not found on 'object' ''TextBox' (Name='')'. BindingExpression:Path=PlacementTarget.Validation.HasError; DataItem='ToolTip' (Name=''); target element is 'ToolTip' (Name=''); target property is 'BorderBrush' (type 'Brush')
I also tried something like:
<Style TargetType="{x:Type ToolTip}">
<Style.Triggers>
<Trigger Property="PlacementTarget.Validation.HasError" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
But I get an error saying I can't nest the Trigger's property like that.
Is there a way to accomplish what I'm trying to do here?

Searched for an answer for this for hours, and right after posting I saw a related article that got me the answer: you have to wrap Validation.HasError in parentheses for it to recognize it as an attached property.
<Style TargetType="{x:Type ToolTip}">
<Setter Property="BorderBrush" Value="{Binding Path=PlacementTarget.(Validation.HasError), RelativeSource={RelativeSource Self}, Converter={StaticResource ToolTipBorderConverter}}"/>

Related

WPF: Bind to element in DataTemplate in ResourceDictionary

I'd like to bind to a element's property (a ListBox's SelectedItems.Count in my specific case) that's dynamically inserted into my window from a DataTemplate located in a ResourceDictionary. I'd like to enable/disable a button when the count reaches a certain number of ListBoxItems are selected. I thought this would work:
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource myResourceKey}, Path=myListBox.SelectedItems.Count}" Value="25">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
But I'm getting the following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'myListBox' property not found on 'object' ''DataTemplate' (HashCode=50217655)'. BindingExpression:Path=aoiListBox.SelectedItems.Count; DataItem='DataTemplate' (HashCode=50217655); target element is 'Button' (Name='myBtn'); target property is 'NoTarget' (type 'Object')
How can I achieve this binding? Thanks in advance.
Well you can write a workaround, but I strongly recomment not to implement it that way. Consider, that a style in a ResourceDictionary is an empty resource, which should be decoupled from any specific instance (in your case myListBox) in your application. Problem is, that you cannot use this malformed style on another Button. So you don't need to, better you shouldn't, declare it as resource.
I definitely recomment to declare this Style directly in the Button. E.g.
<ListBox x:Name="myListBox" />
<Button>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myListBox,
Path=SelectedItems.Count}" Value="25">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Additionally, I would use a Binding via the ElementName property.

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" />

Trouble binding to a property of templatedparent to set the value inside of a style setter in wpf

My problem:
I haven't been able to figure out how I inside the 'Setter.Value' field of a setter for a property A in a style targeting a particular control can do a binding to a property B of that particualr control. More specifically I want to use the Foreground brush value on a graphical element inside the visual tree of the Content property of a Button. this will ensure that the graphical element always has the foreground color set for this button control.
What I try to achive:
I'm working on a WPF-application where I have three button controls:
DefaultButton
SpecialButton
ExtendedSpecialButton
The DefaultButton is where I define the style of buttons in the application through a style with a ControlTemplate.
The SpecialButton introduces a new property not supposed to be used for general buttons. This property will be represented by one visual state that I define through a style setter. Else from that it shall be identical in apperance to the DefaultButton.
I define the style of this SpecialButton by basing it on the style of the DefaultButton. In this style there is no ControlTemplate only a MultiTrigger-response on the basis of a couple of property conditions setting av a couple of visual properties:
<Style x:Key="SpecialButtonStyle" TargetType="{x:Type MyControls:SpecialButton}" BasedOn="{StaticResource DefaultButtonStyle}">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsActive" Value="false"/>
<Condition Property="IsMouseOver" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="BorderBrush" Value="{DynamicResource ButtonDisabledBorder}" />
<Setter Property="Background" Value="{DynamicResource ButtonDisabledBg}" />
</MultiTrigger>
</Style.Triggers>
</Style>
All this worked great.
The next step is also no problem:
I wanted to base the ExtendedSpecialButton on the SpecialButton and set a default shape content inside the button.
<Style x:Key="ExtendedSpecialButtonStyle" TargetType="{x:Type MyControls:ExtendedSpecialButton}" BasedOn="{StaticResource SpecialButtonStyle}">
<Setter Property="Content">
<Setter.Value>
<Rectangle Fill="Black" Height="5" Width="15"></Rectangle>
</Setter.Value>
</Setter>
</Style>
The original style of DefaultButton is still present - the added visual state responding to the IsActiveProperty of the SpecialButton is still with us - and the ExtendedSpecialButton also inherited the visual behaviour created by the MultiTrigger of the SpecialButton.
I also successfully displayed a graphical element that this ExtendedSpecialButton should have.
However I wanted the fill of this graphical element to use the Foreground color. This foreground color is originally styled in the DefaultButton and works just fine for the two first buttons.
The code below is how I currently thought such a binding should be done. But this does not work:
<Style x:Key="ExtendedSpecialButtonStyle" TargetType="{x:Type MyControls:ExtendedSpecialButton}" BasedOn="{StaticResource SpecialButtonStyle}">
<Setter Property="Content">
<Setter.Value>
<Rectangle Fill="{Binding RelativeSource={RelativeSource TemplatedParent}}" Height="6" Width="20"></Rectangle>
</Setter.Value>
</Setter>
</Style>
Does anyone know what I could do to set up the binding so that it does what I intended it to do?

WPF ComboBox - showing something different when no items are bound

I have a ComboBox, and i want to change its look when the ItemsSource property is null. When it is in that state, i want to show a TextPanel with the text "Retrieving data" in it, and give it a look kind of similar to the watermarked textbox.
I figure to do this i need a ControlTemplate, and a trigger. I have the ControlTemplate here:
<ControlTemplate x:Key="LoadingComboTemplate" TargetType="{x:Type ComboBox}">
<Grid>
<TextBlock x:Name="textBlock" Opacity="0.345" Text="Retrieving data..." Visibility="Hidden" />
</Grid>
<!--
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.ItemsSource" Value="0">
<Setter Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
-->
</ControlTemplate>
but my issue is how do i set up the trigger to show this when the ItemsSource property is null? I have tried a couple of different ways, and each way has given me the error message "Value 'ItemsSource' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.". My ComboBox xaml is this (including the attempted trigger):
<ComboBox Margin="112,35,80,0"
Name="MyComboBox"
Height="22.723"
VerticalAlignment="Top"
DisplayMemberPath="FriendlyName"
SelectedValuePath="Path"
TabIndex="160"
>
<Trigger>
<Condition Property="ItemsSource" Value="0" />
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</ComboBox>
now should the trigger go on the ComboBox, or on the ControlTemplate? How do i access the ComboBox's ItemsSource property? Should i even be using a trigger?
Thanks!
Try putting {x:Null} for the value of the condition instead of 0.
Also I got it working by moving the Trigger to a style and modifing it slightly, see below.
<Style TargetType="ComboBox" x:Key="LoadingComboStyle">
<Style.Triggers>
<Trigger Property="ItemsSource" Value="{x:Null}">
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<ComboBox Style="{StaticResource LoadingComboStyle}" .... >
The reason it only works in a style, is that only EventTriggers are allowed in the triggers collection directly on the Framework Element. For property triggers (like above) you need to use a style (I learn something every day).
See FrameworkElement.Triggers
Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.

WPF Databinding and Styles with MVVM

I have a problem with databinding on a style in WPF.
The basic setup looks like this:
<Style x:Key="{x:Type eo:Player}" TargetType="{x:Type eo:Player}">
<Style.Triggers>
<DataTrigger Binding="{Binding Team}" Value="A">
<Setter Property="Template" Value="{StaticResource TeamATemplate}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
The style is applied to all objects of type Player. These objects have a property of type Teams (Enum having values A, B and C). Depending on which team the player is in the template applied to visualize the player differs.
The problem that now occurs is that the whole thing is used in a MVVM application and that somehow the DataContext of the Player object gets set to the ViewModel of the topmost View. I used the new diagnostics options (TraceLevel) to find out something about the problem and got this:
System.Windows.Data Warning: 66 : BindingExpression (hash=30607723): Found data context element: Player (hash=35170261) (OK)
System.Windows.Data Warning: 74 : BindingExpression (hash=30607723): Activate with root item ToolboxViewModel (hash=61398511)
System.Windows.Data Warning: 104 : BindingExpression (hash=30607723): At level 0 - for ToolboxViewModel.Team found accessor <null>
So basically the Player object is found as a data context element (whatever that means) but still the ToolboxViewModel is used as DataContext. How can I fix this? How can I refer to the styled object in the binding expression?
I don't know why I didn't think of this earlier:
<Style x:Key="{x:Type eo:Player}" TargetType="{x:Type eo:Player}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Team}" Value="A">
<Setter Property="Template" Value="{StaticResource TeamATemplate}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
It works perfectly fine with {RelativeSource Self}
You cant' style anything with a trigger that you haven't styled with your style already. You'll need to do this:
<Style x:Key="{x:Type eo:Player}" TargetType="{x:Type eo:Player}">
<Setter Property="Template" Value="{StaticResource TeamBTemplate" />
<Style.Triggers>
<DataTrigger Binding="{Binding Team}" Value="A">
<Setter Property="Template" Value="{StaticResource TeamATemplate}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Seems like your style should work after this. Those binding warnings are confusing though.

Resources