Documentation | Manual: Messaging
Chapter 3: Messaging
In the previous chapter, the only user input we dealt with was the closing of the Window, and we used Haiku's builtin features to do that. In this tutorial, we're going to do something a little more complicated.
This program for this chapter is called ClickMe
.ext.
Message loops
This section is a brief overview of how the messaging system works. If you don't care about understanding this, just jump ahead to the next section.
A Message is essentially a packet of information. For each event (for example, button clicks), the system will create a Message and post it to a Looper.
Each Looper has a message loop running in a thread; Window and Application are subclasses of Looper.
The core of the message loop determines the Message's target Handler and
then passes the Message to the Looper's DispatchMessage
hook.
DispatchMethod
will convert some Messages into hook calls on other objects,
but most Messages simply pass through to the target Handler's
MessageReceived
hook.
Controls, such as Button, are subclasses of Handler. Looper is a subclass of Handler, so a Looper can also handle messages itself.
The default target for most Controls is not the Control itself, but the owning Window. This means you only need one Window subclass, not a subclass for each Control.
Imports
For this program, we will need Application, Window, Button, and Message. As in the previous tutorial, we will also need some constants, so we'll go ahead and list them here right away.
Perl
use HaikuR1::Window qw(B_TITLED_WINDOW B_QUIT_ON_WINDOW_CLOSE); # since we need the constants, we pull in Window directly # this will pull in InterfaceKit, which will pull in ApplicationKit # and there's no need to pull in the individual classes
Python
from HaikuR1.ApplicationKit import Application, Message use HaikuR1.InterfaceKit import Window, Button, B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE # we will need this in our Window subclass from HaikuR1.SupportKit import code_to_int
Window subclass
Most programs will create their message loops by subclassing Window and
overriding the MessageReceived
hook, which is what we will do here.
The first thing we need is a message constant. These are really 32-bit integers, but since 4 ASCII characters take up the same amount of space and are really handy for debugging, by convention Haiku message constants consist of 4 ASCII characters.
System message codes consist of upper-case ASCII characters and underscores, so if we stick to lower-case characters, we won't collide with any system messages.
In C++
, it is easy to define a 32-bit integer constant using an ASCII
string, but in native code it's a little more complicated. The Support Kit
provides a function to do this for you.
Perl
package ClickMeWindow; use parent 'HaikuR1::Window'; use HaikuR1::SupportKit qw(code_to_int); use strict; my $message_code = code_to_int('clik');
Python
class ClickMeWindow(Window): message_code = code_to_int('clik')
(Note that code_to_int
translates the code to a 32-bit integer. You don't
have to stick to ASCII characters; you can use any encoding you want, but if
you exceed 4 bytes, part of your code will be cut off, and then the reverse
function, int_to_code
, won't generate a string that matches your original
input string.)
Next, we need our button. We could make our button variable globally accessible and work outside the class, like we did with the StringView in the previous chapter. But since we need to create a Window subclass anyway, we'll do it all in our window class.
Button
Perl
sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{button} = HaikuR1::Button->new( frame => [10, 10, 100, 25], name => "ClickMeButton", label => "Click Me", message => HaikuR1::Message->new($message_code), ); $self->AddChild($self->{button}); $self->{count} = 0; return $self; }
Python
def __init__(self, *args, **kwargs): self.button = Button( frame = [10, 10, 100, 25], name = "ClickMeButton", label = "Click Me", message = Message(message_code), ) self.AddChild(self.button) self.count = 0
Note the following:
* The Button class takes a label
instead of the text
parameter that
StringView took. It also takes a message
parameter; this message will be
passed to the window whenever the user clicks the Button.
* In addition to the button, we included a count
variable that tracks
the number of times the user clicks the button.
MessageReceived
hook
Finally, we need to respond to our custom Message.
When implementing custom hooks in a subclass, pay attention to the default
behavior of the hook. In some cases, you need to call the base class version
or you will break something. In other cases, the base class version simply
signals an error and calling it will break something. In the case of
MessageReceived
, we must call the base class version of the hook for any
Message we do not handle.
Perl
sub MessageReceived { my ($self, $message) = @_; if ($message->what == $message_code) { $self->{count}++; $self->{button}->SetLabel("Click Me $self->{count}"); return; } $self->SUPER::MessageReceived($message); }
Python
def MessageReceived(self, message): if message.what == message_code: self.count += 1 self.button.SetLabel("Click Me " + str(self.count)) return return super(ClickMeWindow, self).MessageReceived(message)
Running the app
Although this program is little more complicated than the previous one, it is still simple enough that we can get by with the standard Application class. Since we're not subclassing Application, all that's left is to create the objects, show the window, and run the app.
Perl
package main; # leave the subclass namespace my $app = HaikuR1::Application->new("application/x.vnd-hab.ClickMe"); my $window = ClickMeWindow->new( frame => [50, 50, 300, 300], title => "Hello World", type => B_TITLED_WINDOW, flags => B_QUIT_ON_WINDOW_CLOSE, ); $window->Show(); $app->Run();
Python
app = Application("application/x.vnd-hab.ClickMe") window = ClickMeWindow( frame = [50, 50, 300, 300], title = "Hello World", type = B_TITLED_WINDOW, flags = B_QUIT_ON_WINDOW_CLOSE, ) window.Show() app.Run()
Once again, this program is simple enough that it doesn't require any cleanup code.