保存/読み込み機能を実装する方法は何ですか?
質問
Windowsフォームアプリケーションのロード/保存機能を実装しようとしています。
次のコンポーネントがあります:
- ツリービュー
- リストビューのカップル
- いくつかのテキストボックス
- 2つのオブジェクト(大きな辞書リストを保持)
このすべてをファイルに保存し、後で再開/ロードする方法を実装したい。
これを行う最良の方法は何ですか?
XMLのシリアル化が進むべき道だと思いますが、どのように、どこから始めればよいのかよくわかりません。または、これを行うには本当に複雑なソリューションが必要ですか?
解決
これは、オブジェクトといくつかの祖先をバインドする例です UIに;ここでのC#3.0の使用は純粋に簡潔にするためです- すべてがC#2.0でも機能します。
ここでのコードの大部分はフォームを設定している、および/または プロパティ変更通知の処理- 重要なのは、更新専用のコードがないことです。 オブジェクトモデルのUI、またはオブジェクトモデルの UI。
また、IDEは多くのデータバインディングコードを実行できることに注意してください あなたのために、単にBindingSourceをドロップするだけで を介してDataSourceを型に設定して設定する プロパティグリッドのダイアログ。
プロパティの変更を提供することは必須ではないことに注意してください 通知(PropertyChangedもの)-ただし、 ほとんどの2方向UIバインディングはかなり良く機能します これを実装する場合。 PostSharpにいくつかがあるわけではありません 最小限のコードでこれを行う興味深い方法。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
static class Program { // formatted for vertical space
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Button load, save, newCust;
BindingSource source = new BindingSource { DataSource = typeof(Customer) };
XmlSerializer serializer = new XmlSerializer(typeof(Customer));
using (Form form = new Form {
DataBindings = {{"Text", source, "Name"}}, // show customer name as form title
Controls = {
new DataGridView { Dock = DockStyle.Fill, // grid of orders
DataSource = source, DataMember = "Orders"},
new TextBox { Dock = DockStyle.Top, ReadOnly = true, // readonly order ref
DataBindings = {{"Text", source, "Orders.OrderRef"}}},
new TextBox { Dock = DockStyle.Top, // editable customer name
DataBindings = {{"Text", source, "Name"}}},
(save = new Button { Dock = DockStyle.Bottom, Text = "save" }),
(load = new Button{ Dock = DockStyle.Bottom, Text = "load"}),
(newCust = new Button{ Dock = DockStyle.Bottom, Text = "new"}),
}
})
{
const string PATH = "customer.xml";
form.Load += delegate {
newCust.PerformClick(); // create new cust when loading form
load.Enabled = File.Exists(PATH);
};
save.Click += delegate {
using (var stream = File.Create(PATH)) {
serializer.Serialize(stream, source.DataSource);
}
load.Enabled = true;
};
load.Click += delegate {
using (var stream = File.OpenRead(PATH)) {
source.DataSource = serializer.Deserialize(stream);
}
};
newCust.Click += delegate {
source.DataSource = new Customer();
};
Application.Run(form);
}
}
}
[Serializable]
public sealed class Customer : NotifyBase {
private int customerId;
[DisplayName("Customer Number")]
public int CustomerId {
get { return customerId; }
set { SetField(ref customerId, value, "CustomerId"); }
}
private string name;
public string Name {
get { return name; }
set { SetField(ref name, value, "Name"); }
}
public List<Order> Orders { get; set; } // XmlSerializer demands setter
public Customer() {
Orders = new List<Order>();
}
}
[Serializable]
public sealed class Order : NotifyBase {
private int orderId;
[DisplayName("Order Number")]
public int OrderId {
get { return orderId; }
set { SetField(ref orderId, value, "OrderId"); }
}
private string orderRef;
[DisplayName("Reference")]
public string OrderRef {
get { return orderRef; }
set { SetField(ref orderRef, value, "OrderRef"); }
}
private decimal orderValue, carriageValue;
[DisplayName("Order Value")]
public decimal OrderValue {
get { return orderValue; }
set {
if (SetField(ref orderValue, value, "OrderValue")) {
OnPropertyChanged("TotalValue");
}
}
}
[DisplayName("Carriage Value")]
public decimal CarriageValue {
get { return carriageValue; }
set {
if (SetField(ref carriageValue, value, "CarriageValue")) {
OnPropertyChanged("TotalValue");
}
}
}
[DisplayName("Total Value")]
public decimal TotalValue { get { return OrderValue + CarriageValue; } }
}
[Serializable]
public class NotifyBase { // purely for convenience
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetField<T>(ref T field, T value, string propertyName) {
if (!EqualityComparer<T>.Default.Equals(field, value)) {
field = value;
OnPropertyChanged(propertyName);
return true;
}
return false;
}
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
他のヒント
理想的には、UIの状態を永続化すべきではありません。データを表すオブジェクトモデルの状態を永続化する必要があります。 TreeView
を除き、データバインディングを使用してオブジェクトモデルをUIに結び付けることは非常に簡単です。これは、DataTable
ベースのアプローチ、またはカスタムクラス階層(私の好み)のいずれかになります。
UIからデータを分離したら、データの保存は簡単です。 XmlSerializer
などの例はたくさんあります。
はい、これには間違いなくXMLシリアル化を使用する必要があります。しかし、Marc Gravellが指摘したように、最初にGUIコンポーネントによって表示されるデータを保持するオブジェクトが必要です。次に、最小限のコード行で、実質的に(デ)シリアル化を自動化できます。
上記のサンプルに問題があります。 最終的にアプリケーションが更新されることを考慮してください。オブジェクトモデルが大幅に変更される可能性があるため、逆シリアル化できません。 xmlバージョン1からの逆シリアル化をバージョン2のオブジェクトモデルに逆シリアル化できるようにするためにできることがいくつかありますが、大きな構造変更を行う可能性がある場合は、xml逆シリアル化は not 行く方法。
これが当てはまり、アプリケーションが顧客に展開されている場合、保存/読み込みロジックをさらに検討することを強くお勧めします。
バージョン管理されたシリアル化/逆シリアル化
オブジェクトの状態を次の形式でシリアル化します。
<ObjectState version="1">
<Field1>value</Field1>
... etc ...
</ObjectState>
これで、保存状態を生成したオブジェクトモデルのバージョンが得られました。 デシリアライゼーションでは、この事実に対応するために特別な測定を行うことができます。たとえば、他のオブジェクトのリストにField1-Valueを書き込みます。
別のアプローチは次のとおりです。
逆シリアル化前のバージョン管理されたシリアル化と変換
上記のようにオブジェクトの状態をシリアル化します(バージョン属性を使用)。
シリアル化されたオブジェクトの状態をxsl-scriptsまたはc#コードで現在のバージョンに変換することが期待されるバージョンでない場合、バージョン属性を逆シリアル化するときに見てください。
現在のプロジェクトにxsl変換のリストを保存できます
- conversions
- v1-v2
- v2-v3
現在バージョン3で、xmlファイルをロードする場合は、version属性を確認し、すべてのxslスクリプトを実行して現在のバージョン(バージョン3)を取得します。したがって、xsl-script v1-v2とその後にv2-v3を実行します。
この場合、後方機能を気にする必要のない通常のシリアル化および逆シリアル化クラスを使用できます。
使用するのはかなり簡単です オブジェクトモデルを結び付けるデータバインディング UI。
永続ストレージなしでオブジェクトをGUIコントロールに関連付けるにはどうすればよいですか?手動で行う場合、メモリ内のすべてのオブジェクトに対してとんでもない量のコードを記述する必要があります。このデータ用のクラスストレージは既にありますが、バインドソートのシナリオではありません。こちらの書き込みを読んでいるようなものです。
シリアル化されたXMLをロードしてオブジェクトを取得するローダーを作成し、オブジェクトを読み取ってGUI全体を埋めるのですか?明らかに、これはバインディングではなく手動ロードに似ています。何か不足していますか?
クラスまたは構造体をシリアル化可能にする方法に関する素晴らしい記事があります。必要なすべてのデータを格納できるクラスを作成します。クラスをシリアル化可能にします。このようにして、ほんの数行のコードですべてのデータをファイルに保存できます。その後、わずか数行のコードでファイルからデータを取得できます。
クラスをシリアル化する代わりに、XMLファイルに永続化する機能が組み込まれているADO.NETデータセットをデータストレージに使用することもできます。コードは最小限であり、実行する操作のモデルに適合するテーブルを設計することにより、必要な関連データのみを保存できます。さらに、後でローカルファイルではなくデータベースにUI状態を保持することにした場合、同じコードを使用できます。データセットを保存するには、代替機能のみが必要です。