Creating wxWidgets Programs with Visual Studio 2017 – Part 2

Part 1 of this post described how to set up your development environment for building wxWidgets-based Windows applications, and how to build a bare-bones application. This post continues the development of this application by modifying it to create a HelloWorld program. Continue reading

Advertisements

Creating wxWidgets Programs with Visual Studio 2017 – Part 1

This post is a replacement for the post of the same name for Visual Studio 2015 that I published approximately two years ago. A number of changes made during various updates to Visual Studio 2017 has invalidated some of the instructions in that earlier post.

I have already discussed building the wxWidgets libraries using Visual Studio 2017. I will assume that you have done that.

Prior to the First Program

Note: This section discusses setting up an environment variable that points to the wxWidgets directory. An alternative, and probably better, method to that given in this section is provided in the User-Wide Settings in Visual Studio 2015 post. If you choose to use the procedure in that post, only set up the User Macro for WXWIN. Do not follow the instructions for adding folders to the Include Directories and the Library Directories. If you do make the changes to the Include Directories and the Library Directories, you may not be able to build the wxWidgets libraries in the future. Continue reading

Using Visual Studio 2017 to Build wxWidgets

About two years ago, I wrote a post outlining how to build wxWidgets using Visual Studio 2015. Since then, wxWidgets has been updated, and Visual Studio is now VS 2017. The procedure I outlined also worked for the first few versions of Visual Studio 2017, but later updates made changes that invalidated some of the steps that I outlined. I also recommended downloading wxWidgets directly from the GitHub code page, a practice that wxWidgets does not recommend.

This post provides a new procedure for building wxWidgets with Visual Studio 2017. It works for wxWidgets 3.0.3, 3.1.0 and hopefully later versions, with Visual Studio 2017 version 15.5.3. Hopefully it will be valid for future updates of Visual Studio as well.

Continue reading

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:

 

 

 

Vulkan with wxWidgets

Alexander Overvoorde wrote a good tutorial for programming Vulkan; this is available at vulkan-tutorial.com. In the tutorial, he used GLFW as the windowing system. This is fine if your application uses one main window with no adornments such as menus and status bars, and no additional windows.

I have modified the Hello Triangle example to use wxWidgets. This corresponds to the code up to the end of the Drawing a triangle section of the tutorial. The source code of my wxWidgets version is available on GitHub.

Here is a description of the changes:

  1. HelloTriangleApplication code is split among VulkanTutorialApp, VulkanWindow, and VulkanCanvas. The vast majority of the code is placed in VulkanCanvas. Specifically, the code in HelloTriangleApplication::run is split into two parts:
    • The line:
      mainloop()

      is handled automatically by VulkanTutorialApp, and to some extent by VulkanWindow.

    • The functionality in initWindow and initVulkan are performed in the VulkanCanvas constructor.
  2. The HelloTriangleApplication::drawFrame code is placed in VulkanCanvas::OnPaint.
  3. The HelloTriangleApplication::onWindowResized code is placed in VulkanCanvas::OnResize. wxWindow::RefreshRect must be called to ensure that the canvas is redrawn every time the window is resized. Calling wxWindow::Refresh or wxWindow::Updateonly causes a redraw to occur when the window grows in size, not when shrinking is size.
  4. Two changes were made regarding exceptions:
    • In the tutorial code, runtime_error exceptions are thrown whenever Vulkan errors are encountered. These are replaced with a new exception type called VulkanException which provides information about the actual Vulkan error that was encountered.
    • The tutorial code simply outputs text messages to std::cout. Because the wxVulkanTutorial code is Windows-based, there is no console to output the messages to, so they are output to a wxMessageBox. This was discussed previously in the post C++ Exceptions and wxWidgets.
  5. The tutorial code contains a templated class called VDeleter. This class automatically handles the destruction of various Vulkan handles. I have not included this class in wxVulkanTutorial. Instead, I destroy the handles in the VulkanCanvas destructor.

Have fun with it.

C++ Exceptions and wxWidgets

I have been working on a project to port a set of Vulkan tutorials from using GLFW as the windowing system to using wxWidgets. The code in the original tutorials throws std::runtime exceptions whenever there are unrecoverable Vulkan errors. These exceptions are caught in the program’s main function where the exception’s message is sent to cerr and the program is immediately terminated by returning a value from main. wxWidgets doesn’t work that way!

Here is the code from the first tutorial (original with GLFW, not wxWidgets):

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

There are two problems with this approach when using wxWidgets:

  1. wxWidgets uses the platform’s native API so on Windows systems, that means that wxWidgets applications are Win32 GUI applications. Win32 GUI applications do not by default have a console, so, again by default, outputting to cerr does nothing.
  2. wxWidgets hides the main function and much of the program and GUI initialization. Instead, wxWidgets provides wxApp::OnInit, a method that should be overridden to perform initialization including creating a top-level main window. wxApp::OnExit may be overridden to delete any wxWidgets objects that are not tied to top-level windows. Following the return from OnInit, wxWidgets the calls wxApp::OnRun to enter the main loop. OnRun is not normally overridden.

So, what do we do? It is possible to create a console from a Win32 application and to tie cerr to output to the console. This is not typical of Win32 applications, so it will not be discussed further. A better approach for unrecoverable Vulkan and other exceptions is to display a message box containing exception information and then to terminate the application. Additionally, information could be output to a logging file if desired.

wxWidgets provides an overview document for using exceptions with wxWidgets. The document recommends handling exceptions in the following locations in order of preference:

  1. In a try/catch block inside an event handler.
  2. In wxApp::OnExceptionInMainLoop().
  3. In wxApp::OnUnhandledException().

In my port of the tutorials, a large amount of Vulkan initialization occurs in OnInit, so it will be necessary to handle exceptions there, inside a try/catch block. The rest of the Vulkan code is called from the wxEVT_PAINT handler (OnPaint in my case).

Here is my code from OnInit:

bool VulkanTutorialApp::OnInit()
{
    VulkanWindow* mainFrame;
    try {
        mainFrame = new VulkanWindow(nullptr, wxID_ANY, L"VulkanApp");
    } 
    catch (const std::runtime_error& err) {
        // process runtime_error exception
        // terminate application
        ...
    }
    mainFrame->Show(true);
    return true;
}

Even though wxWidgets has not yet called wxApp::OnRun, a message box can still be displayed and processed before the application terminates.

And here is a bit of code from the Vulkan canvas’s OnPaint method:

void VulkanCanvas::OnPaint(wxPaintEvent& event)
{
    try {
        // paint the image on the canvas using Vulkan
        ...
    } catch(const std::runtime_error& err) {
        // process runtime_error exception
        /// then terminate program
        ...
    }

How do we display a message box and possibly log information to a file? wxWidgets provides a set of wxLog* functions. See the logging overview for information on these functions. The default action for wxLogError is to display a message box with the error information. wxLogFatalError calls wxLogError then calls abort. Since we want to terminate the application after displaying the error information, wxLogFatalError would at first glance appear to be a good choice. Alternatively, we could call wxMessageBox to display the error information and then terminate the application ourselves.

Using wxLogFatalError in OnInit is overkill because simply returning false will result in wxWidgets cleaning up and terminating the application. Specifically, the destructors for all created objects are called and the memory associated with the objects is released.

Here is my code for OnInit:

bool wxVulkanTutorialApp::OnInit()
{
    VulkanWindow* mainFrame;
    try {
        mainFrame = new VulkanWindow(nullptr, wxID_ANY, L"VulkanApp");
    } 
    catch (std::runtime_error& err) {
        wxMessageBox(err.what(), "Vulkan Error");
        return false;
    }
    mainFrame->Show(true);
    return true;
}

wxLogFatalError may still be useful in the OnPaint method, but keep in mind that it calls abort. abort immediately terminates the program without necessarily calling all object destructors and freeing any associated memory. On program termination, Microsoft Windows will free any memory associated with the application, and also release any OS resources (file handles, window handles, etc.) associated with the program. But the operating system does not know about any Vulkan resources and handles that the application may have obtained and therefore, these are not released; they will stay around until the computer is restarted.

So wxLogFatalError does not appear to be a good choice in OnPaint either. This leaves us with wxLogError or wxMessageBox calls and some method to terminate the application. As mentioned above, messages sent to wxLogError display the messages in a message box by default; however, the messages may be redirected elsewhere, such as to a log file. Because I want to always display the messages on screen, I have chosen to use wxMessageBox. wxApp::ExitMainLoop() tells wxWidgets to exit the main loop, which eventually terminates the application cleanly. Let’s try that; the catch block in OnPaint will look like this:

    catch(const std::runtime_error& err) {
        wxMessageBox(err.what(), "Vulkan Error");
        wxTheApp->ExitMainLoop()
    }

To test this, I modified OnPaint to throw a runtime_error. Hmm, no message box is displayed, the program does not terminate, and the program becomes unresponsive. A bit of time using the debugger shows that as soon as the wxMessageBox call is reached, wxWidgets returns control to the message loop which again calls OnPaint, which calls wxMessageBox, which returns control to the message loop which again calls OnPaint… Well, you get the idea.

We must move the calls to wxMessageBox and wxApp::ExitMainLoop outside of the OnPaint method. Prior to wxWidgets 2.9.x, this could be done by creating a new event type, and defining a handler to process that event. In OnPaint, we would queue the event to be processed. wxWidgets 3.x provides the CallAfter method to simplify this. Let’s create a new method called OnPaintException to display the error information and terminate the program:

void VulkanCanvas::OnPaintException(const std::string& msg)
{
    wxMessageBox(msg, "Vulkan Error");
    wxTheApp->ExitMainLoop();
}

Now the catch block in OnPaint would look like this:

    catch (const std::exception& err) {
        CallAfter(&VulkanCanvas::OnPaintException, err.what());
    }

CallAfter adds an event to be processed once OnPaint returns. Only then is OnPaintException called. The call to ExitMainLoop causes the application to terminate normally.

Rerunning our test with OnPaint generating a runtime_error exception, the message box is displayed and the program then terminates normally.

HelloTriangle

The first program that you typically write in any programming language is HelloWorld, where you simply print out the words: “Hello World!” to the console. When you graduate to using a windowing toolkit, the Hello World program displays a window with “Hello World!” in it. The post Creating wxWidgets Programs with Visual Studio 2015 – Part 2 gives an example using wxWidgets. In OpenGL, about the simplest program you can write is HelloTriangle which displays a white triangle on a black background; there is no text. I will develop HelloTriangle in this post.

HelloTriangle will be developed using Visual Studio 2015, wxWidgets, and OpenGL. Actually, Visual Studio is simply the development environment used by me. You could in fact use any environment you want provided it supports wxWidgets and GLEW (for OpenGL).

To begin, we need to create a bare-bones wxWidgets application. This is discussed in Creating wxWidgets Programs with Visual Studio 2015 – Part 1. Call the project HelloTriangle. Change MyProjectApp to HelloTriangleApp. Once you have that program working, you should install the NuGet package called nupengl.core, to include GLEW. Alternatively, you could simply install GLEW on your system and use that. Installing nupengl.core is discussed in Visual Studio, wxWidgets, and OpenGL. Once this is done, we have all of the parts needed to begin coding HelloTriangle.

This post will follow along with the tutorials at open.gl. In the tutorials on that website, the Context creation page shows how to set up an OpenGL program using SFML, SDL, and GLFW. We will use wxWidgets instead. The Drawing polygons page shows how to draw a triangle and then colour the triangle using vertex shaders and fragment shaders. We will not be using shaders; this post simply draws a white triangle on a black background. The code shown in this post can then be modified to use shaders.

If you have followed the steps above, you have a very simple program that simply displays an empty window. This will now be modified. We will add two new classes, called TriangleWindow and TriangleCanvas.

TriangleCanvas subclasses wxGLCanvas. wxGLCanvas is the wxWidgets class that displays OpenGL graphics. In the TriangleCanvas class, we will initialize OpenGL, create the wxGLContext required for OpenGL, and set up the graphics for drawing in the constructor. The paint event handler will paint the background and the triangle. This code is in the OnPaint method. Finally, the destructor does some clean up.

Here is the header file for TriangleCanvas:

#pragma once
#include <memory>
#include "wx/glcanvas.h"

class TriangleCanvas : public wxGLCanvas
{
public:
    TriangleCanvas(wxWindow* parent, wxWindowID id = wxID_ANY,
        const int* attribList = 0, const wxPoint& pos = wxDefaultPosition,
        const wxSize& size = wxDefaultSize, long style = 0L,
        const wxString& name = L"GLCanvas",
        const wxPalette& palette = wxNullPalette);

    virtual ~TriangleCanvas();
    TriangleCanvas(const TriangleCanvas& tc) = delete;
    TriangleCanvas(TriangleCanvas&& tc) = delete;
    TriangleCanvas& operator=(const TriangleCanvas& tc) = delete;
    TriangleCanvas& operator=(TriangleCanvas&& tc) = delete;

private:
    void InitializeGLEW();
    void SetupGraphics();
    void OnPaint(wxPaintEvent& event);

    std::unique_ptr<wxGLContext> m_context;
    GLuint m_vbo; // vertex buffer pointer
    GLuint m_vao; // vertex array pointer
};

The private methods InitializeGLEW and SetupGraphics are helper methods that are called in the constructor. m_context holds the OpenGL context and m_vbo and m_vao are properties used by OpenGL.

Here is the constructor:

TriangleCanvas::TriangleCanvas(wxWindow* parent, wxWindowID id, 
        const int* attribList, const wxPoint& pos, const wxSize& size,
        long style, const wxString& name, const wxPalette& palette)
	: wxGLCanvas(parent, id, attribList, pos, size, style, name, palette),
	m_vbo(0), m_vao(0)
{
	m_context = std::make_unique<wxGLContext>(this);
	Bind(wxEVT_PAINT, &TriangleCanvas::OnPaint, this);
	
	SetCurrent(*m_context);
	InitializeGLEW();
	SetupGraphics();
}

This code is straight-forward. It creates an OpenGL context for the TriangleCanvas, and then binds the OnPaint method to the paint event. Following this, the OpenGL context is set and the two helper methods are called to complete the initialization. The helper methods will only succeed if the context has been set.

InitializeGLEW simply initializes the GLEW so that the OpenGL methods can be accessed. The method is shown here:

void TriangleCanvas::InitializeGLEW()
{
	glewExperimental = true;
	GLenum err = glewInit();
	if (err != GLEW_OK) {
		const GLubyte* msg = glewGetErrorString(err);
		throw std::exception(reinterpret_cast<const char*>(msg));
	}
}

glewInit() must be called to obtain information on the supported extensions from the graphics driver. Experimental and pre-release drivers may not report every available extension through the standard call to glewInit; in that case, GLEW will report that the extension is not supported. By setting glewExperimental to true (or GL_TRUE) before calling glewInit, all of the extensions are exposed.

Following the call to glewInit, the code checks the status returned from the glewInit call, and throws an exception if the call failed. I have not included any code in this program to catch the exception, so the program will simply terminate. Catching the exception is left as an exercise for the reader.

This is the code for the SetupGraphics method:

void TriangleCanvas::SetupGraphics()
{
	// define vertices
	float points[] = {
		0.0f, 0.5f,
		0.5f, -0.5f,
		-0.5f, -0.5f
	};
	// upload vertex data
	glGenBuffers(1, &m_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
	// setup vertex array objects
	glGenVertexArrays(1, &m_vao);
	glBindVertexArray(m_vao);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
}

I will not provide an explanation of this code; it is available on the Drawing polygons page on open.gl.

The glGenBuffers and glGenVertexArrays calls in the code above allocate buffers on the graphics card. These buffers must be released before the program terminates or a memory leak will result. The code in the TriangleCanvas destructor performs this task:

TriangleCanvas::~TriangleCanvas()
{
	SetCurrent(*m_context);
	glDeleteVertexArrays(1, &m_vao);
	glDeleteBuffers(1, &m_vbo);
}

All of the drawing is performed in the OnPaint method:

void TriangleCanvas::OnPaint(wxPaintEvent& event)
{
	SetCurrent(*m_context);

	// set background to black
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// draw the graphics
	glDrawArrays(GL_TRIANGLES, 0, 3);
	// and display
	glFlush();
	SwapBuffers();
}

This code is quite simple. The context is set. the glClearColor and glClear calls set a black background, and glDrawArrays draws the triangle. glFlush is called to force the drawing to the backup buffer, and SwapBuffers swaps the buffers to display the buffer that OnPaint just drew on.

That completes the TriangleCanvas.cpp file except for the preprocessor directives at the top of the file:

#include "wx/wxprec.h"
#include <GL/glew.h>
#include "TriangleCanvas.h"

#pragma comment(lib, "glew32.lib")

The TriangleWindow class is very simple. All it does is create a TriangleCanvas object and attach it to the window. Here is the content of TriangleWindow.h:

#pragma once

class TriangleWindow : public wxFrame
{
public:
	TriangleWindow(wxWindow* parent, const std::wstring& title, const wxPoint& pos = wxDefaultPosition,
		const wxSize& size = wxDefaultSize);
	virtual ~TriangleWindow();
	TriangleWindow(const TriangleWindow& tw) = delete;
	TriangleWindow(TriangleWindow&& tw) = delete;
	TriangleWindow& operator=(const TriangleWindow& tw) = delete;
	TriangleWindow& operator=(TriangleWindow&&) = delete;
};

and here is the content of TriangleWindow.cpp:

#include "wx/wxprec.h"
#include "TriangleCanvas.h"
#include "TriangleWindow.h"

const int triCanvasID = 2000;			// TriangleCanvas widget ID

TriangleWindow::TriangleWindow(wxWindow* parent, const std::wstring& title, const wxPoint& pos,
	const wxSize& size)
	: wxFrame(parent, wxID_ANY, title, pos, size, wxMINIMIZE_BOX | wxCLOSE_BOX | 
		wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN)
{
	TriangleCanvas* canvas = new TriangleCanvas(this, triCanvasID, nullptr, { 0, 0 },
	{ 800, 800 });
	Fit();
	Centre();
}

TriangleWindow::~TriangleWindow()
{
}

Displaying the window with the triangle requires only two changes to HelloTriangleApp.cpp. Add a #include directive for TriangleWindow.h, and replace the first line of HelloTriangleApp::OnInit with

	TriangleWindow* mainFrame = new TriangleWindow(nullptr, L"Hello Triangle!");

Now build and run the application. Here is the resulting window:

HelloTriangle

That’s all there is to it.