Drag tabs between QTabWidgets
Joel Bodenmann last edited by
I have two
QTabWidgets in a
QSplitter. I'd like to be able to drag a tab from one
QTabWidgetto the other
QTabWidget. Can anybody tell me how I can archive this?
I saw this post: https://forum.qt.io/topic/38631/dragging-a-tab-from-qtabwidget-into-a-separate-window/5
However, my problem is that the
QTabWidgetsubclass never gets called. I moved one step further and subclassed
QTabBarbut I have the same problem there:
dragMoveEvent()is never called.
Any ideas? As far as I can tell there's no widget property that must be enabled to enable drags, that seems to only exist for drops.
If there's a better way to archive what I want to archive I'm all ears. After all eventually I'd like to have a visible "animation" when the tab is being dragged around.
The post you linked to is in error. The drag move event takes place on the target widget, not when you want to start a drag from the source. So in that example code it should be
As far as I can tell there's no widget property that must be enabled to enable drags
There's no property but you must implement dragging yourself. See the docs for an example how to do it. That's for the source widget of the drag.
The target has to have dropping enabled. When something is dragged over it it will receive drag enter event. If that event is accepted the drag move events will follow when you move above the target and finally a drop event when you release the mouse button.
Joel Bodenmann last edited by Joel Bodenmann
Thank you very much for clearing things up! I thought that there was something fishy...
So if I am not mistaken I have to subclass
QTabBarfor this, and not
QTabWidget. Is that correct?
Can you tell me what I'd have to do to "animate" the moving tab? I'd like to have the tab (just the tab, thing with the string in it) under the mouse cursor while it is being dragged around.
So if I am not mistaken I have to subclass QTabBar for this, and not QTabWidget. Is that correct?
Yes. You can still use QTabWidget for convenience and just set your customized tab bar on it.
Can you tell me what I'd have to do to "animate" the moving tab?
It's a bit complicated and I haven't actually tried it, so take this with a grain of salt, but I'd try something like this:
First - you need to "remove" the original tab from the source. For this you can simply hide the tab for the
exec()part of the drag and either show it back or permanently delete, depending on whether the drag was aborted or successful (you can get that from the exec return value).
Next part is showing the tab under cursor when it is being dragged. For that you need to set a pixmap on the drag object that will represent the tab. You can probably get it easily with render() of the tab widget. Just calculate the right region based on the geometry of the tab you're dragging.
Last part (and the hardest I think) is placing a tab in the target widget and moving it around. For that I'd create a "dummy" tab in the target in the drop enter event. If you get the drop leave event remove the dummy. If you get a drop event the dummy becomes the new tab. The tricky part is moving the tab around while the drag operation is still going on. I'd try to send a fake mouse press event to the tab widget in the drag enter, then fake mouse move events in the drop move event and finally a fake mouse release in the drop and leave drag events. This way the dummy tab should move along with the cursor when you move it.
Again, I haven't tried it, but I think it should be doable this way.
Joel Bodenmann last edited by
Thank you very much, this helped a lot!
I got it working as per your recommendation. I will publish the resulting widget in the following days for people that stumble across the same issue in the future.
ARussell last edited by ARussell
Had a similar Issue. Was able to find a solution. Below is a generic PyQt5 example that solves the problem using right click.
import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtCore import * class Tabs(QTabWidget): def __init__(self, parent): super().__init__(parent) self.parent = parent self.setAcceptDrops(True) self.tabBar = self.tabBar() self.tabBar.setMouseTracking(True) self.indexTab = None self.setMovable(True) self.addTab(QWidget(self), 'Tab One') self.addTab(QWidget(self), 'Tab Two') def mouseMoveEvent(self, e): if e.buttons() != Qt.RightButton: return globalPos = self.mapToGlobal(e.pos()) tabBar = self.tabBar posInTab = tabBar.mapFromGlobal(globalPos) self.indexTab = tabBar.tabAt(e.pos()) tabRect = tabBar.tabRect(self.indexTab) pixmap = QPixmap(tabRect.size()) tabBar.render(pixmap,QPoint(),QRegion(tabRect)) mimeData = QMimeData() drag = QDrag(tabBar) drag.setMimeData(mimeData) drag.setPixmap(pixmap) cursor = QCursor(Qt.OpenHandCursor) drag.setHotSpot(e.pos() - posInTab) drag.setDragCursor(cursor.pixmap(),Qt.MoveAction) dropAction = drag.exec_(Qt.MoveAction) def dragEnterEvent(self, e): e.accept() if e.source().parentWidget() != self: return print(self.indexOf(self.widget(self.indexTab))) self.parent.TABINDEX = self.indexOf(self.widget(self.indexTab)) def dragLeaveEvent(self,e): e.accept() def dropEvent(self, e): print(self.parent.TABINDEX) if e.source().parentWidget() == self: return e.setDropAction(Qt.MoveAction) e.accept() counter = self.count() if counter == 0: self.addTab(e.source().parentWidget().widget(self.parent.TABINDEX),e.source().tabText(self.parent.TABINDEX)) else: self.insertTab(counter + 1 ,e.source().parentWidget().widget(self.parent.TABINDEX),e.source().tabText(self.parent.TABINDEX)) class Window(QWidget): def __init__(self): super().__init__() self.TABINDEX = 0 tabWidgetOne = Tabs(self) tabWidgetTwo = Tabs(self) layout = QHBoxLayout() self.moveWidget = None layout.addWidget(tabWidgetOne) layout.addWidget(tabWidgetTwo) self.setLayout(layout) if __name__ == '__main__': app = QApplication(sys.argv) window = Window() window.show() sys.exit(app.exec_())