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 Display Modes
  03 March 2013
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.

  03 March 2013
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
			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
			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.
  03 March 2013
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!
  03 March 2013
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) {
	// 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;
			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;
  03 March 2013
Again, thank you very much!
I'll keep you updated!
  03 March 2013
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):
        self.myWindow = mc.window(, title=self.title)    
        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:
        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]

        for myButton in allMyButtons:
            if myButton == draggedButton.split('|')[-1]:
                mc.button(myButton, e=True, p=myDraggedLayout)
                mc.button(myButton, e=True, p=myTempLayout)
        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)
                if y < self.buttonEight / 2:
                    mc.button(myDroppedButton[0], e=True, p=self.myRealLayout)
                    mc.button(myTempButton, e=True, p=self.myRealLayout)
                    mc.button(myTempButton, e=True, p=self.myRealLayout)
                    mc.button(myDroppedButton[0], e=True, p=self.myRealLayout)




Last edited by nookie : 03 March 2013 at 10:04 PM. Reason: Forgot to import maya.cmds
  03 March 2013
Just wanted to drop a line and thank you guys for posting your code.
That is some great stuff.
  03 March 2013
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.
CGTalk Policy/Legalities
Note that as CGTalk Members, you agree to the terms and conditions of using this website.
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 08:48 AM.

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