How to display validation error in button bound to command - c#

I am developing an app and trying to stick to an MVVM model, I have a button that is just bound to a command. Currently the command can be enabled / disabled and greys out the button when the command is set to CanExecute false. But I want to add the ability to show validation errors on the button (by changing its color to red, maybe showing a tooltip).
The validation is currently already shown in another location in the UI, but the users are getting annoyed that the button is enabled and they click it only to have a message box pops up telling them they shouldn't of clicked the button because there is a validation error. But just disabling the button completely without showing them why would make the users confused(since they overlook the large red validation error at the bottom of the screen). So how can I show additional information on the save button?
This is my current button xaml:
<Button Name="SaveDataPointButton" Style="{StaticResource ImageButton}" Command="{Binding OpenDataPoint.SaveDataPointEditsCommand}" Margin="5,0" VerticalAlignment="Stretch">
<Image Source="save.png" Stretch="None" ToolTip="Save Edits"/>
</Button>
And the style that is changing its appearance when IsEnabled is false, is it possible to somehow inspect some other command state from the style to make the button red during validation errors?
<!-- Style for all of the nav and save buttons in the datapoints view -->
<Style x:Key="ImageButton" TargetType="Button">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="0">
<ContentPresenter
Margin="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Border" Property="Background">
<Setter.Value>
<SolidColorBrush Color="LightGray"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="Border" Property="Background">
<Setter.Value>
<SolidColorBrush Color="Gray"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Command" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>

I ended up using a DataTrigger bound to another property on the view model to change the background color, but because I was setting a ControlTemplate in the style, the only way I could figure out to make the DataTrigger apply a background change was to copy the entire style and put the DataTrigger on the Border element in the control template. Since apparently you can't reference a child element from the new ControlTemplate from outside that control template.
<!-- Style for all of the nav and save buttons in the datapoints view -->
<Style x:Key="ImageSaveButton" TargetType="Button">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="0">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding OpenDataPoint.HasValidationError}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ContentPresenter
Margin="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Border" Property="Background">
<Setter.Value>
<SolidColorBrush Color="LightGray"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="Border" Property="Background">
<Setter.Value>
<SolidColorBrush Color="Gray"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Command" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>

Related

How to make button highlight when hovered over using a template

Recently started WPF and am very new to XAML. I'm trying to make a calculator that looks similar to the IOS one. However, after I changed the Button to Ellipse, the highlighting when hovered over or clicking on stopped working, if also the highlighting issue were to be fixed, how would I go about changing the colour of it?
<Button x:Name="ButtonEquals" Grid.Column="4" Grid.Row="6" Width="47" Height="47"
Content="=" Foreground="White"
BorderBrush="{x:Null}" Background="#FFFF9500">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Fill="#FFFF9500"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
You can add a Trigger to the ControlTemplate and target the Ellipse by name assigning an x:Name and referring to it via TargetName. This way, you can define other states as well.
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse x:Name="MyEllipse" Fill="#FFFF9500"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="True">
<Setter TargetName="MyEllipse" Property="Fill" Value="OrangeRed"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="MyEllipse" Property="Fill" Value="Blue"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="MyEllipse" Property="Fill" Value="Red"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="MyEllipse" Property="Fill" Value="Gray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
This targets only the Fill of the Ellipse, without changing other controls in the template. However, the values are hardcoded and not changable from outside.
If you want the template to be customizable to a certain degree, then you can put it in a Style and bind properties on the templated control using TemplateBinding or a RelativeSource with TemplatedParent for two-way and special scenarios. Additionally, you can add a FocusVisualStyle that allows to specify a template for keyboard focusing.
<Style TargetType="Button">
<Setter Property="Background" Value="#FFFF9500"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Ellipse StrokeDashArray="1 2" Stroke="DarkGreen" StrokeThickness="2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse x:Name="MyEllipse" Fill="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="True">
<Setter TargetName="MyEllipse" Property="Fill" Value="OrangeRed"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="MyEllipse" Property="Fill" Value="Blue"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="MyEllipse" Property="Fill" Value="Red"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="MyEllipse" Property="Fill" Value="Gray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If you want to reuse the style on multiple buttons, add it to a resource dictionary in scope and assign an x:Key to refer to it via StaticResource or DynamicResource.
You need to create Style of button instead Template. Button's template you can set inside style (read more about Style in WPF):
<Button x:Name="ButtonEquals"
Grid.Column="4"
Grid.Row="6"
Width="47"
Height="47"
Content="=">
<Button.Style>
<Style TargetType="Button">
<!-- Here is properties for buttons with same style-->
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
<Setter Property="Background" Value="#FFFF9500"/>
<!-- Here is your template -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<!-- Here is style triggers for interact with button -->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- Set color as you wish -->
<Setter Property="Background" Value="LightSalmon"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
But actually, you need to create global ResourceDictionary (read about it) and there set styles for all buttons like <Style x:Key="CalculatorButton"> for set to buttons like <Button Content="1" Style="{StaticResource CalculatorButton}"/>

Multiple styles and content on one button

I have a HomePage with a number of buttons on that I am trying to get to do a couple of things. Firstly, I have a Style in a ResourceDictionary that dictates the overall 'look and feel' of the buttons. It looks like this:
<Style TargetType="Button" x:Key="HomeButton">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#06658D"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" BorderBrush="White" BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="#06658D"/>
</Trigger>
</Style.Triggers>
</Style>
Nothing too complicated, however I want the MouseOver effect to persist after being clicked. So now I am looking at doing something like this:
<Button Grid.Row="0" Style="{StaticResource HomeButton}" Content="Contacts">
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ContactsClicked}" Value="True">
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="#06658D"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button>
I'll bind the command element of the Button to set ContactsClicked = true and reset this when another button is pressed. However, I get the error saying that Content is set multiple times.
Is it possible to have an overall Style of the Button set, as well as a 'Clickedstyle and to have aTextBlockdisplaying text on the button all at once or am I approaching this wrong? I am trying to not have an individual overall style for every singleButton`as that's a lot of repeated code.
For clarity this is what I am aiming for:
I've managed to find a solution myself, instead of using a Button us a ToggleButton instead:
<Style TargetType="ToggleButton" x:Key="HomeButton">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#06658D"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="{TemplateBinding Background}" BorderBrush="White" BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="#06658D"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="#06658D"/>
</Trigger>
</Style.Triggers>
</Style>
I would add this as a comment but im not allowed.
If your goal is that the button will stay selected after it is clicked, why not use a RadioButton? A RadioButton has a built-in property called IsChecked but will also remain "Checked" or "Selected" even after multiple clicks.
Of course you can define the behavior and the styles in a similar manner, create a simple Trigger on the property IsChecked.
If more explanation is needed, just say so.

How to implement event triggered WPF control style change

I have an style template for my Button control that looks like that:
<Style x:Key="myBtnStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" Width="100" Height="25"
Padding="5,5,5,5" CornerRadius="5,5,5,5"
Background="LightGray" BorderBrush="Black"
BorderThickness="1,1,1,1">
<ContentPresenter
x:Name="cpButton"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Width="Auto" Height="Auto" Margin="-6">
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="border" Value="GhostWhite"></Setter>
<Setter Property="BorderBrush" TargetName="border" Value="Gainsboro"></Setter>
<Setter Property="Foreground" Value="Gray"></Setter>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" TargetName="border" Value="SkyBlue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
My button instance itself:
<Button Name="btnResetCount" Content="Reset" Command="{Binding Path=CalcViewModel.ResetCounter}" Style="{StaticResource myBtnStyle}" IsEnabled="{Binding IsButtonCounterEnabled,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
The binding for the IsEnabled Property works fine. So the IsEnabled gets set correct from my ViewModel.
But my problem is that my Styles for IsEnabled <ControlTemplate.Triggers> do not get loaded/refreshed automatically in the UI. I always have to click to any other control first. Once i clicked to another control the style of the button changes also.
What can i do to automatically update the/refresh the style of the button/any control based on the IsEnabled event?
EDIT:
Finally i got it working with an workaround. Im not happy and im sure that there are better ways. But it works.
In my ViewModel i implemented:
CommandManager.InvalidateRequerySuggested();
By calling this method the controls get refreshed.
I just call it in one of my Properties if the condition is given to enable the button..
Thanks in advance!
Create one more trigger and set the style for IsEnabled=true:
<Trigger Property="IsEnabled" Value="true">
<Setter Property="Background" TargetName="border" Value="Black"/>
<Setter Property="BorderBrush" TargetName="border" Value="Black"/>
<Setter Property="Foreground" Value="Gray"/>
</Trigger>

ResourceDictionary Style Triggers not working

from the following Trigger only the Foreground Setter is working. I do not understand why.
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
Thanks for any help.
The reason this doesn't work is because the default Button template uses a ButtonChrome that draws the border and background and handles states like mouseover and disabled depending on the Windows theme (e.g. Windows XP style, Windows 7 style).
In order to allow your triggers to be applied, you will need to define a custom Button template that uses standard stylable WPF elements, like Border, instead of ButtonChrome. Here's a bare-bones working example:
<Button Content="button">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderThickness" Value="3" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Property=Background}"
BorderBrush="{TemplateBinding Property=BorderBrush}"
BorderThickness="{TemplateBinding Property=BorderThickness}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red" />
<Setter Property="BorderBrush" Value="Green" />
<Setter Property="Foreground" Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Button in WPF has a default control template. The correct way to override the Button's default behavior is by overriding default control template. This can be done with something similar to below:
<Button Width="100" Height="50" Content="Click Me!">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="bdr_main" CornerRadius="20" Margin="4" BorderThickness="1" BorderBrush="Black" Background="LightGray">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" Margin="8,6,8,6" ContentSource="Content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bdr_main" Property="Background" Value="LightGreen"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="bdr_main" Property="Background" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
Found # http://harishasanblog.blogspot.com/2011/02/ismouseover-trigger-not-working-in-wpf.html
Hope it helps!
Try using the trigger on the controltemplate.
<ResourceDictionary>
<Style x:Key="TestButton" TargetType="Button">
<Setter Property="Height" Value="30" />
<Setter Property="Width" Value="30" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="Green" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Purple" />
</Trigger>
</ControlTemplate.Triggers>
<Grid>
<Rectangle Fill="{TemplateBinding Foreground}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

Disable style of one text box among the 10 in WPF application

I have use 10 TextBox in my application and in that same application i have defined the style in App.xaml It applies for all the text boxes in my application . how to disable the style applying for a single TextBox.
Can any one help me out in this.
I used the below code to set the style
<Style TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border Background="White"
x:Name="Bd" BorderBrush="#FF50729f" CornerRadius="3"
BorderThickness="1"
>
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" TargetName="Bd" Value="#FFe0dfe3"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="#FF9da3ab"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="#FFd22c2c" BorderThickness="1"
Background="#FFfce8e8" CornerRadius="3" >
<AdornedElementPlaceholder></AdornedElementPlaceholder>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#FFe0dfe3"/>
<Setter Property="BorderBrush" Value="#FF9da3ab"/>
</Trigger>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
if you just want the default style then
<TextBox Style="{x:Null}"/>
otherwise Cédric Bignon's answer with target type defined (like below) will do the job.
No property will be inherited by your application TextBoxStyle
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<!-- your setters-->
</Style>
</TextBox.Style>
</TextBox>
if you want to slightly change the default application style
then use the following tecqnique where in your style you can
redefine properties you want to be different from the default
or define additional ones
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
<!-- your setters-->
</Style>
</TextBox.Style>
</TextBox>
Just use an empty style:
<TextBox>
<TextBox.Style>
<Style>
</Style>
</TextBox.Style>
</TextBox>

Categories