Creazione di GUI con Tk in Go
ํ์ด์ฌ์๋ Tkinter ์ ๊ฐ์ GUI ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ด์ฅ๋์ด ์์ต๋๋ค. ์ต๊ทผ์ Go ์ธ์ด์์๋ Tcl/Tk๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก CGo-Free, Cross Platform Tk ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ฐ๋ฐ๋์์ต๋๋ค. ์ค๋์ ๊ทธ ๊ธฐ์ด์ ์ธ ์ฌ์ฉ๋ฒ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
Hello, Tk ๋ง๋ค๊ธฐ
๋จผ์ ๊ฐ๋จํ 'Hello, TK!' ์์ ๋ก ์์ํด๋ณด๊ฒ ์ต๋๋ค.
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}
์ ์์ ์ฝ๋์ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
ํ์ด์ฌ์ Tk๋ฅผ ์ฌ์ฉํด๋ณธ ๊ฒฝํ์ด ์๋ ๋ถ์ด๋ผ๋ฉด, ์ฐฝ ์์ ์์ ฏ์ด ํจํน๋๊ฑฐ๋ ์ฐฝ ํ์์ ์ง์ ์์ ฏ์ด ํจํน๋๋ ๊ตฌ์กฐ๋ฅผ ์ดํดํ์ค ๊ฒ์ ๋๋ค. ์์ ฏ์ ์ข ๋ฅ์ ๋ฐ๋ผ ๋ผ๋ฒจ ๋ฑ์ด ๊ทธ ์์ ํฌํจ๋ฉ๋๋ค.
Ipadx์ Ipady๋ Internal padding์ ์ฝ์๋ก, ๋ด๋ถ ์์ ฏ๋ค์ ์ฌ๋ฐฑ์ ์กฐ์ ํฉ๋๋ค. ์ด ์์ ์์๋ ๋ฒํผ์ ์ฌ๋ฐฑ์ด ์กฐ์ ๋ฉ๋๋ค.
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ Window ๊ตฌ์กฐ์ฒด๊ฐ ์์ผ๋ฉฐ, App์ด๋ผ๋ ๋ณ์๊ฐ ์ต์์ ์ฐฝ์ ๊ด๋ฆฌํฉ๋๋ค. ์ด๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ด๋ถ์ ๋ฏธ๋ฆฌ ์ ์๋์ด ์์ต๋๋ค. ๋ฐ๋ผ์ tk.App.Wait()๋ฅผ ์ข ๋ฃํ๋ tk.App.Destroy() ํจ์๊ฐ ์ต์์ ์ฐฝ์ ๋ซ๋ ์ญํ ์ ํฉ๋๋ค.
์ด์ GitLab์ _examples ํด๋์ ์๋ ๋ช ๊ฐ์ง ์์ ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
SVG ํ์ผ ์ฒ๋ฆฌํ๊ธฐ
๋ค์์ SVG ํ์ผ์ ๋ผ๋ฒจ ์์ ฏ์ ํ์ํ๋ ์์ ์ ๋๋ค.
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}
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ SVG๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- SVG ํ์ผ์ ๋ด์ฉ์ ๋ฌธ์์ด๋ก ์ฝ์ด๋ค์ ๋๋ค(๋๋ ์ ์์์ฒ๋ผ ์ง์ ํฌํจ์ํต๋๋ค).
- ์ด ๋ด์ฉ์ Data ํจ์์ ์ ๋ฌํ์ฌ ์ต์ ์ด ํฌํจ๋ ๋ฌธ์์ด๋ก ๋ณํํฉ๋๋ค(-data ์ต์ ).
- ๋ณํ๋ ๋ฐ์ดํธ๊ฐ์ NewPhoto ํจ์๋ก ์ ๋ฌ๋์ด Tcl/Tk ์ด๋ฏธ์ง๋ฅผ ํํํ๋ Img ๊ตฌ์กฐ์ฒด ํฌ์ธํฐ๋ฅผ ๋ฐํํฉ๋๋ค.
- Image ํจ์๋ฅผ ํต๊ณผํ๋ฉด์ Img ๊ตฌ์กฐ์ฒด ํฌ์ธํฐ๋ -Image ์ต์ ์ด ์ถ๊ฐ๋ ๋ฌธ์์ด๋ก ๋ณํ๋ฉ๋๋ค.
- ๊ตฌ์กฐ์ฒด RAW ๊ฐ์ ๋ด์ ๋ฌธ์์ด๋ก ๋ณํํ๋ ์ด์ ๋ Label ์์ ฏ ์์ฑ์ ์ํด์์ ๋๋ค.
ICO์ PNG ํ์ผ๋ ๋น์ทํ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
PNG ํ์ผ ์ฒ๋ฆฌํ๊ธฐ
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}
PNG ํ์ผ ์ฒ๋ฆฌ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์๋ฒ ๋ฉ๋ gopher.png๋ฅผ ์ต์ ์ด ํฌํจ๋ ๋ฌธ์์ด ํ์ ์ผ๋ก ๋ณํํฉ๋๋ค.
- NewPhoto ํจ์๋ฅผ ํตํด *Img ํ์ ์ผ๋ก ๋ณํํฉ๋๋ค.
- Image ํจ์๋ฅผ ๊ฑฐ์ณ RAW ๋ฌธ์์ด๋ก ๋ณํ๋ ํ, ๋ผ๋ฒจ ์์ ฏ์ผ๋ก ์์ฑ๋ฉ๋๋ค.
ICO ํ์ผ๋ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, SVG ํฌ๋งท๊ณผ์ ์ฐจ์ด์ ์ Data ํจ์ ๋ด๋ถ ์ฒ๋ฆฌ ๋ฐฉ์๋ฟ์ ๋๋ค.
์ฌ๊ธฐ์ "์ต์ ์ด ํฌํจ๋ ๋ฌธ์์ด"์ ์ ์ฒด๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
1type rawOption string
์์ ์ธ๊ธํ ์ต์ ์ด ํฌํจ๋ ๋ฌธ์์ด์ ๋จ์ํ ํฌ๋งทํ ๋ ๋ฌธ์์ด์ ๋ถ๊ณผํฉ๋๋ค.
1func (w *Window) optionString(_ *Window) string {
2 return w.String()
3}
optionString ๋ฉ์๋๋ Window ํฌ์ธํฐ์ ๋ํ ๋ฉ์๋๋ก, ๋ฌธ์์ด์ ๋ฐํํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก Data ํจ์์ ๋ด๋ถ ๊ตฌ์กฐ๋ฅผ ๊ฐ๋จํ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
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}
์ฝ๋๋ฅผ ๋ณด๋ฉด ICO๋ PNG ํ์ผ์ ๊ฒฝ์ฐ ์ธ์ฝ๋ฉ/๋์ฝ๋ฉ ๊ณผ์ ์ด ํ์ํฉ๋๋ค. ๊ทธ ์ธ์ ๊ฒฝ์ฐ์๋ ํน๋ณํ ๋ณํ ์์ด ๋ฐ์ดํธํ์ผ๋ก ๋ณํ๋ ๋ฌธ์์ด์ -data ์ต์ ๋ง ์ถ๊ฐํ์ฌ Data ํจ์์ ๊ฒฐ๊ณผ๋ฌผ์์ ํ์ํฉ๋๋ค.
๋ฉ๋ด ์์ ฏ์ผ๋ก ์ด๋ฏธ์ง ๋ถ๋ฌ์ค๊ธฐ
์์ ์ฐ์ตํ PNG, ICO ๋ถ๋ฌ์ค๊ธฐ ์์ ์ ๋ฉ๋ด ์์ ฏ์ ์ถ๊ฐํ๋ฉด ํ์ํ ์ด๋ฏธ์ง๋ฅผ ํ์ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ต๋๋ค.
๋จผ์ ๊ฐ๋จํ ๋ฉ๋ด ์์ ฏ ์์ ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
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}
์ด ์์ ์์๋ ๋ฉ๋ด ๋ฐ์ ๋ฉ๋ด ์์ฑ, ๊ธ์ ๊ฐ์กฐ, Command ์ต์ , ๋จ์ถํค ๋ฐ์ธ๋ฉ, ๊ทธ๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ฐฝ์ ์ด๊ธฐ ํฌ๊ธฐ๋ฅผ ์ค์ ํด๋ณด์์ต๋๋ค.
์ด์ GetOpenFile๋ก ์ง์ ๋ Command ํจ์๋ฅผ ์์ ํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ ํ์ํ๋ ํ๋ก๊ทธ๋จ์ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
ํ๋ก๊ทธ๋จ ์์ฑ ๊ณํ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- PNG์ ICO ํ์ผ๋ง ์ด ์ ์๋๋ก ์ ํ
- ํ์ผ ๋ค์ด์ผ๋ก๊ทธ์์ ์ ํํ ํ์ผ ์ฒ๋ฆฌ
- ์ด๋ฏธ์ง ํ์๋ฅผ ์ํ ์์ ฏ ๊ตฌํ
๋ค์์ ์ด๋ฌํ ๊ณํ์ ๋ฐ์ํ ์ฝ๋์ ๋๋ค:
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}
์ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์๋ํฉ๋๋ค:
- strings ํจํค์ง์ ๋ฌธ์์ด ๋น๊ต ํจ์๋ก ํ์ผ ํ์ฅ์๋ฅผ ํ์ธํฉ๋๋ค
- os ํจํค์ง๋ฅผ ์ฌ์ฉํด ํ์ผ์ ์ด๊ณ ์ฝ์ ํ ๋ซ์ต๋๋ค
- ์ฝ์ด๋ค์ธ ์ด๋ฏธ์ง๋ ๋ ์ด๋ธ ์์ ฏ์ผ๋ก ํ์๋ฉ๋๋ค
์ ๋ฆฌ
์ด๋ฒ ๊ธ์์๋ Go ์ธ์ด์ Tcl/Tk ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ๋ค์ ๋ด์ฉ์ ๋ค๋ค์ต๋๋ค:
- GUI ์ ํ๋ฆฌ์ผ์ด์ ๊ธฐ๋ณธ ๊ตฌ์กฐ
- SVG, PNG, ICO ๋ฑ ๋ค์ํ ์ด๋ฏธ์ง ํฌ๋งท ์ฒ๋ฆฌ
- ์์ ฏ ํจํน๊ณผ ๋ ์ด์์ ๊ด๋ฆฌ
- ์ด๋ฏธ์ง ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๊ตฌ์กฐ
- ๋จ์ถํค ๋ฐ์ธ๋ฉ๊ณผ ์์ ฏ ์ปค๋งจ๋
Go ์ธ์ด์ Tcl/Tk๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ฉด ๊ฐ๋จํ๋ฉด์๋ ์ค์ฉ์ ์ธ GUI ํ๋ก๊ทธ๋จ์ ๋ง๋ค ์ ์์ต๋๋ค. ์ด๋ฅผ ๋ฐํ์ผ๋ก ๋ ๋ณต์กํ GUI ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ๋์ ํด๋ณด์๊ธฐ ๋ฐ๋๋๋ค.