Как реализовать функцию сохранения / загрузки? - PullRequest
5 голосов
/ 07 декабря 2008

Я пытаюсь реализовать функцию загрузки / сохранения для приложения Windows Forms.

У меня есть следующие компоненты:

  • Вид дерева
  • Несколько просмотров списка
  • Пара текстовых полей
  • Пара объектов (которая содержит большой список словарей)

Я хочу реализовать способ сохранить все это в файл и позже возобновить / загрузить его.

Какой лучший способ сделать это?

Я думаю, что сериализация XML - это путь, но я не совсем уверена, как и с чего начать. Или для этого потребуется действительно сложное решение?

Ответы [ 7 ]

6 голосов
/ 08 декабря 2008

Вот пример, который связывает объект и некоторых предков для пользовательского интерфейса; использование C # 3.0 здесь просто для краткости - все будет работать и с C # 2.0.

Большая часть кода здесь настраивает форму и / или работа с уведомлениями об изменении собственности - важно, что нет никакого кода, посвященного обновлению пользовательский интерфейс из объектной модели или объектной модели из пользовательский интерфейс.

Обратите внимание, что IDE может выполнять много кода привязки данных для вас, просто поместив BindingSource на сформировать и установить DataSource для типа через диалоговое окно в сетке свойств.

Обратите внимание, что нет необходимости предоставлять изменение свойства уведомления (PropertyChanged материал) - однако, большинство двухсторонних привязок пользовательского интерфейса будут работать значительно лучше если вы реализуете это. Не то чтобы 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));
    }
}
6 голосов
/ 07 декабря 2008

В идеале, вы не должны сохранять состояние пользовательского интерфейса; Вы должны сохранять состояние некоторой объектной модели, представляющей ваши данные. За исключением TreeView, довольно просто использовать привязку данных для привязки объектной модели к пользовательскому интерфейсу. Это может быть подход, основанный на DataTable, или пользовательская иерархия классов (мои предпочтения).

После того как вы отделите данные от пользовательского интерфейса, сохранение данных становится простым. Есть множество примеров для XmlSerializer и т. Д.

1 голос
/ 25 декабря 2010

есть проблема с примером выше. Учтите, что со временем ваше приложение будет обновлено. Ваша объектная модель может резко измениться и, следовательно, не может быть десериализована. Есть некоторые вещи, которые вы могли бы сделать, чтобы десериализацию из xml версии 1 можно было десериализовать в вашу объектную модель в версии 2, но если есть вероятность, что вы можете иметь большие структурные изменения, десериализация xml будет , а не . путь.

Если это так, и ваше приложение развернуто для клиентов, я настоятельно рекомендую еще раз взглянуть на логику сохранения / загрузки.

Модифицированная сериализация / десериализация
сериализовать состояние вашего объекта в виде:

<ObjectState version="1">
    <Field1>value</Field1>
    ... etc ...
</ObjectState>

так что теперь у вас есть версия объектной модели, которая сгенерировала сохраненное состояние. в вашей десериализации вы можете провести специальные измерения, чтобы учесть этот факт. например, напишите Field1-Value в списке в другом объекте.

другой подход будет:

Версионная сериализация и преобразование перед десериализацией
сериализовать состояние вашего объекта, как указано выше (с атрибутом версии).
при десериализации посмотрите на атрибут version, если это не та версия, которую вы ожидаете, преобразуйте состояние сериализованного объекта с помощью xsl-scripts или c # code в вашу текущую версию. Вы можете сохранить список преобразований xsl в вашем текущем проекте

- conversions
    - v1-v2
    - v2-v3

Если вы в настоящее время находитесь в версии 3 и хотите загрузить свой XML-файл, посмотрите на атрибут version и запустите все сценарии xsl, чтобы получить текущую версию (версия 3). поэтому вы должны запустить xsl-script v1-v2, а затем v2-v3.

в этом случае вы можете иметь обычные классы сериализации и десериализации, которые не должны заботиться о возможности обратного.

1 голос
/ 07 декабря 2008

Да, для этого вам обязательно нужно использовать сериализацию XML. Но, как отметил Марк Гравелл, у вас должны быть объекты, которые содержат данные, отображаемые вашими компонентами GUI. Тогда вы можете практически сделать (де) сериализацию автоматической, с минимальным количеством строк кода.

0 голосов
/ 08 декабря 2008

Альтернативой сериализации ваших классов является использование набора данных ADO.NET для хранения данных, который имеет встроенные средства для сохранения в файле XML. Код будет минимальным, и вы можете хранить только необходимые данные, разработав таблицы, которые соответствуют модели выполняемой вами операции. Кроме того, вы сможете использовать тот же код, если позже решите сохранить состояние пользовательского интерфейса в базе данных вместо локального файла. Вам понадобится только альтернативная функция для сохранения набора данных.

0 голосов
/ 08 декабря 2008

Вот отличная статья о том, как сделать ваш класс или структуру сериализуемой. Я хотел бы создать класс, который позволит вам хранить все данные, которые вы хотите. Сделайте класс сериализуемым. Таким образом, всего за несколько строк кода вы можете сохранить все свои данные в файл. Затем с помощью всего лишь нескольких строк кода вы можете извлечь данные из файла.

http://www.codeproject.com/KB/cs/objserial.aspx

0 голосов
/ 08 декабря 2008

это довольно тривиально для использования привязка данных для привязки объектной модели к пользовательский интерфейс.

Как связать объект с элементом управления GUI без постоянного хранилища? Если я делаю это вручную, это означает, что я должен написать нелепое количество кода для каждого объекта в памяти. У меня уже есть какое-то хранилище классов для этих данных, но это не сценарий сортировки по связыванию, это как читать эту запись здесь.

Должен ли я написать загрузчик, который загружает сериализованный XML и получает объект, а затем читает объект и заполняет весь графический интерфейс? Очевидно, это больше похоже на ручную загрузку, а не на привязку. Я что-то упустил?

...