檔案對話方塊
開啟、儲存及資料夾選取對話方塊
檔案對話方塊
Wails 提供具備平台原生外觀的原生檔案對話方塊,可用於開啟檔案、儲存檔案及選取資料夾。API 簡潔,並支援檔案類型篩選、多重選取和預設位置。

Wails API 會將操作委派給作業系統的選取器,因此可保留使用者熟悉的導覽、篩選及選取行為。
建立檔案對話方塊
透過app.Dialog管理器存取檔案對話方塊:
go
app.Dialog.OpenFile()
app.Dialog.SaveFile()開啟檔案對話方塊
選取要開啟的檔案:
go
path, err := app.Dialog.OpenFile().
SetTitle("Select Image").
AddFilter("Images", "*.png;*.jpg;*.gif").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err != nil || path == "" {
return
}
openFile(path)使用情境:
- 開啟文件
- 匯入檔案
- 載入圖片
- 選取設定檔
選取單一檔案
go
path, err := app.Dialog.OpenFile().
SetTitle("Open Document").
AddFilter("Text Files", "*.txt").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err != nil || path == "" {
// User cancelled or error occurred
return
}
// Use selected file
data, _ := os.ReadFile(path)選取多個檔案
go
paths, err := app.Dialog.OpenFile().
SetTitle("Select Images").
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif").
PromptForMultipleSelection()
if err != nil {
return
}
// Process all selected files
for _, path := range paths {
processFile(path)
}指定預設目錄
go
path, err := app.Dialog.OpenFile().
SetTitle("Open File").
SetDirectory("/Users/me/Documents").
PromptForSingleSelection()儲存檔案對話方塊
選擇儲存位置:
go
path, err := app.Dialog.SaveFile().
SetFilename("document.txt").
AddFilter("Text Files", "*.txt").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err != nil || path == "" {
return
}
saveFile(path, data)使用情境:
- 儲存文件
- 匯出資料
- 建立新檔案
- 另存新檔…
指定預設檔名
go
path, err := app.Dialog.SaveFile().
SetFilename("export.csv").
AddFilter("CSV Files", "*.csv").
PromptForSingleSelection()指定預設目錄
go
path, err := app.Dialog.SaveFile().
SetDirectory("/Users/me/Documents").
SetFilename("untitled.txt").
PromptForSingleSelection()覆寫確認
go
path, err := app.Dialog.SaveFile().
SetFilename("document.txt").
PromptForSingleSelection()
if err != nil || path == "" {
return
}
// Check if file exists
if _, err := os.Stat(path); err == nil {
dialog := app.Dialog.Question().
SetTitle("Confirm Overwrite").
SetMessage("File already exists. Overwrite?")
overwriteBtn := dialog.AddButton("Overwrite")
overwriteBtn.OnClick(func() {
saveFile(path, data)
})
cancelBtn := dialog.AddButton("Cancel")
dialog.SetDefaultButton(cancelBtn)
dialog.SetCancelButton(cancelBtn)
dialog.Show()
return
}
saveFile(path, data)選取資料夾對話方塊
啟用目錄選取功能,透過開啟檔案對話方塊選擇目錄:
go
path, err := app.Dialog.OpenFile().
SetTitle("Select Output Folder").
CanChooseDirectories(true).
CanChooseFiles(false).
PromptForSingleSelection()
if err != nil || path == "" {
return
}
exportToFolder(path)使用情境:
- 選擇輸出目錄
- 選取工作區
- 選擇備份位置
- 選擇安裝目錄
指定預設目錄
go
path, err := app.Dialog.OpenFile().
SetTitle("Select Folder").
SetDirectory("/Users/me/Documents").
CanChooseDirectories(true).
CanChooseFiles(false).
PromptForSingleSelection()檔案篩選器
使用AddFilter()方法將檔案類型篩選器新增至對話方塊。每次呼叫都會新增一個篩選選項。
基本篩選器
go
path, _ := app.Dialog.OpenFile().
AddFilter("Text Files", "*.txt").
AddFilter("All Files", "*.*").
PromptForSingleSelection()多個副檔名
使用分號在單一篩選器中指定多個副檔名:
go
dialog := app.Dialog.OpenFile().
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp").
AddFilter("Documents", "*.txt;*.doc;*.docx;*.pdf").
AddFilter("All Files", "*.*")模式格式
使用分號分隔單一篩選器中的多個副檔名:
go
// Multiple extensions separated by semicolons
AddFilter("Images", "*.png;*.jpg;*.gif")完整範例
開啟圖片檔案
go
func openImage(app *application.App) (image.Image, error) {
path, err := app.Dialog.OpenFile().
SetTitle("Select Image").
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp").
PromptForSingleSelection()
if err != nil {
return nil, err
}
if path == "" {
return nil, errors.New("no file selected")
}
// Open and decode image
file, err := os.Open(path)
if err != nil {
app.Dialog.Error().
SetTitle("Open Failed").
SetMessage(err.Error()).
Show()
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
app.Dialog.Error().
SetTitle("Invalid Image").
SetMessage("Could not decode image file.").
Show()
return nil, err
}
return img, nil
}經驗證後儲存文件
go
func saveDocument(app *application.App, content string) {
path, err := app.Dialog.SaveFile().
SetFilename("document.txt").
AddFilter("Text Files", "*.txt").
AddFilter("Markdown Files", "*.md").
AddFilter("All Files", "*.*").
PromptForSingleSelection()
if err != nil || path == "" {
return
}
// Validate extension
ext := filepath.Ext(path)
if ext != ".txt" && ext != ".md" {
dialog := app.Dialog.Question().
SetTitle("Confirm Extension").
SetMessage(fmt.Sprintf("Save as %s file?", ext))
saveBtn := dialog.AddButton("Save")
saveBtn.OnClick(func() {
doSave(app, path, content)
})
cancelBtn := dialog.AddButton("Cancel")
dialog.SetDefaultButton(cancelBtn)
dialog.SetCancelButton(cancelBtn)
dialog.Show()
return
}
doSave(app, path, content)
}
func doSave(app *application.App, path, content string) {
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
app.Dialog.Error().
SetTitle("Save Failed").
SetMessage(err.Error()).
Show()
return
}
app.Dialog.Info().
SetTitle("Saved").
SetMessage("Document saved successfully!").
Show()
}批次處理檔案
go
func processMultipleFiles(app *application.App) {
paths, err := app.Dialog.OpenFile().
SetTitle("Select Files to Process").
AddFilter("Images", "*.png;*.jpg").
PromptForMultipleSelection()
if err != nil || len(paths) == 0 {
return
}
// Confirm processing
dialog := app.Dialog.Question().
SetTitle("Confirm Processing").
SetMessage(fmt.Sprintf("Process %d file(s)?", len(paths)))
processBtn := dialog.AddButton("Process")
processBtn.OnClick(func() {
// Process files
var errs []error
for i, path := range paths {
if err := processFile(path); err != nil {
errs = append(errs, err)
}
// Update progress
// app.Event.Emit("progress", map[string]interface{}{
// "current": i + 1,
// "total": len(paths),
// })
_ = i // suppress unused variable warning in example
}
// Show results
if len(errs) > 0 {
app.Dialog.Warning().
SetTitle("Processing Complete").
SetMessage(fmt.Sprintf("Processed %d files with %d errors.",
len(paths), len(errs))).
Show()
} else {
app.Dialog.Info().
SetTitle("Success").
SetMessage(fmt.Sprintf("Processed %d files successfully!", len(paths))).
Show()
}
})
cancelBtn := dialog.AddButton("Cancel")
dialog.SetCancelButton(cancelBtn)
dialog.Show()
}選取資料夾後匯出
go
func exportData(app *application.App, data []byte) {
// Select output folder
folder, err := app.Dialog.OpenFile().
SetTitle("Select Export Folder").
SetDirectory(getDefaultExportFolder()).
CanChooseDirectories(true).
CanChooseFiles(false).
PromptForSingleSelection()
if err != nil || folder == "" {
return
}
// Generate filename
filename := fmt.Sprintf("export_%s.csv",
time.Now().Format("2006-01-02_15-04-05"))
path := filepath.Join(folder, filename)
// Save file
if err := os.WriteFile(path, data, 0644); err != nil {
app.Dialog.Error().
SetTitle("Export Failed").
SetMessage(err.Error()).
Show()
return
}
// Show success with option to open folder
dialog := app.Dialog.Question().
SetTitle("Export Complete").
SetMessage(fmt.Sprintf("Exported to %s", filename))
openBtn := dialog.AddButton("Open Folder")
openBtn.OnClick(func() {
openFolder(folder)
})
dialog.AddButton("OK")
dialog.Show()
}經驗證後匯入
go
func importConfiguration(app *application.App) {
path, err := app.Dialog.OpenFile().
SetTitle("Import Configuration").
AddFilter("JSON Files", "*.json").
AddFilter("YAML Files", "*.yaml;*.yml").
PromptForSingleSelection()
if err != nil || path == "" {
return
}
// Read file
data, err := os.ReadFile(path)
if err != nil {
app.Dialog.Error().
SetTitle("Read Failed").
SetMessage(err.Error()).
Show()
return
}
// Validate configuration
config, err := parseConfig(data)
if err != nil {
app.Dialog.Error().
SetTitle("Invalid Configuration").
SetMessage("File is not a valid configuration.").
Show()
return
}
// Confirm import
dialog := app.Dialog.Question().
SetTitle("Confirm Import").
SetMessage("Import this configuration?")
importBtn := dialog.AddButton("Import")
importBtn.OnClick(func() {
// Apply configuration
if err := applyConfig(config); err != nil {
app.Dialog.Error().
SetTitle("Import Failed").
SetMessage(err.Error()).
Show()
return
}
app.Dialog.Info().
SetTitle("Success").
SetMessage("Configuration imported successfully!").
Show()
})
cancelBtn := dialog.AddButton("Cancel")
dialog.SetCancelButton(cancelBtn)
dialog.Show()
}最佳做法
✅ 建議做法
- 提供檔案篩選器 — 協助使用者尋找檔案
- 設定適當的標題 — 提供清楚的操作情境
- 使用預設目錄 — 從合理的位置開始
- 驗證選取項目 — 檢查檔案類型
- 處理取消操作 — 使用者可能會取消
- 顯示確認提示 — 適用於破壞性操作
- 提供操作結果回饋 — 顯示成功或錯誤訊息
❌ 請勿
- 請勿略過驗證 — 檢查檔案類型
- 請勿忽略錯誤 — 處理取消操作
- 請勿使用籠統的篩選器 — 明確指定類型
- 請勿忘記「所有檔案」 — 一律將其納入選項
- 請勿將路徑寫死 — 使用使用者的主目錄
- 請勿假設檔案存在 — 開啟前先檢查
平台差異
macOS
- 原生 NSOpenPanel/NSSavePanel
- 附加至視窗時,以視窗附屬對話方塊的形式呈現
- 沿用系統主題
- 支援「快速查看」預覽
- 整合標籤與喜好項目
Windows
- 原生「開啟檔案」與「儲存檔案」對話方塊
- 沿用系統主題
- 最近使用的檔案整合
- 支援網路位置
Linux
- GTK 檔案選擇器
- 依桌面環境而異
- 遵循桌面主題
- 支援最近使用的檔案
後續步驟
訊息對話方塊
資訊、警告和錯誤對話方塊。
自訂對話方塊
建立自訂對話方塊視窗。
繫結
從 JavaScript 呼叫 Go 函式。
事件
使用事件提供進度更新。