Как поправить работу кастомизированного окна приложения?
Недавно я задавал вопрос о том, как кастомизировать шапку окна - Как кастомизировать шапку окна приложения определенным образом?
Мне показали пример кода, состоящий из двух python-файлов, который мог бы решить мой вопрос. Пока я настраивал его под свои нужды, заметил одну проблему.
Если потянуть за шапку окна большинства приложений, когда оно находится в развернутом на весь экран состоянии, то это окно перейдет в свое нормальное состояние (свернется до определенного размера) и будет готово к перемещению по экрану. Но окно, которое создает предложенный мне код, никак не реагирует на попытки переместить его, если находится в развернутом состоянии. Автор кода специально заблокировал такую возможность, поэтому я внес в него некоторые его:
Окно теперь может быть перемещено, даже если оно находится в развернутом состоянии.
Если начать перемещать развернутое окно, то оно перейдет в свое нормальное состояние (свернется до определенного размера).
Но этих изменений оказалось недостаточно. Если начать перемещать развернутое окно, то, свернувшись до своих обычных размеров, оно переместится сначала верхний левый угол экрана. Мне же нужно, чтобы в данном случае горизонтальная координата центра окна была равна координате курсора мыши. Иначе говоря, нужно, чтобы окно, которое было развернуто, при перемещении сворачивалось и его горизонтальный центр оказывался там же, где курсор мыши. Пожалуйста, скажите, как мне это сделать?
Main.py
from PyQt5 import QtCore, QtWidgets, QtGui
import FramelessWindow
class MainWindow(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QtWidgets.QVBoxLayout(self, spacing=0)
layout.addWidget(QtWidgets.QPushButton('Button', self))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = FramelessWindow.FramelessWindow()
w.setWidget(MainWindow(w))
w.show()
sys.exit(app.exec_())
FramelessWindow.py
from PyQt5 import QtCore, QtGui, QtWidgets
class TitleBar(QtWidgets.QWidget):
windowMinimumed = QtCore.pyqtSignal()
windowMaximumed = QtCore.pyqtSignal()
windowNormaled = QtCore.pyqtSignal()
windowClosed = QtCore.pyqtSignal()
windowMoved = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, *args, **kwargs):
super(TitleBar, self).__init__(*args, **kwargs)
self.not_Maximum = True
self.setAttribute(QtCore.Qt.WA_StyledBackground, True)
self.mPos = None
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(palette.Window, QtGui.QColor('#36393F'))
self.setPalette(palette)
layout = QtWidgets.QHBoxLayout(self, spacing=0)
layout.setContentsMargins(0, 0, 0, 0)
layout.addSpacerItem(QtWidgets.QSpacerItem(
40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum))
self.buttonMinimum = QtWidgets.QPushButton(self, clicked=self.windowMinimumed.emit, objectName='buttonMinimum')
self.buttonMinimum.setFixedSize(30, 20)
self.buttonMinimum.setStyleSheet(qss_1)
layout.addWidget(self.buttonMinimum)
self.buttonMaximum = QtWidgets.QPushButton(self, clicked=self.showMaximized, objectName='buttonMaximum')
self.buttonMaximum.setFixedSize(30, 20)
self.buttonMaximum.setStyleSheet(qss_2)
layout.addWidget(self.buttonMaximum)
self.buttonClose = QtWidgets.QPushButton(self, clicked=self.windowClosed.emit, objectName='buttonClose')
self.buttonClose.setFixedSize(30, 20)
self.buttonClose.setStyleSheet(qss_4)
layout.addWidget(self.buttonClose)
self.setFixedHeight(20)
def showMaximized(self):
if self.not_Maximum == True:
self.not_Maximum = False
self.buttonMaximum.setStyleSheet(qss_3)
self.windowMaximumed.emit()
else:
self.not_Maximum = True
self.buttonMaximum.setStyleSheet(qss_2)
self.windowNormaled.emit()
def enterEvent(self, event):
self.setCursor(QtCore.Qt.ArrowCursor)
super(TitleBar, self).enterEvent(event)
def mouseDoubleClickEvent(self, event):
super(TitleBar, self).mouseDoubleClickEvent(event)
self.showMaximized()
def mousePressEvent(self, event):
""" Событие клика мыши """
if event.button() == QtCore.Qt.LeftButton:
self.mPos = event.pos()
event.accept()
def mouseReleaseEvent(self, event):
''' Событие отказов мыши '''
self.mPos = None
event.accept()
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton and self.mPos:
if self.not_Maximum == False:
self.not_Maximum = True
self.buttonMaximum.setStyleSheet(qss_2)
self.windowNormaled.emit()
self.windowMoved.emit(self.mapToGlobal(event.pos() - self.mPos))
event.accept()
Left, Top, Right, Bottom, LeftTop, RightTop, LeftBottom, RightBottom = range(8)
class FramelessWindow(QtWidgets.QWidget):
# Четыре периметра
Margins = 5
def __init__(self, *args, **kwargs):
super(FramelessWindow, self).__init__(*args, **kwargs)
self._pressed = False
self.Direction = None
# Фон прозрачный
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
# Нет границы
self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
# Отслеживание мыши
self.setMouseTracking(True)
# макет
layout = QtWidgets.QVBoxLayout(self, spacing=0)
# Зарезервировать границы для изменения размера окна без полей
layout.setContentsMargins(
self.Margins, self.Margins, self.Margins, self.Margins)
# Панель заголовка
self.titleBar = TitleBar(self)
layout.addWidget(self.titleBar)
# слот сигнала
self.titleBar.windowMinimumed.connect(self.showMinimized)
self.titleBar.windowMaximumed.connect(self.showMaximized)
self.titleBar.windowNormaled.connect(self.showNormal)
self.titleBar.windowClosed.connect(self.close)
self.titleBar.windowMoved.connect(self.move)
def setTitleBarHeight(self, height=38):
""" Установка высоты строки заголовка """
self.titleBar.setHeight(height)
def setWidget(self, widget):
""" Настройте свои собственные элементы управления """
if hasattr(self, '_widget'):
return
self._widget = widget
# Установите цвет фона по умолчанию, иначе он будет прозрачным из-за влияния родительского окна
self._widget.setAutoFillBackground(True)
palette = self._widget.palette()
palette.setColor(palette.Window, QtGui.QColor('#161719'))
self._widget.setPalette(palette)
self._widget.installEventFilter(self)
self.layout().addWidget(self._widget)
#def move(self, pos):
#if self.windowState() == QtCore.Qt.WindowMaximized or self.windowState() == QtCore.Qt.WindowFullScreen:
# Максимизировать или полноэкранный режим не допускается
#return
#super(FramelessWindow, self).move(pos)
def showMaximized(self):
""" Чтобы максимизировать, удалите верхнюю, нижнюю, левую и правую границы.
Если вы не удалите его, в пограничной области будут пробелы. """
super(FramelessWindow, self).showMaximized()
self.layout().setContentsMargins(0, 0, 0, 0)
def showNormal(self):
""" Восстановить, сохранить верхнюю и нижнюю левую и правую границы,
иначе нет границы, которую нельзя отрегулировать """
super(FramelessWindow, self).showNormal()
self.layout().setContentsMargins(
self.Margins, self.Margins, self.Margins, self.Margins)
def eventFilter(self, obj, event):
""" Фильтр событий, используемый для решения мыши в других элементах
управления и восстановления стандартного стиля мыши """
if isinstance(event, QtGui.QEnterEvent):
self.setCursor(QtCore.Qt.ArrowCursor)
return super(FramelessWindow, self).eventFilter(obj, event)
def paintEvent(self, event):
""" Поскольку это полностью прозрачное фоновое окно, жесткая для поиска
граница с прозрачностью 1 рисуется в событии перерисовывания, чтобы отрегулировать размер окна. """
super(FramelessWindow, self).paintEvent(event)
painter = QtGui.QPainter(self)
painter.setPen(QtGui.QPen(QtGui.QColor(22, 23, 25, 1), 2 * self.Margins))
painter.drawRect(self.rect())
def mousePressEvent(self, event):
""" Событие клика мыши """
super(FramelessWindow, self).mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self._mpos = event.pos()
self._pressed = True
def mouseReleaseEvent(self, event):
''' Событие отказов мыши '''
super(FramelessWindow, self).mouseReleaseEvent(event)
self._pressed = False
self.Direction = None
def mouseMoveEvent(self, event):
""" Событие перемещения мыши """
super(FramelessWindow, self).mouseMoveEvent(event)
pos = event.pos()
xPos, yPos = pos.x(), pos.y()
wm, hm = self.width() - self.Margins, self.height() - self.Margins
if self.isMaximized() or self.isFullScreen():
self.Direction = None
self.setCursor(QtCore.Qt.ArrowCursor)
return
if event.buttons() == QtCore.Qt.LeftButton and self._pressed:
self._resizeWidget(pos)
return
if xPos <= self.Margins and yPos <= self.Margins:
# Верхний левый угол
self.Direction = LeftTop
self.setCursor(QtCore.Qt.SizeFDiagCursor)
elif wm <= xPos <= self.width() and hm <= yPos <= self.height():
# Нижний правый угол
self.Direction = RightBottom
self.setCursor(QtCore.Qt.SizeFDiagCursor)
elif wm <= xPos and yPos <= self.Margins:
# верхний правый угол
self.Direction = RightTop
self.setCursor(QtCore.Qt.SizeBDiagCursor)
elif xPos <= self.Margins and hm <= yPos:
# Нижний левый угол
self.Direction = LeftBottom
self.setCursor(QtCore.Qt.SizeBDiagCursor)
elif 0 <= xPos <= self.Margins and self.Margins <= yPos <= hm:
# Влево
self.Direction = Left
self.setCursor(QtCore.Qt.SizeHorCursor)
elif wm <= xPos <= self.width() and self.Margins <= yPos <= hm:
# Право
self.Direction = Right
self.setCursor(QtCore.Qt.SizeHorCursor)
elif self.Margins <= xPos <= wm and 0 <= yPos <= self.Margins:
# выше
self.Direction = Top
self.setCursor(QtCore.Qt.SizeVerCursor)
elif self.Margins <= xPos <= wm and hm <= yPos <= self.height():
# ниже
self.Direction = Bottom
self.setCursor(QtCore.Qt.SizeVerCursor)
def _resizeWidget(self, pos):
""" Отрегулируйте размер окна """
if self.Direction == None:
return
mpos = pos - self._mpos
xPos, yPos = mpos.x(), mpos.y()
geometry = self.geometry()
x, y, w, h = geometry.x(), geometry.y(), geometry.width(), geometry.height()
if self.Direction == LeftTop: # Верхний левый угол
if w - xPos > self.minimumWidth():
x += xPos
w -= xPos
if h - yPos > self.minimumHeight():
y += yPos
h -= yPos
elif self.Direction == RightBottom: # Нижний правый угол
if w + xPos > self.minimumWidth():
w += xPos
self._mpos = pos
if h + yPos > self.minimumHeight():
h += yPos
self._mpos = pos
elif self.Direction == RightTop: # верхний правый угол
if h - yPos > self.minimumHeight():
y += yPos
h -= yPos
if w + xPos > self.minimumWidth():
w += xPos
self._mpos.setX(pos.x())
elif self.Direction == LeftBottom: # Нижний левый угол
if w - xPos > self.minimumWidth():
x += xPos
w -= xPos
if h + yPos > self.minimumHeight():
h += yPos
self._mpos.setY(pos.y())
elif self.Direction == Left: # Влево
if w - xPos > self.minimumWidth():
x += xPos
w -= xPos
else:
return
elif self.Direction == Right: # Право
if w + xPos > self.minimumWidth():
w += xPos
self._mpos = pos
else:
return
elif self.Direction == Top: # выше
if h - yPos > self.minimumHeight():
y += yPos
h -= yPos
else:
return
elif self.Direction == Bottom: # ниже
if h + yPos > self.minimumHeight():
h += yPos
self._mpos = pos
else:
return
self.setGeometry(x, y, w, h)
qss_1 = '''
QPushButton {
border: none;
margin: 0px;
padding: 0px;
border-image: url(-_1.png);
}
QPushButton:hover {
border-image: url(-_2.png);
}
'''
qss_2 = '''
QPushButton {
border: none;
margin: 0px;
padding: 0px;
border-image: url(rectangle_1.png);
}
QPushButton:hover {
border-image: url(rectangle_2.png);
}
'''
qss_3 = '''
QPushButton {
border: none;
margin: 0px;
padding: 0px;
border-image: url(duo_rectangle_1.png);
}
QPushButton:hover {
border-image: url(duo_rectangle_2.png);
}
'''
qss_4 = '''
QPushButton {
border: none;
margin: 0px;
padding: 0px;
border-image: url(x_1.png);
}
QPushButton:hover {
border-image: url(x_2.png);
}
'''