views:

1081

answers:

6

I'm trying to implement a Load / Save function for a winforms applications.

I've got following components :

  • A Tree View
  • Couple of List Views
  • Couple of Textboxes
  • Couple of objects (which holds a big dictionarylist)

I want to implement a way to save all of this into a file, and resume/load it later on.

What's the best way to do this? I think XML Serialization is the way to go, but I'm not quite sure how, or where to start. Or shall I implement a really complex solution to be able to do this?

+5  A: 

Ideally, you shouldn't be persisting the UI state; you should be persisting the state of some object model representing your data. With the exception of TreeView, it is fairly trivial to use data-binding to tie an object model to the UI. This could be either a DataTable-based approach, or a custom class hierarchy (my preference).

Once you have separated data from UI, saving data is simple. There are plenty of examples for XmlSerializer etc.

Marc Gravell
I'm not quite sure how to do that, I never used data-binding between controls and the classes (I only use it for persistent storage, settings etc.). I'll look into it.
dr. evil
Could you give me or point to an example to this sort binding between objects and controls?
dr. evil
+1  A: 

Yes, you should definitely use XML serialization for this. But as Marc Gravell noted, you must have objects that hold the data displayed by your GUI components first. Then you can practically make (de)serialization automatic, with minimum lines of code.

hmcclungiii
A: 

it is fairly trivial to use data-binding to tie an object model to the UI.

How can I tie an object with a GUI control without a persistent storage? If I do it manually that means I have to write ridiculous amount of code for every single object in memory. I already have some sort of class storage for this data but it's not bind sort scenario, it's like read this write here.

Am I supposed to write a loader which loads serialized XML and get the object and then read the object and fill up the whole GUI? Obviously this more like manual loading not binding. Am I missing something ?

dr. evil
I'll put an example together on the train; will post back in a couple of hours. Sorry for delay: needed sleep ;-p
Marc Gravell
thanks for the example, it looks great. I'll dig it. Also quite curious about your opinion on using Postsharp for this.
dr. evil
A: 

Here is a great article on how to make your a class or struct serializable. I would create a class that will allow you to store all the data you wish. Make the class serialable. This way in just a few lines of code you can save all your data to a file. Then with just a few more lines of code you can retrieve the data from the file.

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

Bobby Cannon
A: 

An alternative to serializing your classes is to use the an ADO.Net Dataset for data storage which has built in facilities for persisting to an XML file. The code will be minimal and you can store only the relevant data you need by designing tables that fit the model of the operation you are performing. Additionally, you will be able to use the same code if you decide to later persist the UI state to a database instead of local file. You would only need to have an alternate function to save the Dataset.

Eric
Thanks for the idea however unless I absolutely need it, I keep everything in the memory (performance issues), however I think ADO.NET got some sort of support of structures on the memory.
dr. evil
+4  A: 

Here's an example that binds an object and some ancestors to the UI; the use of C# 3.0 here is purely for brevity - everything would work with C# 2.0 too.

Most of the code here is setting up the form, and/or dealing with property-change notifications - importantly, there isn't any code devoted to updating the UI from the object model, or the object model from the UI.

Note also the IDE can do a lot of the data-binding code for you, simply by dropping a BindingSource onto the form and setting the DataSource to a type via the dialog in the property grid.

Note that it isn't essential to provide property change notifications (the PropertyChanged stuff) - however, most 2-way UI binding will work considerably better if you do implement this. Not that PostSharp has some interesting ways of doing this with minimal code.

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));
    }
}
Marc Gravell
thanks for the code, looks like this answers my quesiton, I'll try to implement such a solution and see if I can success. I've seen postsharp, but I'm not quite sure if it's a good way to go or not.
dr. evil
TreeViews are a pain, note
Marc Gravell