まえおき
最近、自分の仕事の効率が低いことを鑑みて、何が原因かを考えてみると、ウィンドウのリサイズに時間をかけすぎていることに気が付いた。
まるで、両手で土をかき分けてタイムカプセルを掘り起こすがごとく、ウィンドウA ~ Dをあっちにこっちに移動させ、埋もれたウィンドウEを発掘する。
次にまたウィンドウAが必要になるが、特に考えもなしに移動させているので、これまた発掘に時間がかかる。
ちなみに、Win + Tabについても普段から利用はしているが、どうにも
- 時間に追われるとWin + Tabを使うのを忘れてしまう
- Win + Tabで表示されるウィンドウの一覧から目的のウィンドウを探すのが苦手。例えば、ブラウザは大体1つしかないのでアイコンで見つけられるのだが、エクセルは基本4 ~ 5程度開いており、ファイル名から判別するのに時間を要してしまう。
- Win + Tabでウィンドウを開いても、そのウィンドウが画面上のどこに表示されているかについても数秒見つけるのに時間がかかる場合がある。
というような傾向にあるようだ(他人事)
ゆえに、常に指定のウィンドウがどこに配置されているかを気にすることなく、欲しいタイミングで表示させる必要があるわけだ。
ちなみに、悲しいかなこの”迷子になる”のはブラウザのタブにおいても同様に発生する。
つくりたいもの
・ウィンドウをリサイズする機能の実装。
・ただし、シーンごとに画面に配置しておきたいウィンドウは違ってくるし、その組み合わせも異なる。ゆえに複数パターンに対応できる必要がある。
動作
マクロに任意のショートカットキー(ex. Ctrl + Shift + Qなど)を割り当てて、WindowMultiArrange.arrange()
を呼び出す。
数秒間は数字キーを受け付けるため、その間に1 ~ 9の数字キーを押下する。
数字キーを押したら、それに対応するコンフィグを呼び出す。
例えば、数字の1であれば、「テンプレートメモ帳」「人口変動*.pdf」「イントラネットリンク集.xlsm」の3つのファイルをリサイズする。x, y, width, heightは画面全体を(0.0 ~ 1.0)とみなしての座標指定である。
例えば「テンプレートメモ帳」は画面の左上に、「人口変動*.pdf」は左下に配置される。
細かいが数字が0.0から始まってないのは単なる設定ミスである。
数字の2を押下すれば、レイアウト2に定義したファイル群がリサイズされるという具合である。
レイアウトは1 ~ 9まで定義可能。
名前の*はワイルドカードなので、ファイル末尾に日付が入るような場合でも、日付手前までを指定しておけば、定義を修正する必要はない。
画面イメージ
ソースコード
' Class : Keyboard
Option Explicit
' 指定した全てのキーが入力されているか
' keys : キーID(vbKeyXX)のコレクション
Public Function IsKeysPushed(keys As Collection) As Boolean
' キーが押下されたを表すステータス
Const KEY_STATE_PRESSED As Integer = &H8000
Dim pushed_flag As Boolean
pushed_flag = True
Dim key As Variant
For Each key In keys
' キーが押下されていない場合、フラグを折る
If ((GetAsyncKeyState(key) And KEY_STATE_PRESSED) = 0) Then
pushed_flag = False
Exit For
End If
Next
If (pushed_flag) Then
IsKeysPushed = True
Exit Function
End If
IsKeysPushed = False
End Function
' 指定した全てのキー + 任意の数字キーが入力されているか
' keys : キーID(vbKeyXX)のコレクション
' return: -1: 押されていない
' 1 ~ 9: 指定の数字が押された
'
' ex keys = [ctrl + shift] ⇒ ctrl + shift + (1 ~ 9)の3キーが押されているか
'
Public Function IsKeysWithNumberPushed(keys As Collection) As Long
' 引数のキーコレクションに数字キーを加える
Dim no As Long
For no = 1 To 9
Dim keysWithNo As Collection
Set keysWithNo = New Collection
Dim key As Variant
For Each key In keys
Call keysWithNo.Add(key)
Next
keysWithNo.Add (vbKey1 + no - 1)
If (IsKeysPushed(keysWithNo)) Then
IsKeysWithNumberPushed = no
Exit Function
End If
Next
IsKeysWithNumberPushed = -1
End Function
' Class : WindowArranger
Option Explicit
' ウィンドウをリサイズする
' ディスプレイの左上を0.0, 右下を1.0とし、サイズを指定する
Public Sub Resize(hWnd As Long, xRate As Double, yRate As Double, widthRate As Double, heightRate As Double)
' ディスプレイサイズを取得する
Dim displayWidth As Long
Dim displayHeight As Long
displayWidth = GetSystemMetrics(0)
displayHeight = GetSystemMetrics(1)
' リサイズする
Dim x As Long
Dim y As Long
Dim width As Long
Dim height As Long
x = displayWidth * xRate
y = displayHeight * yRate
width = displayWidth * widthRate
height = displayHeight * heightRate
Call MoveWindow(hWnd, x, y, width, height, 0)
End Sub
' Class : WindowHandlerSelector
Option Explicit
' 指定したキャプションのウィンドウハンドルを取得する
Public Function GetWindowHandleByCaption(regexpCaption As String) As Long
' ウィンドウの一覧を取得する
Dim hWnds As Collection
Dim captions As Collection
Dim classNames As Collection
Call GetWindowList(hWnds, captions, classNames)
' 合致するウィンドウがあれば、そのウィンドウハンドルを返却する
Dim i As Long
For i = 1 To captions.count
If (captions.Item(i) Like regexpCaption) Then
'And classNames.Item(i) = className) Then
GetWindowHandleByCaption = hWnds.Item(i)
Exit Function
End If
Next
GetWindowHandleByCaption = -1
End Function
' ウィンドウの一覧を取得する
Private Sub GetWindowList(hWnds As Collection, captions As Collection, classNames As Collection)
' 取得ウィンドウの相対位置
Const GW_HWNDLAST As Long = 1
Const GW_HWNDNEXT As Long = 2
Set hWnds = New Collection
Set captions = New Collection
Set classNames = New Collection
Dim hWnd As Long
hWnd = FindWindow(vbNullString, vbNullString)
Do
' ウィンドウが表示されているか
If (IsWindowVisible(hWnd)) Then
Call hWnds.Add(hWnd)
' ウィンドウのキャプションを取得する
Dim fixedCaption As String * 500
Dim caption As String
Call GetWindowText(hWnd, fixedCaption, Len(fixedCaption))
caption = Left(fixedCaption, InStr(fixedCaption, vbNullChar) - 1)
Call captions.Add(caption)
Debug.Print caption
' ウィンドウのクラスを取得する
Dim fixedClassName As String * 500
Dim className As String
Call GetClassName(hWnd, fixedClassName, Len(fixedClassName))
className = Left(fixedClassName, InStr(fixedClassName, vbNullChar) - 1)
Call classNames.Add(className)
Debug.Print className
End If
' 次のウィンドウハンドルを取得する
hWnd = GetNextWindow(hWnd, GW_HWNDNEXT)
' 終端のウィンドウに到達するまで
Loop Until hWnd = GetNextWindow(hWnd, GW_HWNDLAST)
End Sub
' WindowMultiArrange
Option Explicit
Public Sub Arrange()
On Error Resume Next
Application.StatusBar = "数字キーの入力を受け付けます"
On Error GoTo 0
' 数字キー:1 ~ 9の入力待ち
Dim inputNo As Long
inputNo = ObserveInputKey()
If (inputNo = -1) Then
On Error Resume Next
Application.StatusBar = "リサイズを受け付けましたが、タイムアウトしました"
On Error GoTo 0
Exit Sub
End If
Dim hWndSelector As WindowHandleSelector
Set hWndSelector = New WindowHandleSelector
Dim windowArranger As windowArranger
Set windowArranger = New windowArranger
' 数字に対応する定義の座標を取得
Dim sh As Worksheet
Set sh = ThisWorkbook.Worksheets("Config")
Dim r As Range
Set r = sh.Range("B:B").Find(What:="■レイアウト" & inputNo, LookAt:=xlWhole)
Set r = r.Offset(2, 0)
' リストに定義されたアプリケーションの数だけループ
Do While r.Value <> ""
' リストから情報を取得
Dim captionName As String
Dim posX As Double
Dim posY As Double
Dim width As Double
Dim height As Double
captionName = r.Offset(0, 0).Value
posX = CDbl(r.Offset(0, 1).Value)
posY = CDbl(r.Offset(0, 2).Value)
width = CDbl(r.Offset(0, 3).Value)
height = CDbl(r.Offset(0, 4).Value)
Set r = r.Offset(1, 0)
' ウィンドウハンドルを取得する
Dim hWnd As Long
hWnd = hWndSelector.GetWindowHandleByCaption(captionName)
' ウィンドウをリサイズする
If (hWnd <> -1) Then
Call windowArranger.Resize(hWnd, posX, posY, width, height)
Else
Debug.Print (captionName & "のウィンドウは見つかりませんでした。")
End If
Loop
On Error Resume Next
Application.StatusBar = "リサイズが完了しました"
On Error GoTo 0
End Sub
' キー入力状況を監視する
Private Function ObserveInputKey() As Long
' タイムアウト秒(ms)
Const TIMEOUT_MS As Long = 5000
Dim keys As Collection
Set keys = New Collection
Dim keyboard As keyboard
Set keyboard = New keyboard
' 指定時間、キー押下状態を監視する
Dim begin_time As Long
begin_time = Timer()
Do While (Timer() - begin_time) * 1000 < TIMEOUT_MS
DoEvents
Call Sleep(10)
Dim result As Long
result = keyboard.IsKeysWithNumberPushed(keys)
If (result <> -1) Then
ObserveInputKey = result
Exit Function
End If
Loop
ObserveInputKey = -1
End Function
' 標準モジュール WInAPI64Define
Option Explicit
#If Win64 Then
'------------------------------------------------------------------------
' ウィンドウ
'------------------------------------------------------------------------
' ウィンドウハンドルを取得する
Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
' 検索時、次のウィンドウハンドルを取得する
Public Declare PtrSafe Function GetNextWindow Lib "user32" Alias "GetWindow" _
(ByVal hWnd As Long, ByVal wFlag As Long) As Long
' ウィンドウの可視状態を取得する
Public Declare PtrSafe Function IsWindowVisible Lib "user32" _
(ByVal hWnd As Long) As Long
' ウィンドウのキャプションを取得する
Public Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
' ウィンドウのクラス名を取得する
Public Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" _
(ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
' 最前面のウィンドウを取得する
Public Declare PtrSafe Function GetForegroundWindow Lib "user32" () As Long
' ウィンドウの位置を移動する
Public Declare PtrSafe Function MoveWindow Lib "user32" _
(ByVal hWnd As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As Long) As Long
' ディスプレイサイズ(解像度)を取得する
Public Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
'------------------------------------------------------------------------
' キーボード
'------------------------------------------------------------------------
' キーボードの入力状況を取得する
Public Declare PtrSafe Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Integer
'------------------------------------------------------------------------
' Sleep
'------------------------------------------------------------------------
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If
さいごに
VBAの採用について
VBAの場合、エクセルが活性状態でないとキーを受け付けないため、エクセルをクリックしてからショートカットキーを押すという若干の手間がある。
キーを監視してウィンドウのリサイズをするのであれば、C#でもできる。
実際C#出の実装も検討したし、ネット上にサンプルコードも公開されているが、C#自体経験がほぼ皆無であることもあり、実装に使われている技術が高いと感じたため、妥協案としてVBAを採用した。
果たして効率化されるだろうか
正味、慣れるまでにはかなりの時間を要すると思う。
おそらく、「どのウィンドウがどのキーに対応していただろうか?」という具合に。
また、いったんはエクセルをクリックして、という工程がどの程度手間に感じられるか。
Win + 数字キーでタスクバーに配置されたエクセルファイルを開くこともできるが、それすらもWin+Tabの下位互換と感じる可能性がある。
もしかしたら、このマクロはそのままお蔵入りになる可能性もある。