首頁>技術>

生命不止,繼續 go go go!!!

golang官方並沒有提供Windows gui庫,但是今天還是要跟大家分享一下使用golang開發Windows桌面程式,當然又是面向github程式設計了。

知乎上有一個問答:golang為什麼沒有官方的gui包?

這裡,主要使用第三方庫lxn/walk,進行Windows GUI程式設計。

lxn/walk

github地址:https://github.com/lxn/walk

star:2018

描述:A Windows GUI toolkit for the Go Programming Language

獲取:

go get github.com/lxn/walk1

例子:

main.go

package mainimport (    "github.com/lxn/walk"    . "github.com/lxn/walk/declarative"    "strings")func main() {    var inTE, outTE *walk.TextEdit    MainWindow{        Title:   "SCREAMO",        MinSize: Size{600, 400},        Layout:  VBox{},        Children: []Widget{            HSplitter{                Children: []Widget{                    TextEdit{AssignTo: &inTE},                    TextEdit{AssignTo: &outTE, ReadOnly: true},                },            },            PushButton{                Text: "SCREAM",                OnClicked: func() {                    outTE.SetText(strings.ToUpper(inTE.Text()))                },            },        },    }.Run()}12345678910111213141516171819202122232425262728293031

go build生成go_windows_gui.exe。

go_windows_gui.exe.manifest

<?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>    </assembly>123456789

執行結果:

什麼是manifest

上面提到了manifest,這是幹什麼的呢?

https://msdn.microsoft.com/en-us/library/windows/desktop/aa375365(v=vs.85).aspx

介紹:Manifests are XML files that accompany and describe side-by-side assemblies or isolated applications. Manifests uniquely identify the assembly through the assembly’s assemblyIdentity element. They contain information used for binding and activation, such as COM classes, interfaces, and type libraries, that has traditionally been stored in the registry. Manifests also specify the files that make up the assembly and may include Windows classes if the assembly author wants them to be versioned. Side-by-side assemblies are not registered on the system, but are available to applications and other assemblies on the system that specify dependencies in manifest files.

是一種xml檔案,標明所依賴的side-by-side組建。

如果用VS開發,可以Set透過porperty->configuration properties->linker->manifest file->Generate manifest To Yes來自動建立manifest來指定系統的和CRT的assembly版本。

詳解觀察上面的manifest檔案:

<xml>這是xml宣告:

版本號----<?xml version="1.0"?>。這是必選項。 儘管以後的 XML 版本可能會更改該數字,但是 1.0 是當前的版本。編碼宣告------<?xml version="1.0" encoding="UTF-8"?>這是可選項。 如果使用編碼宣告,必須緊接在 XML 宣告的版本資訊之後,並且必須包含代表現有字元編碼的值。standalone表示該xml是不是獨立的,如果是yes,則表示這個XML文件時獨立的,不能引用外部的DTD規範檔案;如果是no,則該XML文件不是獨立的,表示可以用外部的DTD規範文件。1234567

<dependency>這一部分指明瞭其依賴於一個庫:

<assemblyIdentity>屬性裡面還分別是:type----系統型別,version----版本號,processorArchitecture----平臺環境,publicKeyToken----公匙12345
應用做一個巨醜無比的登入框

這裡用到了LineEdit、LineEdit控制元件

package mainimport (    "github.com/lxn/walk"    . "github.com/lxn/walk/declarative")func main() {    var usernameTE, passwordTE *walk.LineEdit    MainWindow{        Title:   "登入",        MinSize: Size{270, 290},        Layout:  VBox{},        Children: []Widget{            Composite{                Layout: Grid{Columns: 2, Spacing: 10},                Children: []Widget{                    VSplitter{                        Children: []Widget{                            Label{                                Text: "使用者名稱",                            },                        },                    },                    VSplitter{                        Children: []Widget{                            LineEdit{                                MinSize:  Size{160, 0},                                AssignTo: &usernameTE,                            },                        },                    },                    VSplitter{                        Children: []Widget{                            Label{MaxSize: Size{160, 40},                                Text: "密碼",                            },                        },                    },                    VSplitter{                        Children: []Widget{                            LineEdit{                                MinSize:  Size{160, 0},                                AssignTo: &passwordTE,                            },                        },                    },                },            },            PushButton{                Text:    "登入",                MinSize: Size{120, 50},                OnClicked: func() {                    if usernameTE.Text() == "" {                        var tmp walk.Form                        walk.MsgBox(tmp, "使用者名稱為空", "", walk.MsgBoxIconInformation)                        return                    }                    if passwordTE.Text() == "" {                        var tmp walk.Form                        walk.MsgBox(tmp, "密碼為空", "", walk.MsgBoxIconInformation)                        return                    }                },            },        },    }.Run()}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970

效果:

TableView的使用

這裡主要使用的是TableView控制元件,程式碼參考github:

package mainimport (    "fmt"    "sort"    "github.com/lxn/walk"    . "github.com/lxn/walk/declarative")type Condom struct {    Index   int    Name    string    Price   int    checked bool}type CondomModel struct {    walk.TableModelBase    walk.SorterBase    sortColumn int    sortOrder  walk.SortOrder    items      []*Condom}func (m *CondomModel) RowCount() int {    return len(m.items)}func (m *CondomModel) Value(row, col int) interface{} {    item := m.items[row]    switch col {    case 0:        return item.Index    case 1:        return item.Name    case 2:        return item.Price    }    panic("unexpected col")}func (m *CondomModel) Checked(row int) bool {    return m.items[row].checked}func (m *CondomModel) SetChecked(row int, checked bool) error {    m.items[row].checked = checked    return nil}func (m *CondomModel) Sort(col int, order walk.SortOrder) error {    m.sortColumn, m.sortOrder = col, order    sort.Stable(m)    return m.SorterBase.Sort(col, order)}func (m *CondomModel) Len() int {    return len(m.items)}func (m *CondomModel) Less(i, j int) bool {    a, b := m.items[i], m.items[j]    c := func(ls bool) bool {        if m.sortOrder == walk.SortAscending {            return ls        }        return !ls    }    switch m.sortColumn {    case 0:        return c(a.Index < b.Index)    case 1:        return c(a.Name < b.Name)    case 2:        return c(a.Price < b.Price)    }    panic("unreachable")}func (m *CondomModel) Swap(i, j int) {    m.items[i], m.items[j] = m.items[j], m.items[i]}func NewCondomModel() *CondomModel {    m := new(CondomModel)    m.items = make([]*Condom, 3)    m.items[0] = &Condom{        Index: 0,        Name:  "杜蕾斯",        Price: 20,    }    m.items[1] = &Condom{        Index: 1,        Name:  "傑士邦",        Price: 18,    }    m.items[2] = &Condom{        Index: 2,        Name:  "岡本",        Price: 19,    }    return m}type CondomMainWindow struct {    *walk.MainWindow    model *CondomModel    tv    *walk.TableView}func main() {    mw := &CondomMainWindow{model: NewCondomModel()}    MainWindow{        AssignTo: &mw.MainWindow,        Title:    "Condom展示",        Size:     Size{800, 600},        Layout:   VBox{},        Children: []Widget{            Composite{                Layout: HBox{MarginsZero: true},                Children: []Widget{                    HSpacer{},                    PushButton{                        Text: "Add",                        OnClicked: func() {                            mw.model.items = append(mw.model.items, &Condom{                                Index: mw.model.Len() + 1,                                Name:  "第六感",                                Price: mw.model.Len() * 5,                            })                            mw.model.PublishRowsReset()                            mw.tv.SetSelectedIndexes([]int{})                        },                    },                    PushButton{                        Text: "Delete",                        OnClicked: func() {                            items := []*Condom{}                            remove := mw.tv.SelectedIndexes()                            for i, x := range mw.model.items {                                remove_ok := false                                for _, j := range remove {                                    if i == j {                                        remove_ok = true                                    }                                }                                if !remove_ok {                                    items = append(items, x)                                }                            }                            mw.model.items = items                            mw.model.PublishRowsReset()                            mw.tv.SetSelectedIndexes([]int{})                        },                    },                    PushButton{                        Text: "ExecChecked",                        OnClicked: func() {                            for _, x := range mw.model.items {                                if x.checked {                                    fmt.Printf("checked: %v\n", x)                                }                            }                            fmt.Println()                        },                    },                    PushButton{                        Text: "AddPriceChecked",                        OnClicked: func() {                            for i, x := range mw.model.items {                                if x.checked {                                    x.Price++                                    mw.model.PublishRowChanged(i)                                }                            }                        },                    },                },            },            Composite{                Layout: VBox{},                ContextMenuItems: []MenuItem{                    Action{                        Text:        "I&nfo",                        OnTriggered: mw.tv_ItemActivated,                    },                    Action{                        Text: "E&xit",                        OnTriggered: func() {                            mw.Close()                        },                    },                },                Children: []Widget{                    TableView{                        AssignTo:         &mw.tv,                        CheckBoxes:       true,                        ColumnsOrderable: true,                        MultiSelection:   true,                        Columns: []TableViewColumn{                            {Title: "編號"},                            {Title: "名稱"},                            {Title: "價格"},                        },                        Model: mw.model,                        OnCurrentIndexChanged: func() {                            i := mw.tv.CurrentIndex()                            if 0 <= i {                                fmt.Printf("OnCurrentIndexChanged: %v\n", mw.model.items[i].Name)                            }                        },                        OnItemActivated: mw.tv_ItemActivated,                    },                },            },        },    }.Run()}func (mw *CondomMainWindow) tv_ItemActivated() {    msg := ``    for _, i := range mw.tv.SelectedIndexes() {        msg = msg + "\n" + mw.model.items[i].Name    }    walk.MsgBox(mw, "title", msg, walk.MsgBoxIconInformation)}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240

效果:

檔案選擇器(加入了icon)

這裡就是呼叫Windows的檔案選擇框主要是使用:FileDialog

package mainimport (    "github.com/lxn/walk"    . "github.com/lxn/walk/declarative")import (    "fmt"    "os")type MyMainWindow struct {    *walk.MainWindow    edit *walk.TextEdit    path string}func main() {    mw := &MyMainWindow{}    MW := MainWindow{        AssignTo: &mw.MainWindow,        Icon:     "test.ico",        Title:    "檔案選擇對話方塊",        MinSize:  Size{150, 200},        Size:     Size{300, 400},        Layout:   VBox{},        Children: []Widget{            TextEdit{                AssignTo: &mw.edit,            },            PushButton{                Text:      "開啟",                OnClicked: mw.pbClicked,            },        },    }    if _, err := MW.Run(); err != nil {        fmt.Fprintln(os.Stderr, err)        os.Exit(1)    }}func (mw *MyMainWindow) pbClicked() {    dlg := new(walk.FileDialog)    dlg.FilePath = mw.path    dlg.Title = "Select File"    dlg.Filter = "Exe files (*.exe)|*.exe|All files (*.*)|*.*"    if ok, err := dlg.ShowOpen(mw); err != nil {        mw.edit.AppendText("Error : File Open\r\n")        return    } else if !ok {        mw.edit.AppendText("Cancel\r\n")        return    }    mw.path = dlg.FilePath    s := fmt.Sprintf("Select : %s\r\n", mw.path)    mw.edit.AppendText(s)}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

效果:

文字檢索器
package mainimport (    "fmt"    "log"    "strings"    "github.com/lxn/walk"    . "github.com/lxn/walk/declarative")func main() {    mw := &MyMainWindow{}    if _, err := (MainWindow{        AssignTo: &mw.MainWindow,        Title:    "SearchBox",        Icon:     "test.ico",        MinSize:  Size{300, 400},        Layout:   VBox{},        Children: []Widget{            GroupBox{                Layout: HBox{},                Children: []Widget{                    LineEdit{                        AssignTo: &mw.searchBox,                    },                    PushButton{                        Text:      "檢索",                        OnClicked: mw.clicked,                    },                },            },            TextEdit{                AssignTo: &mw.textArea,            },            ListBox{                AssignTo: &mw.results,                Row:      5,            },        },    }.Run()); err != nil {        log.Fatal(err)    }}type MyMainWindow struct {    *walk.MainWindow    searchBox *walk.LineEdit    textArea  *walk.TextEdit    results   *walk.ListBox}func (mw *MyMainWindow) clicked() {    word := mw.searchBox.Text()    text := mw.textArea.Text()    model := []string{}    for _, i := range search(text, word) {        model = append(model, fmt.Sprintf("%d檢索成功", i))    }    log.Print(model)    mw.results.SetModel(model)}func search(text, word string) (result []int) {    result = []int{}    i := 0    for j, _ := range text {        if strings.HasPrefix(text[j:], word) {            log.Print(i)            result = append(result, i)        }        i += 1    }    return}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778

效果:

郵件群發器

別人寫的郵件群發器,出自:https://studygolang.com/articles/2078關於golang中stmp的使用,請看部落格:Go實戰–透過net/smtp傳送郵件(The way to go)

package mainimport (    "bufio"    "encoding/gob"    "errors"    "fmt"    "io"    "net/smtp"    "os"    "strconv"    "strings"    "time")import (    "github.com/lxn/walk"    . "github.com/lxn/walk/declarative")type ShuJu struct {    Name    string    Pwd     string    Host    string    Subject string    Body    string    Send    string}func SendMail(user, password, host, to, subject, body, mailtype string) error {    fmt.Println("Send to " + to)    hp := strings.Split(host, ":")    auth := smtp.PlainAuth("", user, password, hp[0])    var content_type string    if mailtype == "html" {        content_type = "Content-Type: text/html;charset=UTF-8"    } else {        content_type = "Content-Type: text/plain;charset=UTF-8"    }    body = strings.TrimSpace(body)    msg := []byte("To: " + to + "\r\nFrom: " + user + "<" + user + ">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body)    send_to := strings.Split(to, ";")    err := smtp.SendMail(host, auth, user, send_to, msg)    if err != nil {        fmt.Println(err.Error())    }    return err}func readLine2Array(filename string) ([]string, error) {    result := make([]string, 0)    file, err := os.Open(filename)    if err != nil {        return result, errors.New("Open file failed.")    }    defer file.Close()    bf := bufio.NewReader(file)    for {        line, isPrefix, err1 := bf.ReadLine()        if err1 != nil {            if err1 != io.EOF {                return result, errors.New("ReadLine no finish")            }            break        }        if isPrefix {            return result, errors.New("Line is too long")        }        str := string(line)        result = append(result, str)    }    return result, nil}func DelArrayVar(arr []string, str string) []string {    str = strings.TrimSpace(str)    for i, v := range arr {        v = strings.TrimSpace(v)        if v == str {            if i == len(arr) {                return arr[0 : i-1]            }            if i == 0 {                return arr[1:len(arr)]            }            a1 := arr[0:i]            a2 := arr[i+1 : len(arr)]            return append(a1, a2...)        }    }    return arr}func LoadData() {    fmt.Println("LoadData")    file, err := os.Open("data.dat")    defer file.Close()    if err != nil {        fmt.Println(err.Error())        SJ.Name = "使用者名稱"        SJ.Pwd = "使用者密碼"        SJ.Host = "SMTP伺服器:埠"        SJ.Subject = "郵件主題"        SJ.Body = "郵件內容"        SJ.Send = "要傳送的郵箱,每行一個"        return    }    dec := gob.NewDecoder(file)    err2 := dec.Decode(&SJ)    if err2 != nil {        fmt.Println(err2.Error())        SJ.Name = "使用者名稱"        SJ.Pwd = "使用者密碼"        SJ.Host = "SMTP伺服器:埠"        SJ.Subject = "郵件主題"        SJ.Body = "郵件內容"        SJ.Send = "要傳送的郵箱,每行一個"    }}func SaveData() {    fmt.Println("SaveData")    file, err := os.Create("data.dat")    defer file.Close()    if err != nil {        fmt.Println(err)    }    enc := gob.NewEncoder(file)    err2 := enc.Encode(SJ)    if err2 != nil {        fmt.Println(err2)    }}var SJ ShuJuvar runing boolvar chEnd chan boolfunc main() {    LoadData()    chEnd = make(chan bool)    var emails, body, msgbox *walk.TextEdit    var user, password, host, subject *walk.LineEdit    var startBtn *walk.PushButton    MainWindow{        Title:   "郵件傳送器",        MinSize: Size{800, 600},        Layout:  HBox{},        Children: []Widget{            TextEdit{AssignTo: &emails, Text: SJ.Send, ToolTipText: "待發送郵件列表,每行一個"},            VSplitter{                Children: []Widget{                    LineEdit{AssignTo: &user, Text: SJ.Name, CueBanner: "請輸入郵箱使用者名稱"},                    LineEdit{AssignTo: &password, Text: SJ.Pwd, PasswordMode: true, CueBanner: "請輸入郵箱登入密碼"},                    LineEdit{AssignTo: &host, Text: SJ.Host, CueBanner: "SMTP伺服器:埠"},                    LineEdit{AssignTo: &subject, Text: SJ.Subject, CueBanner: "請輸入郵件主題……"},                    TextEdit{AssignTo: &body, Text: SJ.Body, ToolTipText: "請輸入郵件內容", ColumnSpan: 2},                    TextEdit{AssignTo: &msgbox, ReadOnly: true},                    PushButton{                        AssignTo: &startBtn,                        Text:     "開始群發",                        OnClicked: func() {                            SJ.Name = user.Text()                            SJ.Pwd = password.Text()                            SJ.Host = host.Text()                            SJ.Subject = subject.Text()                            SJ.Body = body.Text()                            SJ.Send = emails.Text()                            SaveData()                            if runing == false {                                runing = true                                startBtn.SetText("停止傳送")                                go sendThread(msgbox, emails)                            } else {                                runing = false                                startBtn.SetText("開始群發")                            }                        },                    },                },            },        },    }.Run()}func sendThread(msgbox, es *walk.TextEdit) {    sendTo := strings.Split(SJ.Send, "\r\n")    susscess := 0    count := len(sendTo)    for index, to := range sendTo {        if runing == false {            break        }        msgbox.SetText("傳送到" + to + "..." + strconv.Itoa((index/count)*100) + "%")        err := SendMail(SJ.Name, SJ.Pwd, SJ.Host, to, SJ.Subject, SJ.Body, "html")        if err != nil {            msgbox.AppendText("\r\n失敗:" + err.Error() + "\r\n")            if err.Error() == "550 Mailbox not found or access denied" {                SJ.Send = strings.Join(DelArrayVar(strings.Split(SJ.Send, "\r\n"), to), "\r\n")                es.SetText(SJ.Send)            }            time.Sleep(1 * time.Second)            continue        } else {            susscess++            msgbox.AppendText("\r\n傳送成功!")            SJ.Send = strings.Join(DelArrayVar(strings.Split(SJ.Send, "\r\n"), to), "\r\n")            es.SetText(SJ.Send)        }        time.Sleep(1 * time.Second)    }    SaveData()    msgbox.AppendText("停止傳送!成功 " + strconv.Itoa(susscess) + " 條\r\n")}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216

效果:

19
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Go實戰--實現一個自己的網路請求日誌httplogger