Creating a GUI with Tk in Go
Python natively includes GUI libraries such as Tkinter. Recently, a CGo-Free, Cross Platform Tk library has been developed for Go, enabling the use of Tcl/Tk. Today, we will examine its fundamental usage.
Creating "Hello, Tk"
Let us begin with a simple "Hello, TK!" example.
1package main
2
3import tk "modernc.org/tk9.0"
4
5func main() {
6 tk.Pack(
7 tk.TButton(
8 tk.Txt("Hello, TK!"),
9 tk.Command(func() {
10 tk.Destroy(tk.App)
11 })),
12 tk.Ipadx(10), tk.Ipady(5), tk.Padx(15), tk.Pady(10),
13 )
14 tk.App.Wait()
15}
We will now examine the aforementioned example code and its execution result in detail.
For those familiar with Python's Tk, the structure where widgets are packed within a window or directly under a window will be comprehensible. Depending on the widget type, labels and similar elements are incorporated within it.
Ipadx and Ipady are abbreviations for Internal padding, which adjust the margins of internal widgets. In this example, the margins of the button are adjusted.
This library features a Window structure, and a variable named App manages the top-level window. This is predefined within the library. Therefore, the tk.App.Destroy()
function, which terminates tk.App.Wait()
, is responsible for closing the top-level window.
Let us now examine some examples found in the _examples
folder of GitLab.
Processing SVG Files
The following is an example of displaying an SVG file within a label widget.
1package main
2
3import . "modernc.org/tk9.0"
4
5// https://en.wikipedia.org/wiki/SVG
6const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
7<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
8<svg width="391" height="391" viewBox="-70.5 -70.5 391 391" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9<rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
10<g opacity="0.8">
11 <rect x="25" y="25" width="200" height="200" fill="lime" stroke-width="4" stroke="pink" />
12 <circle cx="125" cy="125" r="75" fill="orange" />
13 <polyline points="50,150 50,200 200,200 200,100" stroke="red" stroke-width="4" fill="none" />
14 <line x1="50" y1="50" x2="200" y2="200" stroke="blue" stroke-width="4" />
15</g>
16</svg>`
17
18func main() {
19 Pack(Label(Image(NewPhoto(Data(svg)))),
20 TExit(),
21 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
22 App.Center().Wait()
23}
The method for processing SVG in this library is as follows:
- The content of the SVG file is read as a string (or directly included as in the example above).
- This content is passed to the Data function to be converted into a string containing options (the -data option).
- The converted byte value is passed to the NewPhoto function, which returns an Img structure pointer representing a Tcl/Tk image.
- As it passes through the Image function, the Img structure pointer is converted into a string with the -Image option appended.
- The reason for converting to a string containing the RAW value of the structure is for the creation of a Label widget.
ICO and PNG files are processed in a similar manner.
Processing PNG Files
1package main
2
3import _ "embed"
4import . "modernc.org/tk9.0"
5
6//go:embed gopher.png
7var gopher []byte
8
9func main() {
10 Pack(Label(Image(NewPhoto(Data(gopher)))),
11 TExit(),
12 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
13 App.Center().Wait()
14}
The PNG file processing procedure is as follows:
- The embedded
gopher.png
is converted into a string type that includes options. - It is converted to an
*Img
type via theNewPhoto
function. - After being converted to a RAW string through the
Image
function, it is created as a label widget.
ICO files are processed identically, with the sole difference from the SVG format being the internal processing method of the Data function.
Let us now examine the nature of this "string containing options":
1type rawOption string
The aforementioned string containing options is merely a formatted string.
1func (w *Window) optionString(_ *Window) string {
2 return w.String()
3}
The optionString
method is a method for a Window
pointer, and it returns a string.
Finally, let us briefly examine the internal structure of the Data function:
1func Data(val any) Opt {
2 switch x := val.(type) {
3 case []byte:
4 switch {
5 case bytes.HasPrefix(x, pngSig):
6 // ok
7 case bytes.HasPrefix(x, icoSig):
8 b := bytes.NewBuffer(x)
9 img, err := ico.Decode(bytes.NewReader(x))
10 if err != nil {
11 fail(err)
12 return rawOption("")
13 }
14
15 b.Reset()
16 if err := png.Encode(b, img); err != nil {
17 fail(err)
18 return rawOption("")
19 }
20
21 val = b.Bytes()
22 }
23 }
24 return rawOption(fmt.Sprintf(`-data %s`, optionString(val)))
25}
The code indicates that ICO and PNG files necessitate an encoding/decoding process.
For other cases, only the -data
option is appended to the byte-converted string without special transformation, indicating that it is the result of the Data function.
Loading Images with Menu Widgets
By incorporating a menu widget into the previously practiced PNG and ICO loading examples, one can construct an application that displays required images.
First, let us examine a simple menu widget example.
1package main
2
3import (
4 "fmt"
5 . "modernc.org/tk9.0"
6 "runtime"
7)
8
9func main() {
10 menubar := Menu()
11
12 fileMenu := menubar.Menu()
13 fileMenu.AddCommand(Lbl("New"), Underline(0), Accelerator("Ctrl+N"))
14 fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func() { GetOpenFile() }))
15 Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(1) }))
16 fileMenu.AddCommand(Lbl("Save As..."), Underline(5))
17 fileMenu.AddSeparator()
18 fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
19 Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(4) }))
20 menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
21
22 App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
23 App.Configure(Mnu(menubar), Width("8c"), Height("6c")).Wait()
24}
In this example, we have configured a menu bar and menu creation, text emphasis, the Command option, shortcut key binding, and the initial size of the application window.
We will now modify the Command function specified as GetOpenFile to create a program that loads and displays images.
The program development plan is as follows:
- Restrict opening to PNG and ICO files only.
- Process the file selected from the file dialog.
- Implement a widget for image display.
The following code reflects this plan:
1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "runtime"
8 "strings"
9
10 . "modernc.org/tk9.0"
11)
12
13func handleFileOpen() {
14 s := GetOpenFile()
15 for _, photo := range s {
16 formatCheck := strings.Split(photo, ".")
17 format := formatCheck[len(formatCheck)-1]
18
19 if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
20 picFile, err := os.Open(photo)
21 if err != nil {
22 log.Println("Error while opening photo, error is: ", err)
23 }
24
25 pictureRawData := make([]byte, 10000*10000)
26 picFile.Read(pictureRawData)
27
28 imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
29 Pack(imageLabel,
30 TExit(),
31 Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
32 }
33 picFile.Close()
34 }
35}
36
37func main() {
38 menubar := Menu()
39
40 fileMenu := menubar.Menu()
41 fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(handleFileOpen))
42 Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
43 fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
44 Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
45 menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
46
47 App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
48 App.Configure(Mnu(menubar), Width("10c"), Height("10c")).Wait()
49}
The aforementioned code operates in the following manner:
- The file extension is verified using the string comparison function from the strings package.
- The file is opened, read, and subsequently closed using the os package.
- The read image is displayed as a label widget.
Summary
In this article, we utilized the Tcl/Tk library for the Go language to cover the following topics:
- Fundamental structure of GUI applications.
- Processing of various image formats, including SVG, PNG, and ICO.
- Widget packing and layout management.
- Image data processing structure.
- Shortcut key binding and widget commands.
The combined use of the Go language and Tcl/Tk facilitates the creation of straightforward yet practical GUI programs. We encourage you to undertake the development of more intricate GUI applications based on this foundation.