📥

【Flutter・Windows】アプリをタスクトレイで起動する方法【tray_manager, window_manager】

に公開

はじめに

常駐型のアプリや、小さなツールアプリはタスクバーではなくタスクトレイにアイコンを表示させたくなる時があります。
今回はFlutterで作るWidowsアプリをタスクトレイにどのように表示させるか説明します。

パッケージはtray_managerwindow_managerを使用します。
tray_managerでタスクトレイにアイコンを表示し、window_managerでタスクバーの設定や、タスクトレイを操作したときのウィンドウ操作を行います。

https://pub.dev/packages/tray_manager
https://pub.dev/packages/window_manager

1. タスクトレイ用のウィジェットを作る

基本的に以下に記載するコードをコピペすればOKです。
initStateonTrayMenuItemClickはアプリに合わせて変更します。

task_tray.dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';

class TaskTray extends StatefulWidget {
  const TaskTray({super.key,required this.child});

  final Widget child;

  
  State<TaskTray> createState() => _TaskTrayState();
}

class _TaskTrayState extends State<TaskTray> with TrayListener {
  final _trayManager = TrayManager.instance;

  
  void initState() {
    unawaited(()async{
      // トレイアイコン設定
      await _trayManager.setIcon('assets/tray_icon.ico'); // アイコン
      await _trayManager.setToolTip('Tooltip');           // ツールチップ
      await _trayManager.setContextMenu(                  // メニュー
        Menu(
          items: [
            MenuItem(key: 'show', label: '表示'),
            MenuItem(key: 'exit', label: '終了'),
          ],
        ),
      );
      _trayManager.addListener(this);
    }());
    super.initState();
  }

  
  void dispose() {
    _trayManager.removeListener(this);
    super.dispose();
  }

  // タスクトレイのアイコンをクリックしたとき
  
  void onTrayIconMouseDown() {
    // メニューを開く       
    _trayManager.popUpContextMenu();           
  }

  // メニューの項目を選んだ時
  
  void onTrayMenuItemClick(MenuItem menuItem) {
    if (menuItem.key == 'show') {
      // ウィンドウを表示する
      windowManager.show();
    } else if (menuItem.key == 'exit') {
      // アプリを終了する
      windowManager.destroy();
    }
  }
  
  
  Widget build(BuildContext context) {
    return widget.child;
  }
}

タスクトレイのアイコンに対して操作を行えるようにするには、
TrayListenerをMixinしてアイコンを押下したときの処理を明示的に書く必要があります。
このため、ウィジェット内で処理を書く必要があります。

注意点

TrayListener

StateクラスにTrayListenerをつけ忘れないようにしましょう。

class _TaskTrayState extends State<TaskTray> with TrayListener {}

アイコンの拡張子

Windowsの場合、アイコン画像の拡張子は、.icoである必要があります。
(MacOSは.pngです。)

await _trayManager.setIcon('assets/tray_icon.ico'); // アイコン

MenuItemonClickは設定しても反応しません。
onTrayMenuItemClickに処理を実装しましょう。

await _trayManager.setContextMenu(                  // メニュー
        Menu(
          items: [
            MenuItem(key: 'show', label: '表示'),
            MenuItem(key: 'exit', label: '終了'),
          ],
        ),
      );

また、メニューに区切りを入れたい場合は、MenuItem.separator()を使うことができます。

メニューが不要な場合

コンテキストメニューが不要な場合は、以下の通り書けば、タスクバーのアイコンをクリックした時と同等の操作にできます。
(ウィンドウが表示中の時は非表示へ、非表示の時は表示へ。)
この時、setContextMenuonTrayMenuItemClickは不要なため、削除しましょう。

task_tray.dart
  // タスクトレイのアイコンをクリックしたとき
  
  void onTrayIconMouseDown() {
    unawaited(()async{
      // ウィンドウがユーザーに見えていれば、
      if(await windowManager.isVisible()){
        // ウィンドウを非表示に
        windowManager.hide();
      // ウィンドウがユーザーに見えていない場合、
      }else{
        // ウィンドウを表示
        windowManager.show();
      }
    }());
  }

2. mainメソッドの実装

windowManagerを使用し、タスクバーのアイコンを非表示にします。
runAppの部分で実装したTaskTrayを使用します。

main.dart
import 'task_tray.dart';
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';

void main() async {
  // Flutterの初期化処理
  WidgetsFlutterBinding.ensureInitialized();

  // ウィンドウの設定
  await windowManager.ensureInitialized();
  WindowOptions windowOptions = const WindowOptions(
    skipTaskbar: true,                                  // タスクバーにアイコンを表示しない
  );
  windowManager.waitUntilReadyToShow(windowOptions);

  // タスクトレイの設定をして、アプリを起動
  runApp(TaskTray(child:const MyApp()));
}

これでタスクバーにアイコンを表示させず、タスクトレイにアイコンを表示し、操作できるようにできました!

Discussion