Drag & Drop in FlexGrid Using Arbitrary Formats
Implementing drag and rop within MS Flexgrid control using any format
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.// Event Sink Map BEGIN_EVENTSINK_MAP(CSourceGrid, CMSFlexGrid) ON_EVENT_REFLECT(CSourceGrid, -606 /* MouseMove */, OnMouseMove, VTS_I2 VTS_I2 VTS_I4 VTS_I4) ... other events... END_EVENTSINK_MAP() // Handle mouse move event and initiate OLE drag if appropriate BOOL CSourceGrid::OnMouseMove(short Button, short Shift, long x, long y) { if (Button&0x1 && !m_dragging) // 1 - vbLeftButton { if (!m_resourcesAssigned[index]) { m_dragging = TRUE; OLEDrag(); // OnOLEStartDrag will be called } } 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.// Dialog Event Sink Map BEGIN_EVENTSINK_MAP(CFlexGridDragDropDlg, CDialog) ON_EVENT(CFlexGridDragDropDlg, IDC_GRID_SOURCE, -606 /* MouseMove */, OnGridSourceMouseMove, VTS_I2 VTS_I2 VTS_I4 VTS_I4) ... other events... END_EVENTSINK_MAP()
-
Handle OLEStartDrag event
OLEDrag()
above triggers aOLEStartDrag
event. Handle this event in the source grid context to create aDataObject
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 yourstruct
to your memory model.// Event Sink Map BEGIN_EVENTSINK_MAP(CSourceGrid, CMSFlexGrid) ON_EVENT_REFLECT(CSourceGrid, 1550 /* OLEStartDrag */, OnOLEStartDrag, VTS_PDISPATCH VTS_PI4) ... other events... END_EVENTSINK_MAP() // Handle OLEStartDrag, and create the data object to pass around BOOL CSourceGrid::OnOLEStartDrag(LPDISPATCH FAR* Data, long FAR* AllowedEffects) { int i_row = GetMouseRow(); int i_col = GetMouseCol(); // OLE gave us object where to put our data in CMSFlexGridDataObject dataObject(*Data); dataObject.Clear(); // Indicate the expected drop effect // Use AllowedEffects to tell OLE whether the // object can be drag-drop or not, or whether // we want: // DROPEFFECT_COPY - we intend to keep the original data // DROPEFFECT_MOVE - we will delete the original // data after successful drop operation if (_example_only_IsCellDraggable(i_row, i_col)) { // Dont want to drag *AllowedEffects = DROPEFFECT_NONE; } else { // OK to copy *AllowedEffects = DROPEFFECT_COPY; if (_example_only_IsDragTypeText(i_row, i_col) { // Drag a text //////////////////////// CString str_text = GetTextMatrix(i_row, i_col); // Setup data to pass around _variant_t dragDropData(_bstr_t((LPCTSTR)str_text)); _variant_t l_format(1L); //cfText dataObject.SetData(dragDropData, l_format); } else { // Drag arbitrary format //////////////////////// // In this example, let's drag our own // format - from an ordinary C++ class CellData myData; // sample only m_currentCellData.i_resourceNumber = 0; // Our sample code needs this //to 'know' where the object came from m_currentCellData.p_gridSource = this; // Now stuff our data into a variant byte array _variant_t dragDropData; ByteArrayFill(&dragDropData, reinterpret_cast<BYTE *>(&m_currentCellData), sizeof(m_currentCellData)); // And tag it with our own format type _variant_t l_noformat(ARBITRARY_FORMAT1); dataObject.SetData(dragDropData, l_noformat); m_colDragging = col; m_rowDragging = row; } } // Done. Let it linger in the drag drop operation dataObject.DetachDispatch(); // Give chance to other handlers 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 theEffect
.// Event Sink Map BEGIN_EVENTSINK_MAP(CTargetGrid, CMSFlexGrid) ON_EVENT_REFLECT(CTargetGrid, 1554 /* OLEDragOver */, OnOLEDragOver, VTS_PDISPATCH VTS_PI4 VTS_PI2 VTS_PI2 VTS_PR4 VTS_PR4 VTS_PI2) ... other events... END_EVENTSINK_MAP() // Handle OLEDragOver, indicate whether we // can accept the object or not 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(); // Get access to the object being dragged over CMSFlexGridDataObject dataObject(*Data); // Don't allow drop on the same cell or in fixed cells if (_example_only_CanDropInCell(row, col)) { *Effect = *Effect&DROPEFFECT_NONE; } // Check the format if we can accept it or not // Our own format1: dragged from CSourceGrid // Our own format2: dragged from within this control if (dataObject.GetFormat(ARBITRARY_FORMAT1) || dataObject.GetFormat(ARBITRARY_FORMAT2)) { // Yes we want it. No need to modify the Effect } else if (dataObject.GetFormat(1)) // VbCFText { // Simple text. Yes we want it. // No need to modify the Effect } else { // Format not supported. Don't want it *Effect = *Effect&DROPEFFECT_NONE; } // We're done here dataObject.DetachDispatch(); // Give chance to other handlers return FALSE; }
-
Handle OLEDragDrop event
Handle the
OLEDragDrop
in the target grid context to get the object being dropped. Update the cell accordingly.// Event Sink Map BEGIN_EVENTSINK_MAP(CTargetGrid, CMSFlexGrid) ON_EVENT_REFLECT(CTargetGrid, 1555 /* OLEDragDrop */, OnOLEDragDrop, VTS_PDISPATCH VTS_PI4 VTS_PI2 VTS_PI2 VTS_PR4 VTS_PR4) ... other events... END_EVENTSINK_MAP() // Handle OnOLEDragDrop. Extract the dropped // data if we know its format 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(); // Get access to the object being droped CMSFlexGridDataObject dataObject(*Data); // Let's examine the format short s_format = 0; // dragged from CSourceGrid if (dataObject.GetFormat(ARBITRARY_FORMAT1)) { s_format = (short)ARBITRARY_FORMAT1; } // dragged from within this control else if (dataObject.GetFormat(ARBITRARY_FORMAT2)) { s_format = (short)ARBITRARY_FORMAT2; } else if (dataObject.GetFormat(1)) // CFText { s_format = 1; } // Good format if (s_format != 0) { // Get data _variant_t itemData = dataObject.GetData(s_format); if (s_format == 1) { // Simple text, easy //////////////////////// SetTextMatrix(row,col,_bstr_t(itemData)); } else { // Arbitrary format, extract from the byte array //////////////////////// CellData myData; ByteArrayGet(itemData, (BYTE *) &myData); // Use the dropped data _example_only_ConsumeData(row, col, myData); } // Redraw the cell _example_only_UpdateCellDisplay(); } else { *Effect = *Effect&DROPEFFECT_NONE; } dataObject.DetachDispatch(); // Give chance to other handlers return FALSE; }
-
Finally, handle OLECompleteDrag event
Handle the
OLECompleteDrag
in the source grid context to end the drag state. If the drag effect wasDROPEFFECT_MOVE
, this would be a good time to delete the source data.// Event Sink Map BEGIN_EVENTSINK_MAP(CSourceGrid, CMSFlexGrid) ON_EVENT_REFLECT(CSourceGrid, 1553 /* OLECompleteDrag */, OnOLECompleteDrag, VTS_PI4) ... other events... END_EVENTSINK_MAP() // Handle OLECompleteDrag to finalize the drag-drop // operation. Delete 'moved' data as appropriate. 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; // Give chance to other handlers 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
- None.
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
- None.
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.