Question

J'essaie d'implémenter une fonction de chargement / enregistrement pour une application Windows Forms.

J'ai les composants suivants:

  • Une vue arborescente
  • Quelques vues de liste
  • Quelques zones de texte
  • Un couple d'objets (qui contient une grande liste de dictionnaires)

Je veux mettre en place un moyen de sauvegarder tout cela dans un fichier et de le reprendre / le charger plus tard.

Quelle est la meilleure façon de faire cela?

Je pense que la sérialisation XML est la voie à suivre, mais je ne sais pas trop comment ni par où commencer. Ou faudra-t-il une solution vraiment complexe pour pouvoir le faire?

Était-ce utile?

La solution

Voici un exemple qui lie un objet et des ancêtres à l'interface utilisateur; l'utilisation de C # 3.0 ici est purement pour la brièveté - tout fonctionnerait aussi avec C # 2.0.

La plupart du code ici est la configuration du formulaire, et / ou traiter les notifications de changement de propriété - surtout, il n'y a pas de code dédié à la mise à jour l'interface utilisateur du modèle d'objet, ou le modèle d'objet de l'interface utilisateur.

Notez aussi que l'EDI peut faire beaucoup de code de liaison de données pour vous, simplement en déposant un BindingSource sur le former et mettre le DataSource à un type via la boîte de dialogue dans la grille des propriétés.

Notez qu'il n'est pas essentiel de fournir un changement de propriété notifications (les choses PropertyChanged) - cependant, la plupart des liaisons d’interface utilisateur bidirectionnelles fonctionneront beaucoup mieux si vous implémentez ceci. Non pas que PostSharp en ait façons intéressantes de le faire avec un code minimal.

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

Autres conseils

Idéalement, vous ne devriez pas conserver l'état de l'interface utilisateur; vous devriez persister dans l'état d'un modèle d'objet représentant vos données. À l'exception de TreeView , l'utilisation de la liaison de données pour lier un modèle d'objet à l'interface utilisateur est relativement simple. Cela peut être une approche basée sur DataTable , ou une hiérarchie de classes personnalisée (ma préférence).

Une fois que vous avez séparé les données de l'interface utilisateur, la sauvegarde des données est simple. Il existe de nombreux exemples pour XmlSerializer , etc.

Oui, vous devez absolument utiliser la sérialisation XML pour cela. Mais, comme l'a souligné Marc Gravell, vous devez avoir en premier lieu des objets contenant les données affichées par vos composants d'interface graphique. Ensuite, vous pouvez pratiquement faire la (dé) sérialisation automatique, avec un minimum de lignes de code.

il y a un problème avec l'exemple ci-dessus. considérez que votre application est éventuellement mise à jour. votre modèle d'objet peut changer radicalement et par conséquent, il ne peut pas être désérialisé. Vous pouvez faire certaines choses pour vous assurer qu'une désérialisation à partir de la version 1 de XML peut être désérialisée dans votre modèle d'objet dans la version 2, mais s'il existe la possibilité que vous puissiez avoir de gros changements structurels, la désérialisation de XML n'est pas . le chemin à parcourir.

si tel est le cas et que votre application est déployée sur les clients, je vous suggère fortement d'examiner de plus près votre logique de sauvegarde / chargement.

Sérialisation / désérialisation versionnalisée
sérialisez votre état d'objet sous la forme de:

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

donc vous avez maintenant la version du modèle objet qui a généré l'état enregistré. lors de votre désérialisation, vous pouvez prendre des mesures spéciales pour tenir compte de ce fait. Par exemple, écrivez la valeur Champ1 dans une liste d'un autre objet.

une autre approche serait:

Sérialisation et conversion converties avant la désérialisation
sérialisez votre état d'objet comme mentionné ci-dessus (avec un attribut de version).
lors de la désérialisation, examinez l'attribut de version s'il ne s'agit pas de la version que vous envisagez, convertissez l'état d'objet sérialisé avec xsl-scripts ou le code c # en votre version actuelle. vous pouvez enregistrer une liste de conversions xsl dans votre projet actuel

- conversions
    - v1-v2
    - v2-v3

si vous êtes actuellement dans la version 3 et que vous souhaitez charger votre fichier xml, consultez l’attribut version et exécutez tous les scripts xsl pour obtenir votre version actuelle (version 3). vous exécuterez donc xsl-script v1-v2 puis v2-v3.

dans ce cas, vous pouvez avoir des classes de sérialisation et de désérialisation normales qui ne doivent pas se soucier de la capacité de retour.

  

il est assez simple d’utiliser   liaison de données pour lier un modèle d'objet à   l'interface utilisateur.

Comment lier un objet avec un contrôle graphique sans stockage persistant? Si je le fais manuellement, cela signifie que je dois écrire une quantité de code ridicule pour chaque objet en mémoire. J'ai déjà une sorte de stockage de classe pour ces données, mais ce n'est pas un scénario de tri lié, c'est comme si vous lisiez cette écriture ici.

Suis-je censé écrire un chargeur qui charge un XML sérialisé et récupère l'objet, puis lit l'objet et remplit toute l'interface graphique? Évidemment, cela ressemble plus à un chargement manuel non contraignant. Est-ce que je manque quelque chose?

Voici un excellent article sur la manière de rendre votre classe ou structure sérialisable. Je créerais une classe qui vous permettra de stocker toutes les données que vous souhaitez. Rendre la classe sérialisable. Ainsi, en quelques lignes de code, vous pouvez enregistrer toutes vos données dans un fichier. Ensuite, avec seulement quelques lignes de code supplémentaires, vous pouvez récupérer les données du fichier.

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

Une alternative à la sérialisation de vos classes consiste à utiliser un jeu de données ADO.NET pour le stockage de données, doté de fonctionnalités intégrées permettant de conserver un fichier XML. Le code sera minimal et vous ne pourrez stocker que les données pertinentes dont vous avez besoin en concevant des tables adaptées au modèle de l'opération que vous effectuez. En outre, vous pourrez utiliser le même code si vous décidez de conserver ultérieurement l'état de l'interface utilisateur dans une base de données au lieu d'un fichier local. Vous n’auriez besoin que d’une autre fonction pour enregistrer le jeu de données.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top