ToggleButton Control VisualStateManager: handling multiple hover states

Regarding the previous question, I came up with ( Silverlight MVVM Confusion: State-Based Image Update ). I started with a new approach. I left the existing question because I did not want to say for sure that my new approach was the correct answer (I still welcome comments on my original problem).

If you read my previous question, feel free to skip this paragraph: I am trying to create a control that provides functionality similar to the audio playback button. When the application is in "Play" mode, the application should display the image "Pause.png". When it pauses, it should display the image "Play.png". There are also two additional images to account for any state when the user hovers over the control (for example, "Play_Hover.png" and "Pause_Hover.png"). The state is determined by the IsPlaying property in my model.

I decided to use ToggleButton to determine which image will be displayed based on the current state. Remember that when IsPlaying is false, the play button is displayed, and when it is true, the pause button is displayed. So I came up with the following XAML. It works, with the exception of freezes:

<UserControl x:Class="Foo.BarMyControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="200"> <UserControl.Resources> <Style x:Key="MyButtonStyle" TargetType="ToggleButton"> <Setter Property="IsEnabled" Value="true"/> <Setter Property="IsTabStop" Value="true"/> <Setter Property="Background" Value="#FFA9A9A9"/> <Setter Property="Foreground" Value="#FF000000"/> <Setter Property="MinWidth" Value="5"/> <Setter Property="MinHeight" Value="5"/> <Setter Property="Margin" Value="0"/> <Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Top" /> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Cursor" Value="Hand"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ToggleButton"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CheckStates"> <VisualState x:Name="Checked"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Pause"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Play"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Collapsed</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Unchecked"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Play"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Pause"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Collapsed</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Indeterminate" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Image x:Name="Play" Source="/Foo.Bar;component/Resources/Icons/Bar/Play.png" /> <Image x:Name="Pause" Source="/Foo.Bar;component/Resources/Icons/Bar/Pause.png" Visibility="Collapsed" /> <Image x:Name="PlayHover" Source="/Foo.Bar;component/Resources/Icons/Bar/Play_Hover.png" Visibility="Collapsed" /> <Image x:Name="PauseHover" Source="/Foo.Bar;component/Resources/Icons/Bar/Pause_Hover.png" Visibility="Collapsed" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <ToggleButton Style="{StaticResource MyButtonStyle}" IsChecked="{Binding LiveEnabled}" Command="{Binding ChangeStatus}" Height="30" Width="30" /> </Grid> </UserControl> 

How do you have different hover images for both states? If I add the Hover state to the CommonStates group, I can only take into account the freezing of only one of the states (checked or not checked).

+4
source share
3 answers

It is not possible to have a different hover / mouse state with the switch, as this is common to the button. General conditions: Normal (what you see at the beginning), Mouseover, Pressed and Disabled

Other conditions associated with verified, unverified, or intermediate. Here, as you did, you can set different images, etc. For different conditions. Hovering will always return to the general state.

If you need this functionality, you can create your own custom control for this and handle the active state mouseover animation. This would require more code on the internal server, since you would need to override the button class for this object and insert testing for different states to allow set animations for each state. It can be done, I just don’t know if it will cost so much effort.

+4
source

You can also achieve this without special controls, for example:

  <ToggleButton x:Name="PlayButton" IsChecked="{Binding Path=IsPlayChecked, Mode=TwoWay}" Width="30" Height="30" Style="{StaticResource ToggleButtonStyle}"> <ToggleButton.Content> <Grid Margin="3"> <Grid Visibility="{Binding ElementName=PlayButton, Path=IsChecked, Converter={StaticResource visConverter}, ConverterParameter=Invert}" Margin="0"> <Image Source="../Resources/play_normal.png"></Image> <Image Source="../Resources/play_hover.png" Visibility="{Binding ElementName=PlayButton, Path=IsMouseOver, Converter={StaticResource visConverter}}"></Image> <Image Source="../Resources/play_hit.png" Visibility="{Binding ElementName=PlayButton, Path=IsPressed, Converter={StaticResource visConverter}}"></Image> <Image Source="../Resources/play_disabled.png" Visibility="{Binding ElementName=PlayButton, Path=IsEnabled, Converter={StaticResource visConverter}, ConverterParameter=Invert}"></Image> </Grid> <Grid Visibility="{Binding ElementName=PlayButton, Path=IsChecked, Converter={StaticResource visConverter}}"> <Image Source="../Resources/pause_normal.png"></Image> <Image Source="../Resources/pause_hover.png" Visibility="{Binding ElementName=PlayButton, Path=IsMouseOver, Converter={StaticResource visConverter}}"></Image> <Image Source="../Resources/pause_hit.png" Visibility="{Binding ElementName=PlayButton, Path=IsPressed, Converter={StaticResource visConverter}}"></Image> <Image Source="../Resources/pause_disabled.png" Visibility="{Binding ElementName=PlayButton, Path=IsEnabled, Converter={StaticResource visConverter}, ConverterParameter=Invert}"></Image> </Grid> </Grid> </ToggleButton.Content> </ToggleButton> 

Where:

 <Converters:BooleanToVisibilityConverter x:Key="visConverter"/> 

And defined BooleanToVisibilityConverter as follows:

 public class BooleanToVisibilityConverter: IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var flag = (bool)value; if (parameter != null) flag = !flag; return (flag ? Visibility.Visible : Visibility.Collapsed); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return ((value is Visibility) && (((Visibility)value) == Visibility.Visible)); } } 

Hope this helps!

+3
source

I know that he is very old. But I just met the same problem and I fixed it differently. (Note that the solution works for UWP, I have not tested it in WPF or Silverlight)

  • Create a new Templated Control and button based
  • Copy the default style and template in the switch style from MSDN and paste it into Themes / Generic.xaml
  • Add below eventHandler for events: PointerExited, PointerReleased, GotFocus, LostFocus, PointerEntered and LayoutUpdated. EventHandler will call the ChangeVisualState method
  • Create a new ChangeVisualState method

     public void ChangeVisualState(bool pointerEnter = false) { string stateName = "Normal"; if (isChecked) { if (pointerEnter) { stateName = "CheckedPointerOver"; } else { stateName = "Checked"; } } else { if (pointerEnter) { stateName = "PointerOver"; } else { stateName = "Normal"; } } VisualStateManager.GoToState(this, stateName, true); } 

End Code:

 public sealed class MyToggleButton : Button { bool isChecked = false; public MyToggleButton() { this.DefaultStyleKey = typeof(MyToggleButton); this.Tapped += MyToggleButton_Tapped; this.PointerExited += MyToggleButton_PointerExited; this.PointerReleased += MyToggleButton_PointerReleased; this.GotFocus += MyToggleButton_GotFocus; this.LostFocus += MyToggleButton_LostFocus; this.PointerEntered += MyToggleButton_PointerEntered; this.LayoutUpdated += ToggleButtonEx_LayoutUpdated; } public void ChangeVisualState(bool pointerEnter = false) { string stateName = "Normal"; if (isChecked) { if (pointerEnter) { stateName = "CheckedPointerOver"; } else { stateName = "Checked"; } } else { if (pointerEnter) { stateName = "PointerOver"; } else { stateName = "Normal"; } } VisualStateManager.GoToState(this, stateName, true); } private void MyToggleButton_PointerEntered(object sender, PointerRoutedEventArgs e) { ChangeVisualState(true); } private void MyToggleButton_LostFocus(object sender, RoutedEventArgs e) { ChangeVisualState(); } private void MyToggleButton_GotFocus(object sender, RoutedEventArgs e) { ChangeVisualState(); } private void MyToggleButton_PointerReleased(object sender, PointerRoutedEventArgs e) { ChangeVisualState(); } private void MyToggleButton_PointerExited(object sender, PointerRoutedEventArgs e) { ChangeVisualState(); } private void MyToggleButton_LayoutUpdated(object sender, object e) { ChangeVisualState(); } private void MyToggleButton_Tapped(object sender, TappedRoutedEventArgs e) { isChecked = !isChecked; ChangeVisualState(true); } } 
+1
source

Source: https://habr.com/ru/post/1340365/


All Articles