2

我在其他问题中得到了有关如何实现 MVVM 的提示。当学生类本身发生更改时(在我的项目中经常发生这种情况),我在将绑定更新传递给 GUI 时遇到了问题。有没有办法让这些事情变得简单,并以比实施更紧凑的方式实现?或者这是实现 MVVM 的最先进技术?

class MainWindowViewModel : INotifyPropertyChanged
{
   ObservableCollection<StudentViewModel> studentViewModels = new ObservableCollection<StudentViewModel>();

   public ObservableCollection<StudentViewModel> StudentViewModels
   {
      get { return studentViewModels; }
   }

   public MainWindowViewModel()
   {
      studentViewModels.Add(new StudentViewModel());
      studentViewModels.Add(new StudentViewModel());
      studentViewModels.Add(new StudentViewModel());
   }

   public event PropertyChangedEventHandler PropertyChanged;
   internal void OnPropertyChanged(String propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}


class StudentViewModel : INotifyPropertyChanged
{
   Student model;
   public String StudentFirstName
   {
      get { return model.StudentFirstName; }
      set { model.StudentFirstName = value; }
   }
   public String StudentLastName
   {
      get { return model.StudentLastName; }
      set { model.StudentLastName = value; }
   }

   public StudentViewModel()
   {
      model = new Student();
      this.model.PropertyChanged += (sender, e) => 
      {
         switch (e.PropertyName)
         {
            case "StudentFirstName": OnPropertyChanged("StudentFirstName"); break;
            case "StudentLastName": OnPropertyChanged("StudentLastName"); break;
            default: break;
         }
      };
   }

   public StudentViewModel(Student model)
   {
      this.model = model;

      this.model.PropertyChanged += (sender, e) =>
      {
         switch (e.PropertyName)
         {
            case "StudentFirstName": OnPropertyChanged("StudentFirstName"); break;
            case "StudentLastName": OnPropertyChanged("StudentLastName"); break;
            default: break;
         }
      ;
   }

   public void changeStudent()
   {
      model.changeStudent();
   }

   public event PropertyChangedEventHandler PropertyChanged;
   internal void OnPropertyChanged(String propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}



class Student : INotifyPropertyChanged
{
   public String studentFirstName;
   public String StudentFirstName
   {
      get { return studentFirstName; }
      set 
      {
         if (studentFirstName != value)
         {
            studentFirstName = value;
            OnPropertyChanged("StudentFirstName");
         }
      }
   }
   public String studentLastName;
   public String StudentLastName
   {
      get { return this.studentLastName; }
      set
      {
         if (studentLastName != value)
         {
            studentLastName = value;
            OnPropertyChanged("StudentLastName");
         }
      }
   }

   public Student() { }

   public void changeStudent()
   {
      StudentLastName = "McRonald";
   }

   public event PropertyChangedEventHandler PropertyChanged;
   internal void OnPropertyChanged(String propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}
4

4 回答 4

7

首先,我想推荐使用其中一个 MVVM 框架(我个人喜欢并使用Caliburn.Micro,但也有MVVM Light无数其他的)。

(从现在开始我将使用Caliburn.Micro实现作为示例,因为这是我或多或少知道的一个框架)

为什么?好吧,它为您提供了强类型NotifyOfPropertyChange()、内置事件聚合器、窗口管理器等等。这样你就不必每次都重新发明轮子。此外,Caliburn.Micro 的引导程序允许您烘焙您选择的 IoC 容器,如果您想采用 MVVM-without-framework 方式,这对于 WPF 来说并不是那么容易。作为奖励,您可以拦截来自 GUI 控件的事件,因此您实际上不必在代码隐藏中编写任何内容。

一些框架允许您按约定绑定并简化命令,但您必须阅读这一点,具体取决于您使用的框架。

第二件事,我强烈支持完全重写视图模型,以便它们是独立的类而不是数据模型的包装器。您可以稍后使用AutomapperValueInjecter进行映射。

那么你就有了视图模型,例如:

public class StudentViewModel : PropertyChangedBase
{
    private string firstName;
    public string FirstName
    {
        get { return firstName; }
        set
        {
            firstName = value;
            NotifyOfPropertyChange(() => FirstName);
        }       
    }

    private string lastName
    public string LastName
    {
        get { return lastName; }
        set
        {
            lastName = value;
            NotifyOfPropertyChange(() => LastName);
        }       
    }
}

这就是视图模型。然后在视图模型中使用数据绑定、验证等。

您的Student类可以是例如简单的 DTO 或 EF 类或其他。为简单起见,让我们使用愚蠢的 DTO:

public class Student
{
    public string FirstName { get;set; }
    public string LastName { get;set; }
}

因此,您仅在例如保存到数据库时才使用 DTO。就是这样。对于“常规”应用程序使用,您使用视图模型的 GUI 交互(绑定)。

这就是 Automapper/ValueInjecter 发挥作用的地方,因为当您想在任何地方“保存”更改/添加新学生时,您必须将视图模型映射到模型,例如:

//ValueInjecter
var dataModel = new Student().InjectFrom(this) as Student;
//AutoMapper
var dataModel = Mapper.Map<StudentViewModel, Student>(this);

就是这样。简单,轻松,干净。你描述它的方式,你想改变底层模型。我建议不要这样做,而是在可以通知您的 UI 的视图模型上进行操作。您仅使用模型来“修改”数据存储中的数据(保存/更新/获取/删除)或以某种方式“传输”数据(例如使用 REST Web 服务),并使用视图模型进行交互。

于 2012-11-26T10:12:07.127 回答
1

我同意其他答案,您应该看看 MVVM 框架。我在工作中使用MVVM Foundation

文字墙是跟随。不久前我也开始使用 MVVM。这段代码对我上一个项目有很大帮助。

最近,我不得不研究一个还需要 IEditableObject 来管理编辑/保存/丢弃对象以及 PropertyChanged 通知的类。既然你提到了一种“让事情变得简单”的方法,我将发布我用来将 MVVM 和可编辑对象绑定在一起的基类。实现所有其他类对我来说节省了大量时间。

EditableObject 继承自 ObservableObject,它是 MVVM Foundation 的一部分。它将您正在使用的对象的结构作为类型参数。

如果您熟悉IEditabeObject的实现,则存在 editData 和 backupData 变量,它们保存您当前正在使用的数据(我没有继承此数据,我创建了自己的 EditableObject)。基本上,我使用 AutoMapper 来创建我正在使用的数据的深层副本(备份),以便可以恢复它。还有其他方法可以做到这一点(查找序​​列化或值注入),但我已经在项目中有AutoMapper,因此不需要更多的 dll。

EditableObject 具有抽象方法 SaveObject 和 RemoveObject,您可以实现这些方法来处理数据库调用等,以删除和保存对象。使用 BeginEdit 和 DiscardChanges 和 SaveChanges 完成对象的编辑。

神奇的是 RaisePropertiesChanged 方法,它在类中的所有修饰属性上引发 PropertyChanged 方法。因此,每当您编辑对象并(假设)放弃更改时。UI 被刷新并恢复为原始值。它还包括您可以将 UI 绑定到的 IsEditEnabled 标志。最初我将 PropertyChanged 与一个空字符串一起使用,但这会在所有属性上引发它。使用属性,我确保它仅在我需要更新的属性上发生变化。

我使用您的学生类来实现这一点,并在下面附加了基类。

希望能帮助到你!

public class Student: EditableObject<WPF.MVVMBase.Student.StudentData>
    {
        #region Struct

        public struct StudentData
        {
            public string firstName;
            public string lastName;
        }

        #endregion

        #region Public Properties

        [ObservableProperty]
        public string FirstName
        {
            get
            {
                return _editData.firstName;
            }
            set
            {
                _editData.firstName = value;
                this.RaisePropertyChanged("FirstName");
            }
        }

        [ObservableProperty]
        public string LastName
        {
            get
            {
                return _editData.lastName;
            }
            set
            {
                _editData.lastName = value;
                this.RaisePropertyChanged("LastName");
            }
        }

        #endregion

        #region Methods

        protected override bool SaveObject()
        {
            //Save Student Changes to Database

            return true;
        }

        protected override bool RemoveObject()
        {
            //Remove Student from Database

            return true;
        }

        #endregion
    }

这是 EditableObject 类

namespace WPF.MVVMBase
{
    /// <summary>
    /// Property Decorator that marks the Property as Observable. This is used by the EditableObject class to determine for which properties to raise the Property Changed method
    /// </summary>
    public class ObservablePropertyAttribute : System.Attribute{};

    /// <summary>
    /// Extends the ObservableObject class. EditableObject implements methods which are used to edit the object as well as raise the Property Changed events.
    /// </summary>
    /// <typeparam name="T">The Struct for the Editable Object</typeparam>
    public abstract class EditableObject<T> : ObservableObject
    {
        #region Private Variables

        bool _IsEditEnabled = false;
        bool _IsSelected = false;

        protected T _editData;
        protected T _backupData;

        #endregion

        #region Public Properties

        /// <summary>
        /// Controls if the Edit is enabled on the Editable Object
        /// </summary>
        public bool IsEditEnabled
        {
            get
            {
                return _IsEditEnabled;
            }
            protected set
            {
                _IsEditEnabled = value;
                this.RaisePropertyChanged("IsEditEnabled");
            }
        }

        /// <summary>
        /// Determines weather the object is Selected. Used with Lists
        /// </summary>
        public bool IsSelected
        {
            get
            {
                return _IsSelected;
            }
            set
            {
                _IsSelected = value;
                this.RaisePropertyChanged("IsSelected");
            }
        }

        #endregion

        #region Constructor

        public EditableObject()
        {
            //Create an instance of the object that will hold the data.
            _editData = Activator.CreateInstance<T>();
        }

        #endregion

        #region Methods

        #region Abstract Methods

        /// <summary>
        /// Handle the object saving. This is called by the SaveChanges method.
        /// </summary>
        /// <returns>Indicates if the object was saved successfully</returns>
        protected abstract bool SaveObject();

        /// <summary>
        /// Handle the object remove. This is called by the Remove method.
        /// </summary>
        /// <returns>Indicates if the object was removed successfully</returns>
        protected abstract bool RemoveObject();

        #endregion

        /// <summary>
        /// Begin editing the object. Sets the IsEditEnabled to true and creates a backup of the Data for restoring.
        /// </summary>
        public void BeginEdit()
        {
            IsEditEnabled = true;
            _backupData = Mapper.DynamicMap<T>(_editData);
        }

        /// <summary>
        /// Discard any changes made to the object. Set the IsEditEnabled flag to false and restore the data from the Backup.
        /// </summary>
        public void DiscardChanges()
        {
            _editData = _backupData;
            IsEditEnabled = false;

            RaisePropertiesChanged(this);
        }

        /// <summary>
        /// Save the changes made to the object. Calls the SaveObject method. If save was successfull IsEditEnabled is set to false and backup data is set to current data.
        /// </summary>
        /// <returns>Indicates if the object was saved successfully</returns>
        public bool SaveChanges()
        {
            bool isSaveSuccessfull = SaveObject();

            if (isSaveSuccessfull == true)
            {
                _backupData = _editData;

                IsEditEnabled = false;

                RaisePropertiesChanged(this);
            }

            return isSaveSuccessfull;
        }

        public bool Remove()
        {
            bool isRemoveSuccessfull = RemoveObject();
            return isRemoveSuccessfull;
        }

        /// <summary>
        /// Raises ObservableObject Property Changed for all the decorated methods in the given object so that the interface can refresh accordingly.
        /// </summary>
        /// <param name="baseObject"></param>
        public void RaisePropertiesChanged(object baseObject)
        {
            PropertyInfo[] properties = baseObject.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                object[] attributes = property.GetCustomAttributes(true);

                bool isObservableProperty = (from attribute in attributes
                                             where attribute is ObservablePropertyAttribute
                                             select attribute).Count() > 0;

                if (isObservableProperty)
                {
                    RaisePropertyChanged(property.Name);
                }
            }
        }

        #endregion
    }
}
于 2012-11-28T20:11:32.720 回答
1

如果您在StudentViewModel课堂上创建一个学生属性怎么办?此外,ViewModelBase 类可以简化代码(或至少使其更短)。

class MainWindowViewModel : ViewModelBase
{
    public ObservableCollection<StudentViewModel> StudentViewModels { get; private set; }
    public MainWindowViewModel()
    {
        StudentViewModels = new ObservableCollection<StudentViewModel>();
    }
}

class StudentViewModel : ViewModelBase
{
    public Student Student { get; private set; }

    public StudentViewModel()
    {
        Student = new Student();
    }

    public StudentViewModel(Student model)
    {
        Student = model;
    }

    public void ChangeStudent()
    {
        Student.changeStudent();
    }
}

public class Student : ViewModelBase
{
    public String studentFirstName;
    public String StudentFirstName
    {
        get { return studentFirstName; }
        set
        {
            if (studentFirstName != value)
            {
                studentFirstName = value;
                OnPropertyChanged("StudentFirstName");
            }
        }
    }
    public String studentLastName;
    public String StudentLastName
    {
        get { return this.studentLastName; }
        set
        {
            if (studentLastName != value)
            {
                studentLastName = value;
                OnPropertyChanged("StudentLastName");
            }
        }
    }

    public Student() { }

    public void changeStudent()
    {
        StudentLastName = "McRonald";
    }
}

这是一个ViewModelBase实现INotifyPropertyChanged接口的类:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string propName)
    {
        var eh = PropertyChanged;
        if (eh != null)
        {
            eh(this, new PropertyChangedEventArgs(propName));
        }
    }
}

用于检测:

<Grid>
    <ListBox Name="lbStudents" ItemsSource="{Binding StudentViewModels}" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=Student.StudentLastName}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

和 MainWidnow 的构造函数

 public MainWindow()
    {
        InitializeComponent();

        var viewModel = new MainWindowViewModel();
        var student = new Student { StudentFirstName = "John", StudentLastName = "Doe" };
        viewModel.StudentViewModels.Add(new StudentViewModel(student));

        DataContext = viewModel;
        MouseLeftButtonDown += new MouseButtonEventHandler((object sender, MouseButtonEventArgs e) =>
        {
            viewModel.StudentViewModels[0].ChangeStudent();
        });
    }

如果单击窗口,将调用ChangeStudent第一个方法,StudentViewModel并且 UI 也将更新。

于 2012-11-26T10:18:00.400 回答
0

好吧,我将 INotifyPropertyChanged 代码放入了一个基类:

public abstract class PropertyChangedBase: INotifyPropertyChanged
{
    protected PropertyChangedBase()
    {
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
        PropertyChangedEventHandler changed = PropertyChanged;
        if (changed != null)
        {
            changed(this, propertyChangedEventArgs);
        }
    }
}

然后在每个需要属性更改事件的类中,将其添加为基类:

class Student : PropertyChangedBase
{
    public String StudentFirstName
    {
        get { return model.StudentFirstName; }
        set 
          { 
            model.StudentFirstName = value;
            this.OnPropertyChanged("StudentFirstName"); 
          }
    }

    public String StudentLastName
    {
       get { return model.StudentLastName; }
       set 
          { 
             model.StudentLastName = value;
             this.OnPropertyChanged("StudentLastName");
          }
    } 
}

在您的代码中我有点困惑的一件事是,为什么您有一个学生视图模型?在 MVVM 中,您有您的模型,即您的对象设计 - 在本例中为“学生”,然后您有一个视图,它将是 MainWindow.xaml,然后是您的 ViewModel,它是 MainWindowViewModel。所以真的你不应该需要 studentViewModel。所以你的 MainViewModel 应该是这样的:

class MainWindowViewModel : PropertyChangedBase
{
   ObservableCollection<Student> _Students = new ObservableCollection<Student>();

   public ObservableCollection<Student> Students
   {
      get { return _Students; }
   }

public MainWindowViewModel()
{
  _Students.Add(new Student() { StudentFirstName = "Foo", StudentLastName = "Bar" });
  _Students.Add(new Student() { StudentFirstName = "John", StudentLastName = "Doe" });
  _Students.Add(new Student() { StudentFirstName = "Emy", StudentLastName = "Bob" });
}
于 2012-11-26T10:09:48.150 回答