by WebSurfer
2019年4月26日 12:14
XML ファイルからデータを取得して Windows Forms アプリの DataGridView に表示し、それをユーザーが編集して結果を XML ファイルに書き戻すサンプルを書きます。
ASP.NET Web Forms アプリの場合は先の記事「XML ファイルの更新操作」に書きましたのでそちらを見てください。
アプリの基本的な構成は、DataGridView ⇔ BindingSource ⇔ DataSet ⇔ XML ファイルとしています。
DataSet.ReadXml メソッドで XML ファイルからデータを DataSet に読み込み、BindingSource 経由で DataGridView に表示。ユーザーが DataGridView に表示されたデータを編集後(編集結果は DataSet に反映されます)、ボタンクリックで DataSet.WriteXml メソッドにより編集結果を XML ファイルに書き戻すという操作を行います。
Windows Forms アプリの場合、ASP.NET Web Froms アプリとは違って、DataSet や DataGridView などすべてのインスタンスをユーザーの PC メモリ上に保持できますので、上記の操作が可能になります。
コードは、表示・編集・更新を行うためのごく基本的な部分だけですが、以下の通りです。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form6 : Form
{
private DataGridView dataGridView1;
private BindingSource bindingSource1;
private DataSet dataset1;
private string dir;
private string file;
public Form6()
{
InitializeComponent();
}
private void Form6_Load(object sender, EventArgs e)
{
// BindingSource をデザイナで追加すると、以下のコードは .Designer.cs
// に定義されるが、この記事のようにした場合は自力で書かないとダメ
this.components = new System.ComponentModel.Container();
this.bindingSource1 = new BindingSource(this.components);
// DataGridView と DataSet はいずれも IDisposable を継承している
// ので.Designer.cs で定義済みの Dispose(bool) メソッドで開放する
// ため以下のようにした ・・・が、ホントに必要か?
// 初期化する前に components.Add しても無効のようなので注意
this.dataGridView1 = new DataGridView();
this.dataset1 = new DataSet();
this.components.Add(this.dataGridView1);
this.components.Add(this.dataset1);
this.dataGridView1.Dock = DockStyle.Fill;
this.dataGridView1.DataSource = this.bindingSource1;
this.Controls.Add(this.dataGridView1);
this.dir = @"XML ファイルのあるフォルダ";
this.file = "XML ファイル";
this.dataset1.ReadXml(dir + file);
this.bindingSource1.DataSource = dataset1.Tables[0];
}
private void button1_Click(object sender, EventArgs e)
{
this.Validate();
this.bindingSource1.EndEdit();
this.dataset1.WriteXml(dir + file,
XmlWriteMode.WriteSchema);
this.dataset1.AcceptChanges();
}
private void button2_Click(object sender, EventArgs e)
{
this.bindingSource1.RemoveCurrent();
}
}
}
XML ファイルはスキーマ付きにしています。その結果、DataTable の各列の DataType はスキーマに応じて型が設定され、例えば bool 型の場合はチェックボックスが表示されます。
DataSet.WriteXml で DELETE 操作を行うためには DataTable の当該行の DataRow.RowState プロパティを DataRowSate.Deleted に設定する必要があります。それを BindingSource.RemoveCurrent メソッドで行っています。
by WebSurfer
2019年1月20日 12:37
Visual Basic .NET (VB.NET) のメソッドの引数の値渡し (ByVal) / 参照渡し (ByRef) と、変数の値型 / 参照型をそれらの引数に渡す場合の違いについて、このように考えると理解しやすいと思っていることを書きます。
ちなみに、C# では ByVal に相当するキーワードがなくデフォルトで値渡しになるだけで、考え方は同じです。(ByRef に相当する ref というキーワードはあります)
まず、メソッドの引数に付与する ByVal, ByRef の違いですが、メソッドの呼び出し元から引数に値を渡す時、以下のようしていると考えれば良いと思います。
-
ByVal: 別の入れ物に中身をコピーして渡す
-
ByRef: 入れ物を中身ごと渡す
上記で「入れ物」というのは、例えば、Dim X As Integer で宣言された変数 X で、「中身」というのは X = 10 のようにして変数 X に代入された値 10 のことです。
次に、変数の値型・参照型ですが、クラスや配列は参照型、Integer のような構造体は値型になります。詳しくは、3-2 値型と参照型を見てください。
メソッドに ByVal, ByRef どちらで渡そうと、変数の参照型が値型に変わったり、値型が参照型に変わることはありません。
という訳で、以下の 4 つのケースに分けて考えると良いと思います。
(1) 値型を ByVal で渡す
呼び出し元の入れ物の中身を、メソッド側で用意した別の入れ物に、コピーして渡す。
呼び出し元とメソッドで入れ物が異なるので、メソッド側で入れ物の中身をどのように操作しようと呼び出し元の変数には何の影響もない。
(2) 値型を ByRef で渡す
呼び出し元の入れ物とメソッドに渡された入れ物は同じなので、メソッド側で中身を変更すれば呼び出し側でも中身が変更される。
(3) 参照型を ByVal で渡す
参照型なので渡すのはオブジェクト(インスタンス)を指すアドレス情報。ByVal なのでメソッド側で用意した別の入れ物にアドレス情報をコピーして渡している。
つまり、呼び出し元の変数とメソッド側の変数の中身は同じアドレス情報即ち単一のオブジェクトを指している。
なので、メソッド側で変数が指すオブジェクトを変更すると、呼び出し元の変数が指すオブジェクトでも同じ結果となる。
(4) 参照型を ByRef で渡す
基本は上記 (3) と同じ。
ただし、呼び出し側の入れ物とメソッドに渡された入れ物は同じなので、メソッド側で新しくオブジェクト(インスタンス)を生成してそれのアドレス情報を入れ物に代入すると、呼び出し元の入れ物の中身が指すオブジェクトも、メソッド側で生成した新しいオブジェクトになる。
by WebSurfer
2018年4月9日 14:33
Visual Stidio のデータソース構成ウィザードを使って型付 DataSet を生成するとき、その名前を DataSet とすると、System.Data.DataSet と名前が衝突して問題を起こすので要注意という話を書きます。
上の画像は、ウィザードで型付 DataSet(.xsd ファイル)を生成しようとしているところですが、ファイル名がデフォルトで DataSet.xsd となるのが諸悪の根源(?)です。
デフォルトの名前が DataSet.xsd のなるのは、自分が調べた限りでは、Visual Studio Cummunity 2015 で、ASP.NET Web Forms アプリケーションを Web サイトプロジェクトとして作り、その App_Code フォルダに .xsd ファイルを生成する場合だけでした。(それ以外ではデフォルトの名前は DataSet1.xsd となります)
なので余計に気づき難いと思います。もちろんコンパイルエラーは出ませんし。
具体的にどういうことになるかと言うと、以下の通りです。
ファイル名を DataSet.xsd とすると、それから生成される型付 DataSet の名前も以下の通り DataSet となります。
/// <summary>
///Represents a strongly typed in-memory cache of data.
///</summary>
[global::System.Serializable()]
// ・・・中略・・・
public partial class DataSet : global::System.Data.DataSet {
private ProductsDataTable tableProducts;
// ・・・中略・・・
}
そうなると、単に DataSet と宣言しただけでは型付 DataSet に定義された DataSet と見なされます。using 句に System.Data を設定しても同じです。
下の画像を見てください。上の型付 DataSet のコードの summary に設定された "Represents a strongly typed in-memory cache of data." がインテリセンスで表示されています。
この状態で System.Data.DataSet を使うには、上の画像の一番下の行に書いたようにしなければなりません。
自分のケースでは、上の ds を ds.ReadXml(xmlTextReader); というように使ったのですが、ds にデータを取得できない(エラーも出ない)というトラブルにハマって 1 ~ 2 時間悩みました。
今時こんなトラブルにハマるのは自分だけかもしれませんが、一応備忘録として残しておくことにします。(笑)