我在其他关于如何实现MVVM的问题中得到了提示。在Student类本身进行更改时(我在项目中发生了很多变化),我在将绑定更新传递给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));
}
}
}
答案 0 :(得分:7)
首先,我建议使用其中一个MVVM框架(我个人喜欢并使用Caliburn.Micro
,但还有MVVM Light
和myriad of others)。
(我将从现在开始使用Caliburn.Micro
实例作为示例,因为这是我或多或少知道的一个框架)
为什么呢?好吧,它为您提供强类型NotifyOfPropertyChange()
,内置事件聚合器,窗口管理器等等。这样你就不必每次都重新发明轮子。此外,Caliburn.Micro的引导程序允许您烘焙您选择的IoC容器,如果您想要使用MVVM而不使用框架方式,那么使用WPF并不是那么容易。
作为奖励,您可以拦截GUI控件中的事件,因此您实际上不必在代码隐藏中编写任何内容。
某些框架允许您按惯例进行绑定并简化命令,但是您必须阅读它,具体取决于您使用的框架。
其次,我非常支持完全重写视图模型,因此它们是独立的类而不是数据模型的包装器。您可以稍后使用Automapper或ValueInjecter进行映射。
那么你就有了视图模型,例如:
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服务),并使用视图模型进行交互。
答案 1 :(得分: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
的{{1}}方法,同时也会更新用户界面。
答案 2 :(得分:1)
我同意其他答案,你应该看看MVVM框架。我使用MVVM Foundation来完成我的工作。
文字墙将随之而来。我不久前也开始使用MVVM。在我的上一个项目中,这段代码对我很有帮助。
最近我不得不处理一个类,它还需要IEditableObject来管理编辑/保存/丢弃对象以及PropertyChanged通知。既然你提到了“轻松的事情”的方法,我将发布我曾经将MVVM和可编辑对象结合在一起的基础classe。对我来说,实施所有其他课程是一个很大的时间。
EditableObject继承自MVVM Foundation的ObservableObject。它将您正在使用的对象的结构作为类型参数。
如果您熟悉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
}
}
答案 3 :(得分: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");
}
}
}
我对你的代码感到困惑的一件事是,为什么你有一个studentview模型?在MVVM中,你有你的模型,你的对象设计 - 在这种情况下'学生'然后你有一个View,它将是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" });
}