キャストについて【C#】
昨日は、キャストが思いつかず悔しい思いをしました。
忘れないように、コードを残します。
問題のキャスト処理を書く前に、条件分岐の処理も復習します。
ublic void kakunin_Click(C1.WPF.DataGrid.DataGridEventArgs e) { this.IsRegistEnable = true; //登録ボタンにバインドさせている。 bool isError = false; string name = e.Column.Name; DataRowView dv = (DataRowView)e.Row.DataItem; dv.Row.ClearErrors(); e.Row.Errors.Clear(); DataGridRowError dgreroor = new DataGridRowError(); //必須入力チェック //sentakuメソッドで、DataGridの項目の選択か必須かを判断している。 if(!this.sentaku(name)) { //DtoCheckメソッドにて、エラーの出ているメソッドを判別。 isError = this.DtoCheck(e.Column,dv.Row) if(isError) { dgreroor.ColumnNames.Add(name); dgreroor.Message="必須入力エラー"; e.Row.Errors.Add(dgreroor); dv.Row.SetColumnError(name,"[項目:"+ name + "]を入力してください"); } } //switchで、入力した値が項目に合う値なのか(数字なのか、半角英数なのかなど)判別。 switch(name) { case"aa": case"bb": if(!InputAuxiliaryUtil.IsNumeric(dv[name].Tostring())) { dgreroor.ColumnNames.Add(name); dgreroor.Message = "入力エラー" e.Row.Errors.Add(dgreroor); dgreroor.Row.SetColumnError(name,"数字を入力してください"); isError = true; } break; case"cc": if(....) { //略... } break; } //エラーがあった場合、登録ボタンを使えないようにする。 if(isError) { e.Cansel = true; this.IsRegistEnable = false; } }
SetColumnErrorというのが、DataGridの問題セルに出現するエラーです。(見た目はフキダシみたいなものです)
InputAuxiliaryUtil.IsNumericというメソッドで、判定させます。
#region 文字チェック public static bool IsNumeric(string value) { if(null == value) { return true; } if(string.IsNullOrEmpty(value.Trim())) { return true; } if(!Regex.IsMach(value.Trim(),REGEX_Numeric)) { return false; } return true; } #endregion
また、この文字チェック内で使われているREGEX_Numericは、辿っていくと正規表現にたどり着きます。
//こんなの //これは例なので、正規表現は半角英数字の判別設定です。 private static string REGEX_Numeric = "^[-_/0-9a-zA-Z]+$";
DataGridは表示されるだけなので、入力文字についてはこのように自分で書いていかなければならないのです。
次はキャストです。
else if(e.Column.Name == "ff") { dv[name] = dv[name].Tostring().PadLeft(2,'0') }
これです。
間違って書いてコードは以下になります。
//このコードは間違っています。 else if(e.Column.Name == "ff") { var i = (DataRowView)e.Row.DataItem; i.PadLeft(7,'0') string col = e.Column.Tostring(); col.PadLeft(7,'0') }
iやcolには値が入っていますが、オブジェクトを知りません。
ただ値を入れているという処理をしているだけなので、DataGridには何も変化がない、です。
値が入っているプロパティを見つけ、キャストして使用できるようにするまでが難しいな・・・。
オブジェクト指向プログラミングについて
オブジェクト指向プログラミング(Object Oriented Programming = OOP)とは
プログラムを書く人自身が、プログラムを把握しきれなくなることを防ぐためのプログラム設計方法です。
このプログラム設計方法の特徴は、3つあります。
①カプセル化
フィールドへの読み書きやメソッドの呼び出しを制限します。
制限することによって、重要(プログラム中で、書き換えてはいけないもの)な情報(フィールド)や操作(メソッド)を保護することができます。
これは誤りの起きにくいプログラム、つまりはクラスを設計する時に役立ちます。
②継承
新しいクラスを作るとき、そのクラスがすでに作られているクラスと類似している部分がある場合があります。
例)
車の設計が書かれたクラスがもともとあって、新しくスポーツカーの設計を書く必要がある。
そのときに、すでに作られたクラスのメンバ変数・メソッドを受け継ぐ仕組みです。
もともとあったクラスは、基本クラスや親クラスと言います。
新しく作るクラスを、派生クラスや子クラスと言います。
③多様性(ポリモーフィズム)
メソッドに多様な振る舞いをさせることです。
多様な振る舞いをさせるメソッドは、抽象的になります。
抽象的になるというのは、簡単に言えば(おそらく)「5m動く」を「動く」にする、ということです。
「動く」にすると、各クラス内で何メートル動くのかを決めることができます。
そうすることによって、効率的にプログラム開発を行えます。
今はこれが限界です。
1年後、詳しく書けるように頑張ろう。
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が作業で出てきました。
これで少し整理できてよかったです。
SQLクエリ実行処理など
今週も色々苦戦していました。
中でもSQL関係が気になったので、その復習です。
DaoBase(親クラス...多分)にコーディングされている実行処理の中で、SQL実行処理クエリがありました。
DaoBase.cs
//機能名称 SQLクエリ実行処理 //パラメータで受け取ったSQL文を実行し、結果セット //SQL条件パラメータ protected DataTable ExecuteQuery(string sSql, IDbDataParameter[] collection, string tblName) { try { DataSet ds = new DataSet(); connection = DbFactory.GetCommand(sSql,connection) comm.Parameters.Clear(); if(conParams.Driver == "ORACLE") { foreach(OracleParameter p in collection) { comm.Parameters.Add(p); } }else { //ココの処理がよくわからない明日聞く // 5.16 ifの条件、DriverでORACLEかSqlserverか切り替えていた。 //が、何らかの理由でその必要がなくなったため、この部分が残った(今ココは通りません) foreach(OracleParameter p in collection) { comm.Parameters.Add(p); } } log.Debug(comm.CommandText); //アダプタ作成 adapter = DbFactory.CreateAdapter(comm); adapter.Fill(ds); ds.Tables[0].TableName = tblName; return ds.Tables[0]; } catch(Exception ex) { log.Info("SQLクエリ実行時エラー",ex); return new DataTable(); } finally { DbFactory.ReleaseConnection(connection); } } }
これによって、子クラスのSQLクエリも実行されるようになります。
またconnectionは、web.config内でパラメータが定義されています。
<add key="DRIVER" value="ORACLE" /> <add key="ConnectionCnt" value="2" />
valueが2なので、connectionが2より多く繋がれると、エラーが発生します。
そうして、DaoBaseにてクエリ実行処理がコーディングされていることによって、実行できるのが以下の情報取得処理です。
namespace Logic { public class DaoImpl:DaoBase,IDaoaa { readonly log4net.Ilog log = log4net.LogManager.GetLogger (System.Reflaction.MethodBase.GetCurrentMethod().DeclaringType); public DaoImpl() :base() { } //情報取得処理 public DtoaaList GetDtoList(DtoaaList dto) { string sSql = string.Empty; sSql = @" SELECT aa ,bb ,cc ,...略 FROM aa_COLUMNS ORDER BY aa,bb "; try { DataTable dt = ExecuteQuery(sSql,"aa_COLUMNS"); dto.Merge(dt,true,MissingSchemeAction.Ignore); } catch(Exception ex) { log.Error("テーブル情報取得エラー",ex); } return dto; } } }
この処理で、データをDtoから取ってきています。
なので、同じDaoの中でセレクト文を作成するときにはconnectionは使用しなくて良いのです。
ちなみにこの中でLIKE文を使用するときには、||で%を囲わなければ文字だと判別してくれないようです。
-----ここから雑記
このconnectionの話を以前にもしていただいたのに、忘れてしまっていて再度教えてもらいました。
ぬおー悔しい。
そして現在、その悔しさをバネにしつつこの参考書を使って自習してます。
この参考書は、綺麗なコードになるまでを詳しく書いてくれているのでわかりやすいです。
わかりやすいといっても、実際にコーディングしながら進めているので数ページ読むのに随分時間がかかってしまいますが…。
今回は以上です。
来週はオブジェクト指向について、今ある知識で頑張って書いていきます。
SQLについて色々【メモ】
Execute Query
DataContextクラスのメソッド。
SQLクエリを直接記述して、実行することができます。
クエリを直接記述したとき
(テーブル項目)
↓
(値:@をつける)
↓
(パラメータ:@をつける)
@がキーワードとなって、値を取得します。
*クエリを書くときは、カンマを前に書いた方が良い。
サブスクエリ 副問い合わせ
SQLではSELECT文による問い合わせを入れ子にすることができます。
入れ子にした問い合わせを副問い合わせと言います。
インジェクション攻撃
ソフトウェアへの攻撃手法の1つです。
文字列入力を受け付けるプログラムに対して、セキュリティを無効化するような不正な文字列を入れてシステムを乗っ取ったり、データを詐取したりします。
不正な文字列と勘違いされないように、MySqlなどではクエリに:=や:をつけます。
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はデータベースなどの処理、管理をさせます。
以上です。
・・・うーん。これで良いのか不安です。
まだまだ勉強不足です。がんばります。