C#のWPFでListViewの選択項目とテキストボックスをバインドする。

コンピュータ

いまいち理解できていないのですが、コードを書いてみたら思った通りに動作したので記事にしてみました。

ソースコード

ファイル名:FileEntity.cs

using System.ComponentModel;
using Reactive.Bindings;

namespace CollectionBinding3;
public class FileEntity : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged(string name) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    
    public ReactiveProperty<string> Name { get; set; } = new("");
    public ReactiveProperty<string> FullName { get; set; } = new("");
    public ReactiveProperty<string> Comment {get; set;} = new("");
 }

ファイル名:MainWindow.xaml

<Window x:Class="CollectionBinding3.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:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
        xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
        xmlns:local="clr-namespace:CollectionBinding3"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <interactivity:EventToReactiveCommand Command="{Binding WindowLoadedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <ListView
            x:Name="FilesListView"
            Grid.Column="0"
            IsSynchronizedWithCurrentItem="True" 
            ItemsSource="{Binding Files}"
            SelectedIndex="{Binding FilesListViewSelectedIndex.Value}">
            <ListView.Resources>
                <Style x:Key="listviewHeaderStyle" TargetType="GridViewColumnHeader">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                </Style>
            </ListView.Resources>
            <ListView.View>
                <GridView ColumnHeaderContainerStyle="{StaticResource listviewHeaderStyle}">
                    <GridViewColumn Width="200" Header="名前">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Width="Auto" TextAlignment="Left" Text="{Binding Name.Value}" />
                                </StackPanel>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Width="300" Header="コメント">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Width="Auto" TextAlignment="Left" Text="{Binding Comment.Value}" />
                                </StackPanel>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
        <WrapPanel
            Grid.Column="1">
            <Label>コメント</Label>
            <TextBox
                Width="200"
                Text="{Binding Files/Comment.Value}"
                />
        </WrapPanel>
    </Grid>
</Window>

ファイル名:MainWindowViewModel.cs

using System.ComponentModel;
using Reactive.Bindings;

using System.Diagnostics;
using System.IO;

namespace CollectionBinding3;

public class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged(string name) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    public ReactiveCommand<EventArgs> WindowLoadedCommand { get; }
    public ReactiveCollection<FileEntity> Files { get; set; } = [];
    public ReactiveProperty<int> FilesListViewSelectedIndex { get; set; } = new();
    public MainWindowViewModel()
    {
        WindowLoadedCommand = new ReactiveCommand<EventArgs>()
            .WithSubscribe(e =>
            {
                int i = 0;
                string targetDir = @"H:\csharp\dotnet8\wpf\CollectionBinding3";
                foreach(var fullname in Directory.EnumerateFileSystemEntries(targetDir))
                {
                    var comment = new ReactiveProperty<string>($"{i++}");
                    comment.Subscribe(e =>
                    {
                        int i = FilesListViewSelectedIndex.Value;
                        if (i < 0) return;
                        if (Files.Any() == false) return;
                        string fullname = Files[i].FullName.Value;
                        string comment = Files[i].Comment.Value;
                        if (e != comment) return;
                        Debug.Print($"{fullname}のコメント{comment}");
                    });
                    Files.AddOnScheduler(
                        new FileEntity()
                        {
                            Name = new(Path.GetFileName(fullname)),
                            FullName = new(fullname),
                            Comment = comment,
                        }
                    );
                }
            }
        );
        FilesListViewSelectedIndex
            .Subscribe(e=>
            {
                // リストビューの選択が変更されると実行される。
                int index = e;
                Debug.Print($"選択されたインデックス:{index}");
            }
        );
    }
}

動作

ListViewの項目を選択すると右側のテキストボックスのコメント欄が連動します。

テキストボックスのコメント欄を編集すると、ListViewのコメントも変更されます。

説明

ListViewのIsSynchronizedWithCurrentItemプロパティをtrueにすると現在選択されている項目とバインドすることが出来るとの情報を見つけたので、TextBoxとバインドしてみました。バインドする場合Files/Commentという感じにコレクション名/プロパティ名をセットしました。この辺りがよく理解できていないのですが、コレクションの要素を指定するインデックスが無いですが、そのあたりは現在の選択行のプロパティを指示していると思われます。

また、CommentをReactivePropertyしたのでSubscribeすることで変更をきっかけにコードを実行することが出来ました。

一応やりたいことは実現出来てはいるのですが、なぜこれで動作しているのか理解できていない状態です。

コメント