Here is an example that links an object and some ancestors to a user interface; using C # 3.0 here is pure for brevity - everything will work with C # 2.0 as well.
Most of the code here sets up the form and / or regarding property change notifications - It is important to note that there is no code dedicated to updating the user interface from the object model or the object model from the user interface.
Note that the IDE can also execute a lot of data binding code for you by simply dropping the BindingSource onto the forms and setting the data source to type through a dialog in the property grid.
Note that there is no need to provide a change to the notification property (PropertyChanged material) - however, most two-way UI bindings will work much better if you implement this. Not that PostSharp has some interesting ways to do 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)); } }