2011-02-17 69 views
1

我正在开发一个Silverlight应用程序,我试图坚持MVVM的原则,但我遇到了一些问题,改变图像的来源基于状态ViewModel中的一个属性。对于所有意图和目的,您可以将我作为音频应用的播放/暂停按钮实现的功能考虑在内。当处于“播放”模式时,ViewModel中的IsActive为真,并且应该显示按钮上的“Pause.png”图像。暂停时,ViewModel中的IsActive为false,按钮上显示“Play.png”。自然地,当鼠标悬停在按钮上时,还有两个额外的图像需要处理。Silverlight MVVM混乱:更新基于状态的图像

我以为我可以使用Style Trigger,但显然它们在Silverlight中不受支持。我一直在评论一个类似于我的建议使用VisualStateManager的问题的论坛帖子。虽然这可能有助于改变悬停/正常状态的图像,但缺失的部分(或我不理解)是如何通过视图模型设置状态。这篇文章似乎只适用于事件而不是视图模型的属性。话虽如此,我也没有成功完成正常/悬停的影响。

以下是我的Silverlight 4 XAML。也应该注意到我正在使用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>

根据视图模型确定的状态更新按钮上图像的正确方法是什么?

回答

1

通过同事的建议,因为我已经使用MVVM Light,我能够利用EventToCommand处理鼠标进入和鼠标离开事件在视图模型,而不是依靠内置的VisualStateManager来处理这些事件。我也将我的按钮更改为ToggleButton。这允许我利用选中状态和未选中状态来处理是否显示播放或暂停按钮。由于状态是由视图模型控制的,我可以通过将ToggleButton的Visibility属性绑定到检查状态的视图模型上的属性来确定要显示哪个图像。我更新的XAML如下所示:

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

一个简单的方法,就是将虚拟机上的IsActive和IsNotActive布尔属性绑定到Button内容中的两个Image控件上的Visibility。当然,你将不得不使用BooleanToVisiblityConverter。

第二个想法:您可以将IsActive绑定到您的Button上的IsEnabled,并使样式显示正确的图像。不确定您在Silverlight中提到的限制是否可能会阻止此问题。

1

我们有一些自定义转换器将布尔(和其他类型)更改为特定图像。这样我们尽可能地将视图/模型分开。

转换器很容易编写,在网上有很多例子。

所以卷起是像这样的XAML:

<Image Source={Binding IsActive, Converter={StaticResource "boolToPlayImageConverter"}}/> 
+0

非常感谢您的回复。我并不完全反对定制转换器,但我想要坚持纯XAML,如果可能的话。 – senfo 2011-02-18 15:43:07

+1

如果你使用MVVM,作为一个规则,我尝试只使用BooleanToVisiblityConverter(如果可能的话)(只是b/c映射布尔到可见性是如此频繁使用)。你的ViewModel应该能够公开你需要绑定的属性。将此逻辑放在ViewModels而不是转换器中通常会更简单一些,并且您可以显式控制INotifyPropertyChanged。 – jonathanpeppers 2011-02-18 17:27:23