【问题标题】:MVVM Binding from Enum来自枚举的 MVVM 绑定
【发布时间】:2012-09-23 05:08:51
【问题描述】:

我正在处理我的第一个 WPF/MVVM 项目,我很难将我的大脑包裹在一些事情上。这个项目将是一个已经在 VB 中制作的东西的独立应用程序,所以所有的逻辑和业务规则都已经存在。

我遇到的问题是尝试实现绑定。我的应用程序上有几个按钮,它们的所有属性(IsEnabled、Text、image)都依赖于一个枚举。枚举是基于状态的,存在于Model中。

在 VB 应用程序中,我有一个巨大的 switch 语句来刷新按钮的属性,并且在状态可能发生变化的每个地方都会调用它。所以对于这个版本,我的 ViewModel 中的每个按钮都有一个 bool 和 string,基于状态,但没有办法在每次状态改变时强制它更新(这是相当频繁的)。

我已经阅读了一些关于 INotifyPropertyChanged 的​​内容,但是需要在我的 ViewModel 中启动更改的属性位于我的模型中。我是不是走错了路?

【问题讨论】:

  • 一定要使用 I(Multi)ValueConverter(s),不要用按钮的可见性/启用状态弄乱你的 ViewModel。

标签: wpf mvvm binding inotifypropertychanged


【解决方案1】:

我试图在 WPF 中处理枚举绑定,但我不太喜欢它。

您可以通过将属性映射到模型中的相应属性来解决此问题,在 get 方法中,您可以实现对枚举状态的依赖。

例如:

<Button  Height="41" HorizontalAlignment="Center" Style="{StaticResource ButtonStyle}"
                    Margin="407,77,289,0" Name="buttonSearch" VerticalAlignment="Top" Width="137" Click="search_click" 
                    IsEnabled="{Binding IsSearchButtonEnabled}" ...

假设你有这个枚举:

public enum States
{
    StateOne,
    StateTwo,
    StateThree
}

在视图模型中你可以这样做:

    public bool IsSearchButtonEnabled
    {
        get
        {
            return ((Model.actualState) == States.StateTwo);
        }
    }

要自动更新,您的 ViewModel 必须实现 INotifyPropertyChanged。我使用我的 ViewModels 总是子类化的通用实现来简化事情。它看起来像这样,并且应该在每次调用 InvokePropertyChanged(string propertyName) 时处理更新视图。 ViewModel 需要知道它应该更新视图,并且知道何时调用此方法。您可以在模型中使用相同的技术,并在模型中放置一个事件处理程序,通知订阅者虚拟机状态枚举的设置器中的更改。

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public static event PropertyChangedEventHandler PropertyChangedStatic;

    public void InvokePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    public static void InvokePropertyChangedStatic(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChangedStatic;
        if (handler != null) handler(null, new PropertyChangedEventArgs(propertyName));
    }
} 

【讨论】:

  • 你说“在模型中你可以这样做:public bool IsSearchButtonEnabled” 理想情况下,模型对 UI 或视图模型一无所知。我会在视图模型中实现一些代表搜索启用状态(可能是布尔依赖属性)的东西,然后当模型中的状态发生变化时,使用 OP 提到的大量 switch 语句。更好的是,在我看来是将状态对象存储在视图模型中,然后在它从 xaml 更改时使用触发器来设置按钮状态。
  • 啊,所以如果您缺少该模型对象,则需要在视图的 DataContext 属性中设置模型对象。此外,模型需要实现 INotifyPropertyChanged。
  • 第三,你可以编写一系列从状态到你需要的东西的转换器。比如,编写一个名为 MyEnumStateToButtonEnabled 的转换器,它采用枚举类型并返回一个布尔值。
  • 我真的很讨厌修改模型来支持任何与 UI 相关的东西。保留所有业务逻辑,并实现一个包含元素的 ViewModel。如果确实需要,您可以在模型上实现 INotifyPropertyChanged,但将所有 UI 内容(视图绑定的目标)保留在视图模型中,并在模型更改时做出反应并更新这些目标。
  • 好的,也许我听错了,我会改写我的答案。
【解决方案2】:

在 MVVM 中,您真的不应该直接从 ViewModel 引用 View 中的任何内容。

最好的解决方案是使用命令,例如 MVVM-Light RelayCommand,它具有基于枚举属性值的 CanExecute 操作。然后将按钮的命令属性绑定到 ViewModel 中的相应命令属性。这将自动设置按钮的启用状态。

然后在枚举属性的 set 方法中,为每个命令调用 CanExecuteChanged 事件。

【讨论】:

    【解决方案3】:

    您需要将每个ButtonIsEnabledTextImage 属性绑定到视图模型的Status 属性。然后,您将需要提供IValueConverter 的 3 个实现,它基本上将Status 枚举值转换为您正在寻找的适当的布尔、字符串或图像。对于每个 Button 控件,提供一个附加参数,转换器可以使用该参数与视图模型的 Status 属性进行比较。

    类似这样的:

    <myView.Resources>
        <local:statToBoolConverter x:Key="statToBoolConv" />
        <local:statToTextConverter x:Key="statToTextConv" />
        <local:statToImgConverter x:Key="statToImgConv" />
    </myView.Resources>
    
    // ..... further down in code ....
    
    <Button x:Key="aButton" 
        IsEnabled="{Binding Path=Status, Converter={StaticResource statToBoolConv}, 
            ConverterParamter=caseA}"
        Text="{Binding Path=Status, Converter={StaticResource statToTextConv}, 
            ConverterParamter=caseA}"
        Image="{Binding Path=Status, Converter={StaticResource statToImgConv},
            ConverterParamter=caseA}"/>
    
    <Button x:Key="aButton" 
        IsEnabled="{Binding Path=Status, Converter={StaticResource statToBoolConv}, 
            ConverterParamter=caseB}"
        Text="{Binding Path=Status, Converter={StaticResource statToTextConv}, 
            ConverterParamter=caseB}"
        Image="{Binding Path=Status, Converter={StaticResource statToImgConv},
            ConverterParamter=caseB}"/>
    

    我不打算介绍实现 IValueConverter 的具体细节,因为它非常简单,但您可以在此处获得更多信息(完整的示例):

    http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx

    还有一点需要注意:INotifyPropertyChanged 将触发所有按钮的绑定更新,因此只需调用一次 NotifyPropertyChanged("MyStatus") 即可。

    【讨论】:

    • 非常感谢@code4life!实现了一个 IMultiValueConverter,它完美地完成了工作:D
    【解决方案4】:

    我会在您的按钮样式中使用DataTrigger。每当更新绑定值时,都会重新评估 DataTrigger 并在必要时设置新值

    <Style x:Key="MyButtonStyle" TargetType="{x:Type Button}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding MyEnumProperty}" 
                         Value="{x:Static local:MyEnum.Value1}">
                <Setter Property="IsEnabled" Value="False" />
                <Setter Property="Content" Value="Value 1" />
            </DataTrigger>
            <DataTrigger Binding="{Binding MyEnumProperty}" 
                         Value="{x:Static local:MyEnum.Value2}">
                <Setter Property="IsEnabled" Value="True" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
    

    您可以对 Image 样式执行相同的操作 - 根据 Status 枚举的当前值在 DataTrigger 中设置 Image.Source 属性。

    请记住,您的MyEnumProperty 需要在更改时发出 PropertyChange 通知,以便 UI 知道值已更改并更新依赖该值的任何绑定。

    【讨论】:

    • {x:Static ... 位的 +1 - 这是我尝试将绑定值绑定到枚举值时缺少的键。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2016-02-06
    • 2011-02-06
    • 2015-08-20
    • 1970-01-01
    • 2016-03-26
    • 1970-01-01
    • 2015-03-29
    • 1970-01-01
    相关资源
    最近更新 更多