Introduction
There are tons of articles around in the web dealing with the implementation of the Model View Controller (MVC) pattern. Nevertheless, I had a hard time finding one usable as a kind of blueprint for a little software project of mine - a Windows Forms application with database access and data table display using the .NET DataGridView. Finally I found Matthew Cochran's article:
Introduction to Model View Control (MVC) Pattern using C#
which helped me a lot in getting an MVC up and running.
Still, I always thought that it would have been useful for me to have something closer to real life - and involving a .NET DataGridView. This is what I want to provide in this blog, even if it shows work in progress.
Technical Framework
System: WinXP SP3
.NET version: 4.0
IDE: Visual Studio C# 2010 Express
DB: SQL Server Express 2008 Express
The UI
Here is what I wanted: a simple UI which enables the user to edit data stored in a database table. The user should be able to view the current data available in the DB, edit values of existing DB table entries, add lines to the data, delete lines and save the data. In the end I want to have a user interface to maintain a couple of database tables. The individual tables are accessible via a dropdown menu in a main window. Each user request from the main window invokes a form for editing data stored in one DB table:
The window called by the user command looks like this:
The mouse points to what will become the save button.
So far I have implemented only data retreval from the DB, changing data in the UI and saving this data back to the DB. The "delete line" and "add line" features are still missing, but I think the existing functionality is enough to show the principles.
The Model
The model is responsible for any DB interaction and data manipluation. Following Matthew's article I created an interface for the model:
public interface IDBTableEditorModel
#region Methods void addLineToTable(); void addObserverView(IDBTableEditorView inTeamTypesView); void deleteLineFromTable(); void notifyObserverView(); DataTable readDataFromDB(); void writeDataToDB(DataTable in_DataTable); #endregion
}
|
Note: Don't get irritated by the order of the methods - I just like sorting them alphabetically...
As one might guess by the interface name, I want to use this model in the future to handle DB table data independent of the concrete DB table. This is similar to Matthew's IVehicleModel together with the implementation in class Automobile.
The interface is then implemented in an abstract class which serves as a blueprint for the table dependent implementation and which implements the MVC-relevant logic:
public abstract class classDBTableEditorModel : IDBTableEditorModel
{ #region Attributes IDBTableEditorView theTeamTypeView; string viewType; #endregion public classDBTableEditorModel(string inViewType) { this.viewType = inViewType; }
public void addObserverView(IDBTableEditorView inTeamTypeView) { theTeamTypeView = inTeamTypeView; } public void notifyObserverView() { theTeamTypeView.updateUserInterface(this); } public void deleteLineFromTable() { } public virtual void writeDataToDB(DataTable inDataTable) { } public virtual DataTable readDataFromDB() { DataTable lt_DataTable = new DataTable(); //Add code for concrete data retreval here in concrete class return lt_DataTable; } } |
Note: those methods that require table-specific logic have the attribute "virtual".
In this specific example I'm dealing with a DB table called "TeamTypes" and which has 3 columns (TeamTypeID (char(3)), TeamTypeDesc (varchar(100)), TeamTypeNumber (bigint)). Thus I have to create a specific table-dependent implementation of my virtual base class called "classTeamTypesModel", in this class I have to override the two "virtual" methods:
class classTeamTypesModel : classDBTableEditorModel { public classTeamTypesModel(string teamTypeName):base(teamTypeName) { }
public override DataTable readDataFromDB() { DataTable lt_TeamTypes = new DataTable(); //Read data from DB via connector try { lt_TeamTypes = classSqlServerConnector.Instance.TeamTypeReadAll; } catch { }
return lt_TeamTypes; } public override void writeDataToDB(DataTable inDataTable) { classSqlServerConnector.Instance.TeamTypeUpdateRecords(inDataTable); } }
|
Note: the classSqlServerConnector is not part of the MVC, but hosts all the functionality to read/write data from/to the DB and will not be discussed here.
The Controller
Following Matthew's article I created an interface for the controller as well:
public interface IDBTableEditorController { DataTable onView_Load(); void onButtonSave_Click(DataTable inDataTable); void onButtonDeleteLine_Click(); void onButtonAddLine_Click(); void onCellData_Changed(); void setView(IDBTableEditorView inTeamTypesView); void setModel(IDBTableEditorModel inTeamTypesModel); }
|
The corresponding implementation of this interface looks like this:
public class classDBTableEditorController : IDBTableEditorController { #region Attributes IDBTableEditorModel theTeamTypesModel; IDBTableEditorView theTeamTypesView; #endregion
public classDBTableEditorController(IDBTableEditorModel inTeamTypesModel, IDBTableEditorView inTeamTypesView) { this.theTeamTypesModel = inTeamTypesModel; this.theTeamTypesView = inTeamTypesView; }
public classDBTableEditorController() { }
public void setModel(IDBTableEditorModel inTeamTypesModel) { this.theTeamTypesModel = inTeamTypesModel; } public void setView(IDBTableEditorView inTeamTypesView) { this.theTeamTypesView = inTeamTypesView; }
public DataTable onView_Load() { return theTeamTypesModel.readDataFromDB(); }
public void onButtonSave_Click(DataTable inDataTable) { if (theTeamTypesModel != null) { theTeamTypesModel.writeDataToDB(inDataTable); } else { throw new Exception("Error in initializing model to controller: TeamTypes"); } }
public void onButtonAddLine_Click() { }
public void onButtonDeleteLine_Click() { }
public void onCellData_Changed() { } }
|
The View
For the view on ehas to implement the logic in the class code coming with the UI elements. Still, I implemented additionally a view interface:
public interface IDBTableEditorView
{ void addObserver(IDBTableEditorController theController); void updateUserInterface(IDBTableEditorModel inTeamTypeModel); }
|
The corresponding view implementation looks like this:
public partial class formTeamTypes : Form, IDBTableEditorView { private IDBTableEditorController theTeamTypesController = new classDBTableEditorController(); private IDBTableEditorModel theTeamTypeModel = new classTeamTypesModel("Test");
public formTeamTypes() { InitializeComponent(); this.initialize(theTeamTypesController, theTeamTypeModel); }
public void initialize(IDBTableEditorController inTeamTypesController, IDBTableEditorModel inTeamTypesModel) { if (theTeamTypeModel != null) { } theTeamTypeModel = inTeamTypesModel; theTeamTypesController = inTeamTypesController; theTeamTypesController.setModel(theTeamTypeModel); theTeamTypesController.setView(this); theTeamTypeModel.addObserverView(this); } public void addObserver(IDBTableEditorController inTeamTypesController) {
this.theTeamTypesController = inTeamTypesController; }
public void updateUserInterface(IDBTableEditorModel inTeamTypeModel) { this.dataGridTeamTypes.Update(); } private void formTeamTypes_Load(object sender, EventArgs e) { //When Form is called, the data for Team Types are read from DB dataGridTeamTypes.DataSource = theTeamTypesController.onView_Load(); dataGridTeamTypes.Columns[0].HeaderText = "Type ID"; dataGridTeamTypes.Columns[1].HeaderText = "Type Description"; dataGridTeamTypes.Columns[2].Visible = false; //Add a fourth virtual column for a marker flag to mark changed rows dataGridTeamTypes.Columns.Add("ChangedFlag", "Changed Flag"); dataGridTeamTypes.Columns[3].Visible = false; } private void buttonSave_Click(object sender, EventArgs e) { DataTable lt_RowsForUpdate = new DataTable(); DataRow ls_DataRow; //Build internal table with rows for update lt_RowsForUpdate.Columns.Add("TeamTypeID"); lt_RowsForUpdate.Columns.Add("TeamTypeDesc");
lt_RowsForUpdate.Columns.Add("TeamTypeNumber"); lt_RowsForUpdate.Columns.Add("Changed");
//Check for Changes and write to DB for (int i = 0; i < dataGridTeamTypes.Rows.Count; i++) { if (dataGridTeamTypes.Rows[i].Cells[3].Value != null && dataGridTeamTypes.Rows[i].Cells[3].Value.ToString() == "C") { ls_DataRow = lt_RowsForUpdate.NewRow(); ls_DataRow[0] = dataGridTeamTypes.Rows[i].Cells[0].Value; ls_DataRow[1] = dataGridTeamTypes.Rows[i].Cells[1].Value; ls_DataRow[2] = dataGridTeamTypes.Rows[i].Cells[2].Value; lt_RowsForUpdate.Rows.Add(ls_DataRow); } } theTeamTypesController.onButtonSave_Click(lt_RowsForUpdate); } private void dataGridTeamTypes_CellValueChanged(object sender, DataGridViewCellEventArgs e) { //Mark rows which have been changed if (e.RowIndex >= 0) { dataGridTeamTypes.Rows[e.RowIndex].Cells[3].Value = "C"; } } } |
Note: marking the changed rows with a "C" might be a bit clumsy, but I didn't find a better way. Maybe you have a better one - I'd be glad to get to know about it.
Calling the MVC from the Application
The whole MVC cluster is called by the standard invocation of the form from wherever (here from the event menu item XYZ_clicked:
private void defineTeamTypes_Click(object sender, EventArgs e) { formTeamTypes theTeamTypesForm = new formTeamTypes(); theTeamTypesForm.Show(); }
|
Conclusion
I hope this real-life example adds a bit to Matthew's article and shows how things can be realized when working with a database.
Enjoy!