2016-07-30 4 views
1

私はWPFビューモデルを理解しようとしています。高度なMVVMロジック - 可能でしょうか?

私はアイテムのリストを持っているとしましょう。場合によっては、リストを作成し、選択する必要がある項目の値を指定する必要があります。

その他のケースでは、リストを再投入したいだけで、コントロールはに、前に選択したアイテムを選択してください。

さらに、アイテムが削除された後にリストを再作成する必要があります。この場合、前に選択したアイテムを選択することはできず、同じリストの位置(または最後のアイテムが削除された場合は1つ下のアイテム)でコードを選択するようにします。

通常、私は上記のようにリストを作成します。しかし、ItemsSourceSelectedItemを縛らなければならない場合、これは非常に扱いにくいようです。

このようなロジックをMVVMデザインに実装できますか?

+0

ViewModelの 'Items'プロパティのセッターに「選択」ロジックを挿入することができます。したがって、新しいリストが設定されるたびに、あなたの 'SelectedItem'が更新されます – Fabio

答えて

1

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を少し消化しやすくすることを願っています。ハッピーコーディング!

+0

ありがとうございます。あなたの答えをもっと詳しく調べる機会がありました。私にはたくさんのことがあり、いくつかのことがあります(AddやRemoveコマンドのクラスがある理由など)。私にとっては意味がありません。残念ながら、WPFの限界のために、私はMVVMを使用することを除いて、仕事を勝ち取るいくつかのコントロールを持っています。だから私は今、このすべてのことを学ぶべき位置にはいませんが、私は、この特定のプロジェクトを前進させるために、もっと理解する必要があります。 –

+0

@Jonathan Wood WPFは、最初に表示されるほど制限されていません。 WinFormsに精通していれば、XAMLは直感的ではないように見えるかもしれませんが、最初の学習曲線が終わった後に利点が現れ始めます。 ICommandの実装の理由は、UIレイヤーをアプリケーションロジックから切り離すためです。これはさまざまな理由で便利です。私はWinFormsに比べてWPFの実質的な制限を発見していない。私はWinFormsだけに適したいくつかの非常にニッチなタスクがあると確信していますが、一般にWPFは非常に柔軟なプラットフォームです。それはちょっと違う – Jace

2

このようなロジックをMVVMデザインに実装できますか?

確かに。
SelectedItemに設定されている値はItemsSourceにある必要があります。だから、あなたの商品は何らかの身元を持っている必要があります例えば、Webサービスからロードされた顧客のリスト、想像:RefreshCommandの並べ替えを実装するとき、あなたは再選択の顧客によいでしょう

public class Customer 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

を、それは以前に選択された:

private async void HandleRefreshAsync() 
{ 
    var selectedCustomerId = SelectedCustomer?.Id; 
    Customers = await customersService.GetCustomersAsync(); 
    SelectedCustomer = Customers.FirstOrDefault(_ => _.Id == selectedCustomerId); 
} 

// these are bound to SelectedItem/ItemsSource of Selector control 
public Customer SelectedCustomer { ... } 
public ObservableCollection<Customer> Customers { ... } 

// this is just relay/delegate command, which is handled by HandleRefreshMethod 
public RelayCommand RefreshCommand { ... } 

同様のアプローチが可能性あなたが言及した他の場合に使用されます。

+0

アイテムが削除された後も同じ選択位置を維持することに関して、選択されたアイテムが削除された場合、削除されたアイテムの位置を追跡します選択したアイテムを「得意先」のそのポジションのアイテムに設定することができますか? –

+0

正確に。一般的なアプローチは、コレクションを再ロードし、コレクションをリロードし、オプションで、保存された値を使用して 'SelectedItem'を復元する必要があるいくつかの値を思い出すことです。 – Dennis

1

ListBox.ItemsSourceにはINotifyCollectionChangedインターフェイスを使用することをおすすめします。特殊なコレクションを扱っている場合、おそらくこれを自分で実装する必要があります。しかし、前述のインターフェースを実装しているObservableCollection<T>を使用する(またはサブクラス化する)ことができます。どちらの方法でも、このインターフェイスを使用すると、基になるコレクションが更新されるときにListBoxが自動的に更新されます。

関連する問題