Multiplatform QML Menus
-
Hi,
I have a question regarding native QML menus. I have a macOS app that works well. To handle menus, I use theQt.labs.platform
module, which has aMenuBar
that creates a native macOS menu.
But I'm now in the process of porting that application to Windows. And I have no clue what to do to make the menu bar appear somewhere in my window. It's clearly stated in the doc that Windows is supported so I'm assuming this is meant to work on that platform too :/Am I missing something obvious here?
And if the doc is incorrect, or for some other reason I can't use the same module on Windows to handle the menu: how can I go about to have a menu that work on both platform? I could conditionally import
Qt.labs.platform
if I'm on macOS, otherwise use theQtQuick.Controls
version.. that is if there was a way to do that?
Edit: well, that wouldn't even work since the name for the "shortcut" is "sequence" in theQtQuick.Controls
version..Any help appreciated!
-
Just an intuition, but I feel it is highly likely there are QML "warnings" being output to the console (whatever Windows uses for stderr, stdout).
I say "warnings" in "scare quotes" because I consider many of these to actually represent critical errors for a UI, but nonetheless, due to the interpreted (or semi-compiled/semi-interpreted) nature of QML, these happen at runtime and understandably Qt has chosen to let the app continue running and not crash.
However, many of these represent flaws in your QML usage that prevent your UI from being usable. You may have already experienced these on MacOS but somehow had 'luck' and the UI was able to recover. Now, on another platform, either there are new warnings or the UI is somehow unable to recover from the preexisting ones.
Examples of what you might see:
"Apparently out-of-range date", "called when not loading", "can't resolve property alias for 'on' assignment", "Cannot create a component", "Cannot create new component instance", "Cannot pause a stopped animation", "Cannot set context object", "Cannot set property", "Component creation is recursing", "Component destroyed while completion pending", "Component is not ready", "Could not convert argument", "Could not convert array value", "Cyclic dependency detected ", "cyclic prototype value", "Detected anchors on an item that is managed by a layout", "emitted, but no receivers connected to handle it", "Failed to get image from provider", "failed to load component", "insertAnimation only supports to add animations after the current one", "Model size of", # "...is less than 0" AND/OR "...is bigger than upper limit" "Must provide an engine", "QML LoaderImage", "qmlRegisterSingletonType requires absolute URLs", "qmlRegisterType requires absolute URLs", "ReferenceError:", "specifying the encoding as fourth argument is deprecated", "TypeError", "Unable to assign", "Unable to handle unregistered datatype", "unregistered datatype",
-
I don't know about the menu-specific portion of the question.
QQmlFileSelector is intended to support similar use cases by placing platform-specific components in different directories.
This can be also be implemented via a Loader and a switch or if-else keying off of platform.os.
-
@jeremy_k Interesting, I didn't know of that one. I'll have a look, thanks!
It won't solve all the problems though: since the Qt.labs menu bar is displayed on the macOS menu, the content of my app is anchored to the whole window. Whereas the QtQuick.controls menu bar is displayed
inside
the window, so I the anchoring can no longer be the whole window on Linux/Windows. And this can't be solved in QML also because the Qt.labs menu bar doesn't support anchoring :D
But at least for the components' implementation, this seems like a viable solution!(note: to solve this, I ended up using CMake's
configure_file
to inject platform specific snippets where necessary. It ended up being "okay'ish". But I really wish QML had at least a very rudimentary preprocessor capability that would allow to#ifdef
parts of the code..) -
@Citron said in Multiplatform QML Menus:
note: to solve this, I ended up using CMake's configure_file to inject platform specific snippets where necessary. It ended up being "okay'ish".
@Citron I'm very curious: What kinds of snippets did you have to inject? I'm interested in seeing what kind of extra code was effective in getting the
Qt.labs.platform
MenuBar
to work on Windows. -
@Citron said in Multiplatform QML Menus:
@jeremy_k Interesting, I didn't know of that one. I'll have a look, thanks!
It won't solve all the problems though: since the Qt.labs menu bar is displayed on the macOS menu, the content of my app is anchored to the whole window. Whereas the QtQuick.controls menu bar is displayed
inside
the window, so I the anchoring can no longer be the whole window on Linux/Windows. And this can't be solved in QML also because the Qt.labs menu bar doesn't support anchoring :DI suspect this merely requires a little more creative thinking. Something along the lines of:
anchors.top: os.platform != "macOS" ? menu.bottom : undefined
(note: to solve this, I ended up using CMake's
configure_file
to inject platform specific snippets where necessary. It ended up being "okay'ish". But I really wish QML had at least a very rudimentary preprocessor capability that would allow to#ifdef
parts of the code..)Component + Loader with conditional use, or define the component as a string and dynamically instantiate it.
-
@jeremy_k Yes you're right, I totally forgot that it's valid to assign
undefined
to anchors, so this can be handled dynamically.@KH-219Design I didn't try to make Qt.labs MenuBar work on Windows. On Windows I'm using the regular QtQuick.Controls MenuBar. So the QML code for defining the menu in my app is the same on both platform. I just have to inject the
import Qt.labs.platform
when I'm on macOS (and a few other "patches" because there are some slight differences between Qt.labs and QtQuick.Controls implementations of menu items) Basically I'm just re-creating the ability to have #if/#else chunks of code in QML...Basically in the header part of my QML, I have a
@MACOS_IMPORT_STATEMENT
which CMake replaces byimport Qt.labs.platform as Labs
on macOS and nothing on other platforms.
And in my main window code, I have something like:@MENU_DEFINITIONS@ MenuBar_ { Menu_ { title: "Application" MenuItem_ { text: "Preferences" shortcut: Qt.platform.os === "windows" ? "Ctrl+P" : StandardKey.Preferences onTriggered: preferences.open() } MenuSeparator {} MenuItem_ { text: "Quit" // NOTE: Because for some reason Alt+F4 is not considered the standard way of quitting an app on Windows? Seriously? shortcut: Qt.platform.os === "windows" ? "Alt+F4" : StandardKey.Quit onTriggered: Qt.quit() } } Menu_ { title: "Edit" // ... } }
And
@MENU_DEFINITIONS@
is replaced by CMake bycomponent MenuBar_ : Labs.MenuBar {} component Menu_ : Labs.Menu {} component MenuItem_ : Labs.MenuItem {}
on macOS and
component MenuBar_ : MenuBar {} component Menu_ : Menu {} component MenuItem_ : MenuItem { id: ctrl property alias shortcut: action.sequence contentItem: RowLayout { Label { text: ctrl.text; Layout.fillWidth: true } Label { text: action.nativeText; enabled: false } } Shortcut { id: action enabled: ctrl.enabled context: Qt.ApplicationShortcut onActivated: ctrl.triggered() } }
on Windows (because I wanted to shortcuts to show up in the menu items, like it does on macOS)
Anyway. It's still a bit annoying that such a trivial thing is so convoluted to do in QML... Yes there are components, loaders and whatnot. But: you pay a small performance price for using those: they are meant for dynamic stuff. How ofter does the same running QML code change platform? Never. Platform specific stuff should be resolved compiled time. And more importantly: using native QML "solutions" introduces a lot of boiler plate code for no good reason.