Purchase Linux CDs / DVDs / Flash Drives at OSDisc.com

Welcome to Our Community

While Linux.org has been around for a while, we recently changed management and had to purge most of the content (including users). If you signed up before April 23rd, 2017 please sign up again. Thanks!

C++ Tutorial: Create QT applications without QTCreator

Discussion in 'Command Line' started by JasKinasis, Jul 2, 2018.

  1. JasKinasis

    JasKinasis Well-Known Member

    Joined:
    Apr 25, 2017
    Messages:
    279
    Likes Received:
    531
    Introduction:
    In this thread user @blackneos940 asked a couple of QT related questions.

    I've put together a little tutorial, which hopefully answers his questions.
    But I've decided to post it in it's own thread, as a piece of stand-alone content for the rest of the community.

    The questions essentially boiled down to:
    1. How can I link a QT button to a terminal command?
    2. Is it possible to create a QT based GUI application in the terminal without using QTCreator?

    So the aim of this tutorial is to create a simple QT based GUI application from the command line without having to use QTCreator. We'll also address the button related question too. More on that in a bit.

    For anybody who doesn't know:
    QTCreator is an IDE (Integrated Development Environment) for QT. It is developed and maintained by the same people who develop the QT libraries and is an excellent piece of software to use for quickly building QT based applications.

    Before trying to build QT programs from the command-line, I would personally recommend using QTCreator for your first few projects, until you have built up a bit more of a working knowledge of the QT application framework, its components and features.

    Anyway, neo wanted to know if it is possible to create a QT GUI without QTCreator - it is - and this tutorial will show you how.
    In order to answer neo's other question about connecting a button to a terminal command, we'll be developing a small QT Widgets based GUI application.
    All we'll have in our main widget is:
    - A button (using QT's QPushButton class)
    - A text-browser (QTextBrowser).

    When we click the button - we will start a terminal program "ls -alh $HOME/Desktop/" in a separate process and display its output in the text-browser.

    In order to connect the button-click to a function that will start a terminal command. And to connect the output of the process to the text-browser, we will be using QT's signals and slots API's.


    Pre-requisites:
    At a minimum - this tutorial will require g++, make, qmake and the qtdeclarative5-dev package.

    NOTE: I have pretty much all of the qt dev packages installed on my machine. But I'm fairly certain that the qtdeclarative5-dev package will bring in everything we need for this tutorial. All of the packages should be available via your distros usual package management tools.
    Also, the exact name of the qt5 package you need might be sightly different on other distros. My listing is for debian based distros, so if you use a different distro, you may want to double check the package names.

    Before we go any further - I will assume that you have all of the pre-requisites installed. Also, QT is primarily a C++ library, so the programming language we will be using will be C++. It is possible to create QT applications using python and various other languages, but this tutorial will be C++ based. So a basic understanding of C++ would be beneficial!

    Creating the bare-bones of the application:
    Rather than giving you all of the complete files in one go - we'll do this step by step as if we were starting from scratch.

    So the first thing we'll do is set up our environment for our project, then we'll build the shell of our application and it's gui - with no functionality. Then we'll set up our build scripts and build our first version of our application. Then we'll add a few bits of functionality and build the final, definitive version.

    So first, let's set up our environment for our project.
    Open up a terminal and create a new empty directory called "NeosQt" for the source files to go into.
    Code:
    mkdir NeosQt
    
    Next, cd into the directory:
    Code:
    cd NeosQt
    
    And now let's fire up our favourite terminal-based text editor and create the main shell of our application. Personally, I use vim, but you can use whatever floats your boat!
    Here's the listing for main.cpp:
    Code:
    #include <QtWidgets>
    #include "mainwidget.h"
    
    int main(int argc, char *argv[])
    {
        // Creates an instance of QApplication
        QApplication a(argc, argv);
    
        // This is our MainWidget class containing our GUI and functionality
        MainWidget w;
        w.show(); // Show main window
    
        // run the application and return execs() return value/code
        return a.exec();
    }
    
    main.cpp is our applications main entry point. It is where our program starts and ends.
    Incidentally, its code is also pretty much identical to what QTCreator would have auto-generated for us.

    So what's going on in the above code?
    We #inlude QtWidgets - which contains declarations of various QT widget classes including QApplication.
    QApplication is a QT class which basically encapsulates all of the low-level stuff required for a QT application. It deals with initialising QT, displaying our GUI, resource management and our applications main events loop. For all intents and purposes it is a little black box.

    We have also #include'd mainwidget.h - we haven't created that yet. We'll get onto that next - mainwidget.h will be the header for our main widget class. This will contain our GUI and all of the functionality for our little application.

    At line 7 of the main() function, we create an instance of QApplication. As mentioned, this will deal with a lot of things for us.
    At lines 10 and 11 - we create an instance of our MainWidget class and call it's show method.
    Despite calling show() - our application is not actually ran until the final line is called:
    Code:
    return a.exec();
    This executes our QApplication. It displays our mainwidget and sets the applications event-loop running so our application will respond to events and signals.

    Our application will run until the user closes it down - at which point the return statement will return whatever value was returned by a.exec().
    If a.exec() ends normally - it will return 0. Otherwise an error code will be returned.
    In other words - Whatever value is returned by a.exec() is returned to the shell from main().

    And that is about it for our main function. Not a huge amount to see and we won't need to see it again. That's done, completely in the bag. We won't need to revisit main.cpp again.

    Next we'll declare our MainWidget class in mainwidget.h:
    Code:
    #ifndef MAINWIDGET_H
    #define MAINWIDGET_H
    
    #include <QWidget>
    
    class QPushButton;
    class QTextBrowser;
    
    // This is the declaration of our MainWidget class
    // The definition/implementation is in mainwidget.cpp
    class MainWidget : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit MainWidget(QWidget *parent = 0); //Constructor
        ~MainWidget(); // Destructor
    
    private:
       QPushButton* button_;
       QTextBrowser* textBrowser_;
    };
    
    #endif // MAINWIDGET_H
    
    Above, we can see our MainWidget class is publicly derived from QWidget. So our class is going to be a custom QT widget.

    Skipping the pre-processor/header sections for now - we'll come back to those in a moment.
    The first thing in our MainWidget class declaration is this:
    Code:
    Q_OBJECT
    This is a pre-processor macro used by the QT build tools.
    This lets the QT build tools know that our class is a QT object and will cause the build-tools to automatically create some additional intermediate code when we build our application later on.

    In the "public:" section of our class - we only have a Constructor and Destructor.
    As their names imply, the constructor is called when we create a new instance of our class and is used to allocate memory and initialise our widget.
    The Destructor is called when we "destroy" an instance of our class. It is used to free up any dynamically allocated memory.

    There are no other public functions in our class. So the only publicly available interfaces for our MainWidget class are its constructor and destructor. Which means the only thing that users of our class can do are create and destroy instances of it! The rest of its internals are private.

    The next section "private:"
    In here we have pointers to a QPushButton, and a QTextBrowser. That's all our application will initially contain - a button and a text-area.

    Going back to the pre-processor section, at the top of the file we have a simple macro include guard (#ifndef XXX ... #define XXX ....#endif // XXX) which encapsulates our entire class declaration. This ensures that our header only gets included by the compiler once.

    Our only #include is QWidget. We include it because our class is derived from QWidget - so the compiler will need to know exactly what QWidget is and how much memory to allocate for it.

    At lines 6 and 7, we have two additional "class" declarations for QPushButton and QTextBrowser. These are called forward-declarations.

    Now you may be wondering "Why didn't we #include the header files for QPushButton and QTextBrowser?".
    The answer to that question is:
    Because our class only stores pointers to a QPushButton and a QTextBrowser.
    Pointers always have the same size - regardless of the size of the object they point to. So we can forward-declare the objects that they point to - which means that we will have to #include their headers in our implementation (.cpp) file.

    So at this point, because we've used forward declarations - the compiler knows that our class contains two pointers to two different classes that will be fully defined later on. And the compiler already knows the size of a pointer - it already knows how much memory will be used by each of the pointers, even though it doesn't know anything about the classes themselves. So it will look for the complete definition of those classes in our implementation (.cpp) file.


    Implementing our MainWidget class:
    Next up we'll fully define our MainWidget class and set up its GUI.
    Firts the code, then we'll take a look at it.

    mainwidget.cpp:
    Code:
    #include <QtWidgets>
    #include "mainwidget.h"
    
    // Constructor for main widget
    MainWidget::MainWidget(QWidget *parent) :
        QWidget(parent)
    {
       button_ = new QPushButton(tr("Push Me!"));
       textBrowser_ = new QTextBrowser();
    
       QGridLayout *mainLayout = new QGridLayout;
       mainLayout->addWidget(button_,0,0);
       mainLayout->addWidget(textBrowser_,1,0);
       setLayout(mainLayout);
       setWindowTitle(tr("Connecting buttons to processes.."));
    }
    
    // Destructor
    MainWidget::~MainWidget()
    {
       delete button_;
       delete textBrowser_;
    }
    
    Analysis:
    We #include QtWidgets - which has the delcarations for all of the QT widgets - e.g. Our QPushButton and QTextBrowser and the QGridLayout class we'll be using to help lay-out our application and the other QTClasses we'll be using later on.
    Then we include the header for our MainWidget class. After all, this is MainWidget.cpp!

    At the moment, we only have a constructor and a destructor in our class.
    The constructor does rather a lot. In it's initialiser list, it first initialises all of its base members (QWidget), by passing the passed-in QWidget pointer (parent) into QWidgets constructor. This will set up and initialise all of the members for the QTWidget base class which our widget is derived from.

    Then we create a new QPushButton and a new QTextBrowser. These are the two widgets/controls that will make up the entirety of our GUI.

    Next we create a new QGridLayout object. This is the QT object which will deal with placing the controls on our widget. As it's name implies QGridLayout is a grid-based layout manager. There are several other types of layout managers available in QT - you can look them up in the QT documentation.

    In our application we just want the button at the top and the text area underneath, So we've added our button to to the grid at position 0,0 (top left corner), and we added the textbox underneath it at position 1,0 (Bottom left corner). If we wanted them side by side, we could have put the textbox at 0,1. But for this example, I'm just going to put the textbox underneath the button.

    Finally, the last thing we do in the constructor is set a window-title for our application.

    Now let's take a look at the Destructor.
    Our destructor deletes/free's the memory used by the dynamically allocated objects pointed to by our pointers (button_ and textBrowser_).

    If we were using QTCreators form designer, it would have added some preprocessor macros, which in turn would add other additional bits of code which would automatically deal with memory management for the GUI elements. But because we have manually created our GUI, it is up to us to take care of memory management. So any new objects that our class creates will need to be deleted in the destructor.


    And that is everything we need for the bare shell of our application - at least for now. Our GUI doesn't have any functionality yet. We'll add that later.


    Before that, let's set up our build environment and build what we have so far.
    So first, we'll use qmake to create a QT project file using the following command:
    Code:
    qmake -project
    
    This will create a .pro file. Typicaally it will be named after the parent directory contaning the source files.
    So, assuming you called your initial directory NeosQt, the file will be called NeosQt.pro

    The generated file should look something like this:
    NeosQt.pro:
    Code:
    ######################################################################
    # Automatically generated by qmake (3.1) Wed Jun 20 21:44:00 2018
    ######################################################################
    
    TEMPLATE = app
    TARGET = NeosQt
    INCLUDEPATH += .
    
    # The following define makes your compiler warn you if you use any
    # feature of Qt which has been marked as deprecated (the exact warnings
    # depend on your compiler). Please consult the documentation of the
    # deprecated API in order to know how to port your code away from it.
    DEFINES += QT_DEPRECATED_WARNINGS
    
    # You can also make your code fail to compile if you use deprecated APIs.
    # In order to do so, uncomment the following line.
    # You can also select to disable deprecated APIs only up to a certain version of Qt.
    #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
    
    # Input
    HEADERS += mainwidget.h
    SOURCES += main.cpp mainwidget.cpp
    
    NOTE: The line "TARGET = NeosQt"
    This will be the name of our final executable. By default qmake will use the name of the directory containing the source code to be the name of the executable.
    You can change the target name if you want.

    Before we can build our project - the next step will be to use qmake again and use our .pro file to generate a makefile.

    But qmake is a bit rubbish at detecting/resolveing dependencies sometimes. So before we move onto the next step we need to add an extra line to the .pro file.
    We need to inform qmake which QT modules we'll be using. In this case, we're only using widgets, so we need to add the following line to our .pro file:
    Code:
    QT += widgets
    So our .pro file should now look something like this:
    Code:
    ######################################################################
    # Automatically generated by qmake (3.1) Wed Jun 20 21:44:00 2018
    ######################################################################
    
    TEMPLATE = app
    TARGET = NeosQt
    INCLUDEPATH += .
    
    QT += widgets
    
    # The following define makes your compiler warn you if you use any
    # feature of Qt which has been marked as deprecated (the exact warnings
    # depend on your compiler). Please consult the documentation of the
    # deprecated API in order to know how to port your code away from it.
    DEFINES += QT_DEPRECATED_WARNINGS
    
    # You can also make your code fail to compile if you use deprecated APIs.
    # In order to do so, uncomment the following line.
    # You can also select to disable deprecated APIs only up to a certain version of Qt.
    #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
    
    # Input
    HEADERS += mainwidget.h
    SOURCES += main.cpp mainwidget.cpp
    
    Now we can move onto the next step. Now we use a different qmake command which will use our .pro file to generate a makefile:
    Code:
    qmake NeosQt.pro
    
    This should have generated a Makefile.
    So the final step should simply be:
    Code:
    make
    
    This will run gnu make and build our executable.

    You can view the application by running:
    Code:
    ./NeosQt
    
    And with any luck that should yield something that looks like this:
    QTTutorialScreenshot.jpeg

    So that is the shell of our GUI built. And we didn't use QTCreator! But our application doesn't do anything yet. So now we will add some functionality.

    Adding functionality:
    Now that we've created our basic application and our GUI - we want to make it do something when the button is pressed.

    To facilitate this, we'll be using QT's "Signals and Slots" implementation.

    So what are signals and slots?
    Every QT widget/class has a set of "signals" that it can send/emit - the exact signals vary from class to class.

    For example:
    Widgets like buttons can send signals like "pressed" when a button is pressed, or "released" when it is released.



    And slots are functions/methods that can be associated with signals - so when a signal is received - a slot function can be called. Some classes have pre-defined slots, but you can also create your own slot methods.

    In order to connect a QT widgets signal to another classes slot method, you need to use QT's "connect" function.

    The QT documentation is really good and lists any signals/slots that are available for any given class.

    Getting back to our application. In order to make our MainWidget do something when we press the button, we need to associate one of our QPushButton's signals with a function/method in our MainWidget using QT's "connect" function/method.

    In this case, we'll create a new slot function in our MainWidget class called onButtonReleased(). Then in MainWidget's constructor, we will use the connect function to associate/bind MainWidget's onButtonReleased() method with the buttons "released" signal.

    If any of that doesn't make sense - hopefully the code will clear it up.

    Speaking of clearing things up. We have a lot of new files that were generated when we last built our executable. So we'll do a little bit of housekeeping by running the following command to remove the executable we built and all of the intermediate files:
    Code:
    rm NeosQt
    make distclean
    
    distclean in the above step will remove all of the intermediate files AND our makefile - but that's not a problem. We'll use qmake to regenerate it later.

    After running the above commands, all we'll be left with will be our .h and .cpp files and our .pro file. And with that little bit of housekeeping out of the way, lets move on.

    Our next task will be to update our mainwidget header to include the new onButtonReleased slot method... While we're there, we'll add a QProcess object, which we'll use to fire off a terminal command in the new method.

    Here's the updated mainwidget.h:
    Code:
    #ifndef MAINWIDGET_H
    #define MAINWIDGET_H
    
    #include <QWidget>
    #include <QProcess>
    
    class QPushButton;
    class QTextBrowser;
    
    // This is the declaration of our MainWidget class
    // The definition/implementation is in mainwidget.cpp
    class MainWidget : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit MainWidget(QWidget *parent = 0); //Constructor
        ~MainWidget(); // Destructor
    
    private slots:
        void onButtonReleased(); // Handler for button presses
    
    private:
       QPushButton* button_;
       QTextBrowser* textBrowser_;
       QProcess process_;
    };
    
    #endif // MAINWIDGET_H
    
    NOTE: the "private slots" section is a QT specific section in the class declaration. This is where we declare QT slot methods that our class will use.
    The "private" part of the "private slots" designation means that our slot function is a private member of the class - in other words it can only be used internally by our class. External classes/functions will not be able to access it.

    Also, I decided that our class will use a QProcess object on the stack rather than dynamically on the heap. So because we aren't using a pointer, we need to include QProcess so the compiler will know how much space our class will need for its process_ member variable.

    Now we'll fully define the new function in our .cpp file.
    MainWidget.cpp:
    Code:
    #include <QtWidgets>
    #include "mainwidget.h"
    
    // Constructor for main window
    MainWidget::MainWidget(QWidget *parent) :
        QWidget(parent)
    {
       button_ = new QPushButton(tr("Push Me!"));
       textBrowser_ = new QTextBrowser();
    
       QGridLayout *mainLayout = new QGridLayout;
       mainLayout->addWidget(button_,0,0);
       mainLayout->addWidget(textBrowser_,1,0);
       setLayout(mainLayout);
       setWindowTitle(tr("Connecting buttons to processes.."));
    
       // Connect the Buttons "released" signal to MainWidget's onButtonReleased method.
       connect(button_, SIGNAL(released()), this, SLOT(onButtonReleased()));
    }
    
    // Destructor
    MainWidget::~MainWidget()
    {
       delete button_;
       delete textBrowser_;
    }
    
    // Handler for button click
    void MainWidget::onButtonReleased()
    {
        // clear the text in the textBrowser
        textBrowser_->clear();
        textBrowser_->append(tr("Running command:"));
    
        // Set up our process to write to stdout and run our command
        process_.setCurrentWriteChannel(QProcess::StandardOutput); // Set the write channel
        process_.start("ls -alh $HOME/Desktop"); // Start the program
    }
    
    OK, so what's new here? We've updated the constructor to connect our buttons "release" signal to MainWidgets onButtonReleased() slot method. We also added the onButtonReleased method, which sets up a QProcess and starts it running a terminal command - in this example we're setting it to run "ls -alh $HOME/desktop".

    In the call to process_.setCurrentWriteChannel, we are setting stdout to be the processes default output stream. The call to process_.start starts the command running.

    If we build and run the program now, we'll be able to press the button and it will run the command, but we have no way of knowing what the output of the command is, or whether it actually ran!

    So our final task will be to find a way to connect the output from our process to our QTextBrowser object (textBrowser_).
    Once we've done that, our application will be complete.

    We've already seen signals and slots in action when we connected our button to a handler function. Now we're going to do exactly the same thing again. But this time, we are going to connect a signal from QProcess to a method that will get the output from the process and append it to our text-browser.

    So first up we'll need to add a declaration for new private slot function in mainwidget.h:
    Code:
    #ifndef MAINWIDGET_H
    #define MAINWIDGET_H
    
    #include <QWidget>
    #include <QProcess>
    
    class QPushButton;
    class QTextBrowser;
    
    // This is the declaration of our MainWidget class
    // The definition/implementation is in mainwidget.cpp
    class MainWidget : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit MainWidget(QWidget *parent = 0); //Constructor
        ~MainWidget(); // Destructor
    
    private slots:
        void onButtonReleased(); // Handler for button presses
        void onCaptureProcessOutput(); // Handler for Process output
    
    private:
       QPushButton* button_;
       QTextBrowser* textBrowser_;
       QProcess process_;   // This is the process the button will fire off
    };
    
    #endif // MAINWIDGET_H
    
    The next part is to connect the Process to the new handler function and to fully define the new handler. So we'll be using signals and slots to connect the QProcess to the new handler function/method, but we'll be listening for a differrent signal. As mentioned previously, different QT classes emit different signals.
    Again, the QT documentation lists each classes signals and any built-in slot functions they might have.
    The QProcess signal we will be listening for will be the readyReadStandardOutput() signal.
    When we receive the readyReadStandardOutput() signal, our new onCaptureProcessOutput() method will be called and in turn, it will append whatever text it receives to the text in our text-browser.

    Here's the final code for mainwidget.cpp - we'll discuss what happens in the new onCaptureProcessOutput method afterwards:
    Code:
    #include <QtWidgets>
    #include "mainwidget.h"
    
    // Constructor for main window
    MainWidget::MainWidget(QWidget *parent) :
        QWidget(parent)
    {
       button_ = new QPushButton(tr("Push Me!"));
       textBrowser_ = new QTextBrowser();
    
       QGridLayout *mainLayout = new QGridLayout;
       mainLayout->addWidget(button_,0,0);
       mainLayout->addWidget(textBrowser_,1,0);
       setLayout(mainLayout);
       setWindowTitle(tr("Connecting buttons to processes.."));
    
       connect(button_, SIGNAL(released()), this, SLOT(onButtonReleased()));
       connect(&process_, SIGNAL(readyReadStandardOutput()), this, SLOT(onCaptureProcessOutput()));
    }
    
    // Destructor
    MainWidget::~MainWidget()
    {
        delete button_;
       delete textBrowser_;
    }
    
    // Handler for button click
    void MainWidget::onButtonReleased()
    {
        // clear the text in the textBrowser and start the process
        textBrowser_->clear();
    
        // Set up our process to write to stdout and run our command
        process_.setCurrentWriteChannel(QProcess::StandardOutput); // Set the write channel
       process_.start("ls -alh $HOME/desktop"); // Start a terminal command
    }
    
    
    // This is called whenever the QProcess::readyReadStandardOutput() signal is received
    void MainWidget::onCaptureProcessOutput()
    {
       // Determine whether the object that sent the signal was a pointer to a process
       QProcess* process = qobject_cast<QProcess*>(sender());
       // If so, append the output to the textbrowser
       if (process)
           textBrowser_->append(process->readAllStandardOutput());
    }
    
    At first glance, what is happening inside the onCaptureProcessOutput method probably looks like some kind of black magic. But it is actually quite simple.

    What it is doing is calling the sender() function to find out which class sent us the readyReadStandardOutput() signal. Sender returns a pointer to the QT class that sent us the signal.
    So we need to check what type of object it was that sent us the signal.
    To do this, we use qobject_cast to try and cast the pointer to a QProcess pointer.

    If the qobject_cast succeeds and we manage to get a valid QProcess pointer, we know that the object that sent us the signal was a QProcess. And if it fails - we'll get a NULL pointer and we'll know it was some other widget/class that sent it, so we ignore it.

    If we successfully managed to cast the sender pointer to a QProcess, we then get all of the standard output from the process and append it into our text-browser.

    And that is the functionality complete for our little example application.

    The final step is to re-build the application and check it all works.
    If you remember - earlier on, we used "make distclean" to clear out all of the intermediate files. But it also removed our makefile.
    So the next step is to re-generate our makefile using qmake:
    Code:
    qmake NeosQt.pro
    
    Then it's just a case of running make, waiting for the executable to build and link and then test the program.
    All being well, the application will look exactly as it did before, but this time - when you click the button, the ls command will be invoked and you should see some text appear in the text box.


    ROUND-UP:
    I think this tutorial meets all of @blackneos940 's requirements.
    We have hand-coded a QT GUI from scratch using one of QT's layout manager classes.
    We have used signals and slots to start a process using a button.
    We also used signals and slots to connect the output of the process to a text-browser.
    And we used nothing more than a text editor and a terminal.

    I haven't had much time to work on this tutorial - some of it was a bit rushed - so it may get some subsequent updates/edits in coming days/weeks/months/years! XD

    Also, the code used in this tutorial isn't completely bulletproof. There are a number of things that could be done slightly differently to make the application a bit more robust, but I was just trying to keep things as simple as possible for the sake of illustrating how to create a simple Widget based application in Qt without using QTCreator.

    If anybody has any questions or comments, please leave them below.
     
    #1 JasKinasis, Jul 2, 2018
    Last edited: Jul 2, 2018
    blackneos940, CptCharis and atanere like this.
  2. blackneos940

    blackneos940 Active Member

    Joined:
    May 16, 2017
    Messages:
    125
    Likes Received:
    76
    WHOA!!..... :O We need a LOVE button on this Site!!..... :D Thank you SO MUCH, Jas..... :3 I'll read this, and read it well..... :3 Again, THANK YOU..... :3
     
    atanere likes this.

Share This Page