wxerlang workups

Coding the wx window Part 1

 
We are done with the layout. Now we will start to code the window to do something. This phase has  four steps:  1) Add listeners to the window 2) Set the return value of the window. 3) Create a loop that receives messages and responds to them. 4) Make changes to the original Erlang code if necessary.
 
Adding connections (listeners)
 
You can program the window to monitor all kinds of user actions (mouse movements, typing, resizing, etc). These actions are called events. In the case of our simple window, there are only two events that we want to monitor:
 
  1) Did the user click a button?
  2) Did the user close the window?
 
The code that sets up the monitoring is short and painless:
 
    wxPanel:connect(Panel, command_button_clicked),
    wxFrame:connect(Frame, close_window),
 
We add this code near the end of make_window/0, just before the return value.
 
The 'connection' concept is fundamental to making your window respond the way you want it to.  Read about 'connection' here  http://www.erlang.org/doc/man/wxEvtHandler.html
 
Return value of the window
 
The return value of make_window/0 must give us a way to access the widgets that we have to read or modify.  This is the State that we carry around in the loop/1 function.  We will need access to the frame (is it closed?) , the wxTextCtrl, T1001, (read the value typed in by the user), and the wxStaticText, ST2001 (which our code modifies as it shows the countdown).  
 
We don't include the buttons in the return value, because the listener which we created by wxPanel:connect(Panel, command_button_clicked) will tell us if a button has been clicked.  When would you want to include the buttons in the return value? One situation would be to enable or disable a button.  In order to be able to send the button the disable instruction from inside the loop/1 function, it would have to be accessible to loop/1.
 
So for this project, the return value of make_window is simply this:
   {Frame, T1001, ST2001}.

By putting the variables inside curly brackets, we create a tuple, which becomes the State parameter for loop/1.  Inside loop/1 the tuple is deconstructed  back into its components.

Note that the widgets are referenced by their variables, not by their ID numbers.
 
Writing the loop
 
Before making the loop do anything useful, we will handle the situation where the user wants to close the window.  Two things must happen: the visible window respond to the click 'close' and Erlang must exit the loop/1 function.   The loop first decomposes State into the components Frame,  T1001, and ST2001.  For closing the window correctly, we need access to Frame.  
 
You will notice that the loop receives messages.  Where do those messages come from?  They come from window events that are being monitored as a result of the 'connections' created during the window-making phase.  If one of those events occurs, a message is sent.  
 
Closing the window can occur in two ways in our program.  In other words, there are two different messages that can be sent, both requesting the window be closed.  One is clicking the built-in (usually red) X button in the upper right corner of the window. The other is clicking our widget, the button that we labeled Exit.  Both of those situations are being monitored by 'connect' statements.   Here's how to code the loop for those specific messages.  Both read the contents of a #wx record.  In the case of the X button, we test for the the event field being a wxClose record.  In the case of clicking the exit button, we test for a) the event is a wxCommand record, in which the type of command is "a button was clicked", and b) the id number associated with that button is equal to wxID_EXIT.  Either way, we do the same thing:  1) destroy the frame, which makes it disappear from the screen, and 2) exit the loop. 
 
loop(State) ->
    {Frame, T1001, ST2001}  = State,
    receive
        #wx{event=#wxClose{}} ->
            wxWindow:destroy(Frame),  
            ok;  
 
        #wx{id = ?wxID_EXIT, event=#wxCommand{type = command_button_clicked} } ->
            wxWindow:destroy(Frame),
            ok;  
        Msg ->
           %everything else
           % ...
           loop(State)
    end.
 
Very important notes on the X button 
 
There are two concepts which sound like they are flip sides of the same coin, but aren't.
 
1. If you don't create a connection for the corner X,    i.e.,  wxFrame:connect(Frame, close_window),  then if the user clicks it, the window will close, but the code will not exit the loop.  The memory that was used creating the frame will not be released, because the frame is not destroyed. 
 
2.  If you do create the connection, (again, using wxFrame:connect(Frame, close_window) ), but don't provide a receive clause in the loop, then the window will stay open.  By creating the connection, you are intercepting the click.  
 

 
A side note: Also click the "Countdown" button. Look at the output that is sent to the shell (from Msg ->) :
 
loop default: Got {wx,101,
                      {wx_ref,70,wxPanel,[]},
                      [],
                      {wxCommand,command_button_clicked,[],0,0}}
 
The key is the contents of the first two elements of the tuple: 1) wx and 2) 101.  'wx' signals that the source is from a widget, and '101' is the ID number that I assigned to the Countdown button.  I will use that information in the next part of the tutorial. 
 
You should be aware that the same 'receive' can be captured using the #wx record format.  Look at the receive statement for the exit button as a model.  In the next version, the Countdown button will get its own 'receive' statement.  I will show an alternate way to capture the same information. 
 

-module(wxcd06).
-compile(export_all).
-include_lib("wx/include/wx.hrl").
 
% The Window should close if the exit button is clicked
%   or if the X in the corner of the window is clicked.
% The Countdown button is not functional yet
 
start() ->
    State = make_window(),
    loop (State).
 
make_window() ->
    Server = wx:new(),
    Frame = wxFrame:new(Server, -1, "Countdown", [{size,{250, 150}}]),
    Panel  = wxPanel:new(Frame),
 
%% create widgets
%% the order entered here does not control appearance
    T1001 = wxTextCtrl:new(Panel, 1001,[{value, "10"}]), %set default value
    ST2001 = wxStaticText:new(Panel, 2001,"Output Area",[]),
    B101  = wxButton:new(Panel, 101, [{label, "&Countdown"}]),
    B102  = wxButton:new(Panel, ?wxID_EXIT, [{label, "E&xit"}]),
 
%%You can create sizers before or after the widgets that will go into them, but
%%the widgets have to exist before they are added to sizer.
    OuterSizer   = wxBoxSizer:new(?wxHORIZONTAL),
    MainSizer   = wxBoxSizer:new(?wxVERTICAL),
    InputSizer  = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, "Enter an integer"}]),
    ButtonSizer = wxBoxSizer:new(?wxHORIZONTAL),
 
%% Note that the widget is added using the VARIABLE, not the ID.
%% The order they are added here controls appearance.
 
    wxSizer:add(InputSizer, T1001, []),
    wxSizer:add(InputSizer, 40, 0, []),
 
    wxSizer:addSpacer(MainSizer, 10),  %spacer
    wxSizer:add(MainSizer, InputSizer,[]),
    wxSizer:addSpacer(MainSizer, 5),  %spacer
 
    wxSizer:add(MainSizer, ST2001, []),
    wxSizer:addSpacer(MainSizer, 10),  %spacer
 
    wxSizer:add(ButtonSizer, B101,  []),
    wxSizer:add(ButtonSizer, B102,  []),
    wxSizer:add(MainSizer, ButtonSizer, []),
 
    wxSizer:addSpacer(OuterSizer, 20), % spacer
    wxSizer:add(OuterSizer, MainSizer, []),
 
%% Now 'set' OuterSizer into the Panel
    wxPanel:setSizer(Panel, OuterSizer),
 
    wxFrame:show(Frame),
 
% create two listeners
    wxFrame:connect( Frame, close_window),
    wxPanel:connect(Panel, command_button_clicked),
 
%% the return value, which is stored in State
    {Frame, T1001, ST2001}.
 
loop(State) ->
    {Frame, T1001, ST2001}  = State,  % break State back down into its components
    io:format("--waiting in the loop--~n", []), % optional, feedback to the shell
    receive
 
        % a connection get the close_window signal
        % and sends this message to the server
        #wx{event=#wxClose{}} ->
            io:format("~p Closing window ~n",[self()]), %optional, goes to shell
            %now we use the reference to Frame
         wxWindow:destroy(Frame),  %closes the window
         ok;  % we exit the loop
 
    #wx{id = ?wxID_EXIT, event=#wxCommand{type = command_button_clicked} } ->
%     {wx, ?wxID_EXIT, _,_,_} ->
            %this message is sent when the exit button is clicked.
            %The exit button is given ID ?wxID_EXIT = 5006 (from wx.hrl).
            %the other fields in the tuple are not important to us.
            io:format("~p Closing window ~n",[self()]), %optional, goes to shell
         wxWindow:destroy(Frame),
         ok;  % we exit the loop
 
%     {wx, 101, _,_,_} ->
%            loop(State);
 
     Msg ->
         %for now, everything else ends up here
         io:format("loop default triggered: Got ~n ~p ~n", [Msg]),
         %The next line is here just to remove compiler warnings
         T1001, ST2001,
         loop(State)
 
    end.
 

Main Menu

Login Form