none
DataGridViewで他のテーブルの値も参照して表示したい RRS feed

  • 質問

  • ユーザーマスタ

    ・User_ID

    ・User_Name

    道具マスタ

    ・Item_CD

    ・Item_Name

    使用道具テーブル

    ・Item_CD

    ・Date

    ・User_ID

    というテーブルがあります。

    このフォームのItemコンボボックスには道具マスタからデータを持ってきており、コンボボックスと数量の入力後

    使用道具テーブルに新規レコードで書き込み、使用道具テーブルとバインドさせたDataGridViewに表示させています。

    この時に道具マスタから「Item名」を参照し、DataGridViewに表示させたいのですが方法が分かりません。

    ビューを利用してみたのですが、このDataGridView上では削除・編集も行う為削除がネックになりビューは利用できません。

    (ひょっとしたらと思って試してみましたが編集はともかく削除しようとすると「複数のテーブルがあるからダメ」と怒られました)

    DataGridViewのColumnTypeをDataGridViewComboBoxColumnにするとDataSourceが選択出来る様になるので

    そのやり方を説明しているHPもありましたがComboBoxColumnは使用したくありません。

    さっくり実装できると思ってた所につまづいてしまい、色々なページを見ましたが自分で解決する事が出来ませんでした。

    ご教授お願いします。

    2012年8月9日 0:58

回答

  • chromeMAO さま。 よろしく。


    User_ID,Item_CD,Item名,数量 の 4項目のうち User_ID,Item_CD,数量の 3項目で DataBase 上のテーブルに書き込むのですね、最終的には。

    ひとつ確認ですが、編集可能とすると Item_CD と Item名 との整合性が崩れる恐れがありますが、大丈夫ですか?
    単に、入力確定後に、 User_ID 毎の入力行単位の削除 と 数量 の変更をするのが目的なら、次の方法は如何ですか?

    DataGridView は表示のみとして、編集はさせない。
    DataGridView には Bind しない。
    編集には Button Control を 5つ Form 下部に新設し各々 移動(上)・移動(下)・編集・削除・確定 とし、イベントハンドラを書く。
    この編集ボタンでは、ComboBox と TextBox に DataGridView から値を取得し、Focus は TextBox にあてる。

    確定で初めて、Bind しなかったVB 上のテーブルに書き込む。

    • 回答としてマーク chromeMAO 2012年8月9日 2:47
    2012年8月9日 2:08

すべての返信

  • Hoshinaです
    こんにちは

    データベースを使用するうえで,正規化は必須ですから,実プロジェクトになると今回のような場合しかないくらいに考えた方が良いと思います。
    それで,私がプログラムを作成する場合は,以下のようにすると思います。

    ・SQLで複数のテーブルをJOINして,その結果を一覧に表示する
    ・複数テーブルをJOINしたので,自動で更新はできません(こうゆうものだとして,割り切ります)
    ・更新や削除用のSQL文をプログラムで動的に作成する

    更新・削除のSQLを作成する方法は,それほど難しいとは思いませんが,いかがでしょう。
    検索結果のDataTableに必要な値は全て格納されているわけですから。

    それでは

    2012年8月9日 1:19
  • chromeMAO さま。 よろしく。


    User_ID,Item_CD,Item名,数量 の 4項目のうち User_ID,Item_CD,数量の 3項目で DataBase 上のテーブルに書き込むのですね、最終的には。

    ひとつ確認ですが、編集可能とすると Item_CD と Item名 との整合性が崩れる恐れがありますが、大丈夫ですか?
    単に、入力確定後に、 User_ID 毎の入力行単位の削除 と 数量 の変更をするのが目的なら、次の方法は如何ですか?

    DataGridView は表示のみとして、編集はさせない。
    DataGridView には Bind しない。
    編集には Button Control を 5つ Form 下部に新設し各々 移動(上)・移動(下)・編集・削除・確定 とし、イベントハンドラを書く。
    この編集ボタンでは、ComboBox と TextBox に DataGridView から値を取得し、Focus は TextBox にあてる。

    確定で初めて、Bind しなかったVB 上のテーブルに書き込む。

    • 回答としてマーク chromeMAO 2012年8月9日 2:47
    2012年8月9日 2:08
  • Hoshina様、ShiroYuki Mot様

    こんにちは、ご返信ありがとうございます。

    >・複数テーブルをJOINしたので,自動で更新はできません(こうゆうものだとして,割り切ります

    そうか、そうですよね。ムキになってどうにかしようとして頭が回ってなかったようです…

    >ひとつ確認ですが、編集可能とすると Item_CD と Item名 との整合性が崩れる恐れがありますが、大丈夫ですか?

    入力されたセルの値をマスタに見に行って、無ければエラーとして弾いて…という処理をしようとしていました。

    が、そこまでしてあげるならShiroYuki Mot様のおっしゃる通り根本的な作り方を変えた方がよっぽどスマートですね。

    >DataGridView は表示のみとして、編集はさせない。
    > DataGridView には Bind しない。
    >編集には Button Control を 5つ Form 下部に新設し各々 移動(上)・移動(下)・編集・削除・確定 とし、イベントハンドラを書く。
    >この編集ボタンでは、ComboBox と TextBox に DataGridView から値を取得し、Focus は TextBox にあてる。

    >確定で初めて、Bind しなかったVB 上のテーブルに書き込む。

    こうやって実装していけばいいのか、と感無量です。前方に光が見えました。

    早速作ってみます、お二方本当にありがとうございました!

    2012年8月9日 2:47
  • 数量は使用道具テーブルが持っているのでしょうか? マスターに関する列、すなわち、Item_CD、Item名は表示のみで編集不可でよろしいですか?
    また、DataGridViewにバインドしているのは何でしょうか? SqlDataAdapterやTableAdapterを使用されていますか? もうそうであれば、その際に削除や更新のSQL文はどのように書かれていますか?

    表示するだけなら必ずしもjoinする必要はありません。例えば、以下のようにSQL文を書けます。

    select Item_CD,
           (select Item名 from 道具マスタ as m where m.Item_CD = t.Item_CD) as Item名,
           数量 
        from 使用道具テーブル as t
        where t.User_ID = @User_ID
    

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年8月9日 2:48
    モデレータ
  • こうやって実装していけばいいのか、と感無量です。前方に光が見えました。

    一般的に多くのケースでは、DataGridViewに表示されている項目と同じ項目を別画面を開いて入力させるのはあまりスマートではありません。それならばDataGridViewで全ての項目を直接入力できるからです。この場合、DataGridViewの1行1行が別画面であり、それらの画面が同時に開いている状態だと考えられます。
    そうではなく、DataGridViewにはあくまで代表的な項目のみが表示されており、代表項目を含めたその他の項目の編集を別画面で行うのであれば、意味があります。例えば、検索時にはユーザー名、およびユーザーの所属部署名のみをDataGridViewで表示し、別画面でユーザーの詳細なプロフィールを編集させるような場合です。この場合、DataGridViewは多くの場合、表示専用となり編集はできないようにします。(よって、DataGridViewを使う必然性は無くなります)

    以上はあくまで私が考えている基本的な指針ですので、実際には仕様要求によっても異なってくるはずですが、今回のケースですと私が知る限り、私はDataGridViewで直接編集させた方がどちらかというとスマートであるように思います。道具の入力はマスターに絡むのでもう少し検討が必要ですし、なぜ、DataGridViewComboBoxColumnが避けられているのかがわからないのですが、必要であればDataGridViewのコンテキストメニューから道具選択用の画面を開くようにすることもできます。

    いずれにしても、DataGridViewに表示させているものが、個々のデータそのものの集合なのか、それとも個々のデータへのインデックスを表しているリストなのかを意識されると良いと思います。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年8月9日 5:27
    モデレータ
  • trapemiya さま いつもお世話になります。


    仰っていることは良く分かります。
    確かに、スマートな画面構成という点では、ご指示のような実装が望ましいと思います。

    DataGridView による一覧入力 と、レコード単位で 個々の項目を独立したコントロールで操作する個別入力 とを混在させるべきではないのも分かります。

    ご質問なさった chromeMAO さまが実際に今回のものを完成され使用されて、その辺をどう捉えられるかですね。
     chromeMAO さまのご質問のきっかけを作ったのは、使用道具テーブルと道具テーブルとをリレーションさせた状態でバインドさせた事がきっかけでしょうから、使用道具テーブルのみでバインドすればいいのですよ ... と投稿したほうが良かったのでしょうか?
    投稿内容が安易・幼稚でしたか?


    話はズレますが、DataGridView による一覧入力 で気の利いたものを作ろうとすると、結構なコーディング量になりますよね。その辺も、私を含め初心者が理解し難いものになっている気がします。特にリレーション!
    個人的にはデータベース絡みで一回である程度納得の行く物が出来たためしがなく、何回も画面構成やデータベース構造を弄って、数ソリューション作ることが多いです。
    素人プログラマーですからその数はタカが知れていますけど。
    コンテキストメニューから別画面を開く というのは良いですね。機会があったら試して見ます。
    個人的には、選択用の別 Form や Dialog を用意しイベントハンドラで起動するようにした事があります。画面の自由な位置に動かせて重宝しています。

    DataGridView による一覧入力 の一番の問題点は、間違った操作を気づかずにレコードを変更確定してしまった後で、取り消し操作(Undo ですか?)が出来ない事です。おっちょこちょいなので。
    OS の Explorer で File を操作していても同じ事を感じています。
    コーディングできるのでしょうが、私の知識では気が遠くなりそうです。

    話が脱線気味なのでこの辺で。

    • 編集済み ShiroYuki_Mot 2012年8月10日 4:55 さま 抜けを訂正・失礼しました
    2012年8月9日 9:09
  • ご質問なさった chromeMAO さまが実際に今回のものを完成され使用されて、その辺をどう捉えられるかですね。
     chromeMAO のご質問のきっかけを作ったのは、使用道具テーブルと道具テーブルとをリレーションさせた状態でバインドさせた事がきっかけでしょうから、使用道具テーブルのみでバインドすればいいのですよ ... と投稿したほうが良かったのでしょうか?
    投稿内容が安易・幼稚でしたか?

    詳しい仕様をchromeMAOさんから聞いたわけではありませんので、ShiroYuki_Motさんも書かれているように、chromeMAOさんが最終的にどう捉えられるかだと私も思います。
    ShiroYuki_Motさんの発言が安易・幼稚だとは思いません。そういう解決策は確かにあると思います。私が先の発言をした理由は、私にはchromeMAOさんが今回はこのやり方しかないと思い込んでいるように受け取れたからです。蛇足だったかもしれませんが、もう少しchromeMAOさんにUIについて考えてほしいと思いました。

    話はズレますが、DataGridView による一覧入力 で気の利いたものを作ろうとすると、結構なコーディング量になりますよね。その辺も、私を含め初心者が理解し難いものになっている気がします。特にリレーション!

    おそらくDataTableをバインドしているからだと思います。DataTableの役割はご存じだと思いますが、非接続状態でデータベースを扱うための仕組みであり、その目的のために最適化されたオブジェクトです。したがって、UIにバインドするために最適化されたものではありませんので、UIにバインドするためのオブジェクトを別に用意した方が無理がありません。例えばDataGridViewのDataSourceにDataTableを指定した場合、実際にはそこから生成されるDataViewがバインドします。このように裏で自動的にDataViewがDataGridViewとDataTableの間に入るのですが、自動的に入ってしまうが故に、このUIにバインドするためのオブジェクトを知らない、もしくは軽視されがちです。ですから、本来は自動的ではなく手動でDataViewを作成し、それをDataSourceに割り当てるようにした方が良かったかもしれません。仕組みを理解した上で自動化を進めるのは良いと思いますが、そうでない自動化は開発者のスキルを下げる危険があると思います。

    上記に書いたように、UIにバインドするオブジェクトの存在に気付かなければ、DataViewがバインドした状態のまま複雑なことをやろうとしてうまくいかないと嘆くことになります。なぜなら、DataViewは、DataGridViewをExcelのシートのような感覚で入力する程度のことしか想定されていないからでしょう。
    一方、UIにバインドするオブジェクトの存在に気付けば、DataViewを使わず、自分の都合の良いバインド用のオブジェクトを作成すれば良いことに気が付くでしょう。

    DataGridView による一覧入力 の一番の問題点は、間違った操作を気づかずにレコードを変更確定してしまった後で、取り消し操作(Undo ですか?)が出来ない事です。おっちょこちょいなので。

    この意味がよくわかりませんでした。Excelでシートに入力していても、最後に保存ボタンを押さないと保存されませんよね。DataGridViewへの入力もそれと同じで、データベースへ保存しなければ、いくらDataGridViewで操作してもその変更がデータベースに反映されることはないのですが・・・


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年8月10日 5:05
    モデレータ
  • trapemiya さま ご回答ありがとうございます。

    とても詳細に書いて下さって恐縮しております。
    分かり易いご説明で、閲覧なさった方にも参考になる気がします。
    ここまでスクロールして尚且つちゃんと読んで戴ければ ... 、ですが ... 。

    最後のご不明な点についてですが、保存の前に行う、一連の変更作業の中で、
    誤入力してしまった操作を元に戻す、という意味です。
    最初からやり直しが原則、とでも言いましょうか ... 。
    いつでも、好きな位置まで操作を戻して、そこからやり直し、というのは VB ではなかなか面倒ですね。
    うまく説明になっていますでしょうか?

    兎も角、ありがとうございました。


    • 編集済み ShiroYuki_Mot 2012年8月10日 7:51 不明瞭な文節削除
    2012年8月10日 7:48
  • 例えば、ある 1項目のみ変更前のデータに戻そうとすると、DataTable なり DataView なりのコピーを保持した上でデータを書き戻すロジックを実装しなければ実現できませんよね。
    入力してしまった操作を元に戻す、という意味です。
    最初からやり直しが原則、とでも言いましょうか ... 。

    DataTableは最初に取得してきた時の値を保持していますから、変更前に戻すことができます。

    (参考)
    方法 : 特定のバージョンの DataRow を取得する
    http://msdn.microsoft.com/ja-jp/library/7kd9zhee(v=vs.80).aspx

    また、UIにバインドするオブジェクトを別に用意しているのであれば、変更前に戻す作業は、DataTableから変更前の値を読めば良いことになります。

    このように変更前と変更後の値を比較することにより、ユーザーが値を変更した時のみ、「このまま閉じると入力した値が破棄されますがよろしいですか?」というような確認ダイアログを出すことが可能になります。

    変更前に戻すのではなく、Excelのシートに入力した値をUndoやRedoで元に戻したり進めたりするのは、私は実装した経験がないので確実なことは言えませんが、値の変更履歴を取り、それに沿って操作すれば良いと思います。しかし、行の削除や追加、スクロール位置などを考えると、ちょっと苦労するかもしれませんね。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年8月10日 9:01
    モデレータ
  • trapemiya さま ご回答ありがとうございます。

    やはり、変更履歴を取り操作するしかないですか ... 。
    Undo の実現って使う分には便利ですが、実装には相当な知識とコード量が必要になりそうですね。
    前から気になっていましたので、本スレッドをお借りしてお聞きしてしまいましたが、有益な機会を頂戴し感謝しています。
    実装時の方向は見定められましたので、助かります。

    もう少し、細々とお尋ねしたい気もしますが、スレッドの本来の内容から完全に離脱していると思いますので、この辺で筆を置きます。

    今回の件、もし、自分で実装する機会を得て不明になりましたら、再度、別スレッドにてご質問させて戴く事としましょう。

    trapemiya さま 有益なご指導に感謝いたします。
    chromeMAO さま かなり脱線して申し訳ありませんでした。

    2012年8月11日 1:53
  • trapemiya さま ShiroYuki_Motさま


    折角の書き込みに対してご返答が遅くなり申し訳ありません。

    >私にはchromeMAOさんが今回はこのやり方しかないと思い込んでいるように受け取れたからです。蛇足だったかもしれませんが、もう少しchromeMAOさんにUIについて考えてほしいと思いました。

    正におっしゃる通りです。煮詰まって煮詰まって方向が分からなくなり、ここに相談させて頂いて回答をもらえて「こうだったのか!」と飛びつきました。猪突猛進状態で頂いたアドバイスを元に実装していて、その後も書き込みがあったことに気付かなかった事をお詫び致します。


    私がUIについてしっかり説明しきれていないせいでお二方にご迷惑をお掛けしてしまいました。今更かなとも思いましたがこんな事で悩む人もいるんだ、というログを残す為にも実際のUIと悩み場所を改めて書いてみようと思います。

    実際のUIは下の様になっています。入力欄はそれぞれマスタがあるものはコンボボックスで、任意フィールドはテキストボックスで作成しており、計8つ程同じ仕様で登録する場所があります。


    「使用材料登録」の場合はマスタは持っていません。「材料名」「数量」「購入先」を入力し終えると、「使用材料テーブル」に登録され、「使用材料テーブル」バインドされたDataGridViewに表示されるようにしています。

    DataGridView上で修正があった場合は下記の処理をしています。

        Private Sub dgvUse_Others_Item_UserDeletingRow(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowCancelEventArgs) Handles dgvUse_Others.UserDeletingRow
    
            If (Not e.Row.IsNewRow) Then
                Dim response As DialogResult = _
                MessageBox.Show("この行を削除しますか??", "削除しますか", _
                MessageBoxButtons.YesNo, _
                MessageBoxIcon.Question, _
                MessageBoxDefaultButton.Button2)
                If (response = DialogResult.No) Then
                    e.Cancel = True
                Else
                    e.Cancel = True
                End If
            End If
    
            Dim r As Integer = dgvUse_Others.CurrentRow.Index
            dgvUse_Others.Rows.RemoveAt(r)
    
            Me.Tbl_Use_OthersBindingSource.EndEdit()
            Me.Tbl_Use_OthersTableAdapter.Update(Me.Daily_reportDataSet.tbl_Use_Others)
    
        End Sub

    「使用肥料登録」の場合、肥料名はマスタからも引っ張ってこれますが、入力も可能にしています。コンボボックスのDataSource、DisplayMember共に肥料マスタの肥料名をセットし、数量と共に「使用肥料テーブル」に書き込み、「使用肥料テーブル」にバインドされたDataGridViewに表示されるようにしています。

    それぞれ各テーブルとデータバインドしたDataGridViewなので修正・削除ともうまく実装できたのですが、

    この「使用道具登録」は「使用道具」マスタからしか道具を選択出来ない様にしています。

    コンボボックス「Item」はDataSourceに道具ID、DisplayMemberに道具名としています。

    「使用道具テーブル」には道具IDと数量が登録されます。(マスタは「道具ID」「道具名」のみです)

    上記2つの「使用材料登録」「使用肥料登録」と同様にDataGridViewに登録した内容を表示させ、修正・削除もしたいと考えました。

    が、DataGridViewを「使用道具テーブル」にバインドさせると当然ですが道具IDと数量しかないので、マスタから道具名を持って来ようとして躓きました。、

    バインドさせてしまうと削除や更新が出来ない、バインドさせずにDataTableで編集し、その結果をDataGridViewに表示するのはどうか、でも結局「道具名」をマスタから持ってこないといけないし、こんな簡単そうな事に躓くなんてそもそもこのやり方で合ってるのか、合ってるとまでも行かなくてももっと王道なやり方があるんじゃないか…とぐるぐるし、色んなサイトを見に行ってピンポイントで解決する所がなくこのスレッドに投稿した次第です。

    あくまで「表示させたいだけ」なので、trapemiya さまからアドバイスのあった

    select Item_CD,
           (select Item名 from 道具マスタ as m where m.Item_CD = t.Item_CD) as Item名,
           数量 
        from 使用道具テーブル as t
        where t.User_ID = @User_ID

    を試してみます。(試す前の投稿ですみません、ともかくお礼が先に言いたかったので)

    >したがって、UIにバインドするために最適化されたものではありませんので、UIにバインドするためのオブジェクトを別に用意した方が無理がありません。例えばDataGridViewのDataSourceにDataTableを指定した場合、実際にはそこから生成されるDataViewがバインドします。このように裏で自動的にDataViewがDataGridViewとDataTableの間に入>るのですが、自動的に入ってしまうが故に、このUIにバインドするためのオブジェクトを知らない、もしくは軽視されがちです。ですから、本来は自動的ではなく手動でDataViewを作成し、それを DataSourceに割り当てるようにした方が良かったかもしれません。仕組みを理解した上で自動化を進めるのは良いと思いますが、そうでない自動化は開発者のスキルを下げる危険があると思います。

    >上記に書いたように、UIにバインドするオブジェクトの存在に気付かなければ、DataViewがバインドした状態のまま複雑なことをやろうとしてうまくいかないと嘆くことになります。なぜなら、DataViewは、DataGridViewをExcelのシートのような感覚で入力する程度のことしか想定されていないからでしょう。
    >一方、UIにバインドするオブジェクトの存在に気付けば、DataViewを使わず、自分の都合の良いバインド用のオブジェクトを作成すれば良いことに気が付くでしょう。

    この辺り、全く意識していませんでした。trapemiya さまのご返答、深く感謝します。ShiroYuki_Motさまのご質問は私も同様に疑問に思っていた事でした。質問して頂いた事、私の方こそお礼申し上げます。

    恥を晒してしまった気もしますが、とても勉強になりました。

    お二方共本当にありがとうございました。


    • 編集済み chromeMAO 2012年8月20日 2:44
    2012年8月20日 2:38