using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace KssSmaPlaLib.Forms { public static class ToastNotifier { private const int WS_EX_NOACTIVATE = 0x08000000; private const int WS_EX_TOPMOST = 0x00000008; //public static void Show(Form owner, string message, Color backColor, int durationMs = 3000) //{ // // 1. フォームの基本設定 // Form toast = new Form // { // FormBorderStyle = FormBorderStyle.None, // StartPosition = FormStartPosition.Manual, // BackColor = backColor, // ShowInTaskbar = false, // TopMost = true, // Size = new Size(300, 60), // Opacity = 0, // Enabled = false // トースト自体は操作不能にしておく // }; // Label lbl = new Label // { // Text = message, // ForeColor = Color.White, // Font = new Font("MS UI Gothic", 10, FontStyle.Bold), // Dock = DockStyle.Fill, // TextAlign = ContentAlignment.MiddleCenter // }; // toast.Controls.Add(lbl); // // オーナーの右下に配置 // toast.Location = new Point( // owner.Location.X + owner.Width - toast.Width - 20, // owner.Location.Y + owner.Height - toast.Height - 20 // ); // // 2. タイマーでアニメーションを管理 // Timer timer = new Timer { Interval = 10 }; // DateTime startTime = DateTime.Now; // timer.Tick += (s, e) => // { // double elapsed = (DateTime.Now - startTime).TotalMilliseconds; // if (elapsed < 200) // 最初の0.2秒でフェードイン // { // toast.Opacity = elapsed / 200.0; // } // else if (elapsed < 200 + durationMs) // 指定時間はそのまま // { // toast.Opacity = 1.0; // } // else if (elapsed < 200 + durationMs + 200) // 最後の0.2秒でフェードアウト // { // toast.Opacity = 1.0 - (elapsed - (200 + durationMs)) / 200.0; // } // else // 終了処理 // { // timer.Stop(); // timer.Dispose(); // toast.Close(); // toast.Dispose(); // } // }; // // 3. 表示開始(ここを通った瞬間にメソッドは終了し、呼び出し元に戻ります) // toast.Show(); // timer.Start(); //} //public static async Task ShowAsync(Form owner, string message, Color backColor, int durationMs = 3000) //{ // // 1. フォームの作成 // using (var toast = new Form // { // FormBorderStyle = FormBorderStyle.None, // StartPosition = FormStartPosition.Manual, // BackColor = backColor, // ShowInTaskbar = false, // TopMost = true, // Size = new Size(300, 60), // Opacity = 0 // }) // { // // フォーカスを奪わないための設定(WinAPIをラップしたCreateParamsの上書き) // // ※簡易的にリフレクションで設定するか、Formを継承したクラスを作るのが定石です // Label lbl = new Label // { // Text = message, // ForeColor = Color.White, // Font = new Font("MS UI Gothic", 10, FontStyle.Bold), // Dock = DockStyle.Fill, // TextAlign = ContentAlignment.MiddleCenter // }; // toast.Controls.Add(lbl); // toast.Location = new Point( // owner.Location.X + owner.Width - toast.Width - 20, // owner.Location.Y + owner.Height - toast.Height - 20 // ); // // 表示(フォーカスを奪わずに表示) // toast.Show(); // // フォーカスが移るのを防ぐための念押し(Activateさせない) // // 2. アニメーション(async/await の真骨頂) // for (int i = 0; i < 10; i++) { toast.Opacity += 0.1; await Task.Delay(10); } // await Task.Delay(durationMs); // for (int i = 0; i < 10; i++) { toast.Opacity -= 0.1; await Task.Delay(10); } // toast.Close(); // } //} public static async Task ShowAsync(Form owner, string message, Color backColor, int durationMs = 3000) { // 【重要】SignalRなどの別スレッドから呼ばれても安全なように、UIスレッドへ強制バトンタッチ if (owner.InvokeRequired) { owner.BeginInvoke(new Action(() => { _ = ShowAsync(owner, message, backColor, durationMs); })); return; } // 1. フォームの作成(usingは使わず、Closeに任せる) // フォーカスを奪わない(WS_EX_NOACTIVATE = 0x08000000)を仕込んだカスタムフォームをその場で定義 var toast = new NoActivateForm { FormBorderStyle = FormBorderStyle.None, StartPosition = FormStartPosition.Manual, BackColor = backColor, ShowInTaskbar = false, TopMost = true, Size = new Size(300, 60), Opacity = 0 }; Label lbl = new Label { Text = message, ForeColor = Color.White, Font = new Font("MS UI Gothic", 10, FontStyle.Bold), Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleCenter }; toast.Controls.Add(lbl); // 右下のベストポジションに配置(オーナーフォーム基準) toast.Location = new Point( owner.Location.X + owner.Width - toast.Width - 20, owner.Location.Y + owner.Height - toast.Height - 20 ); // 表示(フォーカスを絶対に奪わない) toast.Show(); // 2. アニメーション(async/await の真骨頂!) for (int i = 0; i < 10; i++) { toast.Opacity += 0.1; await Task.Delay(10); } await Task.Delay(durationMs); for (int i = 0; i < 10; i++) { toast.Opacity -= 0.1; await Task.Delay(10); } // 閉じると同時に、WinFormsが自動でメモリを綺麗に解放(Dispose)してくれます toast.Close(); } } // 【職人技】フォーカスを絶対に奪わないフォーム(CreateParamsの上書き) public class NoActivateForm : Form { protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.ExStyle |= 0x08000000; // WS_EX_NOACTIVATE(クリックしてもフォーカスが移らない魔法のフラグ) return cp; } } } }