📜 ⬆️ ⬇️

Tai'Dzen: first steps

Dear habrasoobschestvu, welcome!
In this article I would like to share a bit of my modest experience on the path of learning Tai'Dzen (or Tizen). How to join the Truth, I, to the best of my understanding, tried to describe in a previous publication . Being true to my promises, I continue the series of articles.
The article is divided into three parts and is built as a step-by-step guide to the development of a simple native application for maintaining a shopping list. In the first part, the structure of a typical Tizen project, some approaches to the design of the application architecture, as well as the peculiarities of working with a WYSIWYG GUI editor are examined in detail. In the second part, work with scenes and the scene editor, with lists of graphic controls (controls), their customization and event handling are considered. The third part shows the organization of the search and use of the database. The article provides a link to the git-repository, the text shows the labels on the corresponding commits. The material is intended for readers who are familiar with C ++ and have no experience with the Tizen SDK. For those who hear about Tizen for the first time, I recommend to get acquainted with the article mentioned above (it describes in detail the process of installing IDE and launching “Hello, world!” On the target device / emulator). Experience with mobile platforms does not hurt, but is not required, so welcome under cat.

PART ONE

Before embarking on developing an application for a new platform, it is necessary to become familiar with the main guidelines from its creators. In the case of Tizen, the vast majority of those are in the documentation supplied as part of the SDK. Thus, there is a chance to achieve enlightenment in Tai'Dzen even without the help of Google. However, in practice, the neophyte is faced with a number of features that are a serious test of his willpower and determination. These features are not so complicated, they just become apparent after all the available rakes have been put into action. Actually, as soon as the adept deals with another surprise, he immediately finds its description in the documentation. Or does not find. To comprehend Tai'Dzen you can only cease to strive for his comprehension. Therefore, we proceed directly to the practice.

Fig. by Leka

Launch the Tizen IDE, create a new Tizen Native Project: on the Template tab, select Tab-based Application, a subtype With SceneManager.
')


For the current task, the Form-based Application is also suitable, we use it if more complicated customization of the GUI is required (that is, in most commercial projects, ours is a training one). SceneManager allows you to conveniently manage forms (screens): activate / delete forms, transition history between them, determine the current form, etc. SceneManager naturally integrates into the MVC design pattern, where the form is just a feedback control panel, i.e. View.



With the help of SceneManager, the Controller (Controller) activates the “console”, then receives and processes its signals, and stores the results of the calculations in the Model. After making the calculations, the Controller sends a “refresh this” message to View, in response to which the View requests its status from the Model and displays it on the screen. The model in this scheme is the most “powerless” element: its task is only to provide data to the controller and View in a convenient form (controller for modification, View is read-only). It is possible that such a free interpretation of the MVC pattern will not find support in academic circles, but I successfully use it in practice.

We assemble the project and run it on the target device / emulator (the process is described in my previous article). Do not forget about setting up security certificates , otherwise you will not be able to start the application.

Consider the structure of the project that the IDE generated from the Tab-based Application pattern. To navigate through the project files, use the Project Explorer window (Window -> Show View -> Project Explorer).

I gave the project the name ShoppingList.



The application / shared icon is stored in the shared / res directory. The icon for HD screens is contained in the subdirectory screen-density-xhigh. Its size should be 117x117 pixels, the lighting is necessarily top, only round (PNG 32 bits with alpha channel). If these requirements are violated, the application will not be certified by the Tizen Store. The documentation also states that for WVGA screens, the icon size should be 78x78 pixels, but there are no devices with such screens yet. I once tried to make a separate icon for WVGA devices, put it in a separate subdirectory of screen-density-wvga, checked it on the emulator, and ... the application did not pass certification due to the “incorrect icon size”. Therefore, in the near future, we can limit ourselves to one icon for HD screens.

Spaces in the names of projects are not allowed, however, it is possible to insert a space into the displayed application name in the project settings. Settings are stored in a special project file manifest.xml, which by default is automatically opened using the Tizen Manifest Editor utility.



For each locale, you can specify a separate name in the appropriate language. Also, among other things, the manifest indicates the permissions (privileges) that the application requests from the OS. Attempts by the application to call the “undeclared” functions of the OS are harshly suppressed at all levels, starting from a local launch on the target device and ending with the moderation process in the Tizen Store.

The res directory stores application resources: images, audio recordings, as well as GUI description xml files. When the application is running, these xml files are used by the GUI framework to build forms, panels, buttons, and other GUI components (controls). They are formed using the Tizen UI Builder WYSIWYG editor, it also links up the process of visual building interfaces with the automatic code generation system (more later in the article).



The documentation contains a lot of text on the subject in which subdirectories to allocate resources. The text makes a depressing impression: the choice of a subdirectory is influenced by pixel density, screen size, type of coordinate system (logical or physical, indicated in the manifest), etc. However, at the moment in the Tizen Store it is proposed to indicate in the list of supported single resolution 720x1280 (ie HD), which in practice means using the screen-size-normal subdirectory. The type of the coordinate system does not matter.

In the data directory, we will store local data, since the application is read-only without any restrictions by the OS. The lib directory, as the name implies, is intended for storing libraries, the Debug directory contains object files, appears after compilation (if compiled in Release mode, a directory named Release will be added).



The inc and src directories contain header files and source code respectively. At the same time, they can store both their own files and links to files from other places of the workstation's file system. The link is convenient if several projects use the same source code, which often changes (i.e. there is no point in collecting it into the library). Add a link conveniently by dragging files from Explorer / Finder / etc. in the Project Explorer.

Consider the process of deploying an application in RAM.



The application entry point is contained in the file ShoppingListEntry.cpp, in the OspMain function.

_EXPORT_ int OspMain(int argc, char* pArgv[]) { AppLog("Application started."); ArrayList args(SingleObjectDeleter); args.Construct(); for (int i = 0; i < argc; i++) { args.Add(new (std::nothrow) String(pArgv[i])); } result r = Tizen::App::UiApp::Execute(ShoppingListApp::CreateInstance, &args); TryLog(r == E_SUCCESS, "[%s] Application execution failed.", GetErrorMessage(r)); AppLog("Application finished."); return static_cast< int >(r); } 


In this function, the main object of the application is formed, which contains the loop for processing system events and user interface events. The class of this object in our case is called ShoppingListApp and is inherited from the system class Tizen :: App :: UiApp, which encapsulates the mentioned event-loop. One of the events whose handlers are overridden in the ShoppingListApp class is the OnAppInitialized () event.

 bool ShoppingListApp::OnAppInitialized(void) { ShoppingListFrame* pShoppingListFrame = new (std::nothrow) ShoppingListFrame; TryReturn(pShoppingListFrame != null, false, "The memory is insufficient."); pShoppingListFrame->Construct(); pShoppingListFrame->SetName(L"ShoppingList"); AddFrame(*pShoppingListFrame); return true; } 


It occurs immediately after the launch of the application and means that the system is ready to start executing custom code. The root unit of the main GUI, the frame, is formed in the handler of this event. Generally, since pop-up panels and notifications are at the same level of hierarchy. The frame is customized according to the same scheme as the heir to the App class, in turn, is called ShoppingListFrame. In the OnInitializing () frame initialization event handler, a SceneManager is formed.

 result ShoppingListFrame::OnInitializing(void) { SceneManager* pSceneManager = SceneManager::GetInstance(); static ShoppingListFormFactory formFactory; static ShoppingListPanelFactory panelFactory; pSceneManager->RegisterFormFactory(formFactory); pSceneManager->RegisterPanelFactory(panelFactory); pSceneManager->RegisterScene(L"workflow"); result r = pSceneManager->GoForward(SceneTransitionId(IDSCNT_START)); return r; } 


SceneManager requires access to factories of GUI components (forms and panels), as well as a list of “scenes”. A scene is a combination of a form and a panel displayed on the screen at a single moment. Each scene has its own string identifier, the presence of a form for the scene is necessary, but the panel is optional.
Scene registration can occur directly by calling the SceneManager::RegisterScene(const SceneId &sceneId, const Tizen::Base::String &formId, Tizen::Base::String &panelId) method SceneManager::RegisterScene(const SceneId &sceneId, const Tizen::Base::String &formId, Tizen::Base::String &panelId) and indirectly, through loading the SceneManager xml-file with the description of the scenes SceneManager::RegisterScene(const Tizen::Base::String &resourceId) .

In this case, it is the workflow.xml file, which is contained in the res / screen-size-normal directory. When using this method, not only scenes are recorded, but also transitions between scenes, which is somewhat unclear. The SceneManager::GoForward method just activates such a transition (transaction) through its string identifier IDSCNT_START.
Identifiers for transition commands, scenes, forms, panels, etc. are formed using the UI Builder and placed by the code generation system in the AppResourceId file. It would be logical to expect that the name of the resource scene description file workflow.xml will also automatically be placed in the AppResourceId, but this does not happen.



SceneManager obtains transition identifiers from user code and determines form and panel identifiers on their basis. Form and panel factories issue a “product” to SceneManager using these identifiers. Then SceneManager mounts the form into a frame, the panel onto the form and starts rendering.



Thus, as a result of the transition IDSCNT_START, the main form ShoppingListMainForm is displayed on the screen, with the tab open with the number 1 (Tab1).

Consider what happens in ShoppingListMainForm.

The first after the completion of the constructor’s work will be the Initialize method:

 bool ShoppingListMainForm::Initialize(void) { result r = Construct(IDL_FORM); TryReturn(r == E_SUCCESS, false, "Failed to construct form"); return true; } 


As the name implies, it initializes the form, calling the inherited Construct method. The construction of the form is based on the data contained in the GUI description file. The IDL_FORM string is the identifier for this file.

The remaining methods are event handlers, in particular, the event of pressing the back / cancel button of the touch button.

In a typical Tizen-device on the front side under the screen there are three buttons: two touch and one mechanical. A mechanical button is called “Home” and takes the user to HomeScreen, with a long press, launches Task Manager. We cannot influence her behavior in any way. But pressing the touch buttons generate events that can be processed. As a rule, the left button ( Menu Key ) brings up the context menu, and the right ( Back Key ) means cancel, return, exit the application, etc. (depending on context).



 void ShoppingListMainForm::OnFormBackRequested(Tizen::Ui::Controls::Form& source) { UiApp* pApp = UiApp::GetInstance(); AppAssert(pApp); pApp->Terminate(); } 


The static method GetInstance returns a pointer to a ShoppingListApp object. Calling the Terminate method means that it is time for the heir Tizen :: App :: UiApp to reel the fishing rods, i.e. complete the event-loop and at the same time call the ShoppingListApp :: OnAppTerminating method. In this method, you need to close all resources, but it is not recommended to do this for a long time: the system will forcibly shut down the application after about 2 seconds (1950 ms, according to the profiler).

Of course, in order for the OnFormBackRequested handler to be called at all, you need to subscribe to this event by registering a pointer to the object that these events will listen to. In this case, this object is the form itself - it implements the IFormBackEventListener interface and signs itself in the OnInitializing handler:

 result ShoppingListMainForm::OnInitializing(void) { result r = E_SUCCESS; Header* pHeader = GetHeader(); if (pHeader) { pHeader->AddActionEventListener(*this); } SetFormBackEventListener(this); return r; } 


This event occurs at the beginning of the form activation, is called by the SceneManager. In this case, in the same handler, the form subscribes to Header events, a panel that switches tabs. For this form, you need to implement the interface IActionEventListener, i.e. describe the reaction to the OnActionPerformed event:

 void ShoppingListMainForm::OnActionPerformed(const Tizen::Ui::Control& source, int actionId) { SceneManager* pSceneManager = SceneManager::GetInstance(); AppAssert(pSceneManager); switch(actionId) { caseID_HEADER_ITEM1: pSceneManager->GoForward(SceneTransitionId(IDSCNT_1)); break; caseID_HEADER_ITEM2: pSceneManager->GoForward(SceneTransitionId(IDSCNT_2)); break; caseID_HEADER_ITEM3: pSceneManager->GoForward(SceneTransitionId(IDSCNT_3)); break; default: break; } } 


Everything is pretty obvious here: depending on which button is pressed on the Header, we ask SceneManager to show this or that panel.

Now let's take a quick look at the process of generating resource xml description files for the GUI using the UI Builder utility. In short, because the tool is very raw, and a review of all its “features” can serve as a material for a separate article. In the meantime, I can say that when working with the UI Builder, you need to use the “all or nothing” principle. This means that either using it, you need to come to terms with all the oddities and not to change anything (regarding the structure and names of directories, files, source codes), or not to use at all. Save time and nerves.

First, open the IDL_FORM.xml - GUI description file. By default, it should be opened via the UI Builder: if this did not happen, then through the context menu in the Project Explorer, specify Open with -> Native UI Builder ( example ).

The structure and principles of the editor are similar to the generally accepted ones: the working area is located in the center, the navigation and properties windows are located along the edges, and the main menu is on the top. Unusually located only the panel with graphic elements; Moreover, it can be accidentally rolled up and then searched for a long time:





A workspace can contain several tabs, each of which displays an editable GUI element. Working with elements is described in detail in the documentation , examples of the operation of the code generation mechanism are also given there. Opening a tab and navigating to it is done using the Resources window:



You can edit 5 types of GUI containers: Forms, Panels, Popups, QuickPanelFrames and ScrollPanels. At the same time, as mentioned above, SceneManager works only with forms and panels (and their factories). ScrollPanel can also be attributed to them, since it is inherited from the Panel. The purpose of QuickPanelFrames is not entirely clear - you can just as well use a regular panel, with the only difference that QuickPanelFrames is formed in a separate window, along with the frame (but it is still mounted on the form). With Popup everything is clear - this is a pop-up panel, guaranteed to be displayed over the form and its controls.

When opening the IDL_FORM.xml file, we observe only the form and its Header with buttons — the Tab1, Tab 2 and Tab3 panels are separately editable elements. They are mounted on a form not in the process of its creation, but with the participation of SceneManager. This is done so that you can control the lifetime of the panels from the outside (in the usual mode, the lifetime of the GUI component is controlled by the parent container). For example, if it is required that in the course of the application's operation, at any one time, there is only one panel in memory. It should be remembered that the application will work on a mobile platform, while each loaded panel consumes RAM and, more importantly, video memory (where textures are loaded).

The next type of resources are strings. Intended for application localization. They do not depend on the screen resolution, so they are immediately located in the res directory. The mechanism of operation does not differ much from analogs, for example in iOS, if it were not for one BUT: as an identifier for a string, one has to set not the English analogue of the phrase, but the unintelligible IDS_STRING + index. They can be changed, but at the same time both the editor and the parser of the obtained resource begin to fail. As a result, we have two problems:
1) It is easy to make a mistake in the code, since it is much easier to confuse indices than readable phrases;
2) If a phrase is not specified for any language, then instead of the English text in the application, we get this same IDS_STRING.
Of course, adherents who have attained a certain level of enlightenment do not confuse indices and easily fill localization tables by several thousand values, but this path still has to be covered ...

The last in the list is the Workflow resource: responsible for the formation of scenes and transactions (transitions between scenes). When working with it, the editor produces a record number of glitches, so in work projects I prefer scenes and transitions (especially transitions!) To form directly in the code. From a sense of self-preservation, nerves are more expensive.

Anyway, with the new SDK version, the proposed tools are improved, they slow down less, and in the last edition you can even work with them a little (if you don’t turn up complex custom structures and abandon code generation altogether).

Part two

Source: https://habr.com/ru/post/213385/


All Articles