window_manager_plus는 macOS, Windows, Linux용 데스크톱 애플리케이션을 위한 Flutter 플러그인입니다. 창 크기 조절, 위치 이동, 창 간 통신과 같은 기능을 통해 여러 창을 생성하고 관리하며 조작할 수 있습니다. 복잡한 창 처리 작업을 단순화하여 동적 UI 레이아웃을 갖춘 풍부한 데스크톱 경험을 구축하기 쉽게 만듭니다. 기존의 Flutter 프로젝트에 원활하게 통합되며 일관된 API를 사용해 크로스플랫폼 창 관리를 지원합니다.
사용 사례
다중 창 데스크톱 앱 만들기
크기 조절 및 이동 가능한 창 만들기
창 간 메시지 전달 구현
대시보드 또는 편집기 인터페이스 개발
플로팅 패널 또는 도구 창 관리
주요 기능
여러 창을 지원합니다
창 크기 조정 및 재위치
윈도우 간 통신
크로스 플랫폼 호환성
간단한 API 통합
적합한 대상
데스크톱 앱 개발자
다중 창 애플리케이션 제작자
데스크톱을 대상으로 하는 Flutter 개발자
유연한 레이아웃이 필요한 UI 디자이너
모듈러 창 컴포넌트를 필요로 하는 앱
고려 사항
Flutter 3.0 이상 필요
데스크톱 플랫폼에 한정
플랫폼에 특화된 구성이 필요할 수 있습니다
모바일 앱에 적합하지 않습니다
기반 운영 체제의 창 시스템에 의존합니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
window_manager_plus
This plugin allows Flutter desktop apps to create and manage multiple windows, such as resizing and repositioning, and communicate between them.
This is a fork and a re-work of the original window_manager plugin.
With inspiration from the desktop_multi_window plugin,
this new implementation allows the creation and management of multiple windows.
Change the file macos/Runner/AppDelegate.swift as follows:
import Cocoa
import FlutterMacOS
+ import window_manager_plus
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
- return true+ return NSApp.windows.filter({$0 is MainFlutterWindow || $0 is WindowManagerPlusFlutterWindow}).count == 1 // or return false
}
}
Without changing the return logic, the application will close when the main flutter window is closed.
Windows
Change the file windows/runner/main.cpp as follows:
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
+ #include <iostream>+ #include "window_manager_plus/window_manager_plus_plugin.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance,
_In_opt_ HINSTANCE prev,
_In_ wchar_t* command_line,
_In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments = GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"window_manager_example", origin, size)) {
return EXIT_FAILURE;
}
- window.SetQuitOnClose(true);+ window.SetQuitOnClose(false);++ WindowManagerPlusPluginSetWindowCreatedCallback(+ [](std::vector<std::string> command_line_arguments) {+ flutter::DartProject project(L"data");++ project.set_dart_entrypoint_arguments(+ std::move(command_line_arguments));++ auto window = std::make_shared<FlutterWindow>(project);+ Win32Window::Point origin(10, 10);+ Win32Window::Size size(1280, 720);+ // Check whether window->Create or window->CreateAndShow is available.+ // Take a look at the code above for the main flutter window and + // what method the variable "FlutterWindow window(project)" calls+ if (!window->Create(L"window_manager_example", origin, size)) {+ std::cerr << "Failed to create a new window" << std::endl;+ }+ window->SetQuitOnClose(false);+ return std::move(window);+ });
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}
window->SetQuitOnClose(false); is necessary to prevent the application from closing when the window is closed.
If you want to close the App only when the main window is closed, you can set window->SetQuitOnClose(true); in the main window.
The others called inside the WindowManagerPlusPluginSetWindowCreatedCallback should be set to false.
Usage
You must call WindowManagerPlus.ensureInitialized static method and await it before using any WindowManagerPlus methods or WindowManagerPlus.current.
It is used to initialize the plugin with the current window ID.
When creating a new window, the args parameter of the main function will have the window ID as a String.
You must parse it to an integer and pass it to the WindowManagerPlus.ensureInitialized method.
If the args parameter is empty or the first argument is not an integer, then we are in the main window, which ID is exactly 0.
The other arguments will contain the arguments passed to the WindowManagerPlus.createWindow method, if any.
import'package:flutter/material.dart';
import'package:window_manager_plus/window_manager_plus.dart';
// Must add List<String> args parameter to your main function.voidmain(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
// await the initialization of the plugin.// Here is an example of how to use ensureInitialized in the main function:awaitWindowManagerPlus.ensureInitialized(args.isEmpty ? 0 : int.tryParse(args[0]) ?? 0);
// Now you can use the plugin, such as WindowManagerPlus.currentWindowOptions windowOptions = WindowOptions(
size: Size(800, 600),
center: true,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden,
);
WindowManagerPlus.current.waitUntilReadyToShow(windowOptions, () async {
awaitWindowManagerPlus.current.show();
awaitWindowManagerPlus.current.focus();
});
runApp(MyApp());
}
Create a new window
You can create a new window by calling the WindowManagerPlus.createWindow static method.
If you want control the new window, you can use the return value of the method, which returns a WindowManagerPlus instance.
final newWindow = awaitWindowManagerPlus.createWindow(['my test arg 1', 'my test arg 2']);
if (newWindow != null) {
print('New Created Window: $newWindow');
}
Communication between windows
You can communicate with another window by using the WindowManagerPlus.invokeMethodToWindow method.
The first parameter is the ID of the window you want to communicate with.
The second parameter is the method name you want to call.
The third parameter is the arguments you want to pass to the method, if any.
The other window must register and implement the WindowListener class and override the WindowListener.onEventFromWindow method to receive the event.
// assuming we are in the first window, Window ID 0, and we want to communicate with the second window.final secondWindowId = 1; // ID of the second windowfinal result = awaitWindowManagerPlus.current.invokeMethodToWindow(secondWindowId, 'myTestMethod', ['arg1', 'arg2']);
// the result will be 'Hello from Window 1'// assuming we are in the second window, Window ID 1class_MyWidgetStateextendsState<MyWidget> withWindowListener{
// ...@overridevoidinitState() {
WindowManagerPlus.current.addListener(this);
super.initState();
}
@overridevoiddispose() {
WindowManagerPlus.current.removeListener(this);
super.dispose();
}
// ...@overrideFuture<dynamic> onEventFromWindow(String eventName, int fromWindowId, dynamic arguments) async {
print('[${WindowManagerPlus.current}] Event $eventName from Window $fromWindowId with arguments $arguments');
return'Hello from ${WindowManagerPlus.current}';
}
}
Using WindowManagerPlus.getAllWindowManagerIds() static method you can get all the window manager ids available.
Please see the example app of this plugin for a full example.
Listening events
The WindowListener mixin class is used to listen to window events.
If this is used as a Global Listener using the WindowManagerPlus.addGlobalListener static method,
the windowId parameter will be the ID of the window that emitted the event,
otherwise, it will be always null.
Invokes a method on the window with id targetWindowId. It could return a Future that resolves to the return value of the invoked method, otherwise null. Use WindowListener.onEventFromWindow to listen for the event.
You can call this to remove the window frame (title bar, outline border, etc), which is basically everything except the Flutter view, also can call setTitleBarStyle(TitleBarStyle.normal) or setTitleBarStyle(TitleBarStyle.hidden) to restore it.
Set if intercept the native close signal. May useful when combine with the onclose event listener. This will also prevent the manually triggered close event.