WxSmith tutorial: Hello world
"Hello world" Tutorial
Hi, in this tutorial we will write very basic application that will use wxSmith, called "Hello world". Since this term reflects to the very first step for some coding language, it may also be threated as your first step to learn wxSmith.
Before we start you will have to compile wxWidgets (or download precompiled binaries and header files). It's described very well on Compiling wxWidgets 2.6.2 to develop Code::Blocks (MSW) or Installing Code::Blocks from source on Windows wiki pages. I may show you how to compile some older versions of wxWidgets, but the same instructions should apply for newer versions.
If your local wxWidgets copy is ready, let's make some project :)
Creating wxWidgets project
The easiest and the preffered way is to create wxWidgest project using project wizard. It stars like creating any other project using New project menu:
Then to create wxWidgets application, let's choose wxWidgets icon:
After you click "Go" button, Code::Blocks will bring up the wizard window. It consist of several pages where you can enter some options for your project. I'll describe few pages better since they may cause problems for new users.
At the very beginning the wizard asks you which version of wxWidgets you want to use:
The best option for now is to use wxWidgets 2.8.x since it is stable and has few features not present in wxWidgets 2.6.x series. Also wxSmith aims to provide best support for lastest stable version of wxWidgets.
On another wizard page you choose what GUI designer will be used and what type of application should be generated:
If you want your application to work with wxSmith you should check the wxSmith designer. You can also use powerful wxFormBuilder, but this program is not covered in tutorials presented here. Another option let you choose if your application should be created using wxFrame or wxDialog. Both options will lead to valid wxWidgets application but wxFrame should be preffered since it's more natural for wxWidgets and wxDialog-based applications are not as flexible as wxFrame-based ones.
On another page you may choose the location of your wxWidgets library:
You can choose real directory here, but that may lead to problems when your project should also compile on other computers. Another choice is to use Global Variable system availabie in Code::Blocks. When you use it, the location of wxWidgets library is read from such variable. This mean that wxWidgets directory is not hard-coded inside your project, but is stored inside global configuration of Code::Blocks. When your project will be opened on other computer, it will use it's configuration.
The easiest option to use global variable is to leave $(#wx) what means to use global variable named wx. This name is reserved for wxWidgets and should be choosen in most cases.
When you use global variable for the first time, it will ask for wxWidgets directory. The only thing you need to do here is to enter valid location in base field. This is required only for the first time since Code::Blocks will automatically use this setting for other projects you create.
On the next step, wizard will ask you for configuration of wxWidgets library. Since wxWidgets may be compiled differently, each compilation type should also be threated differently inside your project. This step may be little bit confusing for new users since it require some knowledge about build process of wxWidgets. But if you used wiki pages that I've put at the beginning of this tutorial to compile wxWidgets, set your configuration to this one:
Note that I've also checked the option to use precompiled headers (PCH) since it can really speed-up the compilation of wxWidgets applications on some compilers (especially GCC).
When you follow another wizard pages, you may occasionally see message box saying that debug configuration can not be found. This is because the wxWidgets library was build using release mode (the BUILD=release option used for make). If you want real debug library, you can recompile your wxWidgets by using BUILD=debug. Project may also be tuned to work with release version on both debug and release build targets (the way to do this is presented at the end of this tutorial).
After project is created and initialized properly, Code::Blocks will look like this:
Now when you select Release build target and press Compile, it should create wxWidgets application. But before you run it, you will probably need to copy wxWidgets dlls (from lib\gcc_dll inside directory where your copy of wxWidgets resides) to project's directory. When your application is ready and runs without problems, we can jump to the next point.
Let's add some fireworks
Now it's time to fill the empty frame with some "Hello World" message. This text could be added directly into frame, just like in most commercial GUI designers. But we will learn to use sizers. But what's that?
If You have been working with java you will remember something called Layout managers. Implementation in wxWidgets differs a little bit but it does almost the same.
Ok, but let's put some explanation here: Usually when adding items into windows You must specify the item's position and size. wxWidgets tries to automate this process and it uses sizers for that. They are automatically positioning and sizing window items and they can also set the size of window to fit all items inside. Sizers have one big advantage. When You write cross-platform applications, you cannot assume that fonts, buttons, etc. are sized the same on different operating systems. This can even occur on the same platform when using window themes. When You use sizers, You don't have to worry about that. All sizing is done automatically. And one more thing - sizers can even reposition and resize window items when the main window changes size.
Now since we have some background knowledge, it's time to add something.
How can I add something ?
When you look at the wxSmith editor, you'll notice that the area of window is surrounded by eight black boxes.
These black boxes are surrounding a currently selected item. In our case it's a whole window. Adding new item is simply done by clicking on one of the buttons in the palette at the bottom of the editor.
As you can see here, when you keep your mouse cursor over a component, it will show it's name. That may help to find items when you're not familliar with their icons.
Let's add new wxPanel component to out window - this is fourth icon from the left. After you add it, you will notice that the background colour of window changed to light gray. Now our window will look much more like other windowses. Note that currently the selection changed into added panel but since it's size is equal to frame, it's hard to find out what's really seleced. To make sure that wxPanel is selected, click somewhere on light gray area of our window.
If you want to exactly see the selection or select items that overlap, you can use resource browser. To show it, click on resources tab located in same place where project browser is located:
Resource browser consists of two parts. First is resource tree which shows structure of resource and second one is property browser which is common in GUI designers. Inside resource tree you can find currently selected item. In our case it is wxPanel class we adedd before. Selected item also shows the name of this item: Panel1. This name is basically the variable which can be used to access this component in your code.
Selected item works like cursor in text editors - new items are added relatively to the selected one. In case of the main frame, new item was added into it. But the selected cursor may be added in another way. Basically we can add new item into three different positions relative to current selection:
- Before the selected widget
- After the selected widget
- Into the selected widget.
This can be changed by clicking one of three "placement" buttons on the right side of editor:
The upper most means "add into", the one in the middle means "add before", the one at the bottom means "add after". Current mode is spoted with red check mark on the button. Sometimes some selection modes are disabled. This means that they are not valid for currently selected item. For example you can not add any item into wxButton component since wxWidgets declares it as the one that can not have child items. And when the main component (the window) is selected, you won't be able to add anything after it or before it.
Now let's assume that we've made a mistake and want to delete item that we have just added. In fact we have a small mistake and will have to delete wxPanel. To this, click on the red X button:
When you click this button, it deletes current selection, so if the panel didn't disappear, make sure it's selected and click the button again. Now we should return to the state at the beginning.
The mistake made here is that we've added wxPanel directly into main frame. But as I've said before, we will be using sizers to manage window's content. To use sizers, we have to first add sizer into main frame and then add wxPanel into it.
Sizers are available in the Layout tab on the palette. Switch to it and select wxBoxSizer:
This sizer tries to position items in one line, either hroizontal or vertical. Horizontal is by default and we will use this.
After adding sizer, two things have changed. First is that our window has now red border. This mean that there's sizer attached to it. When you look into resource browser you will also see that sizer has been added (you may need to click on the editor area to refresh the selection on resource browser). Second thing is that size of window has shrinked to small box. That's correct since sizer is also responsible for resizing items.
Now let's add wxPanel. Make sure that we will add it into sizer, not main frame. After we do this, you will see something like this:
Here we can see our panel surrounded by drag boxes. There's also some dark gray border around it. Each item added into sizer can be surrounded by such border. It's usefull when you want to have spacing between items. But in our case we should get rid of it to get more proper window. To do so, we will have to change property inside property browser. Search the border size and change it to 0:
Now when we have our panel adjusted, we can finally add "Hello world" text. Since we will also use sizers to manage items added into wxPanel, we have to repeat addition of wxBoxSizer into wxPanel. After sizer is on it's place, switch back into Standard tab on the palette and add wxStaticText:
Because this text looks so lonely, let's add button next to it by clicking on wxButton.
Ok, now we have this content:
======= THE FOLLOWING TEXT IS OUTDATED ================================
- Select the newly created text (now "Hello World !!")
- Chose the "After" Insertion type
- Click on the wxButton icon
Ok, now we have this:
But I would prefer to put this button below the text. How to do this ? As I mentioned before, sizers are automatic positioning items and, in our case, the sizer decided to put the widgets in a horizontal row, one next to another. If we want them to be in a vertical colume, we need to change some properties for the sizer:
- Select wxFlexGridSizer in the resources tree representing the structure of our window (to the left, in the Resources panel)
- Expand "Cols x Rows" property and change the X value to 1.
What have we done ? We instructed the sizer to create only one column of widgets. Alternatively, we could have set the number of rows (Y) to 2. The effect would have been the same. Setting the values to 0 means that the sizer has to find its values automatically. Ok, let's see what we've done.
Using the created panel
In this tutorial we will put our panel over the main frame. To do this:
- Open the main.cpp file
- Add #include "helloworldpnl.h" to the beginning of the file
- At the end of MyFrame::MyFrame add the statement new HelloWorldPnl(this);
- Compile and Run
You should see something like this:
But I want it to look better
Let's see. I would like my panel to be more interesting, I want it to change when we resize our window. And I want a bigger font, and let's say, a blue font colour. The first can be done using the previously created wxFlexGridSizer. It needs just a few modifications:
- Select wxFlexGridSizer from the resource browser
- Change growable cols property to 0 (zero)
- Change growable rows property to 0 (zero)
The changed properties keep information about columns and rows which should expand when the window changes size. Values are integers (zero-based indexes) separated by commas.
The second is also easy:
- Select wxStaticText from the resource browser (or click on it in the editor)
- Find font property and expand it
- Change Use Font to True
- Click on font property below - a button with "..." will appear, click on it
- Select new font, I used "Times New Roman", Bold, size: 20
- Find foreground property and expand it
- Change Use Colour to true
- In Colour below select Custom, colour dialog will appear
- Select colour You like :)
Ok, let's compile & run. Now when we resize the window, our panel changes dynamically, the font is bigger and the colour has changed :)
How to make buttons respond
Now we'll add an action to our button. Let's say it will close the program. First, let's change the button label to "Close". This should be pretty easy and I hope You won't have any problems with it. Ok, now let's add some action.
wxWidgets works like many other GUI systems - through events. An event is a small peace of information saying that something has happened - for example, the user clicked on a button. But we want to do something when such and event happens. To connect events with actions we have to create an event handler. wxSmith can do this automatically:
- Select the button
- Switch to the Events tab (if you see properties, it's right there)
- In the line named EVT_BUTTON choose "-- Add new handler --"
- Change the event handler name to something you like (I will leave it as it is ;)) and click OK
As You can see, a new empty function has been created
void HelloWorldPnl::OnButton1Click(wxCommandEvent& event) { }
Here we can write some code that will be executed when you press the button. So, let's add a Close() command
void HelloWorldPnl::OnButton1Click(wxCommandEvent& event) { Close(); }
and see what happens. I click on Close button and... nothing happens. Why ? Because we closed the panel, not the whole window. How do I know that ? Our event handler is a member of the HelloWorldPnl class. Everything inside the event handler pertains to the panel, not the outer window. When we called Close() we called this function in the wxPanel class. But how can we close the main frame? Change the code to:
void HelloWorldPnl::OnButton1Click(wxCommandEvent& event) { GetParent()->Close(); }
The GetParent() function will return a pointer to the parent window - frame. Now Close() is being called on the panels parent, the window. But be careful. Our example was easy and we just assumed that main frame will be the panel's parent. Usually we can not be so sure.
Our Hello World application is ready to go :) I hope It wasn't boring. Not bored ? Then read next chapter ;)
Pointers
Some technical info
Ok, I'll try to explain how wxSmith affects our code, how to work with it, why you shouldn't be worried about losing your code.
Where wxSmith generates its code ?
wxSmith is not as intelligent as it may seem ;) When we say it generates code, it simply replaces whole code pieces without wondering if code is placed in the right position. But the code works. How is it done ?!
When You look into files generated inside wxSmith, you may find some special comments like :
//(*Headers(HelloWorldPnl) //*)
These comments are used by wxSmith to find the place where new code should be applied. Each //(* comment starts automatically generated block of code and //*) closes it. Everything between these comments is regenerated, even if you add something there
The only exception is a block started with the //(*Handlers comment. wxSmith can only add to this block of code. If you want to write your event handler manually, you can put its declaration here.
Code outside the //(* - //*) comments won't be touched.
Loading XRC resources
When using an XRC file, do not forget to initialize the wxXMLResouce Handlers & XRC File. For example in your App::OnInit:
// Loading XRC resource file (not in a zip file). wxXmlResource::Get()->InitAllHandlers(); wxXmlResource::Get()->Load("<your XRC File name>.xrc");