wxPerl: add component which resizes automatically when parent frame gets resized - perl

I am relatively new to Perl and I am using wxPerl to create a GUI application. Now, I want to add a Panel into a Frame, possibly using a sizer so that the panel resizes automatically as the frame gets resized.
So here's what I got:
(1) I have to use a BoxSizer, which stretch components in one direction.
(2) I have to pass parameters in the Add subroutines to stretch components in another direction.
I wrote the following code:
package Main;
use Wx;
use parent 'Wx::App';
sub OnInit {
my $frame = Wx::Frame->new(undef, -1, "SimpleCalc ".$Information::VERSION_NO, [-1,-1], [-1,-1]);
my $centerPanel = Wx::Panel->new($frame, -1, [-1,-1], [-1,-1]);
#set red background
$centerPanel->SetBackgroundColour(Wx::Colour->new(255,0,0));
my $frameSizer = Wx::BoxSizer->new(wxHORIZONTAL);
$frameSizer->Add($centerPanel, 1, 0, 0);
$frame->SetSizer($frameSizer);
$frame->Center();
$frame->Show(1);
return 1;
}
my $app = Main->new;
$app->MainLoop;
The unwanted result:
What I want is to stretch the red panel in both (horizontal and vertical) direction, or in short, I want something similar to BorderLayout in Java.
According to some online tutorials, I tried to replace $frameSizer->Add($centerPanel, 1, 0, 0); with
$frameSizer->Add($centerPanel, 1, wxEXPAND, 0);, but the script doesn't run. An error occurs saying that it is unable to resolve overload for Wx::Sizer::Add(Wx::Panel, number, scalar, number). I also tried $frameSizer->Add($centerPanel, 1, 0, 0, wxEXPAND);, but the frame obtained is exactly the same as the frame in the image.
Is it possible to have something similar to Java's BorderLayout in wxPerl? Thanks in advance.
P.S. I know there is a duplicate, but there are no concrete answers...

Update
In case you weren't aware, the default sizer for any child window will make it fill its available space, so to achieve the effect you're asking for all you need is this
use strict;
use warnings;
package Information;
our $VERSION_NO = 9.99;
package Main;
use Wx qw/ :colour /;
use parent 'Wx::App';
sub OnInit {
my $frame = Wx::Frame->new(undef, -1, "SimpleCalc $Information::VERSION_NO");
my $centerPanel = Wx::Panel->new($frame);
$centerPanel->SetBackgroundColour(wxRED);
$frame->Center;
$frame->Show;
return 1;
}
my $app = Main->new;
$app->MainLoop;
Original
It would have helped you a lot if you had use strict and use warnings in place! I and several others have to endlessly encourage people to do that but it seems sometimes that the message will never get across. Please try to make a habit of adding these statements to the top of every Perl program you write, and help us to spread the word
There are two things preventing your program from working
The value wxHORIZONTAL is undefined because you haven't imported it from Wx, so you are passing a value of zero to Wx::BoxSizer->new without any warning being raised
You have used a value of zero for the third parameter to $frameSizer->Add, which prevents the panel from expanding transversly to the direction of the sizer. You need wxEXPAND in there to enable it, and you will also need to import the value of that constant of course
Here's a rewrite of your code that fixes these problems, and also takes advantage of the defaults that will be used for missing parameters. I've also used wxRED instead of creating a new Wx::Colour object. I had to set a value for $Information::VERSION_NO too
This code works as you expected
use strict;
use warnings;
package Information;
our $VERSION_NO = 9.99;
package Main;
use Wx qw/ :sizer :colour /;
use parent 'Wx::App';
sub OnInit {
my $frame = Wx::Frame->new(undef, -1, "SimpleCalc $Information::VERSION_NO");
my $centerPanel = Wx::Panel->new($frame);
$centerPanel->SetBackgroundColour(wxRED);
my $frameSizer = Wx::BoxSizer->new(wxHORIZONTAL);
$frameSizer->Add($centerPanel, 1, wxEXPAND);
$frame->SetSizer($frameSizer);
$frame->Center;
$frame->Show;
return 1;
}
my $app = Main->new;
$app->MainLoop;
output
Fixed WxWidgets screen http://bit.ly/1JNrrEL

Related

Perl Gtk3::ScrolledWindow that contains more than one Gtk3::TreeView child. How do you disable child from scrolling to the top when selecting a row?

I am writing a program in Perl using Gtk3. I have a left sidebar (not using any sidebar widgets) that contains multiple tree views.
I it setup like this:
my $sidebarscrollarea = Gtk3::ScrolledWindow->new( undef, undef );
my $sidebarlabelaccounts = Gtk3::Label->new("Accounts");
$sidebarlabelaccounts->set_halign('GTK_ALIGN_START');
my $sidebarlabelincome = Gtk3::Label->new("Income Envelopes");
$sidebarlabelincome->set_halign('GTK_ALIGN_START');
my $sidebarlabelexpense = Gtk3::Label->new("Expense Envelopes");
$sidebarlabelexpense->set_halign('GTK_ALIGN_START');
# *_create_model() builds the models
my $account_tstore = account_create_model();
my $income_tstore = envelope_create_model();
my $expense_tstore = envelope_create_model();
# populate the models with another subroutine
populate_models();
my $accountslist = Gtk3::TreeView->new();
$accountslist->set_model($account_tstore);
my $incomelist = Gtk3::TreeView->new();
$incomelist->set_model($income_tstore);
my $expenselist = Gtk3::TreeView->new();
$expenselist->set_model($expense_lstore);
# Add columns to model and view via view ( Gtk3::TreeView )
account_add_columns($accountslist);
envelope_add_columns($incomelist);
envelope_add_columns($expenselist);
my $sidebarbox = Gtk3::Box->new('vertical',1);
$sidebarbox->set_border_width(5);
$sidebarbox->pack_start($sidebarlabelaccounts,0,0,5);
$sidebarbox->pack_start($accountslist,0,6,5);
$sidebarbox->pack_start($sidebarlabelincome,0,0,5);
$sidebarbox->pack_start($incomelist,0,6,0);
$sidebarbox->pack_start($sidebarlabelexpense,0,0,5);
$sidebarbox->pack_start($expenselist,0,6,0);
$sidebarscrollarea->add($sidebarbox);
The envelopeslist is very long. When I click a row from that list that is toward the bottom of the window, it scrolls so that the envelopes list at the top of the window. I do not want it to move anywhere regardless of where I click a row. Thanks for your help. I am new to all of this.
Simply adding:
$expenselist->set_can_focus(FALSE);
solves my problem.

Wxperl create widget but don't place it

How can I create a widget without it being placed on its parent?
Here's a minimal example.
package MyApp;
use strict;
use warnings;
use Wx;
use base 'Wx::App';
sub OnInit {
my ($self) = #_;
my $frame
= Wx::Frame->new( undef, -1, 'Test', [ -1, -1 ], [ 250, 150 ], );
my $sizer = Wx::GridBagSizer->new( 0, 0 );
my $btn1 = Wx::Button->new( $frame, -1, '1' );
my $btn2 = Wx::Button->new( $frame, -1, '2' );
$sizer->Add( $btn1, Wx::GBPosition->new( 2, 2 ) );
$frame->SetSizer($sizer);
$frame->Show(1);
1;
}
package main;
MyApp->new->MainLoop;
This yields
I want only what is placed in the sizer (button 1) to show.
You can hide things by calling $thing->Show(0). I added:
$btn2->Show(0);
The layout is still kinda funny because the space for the widget is still there—it's just not visible. So, it's still "placed". Maybe you want to create the control somewhere else that you can size on its own.
You have to hide the widget before the call to Layout.
See Hiding Controls Using Sizers
All non top-level windows are created shown by default. If you don't want them to appear on the screen, you need to hide them. The best way to do it is to hide them before actually creating the real window, which can be achieved in C++ by creating the window without giving it any parameters and then calling Create() with the same parameters you would normally use when creating it.
I'm not sure if this is exposed in wxPerl. If it is, something like this
my $btn2 = Wx::Button->new();
$btn2->Hide(); # or $btn2->Show(false)
$btn2->Create($frame, -1, '2' );
should work;
If not, you can still hide it and if you do it before showing the frame, it still won't be visible for the user.

Gtk (mm) limit width of combobox

Because I use Comboboxes that may contain text entries of very long size,
which leads to the combobox increasing its width far beyond reasonable size,
I am trying to give a maximum width to the combobox.
If I am doing this like this:
class MyCombo : public Gtk::ComboBox {
private:
CellRendererText render;
public:
MyCombo() {
render.property_width_chars() = 10;
render.property_ellipsize() = Pango::ELLIPSIZE_END;
pack_start(render, true);
}
};
The result will be an empty cell of the desired width, which seems logical since I did not specify which column to show. But how can I do this with that attempt? Using pack_start will just bypass the renderer...
Another approach is this one:
class MyCombo : public Gtk::ComboBox {
private:
CellRendererText render;
public:
MyCombo() {
pack_start(render, true);
set_cell_data_func(render, sigc::mem_fun(*this, &MyCombo::render_iter));
}
void render_iter(const TreeModel::const_iterator& iter) {
Glib::ustring data = get_string_from_iter(iter);
int desired_width_chars = 10; //for example
render.property_text() = ellipsize_string(data, desired_width_chars);
}
};
Using that approach, it works, but the text in the popup (what opens up when u click the combobox) is also shortened which is not what I want (obviously the user should be able to read the whole string and I dont care about the popup widht.)
Can you please help me with this? I would be happy for any advice/alternative solutions.
Regards tagelicht
NOTE: set_wrap_width is a function that wraps the total number of entries in the combo box over a number of columns specified; it does not answer the question.
Using set_wrap_width(1) | Using set_wrap_width(5)
Following Noup's answer as a guide I managed to get the below code; which directly answers the question and its requirements (C++/Gtkmm).
// Get the first cell renderer of the ComboBox.
auto v_cellRenderer = (Gtk::CellRendererText*)v_comboBox.get_first_cell();
// Probably obsolete; Sets character width to 1.
v_cellRenderer->property_width_chars() = 1;
// Sets the ellipses ("...") to be at the end, where text overflows.
// See Pango::ELLIPSIZE enum for other values.
v_cellRenderer->property_ellipsize() = Pango::ELLIPSIZE_END;
// Sets the size of the box, change this to suit your needs.
// -1 sets it to automatic scaling: (width, height).
v_cellRenderer->set_fixed_size(200, -1);
Result (image):
Result of code
BE AWARE: Depending on where you perform the above code; either all the cells will be the same size, or just the box itself (intended).
From experimenting, I've found:
In the parent object constructor: All cell sizes are the same.
In a separate function: Only the first cell (the box) is affected.
I'd recommend you put the code in a function that's connected to the comboBox's changed signal, such as:
v_comboBox.signal_changed().connect(sigc::mem_fun(*this, &YourClass::comboBox_changedFunction));
This may be what you are looking for:
cell_renderer_text.set_wrap_width(10)
This is for Python, but you get the idea :-)
Unfortunately, the documentation is scarce. I found this by poking around in Anjuta/Glade.
Edit:
the docs are here. They are not overly helpful, but they do exist.
As an alternative, the following works for me without having to set wrap_width nor to subclass ComboBox (in Gtk#):
ComboBoxText cb = new ComboBoxText();
cb.Hexpand = true; //If there's available space, we use it
CellRendererText renderer = (cb.Cells[0] as CellRendererText); //Get the ComboBoxText only renderer
renderer.WidthChars = 20; //Always show at least 20 chars
renderer.Ellipsize = Pango.EllipsizeMode.End;
Note: I'm using Expand to use space if it's available. If you just want to keep the combo box on a fixed width, just remove that bit.

Perl TK with Proc::Background proper usage (keep GUI active?)

I'm trying to build a Toplevel window that will show the progress of a system cmd. I want the GUI to be active (without freezing and "not responding"), so pressing on the "Cancel" button will kill the process, otherwise, when finished, make active the "close" button and disable the "cancel". Following a suggestion to one of my previous questions, I tried to use Proc::Background. The sole way I've found to do it is:
my $proc1;
my $cancel = $toplevel->Button(-text => "Cancel", -command =>sub{$proc1->die;})->pack;
my $close = $toplevel->Button(-text => "Close", -command =>sub{destroy $toplevel;}, -state=>"disabled")->pack;
$proc1 = Proc::Background->new("x264.exe $args");
while ($proc1->alive == 1){
$mw->update();
sleep(1);
}
$cancel->configure(-state=>'disabled');
$close->configure(-state=>'normal');
Is there another, more efficient way to do it (without waiting 1 sec for response)?
Thanks,
Mark.
I use Time::HiRes::usleep.
use Time::HiRes qw(usleep);
while ($proc1->alive == 1){
$mw->update();
usleep(100_000); //0.1 seconds
}
It may be an overkill for this problem, but at some point our UI applications grow and we desperately need to use high resolution timers and asynchronously dispatch and listen events thorough out the application. For this purpose I find the POE framework a great asset.
I particularly use POE with wxWidgets but it is also compatible with Tk:
POE::Loop::Tk
The after method (of any Tk widget) lets you schedule a callback to occur a certain number of milliseconds in the future, and the waitVariable method (you'll need to search in the page) will run the event loop until a variable is set.
my $proc1;
my $cancel = $toplevel->Button(-text => "Cancel", -command =>sub{$proc1->die;})->pack;
$proc1 = Proc::Background->new("x264.exe $args");
my $procDone = 0;
my $timercb = sub {
if ($proc1->alive) {
$toplevel->after(100, $timercb);
} else {
$procDone = 1; # Anything really
}
};
$timercb();
$toplevel->waitVariable(\$procDone) unless ($procDone);
(I'm not sure if this code will work; I don't code very much in Perl these days, so I'm translating what I'd do in another language…)

Perl Curses::UI

I am trying to use the library Curses:UI from http://search.cpan.org/dist/Curses-UI/
to build a UI on linux karmic.
I can create a simple user interface e.g.:
#!usr/usr/bin/perl
use strict;
use Curses;
use Curses::UI;
$ui = new Curses::UI(-color_support=>1,-clear_on_exit=>1,-intellidraw=>1);
my $window = $ui->add('window', 'Window',-intellidraw=>1);
my $message = $window->add(-text=>"Hello!",-intellidraw=>1);
$window->focus();
$ui->mainloop();
Question: I need some way to communicate informatio to the UI i.e. I have a loop which will wait for message to come and change the text in window. Once this message comes a popup will be displayed.
Attempt:
my $ui = new Curses::UI(-color_support=>1,-clear_on_exit=>1,-intellidraw=>1);
my $window = $ui->add('window', 'Window',-intellidraw=>1);
my $message = $window->add(-text=>"Hello!",-intellidraw=>1);
pseudocode
while(true) #implemented a function to wait
{
popup($window->text("Hello how are you?"));
}
$window->focus();
$ui->mainloop();
Problem: The above does not work. I am given a dark screen where my message is displayed. I have read the documentation and when I relocate : $ui->mainloop() above the while loop I am given the user interface but now nothing communicates to the window.
Coincise Question: I need some way of displaying the user interface wait for inputs and display messages.
Could anyone please help me on this? Thank you!
I would just replace $ui->mainloop() with my own eventloop where my own stuff is updated aswell.
For reference $ui->mainloop() is implemented as follows:
sub mainloop {
my ($self) = #_;
# Draw the initial screen.
$self->focus(undef, 1); # 1 = forced focus
$self->draw;
doupdate();
# Inifinite event loop.
while (1) { $self->do_one_event }
}
So I would simply add your own tick() function to the while loop.