DataGridViewのデータをXMLで出力する。

タグの編集
投稿者 grid  (社会人) 投稿日時 2010/1/21 02:08:47
質問させていただきます。
VB2008を使用しています。

やりたいことは

1.ファーム上のテキストに入力したデータ

2.DataGridViewに追加

3.DataGridViewの内容をXML出力する。

4.前項のXMLデータをDataGridViewに取り込む

ということです。

1.2.4.はできるのですが、どうも3.のところだけできません。
XMLからDataGridViewへの取り込みは書籍やネットでいろいろと調べたのですが、DataGridViewをXMLに出力する情報は少なくて困っています。

今現在3.で使おうとしているプログラムは下の記述です。
※VisualBasic中学校のデータベース講座の第4回DataTableの利用と書籍を元に作成しました。

------------------------------------------------------------------------
    Private Sub hozonn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles hozonn.Click

        Dim dSet As DataSet = New DataSet("リスト")
        Dim dTbl As DataTable
        Dim sWrite As System.IO.StreamWriter

    'DataGridViewからDataTableを取得
        dTbl = DirectCast(auctionlist.DataSource, DataTable)


        '出力パスの指定
        sWrite = New System.IO.StreamWriter("ファイルパス\list.xml" _
                                            , False, System.Text.Encoding.Default)
        
        dSet.WriteXml(sWrite)
        MessageBox.Show("SampleData.xmlに出力しました。", "通知")


    End Sub
------------------------------------------------------------------------


実際に実行するとエラーはでずにXML出力できるのですが、XMLデータを開くと【<リスト />】だけ表示されていてDataGridViewのデータが出力できていません・・・。

自分で思うにDataGridViewからDataTableを取得しただけで、実際に出力されるデータの対照になっていないようにも思えます。

どなたかアドバイスをお願いします。


※自分のVB暦は短いですが、HTML(CSS)でのホームページ作成やAccessでの簡単なデータベースを作成(VBも少々使用)できるくらいの知識レベルです。
投稿者 るしぇ  (社会人) 投稿日時 2010/1/21 03:06:54
コードをぱっと見た感じ、
dSet と dTbl に何の関係もないように見えるんだけど???

テーブル用意して、隣りで箱だけ別に用意して、空っぽのまま
ラッピングしてる感じ。。。
投稿者 grid  (社会人) 投稿日時 2010/1/21 18:29:43
返答ありがとうございます。

確かに関連していないような・・・。
変に加工したのがいけなかったのでしょうか?

次に、
-----------------------------------------------------------------
        Dim dTbl As DataTable
        Dim sWrite As System.IO.StreamWriter

        dTbl = DirectCast(auctionlist.DataSource, DataTable)
        dTbl.TableName = "リスト"

        
        sWrite = New System.IO.StreamWriter("ファイルパス\list.xml" _
                                            , False, System.Text.Encoding.Default)
        
        dTbl.WriteXml(sWrite)
        MessageBox.Show("SampleData.xmlに出力しました。", "通知")
-----------------------------------------------------------------
と元の情報に近づけてみました。

・・・。

【dTbl.TableName = "リスト"】の部分に、
オブジェクト参照がオブジェクト インスタンスに設定されていません。
というエラーがでてしまいます。

引き続きアドバイスをお願いします。

投稿者 るしぇ  (社会人) 投稿日時 2010/1/22 03:42:43
プログラムやった事があるわけですから、ステップ実行で止めて
変数の中身を確認するとか、エラーメッセージでWEB検索するとか
くらいはできるのでは?

インスタンスというのは、メモリ上に確保されたオブジェクトの
実体のことです。その参照アドレスが変数に設定されていませんと
いう意味のエラーメッセージです。
このメッセージは良く出ますから覚えておいて下さい。
そのエラーを出しているコードで、オブジェクト変数が空っぽです。

dTbl の中身が Nothing(空っぽ)になっていると思います。
もともと、DataGridView.DataSource が空っぽということでしょう。
DataSource に何も設定していなくても、DataGridView1.Rows.Add
などで表示だけはできますから、そこから連携できていないという
話になります。

DataSource を利用した DataGridView の表示を勉強して、出直して
ください。
投稿者 るしぇ  (社会人) 投稿日時 2010/1/22 03:52:38
ちなみに DataSet をボクは「箱」と表現しましたが、DataTable を複数格納できる
入れ物という機能を持ったオブジェクトです。
DataTable に問題が無ければ、DataSet.Tables.Add(DataTable) で入れ物にテーブルを
入れるだけの事でした。
投稿者 grid  (社会人) 投稿日時 2010/1/22 04:48:00
いつもアドバイスありがとうございます。

ステップ実行を行ったところ、たしかに、
【Nothing(空っぽ)】の表示がでました。

おっしゃったとおりになっているかわかりませんが、下記のように修正しました。

    Private Sub hozonn_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles hozonn.Click

        Dim dSet As DataSet = New DataSet("リスト")
        Dim dTbl As DataTable
        Dim sWrite As System.IO.StreamWriter

    'DataGridViewからDataTableを取得 
        dTbl = DirectCast(auctionlist.DataSource, DataTable)

    'ここ↓を追加。 
    dSet.Tables.Add("商品リスト")

        '出力パスの指定 
        sWrite = New System.IO.StreamWriter("ファイルパス\list.xml" _
                                            , False, System.Text.Encoding.Default)
        
        dSet.WriteXml(sWrite)
        MessageBox.Show("SampleData.xmlに出力しました。""通知")


    End Sub


ステップ実行でまた【Nothing(空っぽ)】の表示がでました・・・。




ちなみに質問なのですが、
DataGridView(ここではauctionlist)の部分がおかしいから【Nothing(空っぽ)】の表示がでるのでしょうか?

現在のところテキストボックス3つに入力したデータを、登録ボタンを押すとDataGridViewのレコードに表示をさせていてます。
追加で2つめのレコードを増やすこともでき、DataGridView(ここではauctionlist)に表示されています。

DataGridViewからDataTableを取得できていないのでしょうか?



投稿者 るしぇ  (社会人) 投稿日時 2010/1/22 05:42:51
全くオブジェクトのイメージを把握できていらっしゃいませんから、出直してきてください。

>    dSet.Tables.Add("商品リスト")
話になりません。テーブルと何の関係もない文字列を追加して
何の意味が?

正解のコードは
    dSet.Tables.Add(dTbl) 
ですが、今回のエラーメッセージとは関係ありません。
修正したところで同じエラーになります。

>DataGridViewからDataTableを取得できていないのでしょうか?
DataGridView のデータが DataTable と連携されていません。
DataTable を使わない表示の手法を採用してしまっているのです。

もちろん、提示されているコードの部分の話ではありません。
DataGridView の表示の手法が違うと言っているのです。
投稿者 るきお  (社会人) 投稿日時 2010/1/22 22:03:16
こんにちは。

るしぇさんのおっしゃるようにDataGridViewに表示するために何をしているのかによって
今回の件で言うxmlファイルに保存する方法は変わります。

DataGridViewにDataTableをセットして表示している場合は、次のように簡単にxmlファイルを作成することができます。
  
Private Sub hozonn_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles hozonn.Click

    Dim dTbl As DataTable
    dTbl = DirectCast(auctionlist.DataSource, DataTable)
    dTbl.TableName = "商品リスト"
    dTbl.WriteXml("C:\temp\SampleData.xml")

    MessageBox.Show("SampleData.xmlに出力しました。""通知")

End Sub


これでうまくいかない場合は、
①DataGridViewにデータを表示するときにDataTableを使う手段に変更する
か、または
②別の方法でxmlに書き込む
ことになります。
②の場合の別の方法はDataGridViewにどうやってデータを表示しているか、その部分のプログラムを投稿されたほうがレスがつきやすいと思います。

また、使い慣れないクラスやメソッドを使用するときは、
勘と経験で適当にやってみるという手法を私はよく採りますが(私以外にも多くの人が、最初は勘と経験で適当にプログラムしてみるはずです。)、
それでうまくいかない場合は、マイクロソフトが公開しているヘルプの情報(MSDNライブラリ)を見るのが一般的と思います。
※でも、最近はその前にGoogleなどで検索する人の方が多い感じです。

MSDNライブラリ
http://msdn.microsoft.com/ja-jp/library/default.aspx

>dSet.Tables.Add("商品リスト")
に関しては、
http://msdn.microsoft.com/ja-jp/library/cxzk77ex.aspx
を見ると、新しいテーブルが作成されるという趣旨のことが書いてあるので、今回の件ではおかしいことがわかります。


ただし、MSDNライブラリを有効に活用にできるようになるにはある程度の経験が必要です。
ちょっと余談が長くなってしまいました。
投稿者 grid  (社会人) 投稿日時 2010/1/23 18:37:58
お二方のいう通りまだ全体的な把握ができていないようです・・・。

勘と経験で適当に1日かけてやったりもするんですが、なかかうまくできなくて・・・。



DataGridViewに表示するために何をしているのか?
ということで、事前の処理をのせます。

クリックイベントに次の内容を記述しています。
        Dim d1 As Byte =text1.Text
        Dim d2 As String = text2.Text
        Dim d3 As String = text3.Text

        Dim Row1() As String = {d1, d2, d3}

        'ColumnCountプロパティで列を追加する場合の例 
        Me.auctionlist.ColumnCount = 3
        Me.auctionlist.Columns(0).HeaderText = "管理番号"
        Me.auctionlist.Columns(1).HeaderText = "タイトル"
        Me.auctionlist.Columns(2).HeaderText = "商品説明"

        With Me.auctionlist.Rows
            .Add(Row1)
        End With


上記内容でDataGridViewに内容を表示しています。

その後るきおさんの記述した
Private Sub hozonn_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles hozonn.Click

    Dim dTbl As DataTable
    dTbl = DirectCast(auctionlist.DataSource, DataTable)
    dTbl.TableName = "商品リスト"
    dTbl.WriteXml("C:\temp\SampleData.xml")

    MessageBox.Show("SampleData.xmlに出力しました。""通知")

End Sub


を記述してあります。

一度DataGridViewに表示された内容をDataGridViewで直接変更することもあるので、今回の場合は②別の方法でxmlに書き込むを採用したしたほうがいいとおもっているのですが・・・。
検討違いでしょうか?

上記コードのどこを修正すればよいのでしょうか?

投稿者 (削除されました)  () 投稿日時 2010/1/23 20:45:24
(削除されました)
投稿者 (削除されました)  () 投稿日時 2010/1/23 20:47:08
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/1/23 21:04:31
今回は、データを保存するために、DataTable の WriteXml メソッドを使おうとしているのですよね?

ならばまずは、「DataTable を作り、列情報を定義し、さらにそこにデータを登録する方法」を覚える必要があるかと思います。
それを DataGridView と関連付けるのは、その後の話です。

DataTable を作る方法は幾つかあります。
(a) DataSet に対して、.Tables.Add のメソッドを呼び出して作成する方法。
(b) Dim dTbl As New DataTable() のように、New で生成する方法。
(c) デザイナで型付 DataSet を作っておき、それを利用する方法。
--
ここでは、(c) の方法を紹介しておきます。
実験用の新しいプロジェクトを作成してください。

まず、[プロジェクト]-[新しい項目の追加]メニューを辿り、そこから
「データセット」というアイコンを選択し、ソリューションに追加します。(DataSet1.xsd)

そうすると、データセット デザイナという画面に切り替わりますので、右クリックして
[追加]→[DataTable]を選択し、新規に DataTable を作ります。
作成されたテーブルの名前は、"商品リスト"などに変更しておきましょう。

さらに、その DataTable の灰色部分を右クリックし、[追加]-[列]で列情報を追加します。
この作業を 3 回繰り返し、テーブルに 3 つの列を追加します。
それぞれの列名は、管理番号/タイトル/商品名などと付ければ良いでしょう。


それができたら、今度はフォーム画面に戻ります。
フォームには、Button を数個と DataGridView を貼っておきます。

そして、コードとして
Private ds As New DataSet1()
Private Sub Form1_Load() Handles MyBase.Load
    DataGridView1.DataSource = ds.商品リスト
End Sub
と記述します。

New している箇所が、定義したテーブルを作成する箇所です。
これを DataSource に割り当てておくと、このテーブルと DataGridView が
関連付けられ、それぞれを編集した結果が連動するようになります。
DataGridView への列定義は、データテーブルの構造から自動的に読み込まれます。

一度、この状態で実行してみてください。
DataGridView にデータを書き込める状態になっているはずです。

そして、書き込まれたデータを保存する処理については、
Private Sub Button1_Click() Handles Button1.Click
    ds.商品リスト.WriteXml("C:\temp\SampleData.xml")
End Sub
これだけで OK です。
DataGridView に何行か書き込んでからボタンを押すと、ファイルが生成されるはずです。

ちなみにファイルの読み込みは、
Private Sub Button2_Click() Handles Button2.Click
    ds.商品リスト.ReadXml("C:\temp\SampleData.xml")
End Sub
です。


なお、データを DataGridView からユーザーに入力させるのではなく、
プログラムから制御したい場合には、
ds.商品リスト.Add商品リストRow(100, "チップスター""紀州の梅味")
などと記述すればOKです。

なお、DataTable の構造を事前に決定できない場合には、デザイナを使わずに
先の(a), (b) 案で DataTable を用意する事になります。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/1/23 21:21:52
さて、今度は DataGridView 側の話。

まず、DataGridView にデータを表示する場合、大きく分けて 3 種類のパターンがあります。
http://msdn.microsoft.com/ja-jp/library/cd28yf6d.aspx

(1) バインドモード(データバインド)
 データのかたまりを .DataSource に渡して、それを表示させる方法です。
 DataSet や DataTable などの表示に利用されます。
 扱いやすい上にもパフォーマンスも良いため、よく利用される方法です。

(2) 非バインド モード(アンバウンド)
 .DataSource を使わず、個々のセルに対してデータを割り当てていく手法です。
 .RowCount 等で行数を確保し、後から「DataGridView1(1,2).Value = 123」などと
 記述したり、あるいは .Rows.Add で行単位にデータを追加する事ができます。
 比較的少量のデータを表示する際に利用されます。

(3) 仮想モード(Virtual)
 描画すべきデータをイベントで渡す手法です。大量のデータがある場合に利用されますが、
 プログラムが複雑化するため、通常の用途で利用される事は稀です。
---

今回は、データを保存するために、DataTable の WriteXml メソッドを使おうと
しているのですから、適切なのは (1) の手法です。先に示したサンプルは、その一例です。


しかし grid さんの元のコードは、データの表示に (1) ではなく、(2) の手法を用いています。
この場合、DataSource は使っていない(空っぽ)の状態ですから、
dTbl = DirectCast(auctionlist.DataSource, DataTable)
と書いても、dTbl は空っぽのままというわけです。


もしも現状のように (2) の手法で DataGridView を使っていきたいのであれば、保存処理を
 (1) XML 保存用に、DataTable の変数を作成する。
 (2) DataGridView の内容を、作成した DataTable にコピーしていく。
 (3) DataTable を WriteXml メソッドで保存する。
という流れにする必要があります。


'DataTable の変数を作成する 
Dim dTbl As DataTable
dTbl = New DataTable("商品リスト")

'列定義を生成する 
dTbl.Columns.Add("管理番号"GetType(Byte))
dTbl.Columns.Add("タイトル")
dTbl.Columns.Add("商品説明")

'DataGridView の内容を、DataTable にコピーしていく 
For Each row As DataGridViewRow In DataGridView1.Rows
    If Not row.IsNewRow Then
        Dim d1 As Byte = row.Cells(0).Value
        Dim d2 As String = row.Cells(1).Value
        Dim d3 As String = row.Cells(2).Value
        dTbl.Rows.Add(d1, d2, d3)
    End If
Next

'DataTable を WriteXml メソッドで保存する 
dTbl.ReadXml("C:\temp\SampleData.xml")
投稿者 grid  (社会人) 投稿日時 2010/1/23 21:33:08
みなさんのおかげで無事やりたいことができました。

最後は魔界の仮面弁士さんのおっしゃった、
>(c) デザイナで型付 DataSet を作っておき、それを利用する方法。
で処理を進めることにしました。

できあがったコードは
    Private dSet As New DataSet1()

    Private Sub touroku_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles touroku.Click
        Dim d1 As Byte = kannrinotext.Text
        Dim d2 As String = syuppinntaitorutext.Text
        Dim d3 As String = setumeitext.Text

        dSet.商品リスト.Add商品リストRow(d1, d2, d3)

    End Sub

    Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click

        dSet.商品リスト.WriteXml("C:\data\auctionlist.xml")

        MessageBox.Show("auctionlist.xmlに出力しました。""通知")

    End Sub



です。

魔界の仮面弁士さんのおっしゃった、
>まず、[プロジェクト]-[新しい項目の追加]メニューを辿り、そこから
>「データセット」というアイコンを選択し、ソリューションに追加します。(DataSet1.xsd)

>そうすると、データセット デザイナという画面に切り替わりますので、右クリックして
>[追加]→[DataTable]を選択し、新規に DataTable を作ります。

という、事前にデータテーブルを作成しておく手法は初めて知りました。
感動です!

ありがとうございました。


それと、今回の場合(c)を使用しましたが、
>(a) DataSet に対して、.Tables.Add のメソッドを呼び出して作成する方法。
>(b) Dim dTbl As New DataTable() のように、New で生成する方法。
>(c) デザイナで型付 DataSet を作っておき、それを利用する方法。
と記載してありますが、それぞれのメリット、デメリットなどあるのでしょうか?
投稿者 るしぇ  (社会人) 投稿日時 2010/1/23 23:53:40
>という、事前にデータテーブルを作成しておく手法は初めて知りました。
ウィザードを使ってって言う意味なら分からないでもないけど、
DataGridView.DataSource は普通、事前に設定しますよ。DataGridView
がそもそも、データベースの検索結果(DataTable)を表示するような目的の
コントロールの流れを受け継いでいるから。WEB 上のサンプルもそっちの
パターンの方が多いです。調べれば分かる事だと思いますが。

全てのオブジェクト(Class)は New というキーワードでインスタンス(実体)を
生成してから使用し、使わなくなったら破棄します。
http://homepage1.nifty.com/CavalierLab/lab/vb/clsmdl/index.html

デザイナやウィザードを利用した場合、自動でコードが追加されますが、
そのコードでやってる事は同じです。

>(a) DataSet に対して、.Tables.Add のメソッドを呼び出して作成する方法。
本来、入れ物にテーブルを入れる命令です。つまり、既存のデータベース検索
処理で DataTable があったとします。新しくインスタンスを生成する必要は
無いって言うか、新しく作っちゃったらデータの入れ替えしないといけませんよね。
そのまま引数に渡せばいいのです。
じゃあ、逆にまだ検索処理をしてなかったら?
>>dSet.Tables.Add("商品リスト")
>に関しては、
>http://msdn.microsoft.com/ja-jp/library/cxzk77ex.aspx
>を見ると、新しいテーブルが作成されるという趣旨のことが書いてあるので、今回の件ではおかしいことがわかります。
新しくインスタンスを生成する必要がありますね。ついでに TableName も設定して
おけば便利かもしれません。つまり
        Dim dTbl As New DataTable()
        dTbl.TableName = "商品リスト"
        dSet.Tables.Add(dTbl)

が内部で実行されていると予想できるわけです。

ところで、DataSet も DataGridView.DataSource に設定できます。
何が違うのか?テーブルを複数格納できるという点です。
1つしかテーブルを扱わないのであれば過重包装かもしれませんね。
でもテーブルが複数あって、全部使うとか、一部使うとかしてたら?
手提げ袋にまとめて入れておいてくれた方が持ち運びに便利ですよね。

>(b) Dim dTbl As New DataTable() のように、New で生成する方法。
コレが一番基本。一番単純なインスタンスの生成。

>(c) デザイナで型付 DataSet を作っておき、それを利用する方法。
やってる事は基本的には同じ。型付なので、データ型がはっきりする
ところはいいけど、ボクは基本的にウィザードが全部嫌いです。
自動化するには処理の一般化・共通化が必要です。同じ手順を踏める
から自動化もできると。すると、必要無いありがた迷惑な設定も勝手に
設定してたり、内部に設定情報持ってたり。。。
。。。結局、全部共通して重いし。掲示板で説明するのも面倒だし。

まぁ、同じ人間の考えることだから常識で考えれば理解できるでしょう?
量産品は自動化できるけど、オーダーメイドが必要になる場面もあります。
ボクは基本オーダーメイドで作るから、量産品を作る手順はむしろ足枷に
なって嫌いなんです。
>メリット、デメリット
単純に処理が遅くなったり、誤動作したりという事ですか?常識で考えて
有り得ないでしょう。作成するものが同じなのに、処理方法を3つ用意
したりします?インターフェースだけ変えて内部で同じ処理に繋げる
でしょう?同じ人間ならそう考えるはずです。

ここで差が出てくるとしたら、構造的にまとめてしまっている場合。
例えばファイルを開いて書込み命令を1万回繰り返して閉じるのと、
ファイルパスを渡すと「開いて1行書いて閉じる」をまとめてしまって
いる場合。これを1万回繰り返せば無駄な「開く閉じる」処理をやる
から遅くなるとか。
もしくは.NET になってフレームワーク上に複数言語を乗せちゃったから、
つじつま合わせで無駄な処理が入っているとか。

無いとは言いません。というかあります。が、実際にコーディングして
問題が出てから調べたのでいいのでは?結局、単体・結合・総合テストで
網羅性を考えているでしょう?
まぁ、でも、ここがノウハウでしょうね。ご飯を食べるネタなので、
ボクはタダで出す事は極稀ですかねー。回答者によってはバンバン出す
人もいますね。