绑定到模型或视图模型

6

我使用了MVVM模式创建了一个项目(或者我认为是这样的;))。为了简化我的情况:

模型:

public class Model {
    public string Name { get; set; }
    public bool IsDefective { get; set; }
}

ViewModel是扩展了MvvmLight ViewModelBase的类:

public class ViewModel : ViewModelBase {
    private ObservableCollection<Model> models;
    public ObservableCollection<Model> Models {
        get {
            if (_models== null) {
                _models= new ObservableCollection<Models>();
            }

            return _models;
        }
        set {
            RaisePropertyChanged("Models");

            _models= value;
        }
    }
}

查看 - 我正在显示一个文本框列表:

<TextBlock Text="{Binding Name}">
    <TextBlock.Style>
        <Style TargetType="{x:Type TextBlock}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=.IsDefective}" Value="True">
                    <Setter Property="Foreground" Value="Red" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBlock.Style>
</TextBlock>

我的情景是这样的:模型类中的一些方法可能会更改IsDefective属性,但由于我的模型没有实现INotifyPropertyChanged接口,因此我的视图不知道这些更改。如何用“mvvm方式”解决这个问题?我在Stack Overflow上遇到了这个问题,但说实话,阅读了最高投票答案和评论讨论后,我更加困惑了:MVVM-Model或ViewModel中的PropertyChanged。我愿意同意Jonathan Allen的观点,因为以这种方式绑定更加自然,但作为mvvm模式的初学者,我可能错过了什么。那么,我错了吗?

你没有错过任何东西,Mvvm及其相关部分只是一些建议,它可以帮助你创建可维护、可测试和解耦的代码片段。你的模型实现INotifyPropertyChanged是完全合法的。在“CRUD”应用程序中非常流行。 - eran otzap
“Model”类中的某些方法可能会更改“IsDefective”属性。请问是哪个类调用了“Model”的这个方法(可能会更改“IsDefective”属性)? - Sergey Vyacheslavovich Brunov
2个回答

12
通常情况下,你希望你的模型只是一个简单的数据传输对象。当你执行数据库查询时,你会得到一个简单的模型返回,它不会进行任何转换,因为否则你就没有遵循SOLID原则中的关注点分离。然而,稍微作弊一下不会杀死你,但这可能会使调试某些事情变得有点沮丧,因为大多数人不会预期他们的POCO(普通旧CLR对象)启动任何业务逻辑。
以下是一些代码:
一些设置类:
ViewModelBase.cs
ViewModelBase是galasoft中一个“更聪明”的版本,这个坏小子自动连接设计时间视图模型(你会喜欢这个)。
namespace WPFPlayground.ViewModel
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
        {
            if (property != null)
            {
                if (property.Equals(value)) return;
            }

            OnPropertyChanged(propertyName);
            property = value;
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

DefectiveToBackgroundColorConverter.cs

一个值转换器,在视图上显示我们的产品时使用(稍后您将看到它被引用):

using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;

namespace WPFPlayground
{
    public class DefectiveToBackgroundColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (System.Convert.ToBoolean(value))
            {
                return new SolidColorBrush(Colors.Red);
            }
            return new SolidColorBrush(Colors.White);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }
}

使用模型优先方法:

ProductModel.cs

POCO数据传输对象
namespace WPFPlayground.Model
{
    public class ProductModel
    {
        public string Name { get; set; }
        public bool IsDefective { get; set; }
    }
}

ProductViewModel.cs

注意使用setvalue自动连接notifypropertychanged事件。

namespace WPFPlayground.ViewModel
{
    public class ProductViewModel : ViewModelBase
    {
        private string _name;
        private bool _isDefective;

        public bool IsDefective
        {
            get { return _isDefective; }
            set { SetValue(ref _isDefective, value); }
        }

        public string Name
        {
            get { return _name; }
            set { SetValue(ref _name, value); }
        }
    }
}

我们有一个ProductModel和一个ProductViewModel。一个在你与数据库交互时处理所有工作,一个在你绑定视图时处理所有工作。
所以我们需要一个仅代表单个ProductViewModel的视图:

ProductView.xaml

注意使用背景颜色转换器来处理触发器。
<UserControl x:Class="WPFPlayground.View.ProductView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:wpfPlayground="clr-namespace:WPFPlayground"
             mc:Ignorable="d" 
             d:DataContext="{d:DesignInstance wpfPlayground:DesignProductViewModel, IsDesignTimeCreatable=True}">
    <UserControl.Resources>
        <wpfPlayground:DefectiveToBackgroundColorConverter x:Key="DefectiveToBackgroundColorConverter" />
    </UserControl.Resources>
    <Viewbox>
        <Border Width="500" Background="{Binding IsDefective, Converter={StaticResource DefectiveToBackgroundColorConverter}}">
            <TextBlock Text="{Binding Name}" FontSize="40" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" />
        </Border>
    </Viewbox>
</UserControl>

接下来我们需要设计时的视图模型,以便在设计时查看我们的 XAML:

DesignProductViewModel.cs

虽然有点无聊,但可以使设计时工作正常运行!

using WPFPlayground.ViewModel;

namespace WPFPlayground
{
    public class DesignProductViewModel : ProductViewModel
    {
        public DesignProductViewModel()
        {
            Name = "This is my product";
            IsDefective = true;
        }
    }
}

现在我们需要显示这些视图模型的列表:

MainWindow.xaml

使用ItemsControl,每天都可以轻松完成。

<Window x:Class="WPFPlayground.MainWindow"
        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:viewModel="clr-namespace:WPFPlayground.ViewModel"
        xmlns:view="clr-namespace:WPFPlayground.View"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" d:DataContext="{d:DesignInstance viewModel:DesignProductsViewModel, IsDesignTimeCreatable=True}">
    <Window.Resources>
        <DataTemplate DataType="{x:Type viewModel:ProductViewModel}">
            <view:ProductView />
        </DataTemplate>
    </Window.Resources>
    <StackPanel>
        <ItemsControl ItemsSource="{Binding Products}">
            <view:ProductView />
        </ItemsControl>
    </StackPanel>
</Window>

DesignProductsViewModel.cs

这是设计时视图模型,您可以在设计时查看其工作原理。它会生成一组随机的产品,以便更轻松地进行设计。

using System;
using System.Collections.ObjectModel;
using System.Linq;

namespace WPFPlayground.ViewModel
{
    public class DesignProductsViewModel : ProductsViewModel
    {
        public DesignProductsViewModel()
        {
            var random = new Random();
            Products = new ObservableCollection<ProductViewModel>(Enumerable.Range(1, 5).Select(i => new ProductViewModel
            {
                Name = String.Format(@"Product {0}", i),
                IsDefective = (random.Next(1, 100) < 50)
            }));
        }
    }
}

1
人们经常用“多余”和“不方便”的说法来对待 TDD、SOLID 原则以及其他有益的实践。只因为它不太方便,就不应该被丢弃。 - C Bauer
那么在您的意见中 - 在这种特定情况下 - 我应该为我的视图创建一个额外的层来绑定模型,还是应该作弊?我编写了一些测试用例,它很容易 - 我没有遇到任何问题,但也许额外的层会使编写测试更加容易(再次强调 - 在这种特定情况下)? - Marek M.
2
这完全取决于你,我的朋友。在我的应用程序中,会有一个“模型”ViewModel,我会在那里执行我的notifypropertychanged属性。这就是MVVM的目的。如果您将此小的业务逻辑添加到模型中,如果出现新的业务需求会发生什么?我猜您也会在那里添加它。最终,期望成为DTO的模型正在执行一堆业务逻辑,而它只应该是DTO。在小项目中,这可能不会对您造成影响。 - C Bauer
那么在这种情况下,你能否给我一个示例代码,让我可以练习一下? - Marek M.
好的!我为你准备了一些东西,朋友。如果你想的话,我可以把它发布到GitHub上,这样看起来可能更容易。 - C Bauer
显示剩余2条评论

1
你没有错过任何东西,Mvvm及其相关部分是建议,可以帮助您创建可维护,可测试和解耦的代码片段。
当你遇到需要重复代码才能满足Mvvm的情况时,你可以“作弊”。
让你的模型实现INotifyPropertyChanged是完全合法的,在“CRUD”应用程序中非常流行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接