Domanda

Sto cercando di implementare una funzione Load / Save per un'applicazione Windows Form.

Ho i seguenti componenti:

  • Una vista ad albero
  • Un paio di visualizzazioni elenco
  • Un paio di caselle di testo
  • Un paio di oggetti (che contiene una grande lista di dizionari)

Voglio implementare un modo per salvare tutto questo in un file e riprenderlo / caricarlo in seguito.

Qual è il modo migliore per farlo?

Penso che la serializzazione XML sia la strada da percorrere, ma non sono sicuro di come o da dove iniziare. O richiederà una soluzione davvero complessa per poterlo fare?

È stato utile?

Soluzione

Ecco un esempio che lega un oggetto e alcuni antenati all'interfaccia utente; l'uso di C # 3.0 qui è puramente per brevità - tutto funzionerebbe anche con C # 2.0.

La maggior parte del codice qui sta impostando il modulo e / o gestire le notifiche di cambio di proprietà - soprattutto, non esiste alcun codice dedicato all'aggiornamento l'interfaccia utente dal modello a oggetti o il modello a oggetti da l'interfaccia utente.

Nota anche che l'IDE può fare molto del codice di associazione dei dati per te, semplicemente facendo cadere una BindingSource sul file modulo e impostare l'origine dati su un tipo tramite la finestra di dialogo nella griglia delle proprietà.

Nota che non è essenziale fornire un cambio di proprietà notifiche (roba PropertyChanged) - tuttavia, la maggior parte dell'associazione dell'interfaccia utente a 2 vie funzionerà notevolmente meglio se lo implementate. Non che PostSharp ne abbia modi interessanti per farlo con un codice minimo.

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

Altri suggerimenti

Idealmente, non dovresti mantenere lo stato dell'interfaccia utente; dovresti mantenere lo stato di alcuni modelli di oggetti che rappresentano i tuoi dati. Con l'eccezione di TreeView , è abbastanza banale utilizzare l'associazione dei dati per associare un modello a oggetti all'interfaccia utente. Potrebbe essere un approccio basato su DataTable o una gerarchia di classi personalizzata (la mia preferenza).

Dopo aver separato i dati dall'interfaccia utente, il salvataggio dei dati è semplice. Ci sono molti esempi di XmlSerializer ecc.

Sì, dovresti assolutamente usare la serializzazione XML per questo. Ma come ha notato Marc Gravell, è necessario disporre di oggetti che contengono prima i dati visualizzati dai componenti della GUI. Quindi puoi praticamente rendere (de) serializzazione automatica, con righe di codice minime.

c'è un problema con l'esempio sopra. considera che alla fine l'applicazione verrà aggiornata. il tuo modello a oggetti potrebbe cambiare drasticamente e quindi non potrebbe essere deserializzato. ci sono alcune cose che potresti fare per assicurarti che una deserializzazione da XML versione 1 possa essere deserializzata al tuo modello di oggetto nella versione 2 ma se c'è la possibilità che tu possa avere grandi cambiamenti strutturali la deserializzazione XML è non la strada da percorrere.

se questo è il caso e la tua applicazione viene distribuita ai clienti, ti consiglio vivamente di dare un'occhiata alla tua logica di salvataggio / caricamento.

Serializzazione / deserializzazione versione
serializza lo stato del tuo oggetto sotto forma di:

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

quindi ora hai la versione del modello a oggetti che ha generato lo stato salvato. nella tua deserializzazione puoi prendere misure speciali per tener conto di questo fatto. ad esempio, scrivi il Field1-Value in un elenco in qualche altro oggetto.

un altro approccio sarebbe:

Serializzazione e conversione versione prima della deserializzazione
serializza lo stato dell'oggetto come indicato sopra (con un attributo della versione).
durante la deserializzazione, osservare l'attributo version se questa non è la versione prevista, convertire lo stato dell'oggetto serializzato con xsl-script o codice c # nella versione corrente. puoi salvare un elenco di conversioni xsl nel tuo progetto corrente

- conversions
    - v1-v2
    - v2-v3

se sei attualmente nella versione 3 e vuoi caricare il tuo file xml, guarda l'attributo version ed esegui tutti gli script xsl per ottenere la tua versione corrente (versione 3). quindi avresti eseguito xsl-script v1-v2 e successivamente v2-v3.

in questo caso è possibile avere normali classi di serializzazione e deserializzazione a cui non deve interessare la possibilità di tornare indietro.

  

è abbastanza banale da usare   associazione dati a cui legare un modello a oggetti   l'interfaccia utente.

Come posso legare un oggetto con un controllo GUI senza una memoria permanente? Se lo faccio manualmente significa che devo scrivere una quantità ridicola di codice per ogni singolo oggetto in memoria. Ho già una sorta di archiviazione di classe per questi dati, ma non è uno scenario di ordinamento vincolante, è come leggere questa scrittura qui.

Devo scrivere un caricatore che carica XML serializzato e ottiene l'oggetto, quindi legge l'oggetto e riempie l'intera GUI? Ovviamente questo è più simile al caricamento manuale non vincolante. Mi sto perdendo qualcosa?

Ecco un ottimo articolo su come rendere la tua classe o struttura serializzabile. Vorrei creare una classe che ti consentirà di memorizzare tutti i dati desiderati. Rendi serialabile la classe. In questo modo in poche righe di codice puoi salvare tutti i tuoi dati in un file. Quindi con solo poche righe di codice puoi recuperare i dati dal file.

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

Un'alternativa alla serializzazione delle classi consiste nell'utilizzare un set di dati ADO.NET per l'archiviazione dei dati che ha funzionalità integrate per la persistenza in un file XML. Il codice sarà minimo e sarà possibile memorizzare solo i dati rilevanti necessari progettando tabelle che si adattino al modello dell'operazione che si sta eseguendo. Inoltre, sarai in grado di utilizzare lo stesso codice se decidi di mantenere in seguito lo stato dell'interfaccia utente in un database anziché in un file locale. Dovresti solo avere una funzione alternativa per salvare il set di dati.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top