経験値上昇中

年々趣味が増えていく男性の感じたことや経験したことを書いていこうかなと。

【備忘録】メッセージボックスのボタン表示を無理矢理変える手順(VB.NET)

どうも、りょーちんです。

 

タイトルの通りです。まぁ、業務上でハマった内容を備忘録として残しておくことにします。

 

経緯:VB.NETでResourceを使用して多言語対応を行った業務プログラムを取り扱っています。メッセージボックスに表示するメッセージ内容については、言語設定を変更することで、Resourceファイルから対象言語のメッセージをIDから引っ張って表示する仕組みで対応できています。

 

しかし、

 

メッセージボックスのボタンだけなぜか日本語のまま…。

「はい(Y)」「いいえ(N)」のやつ。

 

なぜなら、WindowsFormsのボタンをそのまま利用しているためです。

英語OSを使用すれば、「Yes(Y)」「No(N)」になるはず。

日本語OSなら当然日本語になりますよね。

 

そこで、違和感あるからなんとかならんか?と言われてしまい…。

(正直英語OSで切り替わるならいいじゃん…。mndks…。)

 

まぁ、Googleさんで検索してみました。

見つけた方法は2つ。

 

  1. 新規フォームからメッセージボックスを作成する。
  2. フックを使う。

 

1は、確実な方法です。

ただ、既存にメッセージボックスの記述部分が多数存在する場合、書き換えるのに手間がかかってしまう難点があります。

 

今回は、2の方法で対応してみます。

C#やC+の記述例はヒットしますが、VB.NETの記述はヒットしなかったので、

以下、サンプルを記載します。(やろうと思えばC#やC+にも利用可)

 

'【CustomMsgBox.vb】
Imports System.Text
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Runtime.InteropServices

Public Class CustomMsgBox
  Private hHook As IntPtr = IntPtr.Zero
  Public Property ButtonText As CustomButtonText

  Public Sub New()
    Me.ButtonText = New CustomButtonText()
  End Sub

  Public Function Show(ByVal text As String, ByVal caption As String, ByVal buttons As MessageBoxButtons, ByVal icons As MessageBoxIcon) As DialogResult
    Try
      BeginHook()
      Return MessageBox.Show(text, caption, buttons, icons)
    Finally
      EndHook()
    End Try
  End Function

  Private Sub BeginHook()
    EndHook()
    Me.hHook = SetWindowsHookEx(WH_CBT, New HOOKPROC(AddressOf Me.HookProcs), IntPtr.Zero, GetCurrentThreadId())
  End Sub

  Private Function HookProcs(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
    If nCode = HCBT_ACTIVATE Then
      If Me.ButtonText.Abort IsNot Nothing Then
        SetDlgItemText(wParam, ID_BUT_ABORT, Me.ButtonText.Abort)
      End If
      If Me.ButtonText.Cancel IsNot Nothing Then
        SetDlgItemText(wParam, ID_BUT_CANCEL, Me.ButtonText.Cancel)
      End If
      If Me.ButtonText.Ignore IsNot Nothing Then
        SetDlgItemText(wParam, ID_BUT_IGNORE, Me.ButtonText.Ignore)
      End If
      If Me.ButtonText.No IsNot Nothing Then
        SetDlgItemText(wParam, ID_BUT_NO, Me.ButtonText.No)
      End If
      If Me.ButtonText.OK IsNot Nothing Then
        SetDlgItemText(wParam, ID_BUT_OK, Me.ButtonText.OK)
      End If
      If Me.ButtonText.Retry IsNot Nothing Then
        SetDlgItemText(wParam, ID_BUT_RETRY, Me.ButtonText.Retry)
      End If
      If Me.ButtonText.Yes IsNot Nothing Then
        SetDlgItemText(wParam, ID_BUT_YES, Me.ButtonText.Yes)
      End If
      EndHook()
    End If

    Return CallNextHookEx(Me.hHook, nCode, wParam, lParam)
  End Function

  Private Sub EndHook()
    If Me.hHook <> IntPtr.Zero Then
      UnhookWindowsHookEx(Me.hHook)
      Me.hHook = IntPtr.Zero
    End If
  End Sub

  Public Class CustomButtonText
    Public Property OK As String
    Public Property Cancel As String
    Public Property Abort As String
    Public Property Retry As String
    Public Property Ignore As String
    Public Property Yes As String
    Public Property No As String
  End Class

  Const WH_CBT As Integer = 5
  Const HCBT_ACTIVATE As Integer = 5
  Const ID_BUT_OK As Integer = 1
  Const ID_BUT_CANCEL As Integer = 2
  Const ID_BUT_ABORT As Integer = 3
  Const ID_BUT_RETRY As Integer = 4
  Const ID_BUT_IGNORE As Integer = 5
  Const ID_BUT_YES As Integer = 6
  Const ID_BUT_NO As Integer = 7

  Private Delegate Function HOOKPROC(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
  <DllImport("user32.dll")>
  Private Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal lpfn As HOOKPROC, ByVal hInstance As IntPtr, ByVal threadId As IntPtr) As IntPtr
  End Function
  <DllImport("user32.dll")>
  Private Shared Function UnhookWindowsHookEx(ByVal hHook As IntPtr) As Boolean
  End Function
  <DllImport("user32.dll")>
  Private Shared Function CallNextHookEx(ByVal hHook As IntPtr, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
  End Function
  <DllImport("kernel32.dll")>
  Private Shared Function GetCurrentThreadId() As IntPtr
  End Function
  <DllImport("user32.dll", CharSet:=CharSet.Auto)>
  Private Shared Function SetDlgItemText(ByVal hWnd As IntPtr, ByVal nIDDlgItem As Integer, ByVal lpString As String) As Boolean
  End Function
End Class

 

'【Sample.vb】
Dim msg As New CustomMsgBox
'▼「はい」を「イエっ俊龍」に設定
msg.ButtonText.Yes = "イエっ俊龍"
'▼「いいえ」を「ノー俊龍」に設定
msg.ButtonText.No = "ノー俊龍"
'▼「キャンセル」を「メンブレしたのでキャンセル」に設定
msg.ButtonText.Cancel = "メンブレしたのでキャンセル"
'▼メッセージ表示
msg.Show("LINEをやりますか?", "確認", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question)

 

メリットは、新たにフォームを作らずに記述を少々いじる程度で対応ができること。

デメリットは、フックを利用することがあまり推奨されていないことから、問題があった時に対応に困りそう。

※フックの解除のし忘れには要注意らしい。下手すればOS再インストールとか怖い書き込みありましたが…。

あと無理矢理ボタンの表示名を変えているからか、標準のショートカットキー(「はい(Y)」ならYキー、「いいえ」ならNキー)が
使えなくなるところがネックに感じますね。

なんかもっと良い方法があればいいものの…。(あればコメントに是非ご教授下さい。)

以上、備忘録でした。