DataGridの必須入力チェック【WPF】
今週も頑張りました。
オブジェクト指向の前に、昨日の復習を記事にします(難しかった・・・)
昨日会社で教えてもらったのは、DataGridの必須入力チェックです。
DataGridに表示されているデータを編集したとき、必須項目が未入力だった際にその項目の枠が赤くなり、かつ、「必須エラーです」とメッセージが表示される処理です。
この必須入力チェックは、登録ボタン(コマンド)を押すと処理されます。
まずコマンド内でどの項目がエラーなのか判別処理・表示文章設定します。
その後ViewのTargetUpdated内で、実際にエラーメッセージが表示されるように処理していきました。
DataGridViewModel.cs
//このbtnRegist_Clickは、登録ボタンのコマンドに仕込んであるもの。 public void btnRegist_Click() { bool isError = this.DtoCheck(); //チェック処理 if(isError) { this.RaisePropertyChanged("Views"); return; } if(MessageBoxResult.Yes == System.Windows.MessageBox.Show("変更項目を登録しますか?","登録変更確認",MessageBoxButton.YesNo)) { testDataGridModel model = new testDataGridModel(this.Dto); model.InsertData(updateflag); //登録変更が成功したか isSuccess = model.IsSuccess; if(isSuccess) { //略...登録更新の成功・失敗メッセージ処理... } } } private bool DtoCheck() { bool isError = false; //エラーフィールドのクリア //ここで中身をクリアにしておかないと、連続処理したときに前の処理が残ってしまう。 foreach(DataRow dr in this.Dto.testData.Rows) { dr["ERROR_FIELD"] = string.Empty; dr["ERROR_MSG"] = string.Empty; dr.ClearErrors(); } //foreachの中にforeachでRowとColumnを1つずつ取り出している(結果1つのCellずつ処理ができる) foreach(DataRow dr in this.dto.testData.Rows) { //testDataの列を一列ずつ読み込む foreach(DtaColumn dc in this.dto.testData.Columns) { //必須か選択項目か確認 if(sentakuFields(dc.ColumnName)) { continue; } if((dr[dc.ColumnName] == null) || (dr[dc.ColumnName].Tostring() == string.Empty)) { //ここで空のERROR_FIELD行の中身を作っている if(dr["ERROR_FIELD"].Tostring() == string.Empty) { dr["ERROR_FIELD"] = dc.ColumnName; } else { dr["ERROR_FIELD"] += " " + dc.ColumnName; //区切り指定 } dr["ERROR_MSG"] = dc.ColumnName + "で必須エラーです。¥r¥n"; dr.RowError = dc.ColumnName + "で必須エラーです。¥r¥n"; dr.SetColumnError(dc,dr["ERROR_MSG"].Tostring()); isError = true; } } } return isError; } //選択項目を、ここで抜き出し private bool sentakuFields(string strFieldName) { bool isSentaku = false; switch(strFieldName) { case "aa": case "bb": isSentaku = true; break; } return isSentaku; }
DataGrid.cs(View)
public test() { InitializeComponent(); //画面が表示されたときに、色々なデータを取得する処理 ThisForm = ExPageParamsUtil.GetPageParams("TForms") as ExMenuWindow; //...略 this.DataContext = new testViewModel(CommonTbls,exWin,cmbAuth); this.testDataGrid.TargetUpdated += new EventHandler<DataTransferEventArgs>(testDataGrid_TargetUpdated); } private void testDataGrid_TargetUpdated(object sender,DataTransferEventArgs e) { //エラー内容のクリア for(int idx = 0;idx < this.testDataGrid.Rows.Count;idx++) { this.testDataGrid.Rows[idx].Errors.Clear(); } BindingListCollectionView chkView = this.testDataGrid.ItemSorce as BindingListCollectionView; //フィルタかける前のすべてのデータを取得する。 DataTable dt = ((DataView)chkView.SorceCollection).ToTable().Copy(); //フィルタ圧縮かける <>''←これは値が入っているかどうかを判断することができる。 //RowFiletrで圧縮をかけると、番号がおかしくなるので注意。 DataRow[] drs = dt.Select("ERROR_FIELD<>''"); foreach(DataRow dr in drs) { //エラー項目及びエラー内容の取得 DataGridRowError dgErr = new DataGridRowError(); string[] errField = dr["ERROR_FIELD"].Tostring().Split(''); foreach(string fld in errField) { dgErr.ColumnsNames.Add(fld); dgErr.Message = dr.GetColumnError(fld); //エラーオブジェクトを対象データグリッド行にセットする //エラー行番号の取得 int intRowIdx = dt.Rows.IndexOf(dr); //データグリッドエラー対象行にエラーセット this.testDataGrid.Rows[intRowIdx].Errors.Add(dgErr); } } }
以上なのですが、このコードだとセレクターにメッセージが表示されません。
どうすれば良いのか、調べてみようと思います。
-----ここから雑記
教えてもらっていたのに、復習してみるとわかっていないところが出てきました。
書いてみるとわかっていないところがわかるので、これからもコードを書いていこうと思います。
あと、色々コードを教えてもらっていて思っていたことを、昨日やっと聞くことができました。
foreachの中に、foreachやifが入っているのが変な感じがしていたのですが、それは処理が多くなりそうであれば、別のメソッドへ飛ばしても良いそうです。
些細なことも答えてくれる先輩方が、会社にたくさんいるのがとてもありがたいです。
そういやボーナスは出るのかなー。
出たらマイコンキットか、Windowsを購入したいのです・・・うーん難しいかな・・・。
Listでコンボボックスの項目を追加する(グリッドコントロール)
朝早く起きてしまったので、昨日の復習を書きます。
昨日はC1.WPF.DataGridのテキストボックス列(デフォルト)からコンボボックス列へ変更する処理をしました。
以下はそのコードです。
private void c1DataGrid_AutoGeneratingColumns(object sender, C1.WPF.DataGrid.DataGridAutoGeneratingColumnsEventArgs e) { if(e.Property.Name == "aaaa") { //コンボボックス設定 var cmbClmn = new C1.WPF.DataGrid.DataGridComboBoxColumn(e.Property); cmbClmn.DisplayMemberPath = "a.NAME"; cmbClmn.SelectedValuePath = "a.CODE"; DataView dv = new DataView(this.aTbls.Tables["a.CODE"]); dv.RowFilter = "a.CODE.ccc = '00'"; DataTable dtCode = dv.ToTable(); List<Model> models = new List<Model>(); foreach(DataRow dr in dtCode.Rows) { Model data = new Model(); data.a.NAME = dr["a.CODE"].ToString() + " " + dr["a.NEME"].ToString(); data.a.CODE = dr["a.CODE"].ToString(); models.Add(data); } Model data1 = new Model(); data1.a.NAME = "00 その他"; data1.a.CODE = "00"; models.Add(data1); cmbClmn.ItemSource = models; } if(e.Property.Name == "bbbb") { var cmbClmn = new C1.WPF.DataGrid.DataGridComboBoxColumn(e.Property); cmbClmn.DisplayMemberPath = "a.NAME"; cmbClmn.SelectedValuePath = "a.CODE"; List<Model> models = new List<Model>(); Model data1 = new Model(); data1.a.NAME = "00 選択"; data1.a.CODE = "00"; models.Add(data1); cmbClmn.ItemSource = models; }
List
if文が2つあるのですが、Gridのヘッダー名で違った項目を表示することができるようにするためです。
1つ目のif文の中には、ループ文があります。
これは、もともとデータベースにあった項目を使用しているためです。
直接データベースからは持ってこれないので、
DataView dv = new DataView(this.aTbls.Tables["a.CODE"]); ←ここ
にて、DataViewの中身を指定したのちにフィルターをかけて使用する項目を指定しています。
2つ目はもともとある項目を使用していないので、新しい項目だけを追加しています。
また、デフォルトのテキストボックスだった時、各テキストボックスのデータは数字で表示されるようになっていました。
それはDtoから取得してきたデータが、数字であるためです。
それをこの処理をすることによって、数字の意味に適応した文字に変更しています。
変更したのは、数字だと何を意味しているのかわかりづらいからです。
以上です。
昨日はまだよくわかっていないListと、DataViewが作業で出てきました。
これで少し整理できてよかったです。
WPFと戦った記録 ⑵【MVVM】
前回からの続きです。
WPFと戦った記録 ⑴【MVVM】 - プログラミングと日々思ったことなど
↑こちらが前回の記事になります。
今回はViewModelから復習していきます。
まずはコードから(前回に引き続き、コードは省略している部分があります)
KyukaViewModel.cs
ViewModelにて、BindやCommandの処理をコーディングしていきます。
!DTO
データベースからデータを取ってきた際に、データを入れる箱がDTOです。
DTOがあれば直接データベースに接続しなくて良くなります。
(今回のDTOにはkyuka_divisionという名前の箱があります)
using System; using ...略; namespace WpfViewBaseprj.ViewModels { public class KyukaViewModel : Commons,ViewModelBase { //件数表示変数 private string stCount = string.Empty; //登録・更新結果を保存する変数 private bool isSuccess = false; //Viewsフィールド BindingListCollectionView views; public BindingListCollectionView Views { get{return views;} set { views = value; this.RaisePropertyChanged("Views"); } } //データ取得用 private DataSet cmnDs; public DataSet CmnDs { get{return cmnDs;} set{cmnDs = value;} } //Dtoフィールド private WpfViewBaseprjDTOetc.Dto.DtoKyuka dto; public WpfViewBaseprjDTOetc.Dto.DtoKyuka Dto { get{return dto;} set { dto = value; this.RaisePropertyChanged("Views"); } } //Model設定 private KyukaModel kyukaModel; public KyukaModel KyukaModel { get{ return Kyukamodel; } set { kyukaModel = value; this.RaisePropertyChanged("Kyukamodel"); } } //コンストラクタ public BindingListCollectionView ListViews{get; set;} public DataRow dr; //休暇届新規登録用コンストラクタ public KyukaViewModel(DataSet commonDs,System.Widows.Forms.ComboBox cmb) { this.Dto = dto; dto = new DtoKyuka(); this.cmb = commonDs; this.Kyukamodel = new kyukaModel(); dto.kyuka_division.Addkyuka_divisionRow(dto.kyuka_division.Newkyuka_divisionRow()); DtoKyuka.kyuka_divisionRow conRow = (DtoKyuka.kyuka_divisionRow)dto.kyuka_division.Rows[0]; //コンボボックス設定 conRow.sinsei_year = DateTime.Now.Year.Tostring(); conRow.sinsei_month = string.Format("{0:D2}",DateTime.Now.Month); conRow.sinsei_day = ...略; this.Views = new BindingListCollectionView(Dto.kyuka_division.DefaultView); //バインド用データ設定 this.InitBindingData(); //画面クローズ・項目チェックコマンド this.CloseCommand = new RelayCommand<Window>(this.CloseWindow); } //休暇届更新画面用コンストラクタ public KyukaViewModel(DataSet commonDs,System.Windows.Forms.ComboBox cmb,DataRow dataRow) { this.Dto = dto; this.cmnDs = commonDs; this.kyukaModel = new kyukaModel(); this.Dto = kyukaModel.test(dataRow); this.Views = new BindingListCollectionView(Dto.kyuka_division.DefaultView); this.InitBindingData(); this.CloseCommand = new RelayCommand<Window>(this.CloseWindow); } //バインド用データ設定 public void BindingData(string hazimari) { switch(hazimari) { case"shinsei": Days = keisansuru(SelectNen,SelectMonth); break; case"from": Days2 = keisansuru(SelectNen2,SelectMonth2); break; } } //コンボボックスメソッド(年月日を作る) public void Tosituki() { DataTable tosi = new DataTable(); tosi.Columns.Add("tosinum"); tosi.Columns.Add("tosi"); //データテーブルに入れるRowをセット for(int year = 2017;year <=2020 ; year++) { DataRow dr = tosi.NewRow(); tosi.Rows.Add(dr); dr["tosinum"]=year; dr["tosi"]=year; } Nen = tosi; //月設定は省略 //初期値設定 SelectNen = DateTime.Now.Year; SelectMonth = DateTime.Now.Month; SelectDay = DateTime.Now.Day; SelectNen2 = DateTime.Now.Year; SelectMonth2 = DateTime.Now.Month; SelectDay2 = DateTime.Now.Day; } //ラベル設定 public string TopTitle { get{return "休暇届";} } //コンボボックスプロパティ private DataTable nen; public DataTable Nen { get{return nen;} set { nen = value; this.RaisePropertyChanged("Nen"); } } private int selectNen; public int SelectNen; { ...略 } //バインド用日付計算設定 public DataTable keisansuru(int year,int month) { var dt = new DataTable(); int days= DateTime.DaysInMonth(year,month); dt.Columns.Add("id"); dt.Columns.Add("days"); for(int idx=1; idx<=days ; idx++) { DataRow dr = dt.NewRow(); dt["id"] = string.Format("{0:D2}",idx); dt["days"] = string.Format("{0:D2}",idx); dt.Rows.Add(dr) } return dt; } //コマンド public RelayCommand<window> CloseCommand { get; private set; } private void CloseWindow(Window window) { if(window is Entry) { bool ab = false; foreach(System.Windows.Controls.RadioButton rb in ((Entry)window).panel.Children) { if((bool)rb.IsChecked) { ab = true; break; } } if(!ab) { System.Windows.MessageBox.Show("入力項目を確認してください",MessageBoxButton.OK); return; } ((KyukaViewModel)((Entry)window).DataContext).btnSave_Click(); } if((window !=null)&&(isSuccess)) { window.Close(); } } //継承メソッド(ViewModelBaseから継承されたメソッド) public override void btnSave_Click() { KyukaModel kyukaModel = new KyukaModel(this.Dto); kyukaModel.InsertData(); } } }
コンボボックスを分岐させているのは、年月日を記入する項目が3つあるからです。
年月日は1つのコンボボックスではなく、3つのコンボボックス(年と月と日)に分かれています。
また画面上で記入した項目は、ModelからデータベースへInsert・・・ではなく、ModelからFa、インターフェース、最終的にDaoからデータベースへ接続しました。
そして接続して取得したデータを、DTOに入れます。
KyukaModel.cs
using ...略; //休暇届更新画面用 public WpfViewBaseprjDTOetc.Dto.DtoKyuka test(DataRow dr) { WpfViewBaseprjDTOetc.Dto.DtoKyuka testdesu = null; Service = new WpfViewBaseControlLogic.Service.FaKyuka(); testdesu = Service.Gettest(this.dto,dr); return testdesu; }
Modelは、ViewModelで処理しないもの(データベースなど)の処理、管理です。
ViewModelは画面のバインド部分を変更させる処理を主にさせ、Modelはデータベースなどの処理、管理をさせます。
以上です。
・・・うーん。これで良いのか不安です。
まだまだ勉強不足です。がんばります。
WPFと戦った記録 ⑴【MVVM】
会社とWebページで学んだことの復習です。
間違っているかもしれませんので、話半分で読んでください。
WPF とは?
正式名称は、Windows Presentation Foundation。
ウィンドウアプリを開発するライブラリ(色んな機能をまとめたもの)です。
WPFの特徴は、下記の項目が主になります。
①見た目を柔軟に変えることができます。
②グラフィックス・ハードウェア(画像の色々な処理をするもの)を活用していて、ベクター・ベース(ベクタは、画像を扱う形式。ラスタ形式もある)のレンダリング・エンジン(データを画面に表示する場所を教えるプログラム)を使っています。
ベクター・ベースだと、UI要素(UIは、ユーザーとコンピュータとが情報をやり取りをする際に接する、機器やソフトウェアの操作画面や操作方法)の拡大・縮小・回転がスムーズになります。また、ハードウェア・アクセラレーション(高速に処理できる)により、CPUへの負担が少ないそうです。
③外見(画面のデザイン)を設計するXAMLコードと、プログラム処理の内容を設計するコードが分かれています。プログラム処理の内容を設計するコードは、C#やVBなどを使います。
このプログラム処理の内容を設計するコードのことを、「分離コード」とも言います(今後記事の中でも、「分離コード」と書いていきます)。
MVVM ?
MVVMはModel-View-ViewModelの略で、ModelとView、そしてViewModelで役割分担をさせてプログラムを設計する方法です。
このMVVMについて、これから図とコードで詳しく書きます。
コードは実際に書いたものより簡略化しています。(とても長くなってしまうので・・・)
●Entry画面の図
赤色の線、紫色の線、黄色の線、緑色の線がGridです。
LabelやComboBoxの隣に書いてある数は、同じ種類のコントロール(Bindは別)がその枠にあることを示しています。
1番下のButtonにだけある※印は、Commandを示しています。
!Bind
Bindを行うと、分離コード側でバインドの宣言をした内容(データ)を変更することができます。
!Command
複数の操作(イベントなど)をまとめて実行できるようにすることができます。
Entry画面・XAMLコード(Entry.xaml)
<Window x:Class="WpfViewBaseprj.Entry" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Main" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="1*"> <RowDefinition Height="2*"> <RowDefinition Height="以下略………"> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*" /> <ColumnDefinition Width="3*" /> <ColumnDefinition Width="以下略………" /> </Grid.ColumnDefinitions> <Label Content="{Binding Path=TopTitle}" Grid.Column="0" Grid.Row="0" /> <ComboBox Grid.Column="1" Grid.Row="略………" Name="Cmbox0" ItemSource="{Binding Path=Nen}" DisplayMemberPath="tosi" SelectedValuePath="tosinum" SelectedValue="{Binding Path=Views/sinsei_year}" SelectionChanged="Cmbox0_SelectionChanged"/> <Button Content="登録" Command="{Binding Path=CloseCommand}" CommandParameter="{Binding ElementName=Main}" /> <略……… /> </Grid> </Window>
Entry画面・分離コード(Entry.xaml.cs)
using System; namespace WpfViewBaseprj { public partial class Entry : Window { public Entry() { InitializeComponent(); } //引数によって、ViewModelを分岐させる。 //休暇届新規登録用コンストラクタ public Entry(int kyuka) { InitializeComponent(); this.DataContext = new kyukaViewModel(commonDs,cmb) this.Title="休暇届"; this.Height=800; this.Width=700; } //休暇届更新画面用コンストラクタ public Entry(int p,DataRow dataRow) { InitializeComponent(); this.chk1.IsChecked = false; this.chk2.IsChecked = false; this.chk3.....略; this.DataContext = new kyukaViewModel(commonDs,cmb,dataRow); this.Title="休暇届更新"; this.Height=800; this.Width=700; } //作業状況報告用コンストラクタ public Entry(int p,DataRow dataRow) { InitializeComponent(); this.DataContext = new SagyoViewModel(string sagyo); this.Title="作業状況"; this.Height=600; this.Width=500; } //宣言 private DataSet commonDs; private System.Windows.Forms.ComboBox cmb; private WpfViewBaseDtoetc.Dto.DtoKyuka kyuka_save; //コンボボボックスプロパティチェンジ private void Cmbox0_SelectionChanged(object sender,SelectionChangedEventArgs e) { string hajimari = string.Empty; ComboBox cb = sender as ComboBox; switch(cb.Name) { case"Cmbox0" hajimari ="shinsei"; break; case"Cmbox1;" hajimari="from"; break; } ((kyukaViewModel)this.DataContext).BindingData(hajimari); } } }
分離コードを見ていただくとわかるように、コンストラクタが3つあります。
この3つにあるDataContextは、どれも違う引数です。
引数が違うことによって、ViewModel内でも違うコンストラクタを呼び出すことができます。
そのため、ViewModelにて各々違うバインド設定ができる・・・画面(ここで言うEntry画面)は1つですが、いくつかViewModelがあれば、違うデータを表示する画面を作ることができるのです。
そして実は、今まで画面、Entry画面と書いていたのですが、これをMVVMではViewと呼びます。
Viewで外側(デザイン)を作り、ViewModelから中身(データ)の処理を行います。
・・・なのですが、まだ続きがあります。(MVVMはModel-View-ViewModel。Modelの説明がまだです)
このViewModelから、Modelへ繋がっていくのです。
この復習は、長くなってしまったので次の記事に書きます。
ここまでの復習ですが、ざっくりと書きました。(違っていたらごめんなさい)
参考にしたWebページは以下になります。
WPFについて、とても詳しく書かれてあります。
連載:WPF入門 - @IT
DataContextを、わかりやすく解説されています。
【WPF基礎】脱WPF初心者のための基礎知識 その1〜DataContextってなんぞ?〜: おっさんどりーむ 〜日本語で理解するプログラミング技術〜