Flutter Windows GUI集成插件win32_gui的使用

发布于 1周前 作者 wuwangju 来自 Flutter

Flutter Windows GUI集成插件win32_gui的使用

win32_gui 是一个基于 Win32 API 的 GUI 库,它以面向对象的方式实现了一些助手功能。该库依赖于 win32 包和 dart:ffi

示例截图

Example screenshot

使用方法

以下是一个简单的 “Hello World” 窗口示例:

主要代码示例

main.dart

import 'dart:io';
import 'package:win32_gui/win32_gui.dart';

Future<void> main() async {
  // 自定义主窗口类(在下方声明):
  var mainWindow = MainWindow(
    width: 640,
    height: 480,
  );

  // 创建窗口:
  await mainWindow.create();
  
  // 当窗口关闭并销毁时退出:
  // 参见下面的 `MainWindow.processClose`:
  mainWindow.onDestroy.listen((window) {
    print('-- Window Destroyed> $window');
    exit(0);
  });

  // 显示主窗口:
  mainWindow.show();

  // 运行Win32窗口消息循环。
  await Window.runMessageLoopAsync();
}

// 自定义主窗口:
class MainWindow extends Window {
  static final mainWindowClass = WindowClass.custom(
    className: 'mainWindow',
    windowProc: Pointer.fromFunction<WNDPROC>(mainWindowProc, 0),
    bgColor: RGB(255, 255, 255),
    useDarkMode: true,
    titleColor: RGB(32, 32, 32),
  );

  static int mainWindowProc(int hwnd, int uMsg, int wParam, int lParam) =>
          WindowClass.windowProcDefault(
                  hwnd, uMsg, wParam, lParam, mainWindowClass);
  
  MainWindow({super.width, super.height})
          : super(
    defaultRepaint: false, // 告知将使用 `repaint()` 方法。
    windowName: 'Win32 GUI - Example', // 窗口标题。
    windowClass: mainWindowClass,
    windowStyles: WINDOW_STYLE.WS_MINIMIZEBOX | WINDOW_STYLE.WS_SYSMENU,
  ) ;

  late final String imageDartLogoPath;
  late final String iconDartLogoPath;
  
  @override
  Future<void> load() async {
    imageDartLogoPath = await Window.resolveFilePath(
            'package:win32_gui/resources/dart-logo.bmp');

    iconDartLogoPath = await Window.resolveFilePath(
            'package:win32_gui/resources/dart-icon.ico');
  }

  @override
  void build(int hwnd, int hdc) {
    super.build(hwnd, hdc);

    SetTextColor(hdc, RGB(255, 255, 255));
    SetBkColor(hdc, RGB(96, 96, 96));

    // 设置窗口图标: 
    setIcon(iconDartLogoPath);
  }
  
  @override
  bool? processClose() {
    return null; // 使用默认关闭行为(销毁窗口)
  }
  
  @override
  void repaint(int hwnd, int hdc) {
    var hBitmap = loadImageCached(imageDartLogoPath);
    var imgDimension = getBitmapDimension(hBitmap);

    if (imgDimension != null) {
      var imgW = imgDimension.width;
      var imgH = imgDimension.height;
      
      final x = (dimensionWidth - imgW) ~/ 2;
      final y = 10;

      drawImage(hdc, hBitmap, x, y, imgW, imgH);
    }
  }
}

Win32 消息循环

一个 win32 应用程序需要一个消息循环。传统的消息循环会阻塞 Dart VM 循环,这会导致 Future 执行和 Isolate 消息传递中断。为了避免这个问题,可以使用 Window.runMessageLoopAsync() 函数,这样不仅能够处理 Win32 消息,还能让 Dart 代码继续执行。

await Window.runMessageLoopAsync();

如果需要运行特定时间的消息循环:

await Window.runMessageLoopAsync(timeout: Duration(seconds: 10));

或者在某个条件为真时运行:

await Window.runMessageLoopAsync(condition: () => mainWindow.isMinimized);

完整示例

以下是完整的示例,包括一些按钮和文本输出组件:

import 'dart:async';
import 'dart:io';
import 'package:win32_gui/win32_gui.dart';
import 'package:win32_gui/win32_gui_logging.dart';

Future<void> main() async {
  logToConsole();

  var editColors = WindowClassColors(
    textColor: RGB(0, 0, 0),
    bgColor: RGB(128, 128, 128),
  );

  WindowClass.editColors = editColors;
  WindowClass.staticColors = editColors;
  WindowClass.dialogColors = editColors;

  var mainWindow = MainWindow(
    width: 640,
    height: 480,
  );

  print('-- mainWindow.create...');
  await mainWindow.create();

  print('-- mainWindow.show...');
  mainWindow.show();

  mainWindow.onClose.listen((window) async {
    print('-- Main Window closed> $window');
    print('-- Main Window isMinimized> ${mainWindow.isMinimized}');

    var confirmed = mainWindow.showConfirmationDialog(
        "Exit Confirmation", "Exit Application?");

    if (confirmed) {
      mainWindow.destroy();
    } else {
      mainWindow.showMessage(
          'Application Status', 'Application window minimized.');
    }
  });

  mainWindow.onDestroyed.listen((window) {
    print('-- Main Window Destroyed> $window');
    exit(0);
  });

  Timer.periodic(Duration(seconds: 1), (timer) {
    print('TIMER> ${DateTime.now()} ');
  });

  print('-- Window.runMessageLoopAsync...');
  await Window.runMessageLoopAsync();
}

class MainWindow extends Window {
  static final mainWindowClass = WindowClass.custom(
    className: 'mainWindow',
    windowProc: Pointer.fromFunction<WNDPROC>(mainWindowProc, 0),
    bgColor: RGB(255, 255, 255),
    useDarkMode: true,
    titleColor: RGB(32, 32, 32),
  );

  static int mainWindowProc(int hwnd, int uMsg, int wParam, int lParam) =>
      WindowClass.windowProcDefault(
          hwnd, uMsg, wParam, lParam, mainWindowClass);

  late final TextOutput textOutput;
  late final Button buttonOK;
  late final Button buttonExit;

  MainWindow({super.width, super.height})
      : super(
          defaultRepaint: false, 
          windowName: 'Win32 GUI - Example', 
          windowClass: mainWindowClass,
          windowStyles: WINDOW_STYLE.WS_MINIMIZEBOX | WINDOW_STYLE.WS_SYSMENU,
        ) {
    textOutput =
        TextOutput(parent: this, x: 4, y: 160, width: 626, height: 250);

    buttonOK = Button(
        label: 'OK',
        parent: this,
        x: 4,
        y: 414,
        width: 100,
        height: 32,
        onCommand: _onButtonOK);

    buttonExit = Button(
        label: 'Exit',
        parent: this,
        x: 106,
        y: 414,
        width: 100,
        height: 32,
        onCommand: _onButtonExit);
  }

  void _onButtonOK(int w, int l) async {
    print('** Button OK Click!');

    var dialog = DialogExample(parent: this);

    dialog.onTimeout.listen((event) {
      showMessage(
        'Dialog Timeout',
        'Dialog Timeout> result: ${dialog.result} ; timeout: ${dialog.timeout?.inSeconds} s',
      );
    });

    dialog.onDestroyed.listen((_) {
      if (!dialog.timeoutTriggered) {
        showMessage(
          'Dialog Result',
          'Dialog Closed> result: ${dialog.result}',
        );
      }
    });

    dialog.create();

    var result = await dialog.waitAndGetResult();

    print('** DialogExample result: $result');
  }

  void _onButtonExit(int w, int l) {
    print('** Button Exit Click!');
    destroy();
  }

  String imageDartLogoPath = '';
  String iconDartLogoPath = '';

  @override
  Future<void> load() async {
    imageDartLogoPath = await Window.resolveFilePath(
        'package:win32_gui/resources/dart-logo.bmp');

    print('-- imageDartLogoPath: $imageDartLogoPath');

    iconDartLogoPath = await Window.resolveFilePath(
        'package:win32_gui/resources/dart-icon.ico');

    print('-- iconDartLogoPath: $iconDartLogoPath');
  }

  @override
  void build(int hwnd, int hdc) {
    super.build(hwnd, hdc);

    SetTextColor(hdc, RGB(255, 255, 255));
    SetBkColor(hdc, RGB(96, 96, 96));

    setIcon(iconDartLogoPath);

    setWindowRoundedCorners();
  }

  @override
  void repaint(int hwnd, int hdc) {
    var hBitmap = Window.loadImageCached(imageDartLogoPath);
    var imgDimension = Window.getBitmapDimension(hBitmap);

    if (imgDimension != null) {
      var imgW = imgDimension.width;
      var imgH = imgDimension.height;

      final x = (dimensionWidth - imgW) ~/ 2;
      final y = 10;

      drawImage(hdc, hBitmap, x, y, imgW, imgH);
    }

    textOutput.callRepaint();
  }
}

class TextOutput extends RichEdit {
  TextOutput({super.parent, super.x, super.y, super.width, super.height})
      : super(bgColor: RGB(32, 32, 32)) {
    print('-- `TextOutput` default font: `$defaultFont`');
  }

  @override
  void build(int hwnd, int hdc) {
    super.build(hwnd, hdc);

    setBkColor(RGB(32, 32, 32));
    setTextColor(hdc, RGB(255, 255, 255));

    setAutoURLDetect(true);
  }

  @override
  void repaint(int hwnd, int hdc) {
    invalidateRect();

    setBkColor(RGB(32, 32, 32));
    setTextColor(hdc, RGB(255, 255, 255));

    setTextFormatted([
      TextFormatted(" -------------------------\r\n",
          color: RGB(255, 255, 255)),
      TextFormatted(" Hello", color: RGB(0, 255, 255), faceName: 'Courier New'),
      TextFormatted(" Word! \r\n", color: RGB(0, 255, 0)),
      TextFormatted(" -------------------------\r\n",
          color: RGB(255, 255, 255)),
    ]);
  }
}

class DialogExample extends Dialog<int> {
  DialogExample({required super.parent})
      : super(
            dialogFunction:
                Pointer.fromFunction<DLGPROC>(Dialog.dialogProcDefault, 0),
            title: 'Dialog Example',
            x: 0,
            y: 0,
            width: 4 + 50 + 4 + 50 + 4,
            height: 56,
            timeout: Duration(seconds: 10),
            items: [
              DialogItem.text(
                x: 10,
                y: 10,
                width: 100,
                height: 32,
                id: 0,
                text: 'Yes or No? (timeout: 10s)',
              ),
              DialogItem.button(
                x: 4,
                y: 30,
                width: 50,
                height: 22,
                id: 1,
                text: 'Yes',
              ),
              DialogItem.button(
                x: 4 + 50 + 4,
                y: 30,
                width: 50,
                height: 22,
                id: 2,
                text: 'No',
              ),
            ]);

  late final String iconDartLogoPath;

  @override
  Future<void> load() async {
    iconDartLogoPath = await Window.resolveFilePath(
        'package:win32_gui/resources/dart-icon.ico');

    print('-- iconDartLogoPath: $iconDartLogoPath');
  }

  @override
  void build(int hwnd, int hdc) {
    setIcon(iconDartLogoPath);
  }
}

这个示例展示了如何创建一个带有按钮和文本输出的窗口,并且包含了一个对话框的例子。希望这些信息对你有所帮助!


更多关于Flutter Windows GUI集成插件win32_gui的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter Windows GUI集成插件win32_gui的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中集成并使用win32_gui插件来与Windows原生GUI进行交互的示例代码案例。这个插件允许你调用Windows API来进行底层窗口和控件的操作。

首先,确保你已经安装了win32_gui插件。你可以在你的pubspec.yaml文件中添加以下依赖项:

dependencies:
  flutter:
    sdk: flutter
  win32_gui: ^x.y.z  # 请替换为最新版本号

然后,运行flutter pub get来安装依赖。

接下来是一个简单的Flutter应用示例,它使用win32_gui插件来创建一个Windows窗口,并显示一个按钮和一个文本框。

import 'package:flutter/material.dart';
import 'package:win32_gui/win32_gui.dart';
import 'dart:ffi';
import 'dart:typed_data';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Win32 GUI Example'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: _createWin32Window,
            child: Text('Create Win32 Window'),
          ),
        ),
      ),
    );
  }

  void _createWin32Window() {
    // 使用win32_gui插件的初始化方法
    Win32Gui.initialize();

    // 定义窗口过程函数
    Pointer<NativeFunction<WindowProc>> windowProcPointer =
        Pointer.fromFunction<WindowProc>(windowProc, 'windowProc');

    // 注册窗口类
    final className = 'MyWindowClass';
    final hInstance = GetModuleHandle(null)!;

    RegisterClassEx(
      WNDCLASSEX(
        cbSize: sizeof<WNDCLASSEX>(),
        style: 0,
        lpfnWndProc: windowProcPointer,
        cbClsExtra: 0,
        cbWndExtra: 0,
        hInstance: hInstance,
        hIcon: LoadIcon(hInstance, IDI_APPLICATION),
        hCursor: LoadCursor(null, IDC_ARROW),
        hbrBackground: reinterpretCast<IntPtr>(COLOR_WINDOW + 1),
        lpszMenuName: null,
        lpszClassName: className.toNativeUtf16(),
        hIconSm: LoadIcon(hInstance, IDI_APPLICATION),
      ),
    );

    // 创建窗口
    final hwnd = CreateWindowEx(
      0,
      className.toNativeUtf16(),
      'My Win32 Window'.toNativeUtf16(),
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
      null,
      null,
      hInstance,
      null,
    );

    // 显示窗口
    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    // 定义窗口过程函数逻辑
    WindowProc windowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
      switch (uMsg) {
        case WM_DESTROY:
          PostQuitMessage(0);
          break;
        case WM_PAINT: {
          PAINTSTRUCT ps;
          HDC hdc = BeginPaint(hwnd, &ps);
          TextOut(hdc, 50, 50, 'Hello, Win32 GUI!'.toNativeUtf16(), 17);
          EndPaint(hwnd, &ps);
          break;
        }
        default:
          return DefWindowProc(hwnd, uMsg, wParam, lParam);
      }
      return 0;
    }
  }
}

// 以下是必要的Windows API函数和结构体定义
// 注意:这些定义需要根据实际使用的win32_gui插件提供的API进行适当调整

typedef HWND = IntPtr;
typedef UINT = Uint32;
typedef WPARAM = UintPtr;
typedef LPARAM = IntPtr;
typedef HWND CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

class WNDCLASSEX extends Struct {
  @Uint32()
  int cbSize;

  @Uint32()
  int style;

  Pointer<NativeFunction<WindowProc>> lpfnWndProc;

  @Uint32()
  int cbClsExtra;

  @Uint32()
  int cbWndExtra;

  @IntPtr()
  int hInstance;

  @IntPtr()
  int hIcon;

  @IntPtr()
  int hCursor;

  @IntPtr()
  int hbrBackground;

  Pointer<Utf16> lpszMenuName;

  Pointer<Utf16> lpszClassName;

  @IntPtr()
  int hIconSm;
}

// 调用Windows API的函数(这些函数需要在win32_gui插件中定义或通过ffi加载)
external IntPtr GetModuleHandle(Pointer<Utf8>? lpModuleName);
external bool RegisterClassEx(Pointer<WNDCLASSEX> lpWndClassEx);
external HWND CreateWindowEx(
    int dwExStyle,
    Pointer<Utf16> lpClassName,
    Pointer<Utf16> lpWindowName,
    int dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    Pointer<Utf16>? hMenu,
    IntPtr hInstance,
    Pointer<Void>? lpParam);
external bool ShowWindow(HWND hWnd, int nCmdShow);
external bool UpdateWindow(HWND hWnd);
external int DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
external int BeginPaint(HWND hWnd, Pointer<PAINTSTRUCT> lpPaint);
external int EndPaint(HWND hWnd, Pointer<PAINTSTRUCT> lpPaint);
external bool TextOut(IntPtr hdc, int x, int y, Pointer<Utf16> lpString, int c);
external int PostQuitMessage(int nExitCode);

// 常量
const int WS_OVERLAPPEDWINDOW = 0xCF0000;
const int CW_USEDEFAULT = 0x80000000;
const int WM_DESTROY = 0x0002;
const int WM_PAINT = 0x000F;
const int SW_SHOW = 0x0005;
const int IDI_APPLICATION = 32512;
const int IDC_ARROW = 32512;
const int COLOR_WINDOW = 5;

// FFI类型转换函数
Pointer<Utf16> toNativeUtf16(String str) {
  Uint16List utf16 = str.codeUnits;
  Pointer<Uint16> ptr = allocate<Uint16List>(utf16.length + 1);
  ptr.setAll(0, utf16);
  ptr.valueAt(utf16.length) = 0; // Null-terminate the string
  return ptr.cast<Utf16>();
}

注意

  1. 示例代码中的Windows API函数和结构体定义需要根据实际使用的win32_gui插件提供的API进行适当调整。win32_gui插件可能已经封装了一些常见的Windows API调用,因此你可能不需要直接调用FFI函数。
  2. 示例代码中的_createWin32Window函数是异步的,但在这个例子中并没有处理异步逻辑。在实际应用中,你可能需要使用Dart的异步机制(如IsolateFuture)来确保UI线程不会阻塞。
  3. 示例代码中的错误处理和资源释放(如释放内存和注销窗口类)没有详细实现,请在实际应用中添加必要的错误处理和资源释放代码。

由于win32_gui插件的具体API和实现可能会有所不同,建议查阅该插件的官方文档或源代码以获取更详细和准确的信息。

回到顶部