locked
Datagridのtemplatecolumnで設定したcheckboxの値を取得したい。 RRS feed

  • 質問

  • 現在、silverlightを開発中です。 プログラミングも素人ながら色々調べてやっていますが、解決出来ない問題があります。 だれか、解決の糸口でも頂ければと思います。

    開発環境はSilverlight4とVisualStudio2010です。

    シナリオ: Datagridでcheckboxにチェックがついた行のみ削除する。

    <XAML>

    <sdk:DataGridTemplateColumn Width="80" >
    	<sdk:DataGridTemplateColumn.CellTemplate>
         		<DataTemplate>
              	<CheckBox x:Name="chkData" Click="chkData_Click" VerticalAlignment="Center" IsChecked="False" HorizontalAlignment="Center" HorizontalContentAlignment="Center" DataContext="{Binding checkData, Mode=TwoWay}"/>
             </DataTemplate>
         </sdk:DataGridTemplateColumn.CellTemplate>
     </sdk:DataGridTemplateColumn>

    このcheckboxは最初IsChecked = falseで、DataGrid上で後からチェックを付けます。

    <ビハインドコード>

     public partial class MainPage 
        {
          ObservableCollection<Data> source;
          ObservableCollection<Data> MyList = new ObservableCollection<Data>();
          
          public MainPage()
          {
            InitializeComponent();
            //コレクションの初期化
          source = new ObservableCollection<Data>();
            MyDataGrid.ItemsSource = source;
          }
    private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
    {
    	var data = new Data()
    	{
         		checkData = false,
         };
         source.Add(data);
    }
    

    DataGrid上でcheckboxにチェックをして、ボタンクリックでチェックがついた行を削除します。

    if (MyDataGrid.ItemsSource != null)
            {
                foreach (var item in source)
                {
                  CheckBox chkData = sender as CheckBox;
                  bool check = checkData.IsChecked.Value;
                  if (check == true)
                  {
                    source.Remove(MyDataGrid.SelectedItem as Data);
                  }
                }
                MyDataGrid.ItemsSource = source;
                MyList.Clear();
              //}
            }
            else if (MyDataGrid.ItemsSource == null)
            {
              MessageBox.Show("データグリッドに値がありません。");
            }

    <エンティティクラス>

     

    public class Data :INotifyPropertyChanged
      {
        public bool checkData { get; set; }
    
        #region INotifyPropertyChanged メンバー
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string Name)
        {
          if(PropertyChanged != null)
          {
            PropertyChanged(this, new PropertyChangedEventArgs(Name));
          }
        }
    
        #endregion
      }

    この様な環境の中でデバッグを取ると、ビハインドコードの「 bool check = checkData.IsChecked.Value;」の部分でエラーが発生します。

    エラーはNullReferenceExceptionです。

    つまり、DataGridの中のCheckBoxの値が正しく取得出来て無いんだろうな~と予想しています。

    Bindの仕方が悪いのでしょうか? 

    checkBoxの値自体を直接指定しようとしても、指定さえうまくいきません…

    ご指南をお願いします。

     


    03188
    2010年12月21日 4:57

回答

  • DataGrid にバインドしている Data クラスですが、次のように checkData プロパティのセッターで OnPropertyChanged を呼び出すように修正した方がいいです。

    public class Data :INotifyPropertyChanged
    {
      private bool _checkData;
    
      public bool checkData
      {
        get { return _checkData; }
        set
        {
          if (_checkData != value)
          {
            _checkData = value;
            OnPropertyChanged("checkData");
          }
        }
      }
    
      #region INotifyPropertyChanged メンバー
    
      public event PropertyChangedEventHandler PropertyChanged;
      
      protected void OnPropertyChanged(string Name)
      {
        if(PropertyChanged != null)
        {
          PropertyChanged(this, new PropertyChangedEventArgs(Name));
        }
      }
    
      #endregion
    }
    
    

    なかむら(http://d.hatena.ne.jp/griefworker)
    • 回答としてマーク 03188 2010年12月24日 0:36
    2010年12月22日 2:19

すべての返信

  • 例外が発生する箇所の1つ前に

    CheckBox chkData = sender as CheckBox;
    
    

    という部分がありますが、この sender を CheckBox 型に変換できてないのが例外の原因です。as 演算子を使った型変換に失敗した場合、chkData 変数には null が格納されます。

    「Button をクリックしたときに削除」と書かれていますので、sender にはおそらくクリックした Button が入っていると思われます。sender を使っても、チェックされた CheckBox を取得できません。

    DataGrid の ItemsSource にはコレクションをバインドしていて、DataGridTemplateColumn 内の CheckBox にデータを双方向バインドしているので、ItemsSource にバインドしたコレクションを調べるとチェックされたデータを取得できると思います。

    // DataGrid にバインドしたコレクションを取り出す。
    ObservableCollection<Data> collection = (ObservableCollection<Data>)MyDataGrid.ItemsSource;
    
    // CheckBox でチェックされたデータを取得
    List<Data> checkedDataList = collection.Where(d => d.checkData).ToList();
    
    // チェックされたデータを削除
    foreach (Data d in checkedDataList)
    {
     collection.Remove(d);
    }
    
    

     


    なかむら(http://d.hatena.ne.jp/griefworker)
    • 編集済み なかむら 2010年12月21日 8:20 for ではなく foreach
    2010年12月21日 5:55
  • 回答を頂き、ありがとうございます。

    ItemSourceに入っているcollectionから取得する方法は思いつきませんでした。

    ありがとうございます。

    またまた、初歩の質問で恐縮ですが続けて、質問させて下さい。

    List<Data> checkedDataList = collection.Where(d => d.checkData).ToList();

    上記の部分ですが、collectionの中のデータの中から、チェックの入った値のみを取得し、新しくリスト作成しているかと思います。

    デバックを取ってみると、コレクションを取りだした時にはcollectionにデータが入っていますが(count = *)、checkedDataListにはデータがnull表示されています。BinarySarch辺りが怪しいんじゃないか…とmsdnを読んで考えています。

    また、実際のビハインドコードにデータを削除するメソッドを入れるとエラーが起き、in と checkedDataListに”;”が要求されます。d => d.CheckDataの指定辺りで何か変更が必要なのかと考えています。こちらは、今の所何が悪いのか全く分からない状態です。

    この2点に関して、さらにご説明頂けないでしょうか?


    03188
    2010年12月21日 7:50
  • すみません。ループのところは for では無く foreach でした。

    >「デバックを取ってみると、コレクションを取りだした時にはcollectionにデータが入っていますが(count = *)、checkedDataListにはデータがnull表示されています。BinarySarch辺りが怪しいんじゃないか…とmsdnを読んで考えています。

    「null 表示」というのは、checkedDataList 変数が null になっている、ということでしょうか?空の List<Data> ではなくて。

     

     


    なかむら(http://d.hatena.ne.jp/griefworker)
    2010年12月21日 8:23
  • ありがとうございます。

    foreachで(Data d in checkedDataList)の条件文が通りました!

    一応、if(checkedDataList != null)  
                            {
                                checkedDataList.Remove();
                            }

    という方法も自分なりに考えましたが、成功には至りませんでした。

    >「null 表示」というのは、checkedDataList 変数が null になっている、ということでしょうか?空の List<Data> ではなく>て。

    説明不足ですみませんでした。

    詳しくしらべてみるとd.chkDatasでchkDataの値がfalseになっているのが原因の様です。ちなみに、このDataGridにはCheckBoxの他にDataGridTextColumnをいくつか設定していますが、それらの値は正しく取得しています。という事で、もう少し、Binding辺りの状態を調べてみようかと思います。


    03188
    2010年12月22日 2:00
  • DataGrid にバインドしている Data クラスですが、次のように checkData プロパティのセッターで OnPropertyChanged を呼び出すように修正した方がいいです。

    public class Data :INotifyPropertyChanged
    {
      private bool _checkData;
    
      public bool checkData
      {
        get { return _checkData; }
        set
        {
          if (_checkData != value)
          {
            _checkData = value;
            OnPropertyChanged("checkData");
          }
        }
      }
    
      #region INotifyPropertyChanged メンバー
    
      public event PropertyChangedEventHandler PropertyChanged;
      
      protected void OnPropertyChanged(string Name)
      {
        if(PropertyChanged != null)
        {
          PropertyChanged(this, new PropertyChangedEventArgs(Name));
        }
      }
    
      #endregion
    }
    
    

    なかむら(http://d.hatena.ne.jp/griefworker)
    • 回答としてマーク 03188 2010年12月24日 0:36
    2010年12月22日 2:19
  • SilverLightでは、変数等に変更がある時は基本的にINotifyPropertyChangedを実装すると考えた方が良さそうですね。

    上記に示して頂いたコードも試しましたが、結局d.CheckDataの部分ではfalseのままですね・・・

    <ビハインドコード>

    public void chkData_Click(object sender, RoutedEventArgs e)
          {
            CheckBox chkData = sender as CheckBox;
            bool checkData = chkData.IsChecked.Value;
          }

    ビハインドコードに上記のコードを足してみました。(チェックボックスをクリックした時のIsCheckedプロパティの値を調べる為に)chkData.IsChecked.Valueでは値たTrueになっています。

    <XAML> 

    <sdk:DataGridTemplateColumn Width="80" >
                  <sdk:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                      <CheckBox x:Name="chkData" Click="chkData_Click" VerticalAlignment="Center" IsChecked="False" HorizontalAlignment="Center" HorizontalContentAlignment="Center" DataContext="{Binding chkData, Mode=TwoWay}"/>
                    </DataTemplate>
                  </sdk:DataGridTemplateColumn.CellTemplate>
                </sdk:DataGridTemplateColumn>

    <エンティティクラス>

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel;
    
    namespace MainPage_ 
    {
      public class Data :INotifyPropertyChanged
      {
    
        private bool _chkData { get; set; }
        public bool chkData
        {
          get{return this._chkData;}
          set
          {
            if(_chkData != value)
            {
              _chkData = value;
              OnPropertyChanged("chkData");
            }
          }
        }
    
        #region INotifyPropertyChanged メンバー
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string Name)
        {
          if (PropertyChanged != null)
          {
            PropertyChanged(this, new PropertyChangedEventArgs(Name));
          }
        }
    
        #endregion
      }
    }
    

    こんなコード類を用いています。 XAMLでDataContextはTwoWayにしてますし、INotifyPropertyChangedも実装しました。まだ、DataBinding辺りで何か方法は無いのか…探しています。

    例えば、bool checkData = chkData.IsChecked.Value;では無事値を取得している様なので、この値を持ってくる方向でコードを考えています。

    エンティティクラスでcheckDataをget,setした後、削除ボタンで削除する際のにこの値を使用してWhere文を作る等…まだ、エラーが発生しないコードが出来て無いのでコードをアップロードできませんが、一応完成したら載せようと思います。 


    03188
    2010年12月22日 6:27
  •  なんとか、チェックボックスをチェックした際に、ボタンクリックでDataGridの行を削除出来る様になりました!

    チェックボックスのクリック時のイベントを少し変更しました。

    public void chkData_Click(object sender, RoutedEventArgs e)
          {
            CheckBox chd = sender as CheckBox;
            bool chD = chd.IsChecked.Value;
    
            if (this.Dg.SelectedItem != null)
            {
              Data item = (Data)Dg.SelectedItem;
              item.chd = chd.IsChecked.Value;
            }

    これで、Observablecollectionに入れた値が更新されて、IsChecked = trueとなりました。

    ここで、一つ疑問なのが、ObservableDataCollectionとINotifyPropertyChangedを使っていますが、この二つだけではプロパティの変更時に、データの内容が変更されていなかった点です。

    まだ、完全にこの二つを理解出来た訳では無いので当然の動きかもしれませんが・・・

    もっと精進して、理解を深めるようにします!!

    的を得てない質問ばかりで申し訳なかったですが、丁寧な回答をいただいた なかむらさん ありがとうございました。


    03188
    2010年12月22日 12:12
  •  せっかくバインドがあるのでバインドでチェックされたことをDataに反映させればよいと思いますよ。実現されたいことはそういうことですよね??

    <CheckBox x:Name="chkData" Click="chkData_Click" VerticalAlignment="Center"
      IsChecked="{Binding chkData, Mode=TwoWay}"
      HorizontalAlignment="Center" HorizontalContentAlignment="Center"} />
    
    

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年12月23日 0:53
    モデレータ
  • 回答ありがとうございます。

    まさに!、実現したかった事はその通りです。しかし、IsCheckedにバインドした所、私の削除ボタンの処理コードではIsCheckedの値がTre/Falseと変化したのみでした。

    今回は、取りあえず実現したい動きを作る事が出来ている事と、作業時間が限られているので最後まで教えて頂いた方法を検証する事が出来ません・・・ せっかく教えて頂いたのにすみません。

    ただ、今後もSilverlightとはお付き合いして行く予定(!?)なので、今後の参考にさせて頂きます。ありがとうございました。


    03188
    2010年12月24日 0:57
  • 一応、テストしてみましたのでコードを載せておきます。大したコードではありませんが、時間ができた時にでも眺めてみて下さい。
    # .NET的にはプロパティ名は本当は大文字始まり(パスカル形式)が良いです。

    <UserControl x:Class="Silverlight2010.DataGridCheckBox"
     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"
     mc:Ignorable="d"
     d:DesignHeight="420" d:DesignWidth="513" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
     
     <Grid x:Name="LayoutRoot" Background="White" Height="418" Width="511">
      
      <sdk:DataGrid AutoGenerateColumns="False" Height="321" HorizontalAlignment="Left" Margin="21,40,0,0" Name="MyDataGrid" VerticalAlignment="Top" Width="460">
       
       <sdk:DataGrid.Columns>
        
        <sdk:DataGridTemplateColumn Header="時刻">
         <sdk:DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
           <Button Content="{Binding 時刻}" Margin="-2,-2,-2,-2"/>
          </DataTemplate>
         </sdk:DataGridTemplateColumn.CellTemplate>
        </sdk:DataGridTemplateColumn>
        
        <sdk:DataGridTemplateColumn Header="削除">
         <sdk:DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
           <CheckBox x:Name="chkData" VerticalAlignment="Center" IsChecked="{Binding chkData, Mode=TwoWay}" HorizontalAlignment="Center" HorizontalContentAlignment="Center" />
          </DataTemplate>
         </sdk:DataGridTemplateColumn.CellTemplate>
        </sdk:DataGridTemplateColumn>
        
       </sdk:DataGrid.Columns>
    
      </sdk:DataGrid>
      
      <Button Content="追加" Height="23" HorizontalAlignment="Left" Margin="61,367,0,0" Name="追加" VerticalAlignment="Top" Width="75" Click="追加_Click" />
      <Button Content="削除" Height="23" HorizontalAlignment="Left" Margin="218,367,0,0" Name="削除" VerticalAlignment="Top" Width="75" Click="削除_Click" />
     </Grid>
    
    </UserControl>
    
    namespace Silverlight2010
    {
     public partial class DataGridCheckBox : UserControl
     {
      ObservableCollection<Data> source;
    
      public DataGridCheckBox()
      {
       InitializeComponent();
    
       //コレクションの初期化
       source = new ObservableCollection<Data>();
       MyDataGrid.ItemsSource = source;
      }
    
      private void 追加_Click(object sender, RoutedEventArgs e)
      {
       var data = new Data()
       {
        時刻 = DateTime.Now.ToString("mm:HH:ss.ff"),
        chkData = false
       };
       source.Add(data);
      }
    
      private void 削除_Click(object sender, RoutedEventArgs e)
      {
       var items = source.Where(p => p.chkData).ToList();
    
       foreach (var item in items)
       {
        source.Remove(item);
       }
      }
     }
    
     public class Data : INotifyPropertyChanged
     {
      private bool _chkData { get; set; }
      public bool chkData
      {
       get { return this._chkData; }
       set
       {
        if (_chkData != value)
        {
         _chkData = value;
         OnPropertyChanged("chkData");
        }
       }
      }
    
      private string _時刻 { get; set; }
      public string 時刻
      {
       get { return this._時刻; }
       set
       {
        if (_時刻 != value)
        {
         _時刻 = value;
         OnPropertyChanged("時刻");
        }
       }
      }
    
      #region INotifyPropertyChanged メンバー
    
      public event PropertyChangedEventHandler PropertyChanged;
      protected void OnPropertyChanged(string Name)
      {
       if (PropertyChanged != null)
       {
        PropertyChanged(this, new PropertyChangedEventArgs(Name));
       }
      }
    
      #endregion
    
     }
    
    }
    

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年12月24日 5:51
    モデレータ
  • torapemiyaさん、サンプルコードありがとうございます!

    是非、また試してみたいと思います。まだまだsilverlightと格闘中ですが、先に進めば進むほどDataBindingの重要性を感じています。

    今は、むりくりコードを書いている感じですが、時間が出来たら頂いたサンプルを見ながら再度DataBindingを勉強しようかと思います。

    >#.NET的にはプロパティ名は本当は大文字始まり(パスカル形式)が良いです。

    そうなんですね。まだ、開発初めて2カ月目の素人で、知らない事も多いです。なので、この様な助言は、とてもうれしいです。(指摘されないと、何が間違っているのかも気づかない・・・)

    ありがとうございました。

    03188


    03188
    2010年12月28日 2:44