Reputation: 305
I am trying to create a Side Menu in PyQt, similar to this one: https://www.youtube.com/watch?v=O9l75KOB2pE
To create "sub-menus" in the Menu, I opted for the "ToolBox" and then added a QPushButton after ToolBox (which would be a single button below the Sub-Menus. It also helps the "un-opened" sub-menus to not go to the end of Frame)
However, I am facing multiple issues while doing so. In the video, he added a border-bottom under every button and also added a border-left when a button is hovered upon.
I did the same, but apparently my buttons aren't fully expanded and hence the border-bottom is added only below the "text" of the QPushButton and not the whole menu. Similarly, the border-left gets added to the very left of the QPushButton's text (and not to the left of the Side Menu).
Moreover, the Tabs of the Toolbox are also shrinked (that is, full text is not shown in them. That is also probably because it is not getting the whole width of the Side Menu's frame)
I tried to play with the "SizePolicy" of different widgets to somehow make the Buttons expand fully in the width, but nothing is working for me. Can anyone help me solve the issue?
Here is the .ui code of my current program,
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1074</width>
<height>751</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="styleSheet">
<string notr="true">* {
border: none;
}
.QTableWidget
{
alternate-background-color: rgb(241, 241, 241);
background-color: white;
}
.QTableWidget:item
{
color: black;
padding: 5px;
}
.QHeaderView::section
{
background-color: white;
color: black;
font-weight: bold;
border: 0px;
border-bottom: 1px solid;
}</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="styleSheet">
<string notr="true">background-color: white;</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="side_menu_container">
<property name="maximumSize">
<size>
<width>230</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">background-color: #1b1b1b;
color: white;
font-size: 25px;
font-weight: 600;
line-height: 65px;
text-align: center;
letter-spacing: 1px;
</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignTop">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_42">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignLeft|Qt::AlignTop">
<widget class="QFrame" name="frame_46">
<property name="styleSheet">
<string notr="true">font-size: 21px;
</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignLeft|Qt::AlignTop">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<family>Arial</family>
<pointsize>-1</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Timetable Manager</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_45">
<property name="enabled">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true">background-color: #1e1e1e;</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_20">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="side_menu">
<property name="minimumSize">
<size>
<width>228</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QPushButton
{
font-size: 16px;
line-height: 60px;
padding: 5px 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
color: #fff;
border-left: 1px solid transparent;
}
QPushButton:hover
{
color: cyan;
background-color: #1e1e1e;
border-left: 1px solid cyan;
}
QToolBox::tab
{
font-size: 15px;
border: none;
}
QToolBox::tab:selected
{
background-color: #1e1e1e;
border-left: 1px solid cyan;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignLeft|Qt::AlignTop">
<widget class="QToolBox" name="toolBox">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>230</width>
<height>201</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<attribute name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/icons/down-arrrow-navigate.png</normaloff>
<normalon>:/icons/icons/chevron-down.svg</normalon>:/icons/icons/down-arrrow-navigate.png</iconset>
</attribute>
<attribute name="label">
<string>Registration Details</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item alignment="Qt::AlignTop">
<widget class="QFrame" name="frame_5">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="leftMargin">
<number>15</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="0">
<widget class="QPushButton" name="course_menu_button">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Courses</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="room_menu_button">
<property name="text">
<string>Rooms</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="teacher_menu_button">
<property name="text">
<string>Teachers</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="registered_menu_button">
<property name="text">
<string>Registered Courses</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="section_menu_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>-1</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Sections</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>230</width>
<height>102</height>
</rect>
</property>
<attribute name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/icons/chevron-down.svg</normaloff>:/icons/icons/chevron-down.svg</iconset>
</attribute>
<attribute name="label">
<string>Teacher Preferences</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item alignment="Qt::AlignTop">
<widget class="QFrame" name="frame_32">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QPushButton" name="room_preference_menu_button">
<property name="text">
<string>Room Preferences</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="slot_preference_menu_button">
<property name="text">
<string>Slot Preferences</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>230</width>
<height>138</height>
</rect>
</property>
<attribute name="label">
<string>Page</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_17">
<item alignment="Qt::AlignTop">
<widget class="QFrame" name="frame_43">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_18">
<item>
<widget class="QPushButton" name="student_clash_menu_button">
<property name="text">
<string>Student Clashes</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="room_clash_menu_button">
<property name="text">
<string>Room Clashes</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="instructor_clash_menu_button">
<property name="text">
<string>Instructor Clashes</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_44">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
<item alignment="Qt::AlignTop">
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="main_body">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<resources>
<include location="icons.qrc"/>
</resources>
<connections/>
</ui>
Upvotes: 0
Views: 1145
Reputation: 48231
There are various issues with the provided UI.
The problem with the buttons has various reasons, and cannot be easily solved from Designer, especially because you changed a lot of properties (many of which are propagated, including stylesheets).
First of all, the left border is shifted because all layouts have a default margin in most systems, so you have to explicitly set it to 0.
In Designer, select each page from the object inspector, scroll to the bottom of the property editor and change the layoutLeftMargin
property to 0. If the property is already 0 but is not shown in bold (which indicates whether a property uses the default value or an explicit one), change to another number and then set it again to 0. Then do the same with the frame you've added in that page.
The problem of the "incomplete" bottom border on the first frame is due to the fact that you used a QFormLayout, which in some styles makes button occupy only the minimum size, instead of making them expand.
Change it to a vertical layout, and then add the following to the side_menu
stylesheet of the QPushButton selector:
text-align: left;
Also note that with such a complex widget structure, you should be careful in not setting too many stylesheets in different places: not only it's usually unnecessary, but it makes it also difficult to track them (at some point you might face some unexpected behavior, and finding the actual source of the problem can become very hard). Consider that it's usually better to not set generic stylesheet properties on parents like you did for side_menu_container
, or even the main window. Complex widgets like scroll areas and combo boxes require that if you set a property then you have to set all properties. Using specific selectors (and not the wildcard one!) is a better and safer choice. Also note that the letter-spacing
is not a recognized property of QSS, so you should remove it.
A well written stylesheet should be set for the top level widget (or even the application), and eventually set a specific stylesheet for a widget only if it's actually required for that widget.
Finally, you've set a layout alignment in some widgets, and that prevents them to be properly displayed, since setting the alignment results in making the widget only use its basic size hint, even if there's more space available.
The issue with the elided text of the tool box buttons is a bit more tricky. The issue arises from the fact that the QStyleSheetStyle (a special and private QStyle that is automatically set on a widget that has a stylesheet or inherits one) automatically uses elided text, and it seems that the implementation is not well done as the style option doesn't correctly initialize the available text rectangle. I strongly believe that this is a bug.
If no stylesheet were set, the solution would be simple: use a QProxyStyle and override drawControl
in order to draw the button text on your own. Unfortunately, this isn't possible for stylesheets as drawControl
is never called, because it's overridden by the installed QStyleSheetStyle.
But, drawItemText()
is called, so there is a possible, a bit hacky and complex solution.
Unfortunately, drawItemText()
has no reference for the widget, as it only paints the text with the given options.
The trick is to use a promoted widget for the toolbox, set the custom proxy style for it, and override some of its function to keep track of the "actual" names using identifiers, and then call the base implementation with the real name.
class ToolBox(QtWidgets.QToolBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.names = []
self.nameDict = {}
self.setStyle(ProxyStyle(self))
def updateNames(self):
self.nameDict.clear()
for i, realName in enumerate(self.names):
fakeName = '__{}'.format(i)
super().setItemText(i, fakeName)
self.nameDict[fakeName] = realName
def itemInserted(self, index):
self.names.insert(index, super().itemText(index))
self.updateNames()
def itemRemoved(self, index):
self.names.pop(index)
self.updateNames()
def setItemText(self, index, text):
self.names[index] = text
self.update()
def itemText(self, index):
return self.names[index]
class ProxyStyle(QtWidgets.QProxyStyle):
def __init__(self, toolBox):
super().__init__(QtWidgets.QStyleFactory.create('fusion'))
self.toolBox = toolBox
def drawItemText(self, painter, rect, alignment, palette, enabled, text, role=QtGui.QPalette.NoRole):
text = self.toolBox.nameDict.get(text, text)
super().drawItemText(painter, rect, alignment, palette, enabled, text, role)
To promote the widget, right click on the tool box from the object inspector, select "Promote to...", then type "ToolBox" (our subclass) in the "Promoted class name" field, type the name of the file in which the class is stored (without the extension!), then click "Add" and finally "Promote".
Note that the file of the class can even be the main script, but in that case you must use the final if __name__ == '__main__':
block for the application, since the promotion loads the "header" as an import.
Upvotes: 2