ExcelのVBAで設定シート
背景
シートに書いてある設定値を読み込んでVBAで使用したい......そんな状況が頻繁に起こるため、テンプレート化する。
Sheet Objectにコードを書くタイプとClassにするタイプの両方を記す。
準備
シートを作成して以下のようなテーブルを作成する。テーブルの名前はConfig
とする。
Config
以外にしたら、後のコード内のCONFIG_TABLE_NAME
を変更する。
シート名とシートオブジェクト名は何でも良い。
今回はシート名をConfig
, オブジェクト名をConfigSheet
とした。
Sheet Objectにコードを書くタイプ
コード
ConfigSheet
に以下コードを張り付ける。
' ConfigSheet Option Explicit Private Config As Collection ' 参照設定をしなくて済むようにDictionaryの代わりにCollectionを使用 Private Const CONFIG_TABLE_NAME As String = "Config" Private Const KEY_COLUMN_NUM As Long = 1 Private Const VALUE_COLUMN_NUM As Long = 2 ' Config tableの内容をDictionaryに読み込む関数 Public Sub LoadConfig() Set Config = New Collection Dim ListRow As ListRow For Each ListRow In Me.ListObjects(CONFIG_TABLE_NAME).ListRows Config.Add ListRow.Range(VALUE_COLUMN_NUM).Value, ListRow.Range(KEY_COLUMN_NUM).Value Next End Sub ' Config tableのKeyに応じたValueを返す関数 Public Function GetConfig(Key As String) As Variant ' GetConfig初回呼び出しの場合、LoadConfigしてからGetConfigするための処理 If Config Is Nothing Then Call LoadConfig ' Keyが見つからなかったらエラー GetConfig = Config.Item(Key) End Function
呼び出し側コードの例
' Module1 Option Explicit Sub Test() Debug.Print ConfigSheet.GetConfig("Key1") End Sub
Classにするタイプ
コード
' ConfigTableReader Option Explicit Private Config As Collection Private Const KEY_COLUMN_NUM As Long = 1 Private Const VALUE_COLUMN_NUM As Long = 2 Public Function ReadConfig(Table As ListObject) As ConfigTableReader Set Config = New Collection Dim ListRow As ListRow For Each ListRow In Table.ListRows Config.Add ListRow.Range(VALUE_COLUMN_NUM).Value, ListRow.Range(KEY_COLUMN_NUM).Value Next Set ReadConfig = Me End Function Public Function GetConfig(Key As String) As Variant GetConfig = Config.Item(Key) End Function
呼び出し側コードの例
Config
だらけで気持ち悪い......
' Module1 Sub Test() Dim CTR As ConfigTableReader: Set CTR = New ConfigTableReader Debug.Print CTR.ReadConfig(ConfigSheet.ListObjects("Config")).GetConfig("Key6") End Sub
使い分け
シートに直接書く
- メリット
- クラスモジュールが増えない
Dim
,New
が要らない
- デメリット
- シートのメンバーが増えてしまう(なんとなく気持ち悪い)
Classにするタイプ
- メリット
- オーソドックスなやり方
- (やるかは別として)同一シートに複数の設定テーブルを作成してもOK
- デメリット
- 設定を読み込むだけのクラスができてしまう
Dim
,New
めんどくさい
PowerShellでファイルパスをルートに向かって存在確認する
背景
Outlookとかにファイルパスがリンクとして張り付けられていることがある。 送信後にフォルダ構成を変更すると、エクスプローラーでアクセスしたときにエラーが出る(当たり前)。
その場合、生きているパスの中で最も深いパスが知りたくなる。 そういう場合に使えそうなPowerShellのコードを乗せとく。 自分が使いやすいように、クリップボードから読み取り既定のプログラム(エクスプローラー)で開くようにする。
注意点
コード
もっといい書き方ないのかな?
function Open-LivingFilePath { # クリップボードからファイルパスを読み込む。 # たまにダブルクォーテーションが付いていることがあるから消す。 $P = (Get-Clipboard) -replace '"', '' # ファイルパスに含まれる`\`の数だけループする。 # 無限ループだとちょっと怖いから。 1..$P.Split("\").Length | % { # パスが存在したらそのパスを返してループを抜ける if (Test-Path $P) { $P break } # パスが存在しなかったら親の存在確認する else { $P = Split-Path $P } # エクスプローラーで開く } | Invoke-Item }
使い方
"C:\Users\XXXXX\aaa\bbb\ccc.ddd" | scb Open-LivingFilePath # エクスプローラーで`C:\Users`が開かれる
この関数を$PROFILE
でSet-Alias
しとけば楽ちん♪
VBAで全て検索と同等の関数作った
作るきっかけ
- FindからのFindNextの処理を毎回検索して書き直すのがめんどくさい
- メインの処理にDo Loopが入って見にくい
使用上の注意点
- Raiseするエラー番号がテキトー
- RangeのCollectionだから検索対象のブックを閉じると参照できなくなる
全て検索する関数
とりあえず書いたけど、もっとスマートにできそう。
Public Function FindAll(ByRef What As String, ByRef Scope As Variant) As Collection ' 検索と置換の[全て検索(I)]に近い関数 ' ' Args: ' What (String): 検索対象文字列。 Cells.[Find|FindNext]に渡すWhat。 ' Scope (Workbook|Worksheet): 検索範囲を示す。渡されたオブジェクトの全体を検索 ' ' Returns: ' Result: (Collection): 検索にヒットしたRangeのCollection ' ' Scopeの型判定 Dim TargetSheets As Object Select Case TypeName(Scope) Case "Workbook" Set TargetSheets = Scope.Worksheets Case "Worksheet" Set TargetSheets = New Collection TargetSheets.Add Scope Case Else Err.Raise 999, Scope, "Scopeの型はWorkbookかWorksheetにして下さい。" End Select ' Return用Collection Dim Result As Collection: Set Result = New Collection ' 検索結果の一時格納変数 Dim FoundCell1 As Range, FoundCell2 As Range ' ループ用Worksheet Dim Ws As Worksheet For Each Ws In TargetSheets ' 最初の検索 Set FoundCell1 = Ws.Cells.Find(What:=What, LookIn:=xlValues, LookAt:=xlPart) ' 検索結果が無ければ次のシートに If FoundCell1 Is Nothing Then GoTo NextWs ' 検索結果をCollectionに格納 Result.Add FoundCell1 ' シート内を複数検索するためのループ Do ' 次のセルを検索 Set FoundCell2 = Ws.Cells.FindNext(After:=Result(Result.Count)) ' ヒットしたセル番地と最初に検索したセルの番地が同じだったら次のシートに If FoundCell1.Address(external:=True) = FoundCell2.Address(external:=True) Then Exit Do ' 検索結果をCollectionに格納 Result.Add FoundCell2 Loop NextWs: ' 他言語のContinue代わり Next Set FindAll = Result End Function
使い方
Scopeに検索対象のオブジェクトを渡す。
Sub Test() Dim FoundCells As Collection, FoundCell As Range ' シート内全検索 Set FoundCells = FindAll("A", Sheet1) For Each FoundCell In FoundCells Debug.Print FoundCell.Address(external:=True); vbTab; FoundCell.Value Next ' ブック内全検索 Set FoundCells = FindAll("A", ThisWorkbook) For Each FoundCell In FoundCells Debug.Print FoundCell.Address(external:=True); vbTab; FoundCell.Value Next End Sub
イミディエイトウィンドウの出力
[FindAll.xlsm]Sheet1!$B$2 A [FindAll.xlsm]Sheet1!$C$5 ASC [FindAll.xlsm]Sheet1!$B$6 AB [FindAll.xlsm]Sheet1!$B$7 aaA [FindAll.xlsm]Sheet1!$B$2 A [FindAll.xlsm]Sheet1!$C$5 ASC [FindAll.xlsm]Sheet1!$B$6 AB [FindAll.xlsm]Sheet1!$B$7 aaA [FindAll.xlsm]Sheet2!$B$2 AC [FindAll.xlsm]Sheet2!$C$4 AD [FindAll.xlsm]Sheet2!$B$6 AV