VisualBrush with Datatrigger not working in WPF - c#

I script-kiddied this code to show helper text in a field before the field is populated by the user (also plan on using some modification of it to show the validation error if one occurs), but the trigger isn't, well, triggering. What's wrong with this code?
xaml:
<TextBox x:Name="firstName" Validation.Error="Text_ValidationError"
Text="{Binding UpdateSourceTrigger=LostFocus, Path=firstName, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" Margin="30,12,50,245">
<TextBox.Style>
<Style TargetType="TextBox" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=firstName}" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="First name" />
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Don't use a DataTrigger for this, it's not necessary (the binding might be broken, in fact that is the only thing that i can think of that might cause this not to work), use a normal Trigger:
<Trigger Property="Text" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="First name" />
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Trigger>
Tested this, it works. This also has the advantage that the background disappears immediately when the user starts typing rather than when the focus on the control is lost and the source string is updated.

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>

How can I set hint text (cue banner) in a text box when it's empty or null, and disable if it's not empty or null?

I'm new to xaml and not sure how databind works. I'm trying to enable hint text when my text box is null. But I'm not sure how the data bind work to disable it when it's not null.
Here is a solution I found that works to get my hint text. How do this code to work to disable it if my first name textbox has a value?
<TextBox Grid.Row="1">
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Label Content="First Name" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<TextBox.Template>
<ControlTemplate>
<TextBox Text="{Binding SelectedItem.FirstName,UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
Never mind I figured out.
I could set the textbox binding like:
<TextBox Text="{Binding SelectedItem.FirstName,
UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1">
I didn't know you could do that if you had an opening and closing <textbox> and </textbox> tags.
I thought you could only do it if the textbox has a self-closing tag (<textbox />).
Sorry for the silly question.

Clicking on WPF ToggleButton changes other ToggleButtons values

I have a simple ToggleButton with Style triggers to change the button's image based on the state of IsChecked. This is working fine for one button. However when I add more than one button to a grid, clicking one of the ToggleButtons changes the state of the others. Should I use some other trigger type instead of one based on the botton's style?
Here is the xaml block to lay out the three ToggleButtons:
<Grid Height="362" Width="259">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ToggleButton Style="{StaticResource TogglebuttonStyle}" IsChecked="{x:Null}" IsThreeState="True"
Width="15" Height="15" Grid.Row="0" Grid.Column="0"/>
<ToggleButton Style="{StaticResource TogglebuttonStyle}" IsChecked="{x:Null}" IsThreeState="True"
Width="15" Height="15" Grid.Row="1" Grid.Column="0"/>
<ToggleButton Style="{StaticResource TogglebuttonStyle}" IsChecked="{x:Null}" IsThreeState="True"
Width="15" Height="15" Grid.Row="2" Grid.Column="0"/>
</Grid>
And here is the Style with triggers:
<Style x:Key="TogglebuttonStyle" TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource ImagePlus}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource ImageMinus}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource ImageNeutral}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
I'll call this a workaround, but now each button retains its own state:
I found if I moved my Style block out of App.xaml (within the tag) and defined style triggers for each button, the problem behavior went away. Since I'm fairly new to WPF/XAML I'm not sure if it's an actual solution or a hacky workaround. It seems redundant to specify things this way.
<ToggleButton IsChecked="True" IsThreeState="True"
Width="25" Height="25" Grid.Row="2" Margin="-1,19,1,-19">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource ImagePlus}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource ImageMinus}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource ImageNeutral}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
After doing some more research, I found I had to specify that the style not be shared.
<Style x:Key="ToggleButtonStyle" x:Shared="False" /Style>
The problem is that the setter instantiates one instance of the Image control, which can have only one parent. When then button goes into the "ImagePlus" state, all's well. When a second button goes into that state, that Image control gets moved to the new parent, leaving the old one bereft.
This is one reason why XAML has templates: When a template is instantiated, a new instance of that snippet of visual tree gets instantiated. Presto, no sharing issues.
With some resources you can set x:Shared="False" on the resource and get around this issue. I couldn't find a way to make that work here (I've not yet gotten around to figuring out the principle behind exactly when that works and when not). So I did it with DataTemplates instead:
<Style x:Key="TogglebuttonStyle" TargetType="ToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{StaticResource ImagePlus}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{StaticResource ImageMinus}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{StaticResource ImageNeutral}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>

How Validation.HasError property is updated for user input validation?

In this tutorial there is a text box that shows a pink background for invalid data. This is the wpf code:
<TextBox Text="{Binding Aid,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay,ValidatesOnDataErrors=True}"
Canvas.Left="95" Canvas.Top="60" Width="297">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Pink"/>
...
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox" ToolTip="{Binding [0].ErrorContent}"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
But I don't get how Validation.HasError is updated as the user inputs the value?
This sample project takes advantage of IDataErrorInfo interface which makes data validation very simple. This link will describe it in details.

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