MODULE Birthdays; (* ========================================================================= Example GPCP .NET WinForms DataGrid Program Read a list of birthdays from a tab-separated text file and display it in a DataGridView. The fields, current age and date of next birthday, are read-only. They are automatically calculated from the date of birth. The display is sorted by 'Next Birthday, Last Name' so that, by default, the next occurring birthday is at the top of the list. Author : Chris Burrows Created: Jun 2007 (c) 2007-2008 CFB Software http://www.cfbsoftware.com/gpcp ========================================================================= *) IMPORT Cpm := "[System]System.ComponentModel", Data := "[System.Data]System.Data", Drw := "[System.Drawing]System.Drawing", Glb := "[mscorlib]System.Globalization", IO := "[mscorlib]System.IO", Sys := "[mscorlib]System", Txt := "[mscorlib]System.Text", Wfm := "[System.Windows.Forms]System.Windows.Forms", WinMain; CONST tab = 09X; colFirstName = 0; colLastName = 1; colDateOfBirth = 2; colCurrentAge = 3; colNextBirthday = 4; TYPE DataTable* = POINTER TO RECORD (Data.DataTable) modified: BOOLEAN END; MenuForm* = POINTER TO RECORD (Wfm.Form) components: Cpm.Container; dataGrid: Wfm.DataGridView; dataTable: DataTable; dataView: Data.DataView; fileName: Sys.String END; VAR frm: MenuForm; (* ==================================================================== *) PROCEDURE (frm: MenuForm) Dispose*(disposing: BOOLEAN); BEGIN IF disposing THEN IF frm.components # NIL THEN frm.components.Dispose() END; frm.Dispose^(disposing) END END Dispose; (* ==================================================================== *) PROCEDURE CurrentAge(birthdate: Sys.DateTime): INTEGER; VAR today, thisBirthday: Sys.DateTime; age, thisYear, birthYear: INTEGER; BEGIN today := Sys.DateTime.get_Now(); thisYear := today.get_Year(); birthYear := birthdate.get_Year(); age := thisYear - birthYear; thisBirthday := birthdate.AddYears(age); IF Sys.DateTime.Compare(today, thisBirthday) < 0 THEN DEC(age) END; RETURN age END CurrentAge; (* ==================================================================== *) PROCEDURE (dataTable: DataTable) ActionColumnChanged(sender: Sys.Object; e: Data.DataColumnChangeEventArgs), NEW; VAR birthdate, nextBirthday: Sys.DateTime; row: Data.DataRow; age: INTEGER; BEGIN IF e.get_Column().get_Ordinal() = colDateOfBirth THEN row := e.get_Row(); birthdate := Sys.Convert.ToDateTime(row.get_Item(colDateOfBirth)); age := CurrentAge(birthdate); row.set_Item(colCurrentAge, Sys.Convert.ToString(age)); nextBirthday := birthdate.AddYears(age + 1); row.set_Item(colNextBirthday, Sys.Convert.ToString(nextBirthday)) END END ActionColumnChanged; (* ==================================================================== *) PROCEDURE (dataTable: DataTable) ActionRowChanged(sender: Sys.Object; e: Data.DataRowChangeEventArgs), NEW; BEGIN dataTable.modified := TRUE END ActionRowChanged; (* ==================================================================== *) PROCEDURE (dataTable: DataTable) LoadFromFile(IN fileName: Sys.String), NEW; VAR sr: IO.StreamReader; line: Sys.String; row: Data.DataRow; delimiter: POINTER TO ARRAY OF CHAR; strings: POINTER TO ARRAY OF Sys.String; birthDate: Sys.DateTime; dateFormat: Sys.IFormatProvider; BEGIN NEW(delimiter, 1); delimiter[0] := tab; sr := IO.File.OpenText(fileName); dateFormat := Glb.DateTimeFormatInfo.get_InvariantInfo()(Sys.IFormatProvider); line := sr.ReadLine(); WHILE line # NIL DO strings := line.Split(delimiter); row := dataTable.NewRow(); row.set_Item(colFirstName, strings[colFirstName]); row.set_Item(colLastName, strings[colLastName]); (* Retrieve date from a portable invariant format *) birthDate:= Sys.DateTime.Parse(strings[colDateOfBirth], dateFormat); (* Convert to local date format *) row.set_Item(colDateOfBirth, birthDate.ToString()); dataTable.get_Rows().Add(row); line := sr.ReadLine() END; sr.Close END LoadFromFile; (* ==================================================================== *) PROCEDURE (dataTable: DataTable) SaveToFile(IN fileName: Sys.String), NEW; VAR sw: IO.StreamWriter; colNo, rowNo: INTEGER; rows: Data.DataRowCollection; row: Data.DataRow; colString: Sys.String; line: Txt.StringBuilder; birthDate: Sys.DateTime; dateFormat: Sys.IFormatProvider; BEGIN NEW(line); sw := IO.StreamWriter.init(fileName); rows := dataTable.get_Rows(); dateFormat := Glb.DateTimeFormatInfo.get_InvariantInfo()(Sys.IFormatProvider); FOR rowNo := 0 TO rows.get_Count() - 1 DO line.set_Length(0); row := rows.get_Item(rowNo); FOR colNo := colFirstName TO colDateOfBirth DO IF colNo > 0 THEN line := line.Append(tab) END; IF colNo = colDateOfBirth THEN (* Convert date from local format to a portable invariant date format for storage *) birthDate := Sys.DateTime.Parse(row.get_Item(colDateOfBirth).ToString()); colString := birthDate.ToString(dateFormat) ELSE colString := row.get_Item(colNo).ToString() END; line := line.Append(colString) END; sw.WriteLine(line.ToString()) END; sw.Close; dataTable.modified := FALSE END SaveToFile; (* ==================================================================== *) PROCEDURE (dataTable: DataTable) CreateColumn(name, type: Sys.String), NEW; VAR col: Data.DataColumn; BEGIN col := Data.DataColumn.init(name); col.set_DataType(Sys.Type.GetType(type)); dataTable.get_Columns().Add(col) END CreateColumn; (* ==================================================================== *) PROCEDURE (dataTable: DataTable) Create(), NEW; BEGIN dataTable.CreateColumn('First Name', 'System.String'); dataTable.CreateColumn('Last Name', 'System.String'); dataTable.CreateColumn('Date of Birth', 'System.DateTime'); dataTable.CreateColumn('Age', 'System.Int32'); dataTable.CreateColumn('Next Birthday', 'System.DateTime') END Create; (* ==================================================================== *) PROCEDURE (frm: MenuForm) OpenTable(fileName: Sys.String), NEW; VAR col: Wfm.DataGridViewColumn; BEGIN NEW(frm.dataTable); frm.dataTable.modified := FALSE; frm.fileName := fileName; frm.dataTable.set_TableName('Birthdays'); frm.dataTable.Create(); frm.dataView.set_Table(frm.dataTable); frm.dataGrid.set_DataSource(frm.dataView); frm.dataGrid.get_Columns().get_Item(colCurrentAge).set_ReadOnly(TRUE); frm.dataGrid.get_Columns().get_Item(colNextBirthday).set_ReadOnly(TRUE); REGISTER(frm.dataTable.ColumnChanged, frm.dataTable.ActionColumnChanged); frm.dataTable.LoadFromFile(frm.fileName); frm.dataView.set_Sort('Next Birthday, Last Name'); frm.dataTable.modified := FALSE; REGISTER(frm.dataTable.RowChanged, frm.dataTable.ActionRowChanged); REGISTER(frm.dataTable.RowDeleted, frm.dataTable.ActionRowChanged) END OpenTable; (* ==================================================================== *) PROCEDURE (frm: MenuForm) ActionOnClosing(sender: Sys.Object; e: Wfm.FormClosingEventArgs), NEW; VAR result: Wfm.DialogResult; BEGIN result := Wfm.DialogResult.No; IF frm.dataTable.modified THEN result := Wfm.MessageBox.Show('Data has been modified. Save it?', 'Warning', Wfm.MessageBoxButtons.YesNoCancel, Wfm.MessageBoxIcon.Exclamation) END; IF result = Wfm.DialogResult.Yes THEN frm.dataTable.SaveToFile(frm.fileName) END; e.set_Cancel(result = Wfm.DialogResult.Cancel) END ActionOnClosing; (* ==================================================================== *) PROCEDURE (frm: MenuForm) ActionOnLoad(sender: Sys.Object; e: Sys.EventArgs), NEW; BEGIN frm.OpenTable(frm.fileName) END ActionOnLoad; (* ==================================================================== *) PROCEDURE (frm: MenuForm) InitializeComponent(), NEW; CONST frmWidth = 600; frmHeight = 400; VAR gridHeight: INTEGER; BEGIN NEW(frm.components); NEW(frm.dataGrid); NEW(frm.dataView); frm.dataView.BeginInit(); frm.SuspendLayout(); frm.set_Text('Birthdays'); frm.set_AutoScaleBaseSize(Drw.Size.init(5, 13)); frm.set_ClientSize(Drw.Size.init(frmWidth, frmHeight)); frm.set_FormBorderStyle(Wfm.FormBorderStyle.Sizable); frm.dataGrid.set_Dock(Wfm.DockStyle.Fill); frm.get_Controls().Add(frm.dataGrid); REGISTER(frm.Load, frm.ActionOnLoad); REGISTER(frm.FormClosing, frm.ActionOnClosing); frm.ResumeLayout(FALSE); frm.dataView.EndInit() END InitializeComponent; (* ==================================================================== *) BEGIN NEW(frm); frm.fileName := 'birthdays.tsv'; frm.InitializeComponent(); (* XP Styles *) Wfm.Application.EnableVisualStyles(); Wfm.Application.Run(frm) END Birthdays.