2011年12月25日日曜日

[ExcelVBA] ウィンドウの表示


■お題
ウィンドウの表示(問題出典:どう書く?org)
- 問題詳細 -
画面中央に幅100ピクセル、高さ75ピクセルのウィンドウを表示してください。
タイトルには「こんにちは、GUI!」と表示してください。
使用するGUIライブラリに名前が付いているならば、それをタグでつけてください。(例えば「Swing」など。)

追記:100x75はあまりに小さすぎるようなので、小さすぎるせいでうまく行かない場合は400x300でもいいということにします。 このお題の意図は「小さいウィンドウを出せ」というわけではないので。

■解答例
ポイントは2つ。
・ディスプレイのサイズはどうやって取得するか?
・ポイントとピクセルの違いは?

ExcelVBAでウィンドウサイズを取得できないようなので、WindowsAPIのGetWindowRect関数を使って取得するとよさそうです。

□ウィンドウズAPIとは?
Windows API(ウィンドウズ エーピーアイ)とは、Microsoft WindowsのAPIのことである。特に32ビットプロセッサで動作するWindows 95以降やWindows NTで利用できるものはWin32 APIと呼ばれる。また、それらのWindowsにおけるWin32 APIの実装をWin32と呼ぶ。
(WikiPediaより引用)

平たくいえば、OSの機能を外部プログラムとやりとりするものですね。
WindowsAPIのGetWindowRect関数は、“ウィンドウ”全体の座標を取得することができます。
この関数では、あらゆる“ウィンドウ”の座標を取得できるのでまずどの“ウィンドウ”の座標を知りたいのか指定する必要があります。“ウィンドウの”指定方法は、全ての“ウィンドウ”には重複のない番号が割り当てられているのでその番号を取得してGetWindowRect関数に渡す必要があります。

今回知りたい“ウィンドウ”は、デスクトップです。
デスクトップのハンドルを知るのにGetDesktopWindow関数を使いました。


では、コードです。
まずは、デスクトップのサイズを取得する関数についてです。
モジュール名:SubModule内
  1. Option Explicit  
  2.   
  3. Declare Function GetDesktopWindow Lib "user32" () As Long  
  4. Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long  
  5.   
  6. Type RECT  
  7.     Left As Long  
  8.     Top As Long  
  9.     Right As Long  
  10.     Bottom As Long  
  11. End Type  
  12.   
  13. Function GetWindowSize() As WindowSize  
  14.     Dim hwnd As Long  
  15.     hwnd = GetDesktopWindow()  
  16.       
  17.     Dim Re As RECT  
  18.     Dim Result As Long  
  19.     Result = GetWindowRect(hwnd, Re)  
  20.       
  21.     Dim WindowSize As WindowSize  
  22.     WindowSize.Width = Re.Right - Re.Left  
  23.     WindowSize.Height = Re.Bottom - Re.Top  
  24.     GetWindowSize = WindowSize  
  25. End Function  
3行目:デスクトップウィンドウのハンドルを取得するためにWindowsAPIの関数を宣言
4行目:ウィンドウ全体の座標を取得する

6~11行目:WindowsAPIのGetWindowRect関数の第二引数で使う変数を定義

13行目:デスクトップの幅と高さを返す関数。返り値に幅と高さを保持したユーザー定義型を返します。
14行目:hwnd変数、デスクトップのウィンドウハンドルを取得する変数
15行目:WindowsAPIのGetDesktopWindow関数でデスクトップのウィンドウハンドルを取得

17行目:変数REをユーザー定義型RECTとして宣言。WindowsAPIのGetWindowRectの第二引数に渡す
18行目:変数Result、GetWindowRect関数の戻り値を格納。0の時関数は、取得失敗を意味する。
19行目:WindowsAPIのGetWindowRect関数で第一引数に渡したウィンドウハンドルの座標を取得

21行目:変数WindowSizeをユーザー定義型WIndowSizeとして宣言
22行目:ユーザー定義型WindowSizeを宣言した変数のWidthにデスクトップの幅を格納
23行目:ユーザー定義型WindowSizeを宣言した変数のHeightにデスクトップの高さを格納
24行目:戻り値としてユーザー定義型WindowSizeを返す


SampleCode1関数を実行することで
・ウィンドウサイズ400x300
・画面中央表示
・タイトルをこんにちは、GUI!
で表示します。

モジュール名:MainModule内
  1. Option Explicit  
  2.   
  3. Type WindowSize  
  4.     Height As Long  
  5.     Width As Long  
  6. End Type  
  7.   
  8. Sub SampleCode1() '問題を含んだコード  
  9.     Application.Height = 400  
  10.     Application.Width = 300  
  11.   
  12.     Dim DeskTopSize As WindowSize  
  13.     DeskTopSize = GetWindowSize()  
  14.   
  15.     Application.Top = DeskTopSize.Height / 2 - Application.Height / 2  
  16.     Application.Left = DeskTopSize.Width / 2 - Application.Width / 2  
  17.   
  18.     ActiveWindow.Caption = "こんにちは、GUI!"  
  19. End Sub  
3~6行目:ユーザー定義型を定義します。
デスクトップの幅と高さを受け取る為に使います。

ウィンドウのサイズを変更します。
タイトルが見づらいので400x300で表示します。
9行目 :ウィンドウの高さを300pxにする
10行目:ウィンドウの幅を400pxにする

12行目:変数DeskTopSizeは、デスクトップの幅と高さを受け取る為の変数
13行目:GetWindowSize関数で、幅と高さを戻り値として受け取る。

ウィンドウの位置をデスクトップの中央に移動します。
(15、16行目に問題あり。)
15行目:デスクトップの高さ÷2 - ウィンドウの高さ÷2
16行目:デスクトップの幅 ÷2 - ウィンドウの幅 ÷2

18行目:タイトルを変更する。

あってそうな気がしますが、残念ながらこれじゃ画面中央に表示されません。
GetWindowRect関数の返す値の単位とApplication.Topや.Leftに指定する値の単位が違います。
・GetWindowRect関数 →px(ピクセル)
・Application.Top/.Left→pt(ポイント)

つまり、単位変換しないと画面中央に表示されません。
pxとptの変換は、
[pt] = [px] ÷ [解像度] × 72
[px] = [pt] × [解像度] ÷ 72

なお、解像度は、
Application.DefaultWebOptions.PixelsPerInchで調べられます。

そこらへんを考慮してSampleCode1関数を書き直すと
モジュール名:MainModule内
  1. Option Explicit  
  2.   
  3. Sub SampleCode2() '完成版  
  4.     Dim WindowSize As WindowSize  
  5.     Call GetWindowSize(WindowSize)  
  6.     Dim dpi As Double  
  7.     dpi = Application.DefaultWebOptions.PixelsPerInch '解像度求める  
  8.   
  9.     Application.Width = 100  
  10.     Application.Height = 75  
  11.     Application.Top = (WindowSize.Height / dpi) * 72 / 2 - Application.Height / 2 'WindowsAPIから得た値はptへ変換する  
  12.     Application.Left = (WindowSize.Width / dpi) * 72 / 2 - Application.Width / 2 'WindowsAPIから得た値はptへ変換する  
  13. End Sub  





よって、最終的なコードは下記になります。
MainModule内のSampleCode2関数を実行すると題意のとおりになります。

モジュール名:MainModule内
  1. Option Explicit  
  2.   
  3. Type WindowSize  
  4.     Height As Long  
  5.     Width As Long  
  6. End Type  
  7.   
  8. Sub SampleCode2() '完成版  
  9.     Dim WindowSize As WindowSize  
  10.     Call GetWindowSize(WindowSize)  
  11.     Dim dpi As Double  
  12.     dpi = Application.DefaultWebOptions.PixelsPerInch '解像度求める  
  13.   
  14.     Application.Width = 100  
  15.     Application.Height = 75  
  16.     Application.Top = (WindowSize.Height / dpi) * 72 / 2 - Application.Height / 2 'WindowsAPIから得た値はptへ変換する  
  17.     Application.Left = (WindowSize.Width / dpi) * 72 / 2 - Application.Width / 2 'WindowsAPIから得た値はptへ変換する  
  18. End Sub  


モジュール名:SubModule内
  1. Option Explicit  
  2.   
  3. Declare Function GetDesktopWindow Lib "user32" () As Long  
  4. Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long  
  5.   
  6. Type RECT  
  7.     Left As Long  
  8.     Top As Long  
  9.     Right As Long  
  10.     Bottom As Long  
  11. End Type  
  12.   
  13. Function GetWindowSize() As WindowSize  
  14.     Dim hwnd As Long  
  15.     hwnd = GetDesktopWindow()  
  16.       
  17.     Dim Re As RECT  
  18.     Dim Result As Long  
  19.     Result = GetWindowRect(hwnd, Re)  
  20.       
  21.     Dim WindowSize As WindowSize  
  22.     WindowSize.Width = Re.Right - Re.Left  
  23.     WindowSize.Height = Re.Bottom - Re.Top  
  24.     GetWindowSize = WindowSize  
  25. End Function  

0 件のコメント: