TableListBox Rows as DragAndDrop Targets?


#1

Hi Jules (and all). I’m converting my program from Cocoa to JUCE and I’m really enjoying the framework!

I have 2 TableListBoxs, Albums and Recordings, with the following basic requirements.

-Add a Recording to an Album by dragging the recording row over a row in the albums table.
-Sort the recordings for an album by dragging a recording row over/between other recording rows.

I have made a top level container into a DragAndDropContainer and implemented getDragSourceDescription in the Recordings table. That much works.

However, I would like to use rows in each table as DragAndDropTargets, but I can only seem to provide custom Cells using refreshComponentForCell in the model. But also, playing with that, I’ve found that using a DragAndDropTarget as the Recording Cell causes the previously noted drag functionality to stop working.

So, am I missing something? I’m surely not the only one who needs to rearrange rows in a table, but I’ve had a look at the demo, and a few posts here in the forum and haven’t found the answer.

Thanks,
Graeme


#2

Of course as soon as I post this I realize that the custom cell components probably need to handle the mouse events themselves and call startDragging on the DragAndDropContainer. Seems to work. :slight_smile:

Still interested in how to use rows as drop targets if anyone has insight on that.

Thanks!


#3

To make something a target you’d just need to use the DragAndDropTarget class.


#4

Right, thanks Jules. That part I generally understand. Seems my confusion is/was due to refreshComponentForRow being implemented in TableListBox.


#5

Ok to get this straight, to be able to drag and drop on a table list row, I need to implement refreshComponentForRow so that I can provide a custom row Component that inherits DragAndDropTarget. Correct?

Unfortunately it seems when I try to do that, then refreshComponentForCell doesn’t get called. Having a look at the code I can see that refreshComponentForCell is tied to the internal TableListRowComp class which does a fair bit of processing in it’s update method before making the call. Obviously it doesn’t make sense for me to re-implement that logic. Am I on the right track here? Feels like I’m doing a lot of clobbering to accomplish a standard function.

It’s pretty straight forward to enable dragging of table rows, it would be great if the drop side was something similar. Maybe TableListRowComp could inherit DragAndDropTarget with a default non-responding implementation whose responsibilities could be passed to the model if so desired. Just my 2 cents of course, I’m no expert.

Thanks.


#6

No. refreshComponentForCell and drag and drop have nothing to do with each other.

The only reason you want to use refreshComponentForCell is if you have a custom component to represent your row. For 99% of of all ListBox and TableListBox, you can simply draw your data in paintCell() and skip the need for having a custom component.

Custom components are for when you want to have controls or buttons as items in the list. The Juce Demo has an example of this.

All you have to do to get Drag and Drop working is override getDragSourceDescription in your LIstBoxModel or TableListBoxModel and make sure that some parent component in the hierarchy is a DragAndDropContainer.

The Juce Demo provides a clear example of this, I would study it.


#7

If you want your ListBox, TableListBox or other Component to be able to receive dropped items all you need to do is derive your class from DragAndDropTarget and override some or all of these:

    virtual bool isInterestedInDragSource (const String& sourceDescription,
                                           Component* sourceComponent);
    virtual void itemDragEnter (const String& sourceDescription,
                                Component* sourceComponent,
                                int x, int y);
    virtual void itemDragMove (const String& sourceDescription,
                               Component* sourceComponent,
                               int x, int y);
    virtual void itemDragExit (const String& sourceDescription,
                               Component* sourceComponent);
    virtual void itemDropped (const String& sourceDescription,
                              Component* sourceComponent,
                              int x, int y);

For a ListBox or TableListBox, when you get itemDropped called you can just use getRowContainingPosition(x, y) or getInsertionIndexForPosition(x, y) to figure out what row it was dropped on.


#8

I stated in my initial post that I had looked at the demo, that I had dragging working and that I’m trying to make the row component of a TableListBox into a DragAndDropTarget. I even provided a high level use case for my intentions. I tried to be clear. :frowning:

If I shouldn’t be trying to use row components as targets for this task, then I’d expect Jules to steer me away from that in his response. I am confused by this.

Anyway, thanks for suggesting getRowContainingPosition. Something like the following is working for me.

//MyTableListBox : public DragAndDropTarget...

	void itemDropped (const String &sourceDescription, Component *sourceComponent, int x, int y) {
		int row = this->getRowContainingPosition(x,y);
		
		//perform application function with row index...
		
		MyTableModel * model = dynamic_cast<MyTableModel*> (this->getModel());
		model->highlight = false;
		model->highlightRow = -1;
                //invoke paintRowBackground in the model
		this->repaint();		
	}
	bool shouldDrawDragImageWhenOver () {
		return true;
	}
	
	void itemDragEnter (const String &sourceDescription, Component *sourceComponent, int x, int y) {
		
		int row = this->getRowContainingPosition(x,y);
		MyTableModel * model = dynamic_cast<MyTableModel*> (this->getModel());
		model->highlight = true;
		model->highlightRow = row;
		this->repaint();
		
		DBG("Drag Enter");
	}
	void itemDragMove (const String &sourceDescription, Component *sourceComponent, int x, int y) {
		int row = this->getRowContainingPosition(x,y);
		MyTableModel * model = dynamic_cast<MyTableModel*> (this->getModel());
		model->highlight = true;
		if (row != model->highlightRow) {
			model->highlightRow = row;
			this->repaint();
		}
		
		DBG("Drag Move");
	}
	void itemDragExit (const String &sourceDescription, Component *sourceComponent) {
		MyTableModel * model = dynamic_cast<MyTableModel*> (this->getModel());
		model->highlight = false;
		model->highlightRow = -1;
		this->repaint();
	}

#9

This begs the question - why are you using a row Component?


#10

Well in the end I’m not. All I was thinking was that I wanted a row to accept a drop. It seemed logical that I should be able to.

Also when I researched the forum to find out more (after the docs, after the demo), I came upon this post where Jules suggests to make the rows themselves into drop targets:
http://www.rawmaterialsoftware.com/viewtopic.php?f=2&t=1766&hilit=DragAndDropTarget

So from that not only am I thinking that I should continue down this path, but also that I should maybe steer clear of just using the table as the drop. But then again it’s an older post. I guess that’s how I came to asking about it.

When I initially wrote my app it was on WinCE and I had to develop the UI stack myself. So I’ve done at least enough framework dev to know when I’m working against a framework. It’s seems pretty evident now that a person shouldn’t be overriding refreshComponentForRow unless they have a very specific need, but then the question begging to be asked is why even use a table at that point. I trust there are indeed use cases where it’s necesarry though, just not this one.

I’d also suggest that a distinction be made in the API docs for getDragSourceDescription.

[quote]To allow rows from your table to be dragged-and-dropped, implement this method.

If this returns a non-empty name then when the user drags a row, the table will try to find a DragAndDropContainer in its parent hierarchy, and will use it to trigger a drag-and-drop operation, using this string as the source description, and the listbox itself as the source component.[/quote]

It only allows your rows to be dragged, not dropped.


#11

row Components are for when you want to put controls in listbox rows or columns. For example, a drop down menu as a control in a list item.

For simply displaying information in the list, without the need for having a control, it is only necessary to override paintCell, and do away with row Components completely.


#12

Then why is refreshComponentForRow reimplemented in TableListBox? heh…I’m not sure I want to get into that, because I also don’t understand why TableListBox implements that method, when consistency suggests that it would be found in the model. e.g. ListBoxModel::refreshComponentForRow and TableListBoxModel::refreshComponentForCell.

Also, of course, the green light response from Jules certainly suggested that working with a row for this purpose is not foolish.

I should say that I am using refreshComponentForCell to be able to use an inline TextEditor as necessary. So I do understand that.


#13

Good point - it’s actually not for public use (and is clearly marked as “internal”), but should probably be made private to avoid any confusion…


#14

It’s not listed in the online docs as being internal.

[quote]Component* TableListBox::refreshComponentForRow ( int rowNumber,
bool isRowSelected,
Component * existingComponentToUpdate
) [virtual]
This is used to create or update a custom component to go in a row of the list.

Any row may contain a custom component, or can just be drawn with the paintListBoxItem() method and handle mouse clicks with listBoxItemClicked().

This method will be called whenever a custom component might need to be updated - e.g. when the table is changed, or TableListBox::updateContent() is called.

If you don’t need a custom component for the specified row, then return 0.

If you do want a custom component, and the existingComponentToUpdate is null, then this method must create a suitable new component and return it.

If the existingComponentToUpdate is non-null, it will be a pointer to a component previously created by this method. In this case, the method must either update it to make sure it’s correctly representing the given row (which may be different from the one that the component was created for), or it can delete this component and return a new one.

The component that your method returns will be deleted by the ListBox when it is no longer needed.

Reimplemented from ListBoxModel.[/quote]


#15

Ah, that must be doxygen getting over-enthusiastic and pulling the parent class’s comments in - have a look at the actual header file and you’ll see more clearly what I’m doing there. It’s nothing fancy, the table just implements a ListBoxModel itself so that it can use it to do all its table-stuff. I certainly didn’t expect/want people to go overriding that stuff, and on reflection, I should probably have put all the ListBoxModel overrides inside a Pimpl class, to keep them away from harm.