0% found this document useful (0 votes)
27 views

Step 2 Creating A Main Function: #Include Int Int Return

The document provides instructions for creating a basic C++ project in Visual Studio 2015 that uses the Philips Sense graphics library. It describes adding necessary includes, changing the build configuration to x64, installing required NuGet packages, and initializing key Sense components like the Scheduler, Gpu, Canvas, and OutputScreen to get a basic "Hello World" application running. The tutorial then introduces some core concepts of the Sense library like namespaces, properties, and how the different library components interact.

Uploaded by

Haresh Gaikwad
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
27 views

Step 2 Creating A Main Function: #Include Int Int Return

The document provides instructions for creating a basic C++ project in Visual Studio 2015 that uses the Philips Sense graphics library. It describes adding necessary includes, changing the build configuration to x64, installing required NuGet packages, and initializing key Sense components like the Scheduler, Gpu, Canvas, and OutputScreen to get a basic "Hello World" application running. The tutorial then introduces some core concepts of the Sense library like namespaces, properties, and how the different library components interact.

Uploaded by

Haresh Gaikwad
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

Page 1 of 20

We start by opening Visual Studio 2015 and select FileàNewàProject from the menu. We want an empty C++
project, so in the templates tree on the left, we select Visual C++àGeneral and select Empty Project in the right
pane; see Figure 1. We will name our project “Tutorial”.
Figure 1

When we click OK you’ll see that we’ve created an empty project! Well almost empty; In the Solution Explorer
you can still see three useless filters (Header Files, Resource Files and Source Files), go ahead and delete those.

Step 2; creating a main function


Now let’s create an application. Right-click Tutorial in the Solution Explorer and click AddàNew Item. Select C++
File, name it main.cpp and click Add.

Now let’s fill it with a standard WinMain function from Snippet 1. Finally press F5 and we should be able to
compile and run.
Snippet 1
#include <Windows.h>

int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)


{
return 0;
}

Notice that the compiler says we’re compiling in Win32 mode. That’s not right; Applications in Philips are built in
x64, so we will need to change the configuration.

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 2 of 20

We have two platforms; Win32 and x64, but we only want one. Click on the combobox and click x64.

Now if we press F5 we should see that the compiler is compiling for x64 instead of Win32.

Step 3; add some Sense to it…


We’re almost there. We’ll add a few lines of code to our main.cpp and then make sure it’ll work.

First let’s make includes work. Change the main.cpp so that it looks like Snippet 2;

Snippet 2
#include <Windows.h>
#include <Sense.h>
#include <DLS.h>

int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)


{
return 0;
}

You’ll see that we’ve added the includes for Sense.h and DLS.h. We’ll discuss why there are two separate
includes later on, for now we just want it to compile; when you press F5 you’ll see that not surprisingly the
compiler can’t find the includes.

To fix this we’ll add the Sense and DLS packages from our nuget server in Best.

First navigate to the nuget package manager; in Visual Studio we can find this by going to Tools -> NuGet
Package Manager -> Manage NuGet Packages for Solution…

Next we need to add the NuGet server; click the settings icon in the top right corner. Under NuGet Package
Manager -> Package Sources, click the add button on the top right. Name is “Best Server” and put
“https://artifactory-ehv.ta.philips.com/artifactory/api/nuget/IGT-AppEngine” for the source. When done click
“Update” then “Ok”.

Now set your package source to your new server and navigate to browse.

Install Philips.Sense.DLS for your project, this will install both the DLS and Sense packages.

Next we will also make sure we can link Sense and DLS. Right-click your project and navigate to properties. We
need to change the runtime libraries used for both our Debug and Release configurations to ensure that we can
link to Sense successfully. Navigate to C/C++ -> Code Generation -> runtime Library. For the debug configuration
set this option to “Multi-threaded Debug (/MTd), and for release configuration set it to “Multi-threaded (/MT)”

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 3 of 20

When you now press F5 you’ll see that we


compile normally.

Add the code Sense::Gpu(0).show(); to your WinMain function and try to compile. This time, your linker should
tell you that symbols are missing, which is correct of course.

When you now compile, everything should go fine. To finish up, remove the ugly Sense::Gpu(0).show(); line from
your code again (its purpose was only to test if you’re linking the Sense library correctly) and save your project.
We’re done!

Your project-file should look now like the project in Sense/Tutorials/Tutorial1

Chapter 1: “Hello World”.


At the end of this chapter you’ll have learned the basics of Sense and you can create an application with a simple
dialog.

Step 1; includes and namespaces


We start by opening the project created in the previous chapter (if you’ve skipped that chapter, you can also
load it from Sense/Tutorials/Tutorial1). You’ll notice that it’s a project with only one file; main.cpp, which looks
like Snippet 3;
Snippet 3

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 4 of 20

#include <Windows.h>
#include <Sense.h>
#include <DLS.h>

int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)


{
return 0;
}

Notice the two separate libraries; Sense and DLS. The difference between these libraries is that the Sense library
contains system-level components and behavior, whereas the DLS library contains only visualizations for
Controls, such as buttons. Were it needed to create a Vechian or other visual style, there would also be a
Vechian library.
Luckily the previous chapter already made sure we include the libraries we need, so we only need to declare
that we use their namespaces (which saves us a lot of typing). See Snippet 4 for the changes we made;
Snippet 4
#include <Windows.h>
#include <Sense.h>
#include <DLS.h>

using namespace Sense;


using namespace Sense::DLS;

int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)


{
return 0;
}

Step 2; initializing the application


Sense applications use a few key components;
l Scheduler; contains the run-loop of your application; all components of Sense rely on the Scheduler for
timing and messaging.

l Gpu; as the name implies, the Gpu class controls a single Gpu. If you have more than one Gpu in your
system, you can also define more gpu instances in your application.

l Canvas; this is where you build your UI upon; like a painting, the canvas is the area that is drawable.

l Output; an output is a screen, a window or an offscreen buffer, connected to a specific Gpu. Outputs
usually show the whole canvas, but this is not required; outputs show a rectangular area of the canvas.

l User; User classes enable input for your application. The default user class is UserMouseKeyboard;
allowing input from the mouse and keyboard to be processed by Sense.

So let’s grab these components and make an application; see Snippet 5 for our new implementation of
WinMain;
Snippet 5
int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
Scheduler scheduler;
Gpu gpu(0);

Canvas canvas(gpu, scheduler);

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 5 of 20

OutputScreen output(canvas, 0);


canvas.size = output.getSize();

UserMouseKeyboard user(canvas);

gpu.show();
return scheduler.run();
}

First, notice that we give argument “0” to both the gpu and the output instances; gpus and screens are
numbered from zero. So if you have three Gpus, you will be able to create Gpu 0, 1 and 2. Likewise each Gpu has
a number of screens connected. For each Gpu the screen identifiers start at zero. So a system with two gpu’s
and 4 screens (evenly divided over the Gpus) will have Gpu:Screen 0:0, 0:1, 1:0 and 1:1. Thus, the code in
Snippet 5 says “create an Gpu-instance for the first gpu, and an OutputScreen instance for the first screen of
that gpu.
Second, notice that the output and user instances require a Canvas in their constructor. As said before, a canvas
can have multiple outputs (and users).
Third, notice that we set the size of the canvas to the same size as the output. By default, the output is
positioned at Point(0,0) so this way the dimensions of the canvas and output are matched. size is a property of
the canvas. This acts as a public variable of the class that can be set by instances of other classes. We’ll come
back to properties later on. For now all you need to know is that as a rule, properties can be set by users to
change the behavior of Sense objects.
Finally, you can see that we end with gpu.show() and immediately return after scheduler.run(). The show()
function causes the gpu instance to initialize the graphical resources and render its outputs for the first time.
You’ll usually put this command at the end, so that you have a fully build-up UI shown from the start. However,
to process animations and user input, we require the scheduler to begin its run-loop run. Hence the last call; it
will not return until the scheduler is exited.
You can run this application; you should see a black screen. However, since we haven’t defined a way to exist
the application we will need to kill it from the debugger (use alt-tab).

Step 3; finalizing the application


Until now, we’ve been using only parts of the Sense library. However, the next parts come from DLS; we will set
the background to a nice light-gray color, add a dialog and a button to exit the application; see Snippet 6.
Snippet 6
OutputScreen output(canvas, 0);
output.color = Palette::Gray40;
canvas.size = output.getSize();

UserMouseKeyboard user(canvas);

Dialog dialog(canvas);

Button button(dialog);

Notice the following;


First, notice that we set the color property of the output to a color from the Palette. The Palette is a set of
predefined colors in DLS that are used for all controls. You can of course create your own color with the Color
(r,g,b) constructors.
Second, notice the hierarchy in which we create the controls; each control has another control as parent, so the

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 6 of 20

dialog is placed upon the canvas, and the button is placed upon the dialog.
Finally, notice the logical naming of controls; a button has a Button as class, a dialog a Dialog etc. Most if not all
controls in Sense/DLS have very clear names.
If you try to run this, you’ll notice that you still see nothing. The reason for this is that we haven’t positioned our
dialog and button yet, and we’ve also failed to give them a size. By default, position and size of all controls is
zero. We fix this in Snippet 7;
Snippet 7
Dialog dialog(canvas);
dialog.size = Size(500,200);
dialog.position = Point(100,100);

Button button(dialog);
button.size = Size(120,46);
button.position = Point(190,60);

We’re almost there; we only need to set a few properties of the button and dialog and we’re done.
We can add unicode text to the button and dialog’s text and tooltip properties; see Snippet 8. Note that we use
an icon from the iconset of DLS; all icons in DLS are available through Icons, the same is true for Cursors and of
course the aforementioned Palette.
Snippet 8
dialog.text = L"Hello World!";
button.text = L"Exit";
button.image = Icons::Exit;
button.tooltip = L"Exits the application";

Now if you run the application everything looks good, but we still don’t have any functionality; our buttonclicks
don’t do anything.
To remedy this, we add a small lambda function that calls Scheduler::exit(), using the event of Button;
Snippet 9
button.eventClicked = [] { Scheduler::exit(); };

Events are function-pointers that are called when a certain action is performed (in this case when the button is
clicked). You can hook your own function to an event, and this is where lambda functions of C++ really shine; In
most cases where you have a class you can simply use [this] { callMyFunction(); } to call a normal function of
your class. The lambda function automatically takes vare that the right object is called.
We’re done; your application should like something like Figure 5 and your code like Snippet 10;
Figure 5

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 7 of 20

Snippet 10
#include <Windows.h>
#include <Sense.h>
#include <DLS.h>

using namespace Sense;


using namespace Sense::DLS;

int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)


{
Scheduler scheduler;
Gpu gpu(0);
Canvas canvas(gpu, scheduler);
OutputScreen output(canvas, 0);

output.color = Palette::Gray40;
canvas.size = output.getSize();

UserMouseKeyboard user(canvas);

Dialog dialog(canvas);
dialog.size = Size (500, 200);
dialog.position = Point(100, 100);
dialog.text = L"Hello World!";

Button button(dialog);
button.size = Size (120, 46);
button.position = Point(190, 60);
button.text = L"Exit";
button.image = Icons::Abort;
button.tooltip = L"Exits the application";

button.eventClicked = []{ Scheduler::exit(); };

gpu.show();
return scheduler.run();
}

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 8 of 20

Chapter 2: Designing a UI.


In this chapter we will design a small image viewer. At the end of this chapter you’ll know how to find the
correct controls for your application, design a user interface with a UI designer and create an application with
flexible positioning.

Step 0; strategy
First we need to think about the best way to create our UI. Sense is built around the idea that controls are
positioned in their parent control, so we will start with the largest container controls (those controls that
contain a lot of other controls) and then move our way in. This also makes it easy to program, because we get
direct visual feedback; we will see our UI build up step by step, in the right positions and the right size, instead of
being focused on small details.

Step 1; selecting the right controls…


So how should our image-viewer look? Normally you get a UI-mockup from one of the UI designers, or you’d
design you own UI (more on that later on).

First we need to find out which controls we will need. That’s where the Sense Gallery is for; go ahead and open
it from Philips.Sense.DLS.(VersionNumber)\tools\ Gallery-DLS.exe. It looks like Figure 6;
Figure 6

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 9 of 20

The Gallery allows users to look at and play with the available controls; you can see all the available controls and
their various states (enabled, hover, mouse-down etc.). You’ll also notice the four buttons at the top; the first
button toggles the enabled state of all controls. The second button shows the bounds of the control (more on
that later). The third button shows how a control is drawn and the final button is a handy color lookup tool; you
can click-and-drag the button across the screen and it will show the color-code and nearest Palette color from
DLS.

Try to find the major container controls that we’re going to need by clicking through the various tabs of the
Gallery and comparing it to our mockup picture.

You should end up with a Panel as our largest container, then an Expander, a Dialog and finally a Toolbar. When
you look at the mockup, the dialog is obviously a temporary one (for information purposes, hence the large “i"
icon), so we’ll ignore that one for now.

Step 2; setup a basis to work with


Let’s start with the basics, and then work up to the details. First we’ll create a new class wherein our primary
window will be. Since our entire window is filled with a panel, we’ll create a subclass of Panel and work from
there;

Open op the Tutorial project from our last chapter. First add a new class; create an ImageViewer.cpp and
ImageViewer.h in our project.

We’ll let ImageViewer subclass Control. Your subclass should look like Snippet 11;
Snippet 11
#pragma once
#include <Sense.h>
#include <DLS.h>

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 10 of 20

namespace Tutorial
{

using namespace Sense;


using namespace Sense::DLS;

class ImageViewer : public Control


{
public:
explicit ImageViewer(Control& parent);
virtual ~ImageViewer();

ImageViewer(const ImageViewer& ) = delete;


ImageViewer(const ImageViewer&&) = delete;
ImageViewer& const operator=(const ImageViewer& ) = delete;
ImageViewer& const operator=(const ImageViewer&&) = delete;
};
}

Notice that we deleted the copy and move constructors/operators. We do this because controls cannot be
copied/moved by design; allowing copyable/moveable controls would make the implementation very difficult
and error prone, and would not serve any purpose.

The implementation of ImageViewer is equally simple; see Snippet 12;


Snippet 12
#include "ImageViewer.h"

using namespace Tutorial;

ImageViewer::ImageViewer(Control & parent) :


Panel(parent)
{
}

ImageViewer::~ImageViewer() = default;

Now, we need to change our WinMain so that we use our new ImageViewer. We also want to render to a
window instead of a screen. Do the following;

1. Include ImageViewer.h,
2. Declare that we’re using namespace Tutorial
3. We want to render to a window instead of a screen, so change OutputScreen to OutputWindow. The “0”
argument can be removed (an OutputWindow uses a optional Rect instead of a screen identifier; if you’ll
leave it out it will use a default positioning strategy).
4. Remove the line setting the output-color; it’s not needed anymore.
5. Remove the code about Dialog and Button, and declare our ImageViewer viewer control (with canvas as
parent of course).
6. Set the size of viewer to the output size.
7. Add output.show() below gpu.show(); because OutputWindow is a normal Windows window, it can be
shown and hidden like all other windows. That’s why you need to explicitly tell Sense to show it.

If you run the application, you’ll notice that when we resize the window, the panel isn’t resized with it. Let’s fix

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 11 of 20

this; we need to do something with our resize behavior. Luckily, OutputWindow has an eventResized that we can
use, so we’ll hook a lambda function to it that resizes the canvas and panel to the new size. Tour code should
look like Snippet 13 now;
Snippet 13
#include <Windows.h>
#include <Sense.h>
#include <DLS.h>

#include "ImageViewer.h"

using namespace Sense;


using namespace Sense::DLS;
using namespace Tutorial;

int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)


{
Scheduler scheduler;
Gpu gpu(0);
Canvas canvas(gpu, scheduler);
OutputWindow output(canvas);

canvas.size = output.getSize();

UserMouseKeyboard user(canvas);

ImageViewer viewer(canvas);
viewer.size = output.getSize();

output.eventResized = [&viewer, &output, &canvas] { viewer.size =


output.getSize(); canvas.size = output.getSize(); };

output .show();
gpu .show();
return scheduler .run();

}
Now when you run the application, you’ll see that the panel will resize with the window. Great!

Step 3; adding some more controls; control placement and ordering.


Okay, so we now have a window with a panel in it, pretty boring still… Let’s add the toolbar and the expander.
In the class declaration of ImageViewer, add a protected segment and declare an expander and a toolbar
(Panel); see Snippet 14;

Snippet 14
#pragma once
#include <Sense.h>
#include <DLS.h>

namespace Tutorial
{

using namespace Sense;


using namespace Sense::DLS;

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 12 of 20

class ImageViewer : public Control


{
public:
explicit ImageViewer(Control& parent);
virtual ~ImageViewer();

ImageViewer(const ImageViewer& ) = delete;


ImageViewer(const ImageViewer&&) = delete;
ImageViewer& const operator=(const ImageViewer& ) = delete;
ImageViewer& const operator=(const ImageViewer&&) = delete;

protected:
Expander expander;
Panel toolbar;
};

}
Now if you try to compile, your compiler will complain our newly added controls don’t have default
constructors. So go ahead and add them to the initialization list in the constructor of ImageViewer. For their
parent, we want *this, since we want the expander and toolbar inside our panel.
So now we just have to set the position and size of the expander and toolbar. But how do we do that when the
panel can be resized; we want our shiny new controls to resize as well.
To do this, we will override the onResize method. This method is always called when a control is resized, so
subclasses of Control can utilize this to reposition their member controls, custom graphics etc.
Now that we’ve been properly scared, let’s get on with the actual positioning.

First, we define the implementation of our onResize function and call the onResize of the superclass (Panel).

Now, we need to know where we can place our controls. For positioning we use a Rect, a Point and/or a Size.
These classes have many useful methods and operators that make them easy and intuitive to use. Take a look in
the headers of Sense.

To determine how large the bounds of our controls are, we could use the size property of our parent, but this is
not such a good idea. In Sense, each control has a space (which determines its bounds amongst others) and a
client-space (the bounds for all its children). These two spaces often differ; take for example a Dialog, Expander
or GroupBox. These controls all have a title-bar, and normally, you want buttons etc. below the titlebar. That’s
[1]
[1]
why Sense
parent. allows controls
Everything outside to
thedefine a client-space
client-area is clipped. . In Sense, each control is rendered in the client-area of its

There are three methods related to bounds;

getBounds()/setBounds() Gets/Sets the bounds of a control in its parent-space.


getSpace().bounds Rectangle of this controls’ space; used mainly when rendering custom
graphics.
getClientSpace().bounds Rectangle of this control’s client-space; used to place child controls.
First we need the bounds of the client-space, so we declare;
Snippet 15
Rect bounds = getClientSpace().bounds;

Next, we want to position and size of the toolbar and the expander. As you can see in the mockup, the toolbar is
at the top of the panel, and is about 60 pixels high. The expander is at the left side of the panel, and is 500 pixels

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 13 of 20

wide.

We can position the controls in two ways; using the setBounds(Rect) method, or setting the position and size
properties individually. Either way works, but from a code perspective the setBounds method leads to nicer and
more understandable code. See Snippet 16;
Snippet 16
expander.setBounds(Rect(bounds.left, bounds.top, bounds.left + 500, bounds.bottom ));
toolbar .setBounds(Rect(bounds.left, bounds.top, bounds.right, bounds.top + 60));

When you run your application now, you’ll see that the Expander and Toolbar are indeed at their expected
locations, but something is wrong; the toolbar is in front of the expander. That’s not really what we had in mind.

The reason for this is that in the class declaration of ImageViewer, we have declared the expander first, and then
the toolbar. This matters; Each Control will render itself, and then the first declared child control on top, then
the second child below the first, and so on and so forth. Note that the order of instantiation is determined by
the class declaration, not the initializers in the class constructor!

The fix is of course easy; swap the declarations of expander and toolbar, and now your application should look
like Figure 7. Note also that you can resize your window, and the expander and toolbar are resized too!
Figure 7

You can also change the order in which controls are rendered using the bringToFront and sendToBack methods.
Use these only when absolutely needed however; it is much easier to debug an application that behaves like the
way it is declared. We sometimes see users using bringToFront() to hide program errors; for example because
the user has accidently placed a transparent control in front of a dialog that eats mouse-clicks. The user will see
the dialog, but can’t click it. Using bringToFront on the dialog removes the symptoms, but doesn’t solve the issue
(and the code becomes harder to understand to boot).

Anyway, back to our application. There’s still something not entirely right; the toolbar shouldn’t be behind the
expander at all. In the toolbar.setBounds call, replace bounds.left with expander.getBounds().right.

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 14 of 20

When we now run the application, we see the border of the toolbar nicely aligned to the border of the
expander. However, when we collapse the expander, the toolbar isn’t compensated.

So we need to check for an event that occurs when the expander is collapsing or expanding. As we can see,
Expander has eventResize, which according to the documentation is called whenever the expander changes size.
We’ll add the following line to the constructor of ImageViewer;
Snippet 17
expander.eventResize = [this] { onExpanderResized(); };

Now, onExpanderResized is called whenever the expander changes shape. Declare onExpanderResized in the
class declaration. To finish up, provide the implementation for onExpanderResized and modify onResize as well;
we move the toolbar.setBounds call to onExpanderResized and provide getCurrentBounds instead of getBounds
of expander. See Snippet 18;
Snippet 18
void ImageViewer::onResize()
{
Panel::onResize();

Rect bounds = getClientSpace().bounds;


expander.setBounds(Rect(bounds.left, bounds.top, bounds.left + 500, bounds.bottom ));
onResizeExpander();
}

void ImageViewer::onResizeExpander()
{
Rect bounds = getClientSpace().bounds;
toolbar .setBounds(Rect(expander.getCurrentBounds().right, bounds.top, bounds.right, bounds.top + 60));
}

Step 4; add some Buttons;


To show that you don’t need to create a separate class for each container, we will now create the buttons of the
toolbar but declare them in ImageViewer. Add the following ButtonToolbar controls right at the top of our
protected section; pan, zoom, rotate, zoomIn, zoomOut, fullscreen and info. Now initialize them in the
ImageViewer constructor and give them the toolbar as parent.

Try running your application; if you’ve indeed declared the buttons and
separators above the toolbar, your application will now crash! This is because
C++ will initialize class members in the order of declaration; because we give
these controls toolbar as parent we crash in the constructor because toolbar is
given as an argument while it hasn’t been constructed yet. We should have
declared the buttons and separators below the toolbar declaration. Fix this, and
you’ll see that your application will run again.

Now we set the properties of the controls. First we’ll set the icons for the controls. Lookup the icons used in the
mockup in the Sense Gallery and set the image property to them. Set all the imageSize properties to 42.0 (a
double can be cast to a Size; it will use the same dimensions for width and height).

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 15 of 20

Now we need to position the controls. We’ll do this in the onResize function (we could set the size in the
constructor, but it’s nicer to do this in the onResize method if we have one; were we ever required to
dynamically change the bounds, everything is already in that method).

We’ll first need the bounds of the toolbar client space, so we’ll reuse the bounds Rect and set it to the toolbar
client-space.

Next we’ll declare bounding Rects for our buttons and separators. We’ll declare Rect b for the buttons, and Rect
s for the separators. We’ll use bounds.height() for the size of b, and Size(8, bounds.height()) for the size of s.
your code should look like Snippet 19;
Snippet 19
bounds = toolbar.getClientSpace().bounds;
Rect b = Rect(0,0,bounds.height(), bounds.height() );
Rect s = Rect(0,4,8, bounds.height() - 8 );

Now we’ll move b to the top-left position of bounds, by adding bounds.topLeft() to it (using the += operator).

Finally the actual positioning; we’ll use <button>.setBounds(b); b += Point(b.width(), 0); to position all the
buttons; see Snippet 20;
Snippet 20
b += bounds.topLeft();

pan .setBounds(b); b += Point(b.width(), 0);


zoom .setBounds(b); b += Point(b.width(), 0);
rotate .setBounds(b); b += Point(b.width(), 0);
zoomIn .setBounds(b); b += Point(b.width(), 0);
zoomOut .setBounds(b); b += Point(b.width(), 0);
fullscreen .setBounds(b); b += Point(b.width(), 0);
info .setBounds(b);

As you can see, this code is now very easy to understand, and looks very simple. If you’d tried doing the same
using the position and size properties, the code would look a lot more complex.

Your application should look like Figure 8.


Figure 8

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 16 of 20

Step 5; using Sense Composer


Tired from typing out all those controls? Fingers hurting from too much contact with your keyboard? No worries,
we have a better way to create static UIs like the toolbar!
Sense Composer is a UI designer that can create composite controls in C++ header files. See Figure 9 for a
screenshot. It looks and acts a lot like the UI Designer in QT Creator (or good ol’ Delphi).
Figure 9

Notice the dialog that’s being designed. The Composer saves directly to header files, so it is easy to use in your
project. Look at Snippet 22; this is the output of the screenshot above, saved to file test.ui.h.

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 17 of 20

Snippet 22
/*----------------------------------------------------
| This file was generated by Sense Composer 2.0 |
| Do not modify the Sense Composer Generated Code |
----------------------------------------------------*/
#pragma once

#include <Sense.h>
#include <DLS.h>

using namespace Sense;


using namespace Sense::DLS;

class Test : public Control


{
public:

Test(Control &parent)
:
Control(parent)
#pragma region Sense Composer (do not modify)
, dialog (*this)
, label (dialog)
, buttonCustom (*this)
#pragma endregion
{
#pragma region Sense Composer (do not modify)
layoutUpdateBegin();
name = L"Test";
size = Size(856, 640);
dialog.position = Point(96, 128);
dialog.size = Size(696, 336);
label.position = Point(96, 0);
label.size = Size(504, 152);
label.font = Font(L"CentraleSansBook", 50, true, false, false, false, Font::Antialias::High);
label.text = L"This makes sense!";
label.horizontalAlign = HAlign::Center;
label.verticalAlign = VAlign::Middle;
buttonCustom.position = Point(336, 376);
buttonCustom.size = Size(216, 56);
buttonCustom.checked = true;
buttonCustom.text = L"OK";
layoutUpdateEnd();
#pragma endregion
}

#pragma region Sense Composer (do not modify)


private: Dialog dialog;
private: Label label;
private: ButtonCustom buttonCustom;
#pragma endregion

private:

Test(const Test&);
Test& operator=(const Test&);
};

You can change the code directly and load it again into the Composer. However, code not related to controls, or
flexible code (like an if-statement) is not understood by the Composer and will be thrown out. Hence the
warning at the top of the file.
Let’s change our toolbar, so that we use the Sense Composer to design it rather than create it ourselves. Open
the Composer from Philips.Sense.DLS.(VersionNumber)/tools/Composer-DLS. In the middle is of course our UI
that we are designing. On the left of Composer are the controls listed, ordered by type. You can drag and drop
controls on the UI you’re designing. At the top is a toolbar with common tasks (New, Open, Save, Cut, Copy,
Paste, Bring to front, Send to back and Show wireframe). Finally, to the right the properties of the currently
selected control(s) is/are displayed.
Click “New…” from the toolbar.In the dialog that follows, select Panel as control type and click OK. You should

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 18 of 20

see a square Panel in the center.


First we want to change the size of this control; we don’t really care about the width, since we’ll change it in our
application anyway. But we’ll want to set the height to 58. You can change the size by dragging the square
anchors at the sides and edges of a selected control, or by setting it directly using the size property. Note that if
you resize using the anchors, the control will snap to a grid (spaced eight pixels apart), unless you press and hold
Ctrl.
Now, we’ll add a ButtonToolbar on the toolbar. Look it up in the left pane and drag it on the toolbar. We need
nine buttons in total, so we can drag & drop eight more buttons onto the toolbar, but selecting the first button
and using Ctrl-C/Ctrl-V works a lot faster!
Now we’ll want to change the size of all of the buttons. Press Ctrl-A to select all of the buttons (Ctrl-A selects all
the controls in the parent of the currently selected control), and set the size property to 59,59 pixels.
Select one of the buttons and align it with the top of the toolbar. Now we’ll check whether the size is ok; zoom
in a bit (using the mousewheel; you can reset our zoom-level by double-clicking with the right mouse-button).
You can see that the anchors at the bottom of this button are below the toolbar. That doesn’t look good. When
you click Preview in the Composer’s toolbar and hover over out aligned button, you’ll see that the bottom of the
button has indeed below the bottom of the toolbar and is therefore clipped.
The reason for this is that the toolbar has a smaller client-space than its own space. We didn’t need to know this
in the previous step, because we used relative dimensions; we asked the toolbar for its client-space and used
the height to dimension our buttons. The Composer uses only absolute values.
So we need to select all buttons again (go back to the “Designer” tab in the Composer toolbar) and change the
size to 56,56. Now it looks good; your UI should now look like Figure 10.
Figure 10

1. Select a control with the same parent, press Ctrl-A and move all controls around until you get all
controls back in the parent (you can also try resizing the parent, but that’ll only work if the missing
control is beneath or to the right of the parent control).
2. Discard your changes and start again.
3. Save the file and manually remove the control from the saved header file.

Right now, everything looks messy. Let’s order things and add some icons to the controls. First select all controls

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 19 of 20

and move them a bit to the right, so we can order everything from left to right. Now move one of the buttons to
the top-left position. This will be our pan button. When you look at the properties of this button you can see we
can set the image; set the correct icon. Next, we need to set the correct image-size. This should be 42,42; but all
images should have the same image-size. So press Ctrl-A again and change the image-size to 42,42. Now, when
we only select our pan-button again, we can see in the properties that its name is Generated. This means that
the Composer will figure out a name automatically. There is some logic behind the naming that will result in a
button with the text “OK” will be named buttonOK, but controls with the same text (or no text property) will be
named button, button2, button3 etc. However, we can of course explicitly set the name by replacing Generated
with a name of ourselves. This is a good idea if you plan to add logic (use events) to your control. Set the name
to “pan”.
Now position the other buttons, name them correctly, add their icons and place the separators at the right
position. You should end up with something like Figure 11;
Figure 11

Save the file in your project folder as tools.ui.h, and go back to Visual Studio; time to clean up the ImageViewer
class.
First add a new filter to our project; right-click Tutorial in the Solution Explorer and click AddàNew Filter… name
our filter “UI Headers”. Now right-click our new filter and click AddàExisting Item… and add tools.ui.h.
Next, we open the ImageViewer class declaration, and remove every member except for expander. Include the
tools.ui.h header file, and add declare Tools toolbar above the expander declaration.
Now, go to the class implementation. Here you can revert everything back to the implementation we had before
step 4! (Sorry for letting you write all that code). Cleans up, doesn’t it?
Now, compile and run; your application will look exactly the same as in Step 4, but with most of our UI code now
visually created with the Composer.

Step 6; finishing up the UI


You should now be able to create and add the message-dialog by yourself. Design them with the Sense
Composer as an exercise, and add them to our application. Create the dialog with the composer using the
mockup in as a guideline, and name the essential controls like this (we’ll use them later on);
OK-button à ok
LabelResolution à resolution
LabelFileName à filename
Save your UI to information.ui.h, include this header in ImageViewer.h and declare Information information as
our top-most control. We’ll want *this to be the parent.
In the onResize method, we want to position Information in the center of our ImageViewer. We don’t want to
alter the size. In this case, using the position property is best. A few hints;
1. Use the center() method of Rect to acquire the center of the client-space.
2. We can use the size of the Information control to determine the position.

We don’t want to show the dialog all the time; it should only be displayed when the “i”-button is pressed on the
toolbar, and disappear again when the OK-button is clicked. To do so, do the following in the ImageViewer
constructor;

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021
Page 20 of 20

1. Set information.visible to false.


2. Add a lambda function that sets information.visible to true when tools.info is clicked.
3. Add a lambda function that sets information.visible to false when information.ok is clicked.

When you have positioned the control, compile and run your application. You’ll see that you your dialog shows
up and goes away as intended. However, you’ll also notice that you can still click on controls beneath the dialog
when it is shown. We don’t want that; in the ImageViewer constructor, set the modal property of information to
true. This causes the information control to receive all input-events of the canvas when it is visible, and no one
else.
We’re done; your application should look like Figure 12.
Figure 12

In this chapter you’ve learned a lot about designing a UI and placement of controls. For custom positioning,
remember that the Rect, Point and Size classes contain many useful functions and constructors that can help you
place your controls easily and efficiently.
Remember our warnings well:
1. Always add the override keyword.

2. Always call your direct superclass.

3. The order of instantiation is determined in the header.

4. Don’t place controls outside its parent’s bounds in the Composer.

mhtml:mk:@MSITStore:D:\Dev\puma\root\Externals\Philips.Sense\doc\Sense.chm::/Se... 14-Oct-2021

You might also like