My Experience with Go walk

📢 This article was translated by gemini-3.5-flash

In December 2024, I tried writing my very first Windows GUI software, Stop Looking at the Screen, Go walk (去散步).

- -

Although it was very simple, it felt like my first time developing without relying heavily on a framework. At the same time, I was determined to use Go no matter what back then. I happened to find a framework that fit the theme so well, creating a pun that echoed the theme with the tech stack.

So looking back at these notes today brings back a lot of feelings. Especially since this software even supports i18n. In today’s era of AI Coding, it’s hard to imagine that I actually hand-coded multi-language support back then using traditional methods, especially since I recall this framework didn’t support it natively. But because it wasn’t very user-friendly, I’ve forgotten how I did it.

That’s it for my reflections. Below are the original notes.

This article is only for publishing the notes from that time and does not guarantee accuracy. Additionally, the walk architecture is not recommended.

Environment

After creating a new Go project in GoLand, go.mod will be initialized by default. If this file does not exist, you can use the command:

1
go mod init

Install walk:

1
go get github.com/tailscale/walk

Project

After downloading the rsrc tool and configuring the PATH, install rsrc:

1
go get github.com/akavel/rsrc

Create a Manifest file main.manifest with the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
        </dependentAssembly>
    </dependency>
    <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
            <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
            <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>
        </windowsSettings>
    </application>
</assembly>

Merge into the Go program:

1
rsrc -manifest main.manifest -o rsrc.syso

Countdown Development

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

import (
	"fmt"
	"github.com/tailscale/walk"
	. "github.com/tailscale/walk/declarative"
	"log"
	"runtime"
	"time"
)

func main() {
	app, err := walk.InitApp()
	if err != nil {
		log.Fatal(err)
	}

	var timeRemain *walk.Label
	var flag bool
	ok := true

	MainWindow{
		Title:   "Hi walk",                   // Window title
		MinSize: Size{Width: 60, Height: 40}, // Minimum size
		Layout:  VBox{MarginsZero: true},     // Window layout, vertical box layout
		Children: []Widget{
			Label{
				AssignTo: &timeRemain,
				Text:     "Start countdown 10 seconds",
			},
			PushButton{
				Text: "Start Countdown",
				OnClicked: func() {
					if flag {
						return
					}
					flag = true
					// Countdown
					go func() {
						for i := 10; i > 0; i-- {
							if ok {
								time.Sleep(1 * time.Second)
								timeRemain.SetText(fmt.Sprintf("Countdown %d seconds", i))
							} else {
								runtime.Goexit()
							}
						}
						walk.MsgBox(nil, "Reminder", "Time's up", walk.MsgBoxIconInformation)
						flag = false
					}()
				},
			},
			PushButton{
				Text: "Stop Countdown",
				OnClicked: func() {
					flag = false
					ok = false
					timeRemain.SetText("Countdown stopped")
					time.Sleep(1 * time.Second)
					timeRemain.SetText("Start countdown 10 seconds")
				},
			},
		},
	}.Create()

	app.Run()
}

New Window

Open a new window:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package main

import (
	"github.com/tailscale/walk"
	. "github.com/tailscale/walk/declarative"
	"log"
)

type MyMainWindow struct {
	*walk.MainWindow
}

func main() {
	app, err := walk.InitApp()
	if err != nil {
		log.Fatal(err)
	}

	mw := new(MyMainWindow)

	MainWindow{
		AssignTo: &mw.MainWindow,
		Title:    "Hi walk",                     // Window title
		MinSize:  Size{Width: 400, Height: 500}, // Minimum size
		Size:     Size{Width: 400, Height: 500}, // Size
		Layout:   VBox{},                        // Window layout
		Children: []Widget{
			Label{
				Text:      "Please select a mode",
				Alignment: AlignHCenterVCenter,
			},
			PushButton{
				Text:      "Short-time high-frequency break",
				OnClicked: mw.shortLayout,
			},
			PushButton{
				Text: "Long-time low-frequency break",
			},
		},
	}.Create()

	app.Run()

}

func (mw *MyMainWindow) shortLayout() {
	shortMainWindow := new(MyMainWindow)
	MainWindow{
		AssignTo: &shortMainWindow.MainWindow,
		Title:    "Short-time high-frequency break",
		MinSize:  Size{Width: 400, Height: 500}, // Minimum size
		Size:     Size{Width: 400, Height: 500}, // Size
		Layout:   VBox{},
		Children: []Widget{
			Label{Text: "Hello"},
		},
	}.Create()

	shortMainWindow.Running()
}

Reference

Two days of using go walk, silently giving up in my heart

Golang—walk learning (1)

walk control learning section 1

This post is licensed under CC BY-NC-SA 4.0 by the author.