reorder wxTreeCtrl (drag in spaces between nodes) Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
mael15
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 542
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

reorder wxTreeCtrl (drag in spaces between nodes)

Post by mael15 »

hi everyone,
i want to have drag and drop inside a wxTreeCtrl, but the way it is now, only nodes can be highlighted as the drag target, not the space between. so, can i somehow:
a) disable highlighting a node when some other node is dragged on top of it
b) show a line between nodes so the user can see where the dragged node would end up
thanx!
mael15
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 542
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

Re: reorder wxTreeCtrl (drag in spaces between nodes)

Post by mael15 »

got it to work.
just in case anyone has the same problem:
a) do not use wxTreeCtrl´s own EVT_TREE_BEGIN_DRAG events, so the item under the dragging mouse is not highlighted. instead, use wxDragImage (see documentation) although i am not sure why it should be of any more use than a simple wxStaticBitmap.
b) to show a line, in the EVT_MOTION while dragging use HitTest to see if the mouse is over a treeItem and if so, get its position with GetBoundingRect and place a line below/above the item. i reposition a custom marker derived from wxPanel because i had problems with flickering when drawing in the wxTreeCtrl´s paint event.
User avatar
xaviou
Super wx Problem Solver
Super wx Problem Solver
Posts: 437
Joined: Mon Aug 21, 2006 3:18 pm
Location: Annecy - France
Contact:

Re: reorder wxTreeCtrl (drag in spaces between nodes)

Post by xaviou »

Hi.

Thank you for sharing =D>

Perhaps (if you have time for this) could you post the code of a "minimal" sample to easily see it in action.

Regards
Xav'
My wxWidgets stuff web page : X@v's wxStuff
mael15
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 542
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

Re: reorder wxTreeCtrl (drag in spaces between nodes)

Post by mael15 »

treeSpaceScrennshot.jpg
treeSpaceScrennshot.jpg (8.97 KiB) Viewed 2760 times
here is the minimal project working in msw, i ignored encapsulation to keep it simpler:

Code: Select all

#ifndef __app_h__
#define __app_h__

#include <wx/treectrl.h>
#include <wx/dragimag.h>
#include <wx/dcbuffer.h>

// little red arrow to the left/right to mark the drag position
class PositionMarker : public wxPanel {
public:
	PositionMarker(wxWindow *par, bool isRight) : 
			wxPanel(par, wxID_ANY, wxDefaultPosition, wxSize(7, 7)){
		Connect(wxEVT_PAINT, wxPaintEventHandler(PositionMarker::onPaint));

		if (isRight){
			points[0] = wxPoint(7, 0);
			points[1] = wxPoint(7, 7);
			points[2] = wxPoint(0, 3);
		}
		else {
			points[0] = wxPoint(0, 0);
			points[1] = wxPoint(7, 3);
			points[2] = wxPoint(0, 7);
		}
	}
private:
	void onPaint(wxPaintEvent &evt){
		wxBufferedPaintDC dc(this);
		dc.SetBrush(*wxWHITE_BRUSH);
		dc.SetPen(*wxTRANSPARENT_PEN);
		dc.DrawRectangle(wxPoint(0, 0), GetClientSize());

		// draw arrow
		dc.SetBrush(*wxRED_BRUSH);
		dc.DrawPolygon(3, points);
	}

	wxPoint points[3] = {};
};

// custom wxTreeCtrl for handling the mouse events
class MyTree : public wxTreeCtrl {
public:
	// this is to separate a single click on an item from
	// dragging so that dragging only really starts in EVT_MOTION
	typedef enum {
		DRAG_NONE,
		DRAG_START,
		DRAGGING
	} TDragState;

	MyTree(wxWindow *par) : wxTreeCtrl(par, wxID_ANY, wxDefaultPosition, wxSize(200, -1),
		wxTR_HAS_BUTTONS | wxTR_LINES_AT_ROOT | wxTR_EDIT_LABELS){
		Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(MyTree::onLeftDown));
		Connect(wxEVT_LEFT_UP, wxMouseEventHandler(MyTree::onLeftUp));
		Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(MyTree::onLeaveWindow));
		Connect(wxEVT_MOTION, wxMouseEventHandler(MyTree::onMouseMotion));

		AddRoot(_("root"));
		AppendItem(GetRootItem(), _("one"));
		AppendItem(GetRootItem(), _("two"));
		AppendItem(GetRootItem(), _("three"));
		AppendItem(GetRootItem(), _("four"));
		AppendItem(GetRootItem(), _("five"));
		Expand(GetRootItem());

		posMarkerLeft = new PositionMarker(this, false);
		posMarkerLeft->Hide();
		posMarkerRight = new PositionMarker(this, true);
		posMarkerRight->Hide();
	}
	~MyTree(){
		delete posMarkerLeft;
		delete posMarkerRight;
	}

	// two red arrows to mark the insert position
	PositionMarker *posMarkerLeft = nullptr, *posMarkerRight = nullptr;

	// the image of the item being dragged shown under the cursor
	wxDragImage *dragImg = nullptr;
private:
	TDragState state = DRAG_NONE;

	// the item being dragged at the moment
	wxTreeItemId itemDragging = nullptr;

	void onLeftDown(wxMouseEvent &evt);
	void onLeftUp(wxMouseEvent &evt);
	void onLeaveWindow(wxMouseEvent &evt){
		if (dragImg)
			endDragging();
	}

	// finish the drag action 
	void endDragging(){
		state = DRAG_NONE;
		dragImg->EndDrag();
		dragImg->Hide();
		if (dragImg)
			delete dragImg;
		dragImg = nullptr;
		itemDragging = nullptr;
	}

	void onMouseMotion(wxMouseEvent &evt);
};


class MyFrame1 : public wxFrame 
{
public:
	MyFrame1( wxWindow* parent = NULL, 
		wxWindowID id = wxID_ANY, 
		const wxString& title = wxEmptyString, 
		const wxPoint& pos = wxDefaultPosition, 
		const wxSize& size = wxSize( 300,300 ), 
		long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );

private:
	MyTree *tree = nullptr;
	
};

class App : public wxApp {
	virtual bool OnInit();
	virtual int OnExit();
};
#endif

Code: Select all

#include "app.h"

bool App::OnInit() {
	MyFrame1 *mainFrame = new MyFrame1();
	mainFrame->Show();
	SetTopWindow(mainFrame);

	return TRUE;
}

int App::OnExit() {
	return 0;
}

IMPLEMENT_APP(App)

MyFrame1::MyFrame1( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : 
	wxFrame( parent, id, title, pos, size, style )
{
	tree = new MyTree(this);
	
	wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
	sizer->Add(tree, 1, wxEXPAND);
	SetSizer(sizer);

	this->SetSizeHints( wxDefaultSize, wxDefaultSize );
	this->Centre( wxBOTH );
}

void MyTree::onLeftDown(wxMouseEvent &evt)
{
	state = DRAG_START;
}

void MyTree::onMouseMotion(wxMouseEvent &evt)
{
	this;
	wxPoint evtPos = evt.GetPosition();
	MyTree *mt = (MyTree*)evt.GetEventObject();
	wxTreeItemId itemHovered = HitTest(evtPos);

	// switch to dragging mode if mouseDown was on item
	if (state == DRAG_START){
		itemDragging = HitTest(evtPos);
		if (itemDragging){
			// create dragImage
			if (dragImg)
				delete dragImg;
			dragImg = new wxDragImage(this, itemDragging);

			// position the dragImage right on top of the dragged item and
			// start dragging
			wxRect rect;
			GetBoundingRect(itemDragging, rect, true);
			dragImg->BeginDrag(evtPos - rect.GetLeftTop(), this);
			dragImg->Show();

			state = DRAGGING;
		}
		else
			state = DRAG_NONE;
	}

	// show markers only when dragging over an item
	bool showMarkers = state == DRAGGING && itemHovered && itemHovered != GetRootItem();
	posMarkerLeft->Show(showMarkers);
	posMarkerRight->Show(showMarkers);

	if (showMarkers){
		dragImg->Hide();

		wxRect rect;
		GetBoundingRect(itemHovered, rect, true);

		if (evtPos.y > rect.GetTop() + rect.GetHeight() / 2){
			// below
			posMarkerLeft->SetPosition(rect.GetBottomLeft() - wxPoint(7, 3));
			posMarkerRight->SetPosition(rect.GetBottomRight() - wxPoint(0, 3));
		}
		else {
			// above
			posMarkerLeft->SetPosition(rect.GetTopLeft() - wxPoint(7, 3));
			posMarkerRight->SetPosition(rect.GetTopRight() - wxPoint(0, 3));
		}

		mt->Refresh(false);
		mt->Update();

		dragImg->Show();
		dragImg->Move(evtPos);
	}
}

void MyTree::onLeftUp(wxMouseEvent &evt)
{
	wxPoint evtPos = evt.GetPosition();
	wxTreeItemId itemUnderMouse = HitTest(evtPos), previous;

	if (itemUnderMouse && itemUnderMouse != GetRootItem()){
		wxRect rect;
		GetBoundingRect(itemUnderMouse, rect, true);

		if (evtPos.y > rect.GetTop() + rect.GetHeight() / 2){
			// below
			previous = itemUnderMouse;
		}
		else {
			// above
			previous = GetPrevSibling(itemUnderMouse);
		}

		InsertItem(GetRootItem(), previous, GetItemText(itemDragging));
		Delete(itemDragging);
	}

	if (dragImg)
		endDragging();
}
in my opinion this should be a feature for wxTreeCtrl, i have requested it with an enhancement ticket.
tonyvsuk
Knows some wx things
Knows some wx things
Posts: 41
Joined: Wed Oct 19, 2011 12:41 pm

Re: reorder wxTreeCtrl (drag in spaces between nodes)

Post by tonyvsuk »

I just wanted to say thanks for this. A few little tweaks and I've got it working in my code.

Some notes to others
1. On the Mac, you need to edit the endDragging() function and swap the order of dragImg->EndDrag(); and dragImg->Hide();
2. On the Mac, I cannot get the drag image to display for some reason.
mael15
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 542
Joined: Fri May 22, 2009 8:52 am
Location: Bremen, Germany

Re: reorder wxTreeCtrl (drag in spaces between nodes)

Post by mael15 »

i am happy that it could help someone! i have updated it myself since then and am still using it. i hope something like this will be part of wxwidgets someday. O:)
Post Reply