Usercontrol style trigger not working - wpf

I am pretty sure this question has been asked from time to time since I found another question with about the same contents, here. But when I try to figure using all those pages, I'm just stuck...
This is what I'm trying to do. I created a usercontrol to select a file and show the path to the file in a textbox. Just like in HTML (input type=file). That all works great and as expected. But when I try to change the colour of the textbox using a trigger (FilePathIsValid), it just does not work. The dependency properties all work fine, as said above. But the style just is not assigned to the textbox.
Here is my XAML - can anybody tell me what I'm doing wrong? (Code Behind here, if needed)
<UserControl x:Class="Project.Controls.SelectFileBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Controls="clr-namespace:Project.Controls"
x:Name="ThisUserControl"
mc:Ignorable="d" d:DesignHeight="30" d:DesignWidth="300">
<UserControl.Resources>
<!-- This does not work... -->
<Style TargetType="{x:Type Controls:SelectFileBox}">
<Style.Triggers>
<Trigger Property="FilePathIsValid" Value="false">
<Setter Property="TextBoxBorderColor" Value="red"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Neither does this: FilePathIsValid can't be found -->
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="FilePathIsValid" Value="false">
<Setter Property="BorderBrush" Value="red"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Margin="3" IsEnabled="{Binding ElementName=ThisUserControl,Path=TextBoxIsEnabled}" Text="{Binding ElementName=ThisUserControl,Path=FilePath}" BorderThickness="{Binding ElementName=ThisUserControl, Path=TextBoxBorderThickness}"/>
<Button Grid.Column="1" Margin="0,3,3,3" Content="{Binding ElementName=ThisUserControl, Path=ButtonText}" Click="SelectFileClick"/>
</Grid>

Ok, I understood the problem after looking at the codebehind.
All you need to do is add a binding to the BorderBrush property on the TextBox like below to get the right BorderBrush applied.
BorderBrush="{Binding ElementName=ThisUserControl, Path=TextBoxBorderColor}"
Full XAML for TextBox element:
<TextBox Grid.Column="0" Margin="3"
IsEnabled="{Binding ElementName=ThisUserControl,Path=TextBoxIsEnabled}"
Text="{Binding ElementName=ThisUserControl,Path=FilePath}"
BorderThickness="{Binding ElementName=ThisUserControl, Path=TextBoxBorderThickness}"
BorderBrush="{Binding ElementName=ThisUserControl, Path=TextBoxBorderColor}"/>
I have tried and tested, below is the screenshot
Alternative Way:
You could have a style for TextBox and have data triggers to change the borderbrush:
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ThisUserControl,
Path=FilePathIsValid}" Value="False">
<Setter Property="BorderBrush" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
With this approach, you don't need style for SelectFileBox and binding for BorderBrush property on TextBox.

If i understand correctly you want to set the textBoxbroder as red if the path is invalid then change your code as below
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="FilePathIsValid" Value="false">
<Setter Property="BorderBrush" Value="red"/>
</Trigger>
</Style.Triggers>
</Style>

Related

HOWTO override properties from a custom WPF UserControl

I have below WPF UserControl:
<UserControl x:Class="myComponents.UI.TextBoxWithPlaceholder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:pEp.UI"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Loaded="UserControl_Loaded">
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TextBoxWithPlaceholder}}"
Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="myCustomTextBox"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
Padding="5"
IsReadOnly="{Binding IsReadOnly}"
HorizontalAlignment="Stretch"
TextChanged="CustomTextBox_TextChanged"
GotFocus="CustomTextBox_GotFocus"
LostFocus="CustomTextBox_LostFocus"
Margin="5"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" />
<TextBlock Name="myPlaceholderTextBlock"
IsHitTestVisible="False"
Padding="5"
Text="{Binding Placeholder}"
HorizontalAlignment="Left"
Foreground="DarkGray"
Margin="5">
</TextBlock>
</Grid>
</UserControl>
Basically it is a TextBox with a placeholder.
Now from a WPF view I reuse this component by doing:
xmlns:ui="clr-namespace:myComponents.UI"
and then place it as a normal control:
<ui:TextBoxWithPlaceholder Name="myNewTextBox" IsReadOnly="{Binding IsReadOnly}"
Style="{StaticResource myTextBoxStyle}"
Placeholder="please, enter something here"/>
Now as you see above I set a custom style for it:
<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="False">
<Setter Property="Background" Value="{x:Null}"/>
<Setter Property="Foreground" Value="{x:Null}"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
Now in my "myNewTextBox" control I am trying to override some inherited properties for the controls named myCustomTextBox and myPlaceholderTextBlock such as Margin, Padding, Background, Foreground, BorderBrush, etc. but I have tried above style and it is not working. Also I have tried:
<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
<Setter Property="{Binding Path=Margin, ElementName=myCustomTextBox}" Value="0" />
<Setter Property="{Binding Path=Padding, ElementName=myCustomTextBox}" Value="0" />
<Setter Property="{Binding Path=Margin, ElementName=myPlaceholderTextBlock }" Value="0" />
<Setter Property="{Binding Path=Padding, ElementName=myPlaceholderTextBlock }" Value="0" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="False">
<Setter Property="{Binding Path=Background, ElementName=myCustomTextBox}" Value="{x:Null}"/>
<Setter Property="{Binding Path=Foreground, ElementName=myCustomTextBox}" Value="{x:Null}"/>
<Setter Property="{Binding Path=BorderBrush, ElementName=myCustomTextBox}" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
If you want to be able to set properties of the myCustomTextBox from view that consumes your TextBoxWithPlaceholder control, you should add dependency properties to the latter and bind to them in TextBoxWithPlaceholder.xaml and set them in the consuming view, e.g.:
<ui:TextBoxWithPlaceholder ....PlaceHolderMargin="10" />
TextBoxWithPlaceholder.xaml:
<TextBlock Name="myPlaceholderTextBlock"
...
Margin="{Binding PlaceHolderMargin,RelativeSource={RelativeSource AncestorType=UserControl}}">
I am afraid you cannot refer to ElementName=myPlaceholderTextBlock from a namescope outside the TextBoxWithPlaceholder control so trying to do this in a Style that's defined in a consuming view won't work.
This is a task that cries out for a Custom control and Visual States instead of a UserControl with Triggers. But if you must do this as a UserControl (and I don't blame you because that's a lot to learn at this stage) then here goes:
First of all, when you use ElementName it is supposed to refer to elements that the XAML processor has already seen, previously in the current UI being laid out. Not elements inside the control being styled. I don't see that approach working.
If you want the TextBox and TextBlock inside a TextBoxWithPlaceholder to use the properties of that outer control, you could bind them to it, inside your control's XAML. For example, to rewrite a small part of that binding the background.
<TextBox Name="myCustomTextBox"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
Background={Binding RelativeSource={RelativeSource.FindAncestor, AncestorType={x:Type ui:TextBoxWithPlaceholder}, Path=Background}}"
But if you truly want that nested TextBox ("myCustomTextBox") to use a style with triggers and its own dedicated property values, then what you might try is creating a Resources section inside your style that itself contains implicit styles for the TextBox and TextBlock Something like this
<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
<Style.Resources>
<!-- Implicit style for TextBox should only apply to TextBoxes inside a TextBoxWithPlaceholder -->
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="False">
<Setter Property="Background Value="{x:Null}"/>
<Setter Property="Foreground" Value="{x:Null}"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
</Trigger>
<Style.Triggers>
</Style>
</Style.Resources>
</Style>

WPF binding ADUser doesn't update TextBox

I have a WPF GUI to show AD-objects loaded and operated through PowerShell.
The GUI works as planned, but some bindings does not update their controls (mainly TextBoxes). Some controls does however work without any problem.
I get the data through the Get-AD* cmdlets and it gets stored directly in the DataContext of a Grid higher up in the hierarchy. The properties I want to list are added to the cmdlet call with -Properties "EmailAddress", .... I have also tried -Properties *, but the bindings still doesn't update.
I suspect that only the properties that are inherited from the baseobject, works with the bindings. But I can be totaly of on this.
I have tried:
different settings for the binding, i.e. NotifyOnSourceUpdate...
separating the properties through Select-Object EmailAddress, ..., but this breaks it all and no controls are shown
using a test object (PSCustomObject) with the necessary properties, this also break the controls
if I assign the values directly in code, the textboxes show the correct info, i.e. $syncHash.tbUserotherTelephone.Text = $syncHash.gridObjInfo.DataContext.otherTelephone
For the example below, the object that is put in $syncHash.gridBaseInfoUser.DataContext is of type ADUser (BaseType: Microsoft.ActiveDirectory.Management.ADAccount)
Exampel of how the object the bindings are sourced from, is retrieved
$syncHash.gridObjInfo.DataContext = Get-ADUser $this.SelectedItem -Properties "EmailAddress", ...
$this.SelectedItems is the selected item in a DataGrid. I have verified that the correct data is retrieved at both Get-ADUser
Xaml hierarchy, showing two controls, one that gets the TextBox.Text entered, one that doesn't. Some code is omitted to save space.
<Window>
<Grid x:Name="gridMain">
...
<Grid x:Name="gridMainInfo" Grid.Row="1" DockPanel.Dock="Top" Width="Auto">
<Grid x:Name="gridObjInfo">
<Grid.Resources>
<Style x:Key="PropValueStyle" TargetType="{x:Type TextBox}">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="IsEnabled" Value="True"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
<Setter Property="Background" Value="LightGray"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="{x:Null}">
<Setter Property="Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<!-- Other grids for other objectclasses -->
<Grid x:Name="gridBaseInfoUser" Grid.Row="{DynamicResource TypeObjInfoRowLoc}">
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ObjectClass}" Value="user">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<!-- This binding apparently does not work, so the textbox is empty -->
<Border Style="{StaticResource PropBorder}" Grid.Row="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource PropTitleWidth}"/>
<ColumnDefinition Width="{StaticResource PropValueWidth}"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="tblUserotherTelephone" Style="{StaticResource PropTitleStyle}"/>
<TextBox x:Name="tbUserotherTelephone" Style="{StaticResource PropValueStyle}" Text="{Binding otherTelephone}"/>
</Grid>
</Border>
<!-- This binding apparently does work, so the textbox gets its Text set to the UserPrincipalName -->
<Border Style="{StaticResource PropBorder}" Grid.Row="6">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource PropTitleWidth}"/>
<ColumnDefinition Width="{StaticResource PropValueWidth}"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="tblUserUserPrincipalName" Style="{StaticResource PropTitleStyle}"/>
<TextBox x:Name="tbUserUserPrincipalName" Style="{StaticResource PropValueStyle}" Text="{Binding UserPrincipalName}"/>
</Grid>
</Border>
</Grid>
...
</Window>

Datatrigger in WPF

I am new to WPF, and learning the basics of WPF. What I want is when a CheckBox is checked then make the background of a Button green.
The following is to code I have written:
<Window x:Class="MyApplication.DataTrigger2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataTrigger2" Height="300" Width="300" Loaded="Window_Loaded">
<Window.Resources>
<Style x:Key="styleDataButton" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding chk}" Value="checked">
<Setter Property="Background" Value="Gold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button x:Name="btn" Height="50" Width="100" Content="Button" Margin="89,33,89,178" Style="{StaticResource styleDataButton}"/>
<CheckBox x:Name="chk" Content="Checkbox" Height="50" Width="100" Margin="89,106,89,105"></CheckBox>
</Grid>
</Window>
You need to bind to CheckBox element. Binding="{Binding chk}" means that you are binding to "chk" property of the DataContext. You should change it to:
<DataTrigger Binding="{Binding ElementName=chk, Path=IsChecked}" Value="True">
<Setter Property="Background" Value="Gold"/>
</DataTrigger>
That way, you are binding to IsChecked property of your CheckBox and checking if value is true.

WPF Combobox Validation.ErrorTemplate error

I have a combobox that i need to edit its error template to show a red border when there is a validation error.
I am using the following style
<Style TargetType="{x:Type ComboBox}" >
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="Red" BorderThickness="3">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontSize" Value="12" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
The border never shows up when validation errors occur. Any tips what is going wrong?
The Style you posted works. You should check your binding, did you add ValidatesOnDataErrors=True and ValidatesOnExceptions=True to the binding of SelectedValue?
enter code heretry without the dock panel, that is uneuseful since it wraps jus one element. However, sicnecerely I don't wnow if it makes sense to wrap a textbox with a border, since it has already a border! You should try to change directly the colour of its border. You could try to use again the panel but then put the border around the panel ie:
Border BorderBrush="Red" BorderThickness="3"
DockPanel
AdornedElement
This makes more sense because the wrap panel has not its own border.
Use This.
<Style x:Key="textBoxStyle" TargetType="{x:Type telerik:RadMaskedTextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Control.BorderBrush" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
I don't like any of the responses here. Simply put, how do you change the border color for the error template for a ComboBox using Blend or not? It shouldn't be acceptable to draw another border around the existing border of the ComboBox. I've figured out how to creat a ControlTemplate in Blend but not a Validation Template.
I've come close with trying to make it appear like I've changed the actual border color, but that's not what I actually want. Suggestions? To complicate it a bit, I'd like to display a red asterisk outside of the right border of the control.
The following code is a close attempt, but it is actually drawing a border inside the ComboBox and if you look close, you can see that the border is 2 pixels wide when combined with the ComboBox border:
<DockPanel Name="myDockPanel">
<AdornedElementPlaceholder>
<Border BorderBrush="Blue" BorderThickness="1" CornerRadius="2" />
</AdornedElementPlaceholder>
<TextBlock Text="*" FontWeight="Bold" FontSize="14" Foreground="Red" DockPanel.Dock="Left" ToolTip="{Binding .CurrentItem}" />
</DockPanel>
I searched around some more and came up with a solution based on another article here: WPF - How to apply style to AdornedElementPlaceholder's AdornedElement?
<!-- This works -->
<ComboBox Name="comboBox1" Style="{StaticResource NewComboBoxStyle}" Validation.ErrorTemplate="{StaticResource comboBoxValidationTemplate}" />
<SolidColorBrush x:Key="MainBorderBrush">#FF91B3FF</SolidColorBrush>
<Style x:Key="NewComboBoxStyle" TargetType="{x:Type ComboBox}" BasedOn="{StaticResource myErrorTemplate}">
<Setter Property="BorderBrush" Value="{DynamicResource MainBorderBrush}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderBrush" Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
<!-- Sets ToolTip when Validation.HasError is True. -->
<Style TargetType="Control" x:Key="myErrorTemplate">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="comboBoxValidationTemplate">
<DockPanel Name="myDockPanel">
<AdornedElementPlaceholder/>
<TextBlock Text="*" FontWeight="Bold" FontSize="14" Foreground="Red" DockPanel.Dock="Left" ToolTip="{Binding .CurrentItem}" />
</DockPanel>
</ControlTemplate>

WPF - How to apply style to AdornedElementPlaceholder's AdornedElement?

I'm trying to apply a style to an adorned element, but I don't know the correct syntax. Here is what I've tried:
<!-- ValidationRule Based Validitaion Control Template -->
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder Style="textStyleTextBox"/>
</DockPanel>
</ControlTemplate>
The only problem is that the following line doesn't work:
<AdornedElementPlaceholder Style="textStyleTextBox"/>
Any help would be greatly appreciated.
Thanks,
-Charles
Need to put where the resource is coming from.
<TextBox Style="{StaticResource textStyleTextBox}"/>
Then define the style in a resource such as the user control resources:
<UserControl.Resources>
<Style TargetType="TextBox" x:Key="textStyleTextBox">
<Setter Property="Background" Value="Blue"/>
</Style>
</UserControl.Resources>
However I dont believe you want to set the style of the adornedelement within the placeholder. It's just a placeholder for any control with that template. You should set the style of the adornedelement in the element itself like the example I provided above. If you want to style the control based upon it's validation then something like this:
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Yellow" Width="55" FontSize="18">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel x:Name="mainPanel">
<TextBlock>Age:</TextBlock>
<TextBox x:Name="txtAge"
Validation.ErrorTemplate="{DynamicResource validationTemplate}"
Style="{StaticResource textBoxInError}">
<Binding Path="Age" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<ExceptionValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox>
</StackPanel>

Resources