TreeView Multiple Selection With LMB



  • Hello! I have a problem with TreeViewFunctions. When i try to select multiple lines with my mouse i get instant crash.
    Here is my Select function:

    void Tree::Select(void* root, void* userdata, void* obj, Int32 mode)
    {
    	TreeNode* res = (TreeNode*)obj;
    	if (!res) return;
    	if (!res->root) {
    		switch (mode)
    		{
    		case SELECTION_NEW:
    			parent->clickID.clear();
    			parent->SelectClick(&(*res->arrayCell));
    			break;
    		case SELECTION_ADD:
    			parent->SelectClick(&(*res->arrayCell));
    			break;
    		case SELECTION_SUB:
    			parent->clickID.erase(std::find(parent->clickID.begin(), parent->clickID.end(), &(*res->arrayCell)));
    			break;
    		}
    	}
    	else {
    		switch (mode)
    		{
    		case SELECTION_NEW:
    			parent->objID.clear();
    			parent->SelectObject(&(*res->cell));
    			break;
    		case SELECTION_ADD:
    			parent->SelectObject(&(*res->cell));
    			break;
    		case SELECTION_SUB:
    			parent->objID.erase(std::find(parent->objID.begin(), parent->objID.end(), &(*res->cell)));
    			break;
    		}
    	}
    }
    

    And Tree class with TreeNode:

    class TreeNode {
    public:
    	Bool root = false, open = false;
    
    	MainMap::iterator cell;				 
    	MainMapCellArray::iterator arrayCell;
    	
    	TreeNode* next = nullptr;
    	TreeNode* pred = nullptr;
    	TreeNode* down = nullptr;
    
    };
    
    class Tree : public TreeViewFunctions {
    public:
    	ManageDialog* parent;
    	MainMap* lst;
    	Int32 currObj;
    	Int32 level = 0;
    	virtual void* GetFirst(void* root, void* userdata);
    	virtual void* GetNext(void* root, void* userdata, void* obj);
    	virtual void* GetDown(void* root, void* userdata, void* obj);
    	virtual void DrawCell(void* root, void* userdata, void* obj, Int32 col, DrawInfo* drawinfo, const GeData& bgColor);
    	virtual Bool IsSelected(void* root, void* userdata, void* obj);
    	virtual Bool IsOpened(void* root, void* userdata, void* obj);
    	virtual String GetName(void* root, void* userdata, void* obj);
    	virtual Int GetId(void* root, void* userdata, void* obj);
    	virtual void* GetPred(void* root, void* userdata, void* obj);
    	virtual void CreateContextMenu(void* root, void* userdata, void* obj, Int32 	lColumn, BaseContainer* bc);
    	virtual Bool ContextMenuCall(void* root, void* userdata, void* obj, Int32 	lColumn, Int32 	lCommand);
    	virtual void Select(void* root, void* userdata, void* obj, Int32 mode);
    	virtual void Open(void* root, void* userdata, void* obj, Bool onoff);
    	virtual void InsertObject(void* root, void* userdata, void* obj, Int32 dragtype, void* dragobject, Int32 insertmode, Bool bCopy);
    	virtual Int32 AcceptDragObject(void* root, void* userdata, void* obj, Int32 dragtype, void* dragobject, Bool& bAllowCopy);
    	virtual Int32 GetDragType(void* root, void* userdata, void* obj);
    	virtual Int32 GetHeaderColumnWidth(void* root, void* userdata, Int32 col, GeUserArea* area);
    	virtual void SetName(void* root, void* userdata, void* obj, const maxon::String& str);
    	virtual Int32 DoubleClick(void* root, void* userdata, void* obj, Int32 	col, MouseInfo* mouseinfo);
    };
    

    Few questions:
    When i select with mouse drag, what does Select get as obj? Is that an array of TreeNodes or TreeView recursively calls Select for each element?

    When i do return; at start of Select i still get crash, i suppose problem can be in something else. What should i do to make it work properly?

    Thanks!



  • Hi @Danchyg1337 on our side everything work nicely,
    Unfortunately with the code provided, I can't help you since I don't have everything. So if you could provide us a simplified version that reproduces the issue I could look at it.

    Just a side note looking at your code doing such a SelectObject(&(*res->cell)); is not very safe. Moreover, you seem to never check for nullptr, so please do it and it should resolve the crash.

    Cheers,
    Maxime.



  • @m_adam Thanks for the reply! About SelectObject(&(*res->cell));. Inside of SelectObject() i check for a nullptr at the very start, and i hope this

    TreeNode* res = (TreeNode*)obj;
    if (!res) return;
    

    is enogh to prevent nullptr in Select. Maybe i just misunderstood you about nullptr, correct me then please.

    I'm not sure what should i send you as simpified version, because there is a bunch of code and i don't know which function causes it. I supposed it was Select, but aparently problen is not in it. What about my virtual functions? Could i miss some important virtual function?



  • Yes, but you don't check for arrayCell.

    If you could provide the minimal piece of code that still reproduce your crash, this way I could reproduce the same issue you are facing and can try to help you (help us to help you).
    So at least GetNext/GetFirst/GetDown/IsSelected/GetName/GetID/GetPred/Select methods.

    If you are interested in Treeview you may find valuable information in:

    Cheers,
    Maxime.



  • @m_adam Here are required functions:

    void* Tree::GetFirst(void* root, void* userdata)
    {
    	TreeNode* obj = (TreeNode*)root;
    	return obj;
    }
    
    void* Tree::GetNext(void* root, void* userdata, void* obj)
    {
    	TreeNode* res = (TreeNode*)obj;
    
    	if (res && res->next)
    		return res->next;
    	return NULL;
    }
    
    void* Tree::GetPred(void* root, void* userdata, void* obj)
    {
    	TreeNode* res = (TreeNode*)obj;
    
    	if (res && res->pred)
    		return res->pred;
    	return NULL;
    }
    
    void* Tree::GetDown(void* root, void* userdata, void* obj)
    {
    	TreeNode* res = (TreeNode*)obj;
    	if (res && res->root && res->down)
    		return res->down;
    
    	return NULL;
    }
    
    String Tree::GetName(void* root, void* userdata, void* obj)
    {
    	if (!obj) return "";
    	TreeNode* res = (TreeNode*)obj;
    	if (res->root) {
    		String name = res->cell->second.GetString(OBJSET_NAME);
    		if (res->cell->second.GetInt32(OBJSET_ID))
    			return name;
    		else {
    			Filename fn(name);
    			return fn.GetFile().GetString();
    		}
    	}
    	return std::get<2>(*(res->arrayCell)).GetString(DESCPREF_NAME);
    }
    
    Int Tree::GetId(void* root, void* userdata, void* obj)
    {
    	return (Int)obj;
    }
    
    Bool Tree::IsSelected(void* root, void* userdata, void* obj)
    {
    	TreeNode* res = (TreeNode*)obj;
    	if (!res) return false;
    	if (res->root) 
    		return std::find(parent->objID.begin(), parent->objID.end(), &(*res->cell)) != parent->objID.end();
    	return std::find(parent->clickID.begin(), parent->clickID.end(), &(*res->arrayCell)) != parent->clickID.end();
    }
    

    And Select() from post above. None of my attempts to check for nullptr helped, so nothing new to provide for that function.
    My code with stuff like &(*res->cell)) and &(*res->arrayCell)) may look unsafe, but it works as intended. If i select with clicking and holding SHIFT button, everything works fine, but when i try to drag LMB i get instant crash even when there are no objects in drag rectangle.

    Thanks!



  • "When i select with mouse drag, what does Select get as obj?"
    Normally Select shouldn't be called when you hold a click while dragging.

    Just to clarify and I should have asked first, but how do you select multiple items?

    1. By clicking all of them individually.
      • Select will be called each time you click.
    2. By clicking on one entry, then SHIFT + click on another one.
      • For the first click, Select will be called with SELECTION_NEW.
      • For the second click, Select will be called over each other entry with SELECTION_ADD.
    3. By clicking on one entry, holding the click, drag the mouse down/up, and release the mouse.
      • I'm not sure I would need to try. But in any case, DragStart will be called to see what to do with the currently selected object and if they are allowed to be dragged. But you can return only TREEVIEW_DRAGSTART_SELECT this way object will be selected (but I'm not sure) or you will need to directly override MouseDown to implement this behavior.

    Note that I overlooked, the fact you didn't override DragStart. Which define what to do when a drag operation is starting, Selection state, and if the object can be dragged.

    But just to recap the dragging process:

    • Drag is starting from the TreeView:
      • DragStart Called when the Drag operation start, to know what to do. Should return TREEVIEW_DRAGSTART_ALLOW and/or TREEVIEW_DRAGSTART_SELECT to inform what to do.
      • GetDragType Called after DragStart. Should return the drag type the current cell is representing.
      • According to the type returned by GetDragType:
      • SetDragObject is called, so we can store the dragged obj if we want to.
    • Drop received:
      • AcceptDragObject is called to see if we accept the drag object. obj can be any kind, this is why you need to check the value of dragtype. Should return, if obj should be inserted and where it should be.
      • InsertObject is the called (if it's an array-like in the case of DRAGTYPE_ATOMARRAY, the function will be called once, with obj* being an AtomArray). It's up to you to do proper insertion within your structure.
      • A Refresh and a redraw will be done to update the treeview according to the new internal Data Structure.

    In any case I can't really help you more, I still miss, MainMap, MainMapCellArray class InsertObject, AcceptDragObject, GetDragType, DragStart.
    And please provide also the GeDialog or at least the option you used to register the treeview.

    Hope this help a bit more,
    Cheers,
    Maxime.



  • @m_adam I select them with third case, by clicking on one, holding, dragging and releasing mouse.
    Thanks for useful information, i'm gonna try everything what you suggesed and answer on my results here.



  • @m_adam Maybe i'm little misunderstood you. I meant not exactly third case. But a case, when you start holding your mouse at empty space and selecting objects with outcoming rectangle.



  • Oki doki, I forget this case, so normally what will happen is:

    1. Select with SELECTION_SUB will be called for each entry.
    2. Select with SELECTION_NEW will be called for the uppermost entry
    3. Select with SELECTION_ADD will be called for each bellow entries.

    So it should work as expected if your methods are safe.
    Does unselect by ctrl+click on a selected entry work for you? (Looks like SELECTION_NEW, and SELECTION_ADD works so the only left is SELECTION_SUB).

    Without your code, I can't help you more.

    Cheers,
    Maxime.



  • @m_adam Yes. I just checked that. A select few items with shift+click, and unselect with ctrl+click. Everything works fine.

    I want to provide you my code, but there is a messy structure like this

    typedef std::tuple<DescID, Int32, BaseContainer> MainMapCellArrayCell;
    typedef std::list<MainMapCellArrayCell> MainMapCellArray;
    typedef std::list<std::pair<MainMapCellArray, BaseContainer>> MainMap;
    typedef std::pair<MainMapCellArray, BaseContainer> MainMapCell;
    

    And i'm not sure it will be efficient way to help. Can i send you whole project to email?



  • Of course, having the whole project would probably more helpful, you can send it to sdk_support@maxon.net, and if needed we can sign an NDA.

    Cheers,
    Maxime.



  • Hi after digging into your code, the main issue is that your root defined by SetRoot is also your first object.
    So you should rethink how you structure your internal data structure.
    I know this is misleading, but root, in the treeviewfunction expects an object that hosts the first object(like the dialog) but the root is not the first object.

    Note the issue is a more general issue that could lead you to other problems, and in fact, you can reproduce your crash when you click on nothing, the drag operation is fine.
    Since internally there are several places where the next logic is used

    DoAction(void* pItem)
    {
    (pItem == root)
       pItem = pItem->GetFirst()
    else
       pItem = pItem->GetDown()
    
    for (; pItem; pItem = functions->GetNext(root, userdata, pItem))
        DoAction(pItem);
    }
    

    So in your case, this creates an infinite loop and at the end, create a stack overflow.
    pItem is the root and the first object. So if you call DoAction with pItem == root,
    pItem == GetFirst == the previous pItem so in fact we didn't changed the object.
    Then we arrive in the loop, and the first step is to call DoAction with pItem, so we get the infinite loop.

    Cheers,
    Maxime



  • @m_adam Now i see the problem. Thanks alot for helping!