C++ GUI Programming for MS Windows

The previous post, Win32 or UWP? looked at some of the advantages and disadvantages of developing C++ applications for either of those two frameworks. This post provides a high-level look at a number of toolkits for GUI programming in C++ for those frameworks. I have no intention of covering all toolkits, only the ones I have used, or contemplated using for more than a few minutes. See Wikipedia for a larger list of GUI toolkits.

MS Windows-Only APIs and Toolkits

Microsoft has provided a number of toolkits and APIs for developing C++ applications on Win32 and UWP. The oldest ones are for Win32 development and the newest ones are for UWP. Because Win32 has been around a much longer time, there are more toolkits for developing Win32 applications than for developing UWP applications. Let’s look at a few.

Win32

Any mentions of Win32 in this post also refer to Win64, the 64-bit equivalent of Win32.

Windows API

The Windows API (sometimes referred to as Win32 API) is a C-based library that has been around since the days of Windows 1.0 (originally Win16). This was the first toolkit used to build Windows applications and still remains somewhat popular today, especially for C applications. However, being a C interface, it tends to be long-winded; for example, the first Hello World application built using Win16 required only 150 lines of code.

I would recommend looking at one of the C++ toolkits instead.

MFC

MFC, the Microsoft Foundation Classes, was released in 1992 as a very thin wrapper around the Windows API. MFC is still available in various versions of Visual Studio, though it was previously not included in Visual Studio Express versions.

While some people still develop applications using MFC, and of course there is a large number of legacy MFC applications, there are now better choices available for developing new applications.

WTL

WTL, the Windows Template Library, was developed originally for use internally by Microsoft, and was later released as an unsupported add-on to Visual Studio. WTL provides a light-weight alternative to MFC.

I have not used WTL so I can’t comment on it, other than it is now available as a download from Sourceforge.

Others

A number of other Win32-specific toolkits have come and gone. As an alternative to any of the toolkits mentioned above, you may wish to consider one of the cross-platform toolkits which are listed in a section below.

UWP

Universal Windows Platform is Microsoft’s new framework for building Windows programs. Unlike Win32, which is limited to running on x64/x64 processors, UWP applications can also be built to run on ARM processors. This opens UWP applications up to running on desktops, laptops, tablets, XBox systems, HoloLens systems, Windows phones, and any other hardware that runs Windows 10.

UWP uses the Windows RunTime architecture (WinRT). WinRT provides a set of APIs that expose all of the functionality of Windows 10 to developers. See the Wikipedia entry for more information. .Net and the Common Language Runtime (CLR) are a subplatform of the Windows Runtime.

C++/CX

C++/CX is a set of extensions to Visual C++ for building UWP applications. This greatly reduces the amount of plumbing code required to interface to WinRT, but at the expense of unfamiliar code syntax. All functionality exposed by WinRT can be programmed using C++/CX. C++/CX supports using XAML to define a program’s user interface.

WRL

The Windows Runtime C++ Library (WRL) provides a low-level interface to the Windows Runtime. There is more boiler-plate code than in C++/CX, but at least it is standard, though not modern C++. For example, there are no modern types and no move semantics.

C++/WinRT

C++/WinRT is a standard C++ language projection implemented entirely as a set of header files. It does not use language extensions like C++/CX does, and avoids the complexity, verbosity, and tediousness of WRL.

One disadvantage is that XAML is not currently supported; XAML will be provided in a later version.

Cross Platform Toolkits

All of the toolkits mentioned below have been under active development for 20 or more years. They were started before C++98; to support backwards compatibility, they all suffer from a number of limitations. For example:

  • No namespaces;
  • Use raw pointers rather than smart pointers; and
  • Pass parameters as raw pointers rather than references.

There are many other limitations, but you get the idea.

wxWidgets

wxWidgets is the only toolkit that uses native libraries to create and display windows and widgets. On Windows, wxWidgets uses Win32, on OSX it uses Cocoa, and on Linux and other Unix-like systems it uses gtk+. Attempts were made to produce ports for both Android and iOS, but they never got past the pre-alpha stage.

gtkmm

gtkmm is a C++ wrapper around gtk+. gtk+ is the C API that is used on the Gnome desktop for Linux and Unix-like operating systems. It has been ported to Windows and OSX, and generally works well but with a few limitations on the fringes. For example, see my comments about printing and printer properties in Adventures in Cross-Platform GUI Programming and Printing.

Qt

Qt is available for the largest number of operating systems: Win32 and WinRT, OSX, Linux and many Unix-like OSes, Android, iOS, and embedded operating systems. Qt is the API used for the KDE desktop which runs on Linux and many Unix-like OSes. It is the oldest and best supported of the cross-platform toolkits. It is under constant development.

fltk

The Fast Light Toolkit is the lightest of the toolkits mentioned here. It provides only GUI functionality and does not provide helper classes like the toolkits mentioned above. The look and feel of its widgets is somewhat reminiscent of Motif from the 1990’s.

fltk is available for Win32, OSX, and Linux.

Others

Other toolkits are either lesser known or created for specific purposes, such as toolkits for gaming and toolkits that use and are designed for use with OpenGL, and in some cases, Vulkan.

What Have I Used?

All of my programming in the last 3 years or so has been on MS Windows systems, with most programs using wxWidgets for the GUI. In the past, I programmed using gtkmm and the Win32 API. More than 20 years ago I used MFC. I have also programmed GUIs in other languages, but they are not the topic of this post.

Summary

This post has provided a list and overview of a number of toolkits that can be used to program GUIs on computers running MS Windows. Hopefully I have provided sufficient information for you to limit the amount of investigation you need to do to select a GUI toolkit that is appropriate for your new applications.

Additional Information

Additional information on each of the platforms and toolkits may be obtained from the following links:

 

 

 

Win32 or UWP?

Microsoft provides two models or frameworks for programming Windows 10 applications: Win32 and UWP (Universal Windows Platform). This post will look at some of the advantages and disadvantages of each framework.

Win32 API

Advantages

  • The most widely used framework. After all, it has been around in some form or other since the release of Windows 1.0 in 1985. If you have written Windows programs in C or C++, you have probably programmed at least one of them using the C Windows API or MFC.
  • More applications have been written for Win32 than any other framework.
  • There are a number of alternative toolkits for building Win32 applications that are cross-platform. Therefore, if you use one of these toolkits to build your application, you may be able to port it to other operating systems. See Adventures in Cross-Platform GUI Programming and Printing for some potential problem areas.

Disadvantages

  • Limited to Intel x86 and x64 architectures, so desktop and laptop computers only.
  • The Win32 toolkits, including the cross-platform ones, were all started long before modern C++ (C++11 and later). They have kept their APIs to maintain backwards compatibility to previous versions of the toolkits, so if you use one of them, you will be doing a lot of C++98 programming.
  • With the rise in popularity of the Internet, Win32 applications have become open to many security threats that can affect the entire computer system.
  • Will not run on Windows 10 S. This may be a disadvantage if Windows 10 S systems become popular.
  • Microsoft is trying to kill off Win32, though this will take many years to do.

UWP

UWP is the “modern” Windows API. It is implemented as a set of COM APIs. You do not (necessarily) reference these COM APIs directly, but rather through language specific projections.

Advantages

  • Applications can be programmed to run on desktops, laptops, tablets, XBox systems and HoloLens systems. You can include Windows phones as well, although they seem to be dying out.
  • UWP applications are sandboxed, and therefore do not suffer from many of the security problems that Win32 apps do.
  • Applications are Windows Store ready.
  • Apps will run on Windows 10 S systems, which can load only UWP applications and only from Windows Store.
  • This is the future of application development, at least as Microsoft sees it at the moment.

Disadvantages

  • This framework is relatively new, so you probably have a new toolkit to learn.
  • Because this framework is relatively new, there are very few C++ toolkits available for developing GUI applications using C++.
    • The most widely-used toolkit for C++ GUI programming of UWP applications (C++/CX) uses proprietary extensions to the C++ language (yuck!!!!). You are limited to using Visual C++ to build your applications.
    • The Windows Runtime C++ Template Library (WRL) provides lower-level access than C++/CX to the UWP API, but at least you can code using standard C++ rather than rely on compiler extensions.
    • There is a C++ header library (C++/WinRT) originally developed by Kenny Kerr to provide a C++ callable interface for UWP GUI programming. Kenny now works for Microsoft on the team that is continuing development of C++/WinRT. It only provides some of the functionality available in C++/CX, but work is continuing on providing additional functionality. The header library is available on GitHub; it is not included in the Windows 10 SDK or in Visual Studio.
    • Alternatively, you could do the GUI programming in .Net languages and the rest of the programming in C++.
  • There is currently no way, short of a complete rewrite of the application, to port a C++ UWP application to other operating systems.

What Now?

This post has provided a look at developing applications in C++ for Windows 10. Specifically, it has documented a number of advantages and disadvantages of selecting either the Win32 or the UWP framework.

A few C++ GUI toolkits were mentioned in the various sections, above. The next post will look more closely at these and a number of other more popular C++ GUI toolkits for developing applications for Win32 and UWP.

Modern Ways of Handling Status and Error Conditions

The post Old School Ways of Handling Status and Error Conditions looked at ways of handling statuses and error conditions in functions and subroutines. Each of these methods worked with pre-C++11 compilers and still work with post-C++11 compilers.

This post will investigate returning statuses and error conditions using functionality that was added in C++11, C++14, and C++17.

std::pair and std::tuple

std::pair is a templated struct that was available priory to C++11, but additional functionality was added in C++11 and C++14, so it is discussed here. Also, std::tuple was added in C++11. A tuple may have any number of elements including 0. std::pair is basically a two-value std::tuple with an additional way of accessing the two values.

Generating a Pair

std::pair<int, vector<float>> foo()
{
    int status {OK};
    vector<float> values {1.0f, 3.14f};
    // do something
    return std::pair<int, vector<float>>(status, values);
}

Alternatively, the return statement in foo could be:

    return std::make_pair(status, values);

make_pair is the preferred method of creating a pair because the value types do not have to be specified; in the constructor, they do.

Even:

    return {status, values};

will work as long as the return type for foo is specified. That is, this final way of generating the pair will not work if foo was declared as:

auto foo();

instead of:

std::pair<int, vector<int>> foo();

 

Generating a Tuple

All of the methods shown above work for generating a std::tuple if you replace pair with tuple. In addition to those, there is one other:

    return std::tie(status, values);

Accessing Values in Pairs and Tuples

There are a number of ways of accessing the values in pairs and tuples. Each subsection below assumes the function foo created in Generating a Pair or Generating a Tuple, above.

first, second

std::pair comes with two member objects, first and second, to retrieve the values in the pair. This is how to use them:

std::pair<int, vector<float>> retValues = foo();
int status = retValues.first;
vector<float> values = retValues.second;
float value1 = retValues.second[1];    // 3.14

There are two weaknesses with first and second:

  1. They only exist for std::pair and not std::tuple; and,
  2. It is difficult at first glance to know what first and second represent.

std::get

Here is how to use std::get:

std::pair<int, vector<float>> retValues = foo();
// retrieve items by index
int status = std::get<0>(retValues);
vector<float> values = std::get<1>(retValues);
float value1 = std::get<1>(retValues)[1];    // 3.14

// retrieve items by type
int newStatus = std::get<int>(retValues);
vector<float> newValues = std::get<vector<float>>(retValues);
float newValue1 = std::get<vector<float>>(retValues)[1];    // 3.14

std::get works with both std::pair and std::tuple. Code that retrieves values by type will only compile if all types in the pair or tuple are different.

As with first and second for std::pair, it is difficult at first glance to know what the various gets represent.

You may want to read Arne Mertz’s post entitled Smelly std::pair and std::tuple where he covers some of the same problem areas.

std::tie

Here is how to use std::tie to retrieve values from pairs or tuples:

int status;
vector<float> values;
std::tie(status, values) = foo();

The types specified for the variables in std::tie must match the types returned by foo or there must be implicit conversions from the types returned by foo to the types of the variables in tie.

Structured Bindings

Structured bindings are new to C++17. Here is how to use structured bindings:

auto [status, values] = foo();

Structured bindings are available in clang 4, and gcc 7. Structured bindings will be recognized as of Visual Studio 2017 Update 3.

std::optional

In Old School Ways of Handling Status and Error Conditions, I discussed special output value. In that case, there is a single return value that indicates that an operation was not successful. Assume you have a configuration file that you read to retrieve the value for some key. Let’s say there are three possible “states” for the key-value pair, each of which has a different meaning:

  1. The key is in the file and has a value set for it.
  2. The key is in the file, but no value is set.
  3. The key is not in the file.

The special output value technique will not handle this because there are two special values. As an example, let’s say that the value is a string. If you return a non-empty string, that is the value that is set. If you return an empty string, does that mean that the key is in the file but has no value set, or that the key is not in the file? Note that I state that a key is in the file but has no value has a different meaning than the key is not in the file.

This is where std::optional is useful. std::optional may or may not contain a value. Using it with the example above, I could return a string containing the value in the file, either the set value or empty. If the optional value is not set, that is an indication that the key is not in the file.

Here is an even simpler example::

std::optional<std::string> optionalFunc(int key)
{
    switch (key) {
        case 1:
            return "key = 1"s;
        case 2:
            return ""s;
        default:
            return {};
    }
}

and how to use it:

 auto opt1 = optionalFunc(1);
 if (opt1) {
     std::cout << "Value returned for key=1: " << opt1.value() << '\n';
 }
 auto opt2 = optionalFunc(2);
 if (opt2.has_value()) {
     std::cout << "Value returned for key=2: " << opt2.value() << '\n';
 }
 auto opt3 = optionalFunc(3);
 if (!opt3) {
     std::cout << "No value returned for key=3\n";
 }

The output is:

Value returned for key=1: key = 1
Value returned for key=2:
No value returned for key=3

std::variant

std::variant is a type-safe union. Here is a simple function that returns a std::variant, either a float value or an int error code:

std::variant<int, float> variantFunc(int key)
{
    switch (key) {
        case 1:
            return 10.0f;
        case 2:
            return 0.0f;
        default:
            return 16; // some error code
    }
}

and here is some code that calls variantFunc:

 for (int key : {1, 2, 3}) {
     auto var = variantFunc(key);
     try {
         float value = std::get<float>(var); // or get<1>(var);
         std::cout << "value for key=" << key << ": " << value << '\n';
    }
    catch (std::bad_variant_access&) {
        int errCode = std::get<int>(var); // or get<0>(var);
        std::cout << "Error code for key=" << key << ": " << errCode << '\n';
    }
 }

Here is the output from this code:

value for key=1: 10
value for key=2: 0
Error code for key=3: 16

Note that if you try to retrieve the wrong type from the variant, a std::bad_variant_access exception is thrown.

Instead of std::get, you could use std::get_if which returns a pointer to the value, or nullptr, so the code above could be written:

for (int key : {1, 2, 3}) {
     auto var = variantFunc(key);
     auto floatPtr = std::get_if<float>(var);
    if(floatPtr != nullptr) {
         std::cout << "value for key=" << key << ": " << *floatPtr << '\n';
    }
    else {
        auto intPtr = std::get_if<int>(var);
        if(intPtr != nullptr) {
            std::cout << "Error code for key=" << key << ": " << *intPtr << '\n';
        }
    }
}

std::any

Similar to std::variant is std::any. In the simple function below, either a float value or an int error code is returned:

std::any anyFunc(int key)
{
    switch (key) {
        case 1:
        case 2:
            return 10.1f * key;
        default:
            return 4; // some error code
    }
}

and here is some code that calls this function and uses the returned value:

 for (int key : {1, 2, 3}) {
     auto var = anyFunc(key);
     try {
         float value = std::any_cast<float>(var);
         std::cout << "value for key=" << key << ": " << value << '\n';
     }
     catch (std::bad_any_cast&) {
         int errCode = std::any_cast<int>(var);
         std::cout << "Error code for key=" << key << ": " << errCode << '\n';
     }
 }

Here is the output from running this code:

value for key=1: 10.1
value for key=2: 20.2
Error code for key=3: 4

Note that std::any_cast will throw a std::bad_any_cast exception if you attempt to cast the std::any value to a type that it does not contain.

Alternatively, you can check the type of the value stored in your std::any variable by calling std::any::type. For example:

for (int key : {1, 2, 3}) {
     auto var = anyFunc(key);
     if(var.type() == typeid(float)) {
         float value = std::any_cast<float>(var);
         std::cout << "value for key=" << key << ": " << value << '\n';
     }
     else if(var.type() == typeid(int)) {
         int errCode = std::any_cast<int>(var);
         std::cout << "Error code for key=" << key << ": " << errCode << '\n';
     }
 }

Summary

Both this post and the Old School Ways of Handling Status and Error Conditions post looked at ways of returning statuses or error codes from a subroutine or function that might also return values. The old school ways still work, but modern C++ (C++11 and later) has provided many more ways of returning either a status or error code, or a valid value.

So, what would I use? For cases where there is a special output value that indicates an operation has failed, then I would use std::optional with no value indicating the failure. For functions where the returned value could be either a valid value or one of a number of different statuses or error codes, then I would use std::pair or std::tuple along with structured bindings (or std::tie until structured bindings are available in Visual C++) to retrieve the values. These object types are the simplest to use; there is no question about what the returned values are, and are the simplest to retrieve the values from. For error conditions where program termination or lengthy recovery procedures are needed, then I would throw an exception to indicate the error condition.

Old School Ways of Handling Status and Error Conditions

C++ provides a number of different ways for returning status and error information from functions. C++17 added a number of new ones or modifications on previously defined ones. This post will look at how you may have done this 15 or more years ago. A second post will continue to this look into handling statuses and error conditions, including the techniques added in C++11, C++14, and C++17.

Let’s say we have a function that takes an input parameter and returns both an output value and a status or error indication. Here are a number of ways of doing this.

Status or Error Code as Function Parameter

Look at the following code:

void foo(int inputParam, int* outputParam, int* status)
{ /* do something */ }

int main()
{
    int in = 1;
    int out;
    int status;
    foo(in, &out, &status);
    if(status == OK) {
        /* use out */
    }
}

In the call to foo, out is valid only if status contains the value OK.

This code is obviously old school. Today, you would more likely use references rather than pointers for the parameters:

void foo(int inputParam, int& outputParam, int& status);

Status or Error Code As Return Value

In the code above, status is returned as a parameter in a subroutine call. Alternatively, and more frequently, the status or error code is set as the return value to a function. For example, foo might be redeclared as:

int foo(int inputParam, int& outputParam);

and used as follows:

int main()
{
    int in = 1;
    int out;
    int status = foo(in, out);
    if (status == OK) {
        /* do something with out value */
    }
}

Special Output Value

If there is a single value that indicates that an operation has failed, but this failure is not necessarily a fatal error condition, a special value may be returned in the output parameter. For example:

int foo(int inputValue)
{
    /* do something, returning valid value, or BadStatus if error condition */
}

int main()
{
    int out = foo(1);
    if ( out != BadStatus ) {
        /* do something with out */
    }
}

This occurs quite frequently, especially in functions that originated in C. A value of NULL might be used as a HANDLE value returned from the Windows libraries to indicate an error occurred. For example, in:

FILE* fp;
fp = fopen("myfile.txt", "r");

fp is NULL if the file myfile.txt does not exist or otherwise cannot be opened for reading.

Similar indicators are used in the Standard Templates Library. For example:

int val {4};
std::vector values {1, 2, 3};
auto iter = std::find(values.begin(), values.end(), val);
if(iter != values.end()) {
    /* iter contains iterator to value found in values vector */
}

In the code above, std::find returns values.end() because 4 is not a value in the values vector.

Return Status and Value in Struct

If a function returns more than one value, a struct containing these multiple values may be defined to contain each of the values. This may be extended to include a status or error code. For example:

struct Ret {
    int status;
    string returnedString;
public:
    Ret() : status(OK) {}
}

Ret foo(int input)
{
    Ret ret{};
    /* do something. Set returnedString if no error.
     * Otherwise, set status to indicate error
     */
    return ret;
}

int main()
{
    Ret returnValue = foo(6);
    if (returnValue.status == OK) {
        // do something with returnValue.returnedString
    }
}

Exceptions

Exceptions may be used to specify that something unexpected has occurred in the execution of the code and that normal processing should not continue. A thrown exception may indicate that the program cannot continue and should therefore be terminated, or that some special processing should be done before continuing.

As an example, let’s say you are creating a C++ library that encapsulates the Vulkan API. You have a Swapchain class that has a method called AcquireNextImage. Within this method, you call vkAcquireNextImageKHR that returns VK_SUCCESS if the call succeeded, VK_ERROR_OUT_OF_DATE_KHR if the drawing surface has changed in some way such as being resized, or any of a number of error statuses. For any of the error statuses, you probably want to inform the program’s user and then terminate the program. For VK_ERROR_OUT_OF_DATE_KHR, you will want to recreate the swap chain to match the new surface properties and then call AcquireNextImage again.

Throwing an exception for any of the error conditions is typical handling, but do you return a status code for the VK_ERROR_OUT_OF_DATE_KHR condition, or do you throw an exception indicating this status? Either would be acceptable, but since this condition happens rarely, I would throw an exception and handle it in the calling code. I think this is slightly cleaner than checking the return status from Swapchain::AcquireNextImage for multiple status values and processing each differently.

That is a rather long-winded description showing how to handle various status and error conditions using exceptions. Here is a simpler case:

void foo()
{
    /* do something */
    throw runtime_error("Error in foo");  // some error occurred
}

int main()
{
    try {
        foo();
    }
    catch (runtime_error& re) {
        /* handle the exception, then either continue after, or terminate */
    }
    // continue processing if non-fatal error occurred
    ...
}

 Review

This post looked into ways that you might return statuses and error codes from a subroutine or function. It also presented exceptions as an alternative. Each of these methods was available prior to C++11 and will certainly show up in legacy code.

The next post will review some of the additional methods that might be used as a result of functionality that was added in C++11, C++14, and C++17.