Cutelyst 3 is out!

Cutelyst, the C++/Qt web framework just got a new major release.

Under the hood it now has both Qt5 and Qt6 support, but because of this a few things had to be changed, the most important one was QMap usage as a multi-key container had to be changed to QMultiMap, if you used ParamsMultiMap types or auto when assigning there's probably little to do, one major exception is that if you still use Grantlee it might not work well, but Cutelee is there with fixes and is almost ported to Qt6.

Another API changes were on the authentication classes that have some friendly methods for QString passwords, WSGI module is now called Cutelyst::Server, it allows you to embed Cutelyst on other applications or have it as a stand alone executable, allowing for better LTO as you can get all stuff compiled as static libraries.

For the future I'm leaning towards increasing minimum version to Qt 5.12 (sadly means LGPLv2 for Qt5.6 will be gone) to start using QStringView where possible.

https://github.com/cutelyst/cutelyst/releases/tag/v3.0.0

(redux) Qt WebAssembly performance enhancement

 In my last post Qt WebAssembly performance enhancement

there were some impressive performance stat speedups. Unfortunately, as my collegue Morten pointed out, both builds were in debug mode.  *sigh*

So I rebuilt them in release mode, and added a few selected benchmarks from the Qt tests/benchmark source directory:

  • tst_affectors
  • tst_emission
  • tst_QGraphicsScene
  • tst_QGraphicsView
  • tst_QGraphicsWidget
  • tst_qanimation
  • tst_QMatrix4x4
  • BlendBench
  • tst_QImageConversion
  • tst_DrawTexture
  • tst_QPainter

Although not as impressive overall, there is still quite a speed up in the image conversions and QPainter areas, for example:

non-simd:

PASS   : tst_QPainter::drawPixmap(BGR30 on RGB32, (1000x1000), circle)

RESULT : tst_QPainter::drawPixmap():"BGR30 on RGB32, (1000x1000), circle":

     2.3 msecs per iteration (total: 76, iterations: 32)

PASS   : tst_QPainter::drawPixmap(BGR30 on RGB32, (1000x1000), line)

RESULT : tst_QPainter::drawPixmap():"BGR30 on RGB32, (1000x1000), line":

     2.4 msecs per iteration (total: 77, iterations: 32)

PASS   : tst_QPainter::drawPixmap(BGR30 on RGB32, (1000x1000), solidrect)

RESULT : tst_QPainter::drawPixmap():"BGR30 on RGB32, (1000x1000), solidrect":

     2.4 msecs per iteration (total: 78, iterations: 32)

PASS   : tst_QPainter::drawPixmap(BGR30 on RGB32, (1000x1000), alpharect)

RESULT : tst_QPainter::drawPixmap():"BGR30 on RGB32, (1000x1000), alpharect":

     2.4 msecs per iteration (total: 78, iterations: 32)


simd:

RESULT : tst_QPainter::drawPixmap():"BGR30 on RGB32, (1000x1000), circle":

     0.95 msecs per iteration (total: 61, iterations: 64)

PASS   : tst_QPainter::drawPixmap(BGR30 on RGB32, (1000x1000), line)

RESULT : tst_QPainter::drawPixmap():"BGR30 on RGB32, (1000x1000), line":

     0.95 msecs per iteration (total: 61, iterations: 64)

PASS   : tst_QPainter::drawPixmap(BGR30 on RGB32, (1000x1000), solidrect)

RESULT : tst_QPainter::drawPixmap():"BGR30 on RGB32, (1000x1000), solidrect":

     0.92 msecs per iteration (total: 59, iterations: 64)

PASS   : tst_QPainter::drawPixmap(BGR30 on RGB32, (1000x1000), alpharect)

RESULT : tst_QPainter::drawPixmap():"BGR30 on RGB32, (1000x1000), alpharect":

     0.95 msecs per iteration (total: 61, iterations: 64


non-simd:

PASS   : tst_QPainter::drawPixmap(ARGB32_pm on RGB32, (1000x1000), circle)

RESULT : tst_QPainter::drawPixmap():"ARGB32_pm on RGB32, (1000x1000), circle":

     1.7 msecs per iteration (total: 56, iterations: 32)

PASS   : tst_QPainter::drawPixmap(ARGB32_pm on RGB32, (1000x1000), line)

RESULT : tst_QPainter::drawPixmap():"ARGB32_pm on RGB32, (1000x1000), line":

     1.7 msecs per iteration (total: 55, iterations: 32)

PASS   : tst_QPainter::drawPixmap(ARGB32_pm on RGB32, (1000x1000), solidrect)

RESULT : tst_QPainter::drawPixmap():"ARGB32_pm on RGB32, (1000x1000), solidrect":

     1.7 msecs per iteration (total: 55, iterations: 32)

PASS   : tst_QPainter::drawPixmap(ARGB32_pm on RGB32, (1000x1000), alpharect)

RESULT : tst_QPainter::drawPixmap():"ARGB32_pm on RGB32, (1000x1000), alpharect":

     3.6 msecs per iteration (total: 58, iterations: 16)


simd:

PASS   : tst_QPainter::drawPixmap(ARGB32_pm on RGB32, (1000x1000), circle)

RESULT : tst_QPainter::drawPixmap():"ARGB32_pm on RGB32, (1000x1000), circle":

     2.6 msecs per iteration (total: 85, iterations: 32)

PASS   : tst_QPainter::drawPixmap(ARGB32_pm on RGB32, (1000x1000), line)

RESULT : tst_QPainter::drawPixmap():"ARGB32_pm on RGB32, (1000x1000), line":

     4.0 msecs per iteration (total: 64, iterations: 16)

PASS   : tst_QPainter::drawPixmap(ARGB32_pm on RGB32, (1000x1000), solidrect)

RESULT : tst_QPainter::drawPixmap():"ARGB32_pm on RGB32, (1000x1000), solidrect":

     2.2 msecs per iteration (total: 71, iterations: 32)

PASS   : tst_QPainter::drawPixmap(ARGB32_pm on RGB32, (1000x1000), alpharect)

RESULT : tst_QPainter::drawPixmap():"ARGB32_pm on RGB32, (1000x1000), alpharect":

     4.5 msecs per iteration (total: 73, iterations: 16)


and image conversions:

non-simd:

PASS   : tst_QImageConversion::convertGenericInplace(argb32 -> argb32pm -> argb32)

RESULT : tst_QImageConversion::convertGenericInplace():"argb32 -> argb32pm -> argb32":

     6.1 msecs per iteration (total: 98, iterations: 16)

PASS   : tst_QImageConversion::convertGenericInplace(argb32 -> rgb32 -> argb32)

RESULT : tst_QImageConversion::convertGenericInplace():"argb32 -> rgb32 -> argb32":

     2.9 msecs per iteration (total: 94, iterations: 32)

PASS   : tst_QImageConversion::convertGenericInplace(argb32 -> rgba8888 -> argb32)

RESULT : tst_QImageConversion::convertGenericInplace():"argb32 -> rgba8888 -> argb32":

     4.6 msecs per iteration (total: 75, iterations: 16)

simd:

PASS   : tst_QImageConversion::convertGenericInplace(argb32 -> argb32pm -> argb32)

RESULT : tst_QImageConversion::convertGenericInplace():"argb32 -> argb32pm -> argb32":

     4.2 msecs per iteration (total: 68, iterations: 16)

PASS   : tst_QImageConversion::convertGenericInplace(argb32 -> rgb32 -> argb32)

RESULT : tst_QImageConversion::convertGenericInplace():"argb32 -> rgb32 -> argb32":

     0.49 msecs per iteration (total: 63, iterations: 128)

PASS   : tst_QImageConversion::convertGenericInplace(argb32 -> rgba8888 -> argb32)

RESULT : tst_QImageConversion::convertGenericInplace():"argb32 -> rgba8888 -> argb32":

     0.90 msecs per iteration (total: 58, iterations: 64)



But others were slower for the simd build. Probably due to emscripten not fully supporting simd instructions and emulating those where it doesn't support.


For full benchmark results get the zip file




Qt WebAssembly performance enhancement

SIMD is something related to performance stuff. It makes certain things go faster (simply put). Kind of like sticking laughing gas in your petrol car's fuel line.

https://en.wikipedia.org/wiki/SIMD

Emscripten, WebAssembly now have better support for SIMD (to various degrees)

https://emscripten.org/docs/porting/simd.html


Chrome and firefox also support SIMD (to various degrees)

So for Qt 6.3, I have been working to get Qt building and running using those SIMD instructions available for javascript (and thereby WebAssembly) in the web browsers (sorry, Safari.. catch up soon?)


Just configure soon to be qt 6.3 with the -sse2 argument (change has not been reviewed or merged yet)

https://codereview.qt-project.org/c/qt/qtbase/+/343563

To see if it is actually worth adding SIMD support to Qt WebAssembly, I built a couple Qt Quick benchmarks, namely the declarative particles benchmarks - affectors and emission.

I had to put image and qml files into a .qrc resource file so that Qt WebAssembly could find them, as we have no real local file system access.


The results are much better than I expected. Clearly, there is a performance boost by using simd in wasm. 

 Someone else has had similar results with wasm SIMD

https://robaboukhalil.medium.com/webassembly-and-simd-7a7daa4f2ecd

 

Next I want to expand the number and type of benchmarks, but this gives us early baseline results.

Chrome browser
no SIMD:

********* Start testing of tst_affectors *********
Config: Using QtTest library 6.2.0, Qt 6.2.0 (wasm-little_endian-ilp32 static debug build; by Clang 13.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 5852582532b3eb3ea8da51a1e272d8d017bd36c9)), unknown unknown
PASS   : tst_affectors::initTestCase()
Heap resize call from 16777216 to 20185088 took 0.09999999403953552 msecs. Success: true
Heap resize call from 20185088 to 24248320 took 0.10000000894069672 msecs. Success: true
Heap resize call from 24248320 to 29097984 took 0.10000000894069672 msecs. Success: true
Heap resize call from 29097984 to 34930688 took 0.5 msecs. Success: true
PASS   : tst_affectors::test_basic(16ms)
RESULT : tst_affectors::test_basic():"16ms":
     0.29 msecs per iteration (total: 75, iterations: 256)
Heap resize call from 34930688 to 41943040 took 0.10000000894069672 msecs. Success: true
PASS   : tst_affectors::test_basic(32ms)
RESULT : tst_affectors::test_basic():"32ms":
     0.41 msecs per iteration (total: 53, iterations: 128)
Heap resize call from 41943040 to 50331648 took 0.29999999701976776 msecs. Success: true
PASS   : tst_affectors::test_basic(100ms)
RESULT : tst_affectors::test_basic():"100ms":
     0.87 msecs per iteration (total: 56, iterations: 64)
Heap resize call from 50331648 to 60424192 took 0.3999999910593033 msecs. Success: true
PASS   : tst_affectors::test_basic(500ms)
RESULT : tst_affectors::test_basic():"500ms":
     3.3 msecs per iteration (total: 53, iterations: 16)
Heap resize call from 60424192 to 72548352 took 0.19999998807907104 msecs. Success: true
PASS   : tst_affectors::test_filtered(16ms)
RESULT : tst_affectors::test_filtered():"16ms":
     0.84 msecs per iteration (total: 54, iterations: 64)
PASS   : tst_affectors::test_filtered(32ms)
RESULT : tst_affectors::test_filtered():"32ms":
     0.96 msecs per iteration (total: 62, iterations: 64)
Heap resize call from 72548352 to 87097344 took 0.20000000298023224 msecs. Success: true
PASS   : tst_affectors::test_filtered(100ms)
RESULT : tst_affectors::test_filtered():"100ms":
     1.3 msecs per iteration (total: 89, iterations: 64)
PASS   : tst_affectors::test_filtered(500ms)
RESULT : tst_affectors::test_filtered():"500ms":
     3.7 msecs per iteration (total: 60, iterations: 16)
PASS   : tst_affectors::cleanupTestCase()
Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted, 15037ms
********* Finished testing of tst_affectors *********

********* Start testing of tst_emission *********
Config: Using QtTest library 6.2.0, Qt 6.2.0 (wasm-little_endian-ilp32 static debug build; by Clang 13.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 5852582532b3eb3ea8da51a1e272d8d017bd36c9)), unknown unknown
PASS   : tst_emission::initTestCase()
Heap resize call from 16777216 to 20185088 took 0 msecs. Success: true
Heap resize call from 20185088 to 24248320 took 0 msecs. Success: true
Heap resize call from 24248320 to 29097984 took 0 msecs. Success: true
PASS   : tst_emission::test_basic(16ms)
RESULT : tst_emission::test_basic():"16ms":
     1.6 msecs per iteration (total: 53, iterations: 32)
Heap resize call from 29097984 to 34930688 took 0.4000000059604645 msecs. Success: true
PASS   : tst_emission::test_basic(32ms)
RESULT : tst_emission::test_basic():"32ms":
     3.1 msecs per iteration (total: 51, iterations: 16)
PASS   : tst_emission::test_basic(100ms)
RESULT : tst_emission::test_basic():"100ms":
     4.5 msecs per iteration (total: 73, iterations: 16)
PASS   : tst_emission::test_basic(500ms)
RESULT : tst_emission::test_basic():"500ms":
     21 msecs per iteration (total: 87, iterations: 4)
Heap resize call from 34930688 to 41943040 took 0.09999999403953552 msecs. Success: true
PASS   : tst_emission::test_basic(1000ms)
RESULT : tst_emission::test_basic():"1000ms":
     22 msecs per iteration (total: 89, iterations: 4)
PASS   : tst_emission::test_basic(10000ms)
RESULT : tst_emission::test_basic():"10000ms":
     23 msecs per iteration (total: 92, iterations: 4)
PASS   : tst_emission::cleanupTestCase()
Totals: 8 passed, 0 failed, 0 skipped, 0 blacklisted, 5398ms
********* Finished testing of tst_emission *********

======================================================================
======================================================================

chrome SIMD


********* Start testing of tst_affectors *********
Config: Using QtTest library 6.2.0, Qt 6.2.0 (wasm-little_endian-ilp32 static debug build; by Clang 13.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 5852582532b3eb3ea8da51a1e272d8d017bd36c9)), unknown unknown
PASS   : tst_affectors::initTestCase()
Heap resize call from 16777216 to 20185088 took 0 msecs. Success: true
Heap resize call from 20185088 to 24248320 took 0 msecs. Success: true
Heap resize call from 24248320 to 29097984 took 0 msecs. Success: true
Heap resize call from 29097984 to 34930688 took 0.3999999761581421 msecs. Success: true
Heap resize call from 34930688 to 41943040 took 0.19999998807907104 msecs. Success: true
PASS   : tst_affectors::test_basic(16ms)
RESULT : tst_affectors::test_basic():"16ms":
     0.059 msecs per iteration (total: 61, iterations: 1024)
Heap resize call from 41943040 to 50331648 took 0.30000001192092896 msecs. Success: true
PASS   : tst_affectors::test_basic(32ms)
RESULT : tst_affectors::test_basic():"32ms":
     0.11 msecs per iteration (total: 59, iterations: 512)
Heap resize call from 50331648 to 60424192 took 0.30000001192092896 msecs. Success: true
PASS   : tst_affectors::test_basic(100ms)
RESULT : tst_affectors::test_basic():"100ms":
     0.15 msecs per iteration (total: 81, iterations: 512)
Heap resize call from 60424192 to 72548352 took 0.30000001192092896 msecs. Success: true
PASS   : tst_affectors::test_basic(500ms)
RESULT : tst_affectors::test_basic():"500ms":
     0.58 msecs per iteration (total: 75, iterations: 128)
Heap resize call from 72548352 to 87097344 took 0.3999999761581421 msecs. Success: true
PASS   : tst_affectors::test_filtered(16ms)
RESULT : tst_affectors::test_filtered():"16ms":
     0.10 msecs per iteration (total: 52, iterations: 512)
Heap resize call from 87097344 to 104529920 took 0.30000001192092896 msecs. Success: true
PASS   : tst_affectors::test_filtered(32ms)
RESULT : tst_affectors::test_filtered():"32ms":
     0.12 msecs per iteration (total: 64, iterations: 512)
PASS   : tst_affectors::test_filtered(100ms)
RESULT : tst_affectors::test_filtered():"100ms":
     0.19 msecs per iteration (total: 51, iterations: 256)
Heap resize call from 104529920 to 125435904 took 0.20000001788139343 msecs. Success: true
PASS   : tst_affectors::test_filtered(500ms)
RESULT : tst_affectors::test_filtered():"500ms":
     0.61 msecs per iteration (total: 79, iterations: 128)
PASS   : tst_affectors::cleanupTestCase()
Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted, 9728ms
********* Finished testing of tst_affectors *********

********* Start testing of tst_emission *********
Config: Using QtTest library 6.2.0, Qt 6.2.0 (wasm-little_endian-ilp32 static debug build; by Clang 13.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 5852582532b3eb3ea8da51a1e272d8d017bd36c9)), unknown unknown
PASS   : tst_emission::initTestCase()
Heap resize call from 16777216 to 20185088 took 0 msecs. Success: true
Heap resize call from 20185088 to 24248320 took 0 msecs. Success: true
Heap resize call from 24248320 to 29097984 took 0 msecs. Success: true
Heap resize call from 29097984 to 34930688 took 0.29999998211860657 msecs. Success: true
PASS   : tst_emission::test_basic(16ms)
RESULT : tst_emission::test_basic():"16ms":
     0.046 msecs per iteration (total: 95, iterations: 2048)
Heap resize call from 34930688 to 41943040 took 0 msecs. Success: true
PASS   : tst_emission::test_basic(32ms)
RESULT : tst_emission::test_basic():"32ms":
     0.090 msecs per iteration (total: 93, iterations: 1024)
Heap resize call from 41943040 to 50331648 took 0.29999998211860657 msecs. Success: true
PASS   : tst_emission::test_basic(100ms)
RESULT : tst_emission::test_basic():"100ms":
     0.27 msecs per iteration (total: 70, iterations: 256)
Heap resize call from 50331648 to 60424192 took 0.4000000059604645 msecs. Success: true
PASS   : tst_emission::test_basic(500ms)
RESULT : tst_emission::test_basic():"500ms":
     1.3 msecs per iteration (total: 85, iterations: 64)
Heap resize call from 60424192 to 72548352 took 0.4000000059604645 msecs. Success: true
PASS   : tst_emission::test_basic(1000ms)
RESULT : tst_emission::test_basic():"1000ms":
     1.3 msecs per iteration (total: 87, iterations: 64)
PASS   : tst_emission::test_basic(10000ms)
RESULT : tst_emission::test_basic():"10000ms":
     1.3 msecs per iteration (total: 86, iterations: 64)
PASS   : tst_emission::cleanupTestCase()
Totals: 8 passed, 0 failed, 0 skipped, 0 blacklisted, 6017ms
********* Finished testing of tst_emission *********


Firefox has similar results:


firefox nosimd:

    ********* Start testing of tst_affectors *********
    Config: Using QtTest library 6.2.0, Qt 6.2.0 (wasm-little_endian-ilp32 static debug build; by Clang 13.0.0 (/opt/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 5f3c99085d4c2ebf57fd0586b013b02e32a8e20b)), unknown unknown
    PASS   : tst_affectors::initTestCase()
    Heap resize call from 16777216 to 20185088 took 0 msecs. Success: true qtloader.js line 443 > eval:11829:17
    Heap resize call from 20185088 to 24248320 took 0 msecs. Success: true qtloader.js line 443 > eval:11829:17
    Heap resize call from 24248320 to 29097984 took 0 msecs. Success: true qtloader.js line 443 > eval:11829:17
    PASS   : tst_affectors::test_basic(16ms)
    RESULT : tst_affectors::test_basic():"16ms":
         0.81 msecs per iteration (total: 52, iterations: 64)
    PASS   : tst_affectors::test_basic(32ms)
    RESULT : tst_affectors::test_basic():"32ms":
         1.1 msecs per iteration (total: 76, iterations: 64)
    PASS   : tst_affectors::test_basic(100ms)
    RESULT : tst_affectors::test_basic():"100ms":
         2.4 msecs per iteration (total: 79, iterations: 32)
    Heap resize call from 29097984 to 34930688 took 17 msecs. Success: true qtloader.js line 443 > eval:11829:17
    PASS   : tst_affectors::test_basic(500ms)
    RESULT : tst_affectors::test_basic():"500ms":
         9.1 msecs per iteration (total: 73, iterations: 8)
    PASS   : tst_affectors::test_filtered(16ms)
    RESULT : tst_affectors::test_filtered():"16ms":
         2.3 msecs per iteration (total: 74, iterations: 32)
    PASS   : tst_affectors::test_filtered(32ms)
    RESULT : tst_affectors::test_filtered():"32ms":
         2.6 msecs per iteration (total: 86, iterations: 32)
    PASS   : tst_affectors::test_filtered(100ms)
    RESULT : tst_affectors::test_filtered():"100ms":
         3.8 msecs per iteration (total: 62, iterations: 16)
    Heap resize call from 34930688 to 41943040 took 0 msecs. Success: true qtloader.js line 443 > eval:11829:17
    PASS   : tst_affectors::test_filtered(500ms)
    RESULT : tst_affectors::test_filtered():"500ms":
         11 msecs per iteration (total: 88, iterations: 8)
    PASS   : tst_affectors::cleanupTestCase()
    Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted, 16781ms
    ********* Finished testing of tst_affectors *********    
        
     ********* Start testing of tst_emission *********
     Config: Using QtTest library 6.2.0, Qt 6.2.0 (wasm-little_endian-ilp32 static debug build; by Clang 13.0.0 (/opt/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 5f3c99085d4c2ebf57fd0586b013b02e32a8e20b)), unknown unknown
     PASS   : tst_emission::initTestCase()
     Heap resize call from 16777216 to 20185088 took 0 msecs. Success: true qtloader.js line 443 > eval:11829:17
     Heap resize call from 20185088 to 24248320 took 0 msecs. Success: true qtloader.js line 443 > eval:11829:17
     Heap resize call from 24248320 to 29097984 took 0 msecs. Success: true qtloader.js line 443 > eval:11829:17
     PASS   : tst_emission::test_basic(16ms)
     RESULT : tst_emission::test_basic():"16ms":
          2.1 msecs per iteration (total: 70, iterations: 32)
     PASS   : tst_emission::test_basic(32ms)
     RESULT : tst_emission::test_basic():"32ms":
          4.1 msecs per iteration (total: 67, iterations: 16)
     PASS   : tst_emission::test_basic(100ms)
     RESULT : tst_emission::test_basic():"100ms":
          8.1 msecs per iteration (total: 65, iterations: 8)
     PASS   : tst_emission::test_basic(500ms)
     RESULT : tst_emission::test_basic():"500ms":
          43 msecs per iteration (total: 87, iterations: 2)
     PASS   : tst_emission::test_basic(1000ms)
     RESULT : tst_emission::test_basic():"1000ms":
          43 msecs per iteration (total: 86, iterations: 2)
     PASS   : tst_emission::test_basic(10000ms)
     RESULT : tst_emission::test_basic():"10000ms":
          43 msecs per iteration (total: 86, iterations: 2)
     PASS   : tst_emission::cleanupTestCase()
     Totals: 8 passed, 0 failed, 0 skipped, 0 blacklisted, 4178ms
     ********* Finished testing of tst_emission *********
                
======================================================================
======================================================================
                
                                         
firefox SIMD:


    ********* Start testing of tst_affectors *********
    Config: Using QtTest library 6.2.0, Qt 6.2.0 (wasm-little_endian-ilp32 static debug build; by Clang 13.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 5852582532b3eb3ea8da51a1e272d8d017bd36c9)), unknown unknown
    PASS   : tst_affectors::initTestCase()
    Heap resize call from 16777216 to 20185088 took 0 msecs. Success: true
    Heap resize call from 20185088 to 24248320 took 0 msecs. Success: true
    Heap resize call from 24248320 to 29097984 took 0 msecs. Success: true
    Heap resize call from 29097984 to 34930688 took 0 msecs. Success: true
    PASS   : tst_affectors::test_basic(16ms)
    RESULT : tst_affectors::test_basic():"16ms":
         0.14 msecs per iteration (total: 73, iterations: 512)
    Heap resize call from 34930688 to 41943040 took 0 msecs. Success: true
    Heap resize call from 41943040 to 50331648 took 1 msecs. Success: true
    PASS   : tst_affectors::test_basic(32ms)
    RESULT : tst_affectors::test_basic():"32ms":
         0.21 msecs per iteration (total: 54, iterations: 256)
    Heap resize call from 50331648 to 60424192 took 5 msecs. Success: true
    PASS   : tst_affectors::test_basic(100ms)
    RESULT : tst_affectors::test_basic():"100ms":
         0.45 msecs per iteration (total: 58, iterations: 128)
    PASS   : tst_affectors::test_basic(500ms)
    RESULT : tst_affectors::test_basic():"500ms":
         1.8 msecs per iteration (total: 60, iterations: 32)
    Heap resize call from 60424192 to 72548352 took 7 msecs. Success: true
    PASS   : tst_affectors::test_filtered(16ms)
    RESULT : tst_affectors::test_filtered():"16ms":
         0.28 msecs per iteration (total: 73, iterations: 256)
    Heap resize call from 72548352 to 87097344 took 0 msecs. Success: true
    PASS   : tst_affectors::test_filtered(32ms)
    RESULT : tst_affectors::test_filtered():"32ms":
         0.35 msecs per iteration (total: 90, iterations: 256)
    PASS   : tst_affectors::test_filtered(100ms)
    RESULT : tst_affectors::test_filtered():"100ms":
         0.60 msecs per iteration (total: 77, iterations: 128)
    Heap resize call from 87097344 to 104529920 took 0 msecs. Success: true
    PASS   : tst_affectors::test_filtered(500ms)
    RESULT : tst_affectors::test_filtered():"500ms":
         2.0 msecs per iteration (total: 66, iterations: 32)
    PASS   : tst_affectors::cleanupTestCase()
    Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted, 6411ms
    ********* Finished testing of tst_affectors *********
                                                
    ********* Start testing of tst_emission *********
    Config: Using QtTest library 6.2.0, Qt 6.2.0 (wasm-little_endian-ilp32 static debug build; by Clang 13.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 5852582532b3eb3ea8da51a1e272d8d017bd36c9)), unknown unknown
    PASS   : tst_emission::initTestCase()
    Heap resize call from 16777216 to 20185088 took 0 msecs. Success: true
    Heap resize call from 20185088 to 24248320 took 0 msecs. Success: true
    Heap resize call from 24248320 to 29097984 took 0 msecs. Success: true
    Heap resize call from 29097984 to 34930688 took 0 msecs. Success: true
    PASS   : tst_emission::test_basic(16ms)
    RESULT : tst_emission::test_basic():"16ms":
         0.12 msecs per iteration (total: 65, iterations: 512)
    PASS   : tst_emission::test_basic(32ms)
    RESULT : tst_emission::test_basic():"32ms":
         0.24 msecs per iteration (total: 63, iterations: 256)
    Heap resize call from 34930688 to 41943040 took 0 msecs. Success: true
    PASS   : tst_emission::test_basic(100ms)
    RESULT : tst_emission::test_basic():"100ms":
         0.75 msecs per iteration (total: 97, iterations: 128)
    Heap resize call from 41943040 to 50331648 took 1 msecs. Success: true
    PASS   : tst_emission::test_basic(500ms)
    RESULT : tst_emission::test_basic():"500ms":
         3.6 msecs per iteration (total: 58, iterations: 16)
    Heap resize call from 50331648 to 60424192 took 4 msecs. Success: true
    PASS   : tst_emission::test_basic(1000ms)
    RESULT : tst_emission::test_basic():"1000ms":
         3.6 msecs per iteration (total: 58, iterations: 16)
    PASS   : tst_emission::test_basic(10000ms)
    RESULT : tst_emission::test_basic():"10000ms":
         3.6 msecs per iteration (total: 58, iterations: 16)
    PASS   : tst_emission::cleanupTestCase()
    Totals: 8 passed, 0 failed, 0 skipped, 0 blacklisted, 3339ms
    ********* Finished testing of tst_emission *********



New in Qt 6.1: std::hash Support for QHash

In the previous blog post of this series, we discussed KDToolBox::QtHasher, a helper class that allows us to use unordered associative containers datatypes which have a qHash() overload but not a std::hash specialization. The majority of Qt value classes indeed are lacking such a specialization, even if they do provide a qHash() overload.

For our datatypes, having to provide both qHash and std::hash (if you want to store them in either QHash/QSet or std::unordered_map/set without the need of a customer hasher) is…mildly annoying. In a project, I’ve even resorted to using a macro to implement one function in terms of the other one.

This consideration led me to ask myself, “why can’t QHash itself support std::hash directly?” This would allow me to implement just one hashing function, and not two. Cherry on top: it would allow us to use QHash/QSet datatypes that only offer std::hash and not qHash, such as the ones coming from the Standard Library! (You may want to read here about why it’s actually impossible to reliably add a qHash overload for them.)

A quick patch later, and now, finally, Qt also supports the usage of std::hash as a hashing function. For a given datatype, Qt will now implement a “fallback” mechanism — it’s going to call the first callable option between:

  1. qHash(T, seed)
  2. qHash(T)
  3. std::hash()(t, seed)
  4. std::hash()(t)

Using the third option with a defaulted seed argument allows Qt to still provide a seed to your hashing function, and your type to still be usable in a Standard Library associative container:

class MyClass {
// ...
};

namespace std {
template <> struct hash<MyClass>
{
    size_t operator()(const MyClass &c, size_t seed = std::hash<int>{}(0)) const noexcept {
       // ~~~
    }
}:
} // std

QHash<MyClass, Data> hash;               // OK
std::unordered_map<MyClass, Data> hash2; // OK

The support for std::hash in Qt’s containers comes with a minor limitation: you can’t use a custom hashing object, like you can in containers from the Standard Library. Qt is going to default construct your std::hash specialization. Honestly, I’ve practically never seen a specialization that isn’t trivially default constructible anyhow.

The end of qHash?

If the Qt containers support std::hash, what does it mean for qHash? Well, the support for qHash inside Qt’s associative containers is likely to never go away — this is part of the strong source compatibility promise of Qt. (If I had to imagine, qHash may be deprecated in favour of std::hash in Qt 7, and removed in Qt 8. But this is 100% speculation, at this point in time…). But even offering qHash overloads for Qt datatypes is part of the public API of Qt, which means we can’t just remove those overloads. For instance, they are used all over the place in the implementation of qHash for end-user datatypes:

size_t qHash(const MyType &t, size_t seed = 0) noexcept
{
  return qHash(t.key, seed); // <-- qHash used as public API
}

Can Qt easily offer std::hash specializations for its own types?

Not so easily, I’m afraid — it would require a massive effort to complement each and every qHash overload with a corresponding std::hash implementation (or outright replace those overloads, if you think qHash should go away). I just don’t see this as being done anytime soon, and that’s why a solution like KDToolBox::QtHasher still makes sense, for the time being.

A similar effort would also be needed if we wanted to offer a constrained version of std::hash. We would need a way to “tag” each Qt datatype (by using some type trait). Then, Qt could offer a centralized:

// Assume we have a magic IsQtClass<T> trait
// that tells us if T is a class that belongs to Qt

template <typename T>
concept QtHashableClass = requires(const T &t) {
    IsQtClass<T>;
    { qHash(t, std::declval<size_t>()) } -> std::same_as<size_t>;
};

namespace std
{
template <typename T> 
requires QtHashableClass<T>
struct hash<T>
{
    size_t operator()(const T &t, size_t seed = std::hash<int>{}(0)) const noexcept
    {
        return qHash(t, seed);
    }
};
} // std

std::unordered_set< /* Any Qt datatype here */ > set; // just works

Did you notice that I’m using constraints and concepts (C++20) here? Such a solution is also unfeasible in C++17, because std::hash does not offer us a “SFINAE hook.” But that’s a discussion for another time…

Thanks for reading!

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post New in Qt 6.1: std::hash Support for QHash appeared first on KDAB.

QML 3D: First impressions

If we think about it for a minute, it is impossible to imagine an app without a UI. We need buttons, checkboxes, dropdown menus and much more. Almost all of these controls are created using 2D elements. However, sometimes we need to put some depth into the UI.

During work on many different apps, I always „thought” only in 2 dimensions and it turned out to be kind of a trap. When you think about it there are plenty of cases when you need one extra dimension in your app. Let’s take the software for a 3D printer as an example. How can rendering a preview of a model to print be implemented only in two dimensions? This is why I took off in search of a solution that will allow me to create 3D elements in my applications using already known technologies such as QML. In ScytheStudio we can easily deepen our knowledge in the area that interests us, what is more, we gain not only time for development but also resources.

Fortunately, there is such thing as Qt Quick 3D, which provides us with an easy high-level API for creating elements in 3D using familiar QML language. It also covers areas like animations, cameras, lightning or materials. The goal of QtQuick 3D is to provide tools for programmers without in-depth knowledge of complex computer graphics.

A solution that doesn’t flood you with complicated mathematical formulas right at the start and fits my technology stack perfectly caught my attention right from the bat. My name is Vins and in this post, I will share, with you, my first impressions on exploring the topic of QML 3D.

Let’s start our journey!

I eagerly started by going through the documentation and watching video tutorials. After a short introduction to the world of Qt Quick 3D, I decided to start a simple project. The first thing I had to do was downloading the Qt3D module through the maintenance tool, as it is not included in the core module. After that, you just need to add an import statement in the qml file and voilà, you can start using Qt Quick 3D!

Below you can see my first program created using Qt Quick 3D. The lack of fluidity is due to the necessary compression of the GIF file.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick3D 1.15
import QtQuick3D.Materials 1.15
import QtQuick3D.Helpers 1.15

 

Window {
  id: root

  width: 640
  height: 960
  visible: true
  title: qsTr("QML 3D")

  View3D {
    id: view
    anchors.fill: parent

    environment: SceneEnvironment {
      clearColor: "#112220"
      backgroundMode: SceneEnvironment.Color
    }

    Node {
      id: scene

      PerspectiveCamera {
        id: camera
        z: 300
      }

      DirectionalLight {
        z: 400
        brightness: 100
      }

      Model {
        source: "#Cube"

        materials: DefaultMaterial {
          diffuseColor: "green"
        }

        PropertyAnimation on eulerRotation.x {
          loops: Animation.Infinite
          duration: 5000
          to: 360
          from: 0
        }

        PropertyAnimation on eulerRotation.y {
          loops: Animation.Infinite
          duration: 5000
          to: 360
          from: 0
        }
      }
    }
  }
}

It may not look spectacular, but for 30 minutes of work, I’m proud of the results 😉

At the beginning of the project, you can see the initialization of a standard Window item. Next in the hierarchy is the View3D object. This is, so to speak, the root object for all 3D elements. I replaced the default environment attribute with a custom SceneEnviorment, with a custom background colour.

Next, you can see an object of the Node type, i.e. a class from which all Qt Quick 3D elements derive. In its body, I placed 3 elements: PerspectiveCamera, DirectionalLight and Model. Let’s analyse them one by one:

PerspectiveCamera provides us with a „view” of the entire scene. It is an eye through which we observe the scene.

DirectionalLight illuminates our whole scene. It is important not to forget about this element because without light most objects will be black (humans perceive colours because the light that bounces off materials is making us see colours).

Model is a physical object that we create. In this example, I used the built-in cube object and the default material which I changed to green. In order to make the cube move, I added two animations that change the rotation of the cube along the X and Y-axis.

Blender + Qt Quick 3D = Great Combo!

As we covered the basics, it’s time for something more difficult. Reading the documentation I found out that Qt Quick 3D supports using models created in applications like Blender, Maya or 3ds max. To import such a model, we need to convert them to QML code first. To do that we will use the built-in Balsam Asset Import Tool (It is located in the path: Qt/Tools/QtDesignStudio-XXX/qt5_design_studio_reduced_version where XXX is your actual QtDesignStudio version code). To demonstrate how this works, I created a simple „house” model in Blender.

Blender is a free solution with which anyone can become an architect

As we now have a model we can use the previously mentioned Balsam. After running it we get a .qml file that is able to be used in our program. A command for running it looks as follows:

balsam [options] source_file_name

 

Our „house” after conversion to QML

Time for a challenge!

I decided it was time to gather all the knowledge and create something more interesting.

The first thing was to add a sphere object to imitate the sun. To do this I used a Model object with source set to #Sphere and yellow colour. Inside it, I added the PointLight item which was responsible for the sun rays.

Next, I added a side menu allowing me to rotate the „house” on all axes, move the sun model, change the strength of its light, and two switches allowing me to turn off the scene light and the sun.

In order to add more interactivity to the application, I used the WasdController allowing me to move around the scene using the keys on my keyboard.


Home, sweet home

 

Summary and my thoughts

I must admit I was very positively surprised by Qt Quick 3D. As a person without any experience in 3D graphics, I was able to understand its mechanics and use it for my needs. The extensive documentation was helpful here, including examples and sample codes.

Moreover, the set-up itself was straightforward. All you had to do was download the appropriate library using the Qt maintenance tool and use it in your project. Everything you need in one place. There was no need to reconfigure the whole project or complicated attaching of files.

The thing that captivated me most was the simplicity. The high level of abstraction makes it easy to combine 2D interface elements with 3D objects without having to change my way of thinking.

Of course, the examples presented in this article might not be very ambitious, but they are simple examples that allow feeling satisfied. I also encourage you to try your hand with Qt Quick 3D package. You will be surprised how easy and fun it is.

If you would like to read a similar article – feel free to email us with your ideas. Who knows, maybe your idea will inspire us to create another article? Take care and stay tuned 😉

Call for Papers – Qt Developer Conference

The Call for Papers for KDAB’s upcoming event, Qt DevCon, is now open. We are planning to make this our first in-person event after the shut-down. The event will take place September 28-30, featuring 1 training day and 2 days of technical talks from developers, for developers.

Call for Papers. What are we looking for?

Qt DevCon is primarily a technical event and we want our content to be as relevant and interesting as possible to our audience. The target audience is developers building software with Qt.

Topics for Talks

Proposals related to any aspect of development with Qt are welcome, as long as they:

  • bring practical knowledge for the attendees
  • discuss the unique technical challenges of creating Qt applications
  • are about Qt and its ecosystem

This conference aims to help the attendees become even better Qt programmers. Talks about general aspects of programming may be off-topic for this conference (as interesting as they may be). For instance, a talk like “C# for Qt developers” is off-topic; a talk like “Here’s what Qt should steal from WForms” could be accepted.

Here are some ideas:

  • Widgets vs Qt Quick
  • Architect your Qt application for multithreading
  • How to write more robust QML code
  • Using Python with Qt
  • Issues with running Qt Webassembly
  • QGraphicsView is still my best tool for…
  • In-depth technical discussion on Qt features
  • Cross-platform development using Qt
  • Creating mobile applications with Qt
  • Automated testing strategies
  • Practical experiences using Qt on real-world applications

These are just suggestions – we welcome any submission!

Proposal

The likelihood of acceptance of your proposal depends on the quality of your abstract.

While we don’t need to see your entire presentation in advance, your abstract should still be detailed enough to enable the committee members to assess your talk on its merits. Therefore, your abstract should help the committee members understand what your talk will be about, what specific topics will be discussed, which limitations the proposed approach has (if applicable; e.g., is it platform-specific?), and so on. If the talk was already presented in the past, please mention so, and possibly discuss what is going to be different since the last time it was presented.

Especially important are the takeaways from your talk. What will the audience have learned? How will it improve the attendees’ everyday life as software developers using Qt?

As the goal of the conference is to improve the technical skills of the participants, your talk should help us achieve that. A talk should not be a product ad.

Format

Your slot is 60 min, ~45 min talk + 15 min Q&A. Please rehearse your speech to be sure it fits the time frame. Each presentation will be live-streamed, recorded, and redistributed on multiple video hosting platforms. Successful applicants will be contacted for general info and asked to send a photograph and bio.

All talks will be presented live, on-site in Berlin, as we expect travel and in-person attendance to be safe under recommended safety measures by that time.

Requirements

We will assist you with the technical setup; you only need your laptop.

You can enter proposals until 2021-06-30 00:00 (Europe/Stockholm)

Links

More information on the event can be found here and here.

Talks can be submitted here.

 

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Call for Papers – Qt Developer Conference appeared first on KDAB.

PySide tutorial now available — Complete course, updated for PySide2 & PySide6

Hello! With the release of Qt6 versions of PyQt and PySide the course was getting a little crowded. So, today I've split the PySide tutorials into their own standalone PySide course.

The tutorials have all been updated for PySide2 & PySide6, with some additional improvements based on the latest editions of the book.

This first update includes the following PySide tutorials --

Getting started creating Python GUIs with PySide

Using Qt Designer with PySide

Extended UI features in PySide

Multi threading PySide applications & QProcess

Qt Model Views

Pyside plotting & graphics with Matplotlib/PyQtGraph

Bitmap graphics and custom widgets

Packaging (PySide2 only)

That's all for now!

You can access the course overview here.

Creating your first app with PySide — A simple Hello World! application with Python and Qt

PySide, also known as Qt for Python, is a Python library for creating GUI applications using the Qt toolkit. PySide is the official binding for Qt on Python and is now developed by The Qt Company itself.

There are two major versions available: PySide2 based on Qt5 and PySide6 based on Qt6. Both versions are almost completely compatible aside from imports, and lack of support for some advanced modules in Qt6.

In this tutorial we'll learn how to use PySide to create desktop applications with Python.

First we'll create a series of simple windows on your desktop to ensure that PySide is working and introduce some of the basic concepts. Then we'll take a brief look at the event loop and how it relates to GUI programming in Python. Finally we'll look at Qt's QMainWindow which offers some useful common interface elements such as toolbars and menus. These will be explored in more detail in the subsequent tutorials.

The entire PySide tutorial features both PySide2 and PySide6 code examples. You can use whichever you prefer.

Creating an application

Let's create our first application! To start create a new Python file — you can call it whatever you like (e.g. app.py) and save it somewhere accessible. We'll write our simple app in this file.

We'll be editing within this file as we go along, and you may want to come back to earlier versions of your code, so remember to keep regular backups.

The source code for the application is shown below. Type it in verbatim, and be careful not to make mistakes. If you do mess up, Python will let you know what's wrong.

python
from PySide2.QtWidgets import QApplication, QWidget

# Only needed for access to command line arguments
import sys

# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.
# If you know you won't use command line arguments QApplication([]) works too.
app = QApplication(sys.argv)

# Create a Qt widget, which will be our window.
window = QWidget()
window.show()  # IMPORTANT!!!!! Windows are hidden by default.

# Start the event loop.
app.exec_()


# Your application won't reach here until you exit and the event
# loop has stopped.

python
from PySide6.QtWidgets import QApplication, QWidget

# Only needed for access to command line arguments
import sys

# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.
# If you know you won't use command line arguments QApplication([]) works too.
app = QApplication(sys.argv)

# Create a Qt widget, which will be our window.
window = QWidget()
window.show()  # IMPORTANT!!!!! Windows are hidden by default.

# Start the event loop.
app.exec_()

# Your application won't reach here until you exit and the event
# loop has stopped.

First, launch your application. You can run it from the command line like any other Python script, for example --

bash
python3 app.py

Run it! You will now see your window. Qt automatically creates a window with the normal window decorations and you can drag it around and resize it like any window.

What you'll see will depend on what platform you're running this example on. The image below shows the window as displayed on Windows, macOS and Linux (Ubuntu).

Our window, as seen on Windows, macOS and Linux. Our window, as seen on Windows, macOS and Linux.

Stepping through the code

Let's step through the code line by line, so we understand exactly what is happening.

First, we import the PySide classes that we need for the application. Here we're importing QApplication, the application handler and QWidget, a basic empty GUI widget, both from the QtWidgets module.

python
from PySide2.QtWidgets import QApplication, QWidget

python
from PySide6.QtWidgets import QApplication, QWidget

The main modules for Qt are QtWidgets, QtGui and QtCore.

You could do from <module> import * but this kind of global import is generally frowned upon in Python, so we'll avoid it here.

Next we create an instance of QApplication, passing in sys.arg, which is Python list containing the command line arguments passed to the application.

python
app = QApplication(sys.argv)

If you know you won't be using command line arguments to control Qt you can pass in an empty list instead, e.g.

python
app = QApplication([])

Next we create an instance of a QWidget using the variable name window.

python
window = QWidget()
window.show()

In Qt all top level widgets are windows -- that is, they don't have a parent and are not nested within another widget or layout. This means you can technically create a window using any widget you like.

Widgets without a parent are invisible by default. So, after creating the window object, we must always call .show() to make it visible. You can remove the .show() and run the app, but you'll have no way to quit it!

What is a window? - Holds the user-interface of your application - Every application needs at least one (...but can have more) - Application will (by default) exit when last window is closed

Finally, we call app.exec_() to start up the event loop.

What's the event loop?

Before getting the window on the screen, there are a few key concepts to introduce about how applications are organised in the Qt world. If you're already familiar with event loops you can safely skip to the next section.

The core of every Qt Applications is the QApplication class. Every application needs one — and only one — QApplication object to function. This object holds the event loop of your application — the core loop which governs all user interaction with the GUI.

The event loop in Qt.

Each interaction with your application — whether a press of a key, click of a mouse, or mouse movement — generates an event which is placed on the event queue. In the event loop, the queue is checked on each iteration and if a waiting event is found, the event and control is passed to the specific event handler for the event. The event handler deals with the event, then passes control back to the event loop to wait for more events. There is only one running event loop per application.

The QApplication class - QApplication holds the Qt event loop - One QApplication instance required - You application sits waiting in the event loop until an action is taken - There is only one event loop at any time

The underscore is there because exec was a reserved word in Python 2.7. PySide handles this by appending an underscore to the name used in the Qt library. You'll also see .print_() methods on widgets for example.

QMainWindow

As we discovered in the last part, in Qt any widgets can be windows. For example, if you replace QtWidget with QPushButton. In the example below, you would get a window with a single push-able button in it.

python
import sys
from PySide2.QtWidgets import QApplication, QPushButton

app = QApplication(sys.argv)

window = QPushButton("Push Me")
window.show()

app.exec_()

python
import sys
from PySide6.QtWidgets import QApplication, QPushButton

app = QApplication(sys.argv)

window = QPushButton("Push Me")
window.show()

app.exec_()


This is neat, but not really very useful -- it's rare that you need a UI that consists of only a single control! But, as we'll discover later, the ability to nest widgets within other widgets using layouts means you can construct complex UIs inside an empty QWidget.

But, Qt already has a solution for you -- the QMainWindow. This is a pre-made widget which provides a lot of standard window features you'll make use of in your apps, including toolbars, menus, a statusbar, dockable widgets and more. We'll look at these advanced features later, but for now, we'll add a simple empty QMainWindow to our application.

python
import sys
from PySide2.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)

window = QMainWindow()
window.show()

# Start the event loop.
app.exec_()


python
import sys
from PySide6.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)

window = QMainWindow()
window.show()

# Start the event loop.
app.exec_()


Run it! You will now see your main window. It looks exactly the same as before!

So our QMainWindow isn't very interesting at the moment. We can fix that by adding some content. If you want to create a custom window, the best approach is to subclass QMainWindow and then include the setup for the window in the __init__ block. This allows the window behavior to be self contained. We can add our own subclass of QMainWindow — call it MainWindow to keep things simple.

python
import sys

from PySide2.QtCore import QSize, Qt
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        # Set the central widget of the Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()

python
import sys

from PySide2.QtCore import QSize, Qt
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        # Set the central widget of the Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()


For this demo we're using a QPushButton. The core Qt widgets are always imported from the QtWidgets namespace, as are the QMainWindow and QApplication classes. When using QMainWindow we use .setCentralWidget to place a widget (here a QPushButton) in the QMainWindow -- by default it takes the whole of the window. We'll look at how to add multiple widgets to windows in the layouts tutorial.

When you subclass a Qt class you must always call the super __init__ function to allow Qt to set up the object.

In our __init__ block we first use .setWindowTitle() to change the title of our main window. Then we add our first widget — a QPushButton — to the middle of the window. This is one of the basic widgets available in Qt. When creating the button you can pass in the text that you want the button to display.

Finally, we call .setCentralWidget() on the window. This is a QMainWindow specific function that allows you to set the widget that goes in the middle of the window.

Run it! You will now see your window again, but this time with the QPushButton widget in the middle. Pressing the button will do nothing, we'll sort that next.

Our QMainWindow with a single QPushButton on Windows, macOS and Linux. Our QMainWindow with a single QPushButton on Windows, macOS and Linux.

We'll cover more widgets in detail shortly but if you're impatient and would like to jump ahead you can take a look at the http://doc.qt.io/qt-5/widget-classes.html#basic-widget-classes[QWidget documentation]. Try adding the different widgets to your window!

Sizing windows and widgets

The window is currently freely resizable -- if you grab any corner with your mouse you can drag and resize it to any size you want. While it's good to let your users resize your applications, sometimes you may want to place restrictions on minimum or maximum sizes, or lock a window to a fixed size.

In Qt sizes are defined using a QSize object. This accepts width and height parameters in that order. For example, the following will create a fixed size window of 400x300 pixels.

python
import sys

from PySide2.QtCore import QSize, Qt
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        self.setFixedSize(QSize(400, 300))

        # Set the central widget of the Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()


python
import sys

from PySide6.QtCore import QSize, Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        self.setFixedSize(QSize(400, 300))

        # Set the central widget of the Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()


Run it! You will see a fixed size window -- try and resize it, it won't work.

Our fixed-size window, notice that the _maximize_ control is disabled on Windows & Linux. On macOS you _can_ maximize the app to fill the screen, but the central widget will not resize. Our fixed-size window, notice that the _maximize control is disabled on Windows & Linux. On macOS you can maximize the app to fill the screen, but the central widget will not resize._

As well as .setFixedSize() you can also call .setMinimumSize() and .setMaximumSize() to set the minimum and maximum sizes respectively. Experiment with this yourself!

You can use these size methods on any widget.

In this section we've covered the QApplication class, the QMainWindow class, the event loop and experimented with adding a simple widget to a window. In the next section we'll take a look at the mechanisms Qt provides for widgets and windows to communicate with one another and your own code.