Getting Started with wxWidgets in Erlang
(DRAFT - 2011-04-10)
Michael Turner
Tama Translation Systems
Introduction
Erlang is a programming language used mostly on the server side, although at least one popular graphics-intensive application, Wings 3D, has been written in it. The best-supported graphics API for Erlang is wxWidgets, a large, mature, stable multi-platform API for GUI programming.
This tutorial shows you how to use basic wxWidgets in Erlang.
Table of Contents
- The How and Why of This Tutorial
- Setting Up
- Frames: A Taste
- The Status Bar
- The Menu Bar
- Menus
- Events
- Dialog boxes
The How and Why of This Tutorial
This tutorial's approach is highly interactive: you make each new wxWidget call work at the Erlang shell command line, first. You see it work, immediately. Only afterward do you add it to a program. For example:
To put some text in the status bar, type this at the command line:
Now you should see this:
|
This approach is better for learning GUI programming. Here's why:
- Short and clear
Most tutorials rely on GUI code samples for entire programs. Problem: even the simplest ones can take more than a page. And they aren't just long, they're wide and dense: GUI calls can take a lot of parameters. It's easier to focus on one line (or two) at a time, one result at a time. The Erlang shell helps you do that. You'll also see more easily see what you did wrong, when you make mistakes.
- Quick feedback
When you look at GUI code, you often wonder: what's really happening, and when? At the command line, you know. Or you can at least find out. Immediately. Before anything else has a chance to happen. (And if what happened is something wrong, you'll know right then as well.)
- Less scary (or less boring)
Look at any "Hello, World!" program for any GUI, in any language. If you already know GUI APIs, you'll yawn: so many inconsequential differences to learn. If you don't, you'll shudder: so much to learn. Either way, if you go a few lines at a time, it's easier to take it all in.
Keep Those Fingers Moving
Do you want to get the most out of this tutorial? Do you have a computer-readable copy of it, on your computer running Erlang? Then here's the most important thing: don't copy-paste lines from the tutorial into the shell. Would you try to learn to speak a language by recording other people's spoken sentences, then playing them back? Where the tutorial says to type lines in, type them in.
It can be hard to resist the temptation to copy-paste. Print lessons out and work through the hardcopy, or get them in book form (e-book or dead-tree.) This helps in another way: It leaves more screen space. At times, you'll need it. You'll have an Erlang shell window, some GUI elements displayed (one would hope so!), and often a program editor. Sometimes you'll want to see them all at the same time.
Objection? Overruled!
Of course there are drawbacks to this approach. Nothing's perfect. Some likely objections:
-
"Typing in everything means making a lot of mistakes."
Of course it does. That's good. Mistakes help you learn.
-
"It's a lot of typing."
Maybe you're worried about tendinitis flare-ups? Consider: stress is also a tendinitis risk factor. Learning new programming techniques is stressful even when it's fun. This is a lower-stress way to learn. Also: you'll be reminded to take a break after each (short) lesson. Finally, where it's reasonable to do so, the tutorial will make it easy to 'recycle' lines already typed, and will use short variable names.
-
"It'll take a lot of time!"
Face it: you're learning a GUI API. Those were never small. These days they're huge. Getting started will take days. Delving deeper into the basics will take weeks. If you're writing a serious GUI for an Erlang program, you could be at it for months. And what if your program is successful and becomes popular? Now we're talking about years.
Ask yourself: how would you be learning otherwise? By staring at turgid sample code? Flipping through cryptic manual pages? Reading a wordy book that too often describes what it could be showing you? You probably already know how well that works.
Learning this way is more fun. It will seem, at least, like it's not taking very long. It might also mean you learn faster even in real time. (For one thing, you'll probably procrastinate less.)
There will be times with wxWidgets when it will be no fun at all. So why not get started the fun way?
Why not get started right now?
Setting Up
Unlike systems designed specifically for rapid prototyping of GUIs,
like Tcl/Tk,
there's some preparation required to start doing
graphics from the Erlang shell command line. This section leads you through the steps.
In this part, it's assumed you that you already
To get started, prepare your Erlang shell session to make understanding easier.
Find the wx header files
Seeing is believing - but only if you can see things clearly.
Not all wx
calls will give you clear graphical results;
In the Erlang shell session, you'll often see only return values.
These values can be cryptic, especially if you're not used to Erlang.
Since Erlang is a functional language,
every wx
call returns a value.
Many of these values will be
tuples
.
Many of those tuples will be the contents of
records
.
It's easier to understand wx
return values shown in record format.
The Erlang shell needs to be told the wx
record definitions, however.
So: where are those record definitions?
Find where wx
lives on your system. Type this:
My_wx_dir = code:lib_dir(wx).
On Windows XP, My_wx_dir
might look something like this:
"c:/PROGRA~1/ERL57~1.2/lib/wx-0.98.2"
Read wx module definitions
Read thewx
record definitions from that directory:
rr (My_wx_dir ++ "/include/wx.hrl").
rr (My_wx_dir ++ "/src/wxe.hrl").
Both rr
calls should return a list of modules.
If you get the rr
calls wrong, you'll get empty lists back.
Open your text editor, if you haven't already. Paste the three commands above to a scratch file. Later, when you start a new lesson, or resume one, or when you need to restart because of a crash, you can copy-paste them into the shell, Try this now, to make sure it works. Quit Erlang, restart it, then copy-paste those commands into the shell.
Unfortunately, we can't use
Erlang macro definitions
in the shell.
That's another good reason to locate the wx.hrl
:
to look up wx
macro symbol values as needed for shell commands.
Initialize wx
To start wx
up, type this:
Wx=wx:new().
Did it work?
Maybe there's a message saying
SMP (symmetric multiprocessing)
isn't enabled,
or "SMP emulator required."
In some Windows Erlang releases, SMP isn't enabled by default.
Exit the Erlang shell and restart it with a "-smp" flag,
e.g., with a DOS command like this:
"C:\Program Files\erl5.8.1.1\bin\werl.exe" -smp
then try again.
When wx:new()
works, you'll get a record back, something like this:
#wx_ref{ref = 0,type = wx,state = []}
These are only one per process, and the Erlang shell is a process.
Ignore the value for now.
Frames: A Taste
In wxWidgets
, a window is just a kind of
frame.
Let's make a simple one, then add to it.
Make a frame
To make a frame, type this:
F=wxFrame:new(Wx, -1, "Hello, World!").
(The second parameter can be an integer ID. The -1 tells wxWidgets to assign the frame some ID by default.)
Now, where is this new frame? We got a record back, something like this:
#wx_ref{ref = 35,type = wxFrame,state = []}
But nothing changed elsewhere on the screen.
Why not?
Because you must ask to see the frame.
Type this:
wxFrame:show(F).
This should return true
.
Now you should be able to see something like this:
Recovering from shell exceptions
To get rid of the frame, you could just click the close control. But first try this nonsense call instead:
nothing:doing().
That made the frame disappear, along with the exception error message.
This is because wxWidgets runs the graphics in its own process,
and exceptions not caught in the shell kill any process started there.
This kind of total GUI loss could happen a lot, just from keying errors. You'd have to start over whenever you made an exception-raising error. You don't want to type everything in from the beginning, when it happens. So, before you do anything else in a shell session, give the command
catch_exception (true).
You'll probably take breaks from this tutorial anyway.
Set things up so that you can take up where you left off.
Paste what you've typed above into the scratch file.
The commands so far should now look like this:
catch_exception (true).
My_wx_dir = code:lib_dir(wx).
rr (My_wx_dir ++ "/include/wx.hrl").
rr (My_wx_dir ++ "/src/wxe.hrl").
Wx=wx:new().
F=wxFrame:new(Wx, -1, "Hello, World!").
wxFrame:show(F).
Frames: creative destruction
With the frame displayed again, type this:
wxFrame:destroy(F).
It should return ok
and the frame should disappear.
Now, just for fun, make some new frames.
Scroll back through the commands, and, by editing them:
- Make a
wxFrame F1
with title "Hey!" - Show
F1
. - Make a
wxFrame F2
with title "Boo!" - Show
F2
. - Then
wxFrame:destroy
them both.
destroy
call.
The Status Bar
A status bar is handy not just for program features, but also debugging. At this point, you might want to quit and restart. Paste your scratchpad contents into the shell.
Make a Status Bar
Type this in:
wxFrame:createStatusBar(F).
Your frame should now look like this:
Set the Status Bar Text
Put some text in the status bar:
wxFrame:setStatusText(F, "Quiet here.").
You should now see this:
Take a moment to add these commands to your scratchpad startup. Try setting the status to some other string values, then back to "Quiet here."
You might also try this:
SB = wxFrame:getStatusBar(F).
wxStatusBar:pushStatusText(SB, "A LITTLE LOUDER NOW.").
wxStatusBar:popStatusText(SB).
You should end up with the same status bar text you started with.
The Menu Bar
Frames in wxWidgets conventionally have one menu bar. In this way, the menu bar is like the status bar. However, menu bars are usually made up of other things: They need to be composed of menus.In wxWidgets, complex things are usually composed from the bottom up. The wxWidgets API makes no assumptions about what goes inside what. For the more complex things, it takes more steps to make anything you can see.
Let's get a menu bar visible as soon as possible. Consider copying each command below to your scratchpad/restart file, as you go.
Make a Menu Bar
Type this:
MenuBar = wxMenuBar:new().
You'll get something like this back:
#wx_ref{ref = 37,type = wxMenuBar,state = []}
But you see no menu bar in the window frame.
Nothing changed in the window.
What could be the problem?
Well, did we say how this menu bar relates to any frame?
You know where you want it to go.
But wx
can't read your mind.
Yes, F
is your only window frame so far.
But wx
doesn't assume you want to put MenuBar
in F
.
Set the Menu Bar for a Frame
Try this: making MenuBar
a part of wxFrame F
.
wxFrame:setMenuBar (F, MenuBar).
Maybe that returns ok
but ... still you see nothing!
Did anything actually happen?
There's a way to ask about a frame's menu bar.
Type this:
wxFrame:getMenuBar (F).
What do you get back in the shell?
It's the same wx_ref
returned from the setMenuBar
call.
Something's happening.
The problem is: there are no menus to show anyway.
Let's do something about that.
Menus
It'll take a few steps to add a menu item and display it.
Make a Menu
Most application GUIs have a File menu. Type this:
FileMn = wxMenu:new().
You should get something like this:
#wx_ref{ref = 37,type = wxMenu,state = []}
Again, wxWidgets doesn't know where you want this menu to go.
You have to say which menu bar FileMn
will attach to.
Add (append
) your FileMn
to the wxMenuBar
of wxFrame F
.
Use the conventional File menu label.
wxMenuBar:append (MenuBar, FileMn, "&File").
(The ampersand before 'F' means that entering Alt-F will be like clicking on the menu.)
You should see a change -- something like this:
But what good is a menu without menu items? Click on the File menu (or press Alt F). Nothing happens.
[Well, the status bar clears. What's with that?]
Add a Menu Item
Every File menu has a Quit menu item. Let's make one. Type this:
Quit = wxMenuItem:new ([{id,400},{text, "&Quit"}]).
(The menu item ID -- 400 here -- must be specified; without one, wxWidgets won't generate a default, and you won't get a menu item. CHECK THIS.)
Again, you won't see any graphical change, just a return value in the shell. A menu is like a menu bar: you need to add something to make it visible.
wxMenu:append (FileMn, Quit).
Click on the File menu (or type Alt F). You should see this:
Reviewing all the commands for the simple menu you've made, you should see:
MenuBar = wxMenuBar:new().
wxFrame:setMenuBar (F, MenuBar).
FileMn = wxMenu:new().
wxMenuBar:append (MenuBar, FileMn,"&File").
Quit = wxMenuItem:new ([{id,400},{text, "&Quit"}]).
wxMenu:append (FileMn, Quit).
What else can we add? Every decent app has a Help menu. And every Help menu should have an About item.
Make a Help Menu
Recycle your new
and append
commands above.
HelpMn = wxMenu:new().
wxMenuBar:append (MenuBar, HelpMn, "&Help").
You should see something like this:
Now add the About menu item. Recycle the commands you used to add the Quit item to the File Menu. You'll put together commands like this (note the different id value):
About = wxMenuItem:new ([{id,500},{text,"About"}]).
wxMenu:append (HelpMn, About).
Click on the Help menu. You should see something like this:
Take a moment to copy-paste the above commands to your scratchpad.
Events
Up to now, we've done nothing with events.
You might think there are no events to see.
If you entered "flush()
" now, you'd see nothing.
But in fact, every mouse click is triggering events inside wxWidgets.
They are being handled in some default manner by wx
.
Often, the wx
default is to ignore them.
Let's catch an event, to see what it looks like.
Using connect
to see events
Type this:
wxFrame:connect (F, close_window).
Click on the close control for the frame, then type this:
flush().
You should get back something like this:
Shell got {wx,-202,{wx_ref,35,wxFrame,[]},[],{wxClose,close_window}}
Note that clicking the close control doesn't close the window frame anymore. You've overridden the default action.
Click on that control a few times, then minimize and maximize the window.
Try calling flush
again.
You see your close_window
events, but no minimize/maximize events.
Also note how the Erlang shell writes out received events:
It doesn't use the wx
record definitions read in earlier.
You're just seeing raw tuples.
This makes it harder to know what these wx
events are about.
There's a way to see the events using the record definitions.
The following fun returns an event sent to the shell (or empty
, if none).
Type it in:
Ev = fun() -> receive E->E after 0 -> empty end end.
Click on the close control again, then call the event-reader.
Ev().
You should see something like this:
#wx{id = -202,
obj = #wx_ref{ref = 35,type = wxFrame,state = []},
userData = [],
event = #wxClose{type = close_window}}
Menu selection events
Let's try connecting to more events. Type this:
wxFrame:connect (F, command_menu_selected).
Now try some menu selections.
Select the File menu's Quit, then the Help menu's About.
Then enter Ev()
to look at the events generated.
Except for id
, the resulting events are the same.
#wx{id = 400,
obj = #wx_ref{ref = 35,type = wxFrame,state = []},
userData = [],
event = #wxCommand{type = command_menu_selected,
cmdString = [],
commandInt = 0,
extraLong = 0}}
Callbacks
It's good to know that events are happening. Sometimes it's helpful to be able to see the details. But most of the time, we just want (non-default) things to happen in response to events. So we have to catch events and figure out what to do with them.
Events in wx
are handled in
callback functions.
First, let's make a function to call.
Type this:
Ding = fun (_,_) -> wx_misc:bell() end.
Try it, using the types of parameters expected for these callbacks:
Ding(#wx{},#wx_ref{}).
Does it ring a bell? It should.
Now connect it to your wxFrame F
through the event close_window
wxFrame:connect (F, close_window, [{callback, Ding}]).
Try clicking the close control on the frame again.
It should just beep.
Try calling Ev
.
It should no longer return close_window
events.
Because simply beeping is a non-sensical thing to do for the close control, you might want to disconnect the event now.
wxFrame:disconnect (F, close_window).
Dialogs
An "About" menu item really ought to take us to a modal dialog. But how do you make one? Here's the simplest way.
Making a modal dialog
Type the following line:
D = wxMessageDialog:new (F, "Let's talk.").
This should return with a value like
#wx_ref{ref = 43,type = wxMessageDialog,state = []}
but will show nothing graphically.
Showing a modal dialog
To show the dialog and interact with it, type this:
wxMessageDialog:showModal (D).
Somewhere on your screen, you should now see something like this:

Because the dialog is modal,
the showModal
call won't return with a value in the shell
until you hit the "OK" button.
The return value should be 5100.
If you look at the include file wx.hrl,
you'll see that this is the value for wxID_OK
.