Skip to content

Adapting SlideViewer to Qt Quick Controls

Several previous posts have introduced our SlideViewer tool which we created for use in the various trainings we deliver. The tool started out as an experiment, created using basic QtQuick 2 Items. Startup configuration was specified by command line arguments, and a simple Keys.onPressed function provided most of the runtime control: both navigating around the slide deck but also more advanced functions such as reloading the slide deck and toggling fullscreen mode.

While the above was sufficient for developers, it was unsuitable if we ever wanted to give SlideViewer to non-technical people as a packaged application – and some developers do prefer to use a real GUI too! Hence the aim was to give SlideViewer a menu-bar and dialogs, using the features available in Qt Quick Controls.

First Attempt

My initial change was to add a Menubar item to our existing Item hierarchy, with some Menus and MenuItems:

    MenuBar {
        id: menuBar

        Menu {
            title: "&File"
            MenuItem { text:"Open" }
        }

        Menu {
            title: "&Go"

            MenuItem {
                text: "&Next slide"
                shortcut: "Right"
                onTriggered: _slideDeckController.incrementPage(1)
            }

            MenuItem {
                text: "&Last slide"
                shortcut: "End"
                onTriggered: _slideDeckController.gotoSlide(_slideDeckController.total,0)
            }
        }
    }

This actually worked on Mac, where we use native code to create and define the menu-bar. But actually MenuBar is supposed to live within an ApplicationWindow element in QtQuick. ApplicationWindow inherits from Window, but is not an Item – so this entailed some changes in the top-level QML element.

Using an ApplicationWindow

Item {
  id: rootWindow
  Item {
    id: content
    // .... all the SlideViewer window content
  }
}

… becomes this …

ApplicationWindow {
  id: rootWindow
  Item {
    id:content
    // .... all the SlideViewer window content
  }
  MenuBar {
    Menu {
      ...
    }
  }
}

But more importantly, it entailed some changes in C++. QQuickWindow is a QWindow, and expects the QML file it loads to define a root Item. But with the new structure above, the root element in our QML is a Window. We need a QML / QtQuick engine which expects us to load window – and fortunately, it exists: QQmlApplicationEngine.

Setting up the application engine is very similar to a QQuickWindow:

QQmlApplicationEngine appEngine;
appEngine.addImportPath(":/qml");
appEngine.load(QUrl("qrc:///qml/SlideViewer/SlideViewerWindow.qml"));

Because we want to manipulate the application window from C++, we use the rootObjects method of the engine to find our application window:

Q_ASSERT(!appEngine.rootObjects().isEmpty());
QQuickWindow * const rootWin = qobject_cast<QQuickWindow*>(appEngine.rootObjects().front());
Q_ASSERT(rootWin);

Actions, always actions

The next problem is that we’re duplicating logic between MenuItems and the Keys.onPressed logic that already existed. The solution in QtQuick Controls is analagous to that in widget-based applications: actions! QAction from C++ is tied to the widget classes, so it can’t be exposed to QtQuick; instead we have a very similar Action item in Qt Quick Controls. As you would expect, you can supply the name, icon, keyboard shortcut and triggered behaviour of each Action once, and then refer to the Action from a MenuItem. Actions also work in toolbars, but SlideViewer doesn’t use those for now.

Action {
  id: printAction
  text: "Print"
  shortcut: "Ctrl+P"
  onTriggered: _printer.start()
}
MenuBar {
  Menu {
    ...
    MenuItem { action:printAction }
    ...
  }
}

Adding a dialog

Having made these changes, adding some dialogs was straightforward (at least for the complexity needed by SlideViewer) – here is our SettingsWindow.qml:

ApplicationWindow
{
  ColumnLayout {
    anchors.fill: parent
    Checkbox { id:skipHandoutPages; text: "Skip handout pages" }
    Checkbox { id:showClock; text: "Show clock" }
    RowLayout {
      anchors: { bottom: parent.bottom; right:parent.right}
      Button {
        text: "OK"
        onTriggered: ....
      }
    }
  } // of column layout
}

Composing buttons and checkboxes using column and row layouts, and mixing QtQuick Controls layouts with standard QtQuick anchors, works nicely.

Finishing up

There is one final piece to avoid long startup times and increased memory footprint, as we add more dialogs: in a widgets-based application it would be unusual to create all dialogs at startup. Similarly for QtQuick we want to defer loading the QML for a dialog until it’s shown. The solution to this is of course the trusty Loader item.

The final result is SlideViewer is now approaching something we could package and deploy to non-technical users, without needing to explain command-line arguments to them, or memorise keyboard shortcuts. From the developer side we did need to restructure some things; if the codebase was larger, these changes could have been painful. Transitioning from a prototype to a real application UI is not something to leave until the day before ship!

This was my first experience using Qt Quick Controls, and for this scale of application they worked well – definitiely a viable alternative to using widgets.

Categories: KDAB Blogs / KDAB on Qt / SlideViewer

Tags:

1 thought on “Adapting SlideViewer to Qt Quick Controls”

Leave a Reply

Your email address will not be published. Required fields are marked *