none
WPFのDataGridにチェックボックスを入れた場合の仕様について RRS feed

  • 質問

  • いつもお世話になっております。arubi-momoと申します。

    開発環境:Windows10 VisualStudio2017 .net Framework4.6.2 WPF C#

    現在、WPFでの開発を行っており、その中でDataGridを使用しております。

    DataGridの中ではチェックボックスを使用しているのですが、DataGridにチェックボックスを入れた場合、おかしな動きが2点ほどありました。

    <DataGrid x:Name="Data" Grid.Column="2" Grid.Row="4" Grid.ColumnSpan="2" ItemsSource="{Binding Data}" AutoGenerateColumns="False" CanUserAddRows="True">
        <DataGrid.Columns>
            <DataGridTemplateColumn IsReadOnly="True" Header="check">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <CheckBox x:Name="CheckBox" IsChecked="{Binding IsCheck, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" Checked="CheckBox_Checked"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Binding="{Binding LargeClass}" Header="区分" MinWidth="50" Width="Auto" SortDirection="Descending">
                <DataGridTextColumn.ElementStyle>
                    <Style TargetType="TextBlock">
                        <Setter Property="VerticalAlignment" Value="Center" />
                    </Style>
                </DataGridTextColumn.ElementStyle>
            </DataGridTextColumn>
            <DataGridTextColumn Binding="{Binding Name}" Header="名称" MinWidth="433" Width="Auto" SortDirection="Descending">
                <DataGridTextColumn.ElementStyle>
                    <Style TargetType="TextBlock">
                        <Setter Property="VerticalAlignment" Value="Center" />
                    </Style>
                </DataGridTextColumn.ElementStyle>
            </DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>

    DataGridの「CanUserAddRows」プロパティをTrueにしているのですが、これをすると、Enterキーで次の行が自動で追加されるようになります。

    まず、1つ目は、新規行のチェックボックスにチェックを入れて、次の列(区分)に値を入力すると、先ほど入れたチェックが消えてしまいます。

    そして、2つ目は、新規行の区分と名称に値を入力して、そのあとチェックボックスにチェックを入れてEnterキーを押下すると、入力行のチェックボックスのチェックが消えて、次に追加された行にチェックが移ってしまいます。

    なぜこのような動作をするのでしょうか。チェックボックスをチェックすることによるイベントなどは特に作っておらず、DataGridのデフォルトの動きと思われます。WPFのバグなのかと思ったのですが、ご存知の方はいらっしゃいますでしょうか。

    また、仮にそうだとして、この動作を回避する方法などがありましたら、ご教示いただければ幸いです。

    よろしくお願いいたします。

    2019年8月22日 12:28

回答

  • 以下のコードで挙動を確認してください。

    <Window x:Class="WpfApp1.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:WpfApp1" mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="400" FontSize="20">
        <Grid>
            <DataGrid x:Name="Data" Grid.Column="2" Grid.Row="4" Grid.ColumnSpan="2" ItemsSource="{Binding Data}" AutoGenerateColumns="False" CanUserAddRows="True">
                <DataGrid.Columns>
                    <DataGridTemplateColumn IsReadOnly="true" Header="check">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox x:Name="CheckBox" IsChecked="{Binding IsCheck, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" 
                                          Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
    
                    <DataGridTextColumn Binding="{Binding LargeClass}" Header="区分" MinWidth="50" Width="Auto" SortDirection="Descending">
                        <DataGridTextColumn.ElementStyle>
                            <Style TargetType="TextBlock">
                                <Setter Property="VerticalAlignment" Value="Center" />
                            </Style>
                        </DataGridTextColumn.ElementStyle>
                    </DataGridTextColumn>
                    <DataGridTextColumn Binding="{Binding Name}" Header="名称" MinWidth="433" Width="Auto" SortDirection="Descending">
                        <DataGridTextColumn.ElementStyle>
                            <Style TargetType="TextBlock">
                                <Setter Property="VerticalAlignment" Value="Center" />
                            </Style>
                        </DataGridTextColumn.ElementStyle>
                    </DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>
    namespace WpfApp1
    {
        using System;
        using System.ComponentModel;
        using System.Windows;
        using System.Windows.Controls;
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                model = new Model();
                model.Data.Add(new Item() { LargeClass = "A", Name = "B" });
                model.Data.Add(new Item() { LargeClass = "X", Name = "Y" });
    
                this.DataContext = model;
    
                model.Data.CollectionChanged += Data_CollectionChanged;
            }
            private Model model;
    
            private void Data_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                //コレクションが変更されたら呼ばれる
                System.Diagnostics.Debug.WriteLine(DateTime.Now.ToLongTimeString() + "\t" + "CollectionChanged");
            }
    
            private void CheckBox_Checked(object sender, RoutedEventArgs e)
            {
                //CheckBoxがチェックされたら呼ばれる。
                CheckBox chk = (CheckBox)sender;
                System.Diagnostics.Debug.WriteLine(DateTime.Now.ToLongTimeString() + "\t" + "CheckBox.IsCheck=" + chk.IsChecked?.ToString());
                System.Diagnostics.Debug.WriteLine("\t" + chk.DataContext?.GetType().ToString() + "\t:" + chk.DataContext?.ToString());
            }
        }
        #region
    
        class Model
        {
            public System.Collections.ObjectModel.ObservableCollection<Item> Data { get; }
            = new System.Collections.ObjectModel.ObservableCollection<Item>();
    
        }
        class Item : ModelBase
        {
            public bool IsCheck
            {
                get { return _IsCheck; }
                set
                {
                    _IsCheck = value;
                    OnPropertyChanged(nameof(IsCheck));
                    System.Diagnostics.Debug.WriteLine(DateTime.Now.ToLongTimeString() + "\t" + "IsCheck=" + value.ToString());
                }
            }
            private bool _IsCheck;
    
            public string LargeClass { get { return _LargeClass; } set { _LargeClass = value; OnPropertyChanged(nameof(LargeClass)); } }
            private string _LargeClass;
    
            public string Name { get { return _Name; } set { _Name = value; OnPropertyChanged(nameof(Name)); } }
            private string _Name;
        }
    
        class ModelBase : System.ComponentModel.INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            }
        }
        #endregion
    }

    DataGrid新規行は、行の編集が開始されるまではDataContextには、新規行であることを表す仮のオブジェクトが設定されています。
    チェックボックスはセルの上に乗っかっているだけであり、チェックボックスを触っても行が編集状態になるわけではありません。
    ですから編集開始前にチェックボックスを変化させても、バインディング対象が違うので何も起こりません。

    他の列で編集が開始されると、ItemsSourceに設定されている要素の型のオブジェクトが作成されて、対応するDataGridRowも作られて、ようやくバインディングが発生します。(仮オブジェクトのDataGridRowが新しいDataGridRowに表示が置き換わる)
    バインディングが行われることで本来のオブジェクトのIsCheckプロパティが読み取られるので、本来のオブジェクトの値がfalseであればCheckBoxのIsCheckもfalseになり、結果的にチェックボックスのチェックが消えたように見えることになります。

    さらに仮オブジェクトが設定されているDataGridRowは作り直されずに使いまわされるため、編集開始前にCheckBoxがチェックされた状態で次の新規行に移ると、当然CheckBoxはそのままチェックされた状態になります。

    回避は簡単で、DataGridCheckBoxColumnを使いましょう。
    DataGridCheckBoxColumnを使いたくないならPreviewMouseDownを捕まえて行を編集状態にする小細工をしてください。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク arubi_momo 2019年9月3日 23:24
    2019年8月22日 16:21
  • arubi_momoさん、こんにちは。フォーラムオペレーターのHarukaです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    >まず、1つ目は、新規行のチェックボックスにチェックを入れて、次の列(区分)に値を入力すると、先ほど入れたチェックが消えてしまいます。
    そして、2つ目は、新規行の区分と名称に値を入力して、そのあとチェックボックスにチェックを入れてEnterキーを押下すると、入力行のチェックボックスのチェックが消えて、次に追加された行にチェックが移ってしまいます。
    →これは、datagird自体のデフォルトの動作のようです。 この問題を報告するか、Developer Communityで新しいリクエストを追加できます。
    また、Observable collection(CanUserAddRows = "False")インスタンスをグローバルインスタンスとしてディクレアできるため、
    上記のコードを追加するだけで空白行を追加できます。

    例えば:

    ObservableCollection<testb> list = new ObservableCollection<testb>();
      
      list.Add(new testb()); //blank row
    

    どうぞよろしくお願いします。


    MSDN/ TechNet Community Support Haruka

    ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、
    ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    • 回答としてマーク arubi_momo 2019年9月3日 23:24
    2019年9月3日 8:03
    モデレータ

すべての返信

  • 以下のコードで挙動を確認してください。

    <Window x:Class="WpfApp1.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:WpfApp1" mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="400" FontSize="20">
        <Grid>
            <DataGrid x:Name="Data" Grid.Column="2" Grid.Row="4" Grid.ColumnSpan="2" ItemsSource="{Binding Data}" AutoGenerateColumns="False" CanUserAddRows="True">
                <DataGrid.Columns>
                    <DataGridTemplateColumn IsReadOnly="true" Header="check">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox x:Name="CheckBox" IsChecked="{Binding IsCheck, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" 
                                          Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
    
                    <DataGridTextColumn Binding="{Binding LargeClass}" Header="区分" MinWidth="50" Width="Auto" SortDirection="Descending">
                        <DataGridTextColumn.ElementStyle>
                            <Style TargetType="TextBlock">
                                <Setter Property="VerticalAlignment" Value="Center" />
                            </Style>
                        </DataGridTextColumn.ElementStyle>
                    </DataGridTextColumn>
                    <DataGridTextColumn Binding="{Binding Name}" Header="名称" MinWidth="433" Width="Auto" SortDirection="Descending">
                        <DataGridTextColumn.ElementStyle>
                            <Style TargetType="TextBlock">
                                <Setter Property="VerticalAlignment" Value="Center" />
                            </Style>
                        </DataGridTextColumn.ElementStyle>
                    </DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>
    namespace WpfApp1
    {
        using System;
        using System.ComponentModel;
        using System.Windows;
        using System.Windows.Controls;
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                model = new Model();
                model.Data.Add(new Item() { LargeClass = "A", Name = "B" });
                model.Data.Add(new Item() { LargeClass = "X", Name = "Y" });
    
                this.DataContext = model;
    
                model.Data.CollectionChanged += Data_CollectionChanged;
            }
            private Model model;
    
            private void Data_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                //コレクションが変更されたら呼ばれる
                System.Diagnostics.Debug.WriteLine(DateTime.Now.ToLongTimeString() + "\t" + "CollectionChanged");
            }
    
            private void CheckBox_Checked(object sender, RoutedEventArgs e)
            {
                //CheckBoxがチェックされたら呼ばれる。
                CheckBox chk = (CheckBox)sender;
                System.Diagnostics.Debug.WriteLine(DateTime.Now.ToLongTimeString() + "\t" + "CheckBox.IsCheck=" + chk.IsChecked?.ToString());
                System.Diagnostics.Debug.WriteLine("\t" + chk.DataContext?.GetType().ToString() + "\t:" + chk.DataContext?.ToString());
            }
        }
        #region
    
        class Model
        {
            public System.Collections.ObjectModel.ObservableCollection<Item> Data { get; }
            = new System.Collections.ObjectModel.ObservableCollection<Item>();
    
        }
        class Item : ModelBase
        {
            public bool IsCheck
            {
                get { return _IsCheck; }
                set
                {
                    _IsCheck = value;
                    OnPropertyChanged(nameof(IsCheck));
                    System.Diagnostics.Debug.WriteLine(DateTime.Now.ToLongTimeString() + "\t" + "IsCheck=" + value.ToString());
                }
            }
            private bool _IsCheck;
    
            public string LargeClass { get { return _LargeClass; } set { _LargeClass = value; OnPropertyChanged(nameof(LargeClass)); } }
            private string _LargeClass;
    
            public string Name { get { return _Name; } set { _Name = value; OnPropertyChanged(nameof(Name)); } }
            private string _Name;
        }
    
        class ModelBase : System.ComponentModel.INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            }
        }
        #endregion
    }

    DataGrid新規行は、行の編集が開始されるまではDataContextには、新規行であることを表す仮のオブジェクトが設定されています。
    チェックボックスはセルの上に乗っかっているだけであり、チェックボックスを触っても行が編集状態になるわけではありません。
    ですから編集開始前にチェックボックスを変化させても、バインディング対象が違うので何も起こりません。

    他の列で編集が開始されると、ItemsSourceに設定されている要素の型のオブジェクトが作成されて、対応するDataGridRowも作られて、ようやくバインディングが発生します。(仮オブジェクトのDataGridRowが新しいDataGridRowに表示が置き換わる)
    バインディングが行われることで本来のオブジェクトのIsCheckプロパティが読み取られるので、本来のオブジェクトの値がfalseであればCheckBoxのIsCheckもfalseになり、結果的にチェックボックスのチェックが消えたように見えることになります。

    さらに仮オブジェクトが設定されているDataGridRowは作り直されずに使いまわされるため、編集開始前にCheckBoxがチェックされた状態で次の新規行に移ると、当然CheckBoxはそのままチェックされた状態になります。

    回避は簡単で、DataGridCheckBoxColumnを使いましょう。
    DataGridCheckBoxColumnを使いたくないならPreviewMouseDownを捕まえて行を編集状態にする小細工をしてください。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク arubi_momo 2019年9月3日 23:24
    2019年8月22日 16:21
  • arubi_momoさん、こんにちは。フォーラムオペレーターのHarukaです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    ご質問いただいた件ですが、その後いかがでしょうか。
    gekkaさんから寄せられた投稿はお役に立ちましたか。

    参考になった投稿には [回答としてマーク] をお願い致します。

    設定いただくことで、
    他のユーザーもお役に立つ回答を見つけやすくなります。

    お手数ですが、ご協力の程どうかよろしくお願いいたします。


    MSDN/ TechNet Community Support Haruka

    ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、
    ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    2019年8月26日 6:59
    モデレータ
  • gekkaさま

    いつもお世話になっております。arubi-momoです。

    ネット環境のない現場にしばらく出ておりまして、返信が遅くなり誠に申し訳ありません。

    仮オブジェクトとバインディングの関係について、とてもわかりやすくご教示いただきありがとうございます。

    さっそくいただいたソースで動作確認を行い、今発生している問題もまさにこの動作の通りでした。

    DataGridCheckBoxColumnも試してみましたが、「使いたくないなら」とおっしゃる意味もわかりました。DataGridCheckBoxColumnは、2度クリックしないとチェックがつかないのですね。一度目のクリックはセル選択として扱われてしまうことがわかりました。

    そうすると、PreviewMouseDownを使用するということになりますが、「編集状態にする」というのがわかりませんでした。dataGrid.BeginEditとしてみましたが、想定の動作にはなりませんでした。

    その行に対して編集状態にしないといけないのかと思いますが、もう少しヒントをいただけると助かります。

    勉強不足で申し訳ありませんが、よろしくお願いいたします。


    2019年8月28日 14:57
  • arubi_momoさん、こんにちは。フォーラムオペレーターのHarukaです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    >まず、1つ目は、新規行のチェックボックスにチェックを入れて、次の列(区分)に値を入力すると、先ほど入れたチェックが消えてしまいます。
    そして、2つ目は、新規行の区分と名称に値を入力して、そのあとチェックボックスにチェックを入れてEnterキーを押下すると、入力行のチェックボックスのチェックが消えて、次に追加された行にチェックが移ってしまいます。
    →これは、datagird自体のデフォルトの動作のようです。 この問題を報告するか、Developer Communityで新しいリクエストを追加できます。
    また、Observable collection(CanUserAddRows = "False")インスタンスをグローバルインスタンスとしてディクレアできるため、
    上記のコードを追加するだけで空白行を追加できます。

    例えば:

    ObservableCollection<testb> list = new ObservableCollection<testb>();
      
      list.Add(new testb()); //blank row
    

    どうぞよろしくお願いします。


    MSDN/ TechNet Community Support Haruka

    ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、
    ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    • 回答としてマーク arubi_momo 2019年9月3日 23:24
    2019年9月3日 8:03
    モデレータ