Поскольку у Go нет своих средств писать графический интерфейс пользователя (ГИП или GUI кому как привычнее), я решил использовать библиотеку Tk для этих целей (не забыть загрузить dev-пакеты с заголовочными файлами, они потребуются для сборки пакетов для go).
На github.com нашёл несколько пакетов для Go, которые позволяют пользоваться средствами Tcl/Tc. Например gothic или go-tk.
Там также имеются пакеты и для Qt и Gtk, кому что больше нравится.
Мне больше нравится Tcl/Tk, потому и выбрал эти библиотеки.
Ниже привожу код небольшой демонстрационной программки, в которой реализован ГИП посредством использоваиня пакета "github.com/edartuz/go-tk/gotk". Это был мой первый опыт, прошу не судить строго.
С установкой пакета пришлось помучиться, поскольку я ставил компилятор go не из репозитория, а своми руками с сайта golang.org, при установке пакета gotk компилятор go не мог найти некоторые заголовочные фалы( tcl.h tk.h X11 и др.), пришлось подсунуть их ему в директорию.
Код: Выделить всё
// Демонстрационная программка возможностей пакета "github.com/edartuz/go-tk/gotk"
// обеспечивающего доступ из программы написанной на Go к средствам Tcl/Tk
// Для её работы необходима установка в системе библиотек Tcl и Tk
// ===============================================================
// программа организована из двух потоков:
// main - главный поток в котором реализован GUI
// imit - второй вспомогательный поток, выполняющий всю основную работу приложения
// и обменивающийся командами Tcl с главным потокм посредством команды Tcl "thread::send"
// ---------------------------------------------------------------
// запускается imit кнопкой "Start" из GUI программы
// для остановки вспомогательного потока imit при нажатии кнопки "Stop" используется канал abort
// ===============================================================
// поскольку это демоверсия, imit совсем маленкиий...
package main
import (
"fmt"
"github.com/edartuz/go-tk/gotk"
"log"
"os"
"strconv"
)
//переменные для хранения каналов сообщений о прерывании работы программы и передачи данных
var abort chan struct{}
var chng chan changes //канал для передачи в imit состояния тех.средств
var imitWork bool // флаг работы imit
var treadId string // идентификатор материнского потока
// структура для передачи в imit состояния тех.средств
type changes struct {
kpdNumb, forKs1, forKs2, forKs3, forKs4 byte
}
func main() {
//init Tcl and Tk
if !gotk.Init(true) {
os.Exit(0)
}
//загружаем пакет для работы с потоками
gotk.Eval(`package require Thread`)
// определяем идентификатор потока и записываем его в переменную
// для дальнейшей передачи его в imit, чтобы он мог обмениваться командами с GUI
gotk.Eval(` set id [thread::names]
puts "Mine thread id: $id"`)
threadId := gotk.GetVar("id") // выводим значение идентификатора потока
//set window geometry
gotk.Tk.Wm(".", "geometry", "=550x150+1100+30")
gotk.Tk.Wm(".", "title", "{Демонстрация GUI}")
// кнопка старта работы имитатора
start := gotk.Tk.New("button", ".start", //set type, id (empty string for automatic generation
"text", "START", //set other parameters
"fg", "white",
"bg", "green",
"width", 10,
"justify", "right",
"relief", "groove",
"state", "normal").
Pack("grid", ".", //pack in root window
"column", 0,
"row", 7,
)
stop := gotk.Tk.New("button", ".stop", //кнопка остановки работы имитатора
"text", "STOP", //set other parameters
"fg", "white",
"bg", "red",
"width", 10,
"justify", "right",
"relief", "groove",
"state", "disabled").
Pack("grid", ".", //pack in root window
"column", 3,
"row", 7,
)
///// Метка состояния связи с аппаратурой ВК //////////////////
vk := gotk.Tk.New("label", ".vk",
"text", "{Связь с ВК}",
"width", 10,
"bg", "red",
"fg", "white")
vk.Pack("grid", ".",
"column", 1,
"row", 7,
)
//// Отображение переменных тех.средств для checbuttons на имена каналов
matr := map[string][]string{
"ks1": {"on_udp1", "on_sa1", "on_ups1", "on_line1"},
"ks2": {"on_udp2", "on_sa2", "on_ups2", "on_line2"},
"ks3": {"on_udp3", "on_sa3", "on_ups3", "on_line3"},
"ks4": {"on_udp4", "on_sa4", "on_ups4", "on_line4"},
}
/////////////// Команды для кнопок ////////////////////////
start.Cmd("command", func() { //set callback function
fmt.Println("======================= Start imit ==================================")
start.CSet("bg", "red", "state", "disabled")
stop.CSet("bg", "green", "state", "normal")
abort = make(chan struct{}) //канал сообщения о прерывании работы программы
chng = make(chan changes, 16)
go imit(threadId, chng, abort)
imitWork = true
})
stop.Cmd("command", func() { //set callback function
fmt.Println("************************ Stop imit **********************************")
start.CSet("bg", "green", "state", "normal")
stop.CSet("bg", "red", "state", "disabled")
vk.CSet("bg", "red")
imitWork = false
close(chng)
close(abort)
for key, lvals := range matr {
switch key {
case "ks1":
if checkVals(lvals) == 4 {
gotk.Eval(`.fr.chan1 config -text И`)
} else {
gotk.Eval(`.fr.chan1 config -text Н`)
}
case "ks2":
if checkVals(lvals) == 4 {
gotk.Eval(`.fr.chan2 config -text И`)
} else {
gotk.Eval(`.fr.chan2 config -text Н`)
}
case "ks3":
if checkVals(lvals) == 4 {
gotk.Eval(`.fr.chan3 config -text И`)
} else {
gotk.Eval(`.fr.chan3 config -text Н`)
}
case "ks4":
if checkVals(lvals) == 4 {
gotk.Eval(`.fr.chan4 config -text И`)
} else {
gotk.Eval(`.fr.chan4 config -text Н`)
}
}
}
})
//////////// Checkbuttons ////////////////////////////
// frame checkbuttons и заголовок
gotk.Eval(` frame .fr -borderwidth 5
grid .fr -column 1 -row 0
label .fr.channals -text КаналЫ
grid .fr.channals -column 0 -row 0
label .fr.upds -text УПД
grid .fr.upds -column 1 -row 0
label .fr.sas -text ММ
grid .fr.sas -column 2 -row 0
label .fr.upss -text УПС
grid .fr.upss -column 3 -row 0
label .fr.lines -text ЛиниИ
grid .fr.lines -column 4 -row 0`)
///// УПД checkbuttons //////////////
gotk.SetVar(matr["ks1"][0], 0)
gotk.SetVar(matr["ks2"][0], 0)
gotk.SetVar(matr["ks3"][0], 0)
gotk.SetVar(matr["ks4"][0], 0)
upd1 := `checkbutton .fr.upd1 -text УПД1 -variable ` + matr["ks1"][0]
upd2 := `checkbutton .fr.upd2 -text УПД2 -variable ` + matr["ks2"][0]
upd3 := `checkbutton .fr.upd3 -text УПД3 -variable ` + matr["ks3"][0]
upd4 := `checkbutton .fr.upd4 -text УПД4 -variable ` + matr["ks4"][0]
gotk.Eval(upd1)
gotk.Eval(upd2)
gotk.Eval(upd3)
gotk.Eval(upd4)
gotk.Eval(`grid .fr.upd1 -column 1 -row 1`)
gotk.Eval(`grid .fr.upd2 -column 1 -row 2`)
gotk.Eval(`grid .fr.upd3 -column 1 -row 3`)
gotk.Eval(`grid .fr.upd4 -column 1 -row 4`)
//// ММ checkbuttons ////////////////
gotk.SetVar(matr["ks1"][1], 0)
gotk.SetVar(matr["ks2"][1], 0)
gotk.SetVar(matr["ks3"][1], 0)
gotk.SetVar(matr["ks4"][1], 0)
sa1 := `checkbutton .fr.sa1 -text ММ1 -variable ` + matr["ks1"][1]
sa2 := `checkbutton .fr.sa2 -text ММ2 -variable ` + matr["ks2"][1]
sa3 := `checkbutton .fr.sa3 -text ММ3 -variable ` + matr["ks3"][1]
sa4 := `checkbutton .fr.sa4 -text ММ4 -variable ` + matr["ks4"][1]
gotk.Eval(sa1)
gotk.Eval(sa2)
gotk.Eval(sa3)
gotk.Eval(sa4)
gotk.Eval(`grid .fr.sa1 -column 2 -row 1`)
gotk.Eval(`grid .fr.sa2 -column 2 -row 2`)
gotk.Eval(`grid .fr.sa3 -column 2 -row 3`)
gotk.Eval(`grid .fr.sa4 -column 2 -row 4`)
//// УПС checkbuttons ///////////////
gotk.SetVar(matr["ks1"][2], 0)
gotk.SetVar(matr["ks2"][2], 0)
gotk.SetVar(matr["ks3"][2], 0)
gotk.SetVar(matr["ks4"][2], 0)
ups1 := `checkbutton .fr.ups1 -text УПС1 -variable ` + matr["ks1"][2]
ups2 := `checkbutton .fr.ups2 -text УПС2 -variable ` + matr["ks2"][2]
ups3 := `checkbutton .fr.ups3 -text УПС3 -variable ` + matr["ks3"][2]
ups4 := `checkbutton .fr.ups4 -text УПС4 -variable ` + matr["ks4"][2]
gotk.Eval(ups1)
gotk.Eval(ups2)
gotk.Eval(ups3)
gotk.Eval(ups4)
gotk.Eval(`grid .fr.ups1 -column 3 -row 1`)
gotk.Eval(`grid .fr.ups2 -column 3 -row 2`)
gotk.Eval(`grid .fr.ups3 -column 3 -row 3`)
gotk.Eval(`grid .fr.ups4 -column 3 -row 4`)
//// Линии связи checkbuttons //////
gotk.SetVar(matr["ks1"][3], 0)
gotk.SetVar(matr["ks2"][3], 0)
gotk.SetVar(matr["ks3"][3], 0)
gotk.SetVar(matr["ks4"][3], 0)
line1 := `checkbutton .fr.line1 -text Линия1 -variable ` + matr["ks1"][3]
line2 := `checkbutton .fr.line2 -text Линия2 -variable ` + matr["ks2"][3]
line3 := `checkbutton .fr.line3 -text Линия3 -variable ` + matr["ks3"][3]
line4 := `checkbutton .fr.line4 -text Линия4 -variable ` + matr["ks4"][3]
gotk.Eval(line1)
gotk.Eval(line2)
gotk.Eval(line3)
gotk.Eval(line4)
gotk.Eval(`grid .fr.line1 -column 4 -row 1`)
gotk.Eval(`grid .fr.line2 -column 4 -row 2`)
gotk.Eval(`grid .fr.line3 -column 4 -row 3`)
gotk.Eval(`grid .fr.line4 -column 4 -row 4`)
//// Каналы связи checkbuttons //////
chan1 := `label .fr.chan1 -text {Н} -bg red -fg white`
chan2 := `label .fr.chan2 -text {Н} -bg red -fg white`
chan3 := `label .fr.chan3 -text {Н} -bg red -fg white`
chan4 := `label .fr.chan4 -text {Н} -bg red -fg white`
gotk.Eval(chan1)
gotk.Eval(chan2)
gotk.Eval(chan3)
gotk.Eval(chan4)
gotk.Eval(`grid .fr.chan1 -column 0 -row 1`)
gotk.Eval(`grid .fr.chan2 -column 0 -row 2`)
gotk.Eval(`grid .fr.chan3 -column 0 -row 3`)
gotk.Eval(`grid .fr.chan4 -column 0 -row 4`)
//start main loop
gotk.Tk.MainLoop()
}
// функция проверяет в канале значения всех переменных состояния тех.средств
// возвращает сумму значений этих переменных
func checkVals(list []string) (ksReady int) {
var err error
for v, name := range list {
if v, err = strconv.Atoi(gotk.GetVar(name)); err != nil {
log.Fatal("checkVals:", err)
}
ksReady += v
}
return ksReady
}
////////////////////////////////////////////////////////
//////////////// IMIT вспомогательный поток //////////
////////////////////////////////////////////////////////
func imit(matherId string, ch chan changes, abrt chan struct{}) {
log.Println("------------ Imit started --------------")
// определить идентификаторы существующих потоков
gotk.Eval(`puts "*** Existing threads: [thread::names]"`)
// установить переменную с полученным значением идентификатора материнского потока
gotk.SetVar("id", matherId)
// отправить команду на изменение label .vk
gotk.Eval(` thread::send $id [list .vk config -bg green]
thread::send $id [list .fr.chan4 config -text К -bg green]
`)
log.Println("------------ Imit finished --------------")
}