F#でWPF --- リストボックスCommand --- 複数選択

下記記事にてリストボックスで利用できるBehaviorを作成しました。
F#でWPF --- リストボックスCommand - 何でもプログラミング

今回はこれを拡張して、複数選択に対応させた実装をします。

ListBoxBehavior

単選択の時とほとんど同じです。

SelectedItemを利用していたのをSelectedItemsに変更しました。

SelectedItemsにsetterはないので、AddやClearでコレクションを変更します。

open System.Collections
open System.Windows.Input

type Item(name : obj) =
    override this.ToString() = name.ToString()

type ListBoxBehavior() =
    inherit BehaviorBase<ListBox>()

    static member val ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof<IEnumerable>, typeof<ListBoxBehavior>)
    member this.ItemsSource with get()              = this.GetValue(ListBoxBehavior.ItemsSourceProperty) :?> IEnumerable
                            and  set(x:IEnumerable) = this.SetValue(ListBoxBehavior.ItemsSourceProperty, x)

    static member val SelectedIndicesProperty = DependencyProperty.Register("SelectedIndices", typeof<int list>, typeof<ListBoxBehavior>)
    member this.SelectedIndices with get()           = this.GetValue(ListBoxBehavior.SelectedIndicesProperty) :?> int list
                                and  set(x:int list) = this.SetValue(ListBoxBehavior.SelectedIndicesProperty, x)

    static member val CommandProperty = DependencyProperty.Register("Command", typeof<ICommand>, typeof<ListBoxBehavior>)
    member this.Command with get()           = this.GetValue(ListBoxBehavior.CommandProperty) :?> ICommand
                        and  set(x:ICommand) = this.SetValue(ListBoxBehavior.CommandProperty, x)

    override this.OnAttached control =
        let mutable items = []
        let mutable isUpdatingFromVM = false
        let getSelectedIndices() =
            control.SelectedItems 
            |> Seq.cast<Item>
            |> Seq.choose (fun x -> List.tryFindIndex ((=) x) items)
            |> Seq.toList
        let setSelectedItems indices =
            control.SelectedItems.Clear()
            indices
            |> List.choose (fun x -> List.tryItem x items)
            |> List.iter (control.SelectedItems.Add >> ignore)
        [ DependencyProperty.changed<IEnumerable> ListBoxBehavior.ItemsSourceProperty this
          |> Observable.subscribe (fun x -> isUpdatingFromVM <- true
                                            let indices = getSelectedIndices()
                                            items <- x |> Seq.cast<obj> |> Seq.toList |> List.map Item
                                            control.ItemsSource <- items      
                                            setSelectedItems indices 
                                            isUpdatingFromVM <- false)          
          DependencyProperty.changed<int list> ListBoxBehavior.SelectedIndicesProperty this
          |> Observable.subscribe (fun x -> isUpdatingFromVM <- true
                                            setSelectedItems x
                                            isUpdatingFromVM <- false)         
          control.SelectionChanged.Subscribe(fun _ -> if (not isUpdatingFromVM) && (this.Command <> null) then
                                                        this.Command.Execute(getSelectedIndices())) ]


作成するアプリケーション

追加ボタンでリストが追加でき、削除ボタンで選択された複数のリストを削除できるアプリケーションです。
f:id:any-programming:20170218184305p:plain

アプリケーションコード

こちらも単選択の時とほとんど同じです。

SelectedIndexをintからint listに変更しました。

リストボックスで複数選択を有効にするには、SelectionModeを変更します。

Extended Ctrlクリック、Shiftクリックで複数選択
Multiple クリックで複数選択 or 解除

XAML

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        xmlns:local="clr-namespace:Behaviors;assembly=ListBoxBehavior"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="150" Width="200">
    <Grid>
        <ListBox Margin="10,10,0,10" HorizontalAlignment="Left" Width="80" SelectionMode="Extended">
            <i:Interaction.Behaviors>
                <local:ListBoxBehavior ItemsSource="{Binding Items}" SelectedIndices="{Binding SelectedIndices}" Command="{Binding SetSelectedIndices}" />
            </i:Interaction.Behaviors>
        </ListBox>
        <Button Command="{Binding AddItem}" Content="追加" HorizontalAlignment="Left" Margin="95,10,0,0" VerticalAlignment="Top" Width="75"/>
        <Button Command="{Binding RemoveSelectedItems}" Content="削除" HorizontalAlignment="Left" Margin="95,35,0,0" VerticalAlignment="Top" Width="75"/>
    </Grid>
</Window>

F#

open System
open System.Windows

type Msg =
    | AddItem
    | RemoveSelectedItems
    | SetSelectedIndices of int list

type Model = 
    { Items           : string list
      SelectedIndices : int list }

let initialModel = 
    { Items = []
      SelectedIndices = [] }

let updateModel model msg =
    match msg with
    | AddItem ->        
        { model with Items = "item" :: model.Items }

    | RemoveSelectedItems ->
        { model with Items = model.Items 
                             |> List.indexed 
                             |> List.filter (fun (i, _) -> List.contains i model.SelectedIndices |> not)
                             |> List.map snd
                     SelectedIndices = [] }

    | SetSelectedIndices x ->
        { model with SelectedIndices = x }

[<STAThread>]
[<EntryPoint>]
let main argv = 
    let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window
    window.DataContext <- DataContext(initialModel, updateModel, id)
    Application().Run(window) |> ignore       
    0