Introduction
Are you stuck on following corporate 'GUI standards' to use FlexGrid
whenever displaying grid data? Stop dreaming, QA wont allow you to use cool MFC grid, or custom CListCtrl
. You must use the dreaded FlexGrid
control :D ! The good news is, this article is for you!
Few months ago, I was in that dilemma. I have been skewering around for any FlexGrid
drag-drop documentation but only found just general, almost useless VB stuff. So here it is - a C++ guide to implementing drag-n-drop in MS FlexGrid.
Implementation
FlexGrid
drag-drop is implemented in five parts:
-
Initiate drag
At the source grid context, detect mouse movement. Call FlexGrid::OLEDrag()
if dragging is appropriate.
BEGIN_EVENTSINK_MAP(CSourceGrid, CMSFlexGrid)
ON_EVENT_REFLECT(CSourceGrid, -606 ,
OnMouseMove, VTS_I2 VTS_I2 VTS_I4 VTS_I4)
... other events...
END_EVENTSINK_MAP()
BOOL CSourceGrid::OnMouseMove(short Button,
short Shift, long x, long y)
{
if (Button&0x1 && !m_dragging)
{
if (!m_resourcesAssigned[index])
{
m_dragging = TRUE;
OLEDrag();
}
}
return m_dragging;
}
Of course, you can also implement the above in the Flexgrid
container window (e.g. the dialog). The dialog's event sink would look like the following.
BEGIN_EVENTSINK_MAP(CFlexGridDragDropDlg, CDialog)
ON_EVENT(CFlexGridDragDropDlg, IDC_GRID_SOURCE,
-606 , OnGridSourceMouseMove,
VTS_I2 VTS_I2 VTS_I4 VTS_I4)
... other events...
END_EVENTSINK_MAP()
-
Handle OLEStartDrag event
OLEDrag()
above triggers a OLEStartDrag
event. Handle this event in the source grid context to create a DataObject
to be passed around the drag-drop operation.
If you are going to pass your own proprietary format, make sure you prepare a struct
or a class that you can comfortably dump and restore as a byte array. Don't forget that you might need to add filler to align your struct
to your memory model.
BEGIN_EVENTSINK_MAP(CSourceGrid, CMSFlexGrid)
ON_EVENT_REFLECT(CSourceGrid, 1550 ,
OnOLEStartDrag, VTS_PDISPATCH VTS_PI4)
... other events...
END_EVENTSINK_MAP()
BOOL CSourceGrid::OnOLEStartDrag(LPDISPATCH FAR* Data,
long FAR* AllowedEffects)
{
int i_row = GetMouseRow();
int i_col = GetMouseCol();
CMSFlexGridDataObject dataObject(*Data);
dataObject.Clear();
if (_example_only_IsCellDraggable(i_row, i_col))
{
*AllowedEffects = DROPEFFECT_NONE;
}
else
{
*AllowedEffects = DROPEFFECT_COPY;
if (_example_only_IsDragTypeText(i_row, i_col)
{
CString str_text = GetTextMatrix(i_row, i_col);
_variant_t dragDropData(_bstr_t((LPCTSTR)str_text));
_variant_t l_format(1L);
dataObject.SetData(dragDropData, l_format);
}
else
{
CellData myData;
m_currentCellData.i_resourceNumber = 0;
m_currentCellData.p_gridSource = this;
_variant_t dragDropData;
"#ByteArrayGet">ByteArrayFill(&dragDropData,
reinterpret_cast<BYTE *>(&m_currentCellData),
sizeof(m_currentCellData));
_variant_t l_noformat(ARBITRARY_FORMAT1);
dataObject.SetData(dragDropData, l_noformat);
m_colDragging = col;
m_rowDragging = row;
}
}
dataObject.DetachDispatch();
return FALSE;
}
-
Handle OLEDragOver event
Handle the OLEDragOver
in the target grid context to examine the data object being dragged over. Tell OLE whether we can accept the object or not by specifying the Effect
.
BEGIN_EVENTSINK_MAP(CTargetGrid, CMSFlexGrid)
ON_EVENT_REFLECT(CTargetGrid, 1554 ,
OnOLEDragOver,
VTS_PDISPATCH VTS_PI4 VTS_PI2 VTS_PI2 VTS_PR4 VTS_PR4 VTS_PI2)
... other events...
END_EVENTSINK_MAP()
BOOL CTargetGrid::OnOLEDragOver(LPDISPATCH FAR* Data,
long FAR* Effect, short FAR* Button,
short FAR* Shift, float FAR* x,
float FAR* y, short FAR* State)
{
int row = GetMouseRow();
int col = GetMouseCol();
CMSFlexGridDataObject dataObject(*Data);
if (_example_only_CanDropInCell(row, col))
{
*Effect = *Effect&DROPEFFECT_NONE;
}
if (dataObject.GetFormat(ARBITRARY_FORMAT1)
|| dataObject.GetFormat(ARBITRARY_FORMAT2))
{
}
else if (dataObject.GetFormat(1))
{
}
else
{
*Effect = *Effect&DROPEFFECT_NONE;
}
dataObject.DetachDispatch();
return FALSE;
}
-
Handle OLEDragDrop event
Handle the OLEDragDrop
in the target grid context to get the object being dropped. Update the cell accordingly.
BEGIN_EVENTSINK_MAP(CTargetGrid, CMSFlexGrid)
ON_EVENT_REFLECT(CTargetGrid,
1555 , OnOLEDragDrop,
VTS_PDISPATCH VTS_PI4 VTS_PI2 VTS_PI2 VTS_PR4 VTS_PR4)
... other events...
END_EVENTSINK_MAP()
BOOL CTargetGrid::OnOLEDragDrop(LPDISPATCH FAR* Data,
long FAR* Effect, short FAR* Button,
short FAR* Shift, float FAR* x, float FAR* y)
{
int row = GetMouseRow();
int col = GetMouseCol();
CMSFlexGridDataObject dataObject(*Data);
short s_format = 0;
if (dataObject.GetFormat(ARBITRARY_FORMAT1))
{
s_format = (short)ARBITRARY_FORMAT1;
}
else if (dataObject.GetFormat(ARBITRARY_FORMAT2))
{
s_format = (short)ARBITRARY_FORMAT2;
}
else if (dataObject.GetFormat(1))
{
s_format = 1;
}
if (s_format != 0)
{
_variant_t itemData = dataObject.GetData(s_format);
if (s_format == 1)
{
SetTextMatrix(row,col,_bstr_t(itemData));
}
else
{
CellData myData;
"#ByteArrayGet">ByteArrayGet(itemData, (BYTE *) &myData);
_example_only_ConsumeData(row, col, myData);
}
_example_only_UpdateCellDisplay();
}
else
{
*Effect = *Effect&DROPEFFECT_NONE;
}
dataObject.DetachDispatch();
return FALSE;
}
-
Finally, handle OLECompleteDrag event
Handle the OLECompleteDrag
in the source grid context to end the drag state. If the drag effect was DROPEFFECT_MOVE
, this would be a good time to delete the source data.
BEGIN_EVENTSINK_MAP(CSourceGrid, CMSFlexGrid)
ON_EVENT_REFLECT(CSourceGrid,
1553 ,
OnOLECompleteDrag, VTS_PI4)
... other events...
END_EVENTSINK_MAP()
BOOL CSourceGrid::OnOLECompleteDrag(long FAR* Effect)
{
if (*Effect&DROPEFFECT_MOVE)
{
_example_only_CellDataMoved(m_rowDragging, m_colDragging)
}
m_dragging = FALSE;
m_colDragging = -1;
m_rowDragging = -1;
return FALSE;
}
In the code snippets above, the arbitrary (my local) formats were defined in stdafx.h as:
const long ARBITRARY_FORMAT1 = 991L;
const long ARBITRARY_FORMAT2 = 992L;
const long ARBITRARY_FORMAT3 = 993L;
You can define your own, just make sure it wont collide with predefined formats like 1-text, 2-bitmap, 3-metafile, 8-DIB and 9-pallete.
Helper functions
Aside from text, bitmap, meta files, CDataObject
supports any format as long as the 'unknown' data is stuffed as a byte array. The following two functions are useful in converting to and extracting from byte array format. These helpers are located in the file stdafx.h of the sample project.
inline void ByteArrayFill(VARIANT* p_variant,
BYTE * p_arraySource, int i_size)
Fills the p_variant
with a byte array taken from the p_arraySource
of size i_size
Parameters
VARIANT* p_variant
- pointer to the variant to fill
BYTE * p_arraySource
- memory location of the source data
int i_size
- the size of the data to copy
Returns
inline void ByteArrayGet(VARIANT variant, BYTE * p_arrayDest)
Extracts the byte array from variant and copy it into p_arrayDest
.
Parameters
VARIANT variant
- the variant data containing a byte array
BYTE * p_arrayDest
- memory location to where the byte array is to be extracted
Returns
Notes
Make sure p_arrayDest
is pointing to an allocated memory enough to contain the data in the variant.
Demo application
The sample project is a dialog-based application containing three FlexGrid
controls: one source grid and two target grids. The source grid supports drag (copy) only, while the target grid supports both drag (move) and drop.
Aside from drag drop, you can see examples of:
- Drawing bitmap or icons in a cell
- Extracting bitmap from image list
- Calculating cell sizes to fit and fill exactly the size of the grid control
Finally, may I say - the codes here were written while I was cooking my dinner :-). It may not be perfect, but I hope it helped to illustrate my points.