Как закруглить края взаимосвязанных виджетов QListWidget и QStackedWidget или второй вариант с QTabWidget?
Код ниже имитирует панель вкладок, создавая взаимосвязанные QListWidget и QStackedWidget. Сейчас это выглядит так:
Мне нужно закруглить края каждой 'вкладки'. Это очень важно, и без этого я не могу двигаться дальше. Конечный результат должен выглядеть так(первая вкладка - 6 закругленных углов, вторая вкладка - 8 закругленных углов):
Мне показалось, что самое сложное - сделать внутренние(впуклые) углы, которые я отметил на схеме восклицательными знаками. Сейчас я придумал одну вещь, которая должна помочь сделать это немного проще. Внутренние углы активной вкладки можно нарисовать на изображении, находящемся на неактивной вкладке. Таким образом, изображение вкладки номер 2, когда вкладка номер 1 будет являться активной, будет выглядеть так:
Т.к. активной счтается вкладка номер 1, она будет находиться выше, значит, своим внутренним углом будет перекрывать вторую вкладку. Поэтому на изображении выше правый верхний угол второй вкладки перекрыт правым нижним углом первой вкладки.
Так же я сделал и для первой вкладки, на случай, когда она станет неактивной:
Но этого не хватает. Когда вторая вкладка становится активной, у нее появляется два внутренних угла. На картинке выше изображен только правый верхний. Правый нижний угол выйдет за пределы обоих элементов QListWidget. Я предполагаю, что можно будет создавать третий элемент в QListWidget, который будет состоять только из одного уголка второй активной вкладки:
^^^ Надпись, чтобы обратить внимание на этот уголок, можно не заметить:)
Что делать с углами основного тела вкладки(QStackedWidget) я тоже не знаю. Когда активной будет первая вкладка, нужно закруглять только три угла(левый нижний, правый верхний, правый нижний). Когда активной будет вторая вкладка, нужно закруглять все углы.
Вот все изображения для вкладок, которые могут пригодиться(радиус всех скруглений - 10px):
Я не знаю, как можно сделать все это. Не понимаю, как можно будет поменять картинку на вкладке, когда она станет активной/неактивной. Не знаю, как можно будет добавить некликабельный уголок в QListWidget. Может быть, все, что я расписал выше, не будет работать? Может быть, есть способ проще? Пожалуйста, подскажите, как можно скруглить края этих 'вкладок'?
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
class LeftTabWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(LeftTabWidget, self).__init__(*args, **kwargs)
self.setMinimumSize(500, 300)
btn = QtWidgets.QPushButton('Button')
layout1 = QtWidgets.QHBoxLayout(self)
layout1.addWidget(btn)
self.listWidget = QtWidgets.QListWidget()
self.listWidget.setContentsMargins(0, 0, 0, 0)
self.listWidget.setViewMode(QtWidgets.QListView.IconMode)
self.listWidget.setIconSize(QtCore.QSize(60, 60))
self.listWidget.setMovement(QtWidgets.QListView.Static)
self.listWidget.setMaximumWidth(60)
self.listWidget.setMinimumWidth(60)
self.listWidget.setSpacing(0)
self.stackedWidget = QtWidgets.QStackedWidget()
label1 = QtWidgets.QLabel('Страница 1', self)
label1.setAlignment(QtCore.Qt.AlignCenter)
label1.setStyleSheet('background: #7189DB;')
self.stackedWidget.addWidget(label1)
label2 = QtWidgets.QLabel('Страница 2', self)
label2.setAlignment(QtCore.Qt.AlignCenter)
label2.setStyleSheet('background: #45507E;')
self.stackedWidget.addWidget(label2)
self.createIcons()
self.listWidget.setCurrentRow(0)
layout2 = QtWidgets.QHBoxLayout(spacing=0)
layout2.setContentsMargins(0, 0, 0, 0)
layout2.addWidget(self.listWidget)
layout2.addWidget(self.stackedWidget, 1)
layout1.addLayout(layout2)
def createIcons(self):
configButton = QtWidgets.QListWidgetItem(self.listWidget)
configButton.setIcon(QtGui.QIcon('btn1.png'))
configButton.setTextAlignment(Qt.AlignHCenter)
configButton.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
updateButton = QtWidgets.QListWidgetItem(self.listWidget)
updateButton.setIcon(QtGui.QIcon('btn2.png'))
updateButton.setTextAlignment(Qt.AlignHCenter)
updateButton.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
self.listWidget.currentItemChanged.connect(self.changePage)
def changePage(self, current, previous):
if not current:
current = previous
self.stackedWidget.setCurrentIndex(self.listWidget.row(current))
Stylesheet = '''
QListWidget {
border: none;
}
QListWidget::item {
width: 60px;
height: 60px;
color: #3b4155;
}
QListWidget::item:selected {
background-color: #7189DB;
}
QListWidget::item:!selected {
background-color: #7189DB;
}
QLabel {
margin: 0px;
color: white;
font: 21pt;
}
'''
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(Stylesheet)
w = LeftTabWidget()
w.show()
sys.exit(app.exec_())
--- ВТОРОЙ ВАРИАНТ С QTabWidget ---
Раньше я пытался использовать QTabWidget. Причиной, по которой я отказался от данного виджета, является странное поведение изображения, которое я пытался поместить в область заголовка вкладки. Почему-то картинка не попадает в центр этой области, а располагается ближе к левому краю виджета. Эту проблему подробнее я расписал тут - Как убрать лишние края области заголовка вкладки виджета QTabWidget?(PyQt 5, Python 3)
В комментариях со мной поделились ссылкой - https://stackoverflow.com/questions/47713741/qtabwidget-tab-icons-not-in-the-center. Тут описывается способ поправить ситуацию, который, к сожалению, мне не помог. Следуя совету из этой ссылки, я изменил тот код с QTabWidget:
from PyQt5 import QtWidgets, QtCore, QtGui
class Color(QtWidgets.QWidget):
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QtGui.QPalette.Window, QtGui.QColor(color))
self.setPalette(palette)
class TabBar(QtWidgets.QTabBar):
def tabSizeHint(self, index):
s = QtWidgets.QTabBar.tabSizeHint(self, index)
s.transpose()
return s
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
opt = QtWidgets.QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt)
painter.save()
s = opt.rect.size()
s.transpose()
r = QtCore.QRect(QtCore.QPoint(), s)
r.moveCenter(opt.rect.center())
opt.rect = r
c = self.tabRect(i).center()
painter.translate(c)
painter.rotate(90)
painter.translate(-c)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt)
painter.restore()
class TabWidget(QtWidgets.QTabWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QTabWidget.__init__(self, *args, **kwargs)
self.setTabBar(TabBar(self))
self.setTabPosition(QtWidgets.QTabWidget.West)
class MyWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super().__init__(parent)
self.resize(250, 300)
self.tabs = TabWidget()
self.tabs.setTabPosition(QtWidgets.QTabWidget.West)
self.tabs.setDocumentMode(True)
self.tabs.addTab(Color('#7189DB'), QtGui.QIcon('play.png'), '')
self.tabs.addTab(Color('#7189DB'), 'Вкладка 2')
self.tabs.setIconSize(QtCore.QSize(40, 40))
qss = '''QTabBar::tab:hover {
background-color: #212121;
}
QTabBar::tab:selected{
background-color: #313131;
}
QTabBar::tab {
background-color: #111111;
height:70px;
width: 70px;
border: none;
margin: 0px;
padding-top: -15px;
padding-bottom: 15px
padding-right: 0px;
padding-left: 0px;
}'''
self.tabs.setStyleSheet(qss)
self.tabs.setCurrentIndex(0)
box = QtWidgets.QVBoxLayout(self)
box.addWidget(self.tabs)
def closeTab(self, index):
tab = self.tabs.widget(index)
tab.deleteLater()
self.tabs.removeTab(index)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.setWindowTitle(' ')
window.show()
sys.exit(app.exec_())
В таблице стиля есть такие строки:
padding-right: 0px;
padding-left: 0px;
Я пытался задать данным параметрам разные значения, но все приводили к одному и тому же результату:
Несмотря на это, я пытался закруглить края виджета, изменив таблицу стиля следующим образом:
qss = '''QTabBar::tab:hover {
background-color: #212121;
}
QTabBar::tab:selected{
background-color: #313131;
}
QTabBar::tab {
background-color: #111111;
height:70px;
width: 70px;
border: none;
margin: 0px;
padding-top: -15px;
padding-bottom: 15px
padding-right: 15px;
padding-left: 15px;
}
QTabBar::pane {
border-style: hidden;
border-width: 2px;
border-radius: 10px;
}'''
Ничего не произошло
Ответы (2 шт):
Я конечно не уверен, что мои фантазии оптимальны, но оно как бы работает.
За основу взят ВТОРОЙ ВАРИАНТ С QTabWidget, т.к.
в таблице стилей QTabBar есть - :first и :last.
Обратите внимание, как вставляются изображения:
border-image: url(btn72.png) repeat;
Картинки, которые я использовал, надо нарисовать поаккуратнее.
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
class Color(QtWidgets.QWidget):
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QtGui.QPalette.Window, QtGui.QColor(color))
self.setPalette(palette)
class TabBar(QtWidgets.QTabBar):
def tabSizeHint(self, index):
s = QtWidgets.QTabBar.tabSizeHint(self, index)
s.transpose()
return s
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
opt = QtWidgets.QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt)
painter.save()
s = opt.rect.size()
s.transpose()
r = QtCore.QRect(QtCore.QPoint(), s)
r.moveCenter(opt.rect.center())
opt.rect = r
c = self.tabRect(i).center()
painter.translate(c)
painter.rotate(90)
painter.translate(-c)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt);
painter.restore()
class TabWidget(QtWidgets.QTabWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QTabWidget.__init__(self, *args, **kwargs)
self.setTabBar(TabBar(self))
self.setTabPosition(QtWidgets.QTabWidget.West)
class MyWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super().__init__(parent)
self.setFixedSize(800, 400)
self.current_index = 1 # +++
self.tabs = TabWidget()
self.tabs.currentChanged.connect(self.tabsCurrentChanged) # +++->(int index)
self.tabs.setTabPosition(QtWidgets.QTabWidget.West)
self.tabs.setDocumentMode(True)
#self.tabs.setMovable(True)
self.tabs.addTab(QtWidgets.QLabel('1', alignment=QtCore.Qt.AlignCenter), '') # 'Вкладка first'
self.tabs.addTab(QtWidgets.QLabel('1', alignment=QtCore.Qt.AlignCenter), '') # 'Вкладка 1'
self.tabs.addTab(QtWidgets.QLabel('2', alignment=QtCore.Qt.AlignCenter), '') # 'Вкладка 2'
self.tabs.addTab(QtWidgets.QLabel('2', alignment=QtCore.Qt.AlignCenter), '') # 'Вкладка last' # 'Вкладка last'
self.tabs.setCurrentIndex(self.current_index) # + self.current_index
box = QtWidgets.QVBoxLayout(self)
box.addWidget(self.tabs)
box.setContentsMargins(0, 0, 0, 0)
# ++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
def tabsCurrentChanged(self, index):
#print(f'def tabsCurrentChanged(self, index): {index} - {self.current_index}')
if index == 0 or index == 3:
self.tabs.setCurrentIndex(self.current_index)
if index == 1:
self.tabs.setStyleSheet('''
QTabBar::tab:first:!selected {
border-image: url(btn72.png) repeat;
}
QTabBar::tab:!selected {
border-image: url(btn6.png) repeat;
}
QTabBar::tab:last:!selected {
border-image: url(btn82.png) repeat;
}
''')
self.current_index = index
if index == 2:
self.tabs.setStyleSheet('''
QTabBar::tab:first:!selected {
border-image: url(btn71.png) repeat;
}
QTabBar::tab:last:!selected {
border-image: url(btn81.png) repeat;
}
QTabBar::tab:!selected {
border-image: url(btn41.png) repeat;
}
''')
self.current_index = index
# ++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
qss = '''
QTabBar {
background-color: #fff;
}
QTabBar::tab {
background: rgb(34, 137, 163);
color: white;
}
QTabBar::tab:selected {
color: #ff0000;
background-color: green;
border-image: url(btn2.png);
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
width: 80px;
height: 80px;
}
QTabBar::tab:!selected {
background-color: yellow;
border-image: url(btn5.png) repeat;
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
width: 80px;
height: 80px;
}
/* ********* first ********************** */
QTabBar::tab:first:!selected {
border-image: url(btn72.png) repeat;
margin-top: 60px;
width: 80px;
height: 20px;
}
QTabBar::tab:first:selected {
border-image: url(btn71.png) repeat;
margin-top: 60px;
width: 80px;
height: 20px;
}
/* ********* last *********************** */
QTabBar::tab:last:!selected {
border-image: url(btn82.png) repeat;
margin-bottom: 60px;
width: 80px;
height: 20px;
}
QTabBar::tab:last:selected {
border-image: url(btn81.png) repeat;
margin-bottom: 60px;
width: 80px;
height: 20px;
}
QWidget>QLabel { /* ! QWidget>QLabel */
margin: 0px;
color: white;
font: 21pt;
background: #7189DB;
border-top-left-radius: 25px;
border-top-right-radius: 25px;
border-bottom-left-radius: 25px;
border-bottom-right-radius: 25px;
}
QWidget {
background-color: #fff;
}
'''
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(qss) # +
window = MyWindow()
window.setWindowTitle('QTabWidget')
window.show()
sys.exit(app.exec_())
btn2.png
btn41.png
btn5.png
btn6.png
btn71.png
btn72.png
btn81.png
btn82.png
Еще один способ.
Тут я использую две кнопки и контейнер со свойством background-color.
1.png
1sleep.png
2sleep.png
corner.png
corner1.png
corner2.png
nothing.png - пустая картинка 10px*10px. Нужна, чтобы один из контейнеров не менял размер
from PyQt5 import QtCore, QtGui, QtWidgets
class MyWindow(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(MyWindow, self).__init__(*args, **kwargs)
self.corner = QtWidgets.QLabel()
self.corner.setPixmap(QtGui.QPixmap('corner.png'))
self.corner.setFixedHeight(10)
self.nothing = QtWidgets.QLabel()
self.nothing.setPixmap(QtGui.QPixmap('nothing.png'))
self.nothing.setFixedSize(10, 10)
self.corner1 = QtWidgets.QLabel()
self.corner1.setPixmap(QtGui.QPixmap('corner1.png'))
self.corner1.setFixedSize(50, 10)
self.corner2 = QtWidgets.QLabel()
self.corner2.setPixmap(QtGui.QPixmap('corner2.png'))
self.corner2.setFixedSize(50, 10)
self.corner_box = QtWidgets.QHBoxLayout()
self.corner_box.addWidget(self.corner, alignment = QtCore.Qt.AlignLeft)
self.corner_box.addWidget(self.nothing, alignment = QtCore.Qt.AlignRight)
self.corner.hide()
self.current_index = 1
main_box = QtWidgets.QHBoxLayout(self)
btn = QtWidgets.QPushButton('Main Box')
main_box.addWidget(btn)
box1 = QtWidgets.QHBoxLayout(spacing = 0)
main_box.addLayout(box1)
self.box2 = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel('Page 1', alignment = QtCore.Qt.AlignCenter)
self.btn1 = self.make_btn('1.png')
self.btn1.clicked.connect(lambda: self.remake_btn(1))
self.btn2 = self.make_btn('2sleep.png')
self.btn2.clicked.connect(lambda: self.remake_btn(2))
self.box2.addWidget(self.btn1)
self.box2.addWidget(self.btn2)
self.box2.addWidget(self.corner1)
self.corner1.hide()
self.box2.addWidget(self.corner2)
self.box2.addStretch(1)
box1.addLayout(self.box2)
self.container = QtWidgets.QWidget()
self.container.setStyleSheet(qss_box)
self.container.setFixedWidth(200)
self.container.setMinimumHeight(150)
box1.addWidget(self.container)
self.box3 = QtWidgets.QVBoxLayout(self.container)
self.box3.setContentsMargins(0, 0, 0, 20)
self.box3.addLayout(self.corner_box)
self.box3.addWidget(self.label)
def make_btn(self, picture):
btn = QtWidgets.QPushButton()
btn.setFixedSize(50, 50)
btn.setIcon(QtGui.QIcon(picture))
btn.setIconSize(QtCore.QSize(50, 50))
btn.setStyleSheet(qss_btn)
return btn
def remake_btn(self, index):
if (index == 1) and (self.current_index != 1):
self.corner.hide()
self.corner1.hide()
self.corner2.show()
self.btn1.setIcon(QtGui.QIcon('1'))
self.btn2.setIcon(QtGui.QIcon('2sleep'))
self.label.setText('Page 1')
self.current_index = 1
if (index == 2) and (self.current_index != 2):
self.corner.show()
self.corner2.hide()
self.corner1.show()
self.btn2.setIcon(QtGui.QIcon('1'))
self.btn1.setIcon(QtGui.QIcon('1sleep'))
self.label.setText('Page 2')
self.current_index = 2
qss_btn = '''QPushButton {
border: none;
margin: 0px;
padding: 0px;
}'''
qss_box = '''QWidget {
background-color: #7189DB;
border-top-right-radius: 10px;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}'''
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())






























