Download PDF
The Prima toolkit includes a built-in visual form designer that lets you create user interfaces, eliminating the need to write UI code by hand. It functions much like other 'drag-and-drop' builders: you can place buttons, text boxes, and other widgets onto your window with a click. Adding commands or event handlers to widgets is straightforward as well. You can save your designs to a file and later open them either in your application.
While I don’t personally use the Visual Builder in my workflow, I explored it for the purpose of this guide and discovered a few things that you might find useful.
When you design a UI in Prima’s Visual Builder and export it as a
This makes the Visual Builder a helpful tool for both rapid prototyping and learning the structure of Prima-based GUIs.
When you open Visual Builder, you’ll see three main windows:
The Object Inspector shows the settings and behaviors of whatever widget you select - like a button or a text box.
How to Use It:
This is a great way to explore and learn how different widgets work. Just pick something (like a button), then explore its properties and events.
The following screenshots will make this clearer.

Select property

Click Events below, you’ll see a new list. Click the item

...and you learn the code my

In this way, you can learn useful codes.
From the menu, choose File > Save As, and select Program scratch (*.pl) as the file type. This generates code that includes
Here’s how this program works, in plain terms:
It follows a standard GUI lifecycle:
Below is the code, thoroughly annotated for clarity.
package Form1Window; # Load the Prima GUI toolkit use Prima; use Prima::Classes;# Declare this package as a subclass of Prima::MainWindow use base 'Prima::MainWindow';# Load support for Button widgets use Prima::Buttons;# This method provides default properties for the window. # It is automatically called during ->new to get the initial setup values. sub profile_default {# Get the default values from the superclass my $def = $_[0]->SUPER::profile_default;# Define our custom default properties for this window my %prf = ( designScale => [8, 19], # UI scaling factor (font, controls, etc.) name => 'Form1', # Internal name for the window widget origin => [496, 268], # Starting position on screen (X, Y) size => [607, 371], # Window dimensions (width x height) );# Merge our custom defaults into the inherited default hash @$def{keys %prf} = values %prf;# Return the merged default settings return $def; }# This method is called right after profile_default. # It sets up widgets and layout inside the window. sub init { my $self = shift;# Call the superclass's init method to complete base initialization my %profile = $self->SUPER::init(@_);# Prevent visual updates while adding widgets (for performance/smoothness) $self->lock;# Add a button to the window $self->insert('Prima::Button', name => 'Button1', # Internal widget name origin => [105, 324], # Position of button inside the window size => [96, 36], # Button size (width x height) backColor => 0xff0000, # Background color (red, in hex) onClick => sub { my $button = shift;# When the button is clicked, change the window background to green $button->owner->backColor(cl::Green); }, );# Allow screen updates again $self->unlock;# Return the initialization profile (optional, but good practice) return %profile; } package main;# Load the Prima application core (this sets up the app framework) use Prima::Application;# Create the main window. This calls: # 1. profile_default to get default values # 2. init to build the interface (e.g., button) Form1Window->new; Prima->run;
However, the first click leaves white artifacts around the button after background color change. So I adapted the code.
onClick => sub { my $button = shift; my $owner = $button->owner; # Set new background color $owner->backColor(cl::Green);# Hide button to allow repaint under it $button->visible(0);# Force window redraw (including under the button) $owner->repaint;# Show the button again $button->visible(1); },
and
# Paint background sub onPaint { my ($self, $canvas) = @_; $canvas->color($self->backColor); $canvas->bar(0, 0, $self->size); }
The trick was forcing the button to hide temporarily so the background beneath it could repaint properly.
Just for fun, I modified the code of 24.3 and added a right-click option. So:
If you want to try, add to
onMouseUp => sub { my ($button, $btn_code, $x, $y, $mod) = @_; return unless $btn_code == mb::Right; my $owner = $button->owner; # Reset background to white on right-click $owner->backColor(cl::White); $button->visible(0); $owner->repaint; $button->visible(1); },
Prima comes with a built-in timer class called
I also saw how Prima uses a
A notification is an event that a widget or object can emit. It's similar to a signal or callback. You can assign a handler for it when you create the object (like
This structure made it very inviting to build my own version on top of it.
In
This tells Prima:
"This object can emit a Tick event, and it uses the default type of notification."
So when the timer interval passes, it internally calls:
This triggers your assigned handler. Where does this come from?
sub notify { my ($self, $notification, @args) = @_; my $handler = $self->can("on$notification"); $self->$handler(@args) if $handler; }
So when the timer fires, it says:
Which then calls:
If you assigned an
I created a new package called
I also added some convenience methods:
Now, this custom timer is more than just a tick generator - it’s a mini time tracker I can drop into any Prima app.
By building my own subclass, I didn’t just write new code - I learned how Prima is structured. It showed me how components in Prima are designed to be extended: clean separation of defaults, initialization, and behavior. I also got hands-on practice with how event notifications work under the hood.
And the best part? My new timer works just like a built-in widget, but it does exactly what I need.
First an overview of the implemented methods and their purpose.
| Method | Purpose |
|---|---|
| Sets initial |
|
| Hooks up the |
|
| Returns how many ticks have passed | |
| Resets counter to 0 | |
| Resets and starts | |
| Stops the timer |
So let’s have a look at the code with some annotations. First the package definition:
package My::ElapsedTimer; use base 'Prima::Timer'; # Add a new notification type if needed (optional) my %RNT = ( %{Prima::Timer->notification_types}, Elapsed => nt::Default, ); sub notification_types { return \%RNT; }# Set default profile values sub profile_default { my $def = $_[0]->SUPER::profile_default; $def->{timeout} = 1000; # tick every 1 second $def->{elapsed} = 0; # track elapsed time return $def; }# Initialization code sub init { my ($self, %profile) = @_; $self->{elapsed} = 0;# Set up the ticking behavior $self->onTick(sub { $self->{elapsed}++; $self->notify('Elapsed'); # emit custom Elapsed event }); return $self→SUPER::init(%profile); }# Public method to access elapsed time sub elapsed { $_[0]->{elapsed} }# Reset timer count sub reset_timer { my $self = shift; $self->{elapsed} = 0; }# Convenience methods sub start_timer { my $self = shift; $self->reset_timer; $self->start; } sub stop_timer { my $self = shift; $self->stop; } 1;
Now use the subclass in your main script:
use Prima qw(Application Buttons Label); use lib '.'; # or adjust to path where My/ElapsedTimer.pm is use My::ElapsedTimer; my $mw = Prima::MainWindow->new( text => 'Custom Timer Demo', size => [220, 150], icon => Prima::Icon->load('icon.png'), borderStyle => bs::Dialog, borderIcons => bi::SystemMenu|bi::TitleBar, ); my $label = $mw->insert('Prima::Label', text => 'Elapsed: 0', origin => [20, 80], size => [200, 40], font => { size => 16 }, ); my $button = $mw->insert('Prima::Button', text => 'Start', origin => [20, 30], size => [80, 30], ); my $reset_button = $mw->insert('Prima::Button', text => 'Reset', origin => [120, 30], size => [80, 30], ); my $timer; $timer = My::ElapsedTimer->new( timeout => 1000, enabled => 0, onElapsed => sub { $label->text("Elapsed: " . $timer->elapsed); }, ); my $running = 0; $button->onClick(sub { if ($running) { $timer->stop_timer; $button->text('Start'); $running = 0; } else { $timer->start_timer; $button->text('Stop'); $running = 1; } }); $reset_button->onClick(sub { $timer->reset_timer; $label->text('Elapsed: 0'); }); Prima->run;
One of the most powerful lessons I’ve learned - and continue to learn - is that true mastery comes from doing. Writing this guide, experimenting with Prima’s Visual Builder, and building custom components like the
Why "Learning by Doing" Works:
A Daily Practice: I encourage you to adopt this mindset: spend a little time each day experimenting. Try modifying an existing example, building a small tool, or even just exploring the Object Inspector. Over time, these small efforts compound into deep understanding and confidence.
Final Encouragement: The journey of learning never truly ends—it evolves. Whether you’re a beginner or an experienced developer, there’s always something new to discover. Embrace the process, celebrate the small wins, and remember: every line of code you write is a step forward.
Now, go build something amazing!
Looking for a project idea? Build your own mini task-manager app!
The Mini Task Manager App is a simple yet functional application built using the Prima toolkit and its Visual Builder. This app allows users to manage tasks, track time spent on each task, and perform basic operations like adding, completing, and deleting tasks. It serves as a practical exercise to reinforce concepts such as UI design, event handling, and custom component integration.
Main Window: A window titled "Mini Task Manager" with a size of 400x300 pixels.
Task List: A
Buttons:
Timer Label: A
Add Task:
Complete Task:
Delete Task:
Main Window Class:
Methods:
This exercise is designed to help you apply what you’ve learned about Prima’s Visual Builder, event handling, and custom components. By building the Mini Task Manager App, you’ll gain practical experience in creating functional and user-friendly applications. Good luck, and have fun coding!