.NETでフォームを最前面表示かつ他ウィンドウのフォーカスを奪わないで表示

Outlookみたいな通知画面を出したかった

Outlookあるじゃないですか、あれって新着メールが来たら画面の右下にぴよっとポップアップというか、じわーっと表示される画面を出せますよね。あんなのを出したかった。
最前面に表示される、だがしかし、表示させるときには他のウィンドウのフォーカスを奪わない。Wordで文書を書いているときにフォーカス奪われたらたまったもんじゃない。

TopMost + ShowWithoutActivationじゃダメ

FormにはTopMostというプロパティがあり、こいつをtrueにすれば最前面表示される。また、ShowWithoutActivationというプロパティをオーバーライドしてtrueを返すようにすると、表示する際にアクティブにならない(他ウィンドウのフォーカスを奪わない)ので、これを組み合せれば良いはず。

ところが、TopMostをtrueにすると必ずアクティブになるようで、使えない。

SetWindowPosする

上記問題はTopMostプロパティを使わずに、SetWindowPosを呼べば解決できます。例えば、C#なら以下のメソッドを呼べばOK。

       private void SetTopMost()
       {
           const int HWND_TOPMOST = -1;
           const uint SWP_NOSIZE = 0x0001;
           const uint SWP_NOMOVE = 0x0002;
           const uint SWP_NOACTIVATE = 0x0010;
           const uint SWP_SHOWWINDOW = 0x0040;
           const uint SWP_NOSENDCHANGING = 0x0400;
            // SetWindowPosはどこかでDllImportする
           SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_SHOWWINDOW);
       }

で、ShowWithoutActivation?をオーバーライドしtrueを返せば、所望の表示が可能。

参考サイト

参考というより、パクリ元と書くべきだな。

Windowsの組織や使用者をセットするPowerShellスクリプト

Windows Server 2003まではインストール時に組織と使用者が問われたが、2008から聞かれなくなった。聞かれないならいいやとも思うが、何となくセットしておきたい気もするので、後々使い回せるようPowerShell?で簡単なスクリプトを書いた。

2008 R2で確認したが、単にレジストリ変えているだけなので、Windows 7とかVistaとかでも大丈夫なはず。変更したいときもこれで良い。

スクリプト

$regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\"

"現在の組織: "   + (Get-ItemProperty $regPath)."RegisteredOrganization"
"現在の使用者: " + (Get-ItemProperty $regPath)."RegisteredOwner"

$newOrganization = Read-Host "新しい組織を入力してください"
$newOwner        = Read-Host "新しい使用者を入力してください"

# 32ビット/64ビット共通
Set-ItemProperty $regPath -name "RegisteredOrganization" -value $newOrganization
Set-ItemProperty $regPath -name "RegisteredOwner" -value $newOwner

# 64ビットのみ
$regPath64 = $regPath -replace "SOFTWARE", "SOFTWARE\Wow6432Node"
if(Test-Path $regPath64)
{
	Set-ItemProperty $regPath64 -name "RegisteredOrganization" -value $newOrganization
	Set-ItemProperty $regPath64 -name "RegisteredOwner" -value $newOwner
}

.NETでメッセージボックスのボタンのテキストを変える

フォーム作るの面倒

メッセージボックスのボタンのテキストをちょこっと変えたいというのはありがちだができない。フォーム作るのが正攻法、でも面倒。ちょこっとだし。

フックする

Google先生いわくフックすればできるそうで。テキストの変更くらい標準でできれば嬉しいのに。

C#で書いた

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace hogehoge
{
    /// 
    /// ボタンのテキストをカスタマイズできるメッセージボックスです。
    /// 
    public class ButtonTextCustomizableMessageBox
    {
        private IntPtr hHook = IntPtr.Zero;

        /// 
        /// ボタンに表示するテキストを指定します。
        /// 
        public CustomButtonText ButtonText { get; set; }

        /// 
        /// コンストラクタ。
        /// 
        public ButtonTextCustomizableMessageBox()
        {
            this.ButtonText = new CustomButtonText();
        }

        /// 
        /// ダイアログボックスを表示します。
        /// 
        public DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icons)
        {
            try
            {
                BeginHook();
                return MessageBox.Show(text, caption, buttons, icons);
            }
            finally
            {
                EndHook();
            }
        }

        /// 
        /// フックを開始します。
        /// 
        void BeginHook()
        {
            EndHook();
            this.hHook = SetWindowsHookEx(WH_CBT, new HOOKPROC(this.HookProc), IntPtr.Zero, GetCurrentThreadId());
        }

        IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode == HCBT_ACTIVATE)
            {
                if (this.ButtonText.Abort != null) SetDlgItemText(wParam, ID_BUT_ABORT, this.ButtonText.Abort);
                if (this.ButtonText.Cancel != null) SetDlgItemText(wParam, ID_BUT_CANCEL, this.ButtonText.Cancel);
                if (this.ButtonText.Ignore != null) SetDlgItemText(wParam, ID_BUT_IGNORE, this.ButtonText.Ignore);
                if (this.ButtonText.No != null) SetDlgItemText(wParam, ID_BUT_NO, this.ButtonText.No);
                if (this.ButtonText.OK != null) SetDlgItemText(wParam, ID_BUT_OK, this.ButtonText.OK);
                if (this.ButtonText.Retry != null) SetDlgItemText(wParam, ID_BUT_RETRY, this.ButtonText.Retry);
                if (this.ButtonText.Yes != null) SetDlgItemText(wParam, ID_BUT_YES, this.ButtonText.Yes);

                EndHook();
            }

            return CallNextHookEx(this.hHook, nCode, wParam, lParam);
        }

        /// 
        /// フックを終了します。何回呼んでもOKです。
        /// 
        void EndHook()
        {
            if (this.hHook != IntPtr.Zero)
            {
                UnhookWindowsHookEx(this.hHook);
                this.hHook = IntPtr.Zero;
            }
        }

        #region メッセージのテキストのクラス定義

        public class CustomButtonText
        {
            public string OK { get; set; }
            public string Cancel { get; set; }
            public string Abort { get; set; }
            public string Retry { get; set; }
            public string Ignore { get; set; }
            public string Yes { get; set; }
            public string No { get; set; }
        }

        #endregion

        #region Win32API

        const int WH_CBT = 5;
        const int HCBT_ACTIVATE = 5;

        const int ID_BUT_OK = 1;
        const int ID_BUT_CANCEL = 2;
        const int ID_BUT_ABORT = 3;
        const int ID_BUT_RETRY = 4;
        const int ID_BUT_IGNORE = 5;
        const int ID_BUT_YES = 6;
        const int ID_BUT_NO = 7;

        private delegate IntPtr HOOKPROC(int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowsHookEx(int idHook, HOOKPROC lpfn, IntPtr hInstance, IntPtr threadId);

        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hHook);

        [DllImport("user32.dll")]
        private static extern IntPtr CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetCurrentThreadId();

        [DllImport("user32.dll", CharSet=CharSet.Auto)]
        private static extern bool SetDlgItemText(IntPtr hWnd, int nIDDlgItem, string lpString);

        #endregion
    }
}

使い方

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace hogehoge
{
    class Program
    {
        static void Main(string[] args)
        {
            var msg = new ButtonTextCustomizableMessageBox();
            msg.ButtonText.Yes = "了解";
            msg.ButtonText.No = "承知";
            msg.ButtonText.Cancel = "はい";
            msg.Show("実行してよろしいですか?", "ほげほげ", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
        }
    }
}