WPF control binding to previous binding before DataTrigger overrides it - c#

I have the following question : what do I need to add in the code below to tell my RadioButton to bind on "IsFacturation" boolean that is attached to datagrid item ? I use a DataTrigger which defines its own binding onto datagrid readonly state, so I need to "get back" in binding definition, probably by looking at appropriate parent. I think I have to play with RelativeSource...
I observe that when a datagrid item has IsFacturation boolean set to true, the radio button isn't checked as it should be.
DataGrid items are an observable collection of "Adresse" objects, which define an "IsFacturation" property.
<DataGrid x:Name="AddressGrid" SelectionUnit="Cell" ItemsSource="{Binding Path=Adresses}" SelectionMode="Single">
<DataGrid.Columns>
<!-- Region Facturation -->
<DataGridTemplateColumn Header="Facturation" SortMemberPath="IsFacturation" HeaderStyle="{StaticResource CenterAlignmentColumnHeaderStyle}" >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=AddressGrid,Path=IsReadOnly}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<!-- Possibly create another contentcontrol which differentiates between errors -->
<DataTemplate>
<Image Source="Resources/Images/Check-icon.png" Visibility="Visible"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=AddressGrid,Path=IsReadOnly}" Value="False">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<RadioButton GroupName="grpRadioButtonFacturationAddresses"
IsChecked="{Binding Path=IsFacturation, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Visible"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The goal of such code is to display an image when datagrid is readonly, and a radio button when it's not. I still have to work on image visibility (easy), but radio button state is directly linked to datagrid item property of my choice.
Thanks a lot

This is just answering your comment, not your question. One way that you could use DataTriggers without a ContentControl is to move them to the actual controls:
<DataTemplate>
<Grid>
<Image Source="Resources/Images/Check-icon.png" Visibility="Visible">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Visibility="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=AddressGrid,Path=IsReadOnly}" Value="True">
<Setter Property="Visibility" Value="Visible">
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<RadioButton GroupName="grpRadioButtonFacturationAddresses" IsChecked="{Binding Path=IsFacturation, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center">
<RadioButton.Style>
<Style TargetType="{x:Type RadioButton}">
<Setter Visibility="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=AddressGrid,Path=IsReadOnly}" Value="True">
<Setter Property="Visibility" Value="Collapsed">
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
</Grid>
</DataTemplate>

I think I found the cause of our problem.
I suppress the ContentControl and the binding of my radiobutton working now.
Edit: Oh, I had not seen your response Sheridan :)

Related

DataTrigger to change TextBlock text on ToggleButton click

I am trying to change the text of a TextBlock when clicking on the ToggleButton the TextBlock is located in. The TextBlock is located within a StackPanel, together with an image, and the StackPanel is located within the DataTemplate of the ToggleButton.
I am trying to accomplish this using a property called EditState within the ViewModel. The EditState property changes to "True" successfully when clicking the ToggleButton, so the issue is not within the ViewModel.
There is no error showing, the text of the TextBlock just won't change. Here is the XAML code I have at the moment:
<ToggleButton x:Name="btnEdit" Grid.ColumnSpan="1" Command="{Binding EditCommand}"
IsEnabled="{Binding CanEdit}" MinWidth="168">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type ToggleButton}">
<StackPanel Orientation="Horizontal">
<Image Source="/Resources/IconEdit.jpg" Height="20">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.2" />
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock x:Name="tbkEdit" Text="Enable Editing" Margin="5" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding EditState}" Value="True">
<Setter TargetName="tbkEdit" Property="Text"
Value="Cancel Editing"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
I am looking for a solution consistent with MVVM. This is my first time using WPF and developing a project using MVVM. If someone could correct my mistake or point me in the right direction I would greatly appreciate it.
"a solution consistent with MVVM" is a misconception when you are styling a control. This is only a view element and should not depend on any view model. MVVM is not relevant here.
The TextBlock in the ContentTemplate should show the ToggleButton's Content property by Text="{Binding}", and the Content property should be set by a Style Setter and changed by a Style Trigger on the ToggleButton's IsChecked property.
Also note that DataType="{x:Type ToggleButton}" on the ContentTemplate is pointless and incorrect. DataType is meant for automatic usage of DataTemplate resources, which does not happen here. And it is supposed to match the type of the Content, which is not the type of the control.
<ToggleButton ...>
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image ...>
...
</Image>
<TextBlock Text="{Binding}" Margin="5"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="Enable Editing"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Cancel Editing"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>

WPF INotifyDataErrorInfo highlight ListBoxItem

I've got a ListBox as such:
<ListBox Margin="5" ItemsSource="{Binding NetworkAdapters, Mode=OneWay}" SelectedItem="{Binding SelectedNetworkAdapter}" SelectionChanged="{s:Action SelectedNetworkAdapterChanged}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="15" Height="15" Margin="5">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Gray"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="{x:Static wpf:NetworkAdapterStatus.Up}">
<Setter Property="Fill" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static wpf:NetworkAdapterStatus.Down}">
<Setter Property="Fill" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<StackPanel Margin="5,0,0,0">
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Description}"></TextBlock>
<TextBlock Text="{Binding Speed, StringFormat='Speed: {0}'}" FontSize="10"/>
<TextBlock Text="{Binding Status}" FontSize="10"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
NetworkAdapters is a collection of View Models that implement INotifyDataErrorInfo.
With the current XAML, if there is an error in any of the View Models the whole ListBox will be highlighted red, but I would like just the single ListBoxItems that contains errors to be highlighted.
I had a look at similar questions such as:
WPF ListBox ErrorTemplate and
Validating a ListBoxItem rather than a ListBox
But I still can't make this work. Any help would be appreciated.
UPDATE:
As per Krzysztof's advice, I tried wrapping the StackPanel around a border and using a DataTrigger as such:
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1">
<Border.Resources>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasErrors}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<StackPanel> ... </StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
However, what this produces is the following:
Which is slightly different from what I expected. I would like to have the highlight around the whole ListBoxItem not just part of it as per the image.
You need to implement an ItemContainerStyle as below:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Validation.HasErrors}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
This will enable you to change the border of the ListBoxItem itself, so the whole things as you want.
You can forget about ErrorTemplate and just use DataTrigger to bind to Validation.HasErrors:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type StackPanel}" BasedOn="{StaticResource {x:Type StackPanel}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Validation.HasErrors}" Value="True"> <!-- change all text to red -->
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
</StackPanel>
If you want a highlight, you can wrap StackPanel with a Border and set its color to red in a style.

XAML Style Won't Apply Until EventTrigger Command Completes

I have a ListBox which defines a custom ControlTemplate. The selected item has a style that changes the background and foreground, and the style basically works. However, I want to introduce a behaviour that displays a modal message box on selection changed, asking the user if they really want to select a different item. I've implemented an ICommand to do this which in the code below is shown as AreYouSureCommand.
The problem is whilst that modal message box is shown, the background style for the selected item is changed but the foreground is not. As soon as I dismiss the modal message box, the foreground colour changes. I haven't included the code for the ICommand because it's a bit convoluted but hopefully it is sufficient to say that a Window is opened with ShowDialog when it is executed.
Can anybody shed any light on why my background colour changes but not my foreground colour?
<ListBox x:Name="SubMenu" ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DisplayName}"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="MainBorder">
<ContentControl x:Name="Presenter">
<ContentPresenter />
</ContentControl>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<!-- Setter on MainBorder applies before AreYouSureCommand completes -->
<Setter TargetName="MainBorder" Property="Background" Value="Red" />
<!-- Setter on Presenter applies after AreYouSureCommand completes -->
<Setter TargetName="Presenter" Property="Foreground" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AreYouSureCommand}"
CommandParameter="{Binding SelectedItem, ElementName=SubMenu}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
I eventually came to a solution for this. Basically I use another setter for the IsSelected property to push the value of IsSelected for the ListViewItem onto the view model. Then I use a DataTrigger instead of a regular Trigger to set the selected styles, and bind the trigger to the IsSelected property on the view model rather than the property of the ListViewItem itself. I don't really know why this works - it shouldn't be any different really, but it does work.
Thanks Juan and Ben for your comments.
<ListBox x:Name="SubMenu" ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DisplayName}"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="MainBorder">
<ContentControl x:Name="Presenter">
<ContentPresenter />
</ContentControl>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="MainBorder" Property="Background" Value="Red" />
<Setter TargetName="Presenter" Property="Foreground" Value="Green" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AreYouSureCommand}"
CommandParameter="{Binding SelectedItem, ElementName=SubMenu}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>

Alternation row colors on ItemsControl by only applying a reusable Style

It is a very common thing people do and I understand how it works. I just can't find a way to make it work by only the use of a Style.
Lets say I have this ItemsControl:
<ItemsControl ItemsSource="{Binding stuffList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid ... />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If I wanted to add an alternating row color I would do something like this:
<ItemsControl ItemsSource="{Binding stuffList}" AlternationCount="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="InnerGrid" ... />
<DataTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="Blue" TargetName="InnerGrid"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="Red" TargetName="InnerGrid"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But I want to clear the xaml from this unwanted additionnal code. I will also need to re-use this mechanic. What do I do? I make a Style!
So, I want to apply a style to the ItemsControl like this:
<ItemsControl ItemsSource="{Binding stuffList}" Style="{StaticResource AlternateStyle}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid ... />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and let the magic happen in a stylesheep somewhere else like that:
<Style TargetType="ItemsControl" x:Key="AlternateStyle">
<Setter Property="AlternationCount" Value="2"/>
<Style.Resources>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="0">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
But it doesn't work.. I'm not able to find the correct Binding or TargetType for the Style (that would replace Grid here). I'm out of idea and can't find help where this is not handled in the xaml. I could use some help from experienced WPF programmers.
Thanks in advance.

WPF Bind to selected Radio Button

I have a Radiobutton group:
<RadioButton Name="_CreateGraph" GroupName="MapOrGraph" Content="Create Graph" />
<RadioButton Name="_CreateMap" GroupName="MapOrGraph" Content="Create Map" />
How do I bind the content of a label to the selected radio button's content?
e.g. the label should say Create Graph or Create Map depending on which radio box is created?
<Label Content="{Binding SelectedValue.Content, ElementName=list}" />
<ListBox x:Name="list" SelectedIndex="1" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem" >
<RadioButton GroupName="a" Content="{TemplateBinding Content}" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent} }" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem>Create Graph</ListBoxItem>
<ListBoxItem>Create Map</ListBoxItem>
</ListBox>
I think you can make it shorter than what I did.
If you have only two RadioButtons, this is a quick & dirty solution:
<Label>
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=_CreateGraph}" Value="True">
<Setter Property="Content" Value="{Binding Content, ElementName=_CreateGraph}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked, ElementName=_CreateMap}" Value="True">
<Setter Property="Content" Value="{Binding Content, ElementName=_CreateMap}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>

Categories

Resources