首页 / Wails 入门教程 / 拖放 Drag and Drop

Wails 入门教程

拖放 Drag and Drop

本教程共 42 篇 · 第 25 篇 · 更新于 2026-08-03

Wails桌面开发拖放Drag and Drop文件拖入

25. 拖放 Drag and Drop

本节目标

  • 在 options 里开启文件拖放开关。
  • OnFileDrop 在 Go 或前端拿到拖入的文件路径。
  • 用 CSS 属性圈出”允许落文件”的区域并高亮。

25-1 先开开关

想让用户把文件拖进窗口,第一步不是写代码,而是开开关。

options.AppDragAndDrop 里,把 EnableFileDrop 设成 true:

DragAndDrop: &options.DragAndDrop{
    EnableFileDrop: true,
},

不开这个,后面所有监听都收不到任何东西。

Note

拖放能力是给”文件”用的,比如拖一张图片、一个压缩包进窗口。拖的是 HTML 元素那种网页内部拖拽,不属于这套机制,那是浏览器原生行为。

关掉开关后,运行时方法 OnFileDrop 才能用。也可以监听 wails:file-drop 事件,两种入口下面都讲。

25-2 Go 侧监听拖放

Go 侧用 runtime.OnFileDrop,回调里拿到三个东西:落点的 x、y 坐标,还有文件路径切片。

func (a *App) startup(ctx context.Context) {
    a.ctx = ctx
    rt.OnFileDrop(ctx, func(x, y int, paths []string) {
        for _, p := range paths {
            fmt.Println("拖进来的文件:", p)
        }
    })
}

paths 是绝对路径。拿到之后怎么处理都行,比如读图、上传、解析。

不想监听了,调 OnFileDropOff 一次性清掉所有监听器:

func (a *App) StopDrop() {
    rt.OnFileDropOff(a.ctx)
}
Tip

拖进来的可能是文件夹,也可能一次拖多个。paths 是切片,循环处理才稳,别只取第一个。

25-3 前端监听拖放

前端同样有 OnFileDrop,但多一个参数 useDropTarget

window.go.main.App.OnFileDrop(
    (x: number, y: number, paths: string[]) => {
        console.log("坐标", x, y);
        console.log("文件", paths);
    },
    false
);

useDropTarget 为 false 时,只要文件落在窗口任意位置,回调就触发。

Warning

前端这套 OnFileDrop 是运行时方法,走的是绑定路径。要确保 Go 侧 OnStartup 已经拿到 ctx,否则监听注册不上。

另一个纯前端的路子是监听事件,不依赖绑定:

import { EventsOn } from "@wailsio/runtime";

EventsOn("wails:file-drop", (data: { x: number; y: number; paths: string[] }) => {
    console.log(data.paths);
});

wails:file-drop 事件带回的数据结构和回调一致,挑顺手的方式用。

25-4 指定拖放目标区域

有时候你不想整个窗口都能落文件,只想让某个框接收。

这时候把 useDropTarget 设成 true。Wails 会在鼠标移动时检查:当前悬停的元素有没有特定的 CSS 属性。有,就给它加一个 wails-drop-target-active 类。

那个”特定属性”由 CSSDropPropertyCSSDropValue 决定,默认是 --wails-drop-targetdrop

DragAndDrop: &options.DragAndDrop{
    EnableFileDrop:   true,
    CSSDropProperty:  "--wails-drop-target",
    CSSDropValue:     "drop",
},

前端给目标元素写上这个属性:

function DropZone() {
    window.go.main.App.OnFileDrop(
        (x, y, paths) => console.log(paths),
        true // 开启拖放目标检测
    );

    return (
        <div
            style={{ "--wails-drop-target": "drop" } as React.CSSProperties}
            className="drop-zone"
        >
            把文件拖到这里
        </div>
    );
}

用 CSS 给高亮状态做视觉反馈:

.drop-zone {
    border: 2px dashed #ccc;
}
.drop-zone.wails-drop-target-active {
    border-color: #2d8cf0;
    background: #eef6ff;
}

鼠标移到框上,边框变蓝,用户体验就清晰了。

25-5 关掉网页原生拖放

默认情况下,WebView 自己也认拖放。拖个文件进去,浏览器会试图打开它,干扰你的逻辑。

不想让网页自己处理,把 DisableWebViewDrop 打开:

DragAndDrop: &options.DragAndDrop{
    EnableFileDrop:     true,
    DisableWebViewDrop: true,
},

这样文件只会走 Wails 的 OnFileDrop,不会触发浏览器的打开行为。

Tip

做”拖文件上传”功能时,强烈建议开 DisableWebViewDrop。否则文件偶尔会被浏览器抢走,你的回调收不到,调试起来很懵。

25-6 实战:拖图片进预览区

做个”拖图片进预览”的小功能。目标区域高亮,松手后把第一张图读出来显示。

function DropZone() {
    window.go.main.App.OnFileDrop((x, y, paths) => {
        if (paths.length > 0) {
            window.go.main.App.LoadImage(paths[0]);
        }
    }, true);

    return <div style={{ "--wails-drop-target": "drop" } as React.CSSProperties }>拖图片到这里</div>;
}

Go 侧 LoadImage 读文件、转 base64,再 EventsEmit 给前端渲染:

func (a *App) LoadImage(path string) {
    data, _ := os.ReadFile(path)
    b64 := base64.StdEncoding.EncodeToString(data)
    rt.EventsEmit(a.ctx, "image:loaded", "data:image/png;base64,"+b64)
}
Tip

拖入的是绝对路径,Go 能直接 os.ReadFile。别在前端用 fetch 去读本地路径,浏览器安全策略会拦,交给后端读最稳。

25-7 多文件与错误处理

多文件和错误处理要提前想好。

用户可能一次拖进十张图,也可能拖了个文件夹。paths 是切片,循环处理才有扩展性。读文件失败(权限、损坏)要兜底,别让一个坏文件崩了整个回调。

func (a *App) LoadImages(paths []string) {
    var ok []string
    for _, p := range paths {
        if data, err := os.ReadFile(p); err == nil {
            ok = append(ok, toDataURL(p, data))
        }
    }
    rt.EventsEmit(a.ctx, "images:loaded", ok)
}
Warning

拖放回调里读文件是同步阻塞的。文件很大或很多时,会卡住界面。真做批量导入,建议起 goroutine 处理,读完再 EventsEmit 回前端。

25-8 与右键菜单协同及安全

拖放和右键菜单经常出现在同一个界面里,比如一个文件列表。

左键拖文件进列表就导入,右键点某个文件弹”删除 / 重命名”。两者互不冲突,因为拖放走 OnFileDrop 的 drop 事件,右键走 mousedown 的 button 判断。

function FileList() {
    window.go.main.App.OnFileDrop((x, y, paths) => {
        window.go.main.App.ImportFiles(paths);
    }, true);

    function onCtx(e: MouseEvent) {
        if (e.button === 2) {
            e.preventDefault();
            window.go.main.App.ShowFileMenu(e.clientX, e.clientY);
        }
    }
    return <ul onMouseDown={onCtx}>...</ul>;
}

拖放目标区域(带 --wails-drop-target 的元素)如果同时是右键菜单的容器,两者能和平共存。因为一个是 drop 事件,一个是 mousedown 事件,触发时机不同。

拿到路径后别直接信任。拖进来的可能是文件夹、快捷方式或权限受限的文件。导入前做一层校验:检查扩展名是否在白名单、用 os.Stat 确认是普通文件、读之前再试一次 os.ReadFile 兜底。

func (a *App) ImportFiles(paths []string) {
    var ok []string
    for _, p := range paths {
        if info, err := os.Stat(p); err != nil || info.IsDir() {
            continue // 跳过目录和读不到的
        }
        if !strings.HasSuffix(p, ".png") && !strings.HasSuffix(p, ".jpg") {
            continue // 只收图片
        }
        ok = append(ok, p)
    }
    rt.EventsEmit(a.ctx, "files:imported", ok)
}
Note

拖放目标区域如果同时是右键菜单容器,两者能共存。一个是 drop、一个是 mousedown,触发时机不同,不会打架。

Tip

文件列表更新后,把最新路径通过事件回传前端让 React 重新渲染。Go 侧改了数据,前端不会自动知道,事件桥是唯一的同步通道。校验不合格的默默跳过即可,别弹一堆报错吓用户。

25-9 排错清单

拖放的排错清单。

第一,收不到任何拖放。先确认 EnableFileDrop 设为 true,这是最常见的漏网。

第二,目标区域不高亮。确认 useDropTarget 为 true,且元素写了 --wails-drop-target: drop(和你 options 里的 CSSDropProperty 一致)。

第三,文件被浏览器抢走打开。关掉 DisableWebViewDrop

第四,回调只拿到一个文件。用户可能拖了多个,paths 是数组,循环处理才稳。

Tip

拖放没反应时,先用 EventsOn("wails:file-drop", ...) 这个事件通道验证,它能绕过绑定直接收数据,帮你判断是开关问题还是绑定问题。另外,拖放和文件选择对话框(前面章节讲过)能互补:拖放图省事,对话框图精准。两者都提供,用户按习惯选,体验最佳。

常见误区

Warning

EnableFileDrop 不设为 true,后面任何监听都收不到。先检查开关,再查代码。

Note

useDropTarget 为 true 时,只有带 --wails-drop-target: drop 的元素会被识别。忘了写这个属性,高亮和回调都不会触发。

Tip

多个文件或文件夹一起拖入时,paths 是数组。处理逻辑要兼容多元素,别写死只认 paths[0]