how do I make wxWidgets (using golang) repaint window on MacOS? - go

Here's some sample code that puts a Gauge on the screen and make the progress bar increase 1 value every second. On MacOS I don't see the progress bar update unless I drag the window around or resize it manually with the mouse. Any idea how to force the whole thing to repaint? I'm calling f.Refresh() and f.Update()
package main
import "github.com/dontpanic92/wxGo/wx"
import "time"
var g wx.Gauge
type MyFrame struct {
wx.Frame
}
func (f *MyFrame) startUpload() {
for {
time.Sleep(time.Second)
g.SetValue(g.GetValue() + 1)
f.Refresh()
f.Update()
}
}
func NewMyFrame() MyFrame {
f := MyFrame{}
f.Frame = wx.NewFrame(wx.NullWindow, -1, "Test Thread")
mainSizer := wx.NewBoxSizer(wx.HORIZONTAL)
g = wx.NewGauge(f, wx.ID_ANY, 100, wx.DefaultPosition, wx.NewSize(600, 40), 0)
f.SetSizer(mainSizer)
mainSizer.Add(g, 100, wx.ALL|wx.EXPAND, 50)
f.Layout()
go f.startUpload()
return f
}
func main() {
wx1 := wx.NewApp()
f := NewMyFrame()
f.Show()
wx1.MainLoop()
return
}
Update: I've been reading http://docs.wxwidgets.org/trunk/overview_thread.html and I'm trying code like:
b := wx.NewPaintEvent()
f.GetEventHandler().QueueEvent(b)
instead of calling Refresh and Update but my wx.NewPaintEvent doesn't do anything. Maybe I'm making the wx.NewPaintEvent wrong? Or I'm adding it to the wrong EventHandler?

Generally speaking, doing anything with GUI objects from the non-main (i.e. not the one that initialized the library) thread is not supported by wxWidgets.
The usual workaround is to post an event to the main thread asking it to update the widget instead of doing it directly in the worker thread. In C++ this can be done easily and elegantly using CallAfter(), but I don't know enough about Go and wxGo to show you an example of doing it in this language.

author of wxGo fixed it:
https://github.com/dontpanic92/wxGo/issues/10
wx.Bind(f, wx.EVT_THREAD, func(e wx.Event) {
threadEvent := wx.ToThreadEvent(e)
the_gauge.SetValue(threadEvent.GetInt())
}, UPLOAD_WORKER_ID)
then in the thread:
threadEvent := wx.NewThreadEvent(wx.EVT_THREAD, UPLOAD_WORKER_ID)
threadEvent.SetInt(50)
f.QueueEvent(threadEvent)

Related

Show/Move main window at custom position

Problem
I can't find function to set position of the window. I'v looked into code and I see SetPos func for different elements and wondering why it's not added for fyne.Window type.
func main() {
a := app.New()
w := a.NewWindow("Trying to position window")
if drv, ok := fyne.CurrentApp().Driver().(desktop.Driver); ok {
w = drv.CreateSplashWindow()
// something like this?
// w.SetPos(x, y)
// w.Move(x, y)
}
Workaround
I forked project and created func:
func (w *window) GLFWindow() *glfw.Window {
return w.view()
}
to expose unexported underlying window attribute w.viewport. and it unlocks bunch of methods i can use now
if ww := w.GLFWindow(); ww != nil {
ww.SetPos(x, y)
}
It looks I'm gonna use it (forked/edited version) but maybe you can suggest fyne-way to do it :)
Question
Is there existing way to set position of the window? Or access underlying w.viewport attribute?
This is not possible with the exported API, because many OS do not support it.
See discussion about the decision process at https://github.com/fyne-io/fyne/issues/1155

Create a verbose console inside Golang app using Fyne

Comming from Python with PyQt gui, I was used to add kind of console in my programm. The purpose was to indicate to the user information on the processes in progress, on the execution errors encountered, etc.
In Python/PyQt, I was using QLineEdit to do that. It was pretty easy to use. Just create and insert the widget in my gui and add a row for each information by calling appen().
For example, the console could say "esedb loading" when loading an esedb file, then "esedb file loaded" when finished, then "esedb parsing" for the next step, etc...
Now, I'm learning Golang with Fyne and I'm looking for a way to do something similar.
I found widget.NewTextGrid() but it doesn't work as I expect.
I can't just append new line. If I understand well, I have to store text in a string variable
Could you advice me about the way to do that ?
Thanks!
package main
import (
//"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
func main() {
myapp := app.New()
myappGui := myapp.NewWindow("Example")
myappGui.Resize(fyne.NewSize(400, 600))
textConsole := widget.NewTextGrid()
TextGrid is a complex component designed for managing character specific font styles in a monospace arrangement (like a terminal etc).
For performance I would recommend a VBox in a Scroll widget where each line is another appended Label (you can set them to monospace text style as well). If you want the text to be interactive then as other answers have said the NewMultiLineEntry is likely for you.
Text is complex and we are working hard to optimise more of the complex usages and large file handling, so it will get smoother in later releases…
widget.TextGrid does not have a method to append a line, but it does support querying its current content using TextGrid.Text(). So what you may do is set a new text that is its current content and the new line concatenated, e.g.:
textConsole.SetText(textConsole.Text() + "\n" + line)
But know that widget.TextGrid does not support scrolling: its size will be dictated by its string content. You can make it scrollable of course by using a container.Scroll.
For example:
func main() {
myapp := app.New()
w := myapp.NewWindow("Example")
w.Resize(fyne.NewSize(500, 300))
textConsole := widget.NewTextGrid()
scrollPane := container.NewScroll(textConsole)
w.SetContent(scrollPane)
go func() {
for {
textConsole.SetText(textConsole.Text() + time.Now().String() + "\n")
scrollPane.ScrollToBottom()
time.Sleep(time.Second)
}
}()
w.ShowAndRun()
}
Alternatively you may use a multiline widget.Entry. It also supports selecting any part of it, and by default it's also editable. You may disable editing of course. It supports scrolling by default.
See this example:
func main() {
myapp := app.New()
w := myapp.NewWindow("Example")
w.Resize(fyne.NewSize(500, 300))
textConsole := widget.NewMultiLineEntry()
textConsole.Disable() // Disable editing
w.SetContent(textConsole)
go func() {
for {
textConsole.SetText(textConsole.Text + time.Now().String() + "\n")
time.Sleep(time.Second)
}
}()
w.ShowAndRun()
}

How to resize window to dialog size in Fyne

I am using the Fyne package for Go. All I want to do is display a dialog, and have the window automatically resize to the maximum size of the dialog. I've gone over the documentation and looked at examples but I can't find it anywhere! The window is always 0x0 pixels. If I resize the window out I can tell that the dialog has a maximum height and width but I can't seem to feed that back into the size of the window. Here is my code:
import (
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/dialog"
)
var myApp fyne.App
func main() {
myApp = app.New()
myWindow := myApp.NewWindow("test")
cnfm := dialog.NewConfirm("Test Dialog!", "Are you sure you want to interact with this test dialog?", loseCallback, myWindow)
cnfm.Show()
cnfm.Resize(cnfm.MaxSize())
myWindow.Show()
// uncomment to have a window greater than 0x0
//myWindow.Resize(fyne.NewSize(375, 180))
myApp.Run()
}
func loseCallback(yes bool) {
myApp.Quit()
}
This seems really basic but I just can't find any way to achieve this! I've tried playing with layouts, containers, etc but nothing makes a difference, the window is always 0x0. I would really appreciate some help with this!
Fyne doesn't really have a concept of MaxSize. There is MinSize() (what a component should never be smaller than) and it's Size() which is the current size on screen.
Dialogs appear over the current content so are not constrained, or expaneded, by the content layout etc.
As a dialog is not designed to be the main content of a while I wonder if changing your content to be inside the window would be more suitable (as the dialog is really used when something in the app is happening and you want to interrupt it).
For example:
import (
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/container"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
)
var myApp fyne.App
func main() {
myApp = app.New()
myWindow := myApp.NewWindow("test")
yes := widget.NewButton("Yes", closeCallback)
yes.Importance = widget.HighImportance
myWindow.SetContent(container.NewVBox(
widget.NewLabel("Are you sure you want to interact with this test dialog?"),
container.NewHBox(layout.NewSpacer(),
widget.NewButton("No", closeCallback), yes,
layout.NewSpacer())))
myWindow.ShowAndRun()
}
func closeCallback() {
myApp.Quit()
}

Getting window geometry in Go for Windows

I want to create a tool with Go that lets me resize multiple windows on my screen. As an example lets assume that I want to find my Firefox window and my Atom (text editor) window and place them, so that they take up exactly half of my screen (FF left, Atom right).
So far I realized, that I need to use the Windows API for that. I created a method that gives me all handles and the titles of all windows, but I'm struggling with geometry information. I understand that the api call GetWindowRect will help, but how can I get the information out of a pointer to a rect?
Follow up question 1: what other information can I get about the windows?
Follow up question 2: How do I resize the window so that it takes exactly half my screen size? I guess, I need another call to get the monitor dimensions.
What I have so far is the code below. The main program finds all handles and displays those containing 'Atom' in the title. The windows package contains the code accessing the windows API.
My current result is that I get 2 handles for atom (why not just 1?). I guess, I have to learn more about the Windows API, too. Are there good summaries to understand the basics?
main.go:
package main
import (
"resizer/windows"
"fmt"
"log"
"strings"
)
func main() {
const title = "Atom"
m := windows.GetAllWindows()
fmt.Printf("Map of windows: \n")
for handle := range m {
if strings.Contains(m[handle].Title(), title) {
fmt.Printf("'%v'\n", m[handle])
}
}
}
windows.go:
package windows
import (
"fmt"
"log"
"syscall"
"unsafe"
)
var (
user32 = syscall.MustLoadDLL("user32.dll")
procEnumWindows = user32.MustFindProc("EnumWindows")
procGetWindowTextW = user32.MustFindProc("GetWindowTextW")
)
// Window represents any Window that is opened in the Windows OS
type Window struct {
handle syscall.Handle
title string
}
// Title returns the title of the window
func (w Window) Title() string {
return w.title
}
// GetAllWindows finds all currently opened windows
func GetAllWindows() map[syscall.Handle]Window {
m := make(map[syscall.Handle]Window)
cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
bytes := make([]uint16, 200)
_, err := GetWindowText(h, &bytes[0], int32(len(bytes)))
title := "||| no title found |||"
if err == nil {
title = syscall.UTF16ToString(bytes)
}
m[h] = Window{h, title}
return 1 // continue enumeration
})
EnumWindows(cb, 0)
return m
}
// EnumWindows loops through all windows and calls a callback function on each
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
// GetWindowText gets the title of a Window given by a certain handle
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
len = int32(r0)
if len == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
GetWindowRect() writes the geometry to the RECT structure you pass the pointer to in. It operates exactly like the GetWindowText() call you already have; the difference is you have to provide the RECT structure yourself.
You should be able to just get away with copying the structure verbatim. To substitute data types, use this page. The definition for RECT says all the fields are LONG, which that page says is "[a] 32-bit signed integer". So this should suffice:
type RECT struct {
left int32 // or Left, Top, etc. if this type is to be exported
top int32
right int32
bottom int32
}
(Most likely irrelevant, but it's worth pointing out that RECT operates identically to image.Rectangle, with left and top being Min and right and bottom being Max. They are not identical because image.Rectangle uses int, so you may want to consider providing conversion functions if you want to use image's geometry functions to manipulate rectangles instead of GDI's.)

Taking control of another window with Go

I'm wondering if there are any libraries that help me take control of another window. For example if user has calc.exe running, I'd like my go code to move it, resize it, maybe even remove it's frame, attach stuff to it, idk.
Right now I only know how to do it with scripting languages like autoit or autohotkey or whatnot.
Yes there are several libraries which can be found using godoc.org or go-search.org. In this example I'm using w32 and w32syscall (which supplies some additional functions):
package main
import (
"log"
"strings"
"syscall"
"github.com/AllenDang/w32"
"github.com/hnakamur/w32syscall"
)
func main() {
err := w32syscall.EnumWindows(func(hwnd syscall.Handle, lparam uintptr) bool {
h := w32.HWND(hwnd)
text := w32.GetWindowText(h)
if strings.Contains(text, "Calculator") {
w32.MoveWindow(h, 0, 0, 200, 600, true)
}
return true
}, 0)
if err != nil {
log.Fatalln(err)
}
}
Both of these libraries are merely exposing the underlying win32 API with minimal wrapping, so you will have to read the corresponding documentation from Microsoft to really know how to use them.

Resources