ToggleButton控件VisualStateManager:处理多个悬停状态

时间:2011-02-18 18:23:45

标签: silverlight mvvm

参考上一个问题(Silverlight MVVM Confusion: Updating Image Based on State),我开始采用新方法。我离开了现有的问题,因为我不想肯定我的新方法是正确的答案(我仍然欢迎对我原来的问题发表评论)。

如果你阅读我之前的问题,请随意跳过这一段:我正在尝试构建一个提供类似音频播放按钮功能的控件。当应用程序处于“播放”模式时,应用程序应显示“Pause.png”图像。当它暂停时,它应该显示“Play.png”图像。还有两个附加图像来说明当用户悬停在控件上时的任一状态(例如,“Play_Hover.png”和“Pause_Hover.png”)。状态由我的视图模型中的IsPlaying属性决定。

我决定使用ToggleButton根据当前状态确定要显示的图像。请记住,当IsPlaying为false时,将显示播放按钮,如果为true,则会显示暂停按钮。因此,我提出了以下XAML。它的工作原理除了悬停:

<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>

你如何为两个州提供不同的悬停图像?如果我将一个悬停状态添加到CommonStates组,我将能够仅考虑其中一个状态(已选中或未选中)的悬停。

3 个答案:

答案 0 :(得分:4)

使用togglebutton时,不可能有不同的悬停/鼠标悬停状态,因为这对于按钮是常见的。 常见状态是正常(您最初看到的),鼠标悬停,按下和禁用

其他与检查,未检查或中间相关的状态。在这里,您可以为各种状态设置不同的图像等。鼠标悬停将始终回滚到公共状态。

如果你必须拥有这个功能,你可以为此创建自己的自定义控件,并根据活动状态处理鼠标悬停动画。这将需要后端更多代码,因为您需要重新定义此对象的按钮类并插入各种状态的测试以允许为每个状态播放设置动画。可以这样做我只是不知道是否值得付出那么多努力。

答案 1 :(得分:3)

你也可以在没有自定义控制的情况下实现这一点,如下所示:

        <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>

其中:

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

并定义 BooleanToVisibilityConverter ,如下所示:

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

希望它会有所帮助!

答案 2 :(得分:1)

我知道它已经很老了。但我刚遇到同样的问题,我以另一种方式修复。 (注意,解决方案适用于UWP,我没有在WPF或Silverlight上测试它)

  1. 创建一个新的模板化控件并基于Button
  2. MSDN复制切换按钮的默认样式和模板,然后将其粘贴到Themes / Generic.xaml
  3. 为事件添加以下eventHandler:PointerExited,PointerReleased,GotFocus,LostFocus,PointerEntered和LayoutUpdated。 eventHandler将调用 ChangeVisualState 方法
  4. 创建一个新方法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);
    }
    
  5. 最终代码:

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