|
The Model-View-Controller design pattern solves these problems by decoupling data access, business logic, and data presentation and user interaction.
Our library provides a thread-safe binding to the data model (C++ objects). Therefore the views have a direct communication with the business model. If the view changes (edit in place or other interactions), the field's value in the model is automatically updated. Conversely, if the field's value in the model changes, the view (grid) is automatically updated. The controller never intervenes. However, in our model the controller is intended to provide custom formatting/parsing values of the model and strings, displayed in grids rather than state management.
class CShare { public: CString GetName() const; double GetPrice() const; private: CString m_Name; double m_Price; };
2. Now, we will describe a mapping that implements data binding mechanism and permits to call Get- & Set- methods of C++ object by identifiers.
//file Share.h class CShare : public Dapfor::Common::CDataObject { ... DF_DECLARE_FIELD_MAP(); }; //file Share.cpp DF_BEGIN_FIELD_MAP(CShare) DF_MFC_STRING_ID(1, _T("Name"), &CShare::GetName, 0, 0) DF_DOUBLE_ID (2, _T("Price"), &CShare::GetPrice, 0, 0) DF_END_FIELD_MAP() CString CShare::GetName() const { return m_Name; } double CShare::GetPrice() const { return m_Price; }
3. Create columns with the declared above identifiers and add to the header
//file ViewDemo.h class CViewDemo : public CView { ... protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); ... private: Dapfor::GUI::CGrid m_Grid; }; //file ViewDemo.cpp int CViewDemo::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; //Initialize the grid m_Grid.Create(0, 0, WS_VISIBLE|WS_CHILD, CRect(), this, 1000); //Create a header. Dapfor::GUI::CHeader* header = new Dapfor::GUI::CHeader(true); //Add columns with the identifiers, described above. header->Add(new Dapfor::GUI::CColumn(1, _T("Name"), 70)); header->Add(new Dapfor::GUI::CColumn(2, _T("Price"), 60)); //Set the header m_Grid.SetHeader(header); ... }
4. Add some C++ data objects to the grid.
//Create our share object CShare* share = new CShare(...) //Add the object to the grid. m_Grid.Add(share); //If there are other grids, we can add the same share in the same way... //The appearance of the share in these grids can be quite different (columns, formats, colors etc.) grid2.Add(share); grid3.Add(share); ...
Now, each grid will handle the share's position according to the sorting, hierarchical and filtering rules.
5. Now, let's explain how to fire events to the grids. Our share has a field m_Price. Let's add a Set- function that changes this field. The share, being derived from the Dapfor::Common::CDataObject has an embedded thread-safe notification mechanism. To send a notification you can just call the NotifyUpdate() method with the field identifier.
//file Share.h class CShare : public Dapfor::Common::CDataObject { ... void SetPrice(double price); }; //file Share.cpp ... void CShare::SetPrice(double price) { m_Price = price; //Fire event. //Note that this event can be used not only by grids, but also by the model. NotifyUpdate(2); }
When the grid is notified, it automatically updates the text in cell, changes row position according to the sorting and filtering rules, updates the timestamp of the last update and automatically highlights the corresponding cell. Each grid does all these operations automatically in a thread-safe way without the deadlock risk.
6. Edit in place? It is very easy. We have nearly all we need. We should only add a SetPrice() method to the mapping and switch on edit in place in the column.
//file Share.cpp DF_BEGIN_FIELD_MAP(CShare) ... DF_DOUBLE_ID (2, _T("Price"), &CShare::GetPrice, &CShare::SetPrice, 0) DF_END_FIELD_MAP() //file ViewDemo.cpp int CViewDemo::OnCreate(LPCREATESTRUCT lpCreateStruct) { ... //Switch on edit in place for the column 'Price' header->Add(new Dapfor::GUI::CColumn(2, _T("Price"), 60, Dapfor::GUI::CColumn::Auto, true)); }
As you see, this powerful methodology separates the core business model functionality from the presentation and grealy simplifies the application.
7. Optimizations: Numeric values are not the best way to determine field identifiers. Instead, we adwise you to use enumerations. This permits to detect and fix potential errors at compile time! See the next example:
//file Share.h class CShare : public Dapfor::Common::CDataObject { public: enum Fields { FidName, FidPrice, }; ... }; //file Share.cpp DF_BEGIN_FIELD_MAP(CShare) DF_MFC_STRING_ID(FidName, _T("Name"), &CShare::GetName, 0, 0) DF_DOUBLE_ID (FidPrice, _T("Price"), &CShare::GetPrice, 0, 0) DF_END_FIELD_MAP() ... void CShare::SetPrice(double price) { m_Price = price; //Fire event. //Note that this event can be used not only by grids, but also by the model. NotifyUpdate(FidPrice); } //file ViewDemo.cpp int CViewDemo::OnCreate(LPCREATESTRUCT lpCreateStruct) { ... //Add columns with the identifiers, described above. header->Add(new Dapfor::GUI::CColumn(CShare::FidName, _T("Name"), 70)); header->Add(new Dapfor::GUI::CColumn(CShare::FidPrice, _T("Price"), 60, Dapfor::GUI::CColumn::Auto, true)); }
| Copyright Dapfor 2007-2009 | Generated on Sat Jan 30 15:01:23 2010 for MFCGrid by 1.5.5 |