Thursday, June 11, 2015

How to get started with wxWidgets these days

Hi! :)

This post aims to be a painless introduction to wxWidgets with C++.

Introduction

When you code in C++ (or other language) - after all these console programs there comes a point when you start to ask yourself: "Alright, I understand whole syntax, but what about GUI programs - buttons, checkboxes, tabs etc.?".
Quick jump to Google and you know what you are looking for - Win32 API. Then, after some tutorials revealing basic concepts of it - like message loop etc. something is happening!
At least for a short period and only for basic programs.

This is exactly my story. I quickly found adding new controls (HWND, CreateWindow) at least cumbersome, breaking at the same time laws of object-oriented coding. Moreover, it was riddiculous to handle size messages. In fact, I was spending more time caring for small details in GUI instead of dealing with true problems.

WinAPI was written in C, before C++98 (which introduced concept of classes).
Let's be honest: You want to code GUI apps under Windows - you should be familiar with it. It has a lot of great features; every function has its description at MSDN.

If you are tired of fighting with WinAPI, you should really consider using one of available GUI toolkits.
 

GUI Toolkits

Luckily for us, there are GUI toolkits. How do they work? They are mostly wrappers. For instance, class "Window" has HWND something with it. A very trivial example, forgive me :) Right now, they are full-featured chunks of code which makes coding GUI a true pleasure.

But let's go straight to the point. There are many of them, like MFC (object-oriented version of Win32), Qt (used by Autodesk and many others), GTK (used by Gimp) and wxWidgets.

I decided to learn wxWidgets for a couple of reasons. First of all, it's free (open source) which is always nice. It's multiplatform (yes, with the same code you can code under Mac).
wxWidgets is widely used (CD Projekt Red's REDkit uses it, for instance).
What's important for learning, there is a rich documentation and samples.

Not convinced yet? :)
See http://wxwidgets.org/about/

Downloading & Building

You shouldn't have problems with downloading wxWidgets. Download the library from here: http://wxwidgets.org/downloads/
I prefer "Windows Installer". At the moment of writing this, the latest version is 3.0.2.

I will use Visual Studio 2013 in this tutorial, but in fact it's very similar for older versions of MSVC (2008-2012).

1. Run Visual Studio 2013.
2. Open proper solution file. It's located at (for instance) C:\wxWidgets-3.0.2\build\msw

 For VS 2013 choose "wx_vc12.sln". For VS 2012 - "wx_vc11.sln" and so on, depending on version of your compiler.

You will see something similar to this:


OK. Think now - do you prefer to link wxWidgets statically (.lib) or dynamically (.dll) to your application? In the first scenario, the final .exe file will be larger, but you will not have to keep it together with a dll files of wxWidgets. In the second one, exe will be smaller, but you will have to store proper DLLs in the same directory.

You should also think about CRT library (DLL or LIB) that wxWidgets will use. It's critical. Both your project and other libraries you use with it have to have the same CRT library. You can change it in "Configuration Settings" ->"C/C++"->"Code Generation"


You also probably want to build both debug/release configurations.
So: If you build LIBs for static linking - you are interested in Debug & Release build configurations.
If you build DLLs for dynamic linking - DLL Debug & DLL Release are for you.
To build one, click right mouse button on "Solution" in Solution Explorer, and click "Build Solution".


It will take some time. Don't forget about the second configuration!
Output files are stored in %WXWIN%\lib\vc_lib
As I said before, I prefer Windows Installer. One of the reasons is creating the WXWIN environment variable. We will take advantage of it later.

Alright, the next thing you should do is building samples. wxWidgets has over 85+ of them, covering different aspects of the library (from minmal code to threads and advanced controls). The best way to learn is to learn by practice - and playing and modifying existing code samples is pretty fun.
It is very similar to building wxWidgets itself. You just have to open proper solution file:


The notification above simply means that we are opening solution file in a newer version of Visual Studio than it was created in. You can backup the original "samples.sln" if you want to.

Migrating projects within solution can take some time. When it's finished, build the solution. Basically you will need only "Release" build. Of course, you can buld "Debug" also, if you really need to.

Tip: Various versions of the library

The library is built, samples are built.
If you have various versions of Visual Studio on your computer, as me, you probably want to avoid conflicts with wrong versions of libraries.
I propose you a simple solution: For each version of Visual C++ compiler create a different directory!

You see, the order matters. At first, we build the library, then samples. At the end we can safely move library files to, for instance, "vc12" directory. Of course, building samples only once is enough :)



Creating a project (and template)

Now the only thing we need to do is to create a basic project to get things running! :)
Moreover, we will create it as a template! It has a lot of advantages, there is no point in rewritting all basic code from scratch every time. NO!


There is much better way to deal with it: After creating a basic project and saving it as a template, this template can be reused (and expanded in future).



Okay, let's go!

1. Run Visual Studio. Click File->New->Project
2. Create a new empty project.
3. add a *.cpp source file, like "program.cpp"
4. At this point you can copy-paste "minimal.cpp" from samples\minimal to the file you just created.
Do not try to compile this right now. We have to set include directories, linking settings and so on.
We will cover here only necessary settings.

5. Open "Project Settings" window.
Set "All configurations" and choose "Use Unicode Character Set" from "Character Set" drop-down menu.

Now we have to set proper settings for either Debug and Release configurations.
Therefore, set "Configuration" to "Debug". Go to "Configration Properties"->"C/C++"->General.

In "Additional Include Directories" insert the following entries:
  • $(WXWIN)\Include
  • $(WXWIN)\lib\vc_lib\mswud
It should look pretty like this:



Alright, now go to the "Preprocessor" tab.
Add the following definitions:
  • WIN32
  • _DEBUG
  • __WXMSW__
  • _WINDOWS
  • NOPCH
(Plus, if you are using DLLs, define also WXUSINGDLL)

It's time for linker settings.
Go to "Linker"->"General".

In field named "Additional Library Directories" enter proper path to *.lib files. In my case this is:

(The screen above comes from VS2012)

Okay, now go to the "Input" tab and in the field "Additional Dependencies" we have to enter neccesary *.lib files to make linking run smoothly.
Here we go:
  • wxmsw30ud_propgrid.lib
  • wxmsw30ud_adv.lib
  • wxmsw30ud_core.lib
  • wxbase30ud_xml.lib
  • wxmsw30ud_html.lib
  • wxmsw30ud_xrc.lib
  • wxbase30ud.lib
  • wxtiffd.lib
  • wxjpegd.lib
  • wxpngd.lib
  • wxzlibd.lib
  • wxregexud.lib
  • wxexpatd.lib
  • winmm.lib
  • comctl32.lib
  • rpcrt4.lib
  • wsock32.lib
  • wininet.lib


6. Okay, now time for *Release* build configuration. This time it will be faster. Remember to set "Release" build configuration!

In "Additional Include Directories" insert the following entries:
  • $(WXWIN)\Include
  • $(WXWIN)\lib\vc_lib\mswu
Preprocessor Definitions:
  • WIN32
  • __WXMSW__
  • NDEBUG
  • _WINDOWS
  • NOPCH
  • (optionally) WXUSINGDLL
"Additional Library Directories" - the same as in Debug.

"Additional Dependencies":
  • wxmsw30u_propgrid.lib
  • wxmsw30u_adv.lib
  • wxmsw30u_core.lib
  • wxmsw30u_html.lib
  • wxbase30u_xml.lib
  • wxmsw30u_xrc.lib
  • wxbase30u.lib
  • wxtiff.lib
  • wxjpeg.lib
  • wxpng.lib
  • wxzlib.lib
  • wxregexu.lib
  • wxexpat.lib
  • winmm.lib
  • comctl32.lib
  • rpcrt4.lib
  • wsock32.lib
  • wininet.lib

OK! This is it! :-)
Make sure that both configurations can be built correctly.
All you need to do now is (after some minor tweaks, if you want) click File -> "Export Template".
Then.. just use it. Of course, you can always modify your project and export it again if you want to change some code or settings.

This Minimal Sample

I think wxWidgets has an excellent documentation - it makes no sense in writing the same over and over. After some practice, you will catch what's going on.

The trick I propose:
You may ask yourself: Where is WinMain?
In fact, it's deeply hidden within IMPLEMENT_APP macro.

Sometimes you want to do something "before" GUI starts.
It's kinda simple in wxWidgets:

IMPLEMENT_APP_NO_MAIN(CMyApp)

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
#if defined (_DEBUG) | defined (DEBUG)
 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

 return wxEntry(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
} 
 
As you can see, all you need to do is use IMPLEMENT_APP_NO_MAIN macro. Then, in WinMain you have to call wxEntry to start wxWidgets.

Summary

OK, this is the end of this post. I'm exhausted :) It's my really first one; therefore, I'm curious: Did you like it? Was it useful? Maybe you have any questions?
In the nearest future I will write about XRC (resources system), wxpgex (my property grid library for wxWidgets - more info soon :-) ) and - of course - I'll show how to write advanced color picker.
Preview:
 

Until the next time!

3 comments:

  1. This is a great introduction on how to set up wxWidgets and it contains the wxEntry description for Windows usage. Thanks a lot, worked great.

    My remaining question is, whether this also can be done for a console application that opens an optional GUI, so that Windows is not needed and it also can run in a non-GUI mode

    ReplyDelete
    Replies
    1. Hey, thanks for kind works. Happy that you enjoyed it! :)

      I think this can be done. Due to the fact that wxEntry is overloaded only for WinMain (for Windows) you cannot use usual int main(int argc, char** argv). Pseudocode:

      int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
      {
      bool bUseGUI = false;
      bool foo = false;
      ...
      if (bUseGUI)
      {
      wxEntry(hInstance, hPrevInstance, lpCmdLine, nShowCmd);

      foo = true;
      }

      AllocConsole();
      freopen("CONOUT$", "w", stdout);

      if (foo)
      {
      std::cout << "GUI was used";
      }
      else
      {
      // GUI was not used
      }

      return 2;
      }


      Delete