Flutter开发

Flutter开发windows应用(二)多窗口实现

2024-01-13  本文已影响0人  非新生代菜鸟

Flutter开发windows桌面应用实现多窗口

相信许多开发者在使用flutter开发windows应用程序的时候,始终有一个逃避不开的问题,那就是应用多窗口。
官方早在2020年就写入了multi_window计划,并在2022年补充了multi_view计划,但时至2024,依然没有给大家带来惊喜。
今天我们就来说一说如何利用pub.dev上现有的一些库来实现我们的windows多窗口。

  1. 首先我们在pubspec.yaml中引入:
  desktop_multi_window: ^0.2.0
  #desktop_lifecycle: ^0.1.1 #自己管理新窗口状态,所以暂时未使用它
  window_manager: ^0.3.4
  1. windows\flutter_window.cpp中FlutterWindow::OnCreate(){},手动注册flutter_engine及引入的库
// 引入用到的头文件
#include "desktop_multi_window/desktop_multi_window_plugin.h"
#include "desktop_lifecycle/desktop_lifecycle_plugin.h"
#include "window_manager/window_manager_plugin.h"

int findAndHideWindow(){
    // 通过窗口标题查找窗口句柄
    const char* windowTitle = "CrossMaskLayer";
    HWND hWnd = FindWindowA(NULL, windowTitle);

    if (hWnd != NULL) {
        // 找到窗口,设置窗口样式为 WS_EX_TOOLWINDOW , 忽略鼠标事件WS_EX_TRANSPARENT,不在任务栏显示WS_EX_TOOLWINDOW
        SetWindowLongPtr(hWnd, GWL_EXSTYLE, GetWindowLongPtr(hWnd, GWL_EXSTYLE) | WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT);
    } else {
        // 未找到窗口
        MessageBoxA(NULL, "Window not found.", "Error", MB_OK | MB_ICONERROR);
    }

    return 0;
}

bool FlutterWindow::OnCreate() {
    if (!Win32Window::OnCreate()) {
        return false;
    }
    RECT frame = GetClientArea();

    // flutter 主窗口引擎============================================================================
    // The size here must match the window dimensions to avoid unnecessary surface
    // creation / destruction in the startup path.
    flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
            frame.right - frame.left, frame.bottom - frame.top, project_);
    // Ensure that basic setup of the controller was successful.
    if (!flutter_controller_->engine() || !flutter_controller_->view()) {
        return false;
    }
    RegisterPlugins(flutter_controller_->engine());
    // channel创建主窗口中与原生Native通信的Native_method_channel,实现主窗口的原生能力
    flutter::MethodChannel<> channel(
            flutter_controller_->engine()->messenger(), "my_native_method_channel",
            &flutter::StandardMethodCodec::GetInstance());
    channel.SetMethodCallHandler(
            [](const flutter::MethodCall<> &call,
               std::unique_ptr <flutter::MethodResult<>> result) {
                if (call.method_name() == "doSth") {
                    int res = doSth();
                    result->Success(res);
                } else {
                    result->NotImplemented();
                }
            });

    SetChildContent(flutter_controller_->view()->GetNativeWindow());
    
    // flutter 子窗口引擎 ===========================================================================
    DesktopMultiWindowSetWindowCreatedCallback([](void *controller) {
        auto *flutter_controller_sub_ =
                reinterpret_cast<flutter::FlutterViewController *>(controller);
        auto *registry = flutter_controller_sub_->engine();
        // 在子窗口引擎中注册DesktopLifecyclePlugin
        DesktopLifecyclePluginRegisterWithRegistrar(
                registry->GetRegistrarForPlugin("DesktopLifecyclePlugin"));
        // 在子窗口引擎中注册WindowManagerPlugin
        WindowManagerPluginRegisterWithRegistrar(
                registry->GetRegistrarForPlugin("WindowManagerPlugin"));
        // 在子窗口引擎中subChannel创建一个在子窗口中可以使用的native_method_channel,实现原生调用
        flutter::MethodChannel<> subChannel(
                flutter_controller_sub_->engine()->messenger(), "sub_native_method_channel",
                &flutter::StandardMethodCodec::GetInstance());
        subChannel.SetMethodCallHandler(
                [](const flutter::MethodCall<> &call,
                   std::unique_ptr <flutter::MethodResult<>> result) {
                    if (call.method_name() == "hideSubWindow") {
                        int res = findAndHideWindow();
                        result->Success(res);
                    } else if (call.method_name() == "closeSubWindow") {
                        int res = closeSubWindow();
                        result->Success(res);
                    } else {
                        result->NotImplemented();
                    }
                });
    });
    return true;
}
  1. dart层定义全局常量
import 'package:desktop_multi_window/desktop_multi_window.dart';

// 创建主窗口平台通道
const platform = MethodChannel('my_native_method_channel');
// 创建子窗口平台通道
const subPlatform = MethodChannel('sub_native_method_channel');
// 多窗口控制器,这里我们只用到一个新窗口,所以固定windowId=1;
final subWindowController = WindowController.fromWindowId(1);
  1. 在main.dart中,编写入口逻辑:
import 'package:window_manager/window_manager.dart';
import 'package:window_manager/window_manager.dart' as subWindowManager;
import 'pages/subWindow.dart';

void main(List<String> args) async {
  // main函数中对窗口初始化模式进行控制
  if (args.firstOrNull == 'multi_window') {
    final windowId = int.parse(args[1]);
    Log.i('windowId:$windowId');
    final argument = args[2].isEmpty ? const {} : jsonDecode(args[2]) as Map<String, dynamic>;

    WidgetsFlutterBinding.ensureInitialized();
    await subWindowManager.windowManager.ensureInitialized();
    WindowOptions wnwOptions = const WindowOptions(
      size: Size(200, 100),
      backgroundColor: Colors.transparent,
      skipTaskbar: false,
      titleBarStyle: TitleBarStyle.hidden,
      title: "CrossMaskLayer",
      windowButtonVisibility: false,
      alwaysOnTop: true,
    );
    subWindowManager.windowManager.waitUntilReadyToShow(wnwOptions, () async {
      subWindowManager.windowManager.setAsFrameless(); //无边框
      subWindowManager.windowManager.setHasShadow(false); //这里不能有阴影,否则会出现一个透明外框
      subWindowManager.windowManager.setMaximizable(false);
      subWindowManager.windowManager.setMinimizable(false);
      subWindowManager.windowManager.setResizable(false);
      //subWindowManager.windowManager.setAlwaysOnTop(true);
      //subWindowManager.windowManager.setClosable(false);
      //subWindowManager.windowManager.setIgnoreMouseEvents(true,forward: true);
      await subWindowManager.windowManager.show();
      // await subWindowManager.windowManager.focus();
    });
    runApp(SubWindow(
      windowController: subWindowController, // 前面定义的全局常量,方便后面引用
      args: argument,
    ));
  } else {
    WidgetsFlutterBinding.ensureInitialized();
    await windowManager.ensureInitialized();
    WindowOptions windowOptions = const WindowOptions(
      size: Size(1200, 700),
      backgroundColor: Colors.transparent,
      skipTaskbar: false,
      titleBarStyle: TitleBarStyle.hidden,
      title: "主窗口",
      windowButtonVisibility: false,
      // alwaysOnTop: true,
      center: true,
    );
    windowManager.waitUntilReadyToShow(windowOptions, () async {
      windowManager.setAsFrameless(); //无边框
      windowManager.setHasShadow(false); //这里不能有阴影,否则会出现一个透明外框
      windowManager.setMaximizable(false);
      windowManager.setResizable(false);
      // windowManager.setAlwaysOnTop(true);
      await windowManager.show();
      await windowManager.focus();
    });
    runApp(const BaseApp());
  }
}
  1. SubWindow.dart
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:window_manager/window_manager.dart' as subWindowManager;

import '../common/global.dart';
import '../common/utils/log.dart';

class SubWindow extends StatefulWidget {
  const SubWindow({
    super.key,
    required this.windowController,
    required this.args,
  });

  final WindowController windowController;
  final Map? args;

  @override
  State<SubWindow> createState() => _SubWindowState();
}

class _SubWindowState extends State<SubWindow> with subWindowManager.WindowListener {
  @override
  void initState() {
    super.initState();
    // WidgetsBinding.instance.addPostFrameCallback((_) {
    //   subPlatform.invokeMethod('hideSubWindow');
    // });
    Future.delayed(const Duration(seconds: 1), () {
      // 延迟1秒调用subChannel原生通道,隐藏状态栏中的新窗口,忽略鼠标事件
      subPlatform.invokeMethod('hideSubWindow');
    });
    subWindowManager.windowManager.addListener(this);
    _init();
  }

  @override
  void onWindowClose() {
    Log.i('sub window on close');
    // 修改子窗口状态为可以创建
    homeController.changeSubWndRunningState(0);
  }
  @override
  void dispose() {
    subWindowManager.windowManager.removeListener(this);
    super.dispose();
  }

  @override
  void onWindowFocus() {
    // Make sure to call once.
    setState(() {});
    // do something
  }
  void _init() async {
    // Add this line to override the default close handler
    await subWindowManager.windowManager.setPreventClose(true);
    setState(() {});
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 40,
      height: 40,
      color: Colors.transparent,
      alignment: Alignment.center,
      child: SvgPicture.asset(
        'assets/images/cross/plus.svg',
        width: 40,
        height: 40,
      ),
    );
  }
}
  1. 在HomeController.dart中,定义新窗口状态变量及set方法
// 子窗口状态码 0未打开,1运行中,2已隐藏
  int subWndRunning = 0;
  // 子窗口是否已经运行中
  void changeSubWndRunningState(int v) {
    subWndRunning = v;
    update();
  }
  1. 在Home.dart页面中调用按钮点击事件,实现创建和隐藏新窗口等操作
  @override
  void dispose() {
    windowManager.removeListener(this);
    // 关闭新窗口
    subWindowController.close(); // 新窗口控制器
    super.dispose();
  }
Future<void> showMaskWindow() async {
    final window = await DesktopMultiWindow.createWindow(jsonEncode({
      'args0': 'multi_window',
      'args1': 1,
    }));
    window
      ..setFrame(const Offset(0, 0) & const Size(200, 100))
      ..center();
    // 注意这里不能使用..show(),因为此能力需要交给main.dart中的window_manager,否则会有一个带toolbar的窗口。
    // ..show();
  }
GetBuilder<HomeController>(
    init: HomeController(),
        builder: (__) {
            return GestureDetector(
                onTap: () {
                    if (__.subWndRunning == 0) {
                        Log.i('子窗口可以创建');
                        showMaskWindow();
                        homeController.changeSubWndRunningState(1);
                    } else if (__.subWndRunning == 1) {
                        Log.i('子窗口可以隐藏');
                        subWindowController.hide();
                        homeController.changeSubWndRunningState(2);
                    } else {
                        Log.i('子窗口可以显示');
                        subWindowController.show();
                        homeController.changeSubWndRunningState(1);
                    }
                },
                child:Text('点我打开/隐藏新窗口'),
        },
),

最后:希望可以给想用flutter开发windows桌面应用的朋友带来一点帮助吧,期待官方的多窗口支持早点出来

上一篇 下一篇

猜你喜欢

热点阅读