13 Mar 2018

Qt Quick Controls 2: Automatically Set The Width of Menus

The Qt Quick Controls 2 module which Qt has introduced some time ago gets better and better with every release. With the new Fusion style it not finally gets interesting enough also for Desktop applications (even though the other styles - like the Material one - also look very appealing on non-mobiles).

However, there are still some rough corners reminding us on the rather mobile heritage of QQC2:

I recently migrated an app to the new component library. The app uses the built-in MenuBar to display a menu bar. There is also a native MenuBar available, however, that one was not a choice for the app in question.

In that app’s menu, I had to use very long menu item names. In this case, file names in a recent files menu. The point is, QQC2 basically just truncates these.

Consider the following QML code:

// main.qml
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Controls 2.3

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    menuBar: MenuBar {
        Menu {
            title: qsTr("Normal Menu")

            MenuItem {
                text: qsTr("This is a menu item with a long title")
            }

            MenuItem {
                text: qsTr("And here is just another one with an even longer one")
            }
        }
    }
}

This basically will lead to the following:

Menu With Long Menu Item Texts

As you can see, the menu item entries get truncated at the end. To prevent this, we could simple set the width of the Menu to some larger value, but: What would be the right width? We could experiment so it looks good with the current theme, font size and resolution, but that’s going to break rather quickly, especially if you plan to translate the user interface strings.

Hence, a better approach might be to automatically set the width of the menu depending on the largest item in it. For this, we just create a new component called AutoSizingMenu:

// AutoSizingMenu.qml
import QtQuick 2.10
import QtQuick.Controls 2.3

Menu {
    width: {
        var result = 0;
        var padding = 0;
        for (var i = 0; i < count; ++i) {
            var item = itemAt(i);
            result = Math.max(item.contentItem.implicitWidth, result);
            padding = Math.max(item.padding, padding);
        }
        return result + padding * 2;
    }
}

And rewrite our main window class to:

// main.qml
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Controls 2.3

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    menuBar: MenuBar {
        AutoSizingMenu {
            title: qsTr("Normal Menu")

            MenuItem {
                text: qsTr("This is a menu item with a long title")
            }

            MenuItem {
                text: qsTr("And here is just another one with an even longer one")
            }
        }
    }
}

And voila, here we go:

Menu With Automatically Set Width

So what we do is basically this:

  1. We bind the width property of the menu to a value we calculate by iterating over all child menu items.
  2. For each menu item, we take the implicitWidth of the contentItem and the padding of the item itself. The content item is responsible for rendering the text of the menu entry.
  3. The desired width of the menu is the sum of the maximum width and the maximum padding values of all entries.

There are probably some things to consider using this approach:

  • Such complex property bindings are expensive. However, granted that most menus have only a limited number of entries which rarely change, this should be fine.
  • An alternative to increasing the width could also be to use a custom contentItem in the affected menu entries and let the text wrap (instead of being ellided). I tried this in the app I ported and it worked well for most menu items, however, in particular for the ones with the very long file names as texts, it did not work. This could have been due to these menu’s were populated via a Repeater - so if you do not use such an approach to populate your menu items, the wrapping approach might be better (as you require less horizontal space).
  • Last but not least, the above sample component does ensure the menu fits into the window. If the largest menu item is too wide, the menu will appear cut off at the window edge.
Thank You For Reading
Martin Hoeher

I am a software/firmware developer, working in Dresden, Germany. In my free time, I spent some time on developing apps, presenting some interesting findings here in my blog.

Comments

EddieC

Hi Martin,

This is a great solution! May I use it on my open-source software? If affirmative, under which license.

Martin Hoeher

Hi EddieC,

sure you may use that code! Just consider it public domain, no attribution or whatever needed for such a little code snippet. 😉

Vladimir

Hello Martin,

Thank you for sharing your approach. The following worked for me:

Menu { id: menu

title: qsTr("Normal Menu")

MyMenuItem {
	text: qsTr("This is a menu item with a long title")
}

MyMenuItem {
	text: qsTr("And here is just another one with an even longer one")
}

component MyMenuItem: MenuItem {
	onImplicitWidthChanged: {
		if(menu.contentWidth < implicitWidth)
			menu.contentWidth = implicitWidth
	}
} }
Your comment has been filed

We'll review it and it will appear here as soon as we're done.

Sorry, something went wrong...

Your comment could not be posted.

Regarding your personal information...
  • The name you enter will be shown next to your comment. You may enter your real name or whatever you like.
  • Your e-mail will be used to show an image representing you next to your comment. We do not store nor show your address somewhere. Instead, an ID will be calculated from it and stored. This ID is then used to retrieve an image from Gravatar.