Download source files - 131 Kb
This paper is based on a previous work by Chris Maunder (copyright � 1998-1999) and is copyright � 1999-2000 Mario Zucca.
Foreword by Chris Maunder
"I first posted my Grid control back in february of '98. At the time I needed a grid for a project I was working on, but could not afford to buy the commercial versions that were on offer at the time. So, using Joe Willcoxson's grid as a start, I proceeded to write my own, and two years later I'm still writing it! One of the biggest requests I've had is to wrap the class in a COM wrapper, or provide an ATL version. I've never had the time nor the energy for such a venture, so when Mario sent me his first prototype I was stunned that someone had spent such a large amount of time understanding the monstrosity I had created, but also extremely glad that someone had the time and energy I was lacking to take the grid to an audience I'd previously been unable to reach. Over the past few months I've seen Mario overcome many hurdles and I think his first release version is a commendable effort.
Thanks Mario!"
After spending a lot of days studying the COM architecture and Activex controls, I started to develop several components using Visual Basic 5 and ATL version 2 .
After one year of COM work I thought it was about time to embrace a nice challenge: writing an Activex control so complex to force me to study the most difficult details of COM and Windows. In this state of mind, I found Chris' grid and I started to work on it.
I read his code so many times I learnt it by heart: pens, pencils, fonts have been my nightmare for several days. I have been using GDI objects only for study or for joke until that day, but I�ve never entered in all the details of Windows graphical world.
Spending my nights among coffee, MSDN@, MFC and many tryouts I reached some knowledge of Device Contexts and other nice little things, so I could start translating Chris' grid to an Activex control.
The final goal was to develop a simple, fast, reliable and, basically, lightweight grid. There are a lot of professional grids on the market and they offer plenty of functionalities, I wanted to realize one, maybe not so professional, but easy and lightweight. This is the reason I chose to abandon MFC for ATL.
The result has been a great experience that makes me learn the details of the inner workings of Activex controls, Windows graphical world and ATL classes. Plus I have now a component of 150 Kb with almost every functionalities of Chris' grid.
In this paper I will describe the strategies I followed in the process of porting the grid, the main problems I met and the solutions I devised for them. I will present a collection of books that helped me on the way.
Through this experience I wrote a lot of beginner-level papers about COM, ATL and Visual Basic.
Finally big thanks to Chris Maunder, author of a very light, simple and reliable grid.
Thank you, Chris.
The big moment has come at last: it�s running.
After a lot of working days now there's a functional version of the grid.
This doesn�t mean the work is finished! I think there are a lot of annoying bugs currently living happily in the code (even though it's been checked by Numega BoundsChecker �).
I think the code can be the right starting point to improving implementations.
It is still not optimized: any help of a good profiler would be appreciated.
And now the good news:
- the grid is running and it has the main functions.
- I looked the depths of Windows: pens, brushes, handles, windows and messages; I had an experience in a complex and real project using COM.
Porting.
The first work was choosing a right strategy for the porting
In fact Chris� grid is a very complex object (about 5000 code's lines) and I think it is hard to convert all the code in ATL in only one passage even if it didn�t depend on MFC.
I created an ActiveX Control starting from ATL with MFC's support, so I made it compatible with the existing project and I could compile the code without too much hard work. The first excellent result was running the Chris� grid in a Visual Basic form.
Another important choice was the kind of container the grid should support: I liked to develop a OC96 control; and then make it a windowless control with quick activation and drawing optimization. This is the main reason I used the wizard to create an ATL control, Windows Only in this release.
When I saw the grid running in a VB form I was very satisfied. But I knew the goal was still far away.
I created the control, took the Chris� code in my project, created another grid like the test� one in the original project and then I deleted all the MFC calls.
I chose to call directly the Windows APIs for two reasons:
Didactics: I like to dive into the Windows APIs and the functionalities of the graphic's details (gdi32.dll).
Performance: my last target is to create a component with the lowest overhead possible, so I need to understand every line of code.
Data types
The data types used in the code and their use in the control are very important: in fact Windows uses handles for windows, pens, brushes, fonts, colors, � and the programmer has to use these objects and call the API . COM clients and scripting languages prefer to make use of COM interfaces and dispinterfaces: IFontDisp, IPictureDisp, and so on. For example, an HFONT (handle to a font object used in the device context) used with the Windows APIs is mapped (as OLE) in a pointer to the IFontDisp interface, which is the IDispatch version of the IFont interface.
In the grid I had to call the Windows graphic APIs also for color, string, font and bitmap, so I preferred to use the standard Windows datatypes and leave the datatype casting to the Automation interface.
Also strings are important in this project: the OLE world likes more BSTR, UNICODE strings (you can find all the details in the technical documentation).
On the WEB there are many classes that permit to use BSTR hiding details (often boring): there is software which implements an interface close to the MFC CString class making easy the porting .
Also the C++ standard library can become an important support with the <string> template class.
So the better choice is to use one of VC++ compiler classes: _bstr_t. This class is free, supported and easy to use. This is what I needed in my simple development.
Now I had to take MFC out from the project: I removed CString, CPen, CRect, CMap and especially CWinApp and MFC*.dll.
From 1 MB, to some KB.
After removing MFC from the project, I had to make the grid a COM object, so I had to define the interfaces, the ingoing one and the outgoing one with the events.
I agree with all the authors of COM books saying that the definition of the interfaces is the starting point of any activity. In this case, Chris had already defined the interface and my work has been to make them Automation compatible, so I realized a dual interface IGrid and then I defined methods and properties instead of functions.
The result is an ActiveX Control (140 Kb) which implements a lot of the functions (functionalities) of Chris� grid except print, print preview, integration with the clipboard and drag and drop, but now I can do all the actions I need using a simple grid.
Other functions to improve:
graphic side:
- merging of the cells
- moving of cells and columns
- setting different types of cell
end user side:
- OLE integration (clipboard, drag and drop)
- print and print preview
- using bound mode
- saving and loading the grid as XML format
design side:
CProxy_IGridEvents is the proxy class and the CGrid fires the methods in the IGridEvents interface. The wizard generated the proxy, but it had a problem. After many crashes, I looked at the VB code generated by the wizard: in details, there was a VARIANT_BOOL parameter (Cancel), defined as pointer in BeforeEdit and ValidateEdit methods of the interface: VB crashed because that parameter was passed byVal.
So I changed the code generated by the wizard as follow:
- pvars[0].vt = VT_BOOL | VT_BYREF;
- pvars[0].pboolVal = Cancel;
and it runs.
Grid�s Interfaces definition
Globals typedef definitions
typedef enum
{
GVL_NONE = 0,
GVL_HORZ = 1,
GVL_VERT = 2,
GVL_BOTH = 3
} grGridLines;
typedef enum
{
grPictOrientationLeft = 0,
grPictOrientationCenter = 1,
grPictOrientationRight = 2
} grPictOrientation;
typedef enum
{
grOrizAlignLeft = 0,
grOrizAlignRight = 1,
grOrizAlignCenter = 2
} grOrizontalAlignment;
typedef enum
{
grVertAlignBottom = 0,
grVertAlignTop = 1,
grVertAlignCenter = 2
} grVerticalAlignment;
typedef enum
{
grBreakingTWNormal = 0,
grBreakingTWWordBreak = 1,
grBreakingTWEndEllipsis = 2
} grBreakingTextWords;
typedef enum
{
grTextSingleLine = 0,
grTextNoLimit = 1
} grTextLine;
The IGridCell defines the behaviour of the cells:
interface IGridCell : IUnknown
{
[propget, helpstring("property PictureOrientation")]
HRESULT PictureOrientation([out, retval] grPictOrientation *pVal);
[propput, helpstring("property PictureOrientation")]
HRESULT PictureOrientation([in] grPictOrientation newVal);
[propget, helpstring("property Text")]
HRESULT Text([out, retval] BSTR *pVal);
[propput, helpstring("property Text")]
HRESULT Text([in] BSTR newVal);
[propget, helpstring("property Font")]
HRESULT Font([out, retval] IFontDisp* *pVal);
[propput, helpstring("property Font")]
HRESULT Font([in] IFontDisp* newVal);
[propget, helpstring("Horizontal Alignment")]
HRESULT HorizontalAlignment([out, retval] grOrizontalAlignment *pVal);
[propput, helpstring("Horizontal Alignment")]
HRESULT HorizontalAlignment([in] grOrizontalAlignment newVal);
[propget, helpstring("Vertical Alignment")]
HRESULT VerticalAlignment([out, retval] grVerticalAlignment *pVal);
[propput, helpstring("Vertical Alignment")]
HRESULT VerticalAlignment([in] grVerticalAlignment newVal);
[propget, helpstring("Breaking Text Words")]
HRESULT BreakingTextWords([out, retval] grBreakingTextWords *pVal);
[propput, helpstring("Breaking Text Words")]
HRESULT BreakingTextWords([in] grBreakingTextWords newVal);
[propget, helpstring("property Picture")]
HRESULT Picture([out, retval] IPictureDisp* *pVal);
[propput, helpstring("property Picture")]
HRESULT Picture([in] IPictureDisp* newVal);
};
The IGrid interface:
interface IGrid : IDispatch
{
[propputref, bindable,requestedit, id(DISPID_FONT)]
HRESULT Font([in]IFontDisp* pFont);
[propput, bindable,requestedit, id(DISPID_FONT)]
HRESULT Font([in]IFontDisp* pFont);
[propget, bindable,requestedit, id(DISPID_FONT)]
HRESULT Font([out, retval]IFontDisp** ppFont);
[propget, id(29), helpstring("property BackColor")]
HRESULT BackColor([out, retval] OLE_COLOR *pVal);
[propput, id(29), helpstring("property BackColor")]
HRESULT BackColor([in] OLE_COLOR newVal);
[propget, id(1), helpstring("Imposta/ legge l'image per l'item specificato")]
HRESULT Image([in] int Row,[in] int Col, [out, retval] short *pVal);
[propput, id(1), helpstring("Imposta/ legge l'image per l'item specificato")]
HRESULT Image([in] int Row,[in] int Col, [in] short newVal);
[propget, id(2), helpstring("Imposta/ legge il testo per l'item specificato")]
HRESULT Text([in] int Row,[in] int Col, [out, retval] BSTR *pVal);
[propput, id(2), helpstring("Imposta/ legge il testo per l'item specificato")]
HRESULT Text([in] int Row,[in] int Col, [in] BSTR newVal);
[id(3), helpstring("method InsertRow")]
HRESULT InsertRow([in] int Row,[in] BSTR caption);
[propget, id(4), helpstring("Imposta/ritorna il numero di righe nella griglia")]
HRESULT RowCount([out, retval] int *pVal);
[propput, id(4), helpstring("Imposta/ritorna il numero di righe nella griglia")]
HRESULT RowCount([in] int newVal);
[propget, id(5), helpstring("Imposta/Ritorna il numero di colonne nella grid")]
HRESULT ColumnCount([out, retval] int *pVal);
[propput, id(5), helpstring("Imposta/Ritorna il numero di colonne nella grid")]
HRESULT ColumnCount([in] int newVal);
[propget, id(6), helpstring("Imposta/ritorna l'altezza della riga specificata")]
HRESULT RowHeight([in] int nRow, [out, retval] int *pVal);
[propput, id(6), helpstring("Imposta/ritorna l'altezza della riga specificata")]
HRESULT RowHeight([in] int nRow, [in] int newVal);
[propget, id(7), helpstring("Imposta/ritorna la larghezza della colonna")]
HRESULT ColumnWidth([in] int Col, [out, retval] int *pVal);
[propput, id(7), helpstring("Imposta/ritorna la larghezza della colonna")]
HRESULT ColumnWidth([in] int Col, [in] int newVal);
[propget, id(9), helpstring("property CellFont")]
HRESULT CellFont([in] int Row,[in] int Col, [out, retval] IFontDisp* *pVal);
[propput, id(9), helpstring("property CellFont")]
HRESULT CellFont([in] int Row,[in] int Col, [in] IFontDisp* newVal);
[id(11), helpstring("method AutoSize")] HRESULT AutoSize();
[propget, id(12), helpstring("Allow Column resizing")]
HRESULT ColumnResizing([out, retval] VARIANT_BOOL *pVal);
[propput, id(12), helpstring("Allow Column resizing")]
HRESULT ColumnResizing([in] VARIANT_BOOL newVal);
[propget, id(13), helpstring("property RowResizing")]
HRESULT RowResizing([out, retval] VARIANT_BOOL *pVal);
[propput, id(13), helpstring("property RowResizing")]
HRESULT RowResizing([in] VARIANT_BOOL newVal);
[propget, id(14), helpstring("property GridLines")]
HRESULT GridLines([out, retval] grGridLines *pVal);
[propput, id(14), helpstring("property GridLines")]
HRESULT GridLines([in] grGridLines newVal);
[propget, id(15), helpstring("property Editable")]
HRESULT Editable([out, retval] VARIANT_BOOL *pVal);
[propput, id(15), helpstring("property Editable")]
HRESULT Editable([in] VARIANT_BOOL newVal);
[propget, id(16), helpstring("property FixedRows")]
HRESULT FixedRows([out, retval] int *pVal);
[propput, id(16), helpstring("property FixedRows")]
HRESULT FixedRows([in] int newVal);
[propget, id(17), helpstring("property FixedCols")]
HRESULT FixedCols([out, retval] int *pVal);
[propput, id(17), helpstring("property FixedCols")]
HRESULT FixedCols([in] int newVal);
[id(18), helpstring("method AutosizeColumn")]
HRESULT AutosizeColumn([in] int col);
[id(19), helpstring("method AutosizeRow")]
HRESULT AutosizeRow([in] int row);
[propget, id(20), helpstring("property CellEnabled")]
HRESULT CellEnabled([in] int row,[in] int col, [out, retval]
VARIANT_BOOL *pVal);
[propput, id(20), helpstring("property CellEnabled")]
HRESULT CellEnabled([in] int row,[in] int col, [in] VARIANT_BOOL newVal);
[propget, id(21), helpstring("property AllowSelection")]
HRESULT AllowSelection([out, retval] VARIANT_BOOL *pVal);
[propput, id(21), helpstring("property AllowSelection")]
HRESULT AllowSelection([in] VARIANT_BOOL newVal);
[propget, id(22), helpstring("property ListMode")]
HRESULT ListMode([out, retval] VARIANT_BOOL *pVal);
[propput, id(22), helpstring("property ListMode")]
HRESULT ListMode([in] VARIANT_BOOL newVal);
[propget, id(23), helpstring("property CurrentRow")]
HRESULT CurrentRow([out, retval] int *pVal);
[propput, id(23), helpstring("property CurrentRow")]
HRESULT CurrentRow([in] int newVal);
[propget, id(24), helpstring("property CurrentCol")]
HRESULT CurrentCol([out, retval] int *pVal);
[propput, id(24), helpstring("property CurrentCol")]
HRESULT CurrentCol([in] int newVal);
[id(25), helpstring("method DeleteRow")] HRESULT DeleteRow([in] int row);
[propget, id(26), helpstring("property HeaderSort")]
HRESULT HeaderSort([out, retval] VARIANT_BOOL *pVal);
[propput, id(26), helpstring("property HeaderSort")]
HRESULT HeaderSort([in] VARIANT_BOOL newVal);
[propget, id(27), helpstring("property SingleRowSelection")]
HRESULT SingleRowSelection([out, retval] VARIANT_BOOL *pVal);
[propput, id(27), helpstring("property SingleRowSelection")]
HRESULT SingleRowSelection([in] VARIANT_BOOL newVal);
[id(28), helpstring("method DeleteColumn")] HRESULT DeleteColumn([in] int col);
[propget, id(30), helpstring("property FixedBackColor")]
HRESULT FixedBackColor([out, retval] OLE_COLOR *pVal);
[propput, id(30), helpstring("property FixedBackColor")]
HRESULT FixedBackColor([in] OLE_COLOR newVal);
[propget, id(31), helpstring("property TextColor")]
HRESULT TextColor([out, retval] OLE_COLOR *pVal);
[propput, id(31), helpstring("property TextColor")]
HRESULT TextColor([in] OLE_COLOR newVal);
[propget, id(32), helpstring("property TextBackColor")]
HRESULT TextBackColor([out, retval] OLE_COLOR *pVal);
[propput, id(32), helpstring("property TextBackColor")]
HRESULT TextBackColor([in] OLE_COLOR newVal);
[propget, id(33), helpstring("property Color")]
HRESULT Color([out, retval] OLE_COLOR *pVal);
[propput, id(33), helpstring("property Color")]
HRESULT Color([in] OLE_COLOR newVal);
[propget, id(34), helpstring("property FixedTextColor")]
HRESULT FixedTextColor([out, retval] OLE_COLOR *pVal);
[propput, id(34), helpstring("property FixedTextColor")]
HRESULT FixedTextColor([in] OLE_COLOR newVal);
[propget, id(35), helpstring("property CellFgColor")]
HRESULT CellFgColor([in] int Row, [in] int Col, [out, retval] OLE_COLOR *pVal);
[propput, id(35), helpstring("property CellFgColor")]
HRESULT CellFgColor([in] int Row, [in] int Col, [in] OLE_COLOR newVal);
[propget, id(36), helpstring("property CellBgColor")]
HRESULT CellBgColor([in] int Row, [in] int Col, [out, retval] OLE_COLOR *pVal);
[propput, id(36), helpstring("property CellBgColor")]
HRESULT CellBgColor([in] int Row, [in] int Col, [in] OLE_COLOR newVal);
[id(37), helpstring("method SimpleConf")] HRESULT SimpleConf();
[propget, id(38), helpstring("property Cell")]
HRESULT Cell([in] int Row,[in] int Col, [out, retval] IGridCell* *pVal);
[propput, id(38), helpstring("property Cell")]
HRESULT Cell([in] int Row,[in] int Col, [in] IGridCell* newVal);
[propget, id(39), helpstring("property ToolTip")]
HRESULT ToolTip([out, retval] VARIANT_BOOL *pVal);
[propput, id(39), helpstring("property ToolTip")]
HRESULT ToolTip([in] VARIANT_BOOL newVal);
[id(40), helpstring("method Refresh")] HRESULT Refresh();
[id(41), helpstring("method SelectAllCells")] HRESULT SelectAllCells();
[propget, id(42),
helpstring("KeepTab specifies if TAB send or not the Focus to the next control")]
HRESULT KeepTab([out, retval] VARIANT_BOOL *pVal);
[propput, id(42),
helpstring("KeepTab specifies if TAB send or not the Focus to the next control")]
HRESULT KeepTab([in] VARIANT_BOOL newVal);
};
and the events (outgoing interface) for the grid:
dispinterface _IGridEvents
{
properties:
methods:
[id(1), helpstring("Fired before start Cell edit")]
HRESULT BeforeEdit(int Row,int Col,VARIANT_BOOL* Cancel);
[id(2), helpstring("Fired before exit Cell edit")]
HRESULT ValidateEdit(int Row,int Col,VARIANT_BOOL* Cancel);
[id(3), helpstring("Fired after Cell edit end")]
HRESULT AfterEdit(int Row,int Col);
[id(4), helpstring("Fired before enter in a Cell")]
HRESULT EnterCell(int Row,int Col);
[id(5), helpstring("Fired after exit from a Cell")]
HRESULT LeaveCell(int Row,int Col);
[id(6), helpstring("Fired after click on a fixed column")]
HRESULT ColumnClick(int Col);
[id(7), helpstring("Fired after click on a fixed row")]
HRESULT RowClick(int Row);
[id(8), helpstring("Fired before a selection changed")]
HRESULT SelChanging();
[id(9), helpstring("Fired after a selection changed")]
HRESULT SelChanged();
};
I�ve been working in software development for ten years, six years in Windows world and three years in COM and C++, and today I can say that this experience has been really great even if hard!
Developing the grid, I looked at arguments I rarely have the occasion to study in depth in my job and I enjoyed myself also posting e-mails to Chris who is so patient in testing beta versions, indicating bugs and encouraging me to finish this work.
If someone is interested in this work or likes to help me developing functions (functionalities) or has suggestions post me an e-mail: my address is Mario@genoavalley.org