Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Tutorials
Bazaar
Status & Roadmap
FAQ
Authors & License
Forums
Funding Ultimate++
Search on this site













SourceForge.net Logo

GUI Tutorial

Welcome in GUI Tutorial! Here you will learn how to write GUI applications using U++ library. All code mentions in this tutorial is multi platform. It means it works exactly the same on different operating system. Moreover, examples attached in this tutorial are bundle with U++ standard distribution and they are localized in tutorial assembly. So, you don't need to rewrite it by yourself. Good luck!

 


1. GUI application main function

To hide platform specific differences, U++ GUI application main function is defined using GUI_APP_MAIN macro:

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

GUI_APP_MAIN {

    PromptOK("Hello world");

}

 

 

Please notice that PromptOK function display simply information dialog with the given parameter in this case it is "Hello world". Of course, in the CtrlLib package, there are more similar library calls. For example - if you want to display an error you can use ErrorOK and for warnings it is corresponding Exclamation function.

To use U++ GUI related code, all you need to do is include <CtrlLib/CtrlLib.h> header. Please also make sure that CtrlLib package is attached to your current project.

 


2. Application window

Application top-level windows are of TopWindow class. You can run modal event loop for TopWindow using the Run or Execute methods. Both methods open the window if it is not yet open (you can also open it using Open or OpenMain). Unlike Run, Execute also closes the window at the end of modal loop. TopWindow is also closed by destructor.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

GUI_APP_MAIN {

    TopWindow w;

    w.Run();

}

 

 


3. Modifying TopWindow properties

You can modify properties and behaviour of TopWindow using "modifier" methods. Note that modifiers usually return *this as return value, which results in simplified syntax. You can also setup the size of TopWindow using SetRect (note that unless instructed otherwise, U++ will center the position of window).

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

GUI_APP_MAIN

{

    TopWindow w;

    w.Title("My application").MinimizeBox().Sizeable();

    w.SetRect(0, 0, 200, 300);

    w.Run();

}

 

 


4. Painting the view area

In order to display something inside TopWindow view area, you have to create derived class and override Paint method:

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    virtual void Paint(Draw& w) {

        w.DrawRect(GetSize(), SWhite);

        w.DrawText(20, 20, "Hello world!", Arial(30), Magenta);

    }

    

    MyAppWindow() {

        Title("My application").Zoomable().Sizeable();

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.SetRect(0, 0, 200, 100);

    app.Run();

}

 

 

The same application running in different operating systems. On the top MS Windows and on the bottom GNU/Linux with KDE.

 


5. Reacting to input events

In order to react to user actions like mouse clicks or keyboard events, you have to override appropriate virtual methods. To issue the repainting of view area, use the Refresh method.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    Point  p;

    String text;

    

    virtual void LeftDown(Point pos, dword flags) {

        p = pos;

        Refresh();

    }

 

    virtual void MouseMove(Point pos, dword flags) {

        text = Format("[%d:%d]", pos.x, pos.y);

        Refresh();

    }

 

    virtual void Paint(Draw& w) {

        w.DrawRect(GetSize(), SWhite);

        w.DrawText(p.x, p.y, text, Arial(20), Magenta);

    }

 

    MyAppWindow() {

        Title("My application").Zoomable().Sizeable();

        p.x = p.y = 0;

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 


6. Multiple main windows

If you application uses multiple top-level peer windows, you cannot use the Run method as it runs the modal loop for the window. Instead, you have to allocate top-level windows on the heap and use Ctrl::EventLoop - this event loop runs as long as there are any top-level windows opened. Use "delete this" in overridden Close method to close and deallocate the main window.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    virtual void Close() {

        delete this;

    }

 

    virtual void LeftDown(Point pos, dword flags) {

        (new MyAppWindow)->OpenMain();

    }

 

    virtual void Paint(Draw& w) {

        w.DrawRect(GetSize(), SWhite);

        w.DrawText(0, 0, "Click the view area to open next window!", Arial(20));

    }

 

    MyAppWindow() {

        Title("My multiwindowed application").Zoomable().Sizeable();

    }

};

 

GUI_APP_MAIN

{

    (new MyAppWindow)->OpenMain();

    Ctrl::EventLoop();

}

 

 


7. Menu

Content of menu in U++ is represented by the function or method adding required menu items to the Bar. Items can contain Events to actions invoked by choosing the menu item or Events to represent sub-menus. Menu itself is managed by MenuBar. MenuBar can act both as widget or as a Frame. Frames are placed to the window border and reduce its view area.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    MenuBar menu;

 

    void Exit() {

        if(PromptOKCancel("Exit MyApp?"))

            Break();

    }

 

    void SubMenu(Bar& bar) {

        bar.Add("Exit", [=] { Exit(); });

    }

 

    void MainMenu(Bar& bar) {

        bar.Sub("Menu", [=](Bar& bar) { SubMenu(bar); });

    }

 

    typedef MyAppWindow CLASSNAME;

 

    MyAppWindow() {

        Title("My application with menu").Sizeable();

        AddFrame(menu);

        menu.Set([=](Bar& bar) { MainMenu(bar); });

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 

                

Of course, with power of C++11 lambdas, it is also possible, if not always advisable, to 'inline' the whole menu structure to single MenuBar::Set call:

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    MenuBar menu;

 

    MyAppWindow() {

        Title("My application with menu").Sizeable();

        AddFrame(menu);

        menu.Set([=](Bar& bar) {

            bar.Sub("Menu", [=](Bar& bar) {

                bar.Add("Exit", [=] {

                    if(PromptOKCancel("Exit MyApp?"))

                        Break();

                });

            });

        });

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 


8. Context menu

Context local menu (usually invoked by right mouse click) is similar to the standard menu bar handling, just instead of adding MenuBar to your application, you execute the callback to the menu:

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    void Exit() {

        if(PromptOKCancel("Exit MyApp?"))

            Break();

    }

 

    void RightDown(Point, dword) {

        MenuBar::Execute(

            [=](Bar& bar) {

                bar.Add("Exit", [=] { Exit(); });

            }

        );

    }

 

    MyAppWindow() {

        Title("My application with local menu").Sizeable();

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 

 


9. Context menu alternative

It is also possible to create context menu by adding menu items directly to MenuBar. Example also demonstrates how to convert the menu item to same value, by using lambda capture by reference:

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    void Exit() {

        if(PromptOKCancel("Exit MyApp?"))

            Break();

    }

 

    void RightDown(Point, dword) {

        int result = Null;

        MenuBar menu;

        for(int i = 0; i < 10; i++)

            menu.Add(AsString(i), [=, &result] { result = i; });

        menu.Separator();

        menu.Add("Exit", [=] { Exit(); });

        menu.Execute();

        if(!IsNull(result))

            PromptOK("You have selected " + AsString((int)result));

    }

 

    MyAppWindow() {

        Title("My application with context menu").Sizeable();

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 

 

        

 


10. Adding images

To add some eye-candy to the menu, you can add small raster images. In U++, images are represented by Image values. You can use image designer, part of TheIDE, to design Image constants

 

 

To get the image constants into your application you have to use somewhat obfuscated but thankfully short sequence of preprocessor commands:

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

#define IMAGECLASS TutorialImg

#define IMAGEFILE <Gui10/images.iml>

#include <Draw/iml.h>

 

struct MyAppWindow : TopWindow {

    MenuBar menu;

 

    void Exit() {

        if(PromptOKCancel("Exit MyApp?"))

            Break();

    }

 

    void SubMenu(Bar& bar) {

        bar.Add("Exit", TutorialImg::Exit(), [=] { Exit(); });

    }

 

    MyAppWindow() {

        Title("My application with menu and images").Sizeable();

        AddFrame(menu);

        menu.Set([=](Bar& bar) {

            bar.Sub("Menu", [=](Bar& bar) { SubMenu(bar); });

        });

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 

Here Tutorial is the name of package where .iml file resides.

Image constants are represented by class methods of class defined as IMAGECLASS (TutorialImg in this case). Image constant then can be added to menu as another parameter of Bar::Add.

Note: #include <Draw/iml.h> works only when images are to be used in single .cpp file, otherwise you must put "obsuscated" sequence with #include <Draw/iml_header.h> to you header and #include <Draw/iml_source.h> to any .cpp file.

 

 


11. Toolbar

Toolbars are in fact quite similar to menus - they provide set of operations with names and Images that can be invoked by user. In U++ you can take advantage of this similarity as toolbars can share the definition with menus - if Bar::Add command contains the Image, it also adds toolbar button with that Image when used with ToolBar. The text of menu item then translates to button tip. If you want one particular Bar item to be used with menu or toolbar only, you can do that by using AddMenu or AddTool instead of simple Add. Also, items without Image are automatically added to menus only and vice versa, items without text are added to toolbars only.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

#define IMAGECLASS TutorialImg

#define IMAGEFILE <Tutorial/images.iml>

#include <Draw/iml.h>

 

struct MyAppWindow : TopWindow {

    MenuBar menu;

    ToolBar tool;

 

    void MenuFn() {

        PromptOK("Fn activated!");

    }

 

    void BarFn() {

        PromptOK("Fn2 activated!");

    }

    

    void Exit() {

        if(PromptOKCancel("Exit MyApp?"))

            Break();

    }

 

    void SubBar(Bar& bar) {

        bar.AddMenu("Function", TutorialImg::Fn(), THISBACK(MenuFn));

        bar.Add(TutorialImg::Fn2(), THISBACK(BarFn));

        bar.Add("Exit", TutorialImg::Exit(), THISBACK(Exit));

    }

 

    void MainMenu(Bar& bar) {

        bar.Add("Menu", THISBACK(SubBar));

    }

 

    typedef MyAppWindow CLASSNAME;

 

    MyAppWindow() {

        Title("My application with bars").Sizeable();

        AddFrame(menu);

        AddFrame(tool);

        menu.Set(THISBACK(MainMenu));

        tool.Set(THISBACK(SubBar));

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 


12. StatusBar and decorative Frames

To improve the visual aspect of the application, we will add some decorative Frames - those are frames that do not represent any action, just draw some kind of border at given frame level. We will also add the StatusBar and connect it to the menu and toolbar - this way it will start displaying descriptions placed to Bar items using a Help method. Note that decorative frame functions (TopSeparatorFrame and InsetFrame here) usually return a reference to single global object, as there are no changing properties required when using them with different windows and widgets concurrently.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

#define IMAGECLASS TutorialImg

#define IMAGEFILE <Tutorial/images.iml>

#include <Draw/iml.h>

 

struct MyAppWindow : TopWindow {

    MenuBar   menu;

    ToolBar   tool;

    StatusBar status;

 

    void MenuFn() {

        PromptOK("Fn activated!");

    }

 

    void BarFn() {

        PromptOK("Fn2 activated!");

    }

    

    void Exit() {

        if(PromptOKCancel("Exit MyApp?"))

            Break();

    }

 

    void SubBar(Bar& bar) {

        bar.AddMenu("Function", TutorialImg::Fn(), THISBACK(MenuFn))

           .Help("This invokes MenuFn method of tutorial example");

        bar.Add(TutorialImg::Fn2(), THISBACK(BarFn))

           .Help("This invokes BarFn method of tutorial example");

        bar.Add("Exit", TutorialImg::Exit(), THISBACK(Exit));

    }

 

    void MainMenu(Bar& bar) {

        bar.Add("Menu", THISBACK(SubBar));

    }

 

    typedef MyAppWindow CLASSNAME;

 

    MyAppWindow() {

        Title("My application with bars").Sizeable();

        AddFrame(menu);

        AddFrame(TopSeparatorFrame());

        AddFrame(tool);

        AddFrame(status);

        AddFrame(InsetFrame());

        menu.Set(THISBACK(MainMenu));

        menu.WhenHelp = status;

        tool.Set(THISBACK(SubBar));

        tool.WhenHelp = status;

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 


13. Adding child Ctrls - widgets, reacting to widget events

If you need to use some predefined child Ctrl (widget), place as variable somewhere, usually as member variable of your window or dialog class and use Add (or operator <<) to put it to your window.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    Button button;

    

    void Click() { PromptOK("You have clicked the button!"); }

 

    typedef MyAppWindow CLASSNAME;

 

    MyAppWindow() {

        Title("My application with button");

        Add(button.LeftPos(10, 100).TopPos(10, 30));

        button.SetLabel("Click me!");

        button <<= THISBACK(Click);

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

Position within parent view is given in logical coordinates (e.g. LeftPos or TopPos) that allow no-nonsense resizing of dialog. Widget events (like "button pushed") are reflected by its callbacks. There exists a "default" WhenAction callback in each widget that is usually invoked for "user changed a value or a state of widget" situation. You can assign this callback either as

 

widget.WhenAction = ....

 

or use equivalent operator version to save some typing

 

widget <<= ....

 

Note also that to make the code more clear and less verbose, U++ uses "method chaining" technique, where methods (usually those that affect appearance, position or behaviour of widgets - "modifiers" in U++ lingo) are designed to return a reference to *this - that should explain the line

 

Add(button.LeftPos(10, 100).TopPos(10, 30));

 

 


14. More about logical coordinates

Logical coordinates can align one or both sides of widget to the side of parent view at specified distance(s) in both vertical and horizontal direction. If only one side is aligned, logical coordinate specifies the size. There is also specific kind of logical coordinate the specifies the center position.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    Button lt, rt, lb, rb, lv, ht, hv, cb, rc;

 

    typedef MyAppWindow CLASSNAME;

 

    MyAppWindow() {

        Title("My application with button").Sizeable();

        *this

            << lt.SetLabel("left-top").LeftPos(10, 100).TopPos(10, 20)

            << rt.SetLabel("right-top").RightPos(10, 100).TopPos(10, 20)

            << lb.SetLabel("left-bottom").LeftPos(10, 100).BottomPos(10, 20)

            << rb.SetLabel("right-bottom").RightPos(10, 100).BottomPos(10, 20)

            << lv.SetLabel("left-vsize").LeftPos(10, 100).VSizePos(40, 40)

            << ht.SetLabel("hsize-pos").HSizePos(120, 120).TopPos(10, 20)

            << hv.SetLabel("hsize-vsize").HSizePos(120, 120).VSizePos(40, 40)

            << cb.SetLabel("hcenter-bottom").HCenterPos(90).BottomPos(10, 20)

            << rc.SetLabel("right-vcenter").RightPos(10, 100).VCenterPos(40)

        ;

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

 

 

 


15. Font-zoomed logical coordinates

All distances in U++ widgets are always in pixels. However, to respond to platform setting of GUI font, there are "font-zoomed" logical coordinates. Such coordinates scale distances by the ratio of current standard GUI font size to design font size (which is fixed to the default Windows font):

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyAppWindow : TopWindow {

    Button button;

 

    typedef MyAppWindow CLASSNAME;

 

    MyAppWindow() {

        Title("My application with font-zoomed button").Sizeable();

        *this << button.SetLabel("Button").LeftPosZ(10, 64).TopPosZ(10, 24);

    }

};

 

GUI_APP_MAIN

{

    MyAppWindow app;

    app.Run();

}

 

      

 


16. Layouts

Placing widgets by specifying their numeric logical coordinates is time consuming, therefore TheIDE provides visual designer to simplify this task.

 

 

Visual design in U++ is called "layout" and group of layouts is stored in .lay file. Format of .lay files is such that it can be directly included into C++:

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

#define LAYOUTFILE <Gui16/dlg.lay>

#include <CtrlCore/lay.h>

 

struct MyApp : public WithDlgLayout<TopWindow> {

    MyApp() {

        CtrlLayout(*this, "My dialog");

    }

};

 

GUI_APP_MAIN

{

    MyApp().Run();

}

 

 

To understand how this works, let us examine content of .lay file first:

 

LAYOUT(DlgLayout, 208, 64)

    ITEM(Label, dv___0, SetLabel(t_("Label")).LeftPosZ(8, 36).TopPosZ(8, 19))

    ITEM(EditString, text, LeftPosZ(48, 92).TopPosZ(8, 19))

    ITEM(Option, option, SetLabel(t_("Option")).LeftPosZ(8, 108).TopPosZ(32, 15))

END_LAYOUT

 

Header CtrlCore/lay.h includes LAYOUTFILE several times, altering definition of LAYOUT, ITEM and END_LAYOUT to produce C++ code which simplified form for this example looks like

 

template <class T> struct WithDlgLayout {

    Label dv___0;

    EditString text;

    Option option;

};

 

template <class T>

CtrlLayout(WithDlgLayout<T>& dlg, const char *title)

{

    Size sz = Ctrl::LayoutZoom(208, 64)

    dlg.SetMinSize(sz);

    dlg.SetRect(sz);

    dlg.Title(title);

    dv___0.SetLabel(t_("Label")).LeftPosZ(8, 36).TopPosZ(8, 19);

    text.LeftPosZ(48, 92).TopPosZ(8, 19);

    option.SetLabel(t_("Option")).LeftPosZ(8, 108).TopPosZ(32, 15);

}

 

This way, U++ keeps tight coupling between visual design and C++ code.

Notes: t_ function with string literal argument provides internationalization of texts. dv___0 is synthetic member variable name used for unnamed layout members (0 is index of member).

 


17. Value of widget

Many widgets have some sort of natural value. E.g. the value of EditString is String entered by user, whereas the value of Option is bool specifying the status of the option.

U++ provides unified interface to determine the main value of widget via GetData and SetData methods. All types of values are passed using polymorphic Value.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

GUI_APP_MAIN

{

    TopWindow  app;

    app.SetRect(0, 0, 200, 20);

    EditString text;

    app.Add(text.TopPosZ(0, 20).HSizePos());

 

    text.SetData("Some text");

    app.Run();

    PromptOK((String)text.GetData());

}

 

Because this feature is used very frequently, U++ provides operator overloads for this interface - operator<<= for SetData and operator~ for GetData.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

GUI_APP_MAIN

{

    TopWindow  app;

    app.SetRect(0, 0, 200, 20);

    EditString text;

    app.Add(text.TopPosZ(0, 20).HSizePos());

 

    text <<= "Some text - operator version";

    app.Run();

    PromptOK((String)~text);

}

 


18. Accepting and rejecting widgets

Ctrl interface provides

 

    virtual bool   Accept();

    virtual void   Reject();

 

methods. Accepting is usually a reaction to pressing OK button or similar approve operation and usually involves testing for correct content and finishing any pending operations for widget (this may involve e.g. updating data to SQL database). Accept must return true if widget is accepted.

Default Ctrl implementation calls GetData for the widget. If ErrorValue (special Value content) is returned by GetData, displays error information contained and returns false, otherwise recursively calls Accept for all children, anding the loop and returning false when any child returns one. If no false was returned so far, Accept returns true.

The opposite method to Accept is Reject. This is used when Cancel button is pressed or similar rejecting action. It simply cancels all pending operations on widget. Default implementation calls Reject for all children.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

GUI_APP_MAIN

{

    TopWindow  app;

    app.SetRect(0, 0, 200, 20);

    EditDate  date;

    app.Add(date.TopPosZ(0, 20).HSizePos());

    app.Run();

    if(app.Accept())

        PromptOK("Correct date.&[* " + AsString(~date));

}

 

 

 


19. Widget edit status flags

Ctrl interface provides several edit status flags:

Enabled/Disabled - disabled widgets do not receive any input. Disabled status is usually expressed by altered visual appearance of widget.

Editable/ReadOnly - read only widgets receive input messages but no changes to data are allowed. The difference from disabled status is that ReadOnly status allows read operations, like selecting and copying data to clipboard.

Modified - modify flag is set to true whenever data in widget change - either by user action or by widget's interface. Flag is cleared by ClearModify method.

Note that with exception of suppressing input events for disabled state, implementation of widgets is responsible for correct behaviour with respect to listed flags.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

GUI_APP_MAIN

{

    TopWindow  app;

    app.SetRect(0, 0, 200, 60);

    EditDate  date1, date2, date3;

    date1 <<= date2 <<= date3 <<= GetSysDate();

    date1.ClearModify();

    app.Add(date1.TopPosZ(0, 20).HSizePos());

    date2.Disable();

    app.Add(date2.TopPosZ(20, 20).HSizePos());

    date3.SetReadOnly();

    app.Add(date3.TopPosZ(40, 20).HSizePos());

    app.Run();

    if(date1.IsModified())

        PromptOK("Date was modified!");

}

 

 


20. Breaking the modal loop

When you invoke Run for your TopWindow,  the new event loop is entered. In order to exit it, e.g. by pressing the button, you have to call TopWindow's Break method. Argument passed to Break is then returned from Run.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyApp : TopWindow {

    Button exit;

 

    void Exit() {

        Break(999);

    }

    

    typedef MyApp CLASSNAME;

    

    MyApp() {

        SetRect(0, 0, 100, 100);

        Add(exit.SetLabel("exit").LeftPosZ(10, 64).TopPosZ(10, 24));

        exit <<= THISBACK(Exit);

    }

};

 

GUI_APP_MAIN

{

    MyApp().Run();

}

 

As this is very common situation, TopWindow provides another method, Breaker, which returns a Callback which, when invoked, performs the Break.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyApp : TopWindow {

    Button exit;

 

    typedef MyApp CLASSNAME;

    

    MyApp() {

        SetRect(0, 0, 100, 100);

        Add(exit.SetLabel("exit").LeftPosZ(10, 64).TopPosZ(10, 24));

        exit <<= Breaker(999);

    }

};

 

GUI_APP_MAIN

{

    MyApp().Run();

}

 

Another common situation is handling OK and Cancel buttons. OK button has to Accept the dialog before breaking the loop (if accept is successful), Cancel has to Reject it. TopWindow therefore provides Acceptor and Rejector methods providing Callbacks that invoke Accept and Reject as needed before calling the Break.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct MyApp : TopWindow {

    Button ok, cancel;

    EditDate date;

 

    typedef MyApp CLASSNAME;

    

    MyApp() {

        SetRect(0, 0, 200, 90);

        Add(date.LeftPosZ(10, 80).TopPosZ(10, 20));

        Add(ok.SetLabel("OK").LeftPosZ(10, 64).TopPosZ(40, 24));

        Add(cancel.SetLabel("Cancel").LeftPosZ(100, 64).TopPosZ(40, 24));

 

        ok.Ok() <<= Acceptor(IDOK);

        cancel.Cancel() <<= Rejector(IDCANCEL);

    }

};

 

GUI_APP_MAIN

{

    MyApp app;

    switch(app.Run()) {

    case IDOK:

        PromptOK(String().Cat() << "OK: " << ~app.date);

        break;

    case IDCANCEL:

        Exclamation("Canceled");

    }

}

 

Calls to Ok and Cancel methods of Button here make Button react to Enter and Esc keys.

 

To reduce tedious tasks even further, there are CtrlLayoutOK, CtrlLayoutCancel, CtrlLayoutOKCancel etc. template functions that both setup layout and assign Acceptors and Rejectors.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

#define LAYOUTFILE <Gui20d/myapp.lay>

#include <CtrlCore/lay.h>

 

GUI_APP_MAIN

{

    WithMyAppLayout<TopWindow> app;

    CtrlLayoutOKCancel(app, "MyApp");

    switch(app.Run()) {

    case IDOK:

        PromptOK(String().Cat() << "OK: " << ~app.date);

        break;

    case IDCANCEL:

        Exclamation("Canceled");

    }

}

 

Note: IDOK, IDCANCEL are predefined constants.


21. Creating and using custom widgets

There is really nothing special about creating your own widgets. All that is to be done is to derive your own class from Ctrl (or some other existing widget, if that fits better) and override Ctrl's virtual methods to paint the widgets and do something useful:

 

struct MyCtrl : public Ctrl {

    int count;

    

    virtual void Paint(Draw& w) {

        w.DrawRect(GetSize(), White());

        w.DrawText(2, 2, AsString(count));

    }

    

    virtual void LeftDown(Point, dword) {

        count++;

        Refresh();

    }

    

    MyCtrl() { count = 0; }

};

 

To put your widget into your layout based dialog, the simple way is to add it as "User class". In that case you just fill in the name of class and layout designer shows the custom widget as empty rectangle with class name sticked on it:

 

 

Note that layout file has to be included after declaration of custom widget (so that the class is defined at that point).

While empty rectangle in layout designer is usually enough for seldom used widgets, you can also teach TheIDE to show something better using .usc script file:

 

ctrl MyCtrl {

    group "Example";

 

    GetMinSize() { sz = GetTextSize("X"); sz.cy += 2; return sz; }

    GetStdSize() { sz = GetMinSize(); sz.cx *= 7; return sz; }

 

    Paint(w) {

        r = GetRect();

        w.DrawRect(r, :White);

        w.DrawText(2, 2, "0", StdFont(), :Black);

    }

};

 

Note: This is not the C++, but a piece of script in "Esc" scripting language, interpreted by layout designer.

 

 


22. Non-modal dialogs

To have non-modal dialog, just open Open it passing its owner window as parameter. Usually the best arrangement is to have non-modal dialog as member variable of its owner window. Non-modal dialog is dismissed using the Close method just like any other window.

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

struct NonModalDialog : public TopWindow {

    Button b;

    

    void DoClose() {

        Close();

    }

    

    typedef NonModalDialog CLASSNAME;

    

    NonModalDialog() {

        SetRect(0, 0, 200, 50);

        Add(b.SetLabel("Close non-modal dialog").SizePos());

        b <<= THISBACK(DoClose);

    }

};

 

struct MainWindow : public TopWindow {

    NonModalDialog dlg;

    Button b;

    

    void DoOpen() {

        if(dlg.IsOpen())

            dlg.Close();

        else

            dlg.Open(this);

    }

    

    typedef MainWindow CLASSNAME;

    

    MainWindow() {

        SetRect(0, 0, 400, 100);

        Add(b.SetLabel("Open/close non-modal dialog").SizePos());

        b <<= THISBACK(DoOpen);

    }

};

 

GUI_APP_MAIN

{

    MainWindow win;

    win.Run();

}

 

 


Recommended tutorials:

If you want to learn more, we have several tutorials that you can find useful:

Draw tutorial - in this tutorial we show more advance techniques of drawing inside window. This is the natural continue of things mention on this page.

Image tutorial - here we move things related to images. We show how to create, use and embed icons directly in application.

Core value types tutorial - learn basics about non user U++ related code.

Last edit by cxl on 10/04/2016. Do you want to contribute?. T++