mirror of
https://github.com/mappu/miqt.git
synced 2025-01-06 15:48:37 +00:00
commit
12618a309c
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,6 +20,7 @@ cmd/genbindings/genbindings
|
||||
cmd/miqt-uic/miqt-uic
|
||||
cmd/miqt-rcc/miqt-rcc
|
||||
|
||||
examples/goroutine6/goroutine6
|
||||
examples/helloworld/helloworld
|
||||
examples/helloworld6/helloworld6
|
||||
examples/mdoutliner/mdoutliner
|
||||
|
@ -94,6 +94,8 @@ Qt class inherited types are projected as a Go embedded struct. For example, to
|
||||
|
||||
The Go runtime migrates goroutines between OS threads, but Qt expects fixed OS threads to be used for each QObject. When you first call `qt.NewQApplication` in MIQT, that will be considered the [Qt main thread](https://doc.qt.io/qt-6/thread-basics.html#gui-thread-and-worker-thread) and will automatically signal the Go runtime to bind to a fixed OS thread using `runtime.LockOSThread()`.
|
||||
|
||||
- When accessing Qt objects from inside another goroutine, it's safest to use `(qt6/mainthread).Wait()` to access the Qt objects from Qt's main thread.
|
||||
|
||||
Some C++ idioms that were difficult to project were omitted from the binding. But, this can be improved in the future.
|
||||
|
||||
### Q6. Can I use Qt Designer and the Qt Resource system?
|
||||
|
BIN
examples/goroutine6/goroutine6.png
Normal file
BIN
examples/goroutine6/goroutine6.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
57
examples/goroutine6/main.go
Normal file
57
examples/goroutine6/main.go
Normal file
@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
qt "github.com/mappu/miqt/qt6"
|
||||
"github.com/mappu/miqt/qt6/mainthread"
|
||||
)
|
||||
|
||||
func main() {
|
||||
threadcount := runtime.GOMAXPROCS(0)
|
||||
|
||||
qt.NewQApplication(os.Args)
|
||||
|
||||
window := qt.NewQMainWindow2()
|
||||
window.QWidget.SetFixedSize2(250, 50*(threadcount+1))
|
||||
window.QWidget.SetWindowTitle("goroutine Example")
|
||||
|
||||
widget := qt.NewQWidget(window.QWidget)
|
||||
var layout = qt.NewQVBoxLayout2()
|
||||
widget.SetLayout(layout.QLayout)
|
||||
window.SetCentralWidget(widget)
|
||||
|
||||
labels := make([]*qt.QLabel, threadcount)
|
||||
for i := range labels {
|
||||
label := qt.NewQLabel(window.QWidget)
|
||||
label.SetAlignment(qt.AlignCenter)
|
||||
widget.Layout().AddWidget(label.QWidget)
|
||||
labels[i] = label
|
||||
}
|
||||
|
||||
button := qt.NewQPushButton5("start!", window.QWidget)
|
||||
button.OnClicked1(func(bool) {
|
||||
button.SetDisabled(true)
|
||||
for i, label := range labels {
|
||||
go func(index int, qlabel *qt.QLabel) {
|
||||
var tick int
|
||||
for range time.NewTicker(time.Duration((index+1)*25) * time.Millisecond).C {
|
||||
tick++
|
||||
// time.Sleep(50 * time.Millisecond)
|
||||
|
||||
mainthread.Wait(func() {
|
||||
qlabel.SetText(fmt.Sprintf("%v %v", tick, time.Now().UTC().Format("15:04:05.0000")))
|
||||
})
|
||||
}
|
||||
}(i, label)
|
||||
}
|
||||
})
|
||||
widget.Layout().AddWidget(button.QWidget)
|
||||
|
||||
window.Show()
|
||||
|
||||
qt.QApplication_Exec()
|
||||
}
|
13
qt6/mainthread/mainthread.cpp
Normal file
13
qt6/mainthread/mainthread.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
#include <QMetaObject>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#ifndef _Bool
|
||||
#define _Bool bool
|
||||
#endif
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void mainthread_exec(intptr_t cb) {
|
||||
QMetaObject::invokeMethod(qApp, [=]{
|
||||
mainthread_exec_handle(cb);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
69
qt6/mainthread/mainthread.go
Normal file
69
qt6/mainthread/mainthread.go
Normal file
@ -0,0 +1,69 @@
|
||||
package mainthread
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"runtime/cgo"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: Qt6Core
|
||||
|
||||
#include "mainthread.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Start runs the callback in the main Qt thread. You should use this whenever
|
||||
// accessing the main Qt GUI from inside a goroutine.
|
||||
// This function is non-blocking.
|
||||
func Start(gofunc func()) {
|
||||
h := cgo.NewHandle(gofunc)
|
||||
|
||||
C.mainthread_exec(C.intptr_t(h))
|
||||
}
|
||||
|
||||
// Wait runs the callback in the main Qt thread. You should use this whenever
|
||||
// accessing the main Qt GUI from inside a goroutine.
|
||||
// The call blocks until the callback is executed in the main thread's eventloop.
|
||||
func Wait(gofunc func()) {
|
||||
// It's possible to use Qt::BlockingQueuedConnection to implement the
|
||||
// blocking, but it has a deadlock risk
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
outerfunc := func() {
|
||||
gofunc()
|
||||
wg.Done()
|
||||
}
|
||||
Start(outerfunc)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Wait2[T any](gofunc func() T) (ret T) {
|
||||
outerfunc := func() {
|
||||
ret = gofunc()
|
||||
}
|
||||
Wait(outerfunc)
|
||||
return ret
|
||||
}
|
||||
|
||||
func Wait3[T any](gofunc func() (T, error)) (ret T, err error) {
|
||||
outerfunc := func() {
|
||||
ret, err = gofunc()
|
||||
}
|
||||
Wait(outerfunc)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
//export mainthread_exec_handle
|
||||
func mainthread_exec_handle(u uintptr) {
|
||||
h := cgo.Handle(u)
|
||||
|
||||
gofunc, ok := h.Value().(func())
|
||||
if !ok {
|
||||
panic("miqt: callback of non-callback type (heap corruption?)")
|
||||
}
|
||||
|
||||
gofunc()
|
||||
|
||||
// Free handle after use
|
||||
h.Delete()
|
||||
}
|
18
qt6/mainthread/mainthread.h
Normal file
18
qt6/mainthread/mainthread.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef QT_MAINTHREAD_H
|
||||
#define QT_MAINTHREAD_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void mainthread_exec(intptr_t cb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user