Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Tutorials
Bazaar
Status & Roadmap
FAQ
Authors & License
Forums
Funding Ultimate++
Search on this site
Search in forums












SourceForge.net Logo
Home » U++ Library support » TreeCtrl » Restrict drag&drop to one level
Restrict drag&drop to one level [message #25209] Sun, 14 February 2010 11:14 Go to next message
dolik.rce is currently offline  dolik.rce
Messages: 1757
Registered: August 2008
Location: Czech Republic
Senior Contributor

Hello!

I've got a little problem here. I want to have a TreeCtrl with drag and drop implemented in such a way, that items from one level can not be inserted into different level. An example to illustrate:
+ Graphs
  + Graph1
  | + Data1
  | + Data2
  + Graph2
    + Data3
I need to allow user to drag any Data into any Graph and disable dropping it into any other level. Also, any Graph must not be dropped anywhere else then top level.

I think this is quite common situation, so there should be easy solution. I'm probably just missing something Smile Bellow is the code I got so far, but it has two problems. First, it doesn't work properly with MultiSelect(). Second, it paints insert marks even in positions where drop is not allowed, which might confuse user. I hope someone can give me some hint how to solve this. The code:
#include "CtrlLib/CtrlLib.h"
using namespace Upp;

struct App : TopWindow {
	typedef App CLASSNAME;
	TreeCtrl   tree;

	void DropInsert(int parent, int ii, PasteClip& d){
		if(AcceptInternal<TreeCtrl>(d, "graph")) {
			if(GetLevel(parent)!=0) {d.Reject();return;}
			tree.InsertDrop(parent, ii, d);
			tree.SetFocus();
			LOG("accepted graph");
			return;
		}
		if(AcceptInternal<TreeCtrl>(d, "data")) {
			if(GetLevel(parent)!=1) {d.Reject();return;}
			tree.InsertDrop(parent, ii, d);
			tree.SetFocus();
			LOG("Accepted series");
			return;
		}
	}

	void Drag()	{
		int type=GetLevel(tree.GetCursor());
		switch(type) {
		case 1:
			if(tree.DoDragAndDrop(InternalClip(tree, "graph"),tree.GetDragSample()) == DND_MOVE)
				tree.RemoveSelection();
			break;
		case 2:
			if(tree.DoDragAndDrop(InternalClip(tree, "data"),tree.GetDragSample()) == DND_MOVE)
				tree.RemoveSelection();
			break;
		}
	}
	
	int GetLevel(int id){
		int i=0;
		while(id!=0){
			id=tree.GetParent(id);
			i++;
		}
		return i;
	}
	
	App() {
		Add(tree.SizePos());
		tree.SetRoot(Image(), "Graphs");
		for(int i=1;i<3;i++){
			int id=tree.Add(0,Image(),"Graph "+AsString(i));
			for(int j=1;j<3;j++)
				tree.Add(id,Image(),"Data "+AsString(i)+"/"+AsString(j));
		}
		tree.OpenDeep(0);
		tree.WhenDropInsert = THISBACK(DropInsert);
		tree.WhenDrag = THISBACK(Drag);
		tree.MultiSelect();
		Sizeable();
	}
};

GUI_APP_MAIN
{
	App().Run();
}


Best regards,
Honza
Re: Restrict drag&drop to one level [message #26101 is a reply to message #25209] Thu, 01 April 2010 13:22 Go to previous messageGo to next message
mrjt is currently offline  mrjt
Messages: 705
Registered: March 2007
Location: London
Contributor
A bit late replying, but it's an interesting situation and as you say someone else may want the same behaviour.

The DnD stuff is quite elegant but unfortunately quite difficult to understand. We can't all be as smart as Mirek Smile

Basically you have to know that Accept<> and AcceptInternal<> serve a dual function. When the user is dragging (ie on MouseMove) the function does the accept but always returns false so that when you use it in an 'if' statement the actual insert code is avoided. Only when actually doing the 'drop' does it ever return true!

Therefor, to add the level check you must reproduce some of the behaviour from the Accept<> function:
	void DropInsert(int parent, int ii, PasteClip& d){
		// Check type of drag data, and restrict to level
		if (IsAvailableInternal<TreeCtrl>(d, "graph") && GetLevel(parent) == 0) {
			// Yes we like this data
			d.Accept();
			// If we haven't dropped the data yet (we are still dragging) don't do anything
			if (!d.IsPaste()) return;
                        // The user has dropped it! Do the insert
			tree.InsertDrop(parent, ii, d);
			tree.SetFocus();
			LOG("accepted graph");
			return;
		}
		if (IsAvailableInternal<TreeCtrl>(d, "data") && GetLevel(parent) == 1) {
			d.Accept();
			if (!d.IsPaste()) return;
			tree.InsertDrop(parent, ii, d);
			tree.SetFocus();
			LOG("accepted data");
			return;
		}
	}

You could replace the 'd.Accept' and 'if (d.IsPaste())' with:
if (!AcceptInternal<TreeCtrl>(d, "data")) return;

but it's marginally less efficient.

Smile

[Updated on: Thu, 01 April 2010 13:23]

Report message to a moderator

Re: Restrict drag&drop to one level [message #26114 is a reply to message #26101] Thu, 01 April 2010 20:55 Go to previous messageGo to next message
dolik.rce is currently offline  dolik.rce
Messages: 1757
Registered: August 2008
Location: Czech Republic
Senior Contributor

Hello James,
Thanks for your answer! Better late than never Wink I still didn't solve it, so your hints are more than welcome. I'll report here when I get time to test it...

Thank you,
Honza
Re: Restrict drag&drop to one level [message #26115 is a reply to message #26101] Thu, 01 April 2010 23:59 Go to previous messageGo to next message
dolik.rce is currently offline  dolik.rce
Messages: 1757
Registered: August 2008
Location: Czech Republic
Senior Contributor

It works almost perfect. I was missing the IsAvailableInternal<> function, that helps a lot Smile But few problems still remain:

First, as I am using MultiSelect(), the level restriction fails when multiple levels are in selection. I solved this by adding following function as WhenSel callback:
void CheckSel(){
		Vector<int> sel=tree.GetSel();
		int last=sel.GetCount()-1;
		if (last<1) return;
		if (GetLevel(sel[0])!=GetLevel(sel[last]))
			tree.SelectOne(sel[last],false);
	}

It is not perfect solution, but works reasonably with minimal effort.

Another problem is, that if you drag data item to root (or anywhere below the tree), it disappears on drop. I think it is because it just slips through the restriction rules without triggering any of them, but I couldn't find how to prevent that. Confused Any ideas?

Best regards,
Honza
Re: Restrict drag&drop to one level [message #26177 is a reply to message #26115] Thu, 08 April 2010 12:58 Go to previous message
mrjt is currently offline  mrjt
Messages: 705
Registered: March 2007
Location: London
Contributor
I think this selection filter would work better:
	void OnSel() {
		Vector<int> sel = tree.GetSel();
		int level = GetLevel(tree.GetCursor());
		for (int i = 0; i < sel.GetCount(); i++)
			if (GetLevel(sel[i]) != level)
				tree.SelectOne(sel[i], false);
	}

It works with both Ctrl select and Shift select and preserves the most recently selected.

I don't have thwe problem you describe with draggin to root/the bottom of the tree. Have you tested with the latest SVN?
Previous Topic: Distorted GUI / memory leak
Next Topic: Suppress drawing the root 0 node
Goto Forum:
  


Current Time: Thu Apr 25 18:41:31 CEST 2019

Total time taken to generate the page: 0.00913 seconds