Issue
This is about my GUI program that I have been working on for over 2 months.
I have finally completed it and I have posted it on Code Review and I have also opened a GitHub repository for it. I have implemented everything I originally intended, and I have fixed every bug that I found.
Now I want to customize the title bar, but somehow it isn't working. Because the project is massive, it contains 113714 characters in the scripts alone, I can't post the complete code here. I also can't post a minimal reproducible example because I have no idea what caused the bug, the code I adapted is working properly, but when I put the custom title bar to the main window with all other stuff, it malfunctions.
I found this answer, and I adapted it to PyQt6:
import sys
from PyQt6.QtCore import QPoint
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication
from PyQt6.QtWidgets import QHBoxLayout
from PyQt6.QtWidgets import QLabel
from PyQt6.QtWidgets import QPushButton
from PyQt6.QtWidgets import QVBoxLayout
from PyQt6.QtWidgets import QWidget
class MainWindow(QWidget):
def __init__(self):
super(QWidget, self).__init__()
self.layout = QVBoxLayout()
self.layout.addWidget(MyBar())
self.setLayout(self.layout)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.addStretch(-1)
self.setMinimumSize(800, 400)
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
self.pressing = False
class MyBar(QWidget):
def __init__(self, ):
super(MyBar, self).__init__()
self.layout = QHBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.title = QLabel("My Own Bar")
btn_size = 35
self.btn_close = QPushButton("x")
self.btn_close.clicked.connect(self.btn_close_clicked)
self.btn_close.setFixedSize(btn_size, btn_size)
self.btn_close.setStyleSheet("background-color: red;")
self.btn_min = QPushButton("-")
self.btn_min.clicked.connect(self.btn_min_clicked)
self.btn_min.setFixedSize(btn_size, btn_size)
self.btn_min.setStyleSheet("background-color: gray;")
self.btn_max = QPushButton("+")
self.btn_max.clicked.connect(self.btn_max_clicked)
self.btn_max.setFixedSize(btn_size, btn_size)
self.btn_max.setStyleSheet("background-color: gray;")
self.title.setFixedHeight(35)
self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(self.title)
self.layout.addWidget(self.btn_min)
self.layout.addWidget(self.btn_max)
self.layout.addWidget(self.btn_close)
self.title.setStyleSheet(
"""
background-color: black;
color: white;
"""
)
self.setLayout(self.layout)
def resizeEvent(self, QResizeEvent):
super(MyBar, self).resizeEvent(QResizeEvent)
self.title.setFixedWidth(self.window().width())
def mousePressEvent(self, event):
self.start = self.mapToGlobal(event.pos())
self.pressing = True
def mouseMoveEvent(self, event):
print(event.pos())
self.end = self.mapToGlobal(event.pos())
delta = self.end - self.start
self.window().setGeometry(
self.mapToGlobal(delta).x(),
self.mapToGlobal(delta).y(),
self.window().width(),
self.window().height(),
)
self.start = self.end
def mouseReleaseEvent(self, QMouseEvent):
self.pressing = False
def btn_close_clicked(self):
self.window().close()
def btn_max_clicked(self):
self.window().showMaximized()
def btn_min_clicked(self):
self.window().showMinimized()
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion")
mw = MainWindow()
mw.show()
sys.exit(app.exec())
It is working properly.
So I tried to add it to my project, but somehow it doesn't work properly, the relevant code is below:
class SquareButton(QPushButton):
def __init__(self, text: str) -> None:
super().__init__()
self.setFont(FONT)
self.setText(text)
self.setFixedSize(32, 32)
class TitleBar(QWidget):
def __init__(self, name: str, exitfunc: Callable) -> None:
super().__init__()
self.hbox = make_hbox(self, 0)
self.name = name
self.exitfunc = exitfunc
self.setObjectName("Title")
self.add_icon()
self.add_title()
self.add_buttons()
self.setFixedHeight(35)
def add_icon(self) -> None:
self.icon = QLabel()
self.icon.setPixmap(GLOBALS["Logo"])
self.icon.setFixedSize(32, 32)
self.hbox.addWidget(self.icon)
self.hbox.addStretch()
def add_title(self) -> None:
self.title = Label(self.name)
self.hbox.addWidget(self.title)
self.hbox.addStretch()
def add_buttons(self) -> None:
self.minimize_button = SquareButton("—")
self.minimize_button.clicked.connect(self.minimize)
self.hbox.addWidget(self.minimize_button)
self.close_button = SquareButton("X")
self.close_button.clicked.connect(self.exitfunc)
self.hbox.addWidget(self.close_button)
def mousePressEvent(self, e: QMouseEvent) -> None:
self.start = self.mapToGlobal(e.pos())
self.pressing = True
def mouseMoveEvent(self, e: QMouseEvent):
if self.pressing:
print(e.pos())
self.end = self.mapToGlobal(e.pos())
delta = self.end - self.start
self.window().setGeometry(
self.mapToGlobal(delta).x(),
self.mapToGlobal(delta).y(),
self.window().width(),
self.window().height(),
)
self.start = self.end
def mouseReleaseEvent(self, e: QMouseEvent) -> None:
self.pressing = False
def minimize(self) -> None:
self.window().showMinimized()
I don't know what caused the issue, the code should work, but when I ran it, and tried to move the window by dragging the title bar, the window moves erratically, its movement doesn't correspond to the movement of the cursor. At first it seemed to follow the cursor but lags behind a little bit, and then it doesn't follow the cursor at all.
The window has a tendency to move to the lower right, and if the mouse moves up the window somehow moves down, and the window will move below the lower edge of the screen, the window seems to move in the same direction as the cursor horizontally, but not the same distance.
So I tried to debug it by printing the positions of the events, and the working example prints all positive coordinates:
PyQt6.QtCore.QPoint(575, 17)
PyQt6.QtCore.QPoint(575, 17)
PyQt6.QtCore.QPoint(575, 17)
PyQt6.QtCore.QPoint(575, 16)
PyQt6.QtCore.QPoint(574, 17)
PyQt6.QtCore.QPoint(575, 17)
PyQt6.QtCore.QPoint(574, 17)
PyQt6.QtCore.QPoint(571, 16)
PyQt6.QtCore.QPoint(575, 16)
PyQt6.QtCore.QPoint(575, 17)
PyQt6.QtCore.QPoint(571, 15)
PyQt6.QtCore.QPoint(573, 17)
PyQt6.QtCore.QPoint(568, 16)
PyQt6.QtCore.QPoint(567, 15)
PyQt6.QtCore.QPoint(570, 14)
PyQt6.QtCore.QPoint(567, 13)
PyQt6.QtCore.QPoint(564, 11)
PyQt6.QtCore.QPoint(564, 13)
PyQt6.QtCore.QPoint(565, 13)
PyQt6.QtCore.QPoint(565, 16)
PyQt6.QtCore.QPoint(562, 13)
PyQt6.QtCore.QPoint(564, 11)
But somehow the code when used in my project prints some negative coordinates:
PyQt6.QtCore.QPoint(55, -322)
PyQt6.QtCore.QPoint(51, -327)
PyQt6.QtCore.QPoint(47, -330)
PyQt6.QtCore.QPoint(41, -334)
PyQt6.QtCore.QPoint(37, -337)
PyQt6.QtCore.QPoint(35, -341)
PyQt6.QtCore.QPoint(30, -343)
PyQt6.QtCore.QPoint(30, -346)
PyQt6.QtCore.QPoint(29, -348)
PyQt6.QtCore.QPoint(26, -351)
PyQt6.QtCore.QPoint(25, -352)
PyQt6.QtCore.QPoint(25, -354)
PyQt6.QtCore.QPoint(23, -356)
PyQt6.QtCore.QPoint(19, -358)
PyQt6.QtCore.QPoint(16, -362)
PyQt6.QtCore.QPoint(14, -365)
PyQt6.QtCore.QPoint(9, -368)
PyQt6.QtCore.QPoint(7, -372)
PyQt6.QtCore.QPoint(3, -374)
PyQt6.QtCore.QPoint(-2, -378)
PyQt6.QtCore.QPoint(-1, -379)
PyQt6.QtCore.QPoint(-7, -383)
PyQt6.QtCore.QPoint(-7, -386)
PyQt6.QtCore.QPoint(-10, -388)
PyQt6.QtCore.QPoint(-14, -392)
PyQt6.QtCore.QPoint(-17, -394)
PyQt6.QtCore.QPoint(-20, -399)
PyQt6.QtCore.QPoint(-22, -401)
PyQt6.QtCore.QPoint(-25, -403)
PyQt6.QtCore.QPoint(-30, -407)
PyQt6.QtCore.QPoint(-31, -409)
I have packaged the full code to a zip file and uploaded to Google Drive, so that you may run the code and debug it. Because I use pickle data files, you may need to run analyze_tic_tac_toe_states.py
to generate the data files before running main.py
to call up the window.
What caused the strange bug and how to fix it?
Solution
For some reason QWidget.mapToGlobal(e.pos())
sometimes outputs incorrect values, it may be related to pending geometry changes which causes invalid (probably random) values of transformation matrix, I guess. To avoid erratic movement use QMouseEvent.globalPosition().toPoint()
.
from PyQt6.QtWidgets import QWidget, QApplication
from PyQt6.QtGui import QMouseEvent
from PyQt6.QtCore import QRect, QPoint
class TitleBar(QWidget):
def mousePressEvent(self, e: QMouseEvent) -> None:
self.start = e.globalPosition().toPoint()
self.pressing = True
def mouseMoveEvent(self, e: QMouseEvent):
if self.pressing:
self.end = e.globalPosition().toPoint()
delta = self.end - self.start
self.window().setGeometry(
self.mapToGlobal(delta).x(),
self.mapToGlobal(delta).y(),
self.window().width(),
self.window().height(),
)
self.start = self.end
def mouseReleaseEvent(self, e: QMouseEvent) -> None:
self.pressing = False
if __name__ == "__main__":
app = QApplication([])
widget = TitleBar()
widget.show()
app.exec()
Or you can save offset from topleft corner of widget (local coordinate) once on mouse press and move window to global coordinate offseted by this value and forget about start
and end
from PyQt6.QtWidgets import QWidget, QApplication
from PyQt6.QtGui import QMouseEvent
from PyQt6.QtCore import QRect
class TitleBar(QWidget):
def mousePressEvent(self, e: QMouseEvent) -> None:
self.offset = e.pos()
self.pressing = True
def mouseMoveEvent(self, e: QMouseEvent):
if self.pressing:
geometry = self.geometry()
newGeometry = QRect(e.globalPosition().toPoint() - self.offset, geometry.size())
self.setGeometry(newGeometry)
def mouseReleaseEvent(self, e: QMouseEvent) -> None:
self.pressing = False
if __name__ == "__main__":
app = QApplication([])
widget = TitleBar()
widget.show()
app.exec()
Answered By - mugiseyebrows
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.