Reorder buttons by drag and drop

Become a member of the CGSociety

Connect, Share, and Learn with our Large Growing CG Art Community. It's Free!

Thread Tools Search this Thread Display Modes
Old 03 March 2013   #1
Reorder buttons by drag and drop


Do you think that would be possible to implement a "drag and drop and reorder" behavior on some buttons in a columnLayout? (something similar to the way you can reorder display and render layers)
I've been trying it for a while but with no success and I would like to do it only with standard Maya libraries (no PyQt) if possible.

Old 03 March 2013   #2
It is possible. I did this in one of my scripts.

The solution I came up with works as follows:

Let's say you have a columnLayout populated with a bunch of buttons (button1, button2,... button10).

The user starts dragging button3 and drops it on button7.

At this point you need to decide for yourself how this works. Should button3's new position now be before or after button7?
Let's say we want it to be before, so Button3 should become Button6.

Since there is no way to insert a child UI (e.g a button) at a certain index among the parent's (e.g. a layout) child-array, we need to get creative.

What I did was:
  1. Create 2 temporary columnLayouts (-manage 0, so it's hidden), e.g. "buttonsTemp" and "dragTemp".
  2. Starting from the top (index 0), move (i.e. reparent) the original columnLayout's children, i.e. the buttons, one by one into the buttonsTemp layout. The exception here is the button that was dragged (button3). That one goes into the 'dragTemp' layout.
  3. The original layout is now empty, the 'buttonsTemp' is a duplicate of its original state (minus the dragged button).
  4. Now, we simply move every button in 'buttonsTemp' back into the original layout (the order is retained this way).
    Exception: While moving every button, we look at them and check whether this is the drop button (button7). If so, then before moving it, we insert the dragged button from 'dragTemp' in to the original layout, then continue proceeding with the rest.
  5. Done. The original layout now contains a list of buttons with the same indices, except for the dragged button, which now sits right before the dropped button.

Here's some pseudocode:

create columnLayout: 'originalLayout' create button: 'button1'(parent=$originalLayout, dropCallback=moveButton()) create button: 'button2'(parent=$originalLayout, dropCallback=moveButton()) create button: 'button3'(parent=$originalLayout, dropCallback=moveButton()) [...] create button: 'button10'(parent=$originalLayout, dropCallback=moveButton()) list originalLayout.childArray # button1, button2, button3, button4, button5, button6, button7, button8, button9, button10 def moveButton(dragged, dropped): create columnLayout: 'tempLayout' create columnLayout: 'draggedLayout' # move buttons to tempLayouts for button in originalLayout: is button = dragged: reparent button to draggedLayout else: reparent button to tempLayout # move them back in order for button in tempLayout # if this isn't the dropped, just move it is button != dropped reparent button to originalLayout # if this is the dropped button, first insert the dragged button, then proceed as usual else: reparent (dragged from draggedLayout) to originalLayout #first the dragged... reparent button to originalLayout #then the dropped delete tempLayout, draggedLayout list originalLayout.childArray # button1, button2, button4, button5, button6, button3, button7, button8, button9, button10

Hope this helps. Maybe there are better solutions (I did this in MEL), but if you have questions, just reply. I can also show you my original code if you like.
Old 03 March 2013   #3
Hey Nyro!

Thank you very much! This is a great help!
I would have never thought about hidden layouts and I was getting crazy trying to refresh the window.
I will give it a try right now, nevertheless, being able to play with your code would be a great help.

Thanks again!
Old 03 March 2013   #4
Okay, here's my code.

But first a few pointers:

- It's straight from my script, so the context may be a bit confusing

- My buttons are not parented directly to the columnLayout; they each have their own columnLayout in which they reside; so I'm actually moving around columnLayouts.

- I have implemented an extra functionality that determines the 'drop zone', i.e. whether a button is dropped on the upper or lower half of another button; it will be inserted before or after that button, respectively. This is realized by passing along the coordinate $y from the dropCallback.

- The dropControl is a layout (the columnLayout the button resides in), the dragControl is a button.

Ok, here goes:

proc aweCPMovePicker(string $dragCtrl, string $dropCtrl, int $y) { // move a Picker to a new location /* // Dragging dragCtrl onto dropCtrl will insert it either // before or after dropCtrl, depending on the dropLocation $y // We do this by moving all btnLayouts into a temp Layout, then // reparenting them to the tabLayout one by one in the same order; // the exception is the $drop button; when reparenting this, we simultaneously // reparent the $drag button (either before or after, depending on $y) // // Note that we're using the shortNames of our UI elements and constructing our // own fullPathNames so we can be sure that // a) we're referring to the right UI (i.e. in the correct tab) and // b) we can refer to that same UI once we've parented it to a temp layout, at // which point it's original FPN ($dragCtrl) will be invalid // // $dropCtrl is assumed to be a layout */ string $dropBtn = ($dropCtrl + "|CPPickerBtn"); if($dragCtrl == $dropBtn) { return; } // get parent Layouts string $dragParent = `control -q -p $dragCtrl`; // dropCtrl is the layout, since it has the dropCallBack to support OSX string $dropParent = $dropCtrl; // get the tabLayout string $tabLayout = `layout -q -p $dragParent`; // get short names for the drag and drop layouts string $drag = match("[^|]*$", $dragParent); string $drop = match("[^|]*$", $dropParent); // get all button layouts string $btnLayouts[] = `layout -q -ca $tabLayout`; // create temp Layouts string $tempAllLayout = `columnLayout -m 0 -p CPMasterLayout`; string $tempDragLayout = `columnLayout -m 0 -p CPMasterLayout`; // move all layouts into their tempLayout // they will remain in the same order they existed originally for($btn in $btnLayouts) { // reconstruct a fullPathName for the current tab|btnLayout string $fpnBtn = ($tabLayout + "|" + $btn); string $fpnDrag = ($tabLayout + "|" + $drag); if($fpnBtn != $fpnDrag) layout -e -p $tempAllLayout $fpnBtn; else layout -e -p $tempDragLayout $fpnBtn; } clear $btnLayouts; // now move them all back until we're dealing with the dropLayout; // in this case, decide in what order to insert the dragLayout. $btnLayouts = `layout -q -ca $tempAllLayout`; for($btn in $btnLayouts) { // reconstruct a fullPathName for the current tempLayout|btnLayout string $fpnBtn = ($tempAllLayout + "|" + $btn); string $fpnDrop = ($tempAllLayout + "|" + $drop); string $fpnDrag = ($tempDragLayout + "|" + $drag); // if this * is not * the dropBtn, we reparent it to the tabLayout if($fpnBtn != $fpnDrop) { layout -e -p $tabLayout $fpnBtn; } // if this * is * the dropBtn, we come to a crossroads... else { // now we decide, based on $y dropLocation, whether to insert $drag before or after if($y <= 8) { // insert before: first dragCtrl, then dropCtrl layout -e -p $tabLayout $fpnDrag; layout -e -p $tabLayout $fpnDrop; } else { // insert after: first dropCtrl, then dragCtrl layout -e -p $tabLayout $fpnDrop; layout -e -p $tabLayout $fpnDrag; } } } // Done! Now delete the tempLayouts and update stored position deleteUI $tempAllLayout; deleteUI $tempDragLayout; evalDeferred("aweCPUpdateUIIndex"); }
Old 03 March 2013   #5
Again, thank you very much!
I'll keep you updated!
Old 03 March 2013   #6
I think that now I got the idea and I have to say that it was a very clever solution.
I had no idea that you could move around layouts and widgets this way. Easy and clean.
Here is a simple Class for future reference

import maya.cmds as mc class MyWindow(object): def __init__(self): = 'myTestWindow' self.title = 'My Test Window' self.buttonEight = 50 self.buttonWidth = 300 if mc.window(, q=True, exists=True): mc.deleteUI( self.myWindow = mc.window(, title=self.title) mc.showWindow(self.myWindow) self.myRealLayout = mc.columnLayout() mc.text('Drag buttons around') for item in range(6): mc.button('buttonNumber_%s' % item, l='button number %s' % item, w=self.buttonWidth, h=self.buttonEight, dragCallback=self.dragButton, dropCallback=self.dropButton) def dragButton(self, draggedButton, x, y, modifier): print 'DRAG CALLBACK' print '-------------' print 'dragControl: ', draggedButton.split('|')[-1] print 'x: ', x print 'y: ', y print 'modifier: ', modifier print '' def dropButton(self, draggedButton, droppedButton, messages, x, y, dragType): print 'DROP CALLBACK' print '-------------' print 'dragControl: ', draggedButton.split('|')[-1] print 'dropControl: ', droppedButton.split('|')[-1] print 'messages: ', messages print 'x: ', x print 'y: ', y print 'dragType: ', dragType print '' if draggedButton == droppedButton: return myTempLayout = mc.columnLayout(manage=False) myDraggedLayout = mc.columnLayout(manage=False) myParentLayout = mc.button(draggedButton, q=True, p=True) allMyButtons = [myButton for myButton in mc.layout(myParentLayout, q=True, ca=True) if mc.objectTypeUI(myButton, isType='button') == True] #UNPARENT for myButton in allMyButtons: if myButton == draggedButton.split('|')[-1]: mc.button(myButton, e=True, p=myDraggedLayout) else: mc.button(myButton, e=True, p=myTempLayout) #REPARENT allMyTempButtons = [myButton for myButton in mc.layout(myTempLayout, q=True, ca=True) if mc.objectTypeUI(myButton, isType='button') == True] myDroppedButton = [myButton for myButton in mc.layout(myDraggedLayout, q=True, ca=True) if mc.objectTypeUI(myButton, isType='button') == True] for myTempButton in allMyTempButtons: if myTempButton != droppedButton.split('|')[-1]: mc.button(myTempButton, e=True, p=self.myRealLayout) else: if y < self.buttonEight / 2: mc.button(myDroppedButton[0], e=True, p=self.myRealLayout) mc.button(myTempButton, e=True, p=self.myRealLayout) else: mc.button(myTempButton, e=True, p=self.myRealLayout) mc.button(myDroppedButton[0], e=True, p=self.myRealLayout) mc.deleteUI(myDraggedLayout) mc.deleteUI(myTempLayout) MyWindow()


Last edited by nookie : 03 March 2013 at 10:04 PM. Reason: Forgot to import maya.cmds
Old 03 March 2013   #7
Just wanted to drop a line and thank you guys for posting your code.
That is some great stuff.
Old 03 March 2013   #8
Thread automatically closed

This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.
Thread Closed share thread

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Society of Digital Artists

Powered by vBulletin
Copyright 2000 - 2006,
Jelsoft Enterprises Ltd.
Minimize Ads
Forum Jump

All times are GMT. The time now is 06:39 PM.

Powered by vBulletin
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.