Pergunta

Eu estou tentando implementar um Carregar / Salvar função para uma aplicação Windows Forms.

Eu tenho os seguintes componentes:

  • A exibição de árvore
  • Um par de exibições de lista
  • Um par de caixas de texto
  • Um par de objetos (que detém uma grande dictionarylist)

Eu quero implementar uma maneira de salvar tudo isso em um arquivo e retomar / carregá-lo mais tarde.

Qual é a melhor maneira de fazer isso?

Eu acho que a serialização XML é o caminho a percorrer, mas eu não tenho certeza de como ou por onde começar. Ou ela vai exigir uma solução muito complexa para ser capaz de fazer isso?

Foi útil?

Solução

Aqui está um exemplo que liga um objeto e alguns antepassados para a interface do usuário; o uso de C # 3.0 aqui é puramente para a brevidade - tudo iria trabalhar com C # 2.0 também.

A maioria do código aqui é a criação do formulário, e / ou lidar com notificações de propriedade de mudança - importante, não há qualquer código dedicado à atualização a interface do usuário a partir do modelo de objeto, ou o modelo de objeto de a interface do usuário.

Note também o IDE pode fazer um monte de código de ligação de dados para você, simplesmente soltando um BindingSource para o formar e definir a fonte de dados para um tipo através o diálogo na grade de propriedade.

Note que não é essencial para fornecer a mudança de propriedade notificações (o material PropertyChanged) - no entanto, mais de 2 vias UI ligação funcionará consideravelmente melhor se você implementar isso. Não que PostSharp tem algum maneiras interessantes de fazer isso com o mínimo de código.

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));
    }
}

Outras dicas

O ideal é que você não deve ser persistindo o estado UI; você deve ser persistente o estado de alguns modelo de objeto que representa seus dados. Com a exceção de TreeView, é bastante trivial para usar de ligação de dados para amarrar um modelo de objeto para a interface do usuário. Esta poderia ser uma abordagem baseada em DataTable, ou uma hierarquia de classe personalizada (minha preferência).

Depois de ter separado os dados de interface do usuário, salvando dados é simples. Há uma abundância de exemplos para XmlSerializer etc.

Sim, você definitivamente deve usar serialização XML para isso. Mas, como Marc Gravell observou, você deve ter objetos que mantêm os dados exibidos por seus componentes GUI em primeiro lugar. Depois, você pode praticamente fazer (de) serialização automática, com linhas mínimas de código.

existe um problema com o exemplo acima. considerar que, eventualmente, a sua aplicação é atualizada. seu modelo de objeto pode mudar drasticamente e, portanto, não poderia se desserializado. há algumas coisas que você pode fazer para garantir que a desserialização de XML versão 1 pode ser desserializado para o seu modelo de objeto na versão 2, mas se houver a possibilidade de que você pode ter grande desserialização mudanças estruturais xml é não o caminho a percorrer.

Se este for o caso e seu aplicativo é implantado para os clientes gostaria de sugerir fortemente para dar uma olhada mais a sua lógica save / load.

Versionized serialização / Deserialization
serializar seu estado objeto na forma de:

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

Então, agora você tem a versão do modelo de objeto que gerou o estado salvo. em sua desserialização você pode tomar medidas especiais para acomodar para este fato. por exemplo gravação do Field1-valor em uma lista em algum outro objeto.

Outra abordagem seria:

Versionized serialização e conversão antes Deserialization
serializar seu estado objeto como mencionado acima (com um atributo de versão).
ao desserializar olhar para o atributo de versão, se esta não é a versão que você espera converter o estado do objeto serializado com XSL-scripts ou código c # para sua versão atual. você pode salvar uma lista de conversões XSL no seu projeto atual

- conversions
    - v1-v2
    - v2-v3

Se você está atualmente na versão 3 e quiser carregar o seu olhar arquivo xml no atributo de versão e executar todos os scripts XSL para obter a sua versão atual (versão 3). assim você iria correr xsl-script v1-v2 e depois v2-v3.

Neste caso, você pode ter de serialização e desserialização aulas normais que não deve se preocupam com capacidade para trás.

é bastante trivial de usar dados de ligao para amarrar um modelo objecto de a interface do usuário.

Como posso amarrar um objeto com um controle GUI sem um armazenamento persistente? Se eu fazê-lo manualmente Isso significa que eu tenho que escrever quantidade absurda de código para cada objeto na memória. Eu já tenho algum tipo de armazenamento de classe para esses dados, mas não é cenário tipo bind, é como ler este write aqui.

Eu deveria escrever um carregador que carrega serializado XML e obter o objeto e, em seguida, ler o objeto e encher toda a GUI? Obviamente esta mais parecido com o carregamento manual não obrigatório. Estou faltando alguma coisa?

Aqui está um ótimo artigo sobre como fazer a sua classe um ou serializável struct. Gostaria de criar uma classe que lhe permitirá armazenar todos os dados que você deseja. Faça o serialable classe. Desta forma, em apenas algumas linhas de código que você pode salvar todos os seus dados em um arquivo. Em seguida, com apenas mais algumas linhas de código que você pode recuperar os dados do arquivo.

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

Uma alternativa para a serialização de suas classes é usar a um ADO.NET DataSet para armazenamento de dados, que foi construído em instalações para persistir para um arquivo XML. O código será mínima, e você pode armazenar apenas os dados relevantes que você precisa através da concepção de mesas que se encaixam no modelo de operação que está a realizar. Além disso, você será capaz de usar o mesmo código, se você decidir persistir mais tarde o estado de interface do usuário para um banco de dados em vez de um arquivo local. Você só precisa ter uma função alternativa para salvar o conjunto de dados.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top