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

PyQt中QGridLayout内QFrame间绘制连线及悬停提示方案问询

PyQt QGridLayout中QFrame中心连线及悬停提示解决方案

问题描述

我用PyQt开发GUI,通过QGridLayout布局QFrame,每行Frame间隔一个网格(比如(0,0)、(0,2)、(1,0)),需要在各Frame中心间绘制连线,且鼠标悬停连线上时显示文本,但目前连线无法正常显示。之前做过未用QGridLayout的示例,虽能绘制但手动计算坐标太繁琐,需要解决两个问题:

  • 无需手动计算坐标,实现QGridLayout中QFrame间的中心连线绘制;
  • 为连线添加鼠标悬停文本提示。

解决方案

一、自动获取QFrame中心坐标绘制连线

核心问题是你在初始化阶段获取的坐标是布局生效前的初始值,且绘制载体选错了(MainWindow不是存放QFrame的直接父控件)。正确实现方式:

  1. 将绘制逻辑放在存放QFrame的main_view控件中,而非MainWindow;
  2. 在paintEvent中实时获取每个QFrame的中心坐标,利用mapToParent将QFrame自身坐标系的中心转换为父控件的坐标系坐标;
  3. 无需额外监听控件移动,paintEvent会在布局更新时自动触发重绘。

二、实现连线的鼠标悬停提示

要实现悬停提示需完成三点:

  1. 开启控件的鼠标跟踪(setMouseTracking(True)),确保无点击时也能触发鼠标移动事件;
  2. 重写mouseMoveEvent,通过向量叉积计算鼠标位置到每条连线的距离;
  3. 当距离小于连线宽度的一半时,显示QToolTip提示文本。

完整修改代码

import sys
from PyQt5.QtCore import Qt, QPointF
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton,
                             QAction, QFrame, QWidget, QGridLayout,
                             QScrollArea, QStyle, QToolTip)
from PyQt5.QtGui import QPainter, QPen, QFont


class MainView(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMouseTracking(True)  # 开启鼠标跟踪
        self.gridLayout = QGridLayout(self)
        self.lines = []  # 存储连线信息:(起点Frame, 终点Frame, 提示文本)

        # 创建QFrame
        self.square1 = QFrame()
        self.square1.setFixedSize(150, 150)
        self.square1.setStyleSheet("background-color:orange")
        self.square2 = QFrame()
        self.square2.setFixedSize(150, 150)
        self.square2.setStyleSheet("background-color:green")
        self.square3 = QFrame()
        self.square3.setFixedSize(150, 150)
        self.square3.setStyleSheet("background-color:blue")
        self.square4 = QFrame()
        self.square4.setFixedSize(150, 150)
        self.square4.setStyleSheet("background-color:cyan")

        # 添加按钮
        btn1 = QPushButton('Orange', self.square1)
        btn2 = QPushButton('green', self.square2)
        btn3 = QPushButton('blue', self.square3)
        btn4 = QPushButton('cyan', self.square4)

        btn1.clicked.connect(lambda: print("This is Orange button"))
        btn2.clicked.connect(lambda: print("This is Green button"))
        btn3.clicked.connect(lambda: print("This is Blue button"))
        btn4.clicked.connect(lambda: print("This is Cyan button"))

        # 布局QFrame
        self.gridLayout.addWidget(self.square1, 0, 0)
        self.gridLayout.addWidget(self.square2, 0, 2)
        self.gridLayout.addWidget(self.square3, 1, 0)
        self.gridLayout.addWidget(self.square4, 1, 2)

        # 定义需要绘制的连线及提示文本
        self.lines = [
            (self.square1, self.square2, "橙色→绿色"),
            (self.square1, self.square3, "橙色→蓝色"),
            (self.square2, self.square4, "绿色→青色"),
            (self.square3, self.square4, "蓝色→青色")
        ]

    def paintEvent(self, event):
        super().paintEvent(event)
        painter = QPainter(self)
        pen = QPen(Qt.red, 2, Qt.SolidLine)
        painter.setPen(pen)

        # 遍历所有连线,实时获取中心坐标并绘制
        for frame1, frame2, _ in self.lines:
            center1 = frame1.mapToParent(frame1.rect().center())
            center2 = frame2.mapToParent(frame2.rect().center())
            painter.drawLine(center1, center2)

    def distance_point_to_line(self, point, line_start, line_end):
        # 向量叉积法计算点到直线的距离
        line_vec = line_end - line_start
        point_vec = point - line_start
        cross_product = line_vec.x() * point_vec.y() - line_vec.y() * point_vec.x()
        line_length = line_vec.manhattanLength() if line_vec.manhattanLength() != 0 else 1
        return abs(cross_product) / line_length

    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)
        mouse_pos = event.pos()
        tip_text = ""

        # 检查每条连线是否被鼠标悬停
        for frame1, frame2, tip in self.lines:
            center1 = QPointF(frame1.mapToParent(frame1.rect().center()))
            center2 = QPointF(frame2.mapToParent(frame2.rect().center()))
            dist = self.distance_point_to_line(QPointF(mouse_pos), center1, center2)
            # 距离小于线宽时判定为悬停
            if dist <= 2:
                tip_text = tip
                break

        if tip_text:
            QToolTip.setFont(QFont("SansSerif", 10))
            QToolTip.showText(event.globalPos(), tip_text, self)
        else:
            QToolTip.hideText()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App window title")
        self.setStyleSheet("background-color: rgb(176,196,222);")
        self.centralwidget = QScrollArea()
        self.setCentralWidget(self.centralwidget)

        menuBar = self.menuBar()
        fileMenu = menuBar.addMenu('&File')
        openFile = QAction(self.style().standardIcon(QStyle.SP_MessageBoxCritical),
                           'Open', self)
        fileMenu.addAction(openFile)

        self.width = 900
        self.height = 500
        self.resize(self.width, self.height)

        self.main_view = MainView()
        self.main_view.setFixedSize(self.width, self.height)
        self.centralwidget.setWidget(self.main_view)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

代码关键说明

  • MainView作为QFrame的容器,同时负责连线绘制和鼠标事件处理,确保坐标计算的准确性;
  • paintEvent中实时获取Frame中心坐标,保证布局变化或控件移动时连线自动更新;
  • 用lines列表统一管理所有连线及其提示文本,便于后续扩展维护;
  • 点到直线的距离计算采用向量叉积法,判断逻辑高效且准确。

内容的提问来源于stack exchange,提问作者A.G.Progm.Enthusiast

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 20:50:23