🙄

デスクトップを整理するアプリを作った(実装説明)

2025/03/04に公開

環境

Windows11 Pro 24H2
Visual Studio Pro2022(64bit) Version 17.13.0

アプリ説明

デスクトップ上にある、アイコンを分ける為の枠を表示するアプリです
枠の上部には任意の文言をかけたり、枠自体の色も変えれるので、
アイコンの塊毎に意味を持たせることが出来て、デスクトップ上のアイコンが迷子になるのを防ぎます

Boothで販売しているので、興味あれば購入下さい
https://nori33.booth.pm/items/6572311
アプリの説明は以下
https://zenn.dev/maedan/articles/428a5eacf0b34a

技術要素

作るにあたって頑張った点です
・タスクトレイに常駐させる
・フォームを枠だけ残して透過とする
・フォームを絶えず最背面とする
・スタートアップに登録する
・ショートカットからの起動時の設定の読み込み

タスクトレイへの常駐

formデザインの画面で、ツールボックス内にある、「NotifyIcon」をダブルクリックしてください
デザイン画面の下側に「NotifyIcon」が表示されます

プロパティウィンドウにて、「Icon」に、タスクトレイで表示されるアイコンを指定してください
これでタスクトレイに指定のアイコンが表示されますが、Formも表示されます
起動時には非表示にしたいので、Formのプロパティを変更しましょう

プロパティ
Icon アイコンファイルを選択下さい
WindowsState Minimized

フォームを枠だけ残して透過とする

デフォルトのフォームの表示では、タイトルなど不要な部分があるので、それらを消しちゃいます
また、枠を表示しているだけなので、タスクバーにアイコンが表示されるのは違和感があるので、これを辞めます
これらは、Fromのコンストラクタに記載します

this.FormBorderStyle = FormBorderStyle.None; // フォームの上部を無くす
this.ShowInTaskbar = false; // タスクバーにアイコンを表示しない

あとは、FormのPaintイベントに記述します
まずは、外周の角を丸くします
pathを作成して、詳細を追加する前にstartします

int radius = 10;// 角の半径
System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
path.StartFigure();

後はpathを追加していきます、まず左上の円弧です

path.AddArc(new Rectangle(0, 0, radius, radius), 180, 90);      

という感じで、pathにそれぞれの角を追加していきます
AddArcの引数には、第1引数にRectangleを渡します
Rectangleは名前の通り、円の情報ですが、
AddArcの第2,3引数が度数となっており、
Rectanbleで指定した円の180度から90度を有効としています

ここではは、左上側だけを追加していますが、残り3か所分も追加しましょう

    path.AddArc(new Rectangle(this.Width - radius, 0, radius, radius), 270, 90);
    path.AddArc(new Rectangle(this.Width - radius, this.Height - radius, radius, radius), 0, 90);
    path.AddArc(new Rectangle(0, this.Height - radius, radius, radius), 90, 90);

右上、右下、左下の順で追加していきます
AddArcの最後は、Closeが必要です

path.CloseFigure();

次にFormの中を切り抜くために同じ様に設定してきます

GraphicsPath innerPath = new GraphicsPath();
innerPath.StartFigure();
innerPath.AddLine(lineWidth, lineWidth, this.Width - lineWidth, lineWidth);
innerPath.AddArc(new Rectangle(this.Width - lineWidth - diameter, this.Height - lineWidth - diameter, diameter, diameter), 0, 90);
innerPath.AddArc(new Rectangle(lineWidth, this.Height - lineWidth - diameter, diameter, diameter), 90, 90);
innerPath.CloseFigure();
path.AddPath(innerPath,false);

フォームを絶えず最背面とする

ここが一番手間取った処理です
Formのプロパティでは、最前面にすることは出来ても、絶えず最背面には出来ません
クリックすると、前面に出てきちゃいます
今回の枠としては、前面に出てきて欲しくないので、絶えず最背面としています

Win32APIを使用します
コードの先頭に以下の宣言を記述下さい

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

「Program Manager」内の「Program Manager」の子ウィンドウはすでにいくつかあるのですが、
この中の「FolderView'SysListView32」の子ウィンドウとすることで最背面としています
この「Program Manager」内の「FolderView'SysListView32」をどうやって探したかというと、
Spy++というアプリでWindowハンドルを知る事が出来ます
Spy++はVisualStudioのメニューから「ツール」「Spy++」で起動します

実装としては、「FolderView'SysListView32」は、「Progman」の中にあるので、まずはこのProgmanを探して、
その中から、SysListView32を探し、ウィンドウハンドルを取得します

IntPtr shellWindow = FindWindow("Progman", null);
IntPtr desktopWindow = IntPtr.Zero;

if (shellWindow != IntPtr.Zero){
    IntPtr shellDllDefView = FindWindowEx(shellWindow, IntPtr.Zero, "SHELLDLL_DefView", null);
    if (shellDllDefView != IntPtr.Zero){
        desktopWindow = FindWindowEx(shellDllDefView, IntPtr.Zero, "SysListView32", "FolderView");
    }
}

取得したウィンドウハンドルを用いて、枠であるFormを子ウィンドウに設定します

SetParent(this.Handle, desktopWindow);

これで、ずっと最背面に固定されます

スタートアップに登録する

設定ウィンドウ内に、「スタートアップに登録」というチェックボックスを用意しました
ここにチェックを入れると、スタートアップに登録されて、次回からPC起動時に勝手にソフトが立ち上がります
このスタートアップへの登録は、レジストリへ登録する事で実現します

Microsoft.Win32.RegistryKey regkey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
    @"Software\Microsoft\Windows\CurrentVersion\Run", true);
regkey.SetValue(Application.ProductName, Application.ExecutablePath);
regkey.Close();

ショートカットからの起動時の設定の読み込み

インストーラを作成して、動作確認を行うと、設定ファイルを読み出せずに、起動毎に初期化される現象が起こりました。これはショートカットからの起動時に、カレントフォルダが変わってしまい、設定ファイルを読み出せない、といった事が原因でした

string exePath = Path.GetDirectoryName(Application.ExecutablePath);
Directory.SetCurrentDirectory(exePath);

自分で作成したアプリをインストールして使用するといった事があまりなかったので、
簡単な事なんですが、結構悩みました

Discussion