Pregunta

Estoy intentando implementar una función Cargar / Guardar para una aplicación de formularios Windows Forms.

Tengo los siguientes componentes:

  • Una vista de árbol
  • Un par de vistas de lista
  • Un par de cuadros de texto
  • Un par de objetos (que contiene una gran lista de diccionarios)

Quiero implementar una forma de guardar todo esto en un archivo y reanudarlo / cargarlo más adelante.

¿Cuál es la mejor manera de hacer esto?

Creo que la serialización XML es el camino a seguir, pero no estoy muy seguro de cómo o dónde comenzar. ¿O requerirá una solución realmente compleja para poder hacer esto?

¿Fue útil?

Solución

Aquí hay un ejemplo que une un objeto y algunos antepasados a la interfaz de usuario; El uso de C # 3.0 aquí es puramente breve. todo funcionaría también con C # 2.0.

La mayor parte del código aquí está configurando el formulario, y / o lidiar con notificaciones de cambio de propiedad - importante, no hay ningún código dedicado a la actualización la interfaz de usuario del modelo de objetos, o el modelo de objetos de la interfaz de usuario.

Tenga en cuenta también que el IDE puede hacer mucho del código de enlace de datos para usted, simplemente colocando un BindingSource en el formulario y establecer el DataSource a un tipo a través de el cuadro de diálogo en la cuadrícula de propiedades.

Tenga en cuenta que no es esencial proporcionar un cambio de propiedad notificaciones (el elemento PropertyChanged); sin embargo, la mayoría de los enlaces de UI bidireccionales funcionarán considerablemente mejor si implementa esto. No es que PostSharp tenga algo de formas interesantes de hacer esto con un código mínimo.

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

Otros consejos

Idealmente, no debería persistir el estado de la interfaz de usuario; debe persistir el estado de algún modelo de objeto que represente sus datos. Con la excepción de TreeView , es bastante trivial usar el enlace de datos para vincular un modelo de objeto a la interfaz de usuario. Esto podría ser un enfoque basado en DataTable o una jerarquía de clases personalizada (mi preferencia).

Una vez que haya separado los datos de la interfaz de usuario, guardar los datos es simple. Hay muchos ejemplos para XmlSerializer etc.

Sí, definitivamente deberías usar la serialización XML para esto. Pero como señaló Marc Gravell, primero debe tener objetos que contengan los datos que muestran los componentes de su GUI. Entonces prácticamente puede hacer que la (des) serialización sea automática, con un mínimo de líneas de código.

hay un problema con el ejemplo anterior. considere que eventualmente su aplicación se actualiza. su modelo de objeto podría cambiar drásticamente y, por lo tanto, no podría deserializarse. hay algunas cosas que puede hacer para asegurarse de que una deserialización de xml versión 1 se pueda deserializar a su modelo de objetos en la versión 2, pero si existe la posibilidad de que pueda tener grandes cambios estructurales, la deserialización xml es no el camino a seguir.

si este es el caso y su aplicación está implementada para los clientes, le recomendaría que revise su lógica de guardar / cargar.

Serialización / deserialización versionada
serializar el estado de su objeto en forma de:

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

así que ahora tiene la versión del modelo de objeto que generó el estado guardado. en su deserialización puede tomar medidas especiales para acomodar este hecho. por ejemplo, escriba el Field1-Value en una lista en algún otro objeto.

otro enfoque sería:

Serialización y conversión versionadas antes de la deserialización
serializar el estado de su objeto como se mencionó anteriormente (con un atributo de versión).
al deserializar, mire el atributo de versión si esta no es la versión que espera convertir el estado del objeto serializado con xsl-scripts o código c # a su versión actual. puede guardar una lista de conversiones xsl en su proyecto actual

- conversions
    - v1-v2
    - v2-v3

si actualmente está en la versión 3 y desea cargar su archivo xml, mire el atributo de versión y ejecute todos los scripts xsl para obtener su versión actual (versión 3). entonces ejecutarías xsl-script v1-v2 y luego v2-v3.

en este caso, puede tener clases de serialización y deserialización normales que no deben preocuparse por la capacidad hacia atrás.

  

es bastante trivial de usar   enlace de datos para vincular un modelo de objeto a   la interfaz de usuario.

¿Cómo puedo vincular un objeto con un control GUI sin un almacenamiento persistente? Si lo hago manualmente, eso significa que tengo que escribir una cantidad ridícula de código para cada objeto en la memoria. Ya tengo algún tipo de almacenamiento de clase para estos datos, pero no es un escenario de clasificación vinculante, es como leer esto, escribir aquí.

¿Se supone que debo escribir un cargador que cargue XML serializado y obtenga el objeto y luego lea el objeto y complete toda la GUI? Obviamente esto más como carga manual no vinculante. ¿Me estoy perdiendo algo?

Aquí hay un gran artículo sobre cómo hacer que una clase o estructura sea serializable. Crearía una clase que le permitirá almacenar todos los datos que desee. Hacer la clase serialable. De esta manera, en solo unas pocas líneas de código puede guardar todos sus datos en un archivo. Luego, con unas pocas líneas de código más, puede recuperar los datos del archivo.

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

Una alternativa a la serialización de sus clases es utilizar un conjunto de datos ADO.NET para el almacenamiento de datos que ha incorporado funciones para persistir en un archivo XML. El código será mínimo y puede almacenar solo los datos relevantes que necesita diseñando tablas que se ajusten al modelo de la operación que está realizando. Además, podrá usar el mismo código si luego decide mantener el estado de la interfaz de usuario en una base de datos en lugar de un archivo local. Solo necesitaría tener una función alternativa para guardar el conjunto de datos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top