Build version | Dapfor libraries | Character set | MFC libraries | Runtime libraries |
Debug | static | Not set | static | /MTd |
Release | static | Not set | static | /MT |
Debug | static | Unicode | static | /MTd |
Release | static | Unicode | static | /MT |
Debug | static | Not set | dynamic | /MDd |
Release | static | Not set | dynamic | /MD |
Debug | static | Unicode | dynamic | /MDd |
Release | static | Unicode | dynamic | /MD |
Debug | dynamic | Not set | dynamic | /MDd |
Release | dynamic | Not set | dynamic | /MD |
Debug | dynamic | Unicode | dynamic | /MDd |
Release | dynamic | Unicode | dynamic | /MD |
7. To choose the right version of the GUI & Common libraries we have added Autolink.h files to each project. You can manually choose library version, but we recommend that you add these files to StdAfx.h of your project. In this case, GUI and Common are chosen automatically.
//StdAfx.h file #include <Dapfor/GUI/Autolink.h> #include <Dapfor/Common/Autolink.h>
8. Let's create the first C++ class, objects of which can be inserted into the grid. Read What is a data object? article to get more information. This class must be derived from Dapfor::Common::CDataObject. The base class adds only a possibility to use the objects of derived classes in the grid
class CTestClass : public Dapfor::Common::CDataObject { ...
Add a constructor, destructor and some variables and methods to our class.
... public: CTestClass(CString vName, long vLong, __int64 vInt64, double vDouble); ~CTestClass(); //Some functions. CString GetName() const; long GetSomeLong() const; __int64 GetSomeInt64() const; double GetSomeDouble() const; long GetSomeDate() const; private: //Some variables CString m_Name; long m_Long; __int64 m_Int64; double m_Double; long m_Date; ...
Now we will declare a very important thing - Field map. This is the heart of the Common library. It enables the grid to find and call functions of the class to get object values. The Fildmap contains a list of fields. Each field has a unique identifier, and may also contain: a name, Get-function, Set-function and a format to convert value returned by Get- function to a string and vice-versa. The same identifiers are used in columns of the grid. This means that objects of this class correspond to rows in the grid and methods of this class correspond to columns.
... DF_DECLARE_FIELD_MAP(); ...
Finally, we will declare identifiers for fields. Of course, you can freely use identifiers like 1, 2, 3... However, the best practice is to use the enumeration, so that the code becomes comprehensible and safe. In this case multiple verifications are done at compile time.
public: enum Fields { FidName = 1234, //You can use any values. (-1 & -2 have special meaning) FidSomeLong = 5000, //You don't have to follow the sequence... FidSomeInt64, FidSomeDouble, FidSomeDate, }; ...
Below you will find the full text of the TestClass.h file:
#pragma once #include <string> #include <Dapfor/Common/DataObject.h> class CTestClass : public Dapfor::Common::CDataObject { public: //The functions declared below can be called by the MFC grid. To call them we use a mapping table //(one per class, not per object). The Get- and Set- functions are grouped in properties. Each property //has a numeric identifier. The same numeric identifiers are used in columns of the grid. //You can freely use identifiers like 1, 2, 3... However, the best practice is to use the enumeration! In this case //many verifications are done at compile time, the code become more comprehensible and safe. enum Fields { FidName = 1234, //You can use any values. (-1 & -2 have special meaning) FidSomeLong = 5000, //You may not respect the sequence... FidSomeInt64, FidSomeDouble, FidSomeDate, }; public: CTestClass(CString vName, long vLong, __int64 vInt64, double vDouble); ~CTestClass(); //Some functions. The grid calls them through the mapping as declared below CString GetName() const; long GetSomeLong() const; __int64 GetSomeInt64() const; double GetSomeDouble() const; long GetSomeDate() const; private: //Some variables CString m_Name; long m_Long; __int64 m_Int64; double m_Double; private: //To make the functions visible by the grid, we declare the field map - the list of properties (Get- & Set functions) DF_DECLARE_FIELD_MAP(); };
Now let's implement our class.
//file TestClass.cpp #include "StdAfx.h" #include "TestClass.h" #include <Dapfor/Common/LongDateFormat.h> //Field map implementation. Here you declare the properties of the class that are accessible from the grid. //Each property has a unique identifier, and optionally may have a name, Get-function, Set-function and a format //to convert value, returned by the Get- function to string and vice-versa. DF_BEGIN_FIELD_MAP(CTestClass) DF_MFC_STRING_ID(FidName, _T("Name"), &CTestClass::GetName, 0, 0) DF_LONG_ID(FidSomeLong, _T("Long"), &CTestClass::GetSomeLong, 0, 0) DF_INT64_ID(FidSomeInt64, _T("Int64"), &CTestClass::GetSomeInt64, 0, 0) DF_DOUBLE_ID(FidSomeDouble, _T("Double"),&CTestClass::GetSomeDouble, 0, 0) //The last parameter in the field declaration - the custom format DF_LONG_ID(FidSomeDate, _T("Date"), &CTestClass::GetSomeDate, 0, new CLongDateFormat(CLongDateFormat::short_date)); DF_END_FIELD_MAP() //The constructor CTestClass::CTestClass(CString vName, long vLong, __int64 vInt64, double vDouble) : m_Name(vName) , m_Long(vLong) , m_Int64(vInt64) , m_Double(vDouble) , m_Date(CTime::GetCurrentTime().GetTime()) { } //The destructor CTestClass::~CTestClass() { } //Implementation of class methods CString CTestClass::GetName() const {return m_Name;} long CTestClass::GetSomeLong() const {return m_Long;} __int64 CTestClass::GetSomeInt64() const {return m_Int64;} double CTestClass::GetSomeDouble() const {return m_Double;} long CTestClass::GetSomeDate() const {return m_Date;}
We have created and implemented the class, objects of which can be inserted to the grid. It was easy, wasn't it? How does it work? If you see a definition of the CDataObject class, you may discover methods like long GetLong(FID fid) const, double GetDouble(FID fid) const, etc. FID is a numeric identifier, that you have defined in the FieldMap. Thanks to this mapping we can call functions indirectly by their identifiers:
//In this example both calls will give the same result CTestClass obj(_T("object 1"), 121, 1234567890, 1.32); long l1 = obj.GetSomeLong(); //Direct call long l2 = obj.GetLong(CTestClass.FidSomeLong); //Indirect call through the identifier //The following call returns the formatted string "121" that represents the long value 121 //Note that the second parameter is a format that may be specified in the column of the grid's header. //So, we can present the same object differently in different grids. CString s1 = obj.GetFormattedMfcString(CTestClass.FidSomeLong, 0);
The grid uses indirect callling to get values from an object.
Another important thing is that the last parameter in the field declaration
DF_LONG_ID(FidSomeLong, _T("Long"), &CTestClass::GetSomeLong, 0, 0)
9. To demonstrate a realistic use case, let's create a container of CTestClass objects. In the created SDI application we should have a document class CMyFirstApplicationDoc. Let's add the container to the document class:
// MyFirstApplicationDoc.h : interface of the CMyFirstApplicationDoc class #pragma once #include "TestClass.h" #include <vector> class CMyFirstApplicationDoc : public CDocument { public: //Declaration of the container. This container will keep pointers to the objects of CTestObject class typedef std::vector<CTestClass*> Container; public: CMyFirstApplicationDoc(); virtual ~CMyFirstApplicationDoc(); const Container& GetContainer() const; ... private: Container m_Container; ... };
Below you will find an implementation of the document in the MyFirstApplicationDoc.cpp file:
//Constructor CMyFirstApplicationDoc::CMyFirstApplicationDoc() { // Populate our container m_Container.push_back(new CTestClass(_T("object 1"), 121, 1234567890, 1.32)); m_Container.push_back(new CTestClass(_T("object 2"), 122, 1234567891, 1.33)); m_Container.push_back(new CTestClass(_T("object 3"), 123, 1234567892, 1.34)); m_Container.push_back(new CTestClass(_T("object 4"), 124, 1234567893, 1.35)); m_Container.push_back(new CTestClass(_T("object 5"), 125, 1234567894, 1.36)); m_Container.push_back(new CTestClass(_T("object 6"), 126, 1234567895, 1.37)); m_Container.push_back(new CTestClass(_T("object 7"), 127, 1234567896, 1.38)); m_Container.push_back(new CTestClass(_T("object 8"), 128, 1234567897, 1.39)); m_Container.push_back(new CTestClass(_T("object 9"), 129, 1234567898, 1.40)); m_Container.push_back(new CTestClass(_T("object 10"), 130, 1234567899, 1.41)); } //Destructor CMyFirstApplicationDoc::~CMyFirstApplicationDoc() { for(Container::iterator it = m_Container.begin(); it != m_Container.end(); ++it) { delete *it; } } //Field accessor const CMyFirstApplicationDoc::Container& CMyFirstApplicationDoc::GetContainer() const { return m_Container; }
10. Now we will initialize the grid. To add the grid to view you need to include the grid header to the MyFirstApplicationView.h file. Then add a member of the Dapfor::GUI::CGrid, and add OnCreate, OnSize and OnDestroy message handlers as shown below:
// MyFirstApplicationView.h #include <Dapfor/GUI/Grid.h> class CMyFirstApplicationView : public CView { ... protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnSize(UINT nType, int cx, int cy); ... private: Dapfor::GUI::CGrid m_Grid; ...
Below you will find an implementation of view and grid initialization
// MyFirstApplicationView.cpp : implementation of the CMyFirstApplicationView class ... #include "TestClass.h" //Messages mapping BEGIN_MESSAGE_MAP(CMyFirstApplicationView, CView) ... ON_WM_CREATE() ON_WM_DESTROY() ON_WM_SIZE() ... END_MESSAGE_MAP() ... int CMyFirstApplicationView::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. The 'true' value in the constructor indicates, that the header is owned by the grid and //the grid handles the life time of the header. This means that the grid destroys the header in its destructor. Dapfor::GUI::CHeader* header = new Dapfor::GUI::CHeader(true); //Add some columns. To show values, returned by functions, defined in CTestClass, //we will use identifiers, declared in this class. header->Add(new Dapfor::GUI::CColumn(CTestClass::FidName, _T("Name"), 70)); header->Add(new Dapfor::GUI::CColumn(CTestClass::FidSomeLong, _T("Long"), 60)); header->Add(new Dapfor::GUI::CColumn(CTestClass::FidSomeInt64, _T("Int64"), 100)); header->Add(new Dapfor::GUI::CColumn(CTestClass::FidSomeDouble, _T("Double"), 60)); header->Add(new Dapfor::GUI::CColumn(CTestClass::FidSomeDate, _T("Date"), 80)); //Set the header m_Grid.SetHeader(header); //Initialize background colors m_Grid.GetAppearance().SetBackEvenColor(RGB(255, 255, 255)); m_Grid.GetAppearance().SetBackOddColor(RGB(240, 240, 240)); //Add objects from the document to the grid... for(CMyFirstApplicationDoc::Container::const_iterator it = GetDocument()->GetContainer().begin(); it != GetDocument()->GetContainer().end(); ++it) { Dapfor::GUI::HITEM handle = m_Grid.Add(*it); } //Here you can set your sorting rules. //A small tweak: left-click the header to change sorting direction //Ctrl-click adds/changes multiple sorting //Shift-click cancels the sorting m_Grid.SetSort(0, Dapfor::GUI::CSortInfo(CTestClass::FidSomeLong, false)); //Sorting by the first column m_Grid.SetSort(1, Dapfor::GUI::CSortInfo(CTestClass::FidSomeInt64, true)); //This line adds multiple sorting by the second column //Select rows 3 and 4 m_Grid.Select(3, Dapfor::GUI::ScrollableContext, true); m_Grid.Select(4, Dapfor::GUI::ScrollableContext, true); //Select a row in the grid that corresponds to the sixth element in the container. //Note, that you don't need to find the row in the grid. //You can be sure that this is more efficient than linear search even in the grids with huge number of elements. m_Grid.Select(GetDocument()->GetContainer()[6], true); return 0; } void CMyFirstApplicationView::OnDestroy() { //Remove all values from the grid. Note, that the grid doesn't handle the lifetime of inserted objects and doesn't //destroy them. m_Grid.DeleteAll(); CView::OnDestroy(); } void CMyFirstApplicationView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); //Below we modify a size of the grid CRect rc; GetClientRect(rc); m_Grid.MoveWindow(rc); }
Please note that the grid provides a number of methods like Select(const Common::CDataObject* pDO, ...), where you can use directly your business object. You don't need your object placement anymore. The same approach is used for the columns. Instead of indexing the columns, you can use enumerations (like defined in the CTestObject). You are free forget that your object was inserted into the grid.
11. Now, we will show you how to make the grid editable. If we take a data object, it has only the Get- properties that return values by their identifiers. These values are shown in grid cells. Now, we provide a set of functions to modify data inside the data object.
class CTestClass : public Dapfor::Common::CDataObject { ... //Some set functions. The grid will call them while edit in place operations void SetSomeLong(long v); void SetSomeInt64(__int64 v); void SetSomeDouble(double v); void SetSomeDate(long v); ... };
Below you will find an implementation of these function. Note that there are declarations of Set- methods in the Field map and each Set-method of your objects sends notifications. This is a very important step. You can call the Set- methods of your objects anywhere in the application, and each grid (one or more), where the objects were inserted will automatically update, sort, filter and highlight the cells. You do not need to know, where your objects are placed.
DF_BEGIN_FIELD_MAP(CTestClass) DF_MFC_STRING_ID(FidName, _T("Name"), &CTestClass::GetName, 0, 0) DF_LONG_ID(FidSomeLong, _T("Long"), &CTestClass::GetSomeLong, &CTestClass::SetSomeLong, 0) DF_INT64_ID(FidSomeInt64, _T("Int64"), &CTestClass::GetSomeInt64, &CTestClass::SetSomeInt64, 0) DF_DOUBLE_ID(FidSomeDouble, _T("Double"), &CTestClass::GetSomeDouble, &CTestClass::SetSomeDouble, 0) DF_LONG_ID(FidSomeDate, _T("Date"), &CTestClass::GetSomeDate, &CTestClass::SetSomeDate, new CLongDateFormat(CLongDateFormat::short_date)); DF_END_FIELD_MAP() //Implementation of Set- methods void CTestClass::SetSomeLong(long v) { m_Long = v; //Send notification to the grid. Each grid automatically updates the cell, sorts and filters the row if needed. NotifyUpdate(FidSomeLong); } void CTestClass::SetSomeInt64(__int64 v) { m_Int64 = v; NotifyUpdate(FidSomeInt64); } void CTestClass::SetSomeDouble(double v) { m_Double = v; NotifyUpdate(FidSomeDouble); } void CTestClass::SetSomeDate(long v) { m_Date = v; NotifyUpdate(FidSomeDate); }
We have almost made the grid editable. To finish, we will declare the columns editable:
//file MyFirstApplicationView.cpp ... //The last parameter indicates that the column will be editable header->Add(new Dapfor::GUI::CColumn(CTestClass::FidName, _T("Name"), 70)); header->Add(new Dapfor::GUI::CColumn(CTestClass::FidSomeLong, _T("Long"), 60, Dapfor::GUI::CColumn::Auto, true)); header->Add(new Dapfor::GUI::CColumn(CTestClass::FidSomeInt64, _T("Int64"), 100, Dapfor::GUI::CColumn::Auto, true)); header->Add(new Dapfor::GUI::CColumn(CTestClass::FidSomeDouble, _T("Double"), 60, Dapfor::GUI::CColumn::Auto, true)); header->Add(new Dapfor::GUI::CColumn(CTestClass::FidSomeDate, _T("Date"), 80, Dapfor::GUI::CColumn::Auto, true)); ...
How does it work? When the user clicks on the grid, an appropriate MFC control is shown over the cell. Then the user can modify the text, select the desired item in the combobox, or date in the datetime picker. Then the grid tries to set the new value to the data object through the Set- property that we have implemented before. Note that all grids, where the data object was inserted are notified (the call of the NotifyUpdate() method) and automatically perform nested operations to update the visibility and position of this object.
Try to edit a cell in the sorted column in the demo application. The row will be resorted correctly
12. Compile and execute the application. Below you will find a snapshot of this application.
The sources of MyFirsrtApplication project are available here.
Read Drawing workflow and Custom drawing to implement the powerful features in your application.
Copyright Dapfor 2007-2009 | Generated on Wed Jul 7 03:24:43 2010 for MFCGrid by 1.5.5 |