Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Drag & Drop in FlexGrid Using Arbitrary Formats

0.00/5 (No votes)
2 Jun 2003 3  
Implementing drag and rop within MS Flexgrid control using any format

Sample Image - FlexGridDragDrop.gif

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:

  1. 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()
  2. 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.

    // 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;
                "#ByteArrayGet">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;
    }
  3. 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.

    // 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;
    }
  4. 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">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;
    }
  5. 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.

    // 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.

    • License

      This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

      A list of licenses authors might use can be found here