砂漠の旅人(たびと)

UNIX / MS-DOS 時代から電脳砂漠を旅しています

【Win32API】.NET 6 C# で「タスクバーを自動的に隠す」の有効・無効を制御する

こんにちは、たびとです。

古い話ですが、Windows XP 時代に .NET Framework による WinForm を散々作っていましたが、 Win32 API を使わないとできないことが多々あったので、よく使っていました。

この Win32 API .NET 6 で使うことができるのだろうか、というのが今回のテーマです。

Windows タスクバーを右クリックしたときに表示される「タスク バーの設定」から 「タスク バーを自動的に隠す」のチェックボックスを有効・無効に切り替えるアプリを作って検証してみます。

この記事の対象者

  • Win32 API を用いて Windows を制御することに興味のある方
  • .NET 6 で Win32 API アプリを開発してみたい方

Win32 API .NET 6 で実装する

Win32 API を使うことだけを考えると、.NET Framework を選択した方がいいと思います。 しかし、サーバアプリを ASP.NET .NET 6 で作成した場合、 クライアントアプリを .NET Framework 4.8 で作るのはどうなのかなと思ってしまいます。 例えば、共通ライブラリとか作る場合、.NET 6 に統一した方がメンテナンスが楽になります。

タスクバー制御の Win32 API

「タスクバーを自動的に隠す」を実装するには、Win32 APIFindWindowSHAppBarMessage を使います。 詳細は、以下を参照してください。

learn.microsoft.com

learn.microsoft.com

この Win32 APIC# で実装するには、DllImport 属性を使って定義するだけです。 それに加えて、Win32 API に指定する引数や構造体を必要に応じて定義します。

ソースコード

Visual Studio 2022 の 新規プロジェクトの Console アプリで .NET 6 を選択し、 適当な名前で作成します。 Program.cs のソースに以下のコードをコピペして完了です。

using System.Drawing;
using System.Runtime.InteropServices;

// ウィンドウハンドルを取得する
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
extern static IntPtr FindWindow(
    [MarshalAs(UnmanagedType.LPWStr), In] string lpClassName,
    [MarshalAs(UnmanagedType.LPWStr), In] string? lpWindowName);

// appbar メッセージをシステムに送信する
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
extern static UIntPtr SHAppBarMessage(UInt32 dwMessage, ref AppBarData pData);

const UInt32 ABM_GETSTATE = 0x00000004;     //Windows タスクバーの自動表示と常時表示の状態を取得する
const UInt32 ABM_SETSTATE = 0x0000000a;     //Windows タスクバーの自動表示と常時表示の状態を設定する

const uint ABS_AUTOHIDE = 0x01;             //自動的に隠す
const uint ABS_ALWAYSONTOP = 0x02;          //常に表示する

// Windows(OS)判定
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
    Console.Error.WriteLine("Windows で実行してください。");
    return;
}

// タスバーの状態を取得する
if (ABS_AUTOHIDE == GetTaskbarStatus())
{
    Console.WriteLine("Auto hide -> Always on top");
    SetTaskbarStatus(ABS_ALWAYSONTOP);
}
else
{
    Console.WriteLine("Always on top -> Auto hide.");
    SetTaskbarStatus(ABS_AUTOHIDE);
}

// タスクバーの状態を取得する
uint GetTaskbarStatus()
{
    var data = new AppBarData { hWnd = FindWindow("System_TrayWnd", null) };
    data.cbSize = (UInt32)Marshal.SizeOf(data);
    return (uint)SHAppBarMessage(ABM_GETSTATE, ref data);
}

// タスクバーの状態を変更する
uint SetTaskbarStatus(uint option)
{
    var data = new AppBarData
    {
        hWnd = FindWindow("System_TrayWnd", null),
        lParam = (Int32)option
    };
    data.cbSize = (UInt32)Marshal.SizeOf(data);
    return (uint)SHAppBarMessage(ABM_SETSTATE, ref data);
}

// APPBARDATA 構造体
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct AppBarData
{
    public UInt32 cbSize;
    public IntPtr hWnd;
    public UInt32 uCallbackMessage;
    public UInt32 uEdge;
    public Rectangle rc;
    public Int32 lParam;
}

ポイント

Win32 API を使うときに、マーシャリングを意識する必要があります。 詳細は、以下のページを参考にしてください。

learn.microsoft.com

ここで、SHAppBarMessage の引数 struct AppBarData[MarshalAs(UnmanagedType.LPStruct)] を付けると、 エラーになったので外してあります。 Rectangle を使っているため、アンマネージドへの変換に失敗しているのかもしれません。 自前で Rect 構造体を用意するといいのかもしれないですけど、今回はここまでにしておきます。

実行結果

ソースコードを実行するたびに、「タスクバーを自動的に隠す」設定の有効・無効が交互に切り替わります。 以下、Windows 11 で実行した結果を張り付けています。 たまに、タスクバーが自動的に隠れない場合があります。そんなときは、一度スタートメニューをクリックすると隠れると思います。

タスクバーが常に表示されている状態

タスクバーを自動的に隠す状態

WSL2 Ubuntu で実行するとどうなるのか

WSL2 Ubuntuソースコードをコピーしてビルドすると、 Win32 API でエラーになるかと思いきや、成功してしまいました。

$ dotnet build
Microsoft (R) Build Engine version 17.0.1+b177f8fa7 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  Taskbar -> /home/tabito/Taskbar/Taskbar/bin/Debug/net6.0/Taskbar.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.73

実行すると、Windows の OS 判定を入れているので、期待通りに終了します。

$ dotnet run
Windows で実行してください。

まとめ

今回は、Win32 API .NET 6 で実装するために簡単なアプリを作成しました。 .NET Framework の頃と同じ感覚で実装できるようなので、過去資産がある場合は有効活用ができると思います。 ただし、昔はマーシャリングを厳密に書いていないソースが多いので、そこを注意した方がいいかもしれません。

また、今回のように WSL2 Ubuntu 上で Win32 API を指定したソースコードは、ビルドも実行もできてしまいます。 .NET 6 に移植する場合、他のプラットフォームで動作することも注意する必要があります。

では、皆さん、よい旅を。