GUI component interaction
Almost every application needs to communicate with the user in some way. Therefore, a substantial part of the code deals with the interaction of program logic with GUI components. Typically, the following is needed:
- put values into input fields under program control
- read and check input from the user
- pop up dialogs to query the user for further information
You are encouraged to solve this task according to the task description, using any language you may know.
The task: For a minimal "application", write a program that presents a form with three components to the user: A numeric input field ("Value") and two buttons ("increment" and "random").
The field is initialized to zero. The user may manually enter a new value into the field, or increment its value with the "increment" button. Entering a non-numeric value should be either impossible, or issue an error message.
Pressing the "random" button presents a confirmation dialog, and resets the field's value to a random value if the answer is "Yes".
(This task may be regarded as an extension of the task Simple windowed application).
C++
with library Qt 4.4 , using (under Linux) first qmake -project and then qmake -o Makefile <projectfile>, then make
file interaction.h
<lang c++>#ifndef INTERACTION_H
- define INTERACTION_H
- include <QWidget>
class QPushButton ; class QLineEdit ; class QVBoxLayout ; class MyWidget : public QWidget {
Q_OBJECT
public :
MyWidget( QWidget *parent = 0 ) ;
private :
QLineEdit *entryField ; QPushButton *increaseButton ; QPushButton *randomButton ; QVBoxLayout *myLayout ;
private slots :
void doIncrement( ) ; void findRandomNumber( ) ;
} ;
- endif</lang>
file interaction.cpp
<lang c++>#include <QPushButton>
- include <QLineEdit>
- include <QMessageBox>
- include <QString>
- include <QRegExpValidator>
- include <QVBoxLayout>
- include <QRegExp>
- include <ctime> //for the srand initialization
- include <cstdlib> //for the random number
- include "interaction.h"
MyWidget::MyWidget (QWidget *parent ) : QWidget( parent ) {
myLayout = new QVBoxLayout( ) ; entryField = new QLineEdit( "0" ) ; QRegExp rx( "\\d+" ) ; QValidator *myvalidator = new QRegExpValidator( rx , this ) ; entryField->setValidator( myvalidator ) ; increaseButton = new QPushButton( "increase" ) ; connect( increaseButton, SIGNAL( clicked( ) ) ,
this , SLOT( doIncrement( ) )) ;
randomButton = new QPushButton( "random" ) ; connect( randomButton , SIGNAL( clicked( ) ) ,
this , SLOT ( findRandomNumber( ) )) ;
myLayout->addWidget( entryField ) ; myLayout->addWidget( increaseButton ) ; myLayout->addWidget( randomButton ) ; setLayout( myLayout ) ;
}
void MyWidget::doIncrement( ) {
bool ok ; int zahl = entryField->text( ).toInt( &ok, 10 ) ; entryField->setText( QString( "%1").arg( ++zahl ) ) ;
}
void MyWidget::findRandomNumber( ) {
QMessageBox msgBox( this ) ; msgBox.setText( "Do you want to create a random number ?" ) ; msgBox.setStandardButtons( QMessageBox::Yes | QMessageBox::No ) ; int ret = msgBox.exec( ) ; switch ( ret ) { case QMessageBox::Yes :
srand( time( 0 ) ) ; int zahl = random( ) ; entryField->setText( QString( "%1" ).arg( zahl )) ; break ;
}
}</lang>
file main.cpp
<lang c++>#include <QApplication>
- include "interaction.h"
int main( int argc , char *argv[ ] ) {
QApplication app( argc, argv ) ; MyWidget theWidget ; theWidget.show( ) ; return app.exec( ) ;
}</lang>
C_sharp
C# 3.0 with Windows Forms; compile as csc -t:winexe Program.cs on MS.NET or as gmcs -t:winexe Program.cs on Mono. <lang c_sharp>using System; using System.ComponentModel; using System.Windows.Forms;
class RosettaInteractionForm : Form {
// Model used for DataBinding. // Notifies bound controls about Value changes. class NumberModel: INotifyPropertyChanged {
Random rnd = new Random();
// initialize event with empty delegate to avoid checks on null public event PropertyChangedEventHandler PropertyChanged = delegate {};
int _value; public int Value { get { return _value; } set { _value = value; // Notify bound control about value change PropertyChanged(this, new PropertyChangedEventArgs("Value")); } }
public void ResetToRandom(){ Value = rnd.Next(5000); } }
NumberModel model = new NumberModel{ Value = 0}; RosettaInteractionForm() { //MaskedTextBox is a TextBox variety with built-in input validation var tbNumber = new MaskedTextBox { Mask="0000", // allow 4 decimal digits only ResetOnSpace = false, // don't enter spaces Dock = DockStyle.Top // place at the top of form }; // bound TextBox.Text to NumberModel.Value; tbNumber.DataBindings.Add("Text", model, "Value");
var btIncrement = new Button{Text = "Increment", Dock = DockStyle.Bottom}; btIncrement.Click += delegate { model.Value++; }; var btDecrement = new Button{Text = "Decrement", Dock = DockStyle.Bottom}; btDecrement.Click += delegate { model.Value--; }; var btRandom = new Button{ Text="Reset to Random", Dock = DockStyle.Bottom }; btRandom.Click += delegate { if (MessageBox.Show("Are you sure?", "Are you sure?", MessageBoxButtons.YesNo) == DialogResult.Yes) model.ResetToRandom(); }; Controls.Add(tbNumber); Controls.Add(btIncrement); Controls.Add(btDecrement); Controls.Add(btRandom); } static void Main() { Application.Run(new RosettaInteractionForm()); }
} </lang>
J
<lang j>INTERACT=: 0 : 0 pc interact closeok; xywh 6 6 48 12;cc Value edit; xywh 6 18 48 12;cc increment button;cn "+"; xywh 6 30 48 12;cc random button;cn "?"; pas 6 6;pcenter; rem form end; )
interact_run=: 3 : 0
wd INTERACT wd 'set Value 0;' wd 'pshow;'
)
interact_close=: 3 : 0
wd'pclose'
)
interact_Value_button=: 3 : 0
wd 'set Value ' , ": {. 0 ". Value
)
interact_increment_button=: 3 : 0
wd 'set Value ' , ": 1 + {. 0 ". Value
)
interact_random_button=: 3 : 0
if. 0 = 2 wdquery 'Confirm';'Reset to random number?' do. wd 'set Value ' , ": ?100 end.
)</lang>
Note: I used an edit box for the value, and edit boxes do not get onChange events, so rejection of non-numeric values will be delayed until the use of a button (or pressing enter).
Example use:
<lang j>interact_run</lang>
Liberty BASIC
Input Verification
<lang lb>nomainwin
textbox #demo.val, 20, 50, 90, 24 button #demo.inc, "Increment", [btnIncrement], UL, 20, 90, 90, 24 button #demo.rnd, "Random", [btnRandom], UL, 20, 120, 90, 24 open "Rosetta Task: GUI component interaction" for window as #demo #demo "trapclose [quit]" validNum$ = "0123456789." #demo.val 0
wait
[quit]
close #demo
end
[btnIncrement]
#demo.val "!contents? nVal$" nVal$ = trim$(nVal$) if left$(nVal$, 1) = "-" then neg = 1 nVal$ = mid$(nVal$, 2) else neg = 0 end if validNum = 1 nDecs = 0 for i = 1 to len(nVal$) if instr(validNum$, mid$(nVal$, i, 1)) = 0 then validNum = 0 end if if mid$(nVal$, i, 1) = "." then nDecs = nDecs + 1 end if next i if nDecs > 1 then validNum = 0 end if if neg = 1 then nVal$ = "-";nVal$ end if if validNum = 0 then notice nVal$;" does not appear to be a valid number. " + _ "(Commas are not allowed.)" else nVal = val(nVal$) nVal = nVal + 1 end if #demo.val nVal
wait
[btnRandom]
confirm "Reset value to random number";yn$ if yn$ = "yes" then nVal = int(rnd(1) * 100) + 1 #demo.val nVal end if
wait</lang>
Impossible to type non-numeric characters
<lang lb>nomainwin
stylebits #demo.val, _ES_NUMBER, 0, 0, 0 textbox #demo.val, 20, 50, 90, 24 button #demo.inc, "Increment", [btnIncrement], UL, 20, 90, 90, 24 button #demo.rnd, "Random", [btnRandom], UL, 20, 120, 90, 24 open "Rosetta Task: GUI component interaction" for window as #demo #demo "trapclose [quit]" #demo.val 0
wait
[quit]
close #demo
end
[btnIncrement]
#demo.val "!contents? nVal" nVal = nVal + 1 #demo.val nVal
wait
[btnRandom]
confirm "Reset value to random number";yn$ if yn$ = "yes" then nVal = int(rnd(1) * 100) + 1 #demo.val nVal end if
wait</lang>
Oz
Using Mozart's standard GUI library, building a small desktop application: <lang oz>declare
[QTk] = {Module.link ['x-oz://system/wp/QTk.ozf']}
proc {Main} MaxValue = 1000 NumberWidget GUI = lr( numberentry(init:1 min:0 max:MaxValue handle:NumberWidget) button(text:"Increase" action:proc {$} OldVal = {NumberWidget get($)} in {NumberWidget set(OldVal+1)} end) button(text:"Random" action:proc {$} if {Ask "Reset to random?"} then Rnd = {OS.rand} * MaxValue div {OS.randLimits _} in {NumberWidget set(Rnd)} end end) ) Window = {QTk.build GUI} in {Window show} end
fun {Ask Msg} Result Box = {QTk.build td(message(init:Msg) lr(button(text:"Yes" action:proc {$} Result=true {Box close} end) button(text:"No" action:proc {$} Result=false {Box close} end) ))} in {Box show} {Box wait} Result end
in
{Main}</lang>
As a web application, using the "Roads" web programming library. Connect your browser to http://localhost:8080/start after starting the program. <lang oz>declare
[Roads] = {Module.link ['x-ozlib://wmeyer/roads/Roads.ozf']}
MaxValue = 1000
fun {Start Session} {Page 0} end
fun {Page Val} html( body( %% numerical input with an HTML form local NewVal in form( {NumberInput Val NewVal} input(type:submit) method:post action:fun {$ _} {Page NewVal} end ) end %% link with button functionality a("Increase" href:fun {$ _} {Page Val+1} end) " " %% another "button-link" a("Random" href:fun {$ S} p("Reset to random? - " a("Yes" href:fun {$ _} Rnd = {OS.rand} * MaxValue div {OS.randLimits _} in {Page Rnd} end) " " a("No" href:fun {$ _} {Page Val} end) ) end) )) end
%% a "formlet", managing input of an integer value fun {NumberInput OldVal NewVal} input(type:text validate:int_in(0 MaxValue) value:{Int.toString OldVal} bind:proc {$ Str} NewVal = {String.toInt Str.escaped} end ) end
in
{Roads.registerFunction 'start' Start} {Roads.run}</lang>
PicoLisp
The standard PicoLisp GUI is HTTP based. Connect your browser to http://localhost:8080 after starting the following script. <lang PicoLisp>#!/usr/bin/picolisp /usr/lib/picolisp/lib.l
(load "@ext.l" "@lib/http.l" "@lib/xhtml.l" "@lib/form.l")
(de start ()
(and (app) (zero *Number)) (action (html 0 "Increment" "lib.css" NIL (form NIL (gui '(+Var +NumField) '*Number 20 "Value") (gui '(+JS +Button) "increment" '(inc '*Number) ) (gui '(+Button) "random" '(ask "Reset to a random value?" (setq *Number (rand)) ) ) ) ) ) )
(server 8080 "@start") (wait)</lang>
PureBasic
<lang PureBasic>Enumeration
#StringGadget #Increment #Random
EndEnumeration
If OpenWindow(0,#PB_Ignore,#PB_Ignore,180,50,"PB-GUI",#PB_Window_SystemMenu)
StringGadget(#StringGadget,5,5,170,20,"",#PB_String_Numeric) ButtonGadget(#Increment,5,25,80,20, "Increment") ButtonGadget(#Random, 90,25,80,20, "Random") Repeat Event=WaitWindowEvent() If Event=#PB_Event_Gadget Select EventGadget() Case #Increment CurrentVal=Val(GetGadgetText(#StringGadget)) SetGadgetText(#StringGadget,Str(CurrentVal+1)) Case #Random Flag=#PB_MessageRequester_YesNo Answer=MessageRequester("Randomize","Are you sure?",Flag) If Answer=#PB_MessageRequester_Yes SetGadgetText(#StringGadget,Str(Random(#MAXLONG))) EndIf EndSelect EndIf Until Event=#PB_Event_CloseWindow CloseWindow(0)
EndIf</lang>
Ruby
Uses the Shoes library. <lang ruby>Shoes.app do
stack do @textbox = edit_line
@textbox.change do @textbox.text = @textbox.text.gsub(/[^\d]/, ) and alert "Input must be a number!" if @textbox.text !~ /^\d*$/ end
flow do button "Increment" do @textbox.text = @textbox.text.to_i + 1 end
button "Random" do @textbox.text = rand 5000 if confirm "Do you want a random number?" end end end
end</lang>
Tcl
<lang tcl>package require Tk
- --- Our data Model! ---###
- A single variable will do just fine
set field 0
- --- Lay out the GUI components in our View ---###
- We use the Ttk widget set here; it looks much better on Windows and OSX
- First, a quick hack to make things look even nicer
place [ttk::frame .bg] -relwidth 1 -relheight 1
- A labelled frame containing an entry field constrained to use numbers
pack [ttk::labelframe .val -text "Value"] pack [ttk::entry .val.ue -textvariable field \ -validate key -invalidcommand bell \ -validatecommand {string is integer %P}]
- Now, a pair of buttons
pack [ttk::button .inc -text "increment" -command step] pack [ttk::button .rnd -text "random" -command random]
- --- Now we define the behaviors, the Controller ---###
- How to respond to a click on the "increment" button
proc step {} {
global field incr field
}
- How to respond to a click on the "random" button
proc random {} {
global field if {[tk_messageBox -type yesno -parent . \
-message "Reset to random?"] eq "yes"} { set field [expr {int(rand() * 5000)}]
}
}</lang>