Link to home
Start Free TrialLog in
Avatar of Anthony2000
Anthony2000Flag for United States of America

asked on

Simple "drag and drop like" behavior using MFC

I need a simple Windows project (for Visual Studio 6) that demonstrates dragging a button's bitmap image on a dialog.
I cannot use OLE. I need it to behave like the Windows shell (I want the effect of taking a copy of the bitmap image and making it translucent and when you get close to the target, the target changes color to give feedback that you are on the target).

I am using CBitmapButtons. I want to drag them and drop them in other places on the same dialog.
ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Anthony2000

ASKER

>> I cannot use OLE.
>>Does that mean you don't yet know how to or that it is disallowed in this project?

It is because I am using WinCE 5.0, and the version of MFC does not support some of the OLE classes that are normally available to the desktop.

I tried the outline you listed and it mostly works. I ran into the problem where I drag the button bitmap and it disappears behind several controls ( you helped me with a similar problem before). This is minor, so I might try to fix this later.

The next issue related to the current task is that I need to high-light the target CBitmapButton object.

In your outline:
OnMouseMove
   CImageList::DragMove
   CImageList::DragShowNolock( FALSE)
   tree->HitTest  -- see if over a drop target ( replace with my hittest code
   tree->SelectDropTarget -- and highlight it ( I not sure how to change the bitmap of the CBitmapButton object to look high-lighted?)
   CImageList::DragShowNolock( TRUE )


Also, if the user moves away from the target, how do I change the CBitmapButton back to its normal bitmap??

Thanks, for all your help.
You can associate three images with a CBitmapButton  
    CBitmapButton::LoadBitmaps
    http://msdn2.microsoft.com/en-us/library/6y6acs49(VS.80).aspx

What I'd first try is calling SetState(TRUE) on the drop target.  That will make it appear in the "pushed down state" which may be a reasonable visual feedback for the user.  An alternative would be to call SetBitmap on the target to give it an entriely different look (one that makes it look highlighted, for instance black with white letters).

To change it back, be sure to restore the state (or restore the normal bitmap) on the OnButtonUp handler,  You also must restore the status (or image) any time the hit-test indicates that the drop object is not over the drop target.   For this latter part, an optimization would be to keep a boolean variable with the current highlighting status so you know what state it is in (and thus don't need to change it back unnecessarily).

-- Dan
Dan, I don't if you can help. I have run into a strange problem with the code you have outlined.

Basically I have a new button class that is derived from CBitmapButton. This new class just sends a user defined message along with the "this" pointer to the main dialog. The main dialog's handler of this message starts the drag image processing. And the dialogs OnMouseMove calls the m_imgl.DragMove(point); and the OnLButtonUp ends the process.

The problem is that the Windows CE environment appears to get corrupted after I perform a drag operation. I am attaching the entire project (I created a stripped down version of my app to demonstrate the problem).

Some portions are commented out because I was experimenting to see if I could narrow down the cause of the problem. The version below still exhibits the problem.

To re-create (if you had a Windows CE platform and I realize that you don't) run the program perform a drag operation on the volume button. Then exit and repeat. The second time you enter the app, the drag image is blank and eventually the app crashes somewhere in la-la land.

What I am going to try next is to port the app to Windows desktop and see if I can spot the problem.

Thanks,

Anthony


// drag_and_drop_cbitmapbuttonDlg.cpp : implementation file
// main dialog 
//
 
#include "stdafx.h"
#include "drag_and_drop_cbitmapbutton.h"
#include "drag_and_drop_cbitmapbuttonDlg.h"
#include "dragdropbitmapbutton.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
 
int WM_DRAGBUTTON = RegisterWindowMessage (_T("WM_DRAGBUTTON"));
 
/////////////////////////////////////////////////////////////////////////////
// CDrag_and_drop_cbitmapbuttonDlg dialog
 
CDrag_and_drop_cbitmapbuttonDlg::CDrag_and_drop_cbitmapbuttonDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CDrag_and_drop_cbitmapbuttonDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CDrag_and_drop_cbitmapbuttonDlg)
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
 
void CDrag_and_drop_cbitmapbuttonDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CDrag_and_drop_cbitmapbuttonDlg)
	DDX_Control(pDX, IDC_BUTTON3_VOLUME, m_btnVolume);
	DDX_Control(pDX, IDC_BUTTON4_HOME, m_btnHome);
	DDX_Control(pDX, IDC_BUTTON2_SAVE, m_btnSave);
	DDX_Control(pDX, IDC_BUTTON1_CAMERA, m_btnCamera);
	//}}AFX_DATA_MAP
}
 
BEGIN_MESSAGE_MAP(CDrag_and_drop_cbitmapbuttonDlg, CDialog)
	//{{AFX_MSG_MAP(CDrag_and_drop_cbitmapbuttonDlg)
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_REGISTERED_MESSAGE(WM_DRAGBUTTON, OnDragButton)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
/////////////////////////////////////////////////////////////////////////////
// CDrag_and_drop_cbitmapbuttonDlg message handlers
 
BOOL CDrag_and_drop_cbitmapbuttonDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
 
	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	CenterWindow(GetDesktopWindow());	// center to the hpc screen
 
	// TODO: Add extra initialization here
	m_btnCamera.LoadBitmaps(IDB_CAMERA,       IDB_CAMERA_DOWN, 
		                    IDB_CAMERA_FOCUS, IDB_CAMERA_DISABLE); 
	m_btnCamera.SizeToContent();
 
	m_btnSave.LoadBitmaps(IDB_SAVE,       IDB_SAVE_DOWN, 
		                  IDB_SAVE_FOCUS, IDB_SAVE_DISABLE); 
	m_btnSave.SizeToContent();
	
	m_btnVolume.LoadBitmaps(IDB_VOLUME,       IDB_VOLUME_DOWN, 
		                    IDB_VOLUME_FOCUS, IDB_VOLUME_DISABLE); 
	m_btnVolume.SizeToContent();
	
	m_btnHome.LoadBitmaps(IDB_HOME,       IDB_HOME_DOWN, 
		                  IDB_HOME_FOCUS, IDB_HOME_DISABLE); 
	m_btnHome.SizeToContent();
 
	BOOL bRet = m_imgl.Create(48, 48, ILC_MASK|ILC_COLOR8, 1, 1);
	ASSERT(bRet);
 
	m_bButtonDragging = FALSE;
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}
 
 
 
void CDrag_and_drop_cbitmapbuttonDlg::OnMouseMove(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	if(m_bButtonDragging)
	{
		m_imgl.DragMove(point);
	}
	else
	{
		// just trying different things to try to alter the behavior
		CDialog::OnMouseMove(nFlags, point);
	}
	
	//CDialog::OnMouseMove(nFlags, point);
}
 
void CDrag_and_drop_cbitmapbuttonDlg::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	
	CDialog::OnLButtonDown(nFlags, point);
}
 
void CDrag_and_drop_cbitmapbuttonDlg::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	OutputDebugString(_T("L-button UP\n"));
 
	if(m_bButtonDragging)
	{
		(void)ReleaseCapture();
 
		m_imgl.DragLeave(this);
		m_imgl.EndDrag();
 
		// Delete the image list and verify.
		//m_imgl.DeleteImageList();
		//ASSERT(m_imgl.GetSafeHandle() == NULL);
 
		//SetTimer(TIMER_ID_AUTO_HIDE, AUTO_HIDE_TIMEOUT, NULL);
 
		OutputDebugString(_T("dragging done\n"));
	}
 
	m_bButtonDragging = FALSE;
	
	CDialog::OnLButtonUp(nFlags, point);
}
 
void CDrag_and_drop_cbitmapbuttonDlg::OnDragButton(WPARAM wParam, LPARAM lParam)
{
	OutputDebugString(_T("button dragging has started!\n"));
 
	COLORREF rgbTransparentColor=RGB(0, 0, 0);
	// Set up your transparent color as appropriate
//		crTransparentColor[i] = GetPixel( hdc, x + 1, y + 1 );
 
	CPoint cp;
	(void)GetCursorPos(&cp);
 
	// Add the bitmap into the image list
//	for(int i=0; i<m_imgl.GetImageCount(); i++)
//	{
//		m_imgl.Remove(i);
//		OutputDebugString(_T("removing image objs\n"));
//	}
 
	//if(m_imgl.GetImageCount() == 0)
	//	m_imgl.Add(((CDragDropBitmapButton*)lParam)->GetDragImage(), rgbTransparentColor);
 
	m_imgl.BeginDrag(0, CPoint(0,0)); // roughly center of bitmap
	m_imgl.DragEnter(this, cp); 
 
	m_bButtonDragging = TRUE;
	SetCapture();
};
 
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* end of file */
 
// drag_and_drop_cbitmapbuttonDlg.h : header file
//
 
#if !defined(AFX_DRAG_AND_DROP_CBITMAPBUTTONDLG_H__45A20897_80A8_4FF3_8B01_35214FC4B2B5__INCLUDED_)
#define AFX_DRAG_AND_DROP_CBITMAPBUTTONDLG_H__45A20897_80A8_4FF3_8B01_35214FC4B2B5__INCLUDED_
 
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
 
#include "dragdropbitmapbutton.h"
 
/////////////////////////////////////////////////////////////////////////////
// CDrag_and_drop_cbitmapbuttonDlg dialog
 
class CDrag_and_drop_cbitmapbuttonDlg : public CDialog
{
// Construction
public:
	CDrag_and_drop_cbitmapbuttonDlg(CWnd* pParent = NULL);	// standard constructor
 
// Dialog Data
	//{{AFX_DATA(CDrag_and_drop_cbitmapbuttonDlg)
	enum { IDD = IDD_DRAG_AND_DROP_CBITMAPBUTTON_DIALOG };
	CDragDropBitmapButton	m_btnVolume;
	CBitmapButton	m_btnHome;
	CBitmapButton	m_btnSave;
	CBitmapButton	m_btnCamera;
	//}}AFX_DATA
 
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CDrag_and_drop_cbitmapbuttonDlg)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support
	//}}AFX_VIRTUAL
 
// Implementation
protected:
	HICON m_hIcon;
	CImageList m_imgl;
	BOOL m_bButtonDragging;
 
	// Generated message map functions
	//{{AFX_MSG(CDrag_and_drop_cbitmapbuttonDlg)
	virtual BOOL OnInitDialog();
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnDragButton(WPARAM wParam, LPARAM lParam);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};
 
//{{AFX_INSERT_LOCATION}}
// Microsoft eMbedded Visual C++ will insert additional declarations immediately before the previous line.
 
#endif // !defined(AFX_DRAG_AND_DROP_CBITMAPBUTTONDLG_H__45A20897_80A8_4FF3_8B01_35214FC4B2B5__INCLUDED_)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
// DragDropBitmapButton.cpp : implementation file
// My derived button class (from CBitmapButton)
 
#include "stdafx.h"
//#include "proof_mfc.h"
#include "DragDropBitmapButton.h"
#include "WinUser.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
 
/////////////////////////////////////////////////////////////////////////////
// CDragDropBitmapButton
 
CDragDropBitmapButton::CDragDropBitmapButton()
{
	m_bDragging   = FALSE;
	m_bDown       = FALSE;
	m_WM_DRAGBUTTON = RegisterWindowMessage (_T("WM_DRAGBUTTON"));
}
 
CDragDropBitmapButton::~CDragDropBitmapButton()
{
}
 
 
BEGIN_MESSAGE_MAP(CDragDropBitmapButton, CBitmapButton)
	//{{AFX_MSG_MAP(CDragDropBitmapButton)
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
/////////////////////////////////////////////////////////////////////////////
// CDragDropBitmapButton message handlers
 
void CDragDropBitmapButton::OnMouseMove(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	if(m_bDown)
	{
		// m_bDragging flag is used to skip the first time
		// the mouse button is pressed, otherwise a simple press of the
		// button would cause the WM_DRAGBUTTON message to be sent
		if(m_bDragging)
		{
			m_bDown = FALSE;
			m_bDragging = FALSE;
 
			GetParent()->SendMessage(
				m_WM_DRAGBUTTON,
				(WPARAM) 0,
				(LPARAM) this);
		}
		else
			m_bDragging = TRUE; // for next time through
	}
 
	CString x;
	x.Format(_T("*DragBtn mouse: %d,%d\n"), point.x, point.y);
	OutputDebugString((LPCTSTR(x)));
	
	CBitmapButton::OnMouseMove(nFlags, point);
}
 
void CDragDropBitmapButton::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	m_bDown = TRUE;
	m_bDragging = FALSE;
	OutputDebugString(_T("*DragBtn Left-down\n"));
	
	CBitmapButton::OnLButtonDown(nFlags, point);
}
 
void CDragDropBitmapButton::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	m_bDown = FALSE;
	m_bDragging = FALSE;
	OutputDebugString(_T("*DragBtn Left-up\n"));
 
	CBitmapButton::OnLButtonUp(nFlags, point);
}
 
CBitmap * CDragDropBitmapButton::GetSelectedBitmap()
{
	return &m_bitmapSel;
}
 
/* end of file */
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if !defined(AFX_DRAGDROPBITMAPBUTTON_H__7AA290F9_95BC_46FB_8A23_CB373D7D0CD9__INCLUDED_)
#define AFX_DRAGDROPBITMAPBUTTON_H__7AA290F9_95BC_46FB_8A23_CB373D7D0CD9__INCLUDED_
 
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// DragDropBitmapButton.h : header file
//
 
/////////////////////////////////////////////////////////////////////////////
// CDragDropBitmapButton window
 
class CDragDropBitmapButton : public CBitmapButton
{
// Construction
public:
	CDragDropBitmapButton();
 
// Attributes
public:
 
// Operations
public:
	CBitmap * GetDragImage() { return &m_bitmap; };
 
// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CDragDropBitmapButton)
	//}}AFX_VIRTUAL
 
// Implementation
public:
	virtual ~CDragDropBitmapButton();
 
	// Generated message map functions
protected:
	BOOL m_bDown;
	BOOL m_bDragging;
	CImageList m_imgl;
	UINT m_WM_DRAGBUTTON;
 
	CBitmap * GetSelectedBitmap();
 
	//{{AFX_MSG(CDragDropBitmapButton)
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	//}}AFX_MSG
 
	DECLARE_MESSAGE_MAP()
};
 
/////////////////////////////////////////////////////////////////////////////
 
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
 
#endif // !defined(AFX_DRAGDROPBITMAPBUTTON_H__7AA290F9_95BC_46FB_8A23_CB373D7D0CD9__INCLUDED_)

Open in new window

Right... I don't have a CE emulator (or the experience).

>>The second time you enter the app, the drag image is blank and

Disapearing images is symptomatic of a massive resource leak.  On desktop OSes, I open up the Task Manager and track the handle usage.  If you can do a similar thing on CE, that that would narrow it down considerably.   If it is a resource leak, then you are probably creating thousands of copies of something and failing to delete them when you stop using them -- so look to the parts of the looping action that occur releatedly (I.e., the OnMouseMove parts)
I have not found the issue, yet. I am embarrassed that I did not test the return code of each of the methods. I have since added the appropriate checking and have discovered that the m_imgl.DragEnter(this, cp); (see CDrag_and_drop_cbitmapbuttonDlg::OnDragButton). The value returned by GetLastError is 87 (which is bad parameter). This occurs the second time into the app. I tried commenting out the m_imgl.DragMove(point) in OnMouseMove. And the problem still exists. I can re-create the problem as easily as enter program. Drag one bitmap over just a small amount (like half an inch). Exit and repeat. I instantly see a dialog box appear on the WinCE lcd (I am using remote desktop). I have tried both with and without the remote desktop and no difference.

Anyway I am beginning to believe there is something wrong with the API. I have a copy of the WinCE shell code and it uses icons instead of bitmaps. I am going to modify my example to use icons and see if that fixes my problems.

Thanks!!
Dan, thank you again for all of your help. I fixed my other problem by using a 32 x 32 icon rather than a 48 x 48 icon. I don't know why, I cannot find documentation stating that there is a limit. Again, thank you for all your help!!!
Anthony2000,
Glad to help.  Thanks for the points and the grade.

For future PAQ searchers:
Anthony2000 included this note when accepting the answer:

Dan, thank you again for all of your help. I fixed my other problem by using a 32 x 32 icon rather than a 48 x 48 icon. I don't know why, I cannot find documentation stating that there is a limit. Again, thank you for all your help!!!