【问题标题】:Silverlight MVVM Confusion: Updating Image Based on StateSilverlight MVVM 困惑:根据状态更新图像
【发布时间】:2011-02-17 20:25:14
【问题描述】:

我正在开发一个 Silverlight 应用程序,并试图坚持使用 MVVM 原则,但是我遇到了一些问题,它根据 ViewModel 中的属性状态更改图像的源。出于所有意图和目的,您可以将我正在实现的功能视为音频应用程序的播放/暂停按钮。当处于“播放”模式时,ViewModel 中的 IsActive 为 true,应显示按钮上的“Pause.png”图像。暂停时,ViewModel 中的 IsActive 为 false,并且按钮上显示“Play.png”。当然,当鼠标悬停在按钮上时,还有两个额外的图像需要处理。

我以为我可以使用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>

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

【问题讨论】:

    标签: c# mvvm silverlight-4.0


    【解决方案1】:

    一种简单的方法是将 VM 上的 IsActive 和 IsNotActive 布尔属性绑定到 Button Content 内两个 Image 控件的 Visibility。

    当然,您必须使用 BooleanToVisiblityConverter。

    第二个想法:您能否将 IsActive 绑定到您的 Button 上的 IsEnabled 并让样式显示正确的图像。不确定您在 Silverlight 中提到的限制是否会阻止这种情况。

    【讨论】:

      【解决方案2】:

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

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

      所以它最终在 xaml 中是这样的:

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

      【讨论】:

      • 非常感谢您的回复。我并不完全反对自定义转换器,但如果可能的话,我想坚持使用纯 XAML。
      • 如果你使用 MVVM,作为一项规则,我尽量只使用 BooleanToVisiblityConverter (只是 b/c 将布尔值映射到可见性是如此频繁地使用)。您的 ViewModel 应该能够公开您需要绑定的属性。将此逻辑放在 ViewModel 中而不是转换器中通常也更简单一些,并且您可以显式控制 INotifyPropertyChanged。
      【解决方案3】:

      通过一位同事的建议,并且由于我已经在使用MVVM Light,我能够利用EventToCommand 来处理视图模型中的鼠标进入和鼠标离开事件,而不是依赖于内置的在 VisualStateManager 中处理这些事件。我还将我的 Button 更改为 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>
      

      【讨论】:

      • 只是好奇你还需要 Grid、LayoutRoot 吗? [不知道这是否是 MVVM Light 的东西,我主要使用 Caliburn]
      • @Jonathan 我需要 Grid 作为多个 Image 控件的容器。
      • @jonathanpeppers 我刚回到这篇文章,意识到我误解了你的问题。我不需要 LayoutRoot Grid。当我最初阅读这篇文章时,我以为您指的是模板中的网格。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-09
      • 2011-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-10
      相关资源
      最近更新 更多