Articles and samples about developing for BlackBerry 10 using Cascades or Native APIs.    

Blog

    Publish
     
    • Configuring Momentics for API-specific deployments of C++ code

      Published Feb 09 2018, 2:16 PM by greenmr

      When I first started writing my multiFEED app for BlackBerry 10 back in early 2013 there was only one Cascades API to worry about (10.0) and I wrote the user interface for multiFEED 1.x with QML as a learning experience so I didn’t worry too much about future API releases. Even when the Q10 debuted along with BlackBerry 10.1 there was nothing in my app at that time that would have benefited from 10.1 API features, so it wasn’t until the 10.2 API came along to support the new Z30 that I began to consider configuring Momentics to simplify the build process for devices running specific versions of BlackBerry 10. One thing that caught my attention was that with 10.2 my apps could define hotkeys for keyboard devices like the Q10 and Q5 to simplify common tasks, but it wasn’t possible to use this new feature without preventing my app from running on BlackBerry 10.0 or 10.1. About that time I was regretting a few internal design decisions behind multiFEED and decided to do a complete rewrite. The app would look almost identical, but I would rely on my 25 years of experience writing Enterprise Desktop applications with C++ (in part) to produce a leaner, more architecturally elegant version of multiFEED that used QML very sparingly and leveraged the speed advantage of compiled C++ over QML parsing and JavaScript execution.

       

      I knew I didn’t want to leave users running older versions of BlackBerry 10 behind, especially back then when carriers were sluggish at pushing 10.2 out to devices and most users were stuck on 10.0 or 10.1 (even today with 10.3.3 on the horizon a miniscule percentage of my downloads every month are still from 10.1 and even 10.0 devices), but I also wanted to give those on the newer OS the enhanced experience made possible by the 10.2 API. Since multiFEED 2.x was going to be 98% C++ I knew I could use the #if/#ifdef/#ifndef directives to include or block out sections of the code when compiling for the different API versions. I figured that I could create some new build configurations in Momentics and set them up to pass configuration-specific #defines to the compiler so that building for any API would be as simple as setting the API level in Momentics and then building with the appropriate configuration. Momentics lets you define symbols on a per build configuration basis, so I expected it to be easy. It wasn’t.

       

      The problem is that no version of Momentics yet released (at least up to 2.12) actually does anything with the symbols you define for a build configuration. Actually, that isn’t quite true. The code indexer reads them and will grey out sections of code it thinks are going to be eliminated due to #ifdef evaluations, but since the #define symbols are never actually passed to the compiler on the command line, code that shouldn’t be compiled… is. I tried hard to get it to work the way I expected, but once I accepted this was not something I could fix within Momentics I put together a workaround that bypasses the limitations of the IDE.

       

      The basis for this method is that while Momentics ignores any configuration specific #defines when compiling, it does honour configuration-specific include paths. If you specify a different include path depending on which API level your build configuration targets you can #include a header file in your C++ code which will resolve to different versions of the file when compiled. If those build-specific header files contain useful #defines you can manage which blocks of code get compiled by the various build configurations.

       

      To begin I created new build configurations for my app, inheriting each one from the appropriate base configuration. The names I used were:

       

      • Device‑Debug‑10.0
      • Device‑Debug‑10.1
      • Device‑Debug‑10.2
      • Device‑Profile‑10.0
      • Device‑Profile‑10.1
      • Device‑Profile‑10.2
      • Device‑Release‑10.0
      • Device‑Release‑10.1
      • Device‑Release‑10.2
      • Simulator‑Debug‑10.0
      • Simulator‑Debug‑10.1
      • Simulator‑Debug‑10.2

       

      Next I created a new directory in the root of my application called “Build‑Includes”, and under that I created one directory for each build configuration, including the base ones, each with exactly the same name as the build configuration it represents. In the end I had sixteen subdirectories under Build‑Includes, matching every build configuration I had set up for my app. Finally I created a template header file called “BuildSpecific.h” and copied it to each of the new subdirectories of Build‑Includes.

       

      Now each version of BuildSpecific.h had to be customized with #defines which can be used during compilation to decide which blocks of code to include. For example, the Simulator‑Debug‑10.1 version of BuildSpecific.h contains:

       

       

      #define _DEBUG_
      #define _API_10_1_

       

      …whereas the Device‑Release‑10.2 version contains:

       

       

      #define _RELEASE_
      #define _API_10_2_

       

       

      If creating and configuring sixteen header files seems like too much work for you, don’t worry, you can download a ZIP file containing the whole Build‑Includes directory structure (including headers for API 10.3). Just extract it to the root directory of your app and you are done. The whole setup process only has to be done once for each of your applications and shouldn’t take you more than five minutes each time.

       

      Once you have set up or copied all your header files you need to configure your Momentics build configurations to point to the correct version for each one. Before you can do that you need to know about yet another Momentics quirk, this time regarding build variables. If you open the properties page for your app and look at “C/C++ Build/Build Variables” (make sure “Show system variables” is checked) you will see a list of tokens you can use elsewhere in your build configuration as variables. The problem is that many of them don’t resolve properly when building, resulting in empty strings instead of the value they should be replaced with. After much trial and error I discovered that if you use one of these misbehaving variables to define a matching environment variable, a new build variable by the same name is created behind the scenes which does resolve properly. In our case we need the system “ConfigName” variable, so we will use it to create a new environment variable called “CONFIG_NAME”.

       

      Open the project properties for your app and navigate to “C/C++ Build/Environment”. IMPORTANT – select “All configurations” from the dropdown… you don’t want to have to do this next step sixteen or twenty times. Click the “Add…” button and enter “CONFIG_NAME” and “${ConfigName}” in the name and value fields respectively, then Click “Ok”. You now have a variable available to all your configurations that will resolve properly at build time.

       

      Now you need to add an include path that uses this new variable to the four base configurations. You don’t need to add it to all the new API-specific configurations because include paths are inherited by any configurations from the base ones they were copied from (the same is not true for Environment variables). Navigate to “C/C++ General/Paths and Symbols”. Set the Configuration dropdown to “Multiple configurations…” and select just the Device‑Debug, Device‑Profile, Device‑Release, and Simulator‑Debug configurations. Click the “Add…” button then check the “Is a workspace path” box. Enter “Build‑Includes/${CONFIG_NAME}” into the Directory field and click “Ok”. Close the project properties editor and you are now done configuring Momentics for API specific builds.

       

      Here’s an example of how to use this setup. Suppose that you have a C++ source file that creates a SystemListDialog. You want to set both the title and the rememberMeText, but rememberMeText wasn’t added until the 10.1 API. You also want to be able to do “live” translations of both attributes so the text will change appropriately if the user changes the handset Locale/Language while the dialog is being displayed, but the update() function that makes this possible wasn’t added till the 10.2 API. Here is a simple source file example to allow compiling without error with API 10.0, 10.1, or 10.2…

       

       

      // ---This header enables build-specific #defines
      #include “BuildSpecific.h”
      
      MyClass::MyClass() {
          this >_listDialog = new bb::system::SystemListDialog();
          this >_listDialog >setTitle( tr(“Title”) );
      
      #if !defined(_API_10_0_)
          // ---Only compile this if the API is at least 10.1
          this >_listDialog >setRememberMeText( tr( “Remember Me Text” ) );
      #endif
      
          …
          …
          …
      }
      void MyClass::liveUpdate() { #if !defined(_API_10_0_) && !defined(_API_10_1_)     // ---Only compile this if the API is at least 10.2     this >_listDialog >update(); #endif    }

       

       

      To build your app for the 10.0 API set the project API level in Momentics, then build using Device‑Debug‑10.0, Device‑Profile‑10.0, Device Release‑10.0, or Simulator Debug‑10.0. Likewise for 10.1 set the API level in Momentics then build with Device‑Debug‑10.1, Device‑Profile‑10.1, Device‑Release‑10.1, or Simulator‑Debug‑10.1. In this example we are not using any API features higher than 10.2, so for that level or higher you can build with either the 10.2 API level specific configurations, or just the base configurations since we never check for _API_10_2_. Note that in the example the API level is tested with “negative logic”, which is to say that we always check that we aren’t building with any API levels lower than the one we need for the code block to be compiled rather than test for the API we want or higher. This way any API level above that required for our most demanding features will automatically compile all the code even for higher API levels that don't exist yet. The four base build configurations always compile the highest API version of the code. Of course there might be some code that you want to compile only for lower API levels. When my multiFEED app is compiled for the 10.0 API pages that normally use custom pickers present a combination of simpler controls instead because custom pickers didn’t exist with the first OS release. Oh, and don’t forget to select the appropriate build configuration when exporting your release builds for each API level.

       

      The BuildSpecific.h files are not limited to just controlling what code is compiled for a given API level. They also contain a #define specifying the type of build… _DEBUG_, _RELEASE_, or _PROFILE_. This is very useful for code that makes debugging or testing easier but which you don’t want in your final release. For instance, I use this code to enable real PaymentManager transactions only in release builds...

       

       

      #ifndef _RELEASE_
          // ---Set the Payment manager to test mode
          PaymentManager::setConnectionMode( PaymentConnectionMode::Test );
      #endif

       

       

      The build type #defines are also handy for including profiling hooks in your code that will only be compiled when you are building for the profiler. If you need to control compilation based on whether the target is the simulator or a device you can use the built-in symbols __arm__ and __i386__.

       

      A simple Momentics tweak can make using this technique even more useful. Open the project properties, navigate to “C/C++ General/Indexer” and either enable project-specific settings or edit the Workspace Settings. In either case, ensure that “Build configuration for the indexer” is set to “Use active build configuration”. When you enable this setting the Momentics code editor will grey out blocks of your code that won’t be compiled with the currently active build configuration. Toggling the build configuration while watching the editor will immediately change the blocks that are greyed out, allowing you to check that each build type will include and exclude the code that you expect it to.

       

      Building with C++ #defines is a smart way to give your users the most advanced features their device OS provides without preventing the ones stuck on older operating systems from using your application at all. Momentics makes managing this process more difficult than it needs to be, but with a little ingenuity it can become an easy part of your workflow.

    • Impacts of Changing SSL / TLS Security Level

      Published Feb 09 2018, 2:16 PM by msohm

      BlackBerry OS 10.3.2 introduced a new setting founder under Settings -> Security and Privacy -> SSL/TLS.  This setting has 3 options, High, Compatible and Low.  This new setting will configure native OpenSSL and Android OpenSSL to use the proper ciphers and protocols accordingly.

       

       

      • "High" mode disables:   SSLv3, TLS 1.0, TLS 1.1, SHA1, SHA224
      • "Compatible" (default mode of operation) disables: SSLv3 and TLS 1.0.
      • "Low" mode allows anything OpenSSL supports.

    • Workaround for Error: Cannot Find Matching Debug Symbols

      Published Feb 09 2018, 2:16 PM by msohm

      Currently, debug symbols are not available for all in market BlackBerry 10 OS releases.  If you attempt to debug your application using a version we have not published debug symbols for you will receive the error of "Cannot find matching debug symbols".  This will prevent BlackBerry Momentics from starting its debugger.

       

      You can work around this issue by enabling debugging with mismatched debug symbols.  Use the following steps to enable this feature, which is disabled by default.

       

      1. Start BlackBerry Momentics.
      2. Click on the Window menu.
      3. Click on Preferences.
      4. Expand BlackBerry.
      5. Select Targets.
      6. Check "Allow debugging and profiling with mismatched symbols".
      7. Click OK.

      debugsymbols.png

    • Using Invocation with UTF-8 Characters from QML

      Published Feb 09 2018, 2:16 PM by msohm

      When you work in QML and JavaScript, the Unicode character set is used.  If you attempt to share UTF-8 characters that are either hard coded in QML or created using JavaScript, you may find some characters don't appear correctly in the application that is being invoked.  These characters will usually appear as squares or question marks.

       

      You can work around this by converting the Unicode string into UTF-8.  To do this, you will need to create a method in C++ that does the conversion, which you can call from QML.  Here is an example of a method that will perform this conversion in C++.

       

      QByteArray YourClass::encodeQString(const QString& toEncode){
          return toEncode.toUtf8();
      }

       

      Assuming the method above is exposed as a Q_INVOKABLE method and the class is exposed as the QML variable _app, here is how it can be used when invoking from QML.

       

      Button {
             id: shareUTF8
             text: "Share UTF8 Text QML"
             onClicked: {
                 invokeQuery.data =  _app.encodeQString("Pourquoi ça ne marche pas?");
                 invokeQuery.updateQuery();
                 invokeShare.trigger("bb.action.SHARE");
             }
             attachedObjects: [
                 Invocation {
                     id: invokeShareUTF8
                     query {
                         InvokeQuery {
                             id: invokeQueryUTF8
                             mimeType: "text/plain"
                             onQueryChanged: invokeShare.query.updateQuery()
                         }
                     }
                 }
             ]
         }

       

       

       

    • Applications That Use SystemDialog May Freeze on Startup on BlackBerry OS 10.3.1+

      Published Feb 09 2018, 2:16 PM by msohm

      Changes have been made in BlackBerry OS 10.3.1.1276 and higher that can cause an application to freeze on startup if either of the following scenarios are met:

       

       

      In BlackBerry OS 10.3.1.1276 and higher, SystemDialog is no longer shown until an application has a window group that owns the dialog. The window group is created in Cascades applications when setScene(bb::cascades::AbstractPane *scene) is called. Native applications not using Cascades will need to create their own window group.  Applications that called SystemDialog.exec() before calling setScene will now freeze, blocking for input from the dialog that is never displayed. The workaround is to move the exec() call after setScene or to use the SystemDialog.show() method, which is not blocking.

       

      Why this changed: If an application created a SystemDialog that didn't have a window group, the dialog may not be tied to the application. This means the dialog is not minimized or closed when the application is minimized or closed. If an application displayed a dialog which couldn't be dismissed, the user needed to reboot their device to clear the dialog.  This was triggered by a race condition, so you may not have seen this case in your own testing.

       

      You can use a BlackBerry 10 Simulator to verify if your application is affected by this.

    • How to Determine the Perimeter an Application is Running In

      Published Feb 09 2018, 2:16 PM by msohm

      Perimeter Overview

       

      BlackBerry 10 smartphones can operate with multiple, segregated perimeters.

       

      Personal Only - This is a consumer device not connected to a BlackBerry Enterprise Server.  There is only one perimeter, personal.

       

      Personal + Workspace - This is a device that is connected to a BlackBerry Enterprise Server.  There is a personal section for the user's personal applications and data (contacts, calendar, files, etc...) and a workspace section for corporate applications and data.

       

      Workspace Only - This is a device that is connected to a BlackBerry Enterprise Server and is operating in EMM Regulated mode.  In this mode there is only a workspace perimeter, no personal perimeter.

       

      In most cases applications can run in either perimeter without requiring any changes.  However applications may wish to adapt their features depending on the perimeter they are running in. 

       

      How to Determine Which Perimeter an Application is Running In

       

      You can determine which perimeter an application is running in by using a combination of the NET_PPS_ROOT and PERIMETER environment variables.

       

      PERIMETER = enterprise (Balance, Work perimeter)
      PERIMETER = personal (Workspace Only)
      PERIMETER = personal (Balance, Personal Perimeter)
      PERIMETER = personal (Personal only)

       

      NET_PPS_ROOT = /pps/services/networking/enterprise (Balance, Work Perimeter)

      NET_PPS_ROOT = /pps/services/networking/enterprise (Workspace only)
      NET_PPS_ROOT = /pps/services/networking (Balance, Personal Perimeter)
      NET_PPS_ROOT = /pps/services/networking (Personal Only)

       

      Examples:
      NET_PPS_ROOT = /pps/services/networking/enterprise) would mean a work perimeter – in either Balance or Workspace only.

      PERIMETER = personal AND NET_PPS_ROOT = /pps/services/networking/enterprise) would mean a Workspace only device.

       

      Example Implementation

       

      Here is an example of reading these environment variables to determine the perimeters being used.

       

       

       

      bool ApplicationUI::isWorkPerimeter()
      {
          char* ppsRoot = getenv("NET_PPS_ROOT");
          if (!strcmp(ppsRoot, "/pps/services/networking/enterprise")) {
              return true;
          }
          return false;
      }
      bool ApplicationUI::isPersonalPerimeter()
      {
          char* ppsRoot = getenv("NET_PPS_ROOT");
          if (!strcmp(ppsRoot, "/pps/services/networking")) {
              return true;
          }
          return false;
      }
      
      bool ApplicationUI::isWorkspaceOnlyDevice()
      {
          char *perimeter = getenv("PERIMETER");
          if (!strcmp(perimeter, "personal")) {
              if (isWorkPerimeter()) {
                  return true;
              }
          }
          return false;
      }

       

       

       

    • BlackBerry Native SDK Installation Fails Repeatedly At the Same Point

      Published Feb 09 2018, 2:16 PM by msohm

      Problem

       

      The BlackBerry® Native SDK failed to install into BlackBerry Momentics and now consistently fails at the same percentage when you try to install again.

       

      Cause

       

      A partial or corrupt installation exists of the BlackBerry Native SDK exists in your instance of BlackBerry Momentics.

       

      Resolution

       

      Delete the partial/corrupt installation using the following steps.

       

      1. Close BlackBerry Momentics.
      2. Delete the and folders inside BlackBerry Momentics installation folder that correspond to the BlackBerry Native SDK version that is failing to install.
      3. Delete the XML file in located in the folder below that corresponds to the BlackBerry Native SDK version that is failing to install.
        • Mac: ‘/Library/Research In Motion/BlackBerry Native SDK/qconfig’.
        • Windows: %HOMEPATH%\AppData\Local\Research In Motion\BlackBerry Native SDK\qconfig
      4. Launch BlackBerry Momentics and try to install again.

    • Logging Within your Application

      Published Feb 09 2018, 2:16 PM by msohm

      When logging activity within your application, you have a couple of options.  You can create your own logging system that writes activity out to a file.  However this isn't ideal because it means constant writes to flash memory, which slows your application down and wears out flash memory.

       

      The better option is to make use of the slogger2 API.  It uses in memory logging, but also provides a file you can access later to gather log statements made by your application.  The log file for your application can be found in /tmp/slogger2/..

       

       

      Logging in a Cascades Application

       

      If you are creating a Cascades application, it's even easier.  In a Cascades application qDebug and console.log is redirected to use slogger2.

       

      Logging from C++ in a Cascades application.

       

      qDebug() << "This is what I want to log";

      Logging from QML in a Cascades application.

       

      console.log("The logs are rolling, rolling, rolling."); 

       

       

      Logging in a non Cascades Application 

       

      If you are creating a non Cascades application (pure Qt, C or C++) you can use the slogger2 API directly.  The first step is to initialize slogger2.

       

      extern char *__progname;
      slog2_buffer_t buffer_handle[1];
      void slog2init() {
       slog2_buffer_set_config_t buffer_config = 
       {1, __progname, SLOG2_ERROR, 
       {{"ManagedTraceTest", 8}}};
       // Register the Buffer Set
       slog2_register( &buffer_config, buffer_handle, 0 );
       slog2_set_default_buffer( buffer_handle[0]); 
      }

       After initializing, you can create log entries like this:

       

      slog2c( NULL, 0, SLOG2_ERROR, "my first log" );

       

      Where To Find Your Log Files

       

      Either method creates a file you can access later to view activity in your application.  It is useful to add a feature to your application that could email this file to your support team.  The log statements for your application are logged in /tmp/slogger2/..  You can also view your application's log file by executing this command for debug releases of your application.

       

      slog2info -l 

       

      Access to logs are more restrictive for release builds of an application. To access the log files for a release build, use the slog2_dump_logs_to_file method to dump the logs files to a text file. Here’s a code example that dumps the log files to a file called mySlogs.txt within the logs directory of the application’s sandbox.

       

      QString theFile = QDir::current().absoluteFilePath("logs/");
      theFile.append("mySlogs.txt");
      
      FILE *file = fopen(theFile.toStdString().c_str(), "w");
      slog2_dump_logs_to_file( file, 0 );
      fclose( file );

       

       

       

    • ListView to show data in columns side by side

      Published Feb 09 2018, 2:16 PM by magnumopus

      If you want to use a ListView to show the data in columns with each column with its own header and side by side, this code could be useful to you:

       

      import bb.cascades 1.3
      
      Page {
          ListView {  
              layout: GridListLayout {
                  headerMode: ListHeaderMode.None
                  // The columnCount property must match the number of headers of your data
                  // or you will end up with more than one header per column.             columnCount: 3         }         dataModel: XmlDataModel {             source: "data.xml"         }                 listItemComponents: [             ListItemComponent {                 type: "header"                 Container {                     id: root 
                          // Uncomment the following line if you don't want the inner ListView to
                          // to have scroll capability. Note that to uncomment this line, your
                          // columnCount must match the number of headers in your data.                     // minHeight: Infinity                                      Header {                         title: ListItemData.title                     }                       Container {                         ListView {                             dataModel: root.ListItem.view.dataModel                             rootIndexPath: root.ListItem.indexPath                             listItemComponents: [                                 ListItemComponent {                                     type: "item"                                                                        Label {                                                                                        text: ListItemData.name                                         multiline: true                                     }                                 }                                                          ]                         }                       }                                                                }                                }         ]     } }

       

       You can test the previous code with the following .xml file:

       

          
              
              
              
              
              
              
          
          
              
              
              
              
              
              
              
              
              
              
              
              
          
          
              
          
      

       

      Here is a screenshot of how it looks.  The items in each column are scrollable.

       

      column_screenshot.png

       

       

    • How to create/remove any number of dynamic controls

      Published Feb 09 2018, 2:16 PM by bbsjdev

      A short example of how to avoid messy QML code such as arrays, maps, switch statements on Ids, etc. when creating and removing multiple dynamic controls.

       

      This example adds a countdown timer every time a button is pressed. To indicate how to pass information and control the multiple instances of the custom control (in this case a countdown timer) there is a button for pausing/unpausing all running timers.

       

      Dynamic timers

      Note: In the screenshots, the term 'infinite' is used to refer to an indeterminate number of controllers.

       

      Right, how is it done?

       

      The first QML file is just the container for the dynamic controls with a button to add the countdown timer and a button to pause the currently running timers as mentioned earlier.

       

      The custom control gets passed in the custom control's object reference on creation and saved in a property to pass back on the timer hitting zero. In this example, a signal is connected to a slot in the custom control after it is added to the Scene Graph so that a single signal tells all the controls to pause their timers.

       

         InfiniteControls.qml

      Page {
          Container {
              Container {
                  layout: StackLayout {
                      orientation: LayoutOrientation.LeftToRight
                  }
      
                  Button {
                      text: qsTr("Add timer")
                      
                      onClicked: {
                          var newTimer = countDownTimer.createObject()
                          
                          if (newTimer) {
                              timers.add(newTimer)
                              newTimer.ctrl = newTimer
      
                              // Connect the pause signal
                              bPause.pauseAll.connect(newTimer.pauseTimer)
                          }
                      }
                  }
         
                  Button {
                      id: bPause
      
                      signal pauseAll(bool pause)       // Signal we are going to connect to all dynamic control slots
                      property bool paused: false
      
                      text: qsTr("Pause running timers")
      
                      onClicked: {
                          paused = !paused
                          bPause.text = paused ? qsTr("Unpause running timers") : qsTr("Pause running timers")
                          pauseAll(paused)
                      }
                  }            
              }
      
              Divider { }
      
              ScrollView {
                  Container {
                      id: timers
                      
                      // Dynamic control area
                  }            
              }
          }
      
          attachedObjects: [
              ComponentDefinition {
                  id: countDownTimer
                  CountDownTimer {
                      onTimedOut: {
                          timers.remove(control)    // Dynamic control has signalled it;s finished, remove from list
                      }
                  }
              }
          ]
      }

       

      The next QML file is the count down timer itself (the custom control), on reaching 0 it will send a signal which is picked up by the previous file as a signal to remove the control. The QML file exposes several properties so that it can be easily modified (e.g. different start time, the from property).  The ctrl property is used to hold the custom control's object reference, there are other ways to achieve the same result here but for simplicity and to demonstrate passing in a property and using it in a signal I have chosen this method. The started property is there so that we don't accidentally start timers that haven't already been started before.

       

         CountDownTimer.qml

      import bb.cascades 1.2
      import custom.lib 1.0
      
      Container {
          signal timedOut(variant control)   // Signal indicating the timer has hit zero
      
          property variant ctrl: null      // Remember the control for passing back out in the signal
          property int from: 10           // from set as a property so that it can be set to any number from creator 
          property bool started: false  // flag to indicate if this timer has been started for use in 'pause all' function
      
          function pauseTimer(pause) {
              // Only restart the timers that have been started before
              if (started && !pause)
                  timer.start()
              else
                  timer.stop()
          }
      
          layout: StackLayout {
              orientation: LayoutOrientation.LeftToRight
          }
      
          Button {
              text: qsTr("Start/Stop")
              onClicked: {
                  timer.start()
                  started = true
              }    
          }
      
          Label {
              text: from 
          }
      
          attachedObjects: [
              QTimer {
                  id: timer
                  interval: 1000
      
                  onTimeout: {
                      from -= 1
                      if (from <= 0) {
                          timer.stop()
                          timedOut(ctrl)       // Tell any control interested the timer has ended
                      }
                  }
      
              }
          ]
      }

       

      In order to use the QTimer in a QML file it needs to be registered first, so in applicationui.cpp add the following line to the constructor...

       

         applicationui.cpp

      qmlRegisterType("custom.lib", 1, 0, "QTimer");
      

      Don't forget to include the header...

       

         applicationui.cpp 

      #include 

       

      As simple as that.

       

      BBSJdev

       

      Note: Make sure to disconnect the signals properly when adapting this to your own code.

    8 pages