2

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));
      }
   }
}
4

4 に答える 4

7

まず、MVVMフレームワークの1つを使用することをお勧めします(個人的には好きで使用Caliburn.Microしていますが、他にも無数にありますMVVM Light)。

(これからはCaliburn.Micro例として実装を使用します。これは、私が多かれ少なかれ知っている1つのフレームワークだからです)

なんで?まあ、それはあなたに強くタイプされた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);
        }       
    }
}

ビューモデルについては以上です。次に、ビューモデルでデータバインディング、検証などを使用します。

クラスは、たとえば、単純なDTOStudentまたは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

クラスでStudentプロパティを作成するとどうなります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 に答える
1

MVVMフレームワークを確認する必要があるという他の回答にも同意します。私は仕事にMVVMFoundationを使用しています。

テキストの壁が続きます。私も少し前にMVVMを始めました。このコードは、私の最後のプロジェクトで非常に役立ちました。

最近、オブジェクトの編集/保存/破棄とPropertyChanged通知を管理するためにIEditableObjectも必要とするクラスで作業する必要がありました。「物事を簡単にする」方法について言及しているので、MVVMと編集可能オブジェクトを結び付けるために使用した基本クラスを投稿します。他のすべてのクラスを実装することは、私にとって大きな時間の節約になりました。

EditableObjectは、MVVMFoundationの一部であるObservableObjectから継承します。使用しているオブジェクトの構造体をtypeparamとして使用します。

IEditabeObjectの実装に精通している場合は、現在作業しているデータを保持するeditData変数とbackupData変数があります(これを継承していないので、独自のEditableObjectを作成しました)。私はAutoMapperを使用して、基本的に、作業中のデータのディープコピー(バックアップ)を作成して、復元できるようにしています。これを行う方法は他にもあります(シリアル化または値の挿入を検索します)が、プロジェクトにはすでにAutoMapperが含まれているため、これ以上dllは必要ありません。

EditableObjectには、オブジェクトを削除および保存するためのデータベース呼び出しなどを処理するために実装する抽象メソッドSaveObjectおよびRemoveObjectがあります。オブジェクトの編集は、BeginEditとDiscardChangesおよびSaveChangesを使用して行われます。

魔法は、クラス内のすべての装飾されたプロパティでPropertyChangedメソッドを発生させるRaisePropertiesChangedメソッドで発生します。したがって、オブジェクトを編集して(たとえば)変更を破棄するときはいつでも。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 に答える
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");
          }
    } 
}

私があなたのコードで少し混乱していることの1つは、なぜあなたがstudentviewmodelを持っているのかということです。MVVMには、オブジェクトデザインであるモデルがあります。この場合は「Student」であり、次にMainWindow.xamlであるViewがあり、次にMainWindowViewModelであるViewModelがあります。したがって、実際には、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 に答える