WPF MutiDataTrigger not firing setter - wpf

I'm trying to use MultiDataTriggers to enabled/disable a button based on the value of two text boxes.
The docs state that the conditions in a MultiDataTrigger are logically ANDed together. In the example below if txtFirst.Text is foo and txtSecond.Text is bar I'd like to enable the button. However, the button always stays disabled (IsEnabled=false).
I'm sure I'm missing a trick here, but a thorough search of Google hasn't gotten me anywhere....
<StackPanel>
<Button IsEnabled="False" Content="OK">
<Button.Style>
<Style>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtFirst, Path=Text}" Value="foo"/>
<Condition Binding="{Binding ElementName=txtSecond, Path=Text}" Value="bar"/>
</MultiDataTrigger.Conditions>
<Setter Property="Button.IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<TextBox x:Name="txtFirst"/>
<TextBox x:Name="txtSecond"/>
</StackPanel>

Setting a property directly on the object itself overrides any style-triggered settings. Change your button declaration to:
<Button Content="OK">
<Button.Style>
<Style>
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
Now that the IsEnabled property is set in the style, the trigger can change it. Don't ask me why. :)

Related

How to select tabs with AvalonDock

I'm trying to select tabs (both in a LayoutDocumentPaneGroup and LayoutAnchorablePane) in AvalonDock. This seems like it should be an easy task, but I'm struggling to find any documentation on the subject. So far the best I've gotten is the ability to select the initial tab (see below), but this binding doesn't seem to persist when changing the bound property after the initial load.
<dock:DockingManager Name="DockingManager" Grid.Row="2"
AnchorablesSource="{Binding Anchorables}"
DocumentsSource="{Binding Documents}"
DocumentClosed="DockingManager_DocumentClosed"
DocumentClosing="DockingManager_DocumentClosing"
Loaded="DockingManager_Loaded"
MouseUp="DockingManager_MouseUp">
<dock:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type dockctrl:LayoutItem}" >
<Setter Property="Title" Value="{Binding Model.Title}" />
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}" />
<Setter Property="CanClose" Value="{Binding Model.CanClose}" />
<Setter Property="IsSelected" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Model.Title}" Value="Resources">
<Setter Property="IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</dock:DockingManager.LayoutItemContainerStyle>
<dock:LayoutRoot>
<dock:LayoutPanel Orientation="Horizontal">
<dock:LayoutAnchorablePaneGroup x:Name="leftAnchorableGroup" DockWidth="300" >
<dock:LayoutAnchorablePane />
</dock:LayoutAnchorablePaneGroup>
<dock:LayoutPanel Orientation="Vertical">
<dock:LayoutPanel Orientation="Horizontal">
<dock:LayoutDocumentPaneGroup x:Name="leftDocumentGroup">
<dock:LayoutDocumentPane />
</dock:LayoutDocumentPaneGroup>
</dock:LayoutPanel>
</dock:LayoutPanel>
</dock:LayoutPanel>
</dock:LayoutRoot>
</dock:DockingManager>
However if I replace these lines:
<Setter Property="IsSelected" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Model.Title}" Value="Resources">
<Setter Property="IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
with:
<Setter Property="IsSelected" Value="{Binding Model.ContentIsSelected" />
...it doesn't work when I change the value of ContentIsSelected. I can see (using Snoop) that the value of ContentIsSelected itself i in fact changing but IsSelected Does not change with it?!
I also found this other question (which lead me down the path of trying to use IsSelected): How to switch between document tabs in AvalonDock 2 However 'm not entirely sure how to programatically access the LayoutItems beyond the bindings in XAML. I tried the DockingManager.GetLayoutItemFromModel() function but could not get it to return anything other than NULL.
How can I select a tab and bring it into view/focus (as if I were clicking the tab with a mouse)?
The solution ended up being that the default binding was not as expected.
<Setter Property="IsSelected" Value="{Binding Model.ContentIsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

Validation using IDataErrorInfo

I'm using IDataErrorInfo to implement some basic logic validation for some values. This seems to work well, and I'm using a contentpresenter to display the results:
<ContentPresenter Content="{ Binding ElementName =MyElement, Path=(Validation.Errors).CurrentItem}"
HorizontalAlignment ="Left">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock Foreground ="Red" FontStyle="Italic" Text="{ Binding Path =ErrorContent}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
I get a nice red message when there's a problem, and the field in question is correctly highlighted. However, when this happens, I'd like to disable the save button for the form. Here's what I've tried so far (without success):
<Button Content="Save" Click ="MyButton_Click"
IsEnabled="{Binding Converter={StaticResource ValidConverter}, ConverterParameter={Binding ElementName=MyElement, Path=(Validation.Errors).CurrentItem}}"/>
ValidConverter is just a converter that returns true for a null or empty string.
I also tried triggers, like this (tried both Trigger and DataTrigger):
<Button Content="Save" Click ="MyButton_Click"
<Button.Style>
<Style>
<Style.Triggers>
<Setter Property ="Button.IsEnabled" Value="True" />
<DataTrigger Binding ="{ Binding Path=(Validation.HasError)}" Value ="True">
<Setter Property ="Button.IsEnabled" Value="False" />
</ DataTrigger>
</Style.Triggers>
</Style>
</ Button.Style>
</ Button>
I've found some on-line information on this, and as far as I can tell the trigger method should work; however, if I use Trigger, nothing happens, and DataTrigger doesn't compile (error MC1000: Unknown build error, 'Index (zero based) must be greater than or equal to zero and less than the size of the argument list).
Can anyone tell me why this doesn't work, and what I'm doing wrong here?
Hmm ... I'm doing it the other way around ... here's a similar button, but it's based on several validations for several TextBoxs and ComboBox that need to be validated.
So, I'll set it to false by default, and when all the validations errors are false, set it to true.
<Button x:Name="Btn_Insert"
Command="{Binding Insert_Command}"
IsDefault="True"
>
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError),
ElementName=txtbx_1}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError),
ElementName=cmb_1}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError),
ElementName=txtbx_2}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError),
ElementName=txtbx_3}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Try also putting your <Setter Property ="Button.IsEnabled" Value="True" /> above the <Style.Triggers>
The problem was with the default:
<Style.Triggers>
<DataTrigger Binding ="{ Binding Path=(Validation.HasError)}" Value ="True">
<Setter Property ="Button.IsEnabled" Value="False" />
</ DataTrigger>
</Style.Triggers>
Works fine. As #Shoe pointed out - there was a typo in the question - now corrected.

MultiTrigger: Combined DataTrigger and PropertyTrigger not working

I have a problem where I need to combine a DataTrigger and a PropertyTrigger into a MultiDataTrigger to show an Image in a GridViewColumn (combined with a TreeView, it's a custom control I'm using). I experimented and searched some thins on the internet and this is how far I've come:
<Image Width="16"
Height="16"
Stretch="UniformToFill">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source"
Value="{Binding ScorecardNiveau, Converter={StaticResource ScorecardNiveauToImageConverter}}" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding WpfSettings.IsExpanded}" Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Source}" Value="/folder_closed.png" />
</MultiDataTrigger.Conditions>
<Setter Property="Source" Value="/folder_open.png" />
</MultiDataTrigger>
</Style.Triggers>
</Image.Style>
</Image>
Now, initially, the Image can have two images, according to the Converter. Either the folder_open, or another one (not important now). Now what I want is: when the TreeViewNode is expanded (WpfSetting.IsExpanded = true) and when the Image Source is folder_closed, I want the Image to get the folder_open image. I think I am close with the above code, but it's not really working. The Image isn't changing at all when I open a TreeViewNode.
I think I am doing something wrong with the Condition on the RelativeSource=Self, but I am not sure.
Anyone who can help me please? Thanks in advance.
Of course, more information/code can be provided if needed.
The problem is not with RelativeSource=Self but when you try to compare Source(which is of type ImageSource) to string value ("/folder_closed.png"), it return false
Try the following Condition :
<Condition Binding="{Binding ScorecardNiveau, Converter={StaticResource ScorecardNiveauToImageConverter}}" Value="/folder_closed.png" />

Binding to the Items properties of an ItemsControl

I'm looking to a way to bind a Button Text or IsEnabled property for example to the IsChecked property in an ItemsControl
Here is my simplified source code :
<StackPanel>
<ItemsControl ItemsSource="{Binding Tasks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,0,10,0" Text="{Binding Name}"/>
<CheckBox IsChecked="{Binding InProgress}"/>
<CheckBox IsChecked="{Binding Done}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button/>
</StackPanel>
I would like (for example) if all the "Done" CheckBoxes are Checked, to set Button Text to some value or to enable it. I thought doing this with Data Binding in Xaml using DataTriggers but I don't know how to do it.
Ca anyone give me a full xaml solution ?
New Answer
Sorry, I misunderstood the question. I would expose another property from your DataContext that simply returns true/false if all the items in the collection are checked or not, and base your Button's Text/IsEnabled off that property using a DataTrigger
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsAllChecked}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
Old Answer
DataTriggers simply take a binding, and check if the result is equal to some value.
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Done}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
If you need to test more than one condition, you need a MultiDataTrigger
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=InProgress}" Value="True" />
<Condition Binding="{Binding Path=Done}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
Note that the default value of setter is part of the style. This is important because if you set the default value on the <TextBox> then it has a higher priority than triggered values, so triggered values are unable to change the current value.
You can change this line...
<Button/>
To this
<Button Content={Binding ButtonContent}/>
And set the 'ButtonContent' property in your ViewModel whenever your 'Done' check box gets checked.
Usually, the best way to do that is to synthesize the state you want to apply to your button into a property of your ViewModel.
The WPFy way in that case is for your ViewModel to expose a command that implements the action of your button.
The command in turns can react to changes in your data to signal if it is available or not, and thus will automaticaally disable/enable your button. An added bonus is that you can easily implement a menu for example, that will be properly enabled/disabled at no cost once you have your command.
It's been a long time since I had any chance to touch WPF, so I do not have any really useful links at hand, but if I were you I would learn about commands in WPF, they are a very important part of the MVVM architecture.

Changing TextBlock.Text in trigger didn't work

I have the next code in my view:
<Style x:Key="documentFileNameStyle">
<Setter Property="TextBlock.Foreground" Value="Gray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Untitled}" Value="True">
<Setter Property="TextBlock.FontStyle" Value="Italic"/>
<Setter Property="TextBlock.Text" Value="no file name"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="documentTemplate">
<TextBlock Text="{Binding Path=FileName}" Style="{StaticResource documentFileNameStyle}"/>
</DataTemplate>
But setting TextBlock.Text to a string didn't work. TextBlock.FontStyle changes to Italic, so whole trigger works properly. What is wrong?
Local assignment of Properties has a higher precedence than setting the values in triggers.
Also you are using Binding (Path=FileName) to set the Text-Property of the TextBlock. So changing the Text in Triggers doesn´t effect the Property.
As you are using Binding. I would change the Property "FileName" to return "no file name" if the Property "Untitled" is "true".

Resources