如何为Windows桌面Flutter应用创建双独立引擎窗口并实现跨窗口通信与双屏分显功能
我来帮你一步步解决双屏窗口的问题,顺便解答你提到的三个技术疑问:
一、先解决修改C++后仅显示一个窗口的问题
你之前修改代码后只出现一个窗口,大概率是因为没有正确枚举系统中的所有显示器,或者窗口创建参数有误。核心要做的是遍历所有外接显示器,为每个显示器单独创建全屏窗口。
在windows/runner/main.cpp中,你可以用Windows API的EnumDisplayMonitors函数枚举所有显示器,然后为每个显示器的区域创建对应的Flutter窗口。这里给你一段关键代码示例:
// 定义窗口数据结构体,用于传递给枚举回调 struct WindowData { HINSTANCE instance; FlutterEngine engine; const wchar_t* window_class; }; // 枚举显示器的回调函数 BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { auto window_data = reinterpret_cast<WindowData*>(dwData); // 创建全屏窗口(使用WS_POPUP去掉标题栏,直接铺满显示器) HWND hwnd = CreateWindowEx( 0, window_data->window_class, L"Flutter Dual Screen", WS_POPUP | WS_VISIBLE, lprcMonitor->left, lprcMonitor->top, lprcMonitor->right - lprcMonitor->left, lprcMonitor->bottom - lprcMonitor->top, nullptr, nullptr, window_data->instance, window_data ); if (!hwnd) { return FALSE; } // 初始化当前窗口的Flutter视图 InitializeFlutterView(hwnd, window_data->engine); return TRUE; } // 在main函数中调用枚举 int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, _In_ wchar_t* cmd_line, _In_ int cmd_show) { // ... 其他初始化代码 ... WindowData window_data = { .instance = instance, .engine = engine, .window_class = kWindowClass, }; // 枚举所有显示器并创建窗口 EnumDisplayMonitors(nullptr, nullptr, MonitorEnumProc, reinterpret_cast<LPARAM>(&window_data)); // ... 消息循环代码 ... }
这段代码会遍历所有显示器,为每个显示器创建一个全屏的Flutter窗口,解决你之前只显示一个窗口的问题。
二、你的三个技术疑问解答
1. 如何为副屏窗口指定不同的Widget?
有两种简单可靠的方式:
通过初始路由区分:在C++创建Flutter视图时,为不同窗口设置不同的初始路由。比如主窗口用
"/main",副屏窗口用"/preview"。然后在Dart层通过路由加载对应的Widget:void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { final initialRoute = WidgetsBinding.instance.window.defaultRouteName; return MaterialApp( title: '双屏演示', initialRoute: initialRoute.isNotEmpty ? initialRoute : '/main', routes: { '/main': (context) => const MainScreen(), // 主屏界面 '/preview': (context) => const PreviewScreen(), // 副屏预览界面 }, ); } }在C++的
InitializeFlutterView函数中,给不同窗口设置路由:// 主窗口设置路由 FlutterViewSetInitialRoute(view, "/main"); // 副窗口设置路由 FlutterViewSetInitialRoute(view, "/preview");通过MethodChannel传递窗口标识:在窗口创建完成后,通过MethodChannel告诉Dart层当前窗口是主窗口还是副窗口,Dart根据这个标识渲染对应的Widget。
2. 如何实现两个视图间的通信?
因为两个窗口共享同一个Flutter Engine,所以通信非常方便,推荐两种方案:
全局状态管理:用
ChangeNotifier、Provider或Riverpod等状态管理工具,全局维护状态,两个窗口监听状态变化即可同步数据。比如:// 全局状态类 class ScreenSyncState extends ChangeNotifier { String _previewContent = ""; String get previewContent => _previewContent; void updatePreview(String content) { _previewContent = content; notifyListeners(); // 通知所有监听者更新 } } // 初始化全局实例 final screenSyncState = ScreenSyncState(); // 主窗口更新状态 screenSyncState.updatePreview("主窗口的操控内容"); // 副窗口监听状态并展示 Consumer<ScreenSyncState>( builder: (context, state, child) { return Center(child: Text(state.previewContent)); }, )MethodChannel通信:通过MethodChannel在两个窗口间发送消息,比如主窗口调用方法更新副屏内容,副屏监听方法调用并更新界面。这种方式适合单次消息传递,状态管理适合持续同步。
3. 修改C++ runner文件是否为最优方案?
目前来看,修改C++ runner是最可控、最成熟的方案。因为Flutter官方对Windows多窗口的支持还处于完善阶段(虽然有Experimental API,但尚未稳定发布),直接修改底层C++代码可以完全掌控窗口的创建、布局和生命周期。
当然,你也可以关注Flutter官方的多窗口API进展,未来官方推出稳定的Dart层多窗口API后,可以迁移过去。但现阶段,修改C++ runner是实现你需求的最优选择。
内容的提问来源于stack exchange,提问作者jacobsieradzki

