DataGridにコンボボックスを表示する

 WPFのDataGridでコンボボックスを表示するのにちょっと詰まってしまったのでメモ。

<結論>
 バインドするデータのクラスにコンボボックスの選択肢のインスタンスを作る
 
<経緯>
 1.自動化ツール(以下、RPA)を作ろう
 2.どんな動作をするか定義をしよう
    例)
      ・マウスを(X,Y)=(20,200)へ移動
      ・クリック
      ・3秒待機
 3.動作定義のためのクラスを定義
  
        public ViewModel ThisViewModel;
        public MainWindow()
        {
            InitializeComponent();

            this.ThisViewModel = new ViewModel();

            this.ThisViewModel.Title = "タイトル";
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "マウス移動", SetValue = "20,200" });
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "クリック", SetValue = "" });
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "待機", SetValue = "2" });


            this.DataContext = this.ThisViewModel;
        }

        public class ViewModel : ModelBase
        {
            private string _title;
            public string Title
            {
                get { return this._title; }
                set { this._title = value; NotifyPropertyChanged(); }
            }

            public ObservableCollection<Action> Actions { get; set; }
            public ViewModel()
            {
                this.Actions = new ObservableCollection<Action>();
            }
        }

        #region PropertyChangeイベントを通知する処理を実装したベースクラス
        public class ModelBase : INotifyPropertyChanged
        {
            #region インターフェイス実装イベント
            public event PropertyChangedEventHandler PropertyChanged;
            public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            #endregion
        }
        #endregion

        /// <summary>
        /// 動作定義を登録するクラス
        /// </summary>
        public class Action: ModelBase
        {
            private string _actionName;
            public string ActionName
            {
                get { return this._actionName; }
                set
                {
                    this._actionName = value;
                    SetSelectedItem();
                    NotifyPropertyChanged();
                }
            }

            private string _setValue;
            public string SetValue
            {
                get { return this._setValue; }
                set
                {
                    this._setValue = value; NotifyPropertyChanged();
                }
            }

            public ComboBoxItem SelectedItem { get; set; }

            public void SetSelectedItem()
            {
                foreach (var item in ComboItems)
                {
                    if(item.Name==this.ActionName)
                    {
                        this.SelectedItem = item;
                        break;
                    }
                }
            }

            public ObservableCollection<ComboBoxItem> ComboItems { get; set; }

            public Action()
            {
                this.ComboItems = new ObservableCollection<ComboBoxItem>();
                this.ComboItems.Add(new ComboBoxItem() { Name = "マウス移動", Value = "001" });
                this.ComboItems.Add(new ComboBoxItem() { Name = "クリック", Value = "002" });
                this.ComboItems.Add(new ComboBoxItem() { Name = "待機", Value = "002" });
            }
        }
      
 4.下記のXAMLにバインド
    <Grid>
        <Canvas>
            <TextBox Canvas.Left="10" Canvas.Top="26" Text="{Binding Title}" TextWrapping="Wrap" Width="400"/>

            <DataGrid Height="217" Width="400" Canvas.Left="10" Canvas.Top="60" ItemsSource="{Binding Actions}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="動作名" Width="150">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <ComboBox ItemsSource="{Binding ComboItems}" Width="140"
                                                 SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                 DisplayMemberPath="Name" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn Width="150" Header="引数" Binding="{Binding SetValue}"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Canvas>
    </Grid>

 5.結果について
    DataGridのRowにバインドされているデータにコンボボックスの選択肢のインスタンスを持っておく必要がある。(検証不足?)
    ってことはDataTableをバインドして一瞬でできたぜヒャッハー!
    ってのは無理ってことなのかなぁ。まぁ入力値のチェックとかを実装することを考えるとどうしてもクラスを一つ作らざるをえないのかな・・・。
    一覧コピー的なことを一括でやろうとすると少し手間かもしれない。

 6.新規行のコンボボックスでてんやわんや
   新規行のコンボボックスにはNewItemPlaceHolderってのがバインドされているため、コンボボックスの選択肢が表示されない。
   この例でいうと「引数」列に何らかの値を入れて、行を確定させてあげると選択肢がわいてくる。何とかしたいなーって思ったので下記のサイトを見つけた。
   https://social.msdn.microsoft.com/Forums/ja-JP/7b1630de-0982-4191-bd73-3cd060e89439/wpfdatagriddatagridtemplatecolumninitializingnewitemraise?forum=wpfja
   このサイトの通りにやると一瞬ちょっとずれは生じるけどなんとかなった。
  
 7.補足
   一覧の件数がめっちゃ多い場合については表示が遅くなることがある。
   DataTemplateで作成している列については、ComboBoxがひたすら表示されているだけなので、DataGridのEditモードに移行しなくても触ることができるのが便利な反面、描画速度には問題が出ることがあるかもしれない。
   まぁ一覧を1000件表示するぐらいなら問題ないだろうし、そんなに入力できるわけないからいいと思うんだけども。
   もしどうしてもしないといけなくなったら、CellTemplateにはTextBlockでも表示しておいて、CellEditingTemplateにComboBoxを配置してあげればいい。
   DataGridTextColumnと同じでDataRowが編集モードになれば触るようになる。
    

0 件のコメント:

コメントを投稿

PowerShellでEdgeを自動化(インストール不要。参考:郵便追跡サービス自動操作)

1.経緯について  RPAのソフトをインストールできないので、これまでVBSでCreateObjectでブラウザの自動操作をすることがたまにあった。 ※いざというときの手札として持っているだけで安心感が段違い  見た目上IEがインストールされていなくても、CreateObject...