Consider the rates of return application shown in Figure F.1, an application used to calculate both average and annualized rates of return. Figure F.1. The Rates of Return Application
Each row represents a single period of return that's encapsulated by the PeriodReturn type: // PeriodReturn.cs class PeriodReturn { string period; decimal returnRate; decimal principal; public PeriodReturn() {} public PeriodReturn( string period, decimal returnRate, decimal principal) { this.period = period; this.returnRate = returnRate; this.principal = principal; } public string Period { get { return this.period; } set { this.period = value; } } public decimal ReturnRate { get { return this.returnRate; } set { this.returnRate = value; } } public decimal Principal { get { return this.principal; } set { this.principal = value; } } } The following code binds the DataGridView to a list of PeriodReturn objects and detects changes to the data source list in order to recalculate the rates of return:[1]
// RatesOfReturnForm.Designer.cs partial class RatesOfReturnForm { ... void InitializeComponent() { ... this.PeriodReturnBindingSource = new BindingSource(this.components); ... // PeriodReturnBindingSource this.PeriodReturnBindingSource.DataSource = typeof(SDIRatesOfReturn.PeriodReturn); this.PeriodReturnBindingSource.ListChanged += this.PeriodReturnBindingSource_ListChanged; ... // dataGridView this.dataGridView.DataSource = this.PeriodReturnBindingSource; ... } ... BindingSource PeriodReturnBindingSource; DataGridView dataGridView; } To complete our simple app, we prepopulate the data source with an initial row and handle the data source's ListChanged event to implement the average and annual rates of return calculations: // RatesOfReturnForm.cs partial class RatesOfReturnForm : Form { ... void RatesOfReturnForm_Load(object sender, System.EventArgs e) { // Add starting principal this.PeriodReturnBindingSource.List.Add( new PeriodReturn("start", 0M, 1000M)); } void PeriodReturnBindingSource_ListChanged( object sender, ListChangedEventArgs e) { // Calculate average and annual returns ... } } Thanks to data binding, the development experience was quite enjoyable, until I realized that I needed to save our newly entered rates of return data to disk for later use. Windows Forms and VS05 provide all kinds of support for easily writing data-bound applications, but neither provides any real support for the staple of MFC programmers everywhere: document-based applications.[2]
Oh, it's easy enough to lay out the File menu and to show the file dialogs. It's even easy to dump the contents of the data source to the disk using the run-time serialization stack in .NET:[3]
// RatesOfReturnForm.cs ... using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; using System.Runtime.Serialization.Formatters.Binary; [Serializable] class PeriodReturn {...} partial class RatesOfReturnForm : Form { ... void saveToolStripMenuItem_Click(object sender, EventArgs e) { if( this.saveFileDialog.ShowDialog(this) != DialogResult.OK ) { return; } string filename = this.saveFileDialog.FileName; using( Stream stream = new FileStream(filename, FileMode.Create, FileAccess.Write) ) { // Serialize object in binary format IFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, this.periodReturns); } } ... } However, document-based applications require a lot more than just showing a file dialog and dumping an object's contents into a file. To satisfy a Windows user's basic expectations, both SDI and MDI applications are required to support a specific set of document-related features. A minimal document-based application needs to support the following document management behavior:
For completeness, it should also support the following features:
Although Windows Forms provides no implementation of these document-related features, we of the ex-MFC brethren (and sistren) shouldn't let that stop us. |