You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Windows桌面应用如何获取“Open with”打开的文件列表?

Flutter Windows桌面应用获取“Open with”打开的文件列表

在Windows平台上,用户通过“Open with”选择你的应用打开文件、或双击关联格式文件时,系统会通过命令行参数传递文件路径;如果应用已在运行,还需要通过Windows消息机制接收新的文件路径。以下是具体实现方式:

一、获取启动时的文件路径

应用首次启动时,系统会将待打开的文件路径作为命令行参数传递给应用。你可以通过dart:io的Platform类读取这些参数:

import 'dart:io';
import 'package:flutter/material.dart';

void main() {
  // 过滤掉应用自身路径,剩下的就是待打开的文件路径
  final List<String> rawArgs = Platform.args;
  final List<String> targetFiles = rawArgs.length > 1 
      ? rawArgs.sublist(1).where((path) => File(path).existsSync()).toList()
      : [];

  runApp(MyApp(openedFiles: targetFiles));
}

class MyApp extends StatelessWidget {
  final List<String> openedFiles;

  const MyApp({super.key, required this.openedFiles});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('已打开文件')),
        body: openedFiles.isEmpty
            ? const Center(child: Text('暂无打开的文件'))
            : ListView.builder(
                itemCount: openedFiles.length,
                itemBuilder: (_, index) => ListTile(title: Text(openedFiles[index])),
              ),
      ),
    );
  }
}

二、处理应用已启动时的新文件

如果应用已经在运行,用户再次通过“Open with”打开文件,默认会启动新的应用实例。要让已有实例接收新文件,需要做两件事:

  1. 实现单实例应用,避免重复启动
  2. 通过Windows的WM_COPYDATA消息传递文件路径

1. 添加依赖

在pubspec.yaml中引入win32包,用于调用Windows原生API:

dependencies:
  flutter:
    sdk: flutter
  win32: ^5.0.0 # 使用最新稳定版本

2. 实现单实例与消息监听

import 'dart:ffi';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:win32/win32.dart';

// 全局存储所有打开的文件
final List<String> allOpenedFiles = [];
// 互斥量名称,确保单实例
const _mutexName = 'com.yourcompany.yourapp.singleinstance';

void main() {
  // 处理启动参数
  final rawArgs = Platform.args;
  final launchFiles = rawArgs.length > 1 
      ? rawArgs.sublist(1).where((p) => File(p).existsSync()).toList()
      : [];
  allOpenedFiles.addAll(launchFiles);

  if (Platform.isWindows) {
    // 检查是否已有实例
    final mutex = CreateMutex(nullptr, TRUE, TEXT(_mutexName));
    final error = GetLastError();
    if (error == ERROR_ALREADY_EXISTS) {
      // 已有实例,发送文件路径并退出当前实例
      _sendFilesToExistingInstance(launchFiles);
      exit(0);
    }

    // 注册窗口消息处理器
    _registerWindowMessageHandler();
  }

  runApp(const MyApp());
}

// 向已有实例发送文件路径
void _sendFilesToExistingInstance(List<String> files) {
  if (files.isEmpty) return;
  // 拼接文件路径(用换行分隔,避免空格问题)
  final data = files.join('\n');
  // 查找已有窗口(这里需要根据你的应用窗口标题或类名调整)
  final hwnd = FindWindow(nullptr, TEXT('你的应用窗口标题'));
  if (hwnd != nullptr) {
    final copyData = COPYDATASTRUCT(
      dwData: 0,
      cbData: (data.length * 2 + 2).cast<DWORD>(),
      lpData: data.toNativeUtf16().cast(),
    );
    SendMessage(hwnd, WM_COPYDATA, nullptr, copyData.addressOf.cast<LPARAM>());
  }
}

// 注册窗口消息处理
void _registerWindowMessageHandler() {
  final hwnd = GetForegroundWindow(); // 获取当前应用窗口句柄,实际可根据情况调整
  SetWindowLongPtr(hwnd, GWLP_WNDPROC, Pointer.fromFunction(_windowProc, 0));
}

// 窗口消息回调
LRESULT _windowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  if (uMsg == WM_COPYDATA) {
    final pCopyData = lParam.cast<COPYDATASTRUCT>();
    final data = pCopyData.ref.lpData.cast<Utf16>().toDartString();
    // 解析传递的文件路径
    final newFiles = data.split('\n').where((p) => p.isNotEmpty && File(p).existsSync()).toList();
    if (newFiles.isNotEmpty) {
      allOpenedFiles.addAll(newFiles);
      // 通知UI更新,可使用Provider、ValueNotifier等状态管理方式
      if (kDebugMode) print('新增打开文件:$newFiles');
    }
    return TRUE;
  }
  return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // 使用ValueNotifier监听文件列表变化,更新UI
  final ValueNotifier<List<String>> _filesNotifier = ValueNotifier(allOpenedFiles);

  @override
  void initState() {
    super.initState();
    // 可以在这里添加监听,比如全局通知更新文件列表
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('已打开文件')),
        body: ValueListenableBuilder<List<String>>(
          valueListenable: _filesNotifier,
          builder: (_, files, __) => files.isEmpty
              ? const Center(child: Text('暂无打开的文件'))
              : ListView.builder(
                  itemCount: files.length,
                  itemBuilder: (_, index) => ListTile(title: Text(files[index])),
                ),
        ),
      ),
    );
  }
}

三、配置文件关联(可选)

要让系统识别你的应用可以打开特定格式的文件,在pubspec.yaml的windows配置中添加文件关联:

flutter:
  windows:
    file_associations:
      - extension: txt
        description: 文本文件
        mime_type: text/plain
      - extension: md
        description: Markdown文档
        mime_type: text/markdown

执行flutter build windows打包后,安装程序会自动配置Windows注册表,用户双击对应格式文件时会默认用你的应用打开。

关键注意事项

  • 带空格的文件路径:命令行参数中带空格的路径会被自动包裹引号,Platform.args会正确解析为单个参数,无需手动处理。
  • 窗口句柄获取:示例中用GetForegroundWindow()获取窗口句柄,实际开发中可以通过flutter_window_manager包或win32的FindWindow根据窗口类名/标题精准获取。
  • 状态管理:当新增文件时,需要通过状态管理工具(如ValueNotifier、Provider)通知UI更新,避免视图不刷新。

内容的提问来源于stack exchange,提问作者mirkaim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 21:55:11