4

In reference to a previous question I had (Silverlight MVVM Confusion: Updating Image Based on State) I started with a new approach. I left the existing question because I didn't want to say for sure my new approach was the correct answer (I still welcome comments on my original problem).

If you read my previous question, please feel free to skip this paragraph: I'm trying to build a control that provides functionality similar to an audio play button. When the app is in its "Play" mode, the app should display the "Pause.png" image. When it's paused, it should display the "Play.png" image. There are also two additional images to account for either state for when a user hovers over the control (e.g., "Play_Hover.png" and "Pause_Hover.png"). The state is determined by the an IsPlaying property in my view model.

I decide to use a ToggleButton to determine which image to display based on the current status. Remember, when IsPlaying is false, the play button is displayed and when it's true, the pause button is displayed. As such, I came up with the following XAML. It works except for the hovers:

<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 a Hover state to the CommonStates group, I'll be able to account for hover of only one of the states (checked or unchecked).

Community
  • 1
  • 1
senfo
  • 28,488
  • 15
  • 76
  • 106

3 Answers3

4

With the togglebutton it is not possible to have a different hover / Mouse over state as this is common to the button. Common states are Normal (what you initally see), Mouseover,Pressed and Disabled

The other states related to checked, unchecked or intermediate. Here as you have done you can set different images etc for the various state. The mouseover though will always roll back to the common state.

If you have to have this funcionality you could create your own custom control for this and handle the mouseover animation based upon the active state. This would require more code on the back-end as you will need to redefine the button class for this object and insert testing for the various states to allow a set animation to play for each state. It could be done I just don't know if it would be worth that much effort.

rlcrews
  • 3,482
  • 19
  • 66
  • 116
3

you also can achieve this without custom control, like this:

        <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 like this:

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 it'll help!

Ivan Leonenko
  • 2,363
  • 2
  • 27
  • 37
1

I know it's very old. But I just met the same problem and I fixed in another way. (Note, the solution work for UWP, I haven't test it on WPF or Silverlight)

  1. Create a new Templated Control and based on the Button
  2. Copy the toggle button's default style and template from MSDN and paste it to the Themes/Generic.xaml
  3. Add below eventHandler for events: PointerExited, PointerReleased, GotFocus, LostFocus, PointerEntered and LayoutUpdated. The eventHandler will call ChangeVisualState method
  4. Create a new method ChangeVisualState

    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);
    }
    

The final 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);
    }
}
Charlie
  • 2,141
  • 3
  • 19
  • 35