none
DataGridCellのElementStyleの中からバインドしたい RRS feed

  • 質問

  • WPFでDataGridを使いたいのですが、列数が可変なため、XAMLではなくコードから列を作っています。

    変数countは作るべき列数で、DataGridのItemsSourceが、子プロパティにResultsというListを持ったクラスのコレクションとして、

                var styleCell = new Style(typeof(DataGridCell));
                var styleTextBlock = new Style(typeof(TextBlock));
    
                for (int i = 0; i < count; i++) // countは別の部分で判明している列数
                {
                    var g = new DataGridTextColumn();
    
    				//※1
                    g.Binding = new Binding("Results[" + i.ToString() + "]");
                    styleCell.Setters.Add(new Setter(DataGridCell.HorizontalAlignmentProperty, HorizontalAlignment.Stretch));
    
    				//※2
                    styleTextBlock.Setters.Add(new Setter(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Right));
    
                    g.CellStyle = styleCell;
                    g.ElementStyle = styleTextBlock;
                    dataGrid1.Columns.Add(g);
                }

    などとしてやれば、HorizontalAlignmentは、値を表示するTextBlock自体はStretchで、その中に表示する文字だけはRightにすることができます。

    ここで、実際にやりたいことは、ELementStyleの方は、常時Rightにするのではなく、表示する文字が数字である場合だけRight、そうでない場合はStretchないしLeftで左詰めの表示にしたいのです。

    CellStyleを指定する※1の部分で、表示する値自体にはバインドできているので、これを※2のElementStyleの中からバインドできれば、後はそれを受け取って、数値かどうか判断して、Right ないし StretchのHorizontalAlignmentを返すコンバーターを通せばそれで行けると思うのですが。

    肝心のバインドが、ElementStyleの中からは、コードからどのように書いたらよいのかわかりません。色々試してみましたがどうもうまく行きません。

    バインドを設定する方法を教えて頂けないでしょうか。バインドする対象は※1と同じ、Resultsの中の1要素へインデクサでバインドです。

    2012年10月23日 6:57

回答

  • new Binding(".")で列用のデータが入っているのが確認できたなら、インデクサ部分の指示が間違っていることがわかります。

    余計なコードは追加せずに新規プロジェクトで以下のコードを試してください。
    提示されたThingクラスに書き換えてありますが、IValueConverterに想定どおりの値が入っています。

    正しく実行することが確認できたら、April19thさんのコードをよく見なおしてください。
    一番最初にApril19thさんが提示されたコードでは同一のstyleTextBlockのままでstyleTextBlock.Setters.Addをしていますが、列ごとに作り直していますか?
    作り直さずに同じSettersにTextBlock.HorizontalAlignmentProperty用のSetterを追加設定しても、最後のしか意味を持ちません。
    さらに、g.ElementStyleに同一インスタンスを設定しているため、全ての列が同一の設定を持っています。
    つまり全ての列で最後の"Result[" + (count-1).ToString() + "]"をバインドしたことになってます。
    #ここでcountが大きすぎる値になっていたりするとインデックスでのバインドに失敗してIValueConverterに値がわたらなくなります

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Data;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Test3();
        }
        public void Test3()
        {
            var dataGridSource = new List<Thing>();
            Thing data = new Thing();
            data.Results.Add("あいうえお");
            data.Results.Add(12345.ToString());
            dataGridSource.Add(data);
            data = new Thing();
            data.Results.Add(Math.PI.ToString());
            data.Results.Add(DateTime.Now.ToString());
            dataGridSource.Add(data);
            int columnCount = 3;
            var styleCell = new Style(typeof(DataGridCell));
            styleCell.Setters.Add(new Setter(DataGridCell.HorizontalAlignmentProperty, HorizontalAlignment.Stretch));
                
            for (int i = 0; i < columnCount; i++)
            {
                var g = new DataGridTextColumn();
                g.Binding = new Binding("Results[" + i.ToString() + "]");
                g.CellStyle = styleCell;
                var styleTextBlock = new Style(typeof(TextBlock)); //** ここをよく見ましょう
                    
                Setter setter = new Setter();
                setter.Property = TextBlock.HorizontalAlignmentProperty;
                setter.Value = new Binding("Results[" + i.ToString() + "]") { Converter = new AlignmentConverter() };
                styleTextBlock.Setters.Add(setter);
                g.ElementStyle = styleTextBlock;
                dataGrid1.Columns.Add(g);
            }
            dataGrid1.AutoGenerateColumns = false;
            dataGrid1.ItemsSource = dataGridSource;
        }
    }
    public class AlignmentConverter : IValueConverter
    {
        #region IValueConverter メンバー
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            System.Diagnostics.Debug.WriteLine(value.ToString());
            object v = value;
            var dv = v as System.Data.DataRowView;
            if (dv != null && parameter != null)
            {
                if (parameter is int)
                {
                    v = dv[(int)parameter];
                }
                else
                {
                    v = dv[parameter.ToString()];
                }
            }
            else
            {
                var row = v as System.Data.DataRow;
                if (row != null && parameter != null)
                {
                    if (parameter is int)
                    {
                        v = row[(int)parameter];
                    }
                    else
                    {
                        v = row[parameter.ToString()];
                    }
                }
            }
            if (v != null && v is string)
            {
                double dbl;
                if (Double.TryParse((string)v, out dbl))
                {
                    return HorizontalAlignment.Left;
                }
                else
                {
                    return HorizontalAlignment.Right;
                }
            }
            else
            {
                return HorizontalAlignment.Left;
            }
        }
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
    public class Thing
    {
        public Thing() { Results = new List<string>(); }
        //見出しとしてDataGrid各行の左端に表示する項目の名前
        public string Name { get; set; }
        //その項目の結果1,結果2,結果3....が入るList
        public List<string> Results { get; set; }
    }

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

    • 回答の候補に設定 April19th 2012年10月24日 23:01
    • 回答としてマーク 佐伯玲 2012年10月25日 1:13
    2012年10月24日 9:07

すべての返信

  • Setter.Valueにバインディングとコンバーターを設定してやるとできます。
    以下のコードではItemsSourceをDataViewにしているのでRow[列名]でバインドしています。ここをResults[i]へ変更してください。。

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Data;
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var dt = new DataTable();
            dt.Columns.Add("DataColumn1", typeof(object));
            dt.Columns.Add("DataColumn2", typeof(object));
            var row = dt.NewRow();
            row[0] = "あいうえお";
            row[1] = 12345;
            dt.Rows.Add(row);
            row = dt.NewRow();
            row[0] = Math.PI;
            row[1] = DateTime.Now;
            dt.Rows.Add(row);
            var styleCell = new Style(typeof(DataGridCell));
            styleCell.Setters.Add(new Setter(DataGridCell.HorizontalAlignmentProperty, HorizontalAlignment.Stretch));
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                var g = new DataGridTextColumn();
                g.Binding = new Binding("DataColumn" + (i + 1).ToString());
                g.CellStyle = styleCell;
                var styleTextBlock = new Style(typeof(TextBlock));
                Setter setter = new Setter();
                setter.Property = TextBlock.HorizontalAlignmentProperty;
                setter.Value = new Binding("Row[DataColumn" + (i + 1).ToString() + "]") { Converter = new AlignmentConverter() };
                styleTextBlock.Setters.Add(setter);
                g.ElementStyle = styleTextBlock;
                dataGrid1.Columns.Add(g);
            }
            dataGrid1.AutoGenerateColumns = false;
            dataGrid1.ItemsSource = dt.DefaultView;
        }
    }
    public class AlignmentConverter : IValueConverter
    {
        #region IValueConverter メンバー
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            object v = value;
            var dv = v as System.Data.DataRowView;
            if (dv != null && parameter != null)
            {
                if (parameter is int)
                {
                    v = dv[(int)parameter];
                }
                else
                {
                    v = dv[parameter.ToString()];
                }
            }
            else
            {
                var row = v as System.Data.DataRow;
                if (row != null && parameter != null)
                {
                    if (parameter is int)
                    {
                        v = row[(int)parameter];
                    }
                    else
                    {
                        v = row[parameter.ToString()];
                    }
                }
            }
            if (v != null && v is string)
            {
                return HorizontalAlignment.Right;
            }
            else
            {
                return HorizontalAlignment.Left;
            }
        }
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
        #endregion
    }


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

    2012年10月23日 10:55
  • 返信ありがとうございます。

    ElementStyleの方で、Setter.Valueにバインディングを設定する箇所で

    new Binding("Row[DataColumn" + (i + 1).ToString() + "]")

    となっている箇所ですが、この部分のバインディングがやはりうまく行きません。

    最初に私の載せていた

    new Binding("Results[" + i.ToString() + "]")

    でセルにデータは正しく表示されており、同じ値を元にコンバーターに分岐処理させるので、これで良さそうな気もしますが、これがうまく行きません。

    gekkaさんのコードでもElementStyleの方では、"Row[" + 列名 "]" となっているように、ここでもまた何か、別の書き方をしなければならないと思うのですが…。


    2012年10月24日 0:51
  • 「うまくいきません」とだけ書かれているだけだと、 どこまでできているのかわかりません。

    new Binding("Results[" + i.ToString() + "]")

    でセルにデータは正しく表示されており、同じ値を元にコンバーターに分岐処理させるので、これで良さそうな気もしますが、これがうまく行きません。

    この記述であっているはずです。
    ですが、Resultsがコレクションなのか配列なのかインデクサなのか書かれていないので、特殊なクラスの場合は情報の提示がない限り判りません。

    gekkaさんのコードでもElementStyleの方では、"Row[" + 列名 "]" となっているように、ここでもまた何か、別の書き方をしなければならないと思うのですが…。

    先のレスでも説明に書きましたが、私がサンプルとして提示したのはItemsSourceにDataTable.DefaultViewであるため、1行毎のデータはDataRowViewになります。ゆえに、DataRowView.Row[列名]をバインドしているのです。
    ですから、ResultsがDataRowViewでないなら当然別の記述になります。

    1行毎のデータが何かわからないのでしたら、

    setter.Value = new Binding(".") { Converter = new AlignmentConverter()};

    として、IValueConverter.Convertのvalue引数をデバッガで何がバインドされているのか確認してください。

    #先のサンプルでは文字列だと左揃え、文字列以外は右揃えになるようにしてあります。数字を入れても右揃えにならないからうまくいかないと勘違いしてません


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

    2012年10月24日 3:55
  • ログインができなくなってしまい登録し直したため表示名が若干変わっていますが質問者です。

    今回のバインドについて詳細を書いていくと、まず、

    public class Thing
    {
        //見出しとしてDataGrid各行の左端に表示する項目の名前
        public string Name { get; set; }		
        //その項目の結果1,結果2,結果3....が入るList
        public List<string> Results { get; set; }	
    }

    というクラスがあり、このクラスのコレクションをDataContextに設定しているViewModel内で、

    public ObservableCollection<Thing> Things { get; set; }

    というプロパティとして設定しています。

    そして、そのプロパティに対して、XAML内では

     <DataGrid Name="dataGrid1" ItemsSource="{Binding Things}" AutoGenerateColumns="False" FrozenColumnCount="1" CanUserAddRows="False">

    として宣言しています。そして、実際に各列(列数は固定でない)ResultsへのバインドはXAMLでなくコードでしているという訳です。

    バインドしているデータの形なのですが、コンバーターに渡っているvalueをGetType()で見ると、

    new Binding("Results[" + i.ToString() + "]")

    では、正しくStringの形になっているのですが、その肝心の文字列の中身が渡されていないのです。

    (全く同じバインドの指定で、表示そのものの方は、正しく、Results[]に格納されている文字列が順次表示されているのですが、なぜかコンバーターを通すとそれが機能しなくなる?)

    ちなみに

    new Binding(".")

    とした場合ならば、GetTypeではThingとなっています。

    なお、コンバーターに関しては、gekkaさんのアップして頂いたものではなく、すでに作っていた自分のものを使用していますが、上で書いた、コンバーターに値がきちんと渡らないという問題は、下のようなダミーのコンバーターを実験で作ってみても、やはり渡っていないようでした。

        public class TestConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return "「"  + value.ToString() + "」";
            }
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }

    このコンバーターを通してバインドした結果をToolTipにバインドすると、Stringの値がきちんと渡っていれば、「文字列」とToolTipに表示されるはずですが、ToolTipは「」となってしまい、valueとして文字列がコンバーターに渡っていないのです(GetTypeするとStringとして認識されるので、型だけは渡っている?)。

    私も、

    new Binding("Results[" + i.ToString() + "]")
    でよいはずだと思うのですが、これでどうしてもうまく行かず、何か他の書き方をしなければならないのではないかと、こちらで質問させていただきました。



    • 編集済み April19th 2012年10月24日 5:58
    2012年10月24日 5:53
  • new Binding(".")で列用のデータが入っているのが確認できたなら、インデクサ部分の指示が間違っていることがわかります。

    余計なコードは追加せずに新規プロジェクトで以下のコードを試してください。
    提示されたThingクラスに書き換えてありますが、IValueConverterに想定どおりの値が入っています。

    正しく実行することが確認できたら、April19thさんのコードをよく見なおしてください。
    一番最初にApril19thさんが提示されたコードでは同一のstyleTextBlockのままでstyleTextBlock.Setters.Addをしていますが、列ごとに作り直していますか?
    作り直さずに同じSettersにTextBlock.HorizontalAlignmentProperty用のSetterを追加設定しても、最後のしか意味を持ちません。
    さらに、g.ElementStyleに同一インスタンスを設定しているため、全ての列が同一の設定を持っています。
    つまり全ての列で最後の"Result[" + (count-1).ToString() + "]"をバインドしたことになってます。
    #ここでcountが大きすぎる値になっていたりするとインデックスでのバインドに失敗してIValueConverterに値がわたらなくなります

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Data;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Test3();
        }
        public void Test3()
        {
            var dataGridSource = new List<Thing>();
            Thing data = new Thing();
            data.Results.Add("あいうえお");
            data.Results.Add(12345.ToString());
            dataGridSource.Add(data);
            data = new Thing();
            data.Results.Add(Math.PI.ToString());
            data.Results.Add(DateTime.Now.ToString());
            dataGridSource.Add(data);
            int columnCount = 3;
            var styleCell = new Style(typeof(DataGridCell));
            styleCell.Setters.Add(new Setter(DataGridCell.HorizontalAlignmentProperty, HorizontalAlignment.Stretch));
                
            for (int i = 0; i < columnCount; i++)
            {
                var g = new DataGridTextColumn();
                g.Binding = new Binding("Results[" + i.ToString() + "]");
                g.CellStyle = styleCell;
                var styleTextBlock = new Style(typeof(TextBlock)); //** ここをよく見ましょう
                    
                Setter setter = new Setter();
                setter.Property = TextBlock.HorizontalAlignmentProperty;
                setter.Value = new Binding("Results[" + i.ToString() + "]") { Converter = new AlignmentConverter() };
                styleTextBlock.Setters.Add(setter);
                g.ElementStyle = styleTextBlock;
                dataGrid1.Columns.Add(g);
            }
            dataGrid1.AutoGenerateColumns = false;
            dataGrid1.ItemsSource = dataGridSource;
        }
    }
    public class AlignmentConverter : IValueConverter
    {
        #region IValueConverter メンバー
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            System.Diagnostics.Debug.WriteLine(value.ToString());
            object v = value;
            var dv = v as System.Data.DataRowView;
            if (dv != null && parameter != null)
            {
                if (parameter is int)
                {
                    v = dv[(int)parameter];
                }
                else
                {
                    v = dv[parameter.ToString()];
                }
            }
            else
            {
                var row = v as System.Data.DataRow;
                if (row != null && parameter != null)
                {
                    if (parameter is int)
                    {
                        v = row[(int)parameter];
                    }
                    else
                    {
                        v = row[parameter.ToString()];
                    }
                }
            }
            if (v != null && v is string)
            {
                double dbl;
                if (Double.TryParse((string)v, out dbl))
                {
                    return HorizontalAlignment.Left;
                }
                else
                {
                    return HorizontalAlignment.Right;
                }
            }
            else
            {
                return HorizontalAlignment.Left;
            }
        }
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
    public class Thing
    {
        public Thing() { Results = new List<string>(); }
        //見出しとしてDataGrid各行の左端に表示する項目の名前
        public string Name { get; set; }
        //その項目の結果1,結果2,結果3....が入るList
        public List<string> Results { get; set; }
    }

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

    • 回答の候補に設定 April19th 2012年10月24日 23:01
    • 回答としてマーク 佐伯玲 2012年10月25日 1:13
    2012年10月24日 9:07
  • 最初に載せたコードは、ポイントだけ抜粋・編集したものでしたが、実際のコードでも、TextBlock用のStyleは、一度作ったまま使いまわしになっていました。

    おっしゃる通り、これが今回の問題の原因だったようです。

    これをループ中で毎回新たに作り直すようにしたところ、無事に、コンバーター経由のバインドで左詰め・右詰めを自動で切り替えて表示できるようになりました。

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

    2012年10月24日 23:01