fyne学习笔记

  • Install MSYS2 from msys2.org
  • Once installed do not use the MSYS terminal that opens
  • Open “MSYS2 MinGW 64-bit” from the start menu
  • Proxy Setting(CN)
1
2
3
4
export HTTP_PROXY="http://127.0.0.1:8889"
export HTTPS_PROXY=$HTTP_PROXY
export http_proxy=$HTTP_PROXY
export https_proxy=$HTTP_PROXY
  • Execute the following commands (if asked for install options be sure to choose “all”):
1
2
pacman -Syu
pacman -S git mingw-w64-x86_64-toolchain
  • You will need to add /c/Program\ Files/Go/bin and ~/Go/bin to your $PATH, for MSYS2 you can paste the following command into your terminal:
1
echo "export PATH=\$PATH:/c/Program\ Files/Go/bin:~/Go/bin" >> ~/.bashrc
  • For the compiler to work on other terminals you will need to set up the windows %PATH% variable to find these tools. Go to the “Edit the system environment variables” control panel, tap “Advanced” and add “C:\msys64\mingw64\bin” to the Path list
1
2
go get fyne.io/fyne/v2@latest
go install fyne.io/fyne/v2/cmd/fyne@latest

https://www.cnblogs.com/vcciccv/p/16842905.html

使用环境变量

1
2
fontPath := "C:\\Windows\\Fonts\\SIMYOU.TTF"`
os.Setenv("FYNE_FONT", fontPath)

然后再myApp = app.New()创建APP实例…..

设置了标题栏任务栏自然就有图标了

1
2
3
4
5
6
7
8
9
import (_ "embed")
//go:embed PDF.png
var icon []byte

func main(){
  myApp = app.New()
  myWindow = myApp.NewWindow("PDF_Downloader")
  myWindow.SetIcon(fyne.NewStaticResource("Icon", icon))
}

此方法有不明缺陷,可以尝试第二种方法,使用fyne bundle -append -o bundled.go -name windowICON PDF.png,其中-append为第一次使用时不需要,用于第一次之后追加不同资源文件到同一个bundled.go-name后面跟自定义的变量名,PDF.png为要内嵌的静态资源文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
>fyne bundle -h                                    
NAME:
   fyne bundle - Embeds static content into your go application.

USAGE:
   fyne bundle [command options] [arguments...]

DESCRIPTION:
   Each resource will have a generated filename unless specified.

OPTIONS:
   --name value                  The variable name to assign the resource (file mode only).
   --output value, -o value      Specify an output file instead of printing to standard output.
   --package value, --pkg value  The package to output in headers (if not appending). (default: "main")
   --prefix value                A prefix for variables (ignored if name is set). (default: "resource")
   --append, -a                  Append an existing go file (don't output headers). (default: false)
   --help, -h                    show help (default: false)
  1. 安装 rsrc
1
go install github.com/akavel/rsrc
  1. 生成资源文件: 使用 rsrc 为你的应用程序图标生成 .syso 文件。假设你的图标文件名为 icon.ico(必须为ico格式的图标文件):
1
rsrc -ico icon.ico

这将生成一个名为 .syso 的文件。 编译时会自动利用该.syso 文件为程序本身添加图标 这里有个坑,如果碰到用rsrc基于不同图标生成资源文件,但是编译后exe的图标仍然是旧图标,那么应该考虑清一下资源管理器的图标缓存,本人以为是go编译缓存或者rsrc缓存等的问题…………

fyne package -os windows -icon xx.jpg[xx.png] 缺点是不能达到go build -ldflags "-w -s"减少程序体积的效果. 下面是fyne package支持的参数

 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
>fyne package -h
NAME:
   fyne package - Packages an application for distribution.

USAGE:
   fyne package [command options] [arguments...]

DESCRIPTION:
   You may specify the -executable to package, otherwise -sourceDir will be built.

OPTIONS:
   --target value, --os value         The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios, iossimulator, wasm, js, web).
   --executable value, --exe value    The path to the executable, default is the current dir main binary
   --name value                       The name of the application, default is the executable file name
   --tags value                       A comma-separated list of build tags.
   --appVersion value                 Version number in the form x, x.y or x.y.z semantic version
   --appBuild value                   Build number, should be greater than 0 and incremented for each build (default: 0)
   --sourceDir value, --src value     The directory to package, if executable is not set.
   --icon value                       The name of the application icon file.
   --use-raw-icon                     Skip any OS-specific icon pre-processing (default: false)
   --appID value, --id value          For Android, darwin, iOS and Windows targets an appID in the form of a reversed domain name is required, for ios this must match a valid provisioning profile
   --certificate value, --cert value  iOS/macOS/Windows: name of the certificate to sign the build
   --profile value                    iOS/macOS: name of the provisioning profile for this build (default: "XCWildcard")
   --release                          Enable installation in release mode (disable debug etc). (default: false)
   --metadata value                   Specify custom metadata key value pair that you do not want to store in your FyneApp.toml (key=value)
   --help, -h                         show help (default: false)

编译时添加参数"-ldflags -H=windowsgui"

1
go build -ldflags -H=windowsgui .

多个ldflags参数的处理,其中 “-w -s"用于减少程序体积。

1
go build -ldflags "-H=windowsgui -w -s" .
  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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
package main

import (
	"compress/gzip"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"regexp"
	"strings"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
)

var (
	base_url   string
	isValidUrl bool
	myApp      fyne.App
	myWindow   fyne.Window
	// 创建一个 HTTP 客户端
	client = &http.Client{}
)

var appICON = &fyne.StaticResource{
	StaticName: "pdf.ico",
	StaticContent: []byte("\x00"),
}
var windowICON = &fyne.StaticResource{
	StaticName: "PDF.png",
	StaticContent: []byte("\x89PNG"),
}

func init() {
	fontPath := "C:\\Windows\\Fonts\\SIMYOU.TTF"
	os.Setenv("FYNE_FONT", fontPath)
	myApp = app.New()
	myWindow = myApp.NewWindow("PDF_Downloader")

	// 设置默认窗口大小
	myWindow.Resize(fyne.NewSize(800, 100)) // 这里可以设置为您想要的尺寸
}

func main() {
	// 尝试使用嵌入的图标
	// iconResource := fyne.NewStaticResource("PDFIcon", icon)

	myWindow.SetIcon(windowICON)
	myApp.SetIcon(appICON)
	label_input := widget.NewLabel("在下面输入网址:")
	label_status := widget.NewLabel("")
	entry := widget.NewMultiLineEntry()
	entry.SetPlaceHolder("我这里很空...") //
	// entry.SetText("我这里好空...")
	button := widget.NewButtonWithIcon("下载", theme.DownloadIcon(), func() {
		go funcForDownloadBTN(entry.Text, label_status, myWindow)
	})

	// 创建一个水平布局的容器,将按钮放在中间
	buttonContainer := container.NewHBox(button, label_status)

	// 将所有组件垂直排列
	content := container.NewVBox(
		label_input,
		entry,
		buttonContainer, // 使用按钮容器代替直接放置按钮
	)
	myWindow.SetContent(content)
	myWindow.ShowAndRun()
}

// 验证 URL 是否有效,并返回基本 url
func isValidURL(u string) (bool, string) {
	parsedURL, err := url.ParseRequestURI(u)
	if err != nil {
		fmt.Println("Error parsing URL:", err)
		return false, ""
	} else if err == nil {
		base_url := parsedURL.Scheme + "://" + parsedURL.Host
		return true, base_url
	}
	return false, ""

}

func fetchHtml(pageURL string) (string, error) {
	// 创建一个 HTTP 客户端
	client := &http.Client{}

	// 创建一个请求
	req, err := http.NewRequest("GET", pageURL, nil)
	if err != nil {
		dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
	}

	// 添加自定义的头部
	req.Header.Set("Proxy-Connection", "keep-alive")
	req.Header.Set("Cache-Control", "max-age=0")
	req.Header.Set("Upgrade-Insecure-Requests", "1")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36")
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
	req.Header.Set("Accept-Encoding", "gzip, deflate")

	// 添加 Cookie
	// 如果你有多个 Cookie 需要添加,可以重复这个步骤
	req.AddCookie(&http.Cookie{Name: "YourCookieName", Value: "YourCookieValue"})

	// 使用自定义的请求进行 HTTP 调用
	resp, err := client.Do(req)
	if err != nil {
		dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
	}

	// 检查响应头部中的 Content-Encoding 字段来判断内容是否被压缩
	contentEncoding := resp.Header.Get("Content-Encoding")

	// 根据 Content-Encoding 解压内容
	var reader io.ReadCloser
	switch {
	case strings.Contains(contentEncoding, "gzip"):
		// 如果内容是gzip压缩的,则使用gzip.Reader解压
		var err error
		reader, err = gzip.NewReader(resp.Body)
		if err != nil {
			dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
		}
	default:
		// 如果内容没有压缩,则直接使用响应体
		reader = resp.Body
	}
	defer reader.Close()

	// 读取响应内容
	html, err := io.ReadAll(reader)
	if err != nil {
		dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
	}

	// 打印 HTML 内容
	// fmt.Println(string(html))
	return string(html), nil
}

func DownloadAndSavePDF(base_url, src_url, Title string, label_status *widget.Label) {
	label_status.SetText("正在下载,请稍后...")
	// 发起 HTTP GET 请求
	// 创建一个请求
	pdfurl := base_url + strings.Split(src_url, "=")[1]
	req, err := http.NewRequest("GET", pdfurl, nil)
	if err != nil {
		dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
	}

	// 添加自定义的头部
	req.Header.Set("Referer", base_url+src_url)
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36")

	// 添加 Cookie
	// 如果你有多个 Cookie 需要添加,可以重复这个步骤
	// req.AddCookie(&http.Cookie{Name: "YourCookieName", Value: "YourCookieValue"})

	// 使用自定义的请求进行 HTTP 调用
	resp, err := client.Do(req)
	if err != nil {
		// fmt.Println("line 217:", err)
		dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
		// return ""
	}
	defer resp.Body.Close()

	// 检查响应头部中的 Content-Encoding 字段来判断内容是否被压缩
	contentEncoding := resp.Header.Get("Content-Encoding")
	// 根据 Content-Encoding 解压内容
	var reader io.ReadCloser
	switch {
	case strings.Contains(contentEncoding, "gzip"):
		// 如果内容是gzip压缩的,则使用gzip.Reader解压
		var err error
		reader, err = gzip.NewReader(resp.Body)
		if err != nil {
			dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
		}
	default:
		// 如果内容没有压缩,则直接使用响应体
		reader = resp.Body
	}
	defer reader.Close()

	// 构建桌面路径
	// 获取当前用户信息
	currentUser, _ := user.Current()
	desktopPath := filepath.Join(currentUser.HomeDir, "Desktop")
	// timenmae := time.Now().Format("20060102_1504")
	pdfFileFullPath := desktopPath + "\\" + Title + ".pdf"
	// 创建本地文件
	file, err := os.Create(pdfFileFullPath)
	if err != nil {
		fmt.Println("line 231:", err)
		dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
	}
	defer file.Close()

	// 将响应内容写入文件
	_, err = io.Copy(file, reader)
	if err != nil {
		fmt.Println("line 239:", err)
		dialog.ShowError(errors.New("发生意外"+err.Error()), myWindow)
	}
	label_status.SetText("文件下载成功,保存在\"" + pdfFileFullPath + "\",已打开并聚焦,注意新打开文件管理器。")
	// return pdfFileFullPath
	LocatFile(pdfFileFullPath)
}

func extract_SrcUrl(html_str string) string {
	re := regexp.MustCompile(`(?i)<iframe[^>]+src\s*=\s*["']([^"']+)["']`)
	match := re.FindStringSubmatch(string(html_str))
	if len(match) >= 2 {
		return match[1]
	}
	return ""
}

func extract_Title(html_str string) string {
	re := regexp.MustCompile(`(?s)<td[^>]*class="wz_title"[^>]*>(.*?)<\/td>`)
	match := re.FindStringSubmatch(html_str)
	if len(match) >= 2 {
		return match[1]
	}
	return ""
}

func funcForDownloadBTN(inputText string, label_status *widget.Label, myWindow fyne.Window) {
	// fmt.Println(inputText)
	isValidUrl, base_url = isValidURL(inputText)
	if isValidUrl {
		html, _ := fetchHtml(inputText)
		SrcUrl := extract_SrcUrl(html)
		Title := extract_Title(html)
		if SrcUrl != "" {
			DownloadAndSavePDF(base_url, SrcUrl, Title, label_status)
		} else {
			dialog.ShowError(errors.New("该网页中无PDF文件链接!"), myWindow)
			label_status.SetText("")
		}
	} else {
		dialog.ShowError(errors.New("请输入正确的网址。"), myWindow)
	}
}

func LocatFile(filePath string) {
	// Windows Explorer 需要的参数是文件的目录
	dir := filepath.Dir(filePath)
	// 构造并执行命令
	cmd := exec.Command("explorer", "/select,", filePath)
	cmd.Dir = dir
	if err := cmd.Start(); err != nil {
		panic(err)
	}
}

https://darjun.github.io/2020/06/15/godailylib/fyne/