A Sane and Simple Image Button for WPF With Triggers? - c#

I've looked at some other Stack Overflow Questions, but none seem to give a simple solution to this.
I have a button that should be an image. On MouseOver the image changes, same as with MouseClick.
I have the code for MouseOver here:
<Button Name="btnNext" Grid.Row="3" Padding="15 3" HorizontalAlignment="Right" Click="OnButtonClick" Visibility="Hidden">
<Button.Template>
<ControlTemplate>
<Border>
<Image Width="90" Height="90">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="/resources/Button1_A.png" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="/resources/Button1_B.png" />
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
This seems like an awful lot of code for something that, I feel, should be relatively simple. Is there a simpler solution that I am missing?

Well ... you define a control template - and the main benefit for defining a control template is making it reusable. Templates are rarely defined inline the element, but typically in a resource dictionary (or even in the theme dictionary, so you can have a different template for each team). If you had 3 buttons, you could use it templates like that:
<Window.Resources>
<ControlTemplate x:Key="ct" TargetType="Button">
<Border>
<Image Width="90" Height="90">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="/resources/a.png" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="/resources/b.png" />
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Border>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<Button Name="btnPrev" Padding="15,3" Click="OnButtonClick" Template="{StaticResource ct}" />
<Button Name="btnNext" Padding="15,3" Click="OnButtonClick" Template="{StaticResource ct}" />
<Button Name="btnFinish" Padding="15,3" Click="OnButtonClick" Template="{StaticResource ct}" />
</StackPanel>
There are still some opportunistic shortcuts you can take:
If you aren't doing anything with Border, you can remove it. It contributes nothing.
There is no need to modify the Button.Template and have a ControlTemplate - remove these two lines
You will end up with (6 lines shorter):
<Button Name="btnNext" Padding="15,3" HorizontalAlignment="Right" Click="OnButtonClick">
<Image Width="90" Height="90">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/resources/a.png" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="/resources/b.png" />
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
Finally, if you want to optimize your program to number of lines - trading off engineering practices - you'll have a shorter program with MouseEnter and MouseLeave events. With 3 lines of code for each event handler in the code behind, and 3 lines of code for the <Button><Image/><Button> int the XAML file, you are with 9 lines of code.

Related

Change property of controls child object by using Styles or DataTemplate in WPF application

Let's say I have a button and I have an image (though it could be any controls) inside of it:
<Button Width="150" Height="80">
<Image Source="..." Width="50" Height="50" />
</Button>
I want to change the style of button when mouse is over it and make it so that I could also change properties of the image inside when it happens.
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.5" />
<!-- Here I want to change alignment of the image inside the button if there is one -->
<Setter Property="Image.HorizontalAlignment" Value="Right" />
</Trigger>
</Style.Triggers>
</Style>
But it doesn't work this way, and for some reason any child controls start to change alignment, not only images but also text and other controls. The other things I tried just didn't compile. Probably DataTemplate should be used; I'm new to WPF and XAML and I still don't understand it enough and couldn't find what I need yet.
You should use HorizontalContentAlignment to set the content alignment of a button:
<Setter Property="HorizontalContentAlignment" Value="Right" />
Based on your comment below, I can give an example:
<Button Grid.Row="2" Width="150" Height="80">
<DockPanel Height="80" Width="150"><!-- A more complex control here-->
<Image Width="50" Height="50">
<Image.Source>
<BitmapImage DecodePixelWidth="50" UriSource="https://www.gravatar.com/avatar/9eae4ae63d70142e81f0365de42474ae?s=32&d=identicon&r=PG&f=1" />
</Image.Source>
</Image>
</DockPanel>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
<Style.Resources>
<Style TargetType="Image"><!-- The style target to the image control only in the button control -->
<Style.Triggers>
<!-- Binding to IsMouseOver of the button-->
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" Value="True">
<Setter Property="HorizontalAlignment" Value="Right" />
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
</Button.Style>
</Button>

Using style triggers on a static resource style to change more than properties

I am using Material Design XAML and I am trying to set a togglebutton icon and background color to something else when checked.
RED BOX CODE
<ToggleButton ToolTip="MaterialDesignFlatPrimaryToggleButton"
IsChecked="False"
Grid.Column="2"
Margin="78,58,99,52"
Grid.Row="1">
<Style TargetType="ToggleButton"
BasedOn="{StaticResource MaterialDesignFlatPrimaryToggleButton}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="Green" />
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton>
GREEN BOX CODE
<ToggleButton Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}"
ToolTip="MaterialDesignFlatPrimaryToggleButton"
IsChecked="False"
Grid.ColumnSpan="2"
Grid.Column="3"
Margin="205,58,188,52"
Grid.Row="1">
<md:PackIcon Kind="WindowClose"
Foreground="Red"
Height="21"
Width="21"/>
</ToggleButton>
I was thinking I could use the basedon attribute to reserve the style with material design...if possible.
I am trying reserve the style and change the icon and set background color when checked to look like this:
How would I do this?
You are currently setting the Style as the ToggleButton.Content.
You've got to define the Style nested into the ToggleButton.Style property:
<ToggleButton ToolTip="MaterialDesignFlatPrimaryToggleButton"
IsChecked="False"
Grid.Column="2"
Margin="78,58,99,52"
Grid.Row="1">
<ToggleButton.Style>
<Style TargetType="ToggleButton"
BasedOn="{StaticResource MaterialDesignFlatPrimaryToggleButton}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="Green" />
<Setter Property="Content">
<Setter.Value>
<md:PackIcon Kind="SmileyHappy" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>

ContentControl switch between two buttons

I've been banging my head over this all night. All I want to do is have a content control that can switch between showing two different buttons according to a boolean in the ViewModel.
Basically I have a task running in the background with a cancel button. Once you hit cancel and the task stops, the cancel button should change to a back button. These are two separate button elements, not just changing a single button's properties. I'm using MahApps TransitioningContentControl so I want to be able to use the transitions.
I would be amazing if this could mostly be done all in XAML. I really don't want to have to add a bunch of boilerplate code for what should be a simple thing.
Edit: Here's the snippet of code. There isn't much because I just deleted everything that wasn't working.
<Controls:TransitioningContentControl x:Name="CancelBackButtonControl" HorizontalAlignment="Left" VerticalAlignment="Top" Width="80" Height="80">
<Canvas>
<Button x:Name="CancelButton" HorizontalAlignment="Left" VerticalAlignment="Top" Width="80" Style="{DynamicResource MetroCircleButtonStyle}" Height="80" IsCancel="True" Content="{StaticResource appbar_close}" BorderBrush="Black" Command="{Binding CancelCommand}"/>
<Button x:Name="GoBackButton" HorizontalAlignment="Left" VerticalAlignment="Top" Width="80" Style="{DynamicResource MetroCircleButtonStyle}" Height="80" IsCancel="True" Content="{StaticResource appbar_arrow_left}" BorderBrush="Black" Command="{Binding GoBackCommand}"/>
</Canvas>
</Controls:TransitioningContentControl>
There are a staggering amount of ways to accomplish this (that's the beauty of WPF); however, in my personal opinion, you would be working less "against the grain" if you simply use a control that derives from the ToggleButton, such as RadioButton, especially since you want it to be done all in XAML. An important thing is not to think of any of the controls by their visuals, but by how they function. I've done some incredible things with RadioButton before, so that's the first thing I'll demonstrate; however, the regular button approach is included in the second half of this answer.
Here are the complete examples of both approaches (RadioButton and Button), done completely in XAML:
Preview:
Code:
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type RadioButton}" x:Key="FlatRadioButtonStyle">
<Setter Property="Width" Value="100"/>
<Setter Property="Background" Value="#FF346FD6"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Padding" Value="5,2,5,3"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<Border Background="{TemplateBinding Background}"
Width="{TemplateBinding Width}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}">
<ContentPresenter Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FF6696E9"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<RadioButton x:Name="BackButton" Content="Back">
<RadioButton.Style>
<Style TargetType="{x:Type RadioButton}" BasedOn="{StaticResource FlatRadioButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=CancelButton}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
<RadioButton x:Name="CancelButton" Content="Cancel" IsChecked="True">
<RadioButton.Style>
<Style TargetType="{x:Type RadioButton}" BasedOn="{StaticResource FlatRadioButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=BackButton}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
</Grid>
</Window>
Most of the resource style code is visual styling and templating, focused on making radio buttons look like regular buttons, so it's not of great importance. The areas we'll focus on are Triggers. You'll notice that in the resource style, I ensure that when a RadioButton gets checked, it collapses. However, in the local style of each button, I ensure that when the other RadioButton gets checked, it makes the current RadioButton visible. This has to be done in the local style, since we need to pass the ElementName into the Binding. So, when a RadioButton gets checked, it collapses and makes the other RadioButton visible. You'll also note that I check the button I wish to hide by default. Obviously, you can wire that up with bindings.
Similar approaches may be applied to regular buttons:
Preview:
Code:
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="ToggleButtonStyle">
<Setter Property="Width" Value="100"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button x:Name="CancelButton" Content="Cancel">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ToggleButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused, ElementName=BackButton}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button x:Name="BackButton" Content="Back">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ToggleButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused, ElementName=CancelButton}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
Here, instead of IsChecked, I'm working with IsFocused. You may accomplish a similar thing by working with the EventTrigger and Click event... but, there's more work. IsPressed may appear like a good candidate, but the problem is that once you press the button, the other one will appear almost instantaneously, and that one will have IsPressed set to true almost instantaneously. So, you'll end up with this cyclic behavior where it seems like nothing is happening. Note that I use Grid to place these buttons on top of each other, with the the one I want to be visible by default at the top, that way I don't have to worry about default visibility or focus. However, you may use any other panel, just set the Visibility of the button you want to hide by default to Collapsed.
If you don't want to work with multiple controls (two buttons in this case), you may also set the Content property of a button based on a condition through DataTrigger to display different text. You just have to ensure that you handle the Command appropriately.
Since you're asking for a XAML only way to switch the button in a content control based on a ViewModel property, how about this:
First define button style to route both buttons to the same event:
<Style x:Key="buttonClickButton" TargetType="Button">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
Then define your content control style with a data trigger switching the content based on a ViewModel property called "IsCancelButton" (bool):
<Style x:Key="ButtonSwitchContentCtrl" TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCancelButton}" Value="True">
<Setter Property="Content">
<Setter.Value>
<Button x:Name="CancelButton" Style="{StaticResource buttonClickButton}"
HorizontalAlignment="Left" VerticalAlignment="Top"
Width="80" Height="80" IsCancel="True" Content="Cancel Button" BorderBrush="Black" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsCancelButton}" Value="False">
<Setter Property="Content">
<Setter.Value>
<Button x:Name="GoBackButton" Style="{StaticResource buttonClickButton}"
HorizontalAlignment="Left" VerticalAlignment="Top"
Width="80" Height="80" IsCancel="True" Content="Go Back Button" BorderBrush="Black" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Then make your button(s) appear like so:
<ContentControl Style="{StaticResource ButtonSwitchContentCtrl}"/>
You can implement such functionality with just one Button (e.g. declared in XAML as Name="btnCancel") and simple code snippet in C# code-behind using Lambda-style event subscription like:
btnCancel.Click+=(s,e)=>{
if (btnCancel.Text=="Cancel")
{
// SOME CODE CORRESPONDING TO "CANCEL" CLICK EVENT
btnCancel.Text ="GoBack"
}
else
{
// CORRESPONDING TO "GoBack" CLICK EVENT
btnCancel.Text ="Cancel"
}
}
In case you want to use some graphic content (images) on the Button, then use Tag property of that Button instead of Text and also programmatically switch between images.
Couple other considerations: the business logic you have described could be implemented in XAML using DataTriggers (as other folks did), but instead of writing megaton of XAML for 2-Buttons solutions it would be reasonable to implement a single-button solution, either like this one, or using .NET ToggleButton Class (re: https://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.togglebutton%28v=vs.110%29.aspx); also, it could be a CheckBox control just properly styled.
In any way, you most likely will need the event handlers to do some actual job in addition to just changing the visual state of the Button, so that compact Lambda-style event subscription would be handy.
Hope this may help. Best regards,

XAML - refine watermark code

I am creating a log in page for my wpf application and want to add watermarks onto the username and password fields. I can achieve this by duplicating code but this is not a satisfactory solution for me. Is there a way I can pass string value through to the watermark binding seen in my Grid.resources section?
the goal is to refactor these two controls so they both can use the single resource login hint below, I have done research on this but came up short on information. Thanks
<Grid.Resources>
<VisualBrush x:Key="LoginHint" Stretch="Uniform" AlignmentX="Left" AlignmentY="Center" >
<VisualBrush.Visual>
<Grid HorizontalAlignment="Left">
<TextBlock FontFamily="Corbel"
HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="Gray" FontStyle="Italic" Opacity="0.3"
Text="Enter Username"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Resources>
<TextBox FontSize="24" BorderBrush="#008AB8" BorderThickness="1" Grid.Row="1" x:Name="usernameTextBox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="25">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=usernameTextBox,Path=Text}" Value="" >
<Setter Property="Background" Value="{StaticResource LoginHint}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<PasswordBox FontSize="24" BorderBrush="#008AB8" BorderThickness="1" Grid.Row="2" x:Name="passwordMarkTextBox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="25">
<PasswordBox.Style>
<Style TargetType="{x:Type PasswordBox}">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=passwordMarkTextBox,Path=Text}" Value="" >
<Setter Property="Background" Value="{StaticResource LoginHint}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</PasswordBox.Style>
</PasswordBox>
I would create a custom attached property (e.g., WatermarkText) and then create new control templates which bind to that value and superimpose the watermarks on top of the regular text area. Use triggers to govern its visibility, showing it only when no text or password has been entered. You may need a separate, readonly (computed) property to control its visibility in order to work with both TextBox and PasswordBox.

WPF change Image when mouse hover on button

I'm trying to change the image when mouse is over a button, but it's not working here is what am trying to do :
<Button x:Name="Play" Content="" ClickMode="Press" BorderThickness="0" UseLayoutRounding="True" Height="120" Width="224">
<Button.Background>
<ImageBrush ImageSource="Resources/Play 1.gif"/>
</Button.Background>
<Button.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ImageBrush.ImageSource" Value="Resources/Play 2.gif"></Setter>
</Trigger>
</Button.Triggers>
</Button>
But this gives me this error : Triggers collection members must be of type EventTrigger.
How would I make it work?
It's correct - you can only put EventTriggers in there. You need to move your Trigger into the button's Style:
<Button x:Name="Play" Content="" ClickMode="Press" BorderThickness="0" UseLayoutRounding="True" Height="120" Width="224">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="..\Resources\Play 1.gif"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="..\Resources\Play 2.gif"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Something like that, I think (haven't tested it).
EDIT: This switches between the correct images, but it doesn't work correctly (it starts off with Play 1.gif, transitions to Play 2.gif, but then transitions to the button's underlying colour). I think you'll have to override the button's ControlTemplate to prevent this from happening, as suggested in the comments.
<Window x:Class="Image_ChangeOnHover"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Image_ChangeOnHover" Height="300" Width="300">
<Grid>
<Image Stretch="None" Margin="10">
<Image.Resources>
<Style TargetType="{x:Type Image}">
<!-- Default image -->
<Setter Property="Source" Value="/Resources/Off2.png"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- Hover image -->
<Setter Property="Source" Value="/Resources/On.png"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Resources>
</Image>
</Grid>
</Window>

Categories