質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
DataGrid

GUIの一種であり、データを表の形式でみることが可能です。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

データベース

データベースとは、データの集合体を指します。また、そのデータの集合体の共用を可能にするシステムの意味を含めます

Q&A

解決済

1回答

1240閲覧

C#使用visual studioのdatagridView新規追行の入力値検証方法

akiteru

総合スコア18

DataGrid

GUIの一種であり、データを表の形式でみることが可能です。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

データベース

データベースとは、データの集合体を指します。また、そのデータの集合体の共用を可能にするシステムの意味を含めます

0グッド

0クリップ

投稿2022/10/06 07:21

前提

(1)C#でvisual studioとMs SQL ServerとでWindowsフォームアプリケーション住所録を作成しています。
登録データの個々の属性マスター(親戚、友人、知人・・・等)のテーブルKuwakeTblを下記の様なフィールドで作成しました。
(2)このデータの編集にDataGridViewとBindingNavigatorをBindingsource経由でフォームに作成。
テーブルのKuwakeNameフィールドは必須入力、重複不可、HyoujiNo(属性により個々データを重要なものから表示)フィールドは必須入力指定です。
(3)入力中のデータ検証のためDataGridViewのCellValidatingメソッドに下記のコードを作りました。
(4)動作検証の為BindingNavigatorにボタンおよびFormにボタンをそれぞれ作成しClickメソッドにそれぞれメッセージ表示させるコートを作成。

「動作状況」
①グリッド内で既存データ行での移動はBindingNavigatorでの行移動を含め正常に動作
②グリッド下端の新規追加行に移動後何も操作せず又は何らかの入力作業後グリッド内の他のセルに「マウス、矢印キー、Tabキー等」で移動は正常
③グリッド下端の新規追加行に移動後何も操作せず又は何らかの入力作業後BindingNavigatorで行移動すると下記Program.csの「エラー発生行」コメント部に「System.Data.NoNullAllowedException: '列 'KuwakeName' に nulls を使用することはできません」のエラー発生。
この場合CellValidatingメソッドが実行された場合表示されるメッセージは発生しません。
④グリッド下端の新規追加行に移動後何も操作せず又は何らかの入力作業後Formに作ったボタンをクリック後BindingNavigatorで行移動すると正常に動作するのに対し、BindingNavigator内に作成したボタンをクリック後BindingNavigatorで行移動すると③と同様のエラー発生。

「気付いたこと」
①DataGridViewの最左端列には現在行のマーク「▶」、新規行マーク「*」がありますが最初の状態は第1行に「▶」が有り最下端に「*」が有ります。新規行に移動すると「▶*」に変ります。この2マーク表示状態ではマウス等でのグリッド内移動は正常ですがBindingNavigatorでの移動はエラー発生します。
②前項と同様にして新規行に移動し「▶*」表示状態でFormに作成した「ボタン」をマウスでクリックすると「*」は新規行に残り、「▶」は1行上に表示されます。このため以後はBindingNavigatorでの移動は正常です。

「解決したいこと」
汎用の必須入力、重複検証はどのようにするのが適当なのでしょうか?
特に新規追加行の場合元々全入力欄が空白でセルを移動して入力していきます、ただし無入力又は一部入力途中で止めて新規追加をキャンセルすることがあります(一部入力時は新規追加行のプロパティNewRowIndexは持たなくなるようです)。
ご教授ください。

発生している問題・エラーメッセージ

エラーメッセージ System.Data.NoNullAllowedException: '列 'KuwakeName' に nulls を使用することはできません。'

該当のソースコード

ソースコード (1)「テーブル定義」 CREATE TABLE [dbo].[CsKuwakeTbl] ( [KuwakeId] INT IDENTITY (1, 1) NOT NULL, [KuwakeName] NVARCHAR (30) NOT NULL, [HyoujiNo] INT NOT NULL, CONSTRAINT [PK_CsKuwakeTbl] PRIMARY KEY CLUSTERED ([KuwakeId] ASC), UNIQUE NONCLUSTERED ([KuwakeName] ASC) ); (2)「グリッド入力値チェックコード」 private void csKuwakeTblDataGridView_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { //セル入力データ検証に入った目印 MessageBox.Show("CellValidating"); //------区分名編集タブ内グリッド内での必須入力及び区分名の重複検証 DataGridView dgv = (DataGridView)sender; //種々のチェックでデータベースへの登録の可否flg int flg = 0; //セル移動の可否 flg= 0 可 // ① 行内でデータ変更無の時は無検証 if (!dgv.IsCurrentRowDirty) { flg = 1; return; } // ② 既存行でのセル移動検証 if (e.RowIndex != dgv.NewRowIndex) { // 区分名、表示順は空白不可 if ((e.FormattedValue.ToString() == "") || (e.FormattedValue == null)) { // 空白不可 flg = 1; MessageBox.Show("区分名又は表示順が空白です。元の値に戻します"); //元の値に戻し、セルを元の位置に戻す dgv.CancelEdit(); e.Cancel = true; return; } } // ③ 区分名は重複不可検証 if (flg == 0 & e.ColumnIndex == 0) { //重複検証 foreach (DataGridViewRow row in dgv.Rows) { if (row.Index != e.RowIndex) { // 編集中の行どうしでないとき if (row.Cells[e.ColumnIndex].Value != null) { // 列に値が格納されているとき if (e.FormattedValue.ToString() == row.Cells[e.ColumnIndex].Value.ToString()) { // 値が重複するならエラー表示してループから抜けるv MessageBox.Show("区分名が重複しています。元の値に戻します"); //元の値に戻し、セルを元の位置に戻す flg = 1; break; } } } } //区分名が重複の時は不可 if (flg == 1) { dgv.CancelEdit(); e.Cancel = true; return; } // ④ 新規追加行で処理 //区分名、表示順の一方のみ入力は行移動可(全項目入力済みでのセル移動時はデータベースへ登録) if (flg == 0 & (e.RowIndex == dgv.NewRowIndex)) { .int n = 0; //全コラム数 int m = 0; //入力の有るコラム数 //追加行のセルがすべて空かを調べる foreach (DataGridViewCell cell in dgv.Rows[e.RowIndex].Cells) { n = n + 1; if (cell.Value != null || cell.Value.ToString() != "") { //未入力セル数 m = m + 1; } } //新規追加行では行内でのセル移動は自由。全欄入力済みの時はデータベース登録 if (n != m) { //一部欄のみ入力時はデータベース登録無。行内セル移動可 //全欄空白時はn=2,m=0となり、ここに入ってくる flg = 1; //データベースへ登録なし } } } // ⑤ 入力値チェックOKでデータベース登録 if (flg == 0) { //何れかのセルに入力が有り、データ検証OKのデータをデータベースに登録 this.Validate(); this.csPostTblBindingSource.EndEdit(); this.tableAdapterManager.UpdateAll(this.csPostDbDataSet); } } (3)「エラー表示部コード」(下記再下端行 Application.Run(new Form1()); 部分にエラー表示) namespace CsPostApp { internal static class Program { /// <summary> /// アプリケーションのメイン エントリ ポイントです。 /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); //----------------この行にエラー発生表示 Application.Run(new Form1()); } } }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

akiteru

2022/10/06 08:51

ご指摘ありがとうございます。結果とお礼がないものが有りました。遅ればせながらコメントを付けました
退会済みユーザー

退会済みユーザー

2022/10/07 22:13

質問者さん、無言ですが、回答したのでそれに対するフィードバックを返してください。役に立った/立たなかったぐらいはすぐに返せるのでは? 役に立たなかったならどこがダメかを書くとより期待に近い回答が出てくるかも。とにかく無言は NG です。
guest

回答1

0

ベストアンサー

Visual Studio のデータソース構成ウィザードを使って型付 DataSet / DataTable + TableAdapter + TableAdapterManager を作って、それをデータソースウィンドウから Form にドラッグ&ドロップしてアプリを作ったのですよね?

で、

(1) BindingNavigator を操作したときに NoNullAllowedException がスローされる問題

(2) DB にユニーク制約を設けた列に重複するデータをユーザーが入力したときの検証

・・・に悩んでいるのですよね?

そうであれば、

(1) の方は DataGridView.DataError イベントを処理することでかなりのところまで対応できるはずです。

ユーザーがDataGridViewのセルに正しくない値を入力した時に発生するエラーを捕捉する
https://dobon.net/vb/dotnet/datagridview/dataerror.html

DataGridView.DataError イベント
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridview.dataerror?view=netframework-4.8

ただし、BindingNavigator の + ボタンを 2 回クリックしたり、新規行に未入力で MoveFirst ボタンをクリックしたりすると補足できないという問題があるはずです。そこまで何とかしたいということですと、かなり手を加える必要があります。具体例は以下の記事を見てください。

NoNullAllowedExceptionのエラーをチェックができない
https://social.msdn.microsoft.com/Forums/ja-JP/45d6e9b4-67cf-48b6-a564-5cddcf6a59e6/nonullallowedexception1239812456125211254012434124811245512483124631236?forum=csharpgeneralja

(2) の問題は以上の処理では解決できないので、別の手段が必要です。SQL Server ということは複数のユーザーが同時にアクセスするわけですから、重複しているか否かは実際に INSERT する時でないと分かりません。それは、INSERT 操作を行ったとき SqlException がスローされ、エラーの種類を表す番号が 2601 であることで判断できます。具体的なサンプルは以下のような感じです。

private void productBindingNavigatorSaveItem_Click(object sender, EventArgs e) { this.Validate(); this.productBindingSource.EndEdit(); try { this.tableAdapterManager.UpdateAll(this.productDataSet); } catch(SqlException sqlEx) { if (sqlEx.Number == 2601) { MessageBox.Show("ProductName", "Unique 制約違反"); } else { throw;   } } }

【追記】

下のコメント欄の 2022/10/08 16:19 の私のコメントで「後で回答欄にサンプルを追記しておきます」と書いた件です。

元の SQL Server のデータベースは以下の通りです。

イメージ説明

これをベースにデータソース構成ウィザードを使って型付 DataSet / DataTable を作って、それをデザイン画面で「データソース」ウィンドウから Form にドラッグ&ドロップして作ったものがベースになってます。

それに手を加えたのが以下のサンプルです。

using System; using System.Data; using System.Windows.Forms; namespace WindowsFormsPkUnique { public partial class Form3 : Form { public Form3() { InitializeComponent(); this.productDataGridView.DataError += ProductDataGridView_DataError; this.productBindingSource.PositionChanged += ProductBindingSource_PositionChanged; } // BindingNavigation の操作、マウスやキーボードを使ったときのポジションの変更に // にしたがって BindingNavigator 上の ToolStripButton の Enabled プロパティの // 設定を変更するには BindingSource.PositionChanged イベントのハンドラで設定 private void ProductBindingSource_PositionChanged(object sender, EventArgs e) { int count = productBindingSource.Count; int position = productBindingSource.Position; if (count <= 1) { bindingNavigatorMoveFirstItem.Enabled = false; bindingNavigatorMovePreviousItem.Enabled = false; bindingNavigatorMoveNextItem.Enabled = false; bindingNavigatorMoveLastItem.Enabled = false; if (count == 0) bindingNavigatorDeleteItem.Enabled = false; else bindingNavigatorDeleteItem.Enabled = true; } else { bindingNavigatorDeleteItem.Enabled = true; if (position == 0) { bindingNavigatorMoveFirstItem.Enabled = false; bindingNavigatorMovePreviousItem.Enabled = false; bindingNavigatorMoveNextItem.Enabled = true; bindingNavigatorMoveLastItem.Enabled = true; } else if (position == count - 1) { bindingNavigatorMoveFirstItem.Enabled = true; bindingNavigatorMovePreviousItem.Enabled = true; bindingNavigatorMoveNextItem.Enabled = false; bindingNavigatorMoveLastItem.Enabled = false; } else { bindingNavigatorMoveFirstItem.Enabled = true; bindingNavigatorMovePreviousItem.Enabled = true; bindingNavigatorMoveNextItem.Enabled = true; bindingNavigatorMoveLastItem.Enabled = true; } } } private void ProductDataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e) { if (e.Exception != null) { MessageBox.Show(this, $"Index {e.RowIndex} の行でエラーが発生しました。\n\n" + $"説明: {e.Exception.Message}", "エラーが発生しました", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } private void productBindingNavigatorSaveItem_Click(object sender, EventArgs e) { this.Validate(); this.productBindingSource.EndEdit(); this.tableAdapterManager.UpdateAll(this.productDataSet); } private void Form3_Load(object sender, EventArgs e) { // TODO: このコード行はデータを 'productDataSet.Product' テーブルに読み込みます。 // 必要に応じて移動、または削除をしてください。 this.productTableAdapter.Fill(this.productDataSet.Product); } private void bindingNavigatorMoveFirstItem_Click(object sender, EventArgs e) { try { productBindingSource.MoveFirst(); } catch (NoNullAllowedException ex) { MessageBox.Show(ex.Message + "\n\nデータを修正するか終了してください。", "「最初に移動」ボタン操作エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } private void bindingNavigatorMovePreviousItem_Click(object sender, EventArgs e) { try { productBindingSource.MovePrevious(); } catch (NoNullAllowedException ex) { MessageBox.Show(ex.Message + "\n\nデータを修正するか終了してください。", "「前に戻る」ボタン操作エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } private void bindingNavigatorMoveNextItem_Click(object sender, EventArgs e) { try { productBindingSource.MoveNext(); } catch (NoNullAllowedException ex) { MessageBox.Show(ex.Message + "\n\nデータを修正するか終了してください。", "「次に移動」ボタン操作エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } private void bindingNavigatorMoveLastItem_Click(object sender, EventArgs e) { try { productBindingSource.MoveLast(); } catch (NoNullAllowedException ex) { MessageBox.Show(ex.Message + "\n\nデータを修正するか終了してください。", "「最後に移動」ボタン操作エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } private void bindingNavigatorAddNewItem_Click(object sender, EventArgs e) { try { productBindingSource.AddNew(); } catch (NoNullAllowedException ex) { MessageBox.Show(ex.Message + "\n\nデータを修正するか終了してください。", "「新規追加」ボタン操作エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } private void bindingNavigatorDeleteItem_Click(object sender, EventArgs e) { productBindingSource.RemoveCurrent(); } } }

投稿2022/10/07 05:55

編集2022/10/08 07:52
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

akiteru

2022/10/08 04:59

ご教授ありがとうございます。DataError イベントまでは現状理解できますが、それ以降の第2、第3を「解読」している最中で「理解」までは至っていません。これからネットを見ながら理解しまう。ただ今回の私の問題がMSのDataGridViewの「癖」というか私だけでないことで「安心しました」。努力します、ありがとうございました
退会済みユーザー

退会済みユーザー

2022/10/08 07:19

今さらながらですが、上の回答欄で紹介した MSDN フォーラムのスレッドの私の回答のやり方では、マウスやキーボードを使ってポジションを変更する場合、BindingNavigator 上のボタン (ToolStripButton)の Enabled プロパティの設定が変わりません。 なので、BindingSource.PositionChanged イベントのハンドラで BindingNavigator 上のボタン (ToolStripButton)の Enabled プロパティの設定を行うようにしてください。 後で回答欄にサンプルを追記しておきます。
akiteru

2022/10/08 09:41

追伸ありがとうございます。自分のコードに焼き直してトライしてみます。たびたびの援助ありがとうございます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問