XAML Style Won't Apply Until EventTrigger Command Completes - c#

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>

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>

Listbox Item selection

I have a WPF project (C#, Visual Studio 2010, MVVM).
In this project I have a ListBox. When I select the items I am getting a blue box around my objects and I'm not sure where said box comes from. Obviously it's something to do with selection, but I'm not sure what to alter to get it to disappear.
The Listbox is here:
<ListBox ItemsSource="{Binding ChatNodeListViewModel.ChatNodeVMs, Source={StaticResource Locator}}" Background="Transparent" Name="LbNodes" SelectedItem="{Binding ChatNodeListViewModel.SelectedNode, Source={StaticResource Locator}}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Width="2000" Height="1600"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Canvas.Left" Value="{Binding XCoord}"/>
<Setter Property="Canvas.Top" Value="{Binding YCoord}"/>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="lb_PreviewMouseLeftButtonDown" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Thumb Name="myThumb" Template="{StaticResource NodeVisualTemplate}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DragDelta">
<cmd:EventToCommand Command="{Binding ChatNodeListViewModel.DragDeltaCommand, Source={StaticResource Locator}}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Thumb>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding NodeVisualMode}" Value="0">
<Setter TargetName="myThumb" Property="Template" Value="{StaticResource NodeVisualTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding NodeVisualMode}" Value="1">
<Setter TargetName="myThumb" Property="Template" Value="{StaticResource NodeTemplateTest}" />
</DataTrigger>
<DataTrigger Binding="{Binding NodeVisualMode}" Value="2">
<Setter TargetName="myThumb" Property="Template" Value="{StaticResource NodeTemplateTest2}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
There is a main ControlTemplate which can be switched out for others (the main one being NodeVisualTemplate). But since this issue seems to happen on all of them, I'm inclined to believe it's further up the tree.
Still, I did do some testing for it, and will show it here:
<ControlTemplate x:Key="NodeVisualTemplate">
<Grid>
<Border x:Name="_Border" Padding="1" SnapsToDevicePixels="true" BorderThickness="3" Margin="2" CornerRadius="5,5,5,5" BorderBrush="SteelBlue" Visibility="Visible">
</Border>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="true">
<Setter TargetName="_Border" Property="Effect">
<Setter.Value>
<DropShadowEffect ShadowDepth="0" Color="Black" Opacity="1" BlurRadius="20" />
</Setter.Value>
</Setter>
<Setter TargetName="_Border" Property="Background" Value="Transparent"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Within the border at the top of this template are some text boxes and stuff, but I don't suspect them as being part of the affair. The point is that I've changed the background of this control template. It is 'transparent' in the XAML above, but I've changed it to red and other colours to test. It doesn't get rid of the blue square that surrounds the ListBoxItem.
Here is an image of the horrible blue background I'm getting:
Can I get rid of it? Honestly I'm really at a loss.
Thanks.
Edit: I should mention something else I tried that is rather important here. I went up a layer into the ListBox.ItemContainerStyle part above and just under this line:
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="lb_PreviewMouseLeftButtonDown" />
I added this:
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
Did it work? No, it didn't. It didn't get rid of the big blue box. It did make my fonts bold though, so I know it at least had that effect.
Your Thumb is still generated inside of a ListBoxItem, Which has ControlTemplate of It's own.
You need to put in your ListBoxItem Style, a Setter for Template, and put in there only:
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter ContentSource="Content"/>
</ControlTemplate>
I think this is a "standard" thing, wpf does for you in ListBoxes. It highlights the currently selected Item for you. I found a similar thread here: WPF ListBox, how to hide border and change selected item background color?
They fixed it by adding this code to the listbox and setting the HighlightBrushKey to Transparent for this ListBox, did you try that in your case?
<ListBox BorderThickness="0" HorizontalContentAlignment="Stretch" >
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
</Style.Resources>
</Style>
</ListBox.Resources>
</ListBox>

WPF ListBoxItem detect IsMouseOver inside a custom item

i have a ListBox which I fill with custom items. I want to detect a MouseOver event from a ListBoxItem inside the item in order to change visibility of a button. I have checked most of the answers on StackOverflow, the following solution was what I was looking for, but it doesn't work.
This is a code snippet from my ContactsView:
<ListBox ScrollViewer.CanContentScroll="False" VerticalContentAlignment="Top" ScrollViewer.ScrollChanged="ListBox_OnScrollChanged" BorderThickness="0,0,0,0" Margin="0,0,0,0" Padding="0" BorderBrush="{StaticResource ResourceKey=PrimaryColor}" Name="ListBox" ItemsSource="{Binding ListBoxItemsSource}" HorizontalContentAlignment="Stretch">
<i:Interaction.Triggers>
<events:RoutedEventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
<i:InvokeCommandAction Command="{Binding Path=ListBoxScrollChangedCommand}" />
</events:RoutedEventTrigger>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding Path=ListBoxLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
<Style.Triggers>
<Trigger Property="ListBoxItem.IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
</Trigger>
<Trigger Property="ListBoxItem.IsMouseOver" Value="False">
<Setter Property="Background" Value="Transparent"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Custom item -->
<items:ItemCorporateContact Value="{Binding Path=., Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Top" />
<Separator Height="1" Margin="0" Background="#ececec" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And I have been trying to detect the event this way (code from a custom item that I add to a ListBox):
<Button Name="StartCallButton" VerticalAlignment="Center" Background="Red" Margin="10" HorizontalAlignment="Left">
<Button.Content>
<Image Source="{StaticResource PhoneIconBitmap}"></Image>
</Button.Content>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsMouseOver}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Any help will be greatly appreciated.
I had been searching for the same thing. Although the answer is provided in the question, but to specify the answer more clearly, following is the code that works for me.
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="ViewTypeStackPanel" Orientation="Horizontal">
<Border BorderThickness="2,0,0,0" Visibility="{Binding Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverterInstance}}" BorderBrush="Blue"/>
<Image Height="32" Width="32">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="{Binding Path=ImagePath}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsMouseOver}" Value="True">
<Setter Property="Source" Value="{Binding Path=ImagePathHover}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>

How to bind CustomControl DataTrigger to viewmodel DataContext

Using MVVM light, I have a control template for a custom control. There are multiple instances of this control. The style is set in Generic.xaml.
<Style TargetType="{x:Type tgvw:TimeSlotRect}">
<Setter Property="DataContext" Value="{Binding TimeSlotRectViewModel, Mode=OneWay, Source={StaticResource Locator}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type tgvw:TimeSlotRect}">
<Border Background="{TemplateBinding Background}"
Name="TimeSlotBorder"
BorderBrush="#A5BFE1"
BorderThickness="0,0.5,0,0.5" Height="30">
<Rectangle Fill="Beige" Name="Rect">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<cmd:EventToCommand Command="{Binding MouseDownCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding DataContext.IsSelected}" Value="True" >
<Setter Property="Fill" Value="Black" TargetName="Rect"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The idea is that the mouse button click fires a command in the viewmodel that sets it's IsSelected property to true and then the rectangle should pick that up and change it's fill to black - but it doesn't seem to work.
If I bind the DataTrigger to the viewmodel like this:
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding TimeSlotRectViewModel.IsSelected, Source={StaticResource Locator}}" Value="True" >
<Setter Property="Fill" Value="Black" TargetName="Rect"/>
</DataTrigger>
</ControlTemplate.Triggers>
It fires and every control changes to black. Any ideas what I'm doing wrong?

WPF control binding to previous binding before DataTrigger overrides it

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 :)

Categories

Resources