MVVMをコレクションで使用する方法の簡単なデモンストレーションとして、このサンプルプロジェクトをお伝えしたいと思います。私はそれが役に立つと思う。
まず、INotifyPropertyChanged
にアクセスできるように、ObservableObject
クラスを定義します。これは非常に基本的な実装ですが、ほとんどの状況で非常にうまく動作します。
using System.ComponentModel;
namespace WpfApplication8
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
私たちは、ビューモデルでこれを実装するだけでなく、私たちがINotifyPropertyChanged
を利用したい任意のカスタムタイプになります。
このデモでは、Book
クラスを使用します。 Book
クラスは2つのフィールドを保持するだけで、同じファイルにICommand
の実装を含めて、後で簡単にバインドすることができます。ICommand
が1つのType
に特に適用されるときは、そのコードファイルをType
というようにグループ化して整理しておくと好きです。
using System;
using System.Windows.Input;
namespace WpfApplication8
{
public class Book : ObservableObject
{
private string _title = "";
public string Title
{
get
{
return _title;
}
set
{
_title = value;
RaisePropertyChanged("Title");
}
}
private string _author = "";
public string Author
{
get
{
return _author;
}
set
{
_author = value;
RaisePropertyChanged("Author");
}
}
}
public abstract class BookCommand : ICommand
{
public abstract void Execute(object parameter);
public virtual bool CanExecute(object parameter)
{
return parameter is Book;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
次は、私たちは私たちに、オブジェクトが追加または削除されたときに何が起こるかを完全に制御できますObservableCollection
実装が必要になります。また、本を削除するためのオブジェクトをICommand
に追加します。
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace WpfApplication8
{
public class BookCollection : ObservableCollection<Book>
{
private ObservableObject parent;
private string name = "";
public BookCollection(ObservableObject parent, string name)
{
this.parent = parent;
AddBook = new CMD_AddBookToCollection(this);
RemoveBook = new CMD_RemoveBookFromCollection(this);
CollectionChanged += (s, e) =>
{
if(e.Action == NotifyCollectionChangedAction.Add)
{
// what will i do when a new item is added to the collection?
}
else if(e.Action == NotifyCollectionChangedAction.Remove)
{
// what will i do when an item is removed from the collection?
if(Items.Count > 0)
{
if(Items.Count < _lastSelectedIndex)
{
SelectedItem = Items[_lastSelectedIndex];
}
else
{
SelectedItem = Items[Items.Count - 1];
}
}
}
};
}
private int _lastSelectedIndex = -1;
private Book _selectedItem = null;
public Book SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
_lastSelectedIndex = Items.IndexOf(_selectedItem);
parent.RaisePropertyChanged(name);
}
}
public ICommand AddBook { get; set; }
public ICommand RemoveBook { get; set; }
}
public abstract class BookCollectionCommand : BookCommand
{
public BookCollection Collection { get; set; }
public BookCollectionCommand(BookCollection collection)
{
this.Collection = collection;
}
}
public class CMD_RemoveBookFromCollection : BookCollectionCommand
{
public CMD_RemoveBookFromCollection(BookCollection collection) : base(collection) { }
public override void Execute(object parameter)
{
base.Collection.Remove((Book)parameter);
}
}
public class CMD_AddBookToCollection : BookCollectionCommand
{
public CMD_AddBookToCollection(BookCollection collection) : base(collection) { }
public override void Execute(object parameter)
{
Book template = parameter as Book;
Book book = new Book()
{
Author = template.Author,
Title = template.Title
};
base.Collection.Add(book);
template.Author = "";
template.Title = "";
}
}
}
これは最初は圧倒的に思えるかもしれませんが、段階的に分解するとそれほど悪くはありません。 CollectionChanged
イベントは、アイテムがコレクションに追加または削除されるたびにカスタム機能を追加する機能を提供します。これにより、例えば、何かが削除された後で、リストから新しい項目を選択することが非常に容易になります。具体的なICommand
の実装の基礎として、BookCollectionCommand
抽象クラスも作成しました。この理由は純粋に冗長性を減らすことです。これはプロジェクトによっては理想的ではないかもしれませんが、ここではかなりうまく機能します。
次は、ビューに直接バインドされるルートビューモデルです。
namespace WpfApplication8
{
public class ViewModel : ObservableObject
{
public ViewModel()
{
MyBooks = new BookCollection(this, "MyBooks");
}
public BookCollection MyBooks { get; set; }
public Book NewBookTemplate { get; set; } = new Book();
}
}
NewBookTemplate
に注意してください。私はなぜCMD_AddBookToCollection
のパラメータをコレクションに直接プッシュしなかったのか疑問に思ったかもしれません。このパターンのパラメータはテンプレートのみであり、ビューから入力をビューモデルに渡すために使用されるスローアウェイオブジェクトです。これは、私たちに2つの非常に重要な自由を与えます。オブジェクトをコレクションに渡す前にオブジェクトをきれいにすることができ、「新しいブックを追加する」フォームのために特別なビューモデルを書くことは避けることができます。 Book
クラス自体はビューモデルとして機能し、私たちの仕事を節約し、他のビューでそのオブジェクトを再利用する自由を与えます。基本的なビューを実装し、これをすべてテストするだけです。
<Window x:Class="WpfApplication8.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:local="clr-namespace:WpfApplication8"
x:Name="main_window"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<StackPanel.Resources>
<Style TargetType="FrameworkElement" x:Key="default_styles">
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="30" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="5" />
</Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource default_styles}" >
<Setter Property="Width" Value="50" />
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource default_styles}" />
<Style TargetType="Button" BasedOn="{StaticResource default_styles}" />
</StackPanel.Resources>
<TextBlock Text="Title: " />
<TextBox Text="{Binding NewBookTemplate.Title, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Author: " />
<TextBox Text="{Binding NewBookTemplate.Author, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Add Book " Command="{Binding MyBooks.AddBook}" CommandParameter="{Binding NewBookTemplate}" />
<Button Content="Remove Book " Command="{Binding MyBooks.RemoveBook}" CommandParameter="{Binding MyBooks.SelectedItem}" />
<TextBlock Text="Count: " />
<TextBlock Text="{Binding MyBooks.Count}" />
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding MyBooks}" SelectedItem="{Binding MyBooks.SelectedItem, UpdateSourceTrigger=PropertyChanged}" >
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding MyBooks.RemoveBook}" CommandParameter="{Binding MyBooks.SelectedItem}" />
</ListBox.InputBindings>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding Author}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
まあ、すべて私の最後に動作します。この例では、MVVM
を少し消化しやすくすることを願っています。ハッピーコーディング!
ViewModelの 'Items'プロパティのセッターに「選択」ロジックを挿入することができます。したがって、新しいリストが設定されるたびに、あなたの 'SelectedItem'が更新されます – Fabio