问题
I'm developing a Silverlight application and I'm trying to stick to the MVVM principals, but I'm running into some problems changing the source of an image based on the state of a property in the ViewModel. For all intents and purposes, you can think of the functionality I'm implementing as a play/pause button for an audio app. When in the "Play" mode, IsActive is true in the ViewModel and the "Pause.png" image on the button should be displayed. When paused, IsActive is false in the ViewModel and "Play.png" is displayed on the button. Naturally, there are two additional images to handle when the mouse hovers over the button.
I thought I could use a Style Trigger, but apparently they're not supported in Silverlight. I've been reviewing a forum post with a question similar to mine where it's suggested to use the VisualStateManager. While this might help with changing the image for hover/normal states, the part missing (or I'm not understanding) is how this would work with a state set via the view model. The post seems to apply only to events rather than properties of the view model. Having said that, I also haven't successfully completed the normal/hover affects, either.
Below is my Silverlight 4 XAML. It should also probably be noted I'm working with MVVM Light.
<UserControl x:Class="Foo.Bar.MyUserControl"
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="Button">
<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="Button">
<Grid>
<Image Source="/Foo.Bar;component/Resources/Icons/Bar/Play.png">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Active">
<VisualState x:Name="MouseOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Source" Storyboard.TargetName="/Foo.Bar;component/Resources/Icons/Bar/Play_Hover.png" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Image>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Button Style="{StaticResource MyButtonStyle}" Command="{Binding ChangeStatus}" Height="30" Width="30" />
</Grid>
</UserControl>
What is the proper way to update images on buttons with the state determined by the view model?
回答1:
A simple way, is to bind an IsActive and IsNotActive boolean property on your VM to Visibility on two Image controls inside the Content of your Button.
You would have to use a BooleanToVisiblityConverter, of course.
Second thought: Could you bind IsActive to IsEnabled on your Button and make the Style show the proper image. Not sure if the limitation you mention in Silverlight might prevent this.
回答2:
We have some custom converters that change boolean (and other types) to specific images. That way we keep view / model as separate as possible.
Converters are easy to write, with lots of examples on the web.
So it winds up being something like this in the xaml:
<Image Source={Binding IsActive, Converter={StaticResource "boolToPlayImageConverter"}}/>
回答3:
Through the advice of a colleague and since I was already using MVVM Light, I was able to make use of EventToCommand to handle the mouse enter and mouse leave events in the view model, as opposed to relying on the built in VisualStateManager to handle those events. I also changed my Button to a ToggleButton. This allowed me to utilize the checked and unchecked states to handle whether to display the play or pause buttons. As the state was controlled by the view model, I was then able to determine which image to display by binding the Visibility attribute of the ToggleButton to a property on the view model that checked the state. My updated XAML looks like the following:
<UserControl x:Class="Foo.Bar.MyControl"
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"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL4"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="200">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<cmd:EventToCommand Command="{Binding MouseEnterCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<cmd:EventToCommand Command="{Binding MouseLeaveCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<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="{Binding PlayHoverVisible,FallbackValue=Collapsed}" />
<Image x:Name="PauseHover" Source="/Foo.Bar;component/Resources/Icons/Bar/Pause_Hover.png" Visibility="{Binding PauseHoverVisible,FallbackValue=Collapsed}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ToggleButton Style="{StaticResource MyButtonStyle}" IsChecked="{Binding IsPlaying}" Command="{Binding ChangeStatus}" Height="30" Width="30" />
</Grid>
</UserControl>
来源:https://stackoverflow.com/questions/5034189/silverlight-mvvm-confusion-updating-image-based-on-state