我正在尝试为Windows窗体应用程序实现加载/保存功能。
我有以下组件:
我想实现一种将所有这些保存到文件中的方法,并在以后恢复/加载它。
最好的方法是什么?
我认为XML序列化是可行的方法,但我不太确定如何或从哪里开始。或者它需要一个非常复杂的解决方案才能做到这一点?
答案 0 :(得分:6)
理想情况下,您不应该持久保持UI状态;你应该坚持表示你的数据的某个对象模型的状态。除了TreeView
之外,使用数据绑定将对象模型绑定到UI是相当简单的。这可以是基于DataTable
的方法,也可以是自定义类层次结构(我的偏好)。
从UI分离数据后,保存数据非常简单。 XmlSerializer
等有很多例子。
答案 1 :(得分:6)
这是绑定对象和一些祖先的示例 到用户界面;这里使用C#3.0纯粹是为了简洁 - 一切都适用于C#2.0。
这里的大部分代码都是设置表单和/或 处理财产变更通知 - 重要的是,没有任何专门用于更新的代码 来自对象模型的UI,或来自的对象模型 用户界面。
另请注意,IDE可以执行大量数据绑定代码 对你来说,只需将一个BindingSource放到 将DataSource格式化并设置为类型 属性网格中的对话框。
请注意,提供属性更改并不重要 通知(PropertyChanged的东西) - 但是, 大多数双向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));
}
}
答案 2 :(得分:1)
是的,你肯定应该使用XML序列化。但正如Marc Gravell指出的那样,您必须首先拥有保存GUI组件所显示数据的对象。然后,您可以使用最少的代码行自动进行(反)序列化。
答案 3 :(得分:1)
上面的示例存在问题。 考虑到最终您的应用程序已更新。您的对象模型可能会发生巨大变化,因此无法进行反序列化。您可以采取一些措施来确保xml版本1的反序列化可以在版本2中反序列化到您的对象模型,但是如果您有可能进行大的结构更改,则xml反序列化不要走的路。
如果是这种情况并且您的应用程序已部署到客户,我强烈建议您进一步查看保存/加载逻辑。
版本化序列化/反序列化
以下列形式序列化您的对象状态:
<ObjectState version="1">
<Field1>value</Field1>
... etc ...
</ObjectState>
所以现在你有了生成保存状态的对象模型的版本。 在你的反序列化中,你可以采取特殊的测量来适应这个事实。例如,将Field1-Value写入某个其他对象的列表中。
另一种方法是:
反序列化前的版本化序列化和转换
如上所述序列化您的对象状态(使用版本属性)
当反序列化查看版本属性时,如果这不是您希望将序列化对象状态与xsl-scripts或c#代码转换为当前版本的版本。
您可以在当前项目中保存xsl转换列表
- conversions
- v1-v2
- v2-v3
如果您当前在版本3中并且想要加载xml文件,请查看版本属性并运行所有xsl脚本以获取当前版本(版本3)。所以你将运行xsl-script v1-v2,然后运行v2-v3。
在这种情况下,您可以拥有正常的序列化和反序列化类,这些类不必关心向后功能。
答案 4 :(得分:0)
使用起来相当简单 数据绑定将对象模型绑定到 用户界面。
如何在没有持久存储的情况下将对象与GUI控件绑定?如果我手动执行这意味着我必须为内存中的每个对象编写大量的代码。我已经为这些数据提供了某种类存储,但它不是绑定排序方案,就像在这里阅读这篇文章一样。
我是否应该编写一个加载序列化XML并获取对象然后读取对象并填满整个GUI的加载器?显然这更像是手动加载而不是绑定。我错过了什么吗?
答案 5 :(得分:0)
这是一篇关于如何使类或结构可序列化的精彩文章。我会创建一个允许您存储所需数据的类。使课程可以连续。这样,只需几行代码,您就可以将所有数据保存到文件中。然后只需几行代码就可以从文件中检索数据。
答案 6 :(得分:0)
序列化类的另一种方法是使用ADO.NET数据集进行数据存储,该数据存储具有内置的工具,可以持久保存到XML文件。代码将是最小的,您可以通过设计适合您正在执行的操作模型的表来仅存储所需的相关数据。此外,如果您决定稍后将UI状态持久保存到数据库而不是本地文件,则可以使用相同的代码。您只需要一个备用函数来保存数据集。