What's the difference between a Trigger and a DataTrigger? - wpf

They seem the same. Is there a significant difference? I think I am missing something.

A regular Trigger only responds to dependency properties.
A DataTrigger can be triggered by any .NET property (by setting its Binding property). However, its setters can still target only dependency properties.

Another difference is that a DataTrigger can be bound to another control, a StaticResource, etc etc.
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger
Binding="{Binding SomeProperty,
ElementName=someOtherControl"
Value="Derp">
<!-- etc -->
You can only examine the instance on which the style is set when using a Trigger. For example, a Trigger applied to a Button can inspect the value of IsPressed, but it would not be able to inspect the (for example) Text value of a TextBox on the same form if you wished to disable the Button if the TextBox was empty.

The short answer (as I'm about to sleep)- A trigger works on dependency properties (typically GUI properties) whereas data triggers can be triggered by any .NET property (typically a property in a ViewModel that implements INotifyPropertyChanged).

Related

Prevent WPF Binding Exception: Cannot find source for binding with reference 'RelativeSource FindAncestor.....' reduces performance

I wrote the Style:
<Style x:Key="ProductItemContainerStyle"
TargetType="{x:Type ListBoxItem}"
BasedOn="{StaticResource ProductItemContainerBaseStyle}">
<Setter Property="IsSelected"
Value="{Binding Path=IsExpanded, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Expander}}, Mode=OneWayToSource}" />
</Style>
It is relevant only when applying grouping for the ListBox that holds this ListBoxItem. However, most of the time it is not in grouping and this causes for dozens, hundreds and thousands of binding exception (depends on how many items are in the list). Binding Exception are known reason for performance problems. This binding should expand the Expander when code behind selects a ListBoxItem and IsSelected is changed to true. As you can see the binding is Mode=OneWayToSource.
Is there a way to prevent these binding exceptions?
It is relevant only when applying grouping for the ListBox that holds this ListBoxItem ...
Then apply it only then, i.e. use a different style when you are indeed grouping or set the IsSelected property in a trigger that determines whether you are currently in a "grouped" stage.
If you hard-code the Setter into the default Style, then XAML processor will of course always try to resolve the binding. The only way to tell it not to is to remove the binding from the XAML.
Is there a way to prevent these binding exceptions?
The only way to do this is to either remove the failing binding. You can turn off the tracings under Tools->Options->WPF Trace Settings->Data Binding but this won't prevent the excpetions from being actually thrown when the XAML processor tries to resolve the bindings.

WPF - Set child controls of a user control to be readonly using binding

I have a WPF application with many different controls. I need to be able to set all child controls to be read only based on a property in my view model that I want to bind to.
There are a couple of challenges that I see:
How to ensure that setting the parent control to read only, also sets the child controls
Not all controls have a ReadOnly property - some IsReadOnly, some only have an IsEnabled
Has anyone any views on a generic solution rather than me having to bind the appropriate property (ReadOnly, IsReadOnly, etc) for each individual control?
Is there some way that I could use an attached property? Is there anyway, for example, that I could set a property on a grid, then in the code iterate through each child control setting it's appropriate property (if applicable at all)?
Any ideas welcomed.
David
I would recommend to do this using WPF implicit styles. The style would contain the Binding to the view model, for example:
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="{Binding IsNotProcessing}" />
</Style>
As this style does not have the x:Key attribute set and uses the x:Type markup extension on the TargetType attribute, it is implicitly applied to all buttons in this case.
You would have to write an implicit style for each distinct control in your view as the following style would not be applied to all your buttons, text boxes and whatever controls you use (although the IsEnabled property is defined on FrameworkElement):
<!-- This implicit style is not applied as the x:Type must be the same type as
the targeted control; inheritance does not work here. -->
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="IsEnabled" Value="{Binding IsNotProcessing}" />
</Style>
Another option would be to make a single style that has a resource key an then reference this from every control, which is also quite cumbersome, but could be done relatively easy using Blend if you know all the controls at design time (you would select all controls and then apply the style using the properties window).
Hope this will help you.
Use the property IsHitTestVisible in xaml file to make real read only
<Grid IsHitTestVisible = "False">
//put a control
</Grid>

XAML "Conditional" Binding

I have a DataTrigger attached to a style for TextBlock, defined as such:
<DataTrigger Binding="{Binding Path=Link, Converter={StaticResource HasContentConverter}}" Value="True">
<Setter Property="TextDecorations" Value="Underline" />
<Setter Property="Cursor" Value="Hand" />
</DataTrigger>
The issue that I'm having is that I have multiple objects that end up using this style, some of which contain a "Link" property, and some of which don't. Whenever the system encounters an object that doesn't, it prints this error in the output window:
BindingExpression path error: 'Link' property not found on 'object' ''DataRowView' (HashCode=53681904)'. BindingExpression:Path=Link; DataItem='DataRowView' (HashCode=53681904); target element is 'TextBlock' (Name=''); target property is 'NoTarget' (type 'Object')
This is expected behaviour, however I'm wondering if there's a way to tell the processor in XAML to only apply if the "Link" property exists (ie. check for the property before attempting to bind, or some other method that doesn't print an error). Is this possible?
Both out of the box and directly it is not possible.
Not out of the box: you can write your own BindingExtension that would behave like that: bind if the prop exists, else ignore. You can also, khem, turn off reporting binding error, but of course that is usually not wanted.
Not directly: you can create an attached attribute of some type, and then set such attribute instead of setting the binding. Your attribute-setter would attach to datacontext-changes and inspect the objects and visual components as they fly around and set the binding or not.
Not directly#2: You can try to "hierarchize" the styles and triggers. As you know, Trigger has a Condition. Split your style in two parts: first is the common style that does not need to be "guarded", and the second one contains features dependent on having "Blargh" property. Set the first style as default/normal. Now create a readonly attached property called "DefinesBlargh" or "HasBlarghDefines" that checks if the target object's datacontext actually has such property. Now add to the first style a trigger that detects whether the styled control has "HasBlarghDefined" equal "true", and in the trigger's action...
...and here's the problem. What to do there? You cannot replace the style again to the second part of the style, as it probably would remove the trigger and in turn deactivate the logic (it would be one-shot). Or, it may simply crash due to the fact of trying to change the style two times in one update sweep. I actually not know what would happend, but I sense "a smell". More over, changing to the second-part would simply erase the common things that the first part set up.
So, if it actually would run and replace the style, you'd have to ENSURE that the original trigger logic and rest of the first style is preserved, I'd suggest using "style inheritace", that is, the based-on style property: http://wpftutorial.net/StyleInheritance.html That is, do not create two separate parts, but rather, make a "base part" with all common things, and a "specialized part" that is based on the first and adds the unsafe extra things. Now dynamically re-replacing to the specialized counterpart is a bit more reasonable.
Or, if you have some control over the layout, you can get smart: Why apply the two styles to the same component? Set the general style on some outer bound of the control and place the extra trigger there, and let the trigger apply the small unsafe second style to the control.
If you really have to target exactly one control with both parts of the style and cannot use "based on" or maybe if it simply does not work etc, you can do another smart trick: use a MultiStyle that allows you to define a style that mergers two/three/+ other styles into one, and then build a trigger hierarchy as follows:
multitrigger
condition: HasBlarghDefined = TRUE
condition: your own data condition
setter: set style = multistyle of "generalpart" and "usnafepart"
multitrigger
condition: HasBlarghDefined = FALSE
condition: your own data condition
setter: set style = just a generalpart
IMHO, that just have to work.
edit: forgot to past the critical link: The MultiStyle
So my final solution for this was to have a base DataGrid class that implements the style in question, minus the "Link" specific data trigger. Then I had a new DataGrid class that derived from my base class, with code to specifically create the data trigger:
Binding binding = new Binding("Link");
binding.Converter = new MDTCommon.Converters.HasContentConverter();
DataTrigger trigger = new DataTrigger();
trigger.Binding = binding;
trigger.Value = true;
Setter setter1 = new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline);
Setter setter2 = new Setter(TextBlock.CursorProperty, Cursors.Hand);
trigger.Setters.Add(setter1);
trigger.Setters.Add(setter2);
Style style = FindResource("DefaultStyleInQuestion") as Style;
style.Triggers.Add(trigger);
I was able to use this method because the binding object that had the "Link" property was only used in my derived DataGrid class.

Setting TaskbarItemInfo via WPF Style or Trigger

WPF 4 includes a "TaskbarItemInfo" Freezable class that adds an attached property to a Window that allows various Windows 7 taskbar items to be changed.
In particular, I'm trying to set progress information on the tasbar icon for the Window. I'd like to use a DataTrigger to do this, but it doesn't seem to work. I tried using a simple style setter, but that doesn't work either - only direct property assignment or direct property bindings will work.
For example:
<Window.Style>
<Style>
<Setter Property="TaskbarItemInfo.ProgressState" Value="Indeterminate" />
</Style>
</Window.Style>
<Window.TaskbarItemInfo>
<TaskbarItemInfo />
</Window.TaskbarItemInfo>
It appears as though the attached property is not being set via the style. Is my syntax for setting attached properties via styles incorrect, or am I missing something else?
TaskbarItemInfo doesn't inherit from FrameworkElement so there is no Style property for you to set in the DataTrigger.
Why don't you bind your TaskbarItemInfo's ProgressState to the property you were thinking of using in your DataTrigger and then use a ValueConverter to convert that to the relevant TaskbarItemProgressState.
<Window.TaskbarItemInfo>
<TaskbarItemInfo ProgressState="{Binding YourProperty, Mode=OneWay, Converter={StaticResource ProgressStateConverter}}" />
</Window.TaskbarItemInfo>
Then a simple converter can return whichever TaskbarItemProgressState applies to your trigger property.

WPF Data Triggers Set Object properties based on events

With DataTriggers in WPF its possible to set properties on controls based on the the object you have bound. For example you could set the Background of a TextBlock based on a IsAlive property on your object.
<DataTrigger Binding="{Binding Path=IsAlive}" Value="true">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
I want to know if its possible to go in reverse. Is it possible to set a property on a databound item based on the state of the control its bound to?
Say I want to set the IsAlive property to true when the control its bound to receives the mouseover event.
Can this be done in WPF & data triggers? Thanks.
I don't know if what you're asking is directly possible, but I suspect it isn't. On the other hand, I think you could make your example scenario work by binding the object's "IsAlive" property directly to the control's "IsMouseOver" dependency property, with Mode=OneWayToSource.
You might want to use EventSetter, then handle the setting by code using the DataContext property of the sender, or with GetBindingExpression.
This gives you an option to set a handler on the style level.

Resources