DataGridで項目スキップ&入力チェックを行う方法

解決


モモ  2005-07-12 23:23:39  No: 123455

VB.NETです。
DataGridでの入力でTABキーやエンターキーで特定の項目をスキップ
させるため、ProcessCmdKeyをオーバーライドして、Me.CurrentCellにて
フォーカスを移動させています。

例)商品コード  商品名  単価
    001      えんぴつ    50
    002      ノート      100
の時、商品コードから単価にスキップさせます。
商品名は商品マスタから引っ張るので表示のみです。

ところがフォームのColumnChangingイベントにて入力チェックを行い、
Throw New ApplicationExceptionにてエラー処理しているにも
関わらず、ProcessCmdKeyで記述したコードが有効になり、
次の項目に移動してしまいます。

例ですと、商品コードにエラーがあるにも関わらず、単価に移動して
しまいます。

入力エラーがなければスキップして次の項目(単価)へ移動しても
良いのですが、入力エラーがあればエラーとなっている項目(商品
コード)に留まるよう設計したいのですが、どうすればよろしい
でしょうか。
よろしくお願い致します。


特攻隊長まるるう  2005-07-13 04:44:18  No: 123456

>ProcessCmdKeyをオーバーライドして
そのレベルのコーディングをするプログラマなら、もっと自分でデバッグ
できなきゃだめだよ(^^;)逆にデバッグできないレベルならそんなコード
書いちゃだめ。

>Throw New ApplicationExceptionにてエラー処理しているにも関わらず、
そのコードをプログラムとして書いてあるのと、実際に動かして、思った
通りの流れで処理が行われているかは全くの別物。で、実際どっち?自分の
用意したコードで実行されてるのはどれとどれとどれ?実行されてないのは
どれ?キャンセル時の処理は何をどうしてるの?その処理はちゃんとキャンセル
出来る方法として確認できてるの?何をキャンセルしてるの?参考にしたコードは?

>ProcessCmdKeyで記述したコードが有効になり
それを確認したコードはどこ?日本語で説明するなら、上手くいかない
時の処理を全てのステップで箇条書きにして下さい。フローチャートを
書くつもりで全て挙げて下さい。多分、コードを載せた方が早いと思うけど
冗長なコードを解析する気はありませんのでよろしく。そこを省略されては
何をしているのか全く伝わりません。ロジック的に正しくても回避できない
現象であれば、何か助言もしますが、ただのコーディングミスを見つける
デバッグ依頼ならお断りします。

以上のような事がハッキリしないと他人には理解できない内容なので、上の
質問ではレス付きません。説明するより自分でデバッグした方が早いと
考えられる内容です。


モモ  2005-07-14 20:44:28  No: 123457

ご指摘ありがとうございました。
デバッグした結果、不明なのでご質問させて頂いたのですが…
記述したコード(抜粋)は以下の通りです。

Private Sub SyohinList_ColumnChanging(ByVal sender As Object, ByVal e As System.Data.DataColumnChangeEventArgs)
        Select Case e.Column.ColumnName
            Case "商品コード"
                If e.ProposedValue > 99
                    MessageBox.Show("不正なコードが入力されました。正しいコードを入力して下さい。")
                    e.ProposedValue = 0
                    Throw New ApplicationException
                End If
        End Select
        ・・・

Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
        Dim Cel As DataGridCell
        Cel = Me.CurrentCell()
        If keyData = Keys.Enter Then
            If Cel.ColumnNumber = 0 Then
                Me.CurrentCell = New DataGridCell(Cel.RowNumber, 2)
                Return True
            End If
        ・・・

で入力値エラーであっても、ProcessCmdKeyで記述している
> Me.CurrentCell = New DataGridCell(Cel.RowNumber, 2)
が有効になってしまいます。

ちなみに
> Me.CurrentCell = New DataGridCell(Cel.RowNumber, 2)
の部分が
> SendKeys.Send("{Tab}") 'TAB
ですと、エラーが解消されるまでセル移動されませんでした。

もちろんこれだととなりの商品名にフォーカス移動されてしまうため、
目的の動作になりません。

TAB×2回も考えましたが、視覚的にかなりおかしい状態でしたのでやめてしまいました。

大変申し訳ございませんが、ご教授の程よろしくお願い致します。


特攻隊長まるるう  2005-07-16 00:12:05  No: 123458

まず、例外エラーを発生させなければいけない場面ではないと思いますが…。

[Throw ステートメント]で作成した例外は、適切な例外処理コードが見つかる
まで呼び出し履歴をさかのぼりますから、適切な例外処理で拾う必要がありますし、
逆に本当にシステムエラーなどが起きた時に、エラーを無視して処理が継続しない
ようにコーディングする必要があります。

また、質問の内容から入力チェックをした後、セルを移動させないような処理を
実行する事になりそうですから、例外エラーで処理を止めるべきではありません。
例外エラーを使わない設計に変更すべきです。

具体的にどうするか?

1:ユーザ定義のイベントを定義しておき、ProcessCmdKey メソッド内で発生させます。
(この時にイベントキャンセル用の Boolean 型の引数を付けておくと便利です。)

これは何も特別な考え方ではありません。例えばテキストボックスで入力チェック
を行う場合、TextChanged や Validating イベントを利用して入力チェックし、
処理を継続します。構造化プログラミングの基本は、既に存在する処理を十分に
理解し、その概念と共通の概念を持ったコーディングをする事です。その場しのぎの
独自のコーディングは、後々自分の首を締める事になります。

2:次にそのイベントを拾い入力チェックします。

3:入力値エラーの場合、イベントキャンセル用のフラグを変更します。
(イメージとしてはフォームの Closing イベントで e.Cancel = True とする感じ)

4:フラグに応じて ProcessCmdKey メソッドに戻りコマンドキーを処理します。
提示されたコードではここにコマンドキーのキャンセル処理が一切書かれていません。
ProcessCmdKey メソッドを継承する意味が半減しています。継承時の注意なども
書かれていますので、ヘルプでどのような機能が用意されているか十分に確認して下さい。

サンプルとして
[PAPA'n VB (ぱぱんぶぃびぃ) - VB.NET Tips データグリッドの行削除の直前に確認する]
http://www.sugi-family.net/papanvb/vbnet_tips.php?cate=20&tips=20002
を解析してみて下さい。

>で入力値エラーであっても、ProcessCmdKeyで記述している
>> Me.CurrentCell = New DataGridCell(Cel.RowNumber, 2)
>が有効になってしまいます。
入力値エラーにしてもコマンドキーをキャンセルするようなコードが書かれていないから
当然の結果です。

>ちなみに
>> Me.CurrentCell = New DataGridCell(Cel.RowNumber, 2)
>の部分が
>> SendKeys.Send("{Tab}") 'TAB
>ですと、エラーが解消されるまでセル移動されませんでした。
前者はサブプロシージャから別のサブプロシージャを呼び出したようなもの。
呼び出した時点でその処理が先に処理されます。
    Private Sub a()
        '[1]
        Call b()
        '[3]
    End Sub
    Private Sub b()
        '[2]
    End Sub
後者はキーストロークをアプリケーションに送信するメソッドですので
OS側で{Tab}キーが押されたような処理を再現します。言うなれば
ボタン連打した時に2回目以降のボタン押下処理がスタックされ、1回目の
ボタン押下の処理が終了するか、OSに制御が戻るまで実行されないのと
同じです。当然の結果です。イベントがどの順番で起こり、スタックされ、
実行されているかを考えて下さい。


モモ  2005-07-18 09:34:11  No: 123459

お返事ありがとうございました。
おかげさまで移動キャンセル動作はうまくいきましたが、
新たな問題が残っていますのでさらに質問させて下さい。
ユーザ定義イベントとして
> Public Event MoveCell(ByRef Cancel As Boolean
を定義し、ProcessCmdKeyの中で
> RaiseEvent MoveCell(blnCancel)
イベントを発生させました。
> Private Sub SyohinList_MoveCell(ByRef Cancel As Boolean)
にて入力チェックを行う際、
> SyohinList(SyohinList.CurrentCell.RowNumber, 0)
を対象にチェックをすると、新たに入力した値を保持していない(前回
入力分を保持)ため、正確なチェックが出来ませんでした。
ColumnChangingやValidatingイベントですと、e.ProposedValueが
それに該当し、入力したばかりの(エラーかもしれない)値を
保持しているので問題ありませんでしたが、
この場合はどうすればよろしいのですか。
素人な質問ばかりで申し訳ございませんが、もう一歩?のところまで
きてますのでご教授のほどよろしくお願い致します。


z  2005-07-19 17:14:21  No: 123460

よく見ていないので勘で書きますが・・

>> Public Event MoveCell(ByRef Cancel As Boolean
>を定義し、

の所で、前の値も渡せるように定義したら駄目なんでしょうか?


モモ  2005-07-20 02:16:45  No: 123461

お返事ありがとうございました。
ただ前の値ではなく、入力したばかりの値が必要なのですが。
入力確定前の値はe.ProposedValue以外に存在するのかどうか
分かりませんが、何とか実現したいと思いますので
よろしくお願い致します。


特攻隊長まるるう  2005-07-20 18:16:52  No: 123462

ああ、そうか。ProcessCmdKey の時点では変更後の値取得が難しいのか…。
本来、この辺りの処理は DataGridTextBoxColumn の TextBox のイベントで
Edit Commit されてるから ProcessCmdKey 内で
Me.TableStyles(0).GridColumnStyles(Cel.ColumnNumber) の
型を調べて DataGridTextBoxColumn なら型変換して、
カラム内部の TextBox.Text で取得してみて下さい。

なお、事前に TableStyles を設定しておく必要があるかも?。

…また、別の方法として、サンプルコードとして書き込まれたコードで
EndEdit して編集確定し、ColumnChanging イベント内でグローバルな
フラグ設定してもエラー値の場合分け出来ました。あんまりいい方法じゃ
なさそうなので詳しくは書きませんが…。


モモ  2005-07-22 19:04:58  No: 123463

ばっちり解決しました!
かなり長いですが、ご指示通り、
> CType(CType(CType(SyohinList.TableStyles(1).GridColumnStyles(0), System.Windows.Forms.DataGridColumnStyle), System.Windows.Forms.DataGridTextBoxColumn).TextBox, System.Windows.Forms.TextBox).Text
で最新入力値を取得することが出来ました。
特攻隊長まるるうさんには的確にご教授頂いて助かります。
本当にありがとうございました。


特攻隊長まるるう  2005-07-22 20:20:53  No: 123464

・2回無駄な型変換してます。
・GridColumnStyles には DataGridTextBoxColumn 以外の型もありえるので型チェック
しておく方が安全です。
        Dim dgc As DataGridColumnStyle
        dgc = SyohinList.TableStyles(1).GridColumnStyles(0)
        If TypeOf dgc Is DataGridTextBoxColumn Then
            MessageBox.Show(DirectCast(dgc, DataGridTextBoxColumn).TextBox.Text)
        Else
            MessageBox.Show("カラムのデータ型を確認して下さい。")
        End If


モモ  2005-07-22 20:55:58  No: 123465

型変換がよく分からなかったので
クイックウォッチで自動表示されたのものをそのまま使用していました。
申し訳ございません…
ご指示頂いた方法でOKでした。
改めてお礼申し上げます。ありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加