You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Qt实现跨平台应用的Photoshop式工具箱拖拽停靠效果

实现Qt自定义拖拽停靠工具箱(Photoshop风格,无需QToolbar)

Hey there, let's build that custom dockable toolbox for your cross-platform Qt app—exactly the Photoshop-style corner docking you want, no QToolbar required. This approach works smoothly on Windows, Mac, and Linux, and gives you full control over the behavior.

Core Idea

We'll create a custom QWidget for the toolbox, handle mouse drag events manually, detect when the toolbox gets close to your main window's corners, and snap it into place when the user releases the mouse. No relying on Qt's built-in dock widgets—we're doing this from scratch for full customization.

Step-by-Step Implementation

1. Build the Custom Toolbox Widget

First, create a ToolBoxWidget class that inherits from QWidget. We'll override mouse events to handle dragging, add corner detection logic, and implement the snap-to-corner behavior.

#include <QWidget>
#include <QMouseEvent>
#include <QLineF>

class ToolBoxWidget : public QWidget
{
    Q_OBJECT
public:
    explicit ToolBoxWidget(QWidget *parent = nullptr) : QWidget(parent) {
        // Set initial size and styling to match a typical toolbox
        setFixedSize(200, 300);
        setStyleSheet("background-color: #f0f0f0; border: 1px solid #cccccc; border-radius: 4px;");
        // Frameless window so we can drag it freely, keep it above other windows
        setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
    }

    // Pass a pointer to the main window so we can track its corners
    void setMainWindow(QWidget* mainWin) { m_mainWindow = mainWin; }

protected:
    void mousePressEvent(QMouseEvent *event) override {
        if (event->button() == Qt::LeftButton) {
            // Record the offset between the mouse click and the widget's top-left corner
            m_dragOffset = event->globalPos() - frameGeometry().topLeft();
            event->accept();
        }
    }

    void mouseMoveEvent(QMouseEvent *event) override {
        if (event->buttons() & Qt::LeftButton) {
            // Move the widget with the mouse
            move(event->globalPos() - m_dragOffset);
            // Check if we're near a main window corner for visual feedback
            updateCornerProximity();
            event->accept();
        }
    }

    void mouseReleaseEvent(QMouseEvent *event) override {
        if (event->button() == Qt::LeftButton) {
            // Snap to the closest corner when the user lets go
            snapToClosestCorner();
            // Reset styling back to normal
            setStyleSheet("background-color: #f0f0f0; border: 1px solid #cccccc; border-radius: 4px;");
            event->accept();
        }
    }

private:
    QPoint m_dragOffset;
    QWidget* m_mainWindow = nullptr;
    const int m_cornerSnapRange = 150; // 150px range around each corner to trigger snapping

    void updateCornerProximity() {
        if (!m_mainWindow) return;

        QRect mainWindowBounds = m_mainWindow->frameGeometry();
        QPoint toolboxCenter = frameGeometry().center();

        // Check if we're within snap range of any corner
        bool nearCorner = false;
        nearCorner |= QLineF(toolboxCenter, mainWindowBounds.topLeft()).length() < m_cornerSnapRange;
        nearCorner |= QLineF(toolboxCenter, mainWindowBounds.topRight()).length() < m_cornerSnapRange;
        nearCorner |= QLineF(toolboxCenter, mainWindowBounds.bottomLeft()).length() < m_cornerSnapRange;
        nearCorner |= QLineF(toolboxCenter, mainWindowBounds.bottomRight()).length() < m_cornerSnapRange;

        // Give visual feedback: highlight the border when near a corner
        if (nearCorner) {
            setStyleSheet("background-color: #f0f0f0; border: 2px solid #0078d7; border-radius: 4px;");
        } else {
            setStyleSheet("background-color: #f0f0f0; border: 1px solid #cccccc; border-radius: 4px;");
        }
    }

    void snapToClosestCorner() {
        if (!m_mainWindow) return;

        QRect mainWindowBounds = m_mainWindow->frameGeometry();
        QPoint toolboxCenter = frameGeometry().center();

        // Calculate distance to each main window corner
        int distTopLeft = QLineF(toolboxCenter, mainWindowBounds.topLeft()).length();
        int distTopRight = QLineF(toolboxCenter, mainWindowBounds.topRight()).length();
        int distBottomLeft = QLineF(toolboxCenter, mainWindowBounds.bottomLeft()).length();
        int distBottomRight = QLineF(toolboxCenter, mainWindowBounds.bottomRight()).length();

        // Find the closest corner
        int minDist = qMin(qMin(distTopLeft, distTopRight), qMin(distBottomLeft, distBottomRight));
        QPoint targetPos;

        // Offset the toolbox slightly from the corner so it doesn't stick directly to the edge
        const int edgeOffset = 10;
        if (minDist == distTopLeft) {
            targetPos = mainWindowBounds.topLeft() + QPoint(edgeOffset, edgeOffset);
        } else if (minDist == distTopRight) {
            targetPos = mainWindowBounds.topRight() - QPoint(width() + edgeOffset, -edgeOffset);
        } else if (minDist == distBottomLeft) {
            targetPos = mainWindowBounds.bottomLeft() + QPoint(edgeOffset, -height() - edgeOffset);
        } else {
            targetPos = mainWindowBounds.bottomRight() - QPoint(width() + edgeOffset, height() + edgeOffset);
        }

        // Move the toolbox to the target corner
        move(targetPos);
    }
};

2. Integrate with Your Main Window

In your main window, initialize the toolbox and pass a reference to the main window so the toolbox can track its position:

#include "ToolBoxWidget.h"
#include <QMainWindow>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
        // Set main window size and position
        setGeometry(100, 100, 800, 600);
        setWindowTitle("Custom Dockable Toolbox Demo");

        // Create and configure the toolbox
        m_toolBox = new ToolBoxWidget(this);
        m_toolBox->setMainWindow(this);
        // Set initial position for the toolbox
        m_toolBox->move(200, 200);
        m_toolBox->show();
    }

private:
    ToolBoxWidget* m_toolBox = nullptr;
};

3. Cross-Platform Tweaks

  • Windows: The Qt::FramelessWindowHint works well here, but if you notice any odd behavior with window dragging, you can add a small QWidget as a title bar area (though our current implementation doesn't need it).
  • Mac: The Qt::WindowStaysOnTopHint works as expected, but if you need the toolbox to stay above full-screen apps, you might need to tweak the window level using native macOS APIs (but for most cases, the default is fine).
  • Linux: Test on GNOME and KDE to ensure the frameless window drag works—some desktop environments require extra hints, but Qt handles most of this out of the box.

4. Enhancements (Optional)

  • Multiple Toolboxes: Add support for multiple toolboxes by creating instances of ToolBoxWidget and ensuring each can snap independently.
  • Edge Docking: Extend the logic to support snapping to the main window's left/right/top/bottom edges, not just corners.
  • Persistent Position: Save the toolbox's position to QSettings so it remembers where it was docked when the app restarts.

内容的提问来源于stack exchange,提问作者Moussa Ball

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:09:23