WPF ListBox Multi-selection Binding (1)

  • 7255
  • 0
  • 2020-02-09

簡介 WPF MVVM 模式下取得 ListBox 多選結果的幾種方式。

資料繫結在 WPF 中是一個很慣用的技巧,大部分的時候都很直覺,但遇到 ListBox 在多選狀況下會有點小麻煩,原因在於 SelectedItems 沒有辦法直接繫結到 View Model 的屬性上,比方說你這麼寫:

  <ListBox HorizontalContentAlignment="Stretch" 
           ItemsSource="{Binding People}"            
           SelectionMode="Multiple"  
           SelectedItems="{Binding SelectedPeople}"
           x:Name="listbox">            

編譯過程就會出現『'SelectedItems' 屬性是唯讀的,而且無法從標記設定 ...』這樣的錯誤訊息。那要如何在 View Model 取得 SelectedItems?我們根據情境的不同發展一些解決方式。

1. 只取得多選後的結果,使用 Command Parameter 與 Element Binding

這個方式基本上是單向的,在這個情境下,主要是希望能夠在 View Model 中取得多選後的結果就可以了。解法很簡單,把 ListBox.SelectedItems 傳給 Command 的參數就行了。先了解一下 ListBox.SelectedItems 的型別是 System.Windows.Controls.SelectedItemCollection ,但很不幸地,這是一個 internal class,意味著我們沒法直接拿來用;不過這個型別的父型別是 ObservableCollection<object> ,所以我們已經知道該怎麼轉型了。

先說明 View Model,這個範例有兩個 View Model,第一個是 PersonalViewModel,代表著某個人的資訊;另一個是 MainViewModel,這個 View Model 會指派給 Window 的 DataContext ,其中內含一 ObservableCollection<PersonViewModel> People 屬性用於 ListBox 的 ItemsSource。

 public class PersonViewModel : NotifyPropertyBase
 {
     private int _age;
     public int Age
     {
         get { return _age; }

         set { SetProperty(ref _age, value); }
     }
     private string _name;
     public string Name
     {
         get { return _name; }
         set { SetProperty(ref _name, value); }
     }

     private string _city;
     public string City
     {
         get { return _city; }
         set { SetProperty(ref _city, value); }
     }    
 }
 public class MainViewModel : NotifyPropertyBase
 {
     public MainViewModel()
     {
         People = GetFakePeople();
     }

     private ObservableCollection<PersonViewModel> _people;
     public ObservableCollection<PersonViewModel> People
     {
         get { return _people; }
         set { SetProperty(ref _people, value); }
     }


     private ICommand _showCommand;
     public ICommand ShowCommand
     {
         get
         {
             _showCommand = _showCommand ?? new RelayCommand((x) =>
             {
                 var builder = new StringBuilder();
                 var selected = ((ObservableCollection<object>)x).Cast<PersonViewModel>();
                 foreach (var p in selected)
                 {
                     builder.AppendLine($"{p.Name} 是 {p.Age} 住在 {p.City}"); 
                 }

                 MessageBox.Show(builder.ToString());

             });
             return _showCommand;
         }
     }

     private static ObservableCollection<PersonViewModel> GetFakePeople()
     {

         var people = new ObservableCollection<PersonViewModel>()
         {
             new PersonViewModel {Name = "小叮噹", Age = 21, City = "台北"},
             new PersonViewModel {Name = "葉大雄", Age = 23, City = "台北"},
             new PersonViewModel {Name = "胖虎", Age = 22, City = "台中"},
             new PersonViewModel {Name = "阿福", Age = 21, City = "高雄"},
             new PersonViewModel {Name = "魯夫", Age = 17, City = "桃園"},
             new PersonViewModel {Name = "索隆", Age = 37, City = "桃園"},
             new PersonViewModel {Name = "香吉士", Age = 28, City = "台南"},
             new PersonViewModel {Name = "羅賓", Age = 25, City = "新北"},
             new PersonViewModel {Name = "喬巴", Age = 11, City = "新北"},
             new PersonViewModel {Name = "娜美", Age = 24, City = "高雄"},
         };
         return people;
     }
 }

MainViewModel 中的 ShowCommand 會被繫結到畫面上的 Button.Command,然後將 ListBox.SelectedItems 當成該 Command 的 Command Parameter,也就會成為 ICommand.Execute 方法的參數,就這麼簡單:

 <Button Grid.Row="1" Margin="12,6,12,64" Content="Show" 
         Command="{Binding ShowCommand}" 
         CommandParameter="{Binding SelectedItems,ElementName=listbox}"/>

完整的範例請參考 ListBoxBindingSamle001