文字列型の列挙体?

文字列型の列挙体?

かつて、

akashi-keirin.hatenablog.com

こんなことを試みたことがあった。

しかし、PredeclaredIdTrueにしたクラスモジュールを用いていたために、必ずクラス名を記述しなくてはいけなかった。

これはイマイチである。

XlLineStyle列挙体を用いるときに、必ずXlLineStyle.xlContinuousと書かねばならんようなものだからである。

チャクラ開く

ちょっと待てよ……。

クラスモジュールを使うから、クラス名記述の強制という問題が起こるのだ。……ということは、

標準モジュール使えばよくね?

ええ、おチャクラ開きましたとも。

標準モジュールのPropertyを使う

標準モジュールを挿入して、オブジェクト名をWaruguchiにする。

で、次のコードを記述。

標準モジュール Waruguchi
Option Explicit

Public Property Get wgAho() As String
  wgAho = "アホ"
End Property

Public Property Get wgBoke() As String
  wgBoke = "ボケ"
End Property

Public Property Get wgKasu() As String
  wgKasu = "カス"
End Property

Public Property Get wgSuttokoDokkoi() As String
  wgSuttokoDokkoi = "スットコドッコイ"
End Property

列挙体の要素っぽい名前にして、全部Property Getにしたったw

使ってみる

別の標準モジュールで利用しようとしてみる。

f:id:akashi_keirin:20191109083832g:plain

おお! いい感じではないか!

出来上がったのが次のコード。

リスト1 標準モジュール
Private Sub test()
  Call MsgBox(wgAho)
End Sub

これだけ。

こいつを実行すると、

f:id:akashi_keirin:20191109083830j:plain

うむ。すばらしい。

ちょっと待て!

なるほど、標準モジュールの場合、名前かぶりさえなければ、親モジュールの記載がなくてもメソッドやプロパティを呼ぶことができる。だから、このようなことができた。

しかし!

列挙体のメリットは、一族をまとめるだけに非ず。引数や返り値の型に指定できるところにあるのだ。

f:id:akashi_keirin:20191109083843g:plain

標準モジュールは「型」ではなかった……orz

おわりに

……というわけで、派手に頓挫。

まあ、たとえば、Waruguchiモジュールのコードを

Option Explicit

Public Enum WgWaruguchi
  wgAho = 1
  wgBoke
  wgKasu
  wgSuttokoDokkoi
End Enum

Public Property Get Waruguchies( _
                ByVal kindOfWaruguchi As WgWaruguchi) As String
  Dim ret As String
  ret = "ち~んw"
  Select Case kindOfWaruguchi
    Case wgAho: ret = "アホ"
    Case wgBoke: ret = "ボケ"
    Case wgKasu: ret = "カス"
    Case wgSuttokoDokkoi: ret = "スットコドッコイ"
  End Select
  Waruguchies = ret
End Property

にしておいたら、

Call MsgBox(Waruguchies(wgSuttokoDokkoi))

みたいにして呼べるけど、これもあんまりキレイじゃないしね。

……というわけで、派手に頓挫したのでした。ニンニン……。

UTF-8のテキストファイルを操作するクラス

UTF-8のテキストファイルを操作するクラス

作った。

何のために?

foobar2000のプレイリストファイルのドライブレター部分を一括して書き換えるために。

決して仕事のためではない。

だから、全然「働き方改革」とは関係ない。許せ。

UTF8TextFileクラスのコード

めんどくさいので、コードを一気に公開。

使ってみたい、という酔狂な方がいらっしゃいましたら、クラスモジュールを挿入して、オブジェクト名をUTF8TextFileにした上で、次のコードをコピペしてくださいまし。長いよ。

クラスモジュール UTF8TextFile
Option Explicit

'### Microsoft Active X Data Objects Library X.Xを参照設定 ###'
'### Microsoft Scripting Runtimeを参照設定                 ###'
'Module Level Variables'
Private adoObj As New ADODB.Stream
Private fsObj As New FileSystemObject

'Constants'
Private Const ERR_NUMBER_BASE As Long = 45000
Private Enum ErrorCode
  ecIndexOutOfBound = 1
  ecFileNotExists
  ecUnknown = 10
End Enum

'Field Variables'
Private lines_() As String
Private fullName_ As String

'Constructor'
Private Sub Class_Initialize()
  '行番号に合わせるため、配列は1始まり'
  ReDim lines_(1 To 1)
  lines_(1) = ""
End Sub

'Properties'
Public Property Let FullName(ByVal argValue As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, Property Let FullName"
  If Not fsObj.FileExists(argValue) Then _
    Call raiseError(ecFileNotExists, ERR_SRC)
  fullName_ = argValue
End Property
Public Property Get FullName() As String
  FullName = fullName_
End Property

Public Property Get Path() As String
  Const ERR_SRC As String = _
        "UTF8TextFile Class, Property Get Path"
  Dim ret As String
  ret = ""
  If Not fsObj.FileExists(fullName_) Then GoTo Finalizer
  Dim arr() As String
  arr = Split(fullName_, "\")         '"
  Dim i As Long
  For i = LBound(arr) To UBound(arr) - 1
    ret = ret & arr(i) & "\"         '"
  Next
  ret = Left(ret, Len(ret) - 1)
Finalizer:
  Path = ret
End Property

Public Property Let Lines(ByVal lineNum As Long, _
                          ByVal lineText As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, Property Get Lines"
  '引数チェック'
  If lineNum < LBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  If lineNum > UBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  'ファイル存否確認'
  If Not fsObj.FileExists(fullName_) Then _
    Call raiseError(ecFileNotExists, ERR_SRC)
  '行の書き換え'
  lines_(lineNum) = lineText
  'ファイルに書き込む'
  Call setLines(fullName_)
End Property
Public Property Get Lines(ByVal lineNum As Long) As String
  Const ERR_SRC As String = _
        "UTF8TextFile Class, Property Get Lines"
  Dim ret As String
  ret = ""
  '引数チェック'
  If lineNum < LBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  If lineNum > UBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  ret = lines_(lineNum)
Finalizer:
  Lines = ret
  Exit Property
ErrorHandler:
  Call raiseError(ecUnknown, ERR_SRC)
End Property

Public Property Get LinesCount() As Long
  Const ERR_SRC As String = _
        "UTF8TextFile Class, Property Get LinesCount"
  On Error GoTo ErrorHandler
  LinesCount = UBound(lines_)
  Exit Property
ErrorHandler:
  Call raiseError(ecUnknown, ERR_SRC)
End Property

'Methods'
'テキストファイルの各行のデータを配列にセット'
Public Sub getLines(Optional ByVal targetFilePath As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, getLines Method"
  If targetFilePath <> "" Then fullName_ = targetFilePath
  If Not fsObj.FileExists(fullName_) Then _
    Call raiseError(ecFileNotExists, ERR_SRC)
  On Error GoTo ErrorHandler
  With adoObj
    .Charset = "UTF-8"
    Call .Open
    Call .LoadFromFile(fullName_)
    Dim n As Long
    n = 1
    Do Until .EOS
      ReDim Preserve lines_(1 To n)
      lines_(n) = .ReadText(adReadLine)
      n = n + 1
    Loop
    Call .Close
  End With
  Exit Sub
ErrorHandler:
  Call adoObj.Close
  Call raiseError(ecUnknown, ERR_SRC)
End Sub

'配列に格納したデータをUTF-8テキストファイルに書き込んで保存'
Private Sub setLines(Optional ByVal targetFilePath As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, setLines Method"
  On Error GoTo ErrorHandler
  If targetFilePath <> "" Then fullName_ = targetFilePath
  With adoObj
    .Charset = "UTF-8"
    Call .Open
    Dim i As Long
    For i = LBound(lines_) To UBound(lines_)
      Call .WriteText(lines_(i), adWriteLine)
    Next
    Call .SaveToFile(fullName_, adSaveCreateOverWrite)
    Call .Close
  End With
  Exit Sub
ErrorHandler:
  Call adoObj.Close
  Call raiseError(ecUnknown, ERR_SRC)
End Sub

'行を追加'
Public Sub appendLine(ByVal targetText As String, _
             Optional ByVal targetFilePath As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, appendLine Method"
  If targetFilePath <> "" Then fullName_ = targetFilePath
  Dim n As Long
  n = UBound(lines_) + 1
  ReDim Preserve lines_(1 To n)
  lines_(n) = targetText
  'ファイルに書き込む'
  Call setLines(fullName_)
End Sub

'テキストを追加'
Public Sub appendText(ByVal targetText As String, _
             Optional ByVal targetFilePath As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, appendText Method"
  If targetFilePath <> "" Then fullName_ = targetFilePath
  Dim n As Long
  n = UBound(lines_)
  lines_(n) = lines_(n) & targetText
  'ファイルに書き込む'
  Call setLines(fullName_)
End Sub

'行を書き換え'
Public Sub replaceLine(ByVal targetLine As Long, _
                       ByVal replaceText As String, _
              Optional ByVal targetFilePath As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, replaceLine Method"
  '存在しない行ならエラー'
  If targetLine < LBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  If targetLine > UBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  'ファイル名が指定されていたらセット'
  If targetFilePath <> "" Then fullName_ = targetFilePath
  '行の書き換え'
  lines_(targetLine) = replaceText
  'ファイルに書き込む'
  Call setLines(fullName_)
End Sub

'行の挿入'
Public Sub insertLine(ByVal targetLine As Long, _
                      ByVal targetText As String, _
             Optional ByVal targetFilePath As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, insertLine Method"
  '存在しない行ならエラー'
  If targetLine < LBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  If targetLine > UBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  'ファイル名が指定されていたらセット'
  If targetFilePath <> "" Then fullName_ = targetFilePath
  '行の挿入'
  '配列の拡張'
  Dim n As Long
  n = UBound(lines_) + 1
  ReDim Preserve lines_(1 To n)
  '配列の要素を挿入地点まで一つづつ後ろへずらす'
  Dim i As Long
  For i = UBound(lines_) To targetLine + 1 Step -1
    lines_(i) = lines_(i - 1)
  Next
  '挿入する地点に新しいテキストを追加'
  lines_(targetLine) = targetText
  'ファイルに書き込む'
  Call setLines(fullName_)
End Sub

'行の削除'
Public Sub deleteLine(ByVal targetLine As Long, _
             Optional ByVal targetFilePath As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, deleteLine Method"
  '存在しない行ならエラー'
  If targetLine < LBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  If targetLine > UBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  'ファイル名が指定されていたらセット'
  If targetFilePath <> "" Then fullName_ = targetFilePath
  '行の削除'
  '配列の要素を一つづつ詰めていく'
  Dim i As Long
  Dim n As Long
  n = UBound(lines_) - 1
  For i = targetLine To n
    lines_(i) = lines_(i + 1)
  Next
  '配列の縮小'
  ReDim Preserve lines_(1 To n)
  'ファイルに書き込む'
  Call setLines(fullName_)
End Sub

'行のテキストの置換'
Public Sub replaceText(ByVal targetLine As Long, _
                       ByVal FindString As String, _
                       ByVal ReplaceString As String, _
              Optional ByVal targetFilePath As String)
  Const ERR_SRC As String = _
        "UTF8TextFile Class, replaceText Method"
  '存在しない行ならエラー'
  If targetLine < LBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  If targetLine > UBound(lines_) Then _
    Call raiseError(ecIndexOutOfBound, ERR_SRC)
  'ファイル名が指定されていたらセット'
  If targetFilePath <> "" Then fullName_ = targetFilePath
  '行のテキストの置換'
  lines_(targetLine) = Replace(lines_(targetLine), _
                               FindString, _
                               ReplaceString)
  'ファイルに書き込む'
  Call setLines(fullName_)
End Sub

Public Sub replaceLines(ByVal findStr As String, _
                       ByVal replaceStr As String, _
              Optional ByVal targetFilePath As String)
  'ファイル名が指定されていたらセット'
  If targetFilePath <> "" Then fullName_ = targetFilePath
  '全ての行について置換を実行'
  Dim i As Long
  For i = LBound(lines_) To UBound(lines_)
    lines_(i) = Replace(lines_(i), findStr, replaceStr)
  Next
  'ファイルに書き込む'
  Call setLines(fullName_)
End Sub

'エラー発生用'
Private Sub raiseError(ByVal errCode As ErrorCode, _
                       ByVal errSource As String)
  Dim msg As String
  Select Case errCode
    Case ecIndexOutOfBound
      msg = "Index is out of bound..."
    Case ecFileNotExists
      msg = "File is not found..."
    Case ecUnknown
      msg = "Some error has occurred..."
  End Select
  msg = msg & vbCrLf & errSource
  Call Err.Raise(Number:=ERR_NUMBER_BASE + errCode, _
                 Source:=errSource, _
                 Description:=msg)
End Sub

長いなーーー。

まあ、エラー対応とかも作り込んでしまったから、しょうがない。許してちょんまげ。

メソッドとかプロパティを列挙するのはめんどくさいので、

f:id:akashi_keirin:20191104195325j:plain

オブジェクト ブラウザーの画像でどうぞ。

何か、プロが作ったクラスっぽくないっすか?

使ってみる

さて、本来の目的は、そう、

Foobar2000のプレイリストファイルのドライブレター書き換え

であった。

初期状態が

f:id:akashi_keirin:20191104195328j:plain

コチラ。楽曲ファイルのあるドライブがOドライブになっている。

これを、Fドライブに変えてみせようホトトギス

リスト1 標準モジュール
Private Sub testUTF8TextFileClass()
  Const SAMPLE_FILE As String = _
        "X:\個人用\はてなブログ用\PlayListEditor\NowPlaying"
  Dim txtFile As UTF8TextFile    '……(1)'
  Set txtFile = New UTF8TextFile
  Call txtFile.getLines(SAMPLE_FILE & ".m3u8")  '……(2)'
  Call txtFile.replaceLines("O:\Music", _
                            "F:\Music", _
                            SAMPLE_FILE & "_.m3u8")  '……(3)'
End Sub

(1)の

Dim txtFile As UTF8TextFile
Set txtFile = New UTF8TextFile

で、わがUTF8TextFileクラスのインスタンスを作る。

(2)の

Call txtFile.getLines(SAMPLE_FILE & ".m3u8")

で、getLinesメソッドを実行し、PlayListEditorフォルダにあるNowPlaying.m3u8というファイルを読み込む。

あとは、(3)の

Call txtFile.replaceLines("O:\Music", _
                          "F:\Music", _
                          SAMPLE_FILE & "_.m3u8")

replaceLinesメソッドを実行。第1引数に"O:\Music"、第2引数に"F:\Music"を渡しているので、ファイル内のOドライブの記述をFドライブに改めることになる。

また、第3引数で渡すファイルパスでは、拡張子の前に「_」(アンダースコア)を追加しているので、置換後、別のファイル名(すなわち、NowPlaying_.m3u8)で保存されることになる。

実行結果

リスト1を実行すると、

f:id:akashi_keirin:20191104195333j:plain

新しいファイルNowPlaying_.m3u8が出来ていて、中身は

f:id:akashi_keirin:20191104195336j:plain

このとおり。文字化けもなく、無事に置換できている。

おわりに

さて、このUTF8TextFileクラス、他に使いどころはあるかのう……?

Shift_JIS と UTF-8 に悩まされた話

文字コードェ……

Foobar2000のプレイリストファイルは、拡張子が「.m3u8」というけったいなもの。こいつをエディタで開くと、

f:id:akashi_keirin:20191104090654j:plain

こんなふうに、登録した楽曲データのフルパスを書き込んでいるだけだった。

ただ、うっとうしいのがドライブ名がジカ書きなところ。

これでは、ドライブレターが変更になったら読み込めなくなってしまう。

で、かつて作成したTextFileクラスを利用して編集してはどうか、と考えた。

ちなみに、TextFileクラスのコードはコチラ

akashi-keirin.hatenablog.com

TextFileクラスを用いてプレイリストファイルを編集する

TextFileクラスを用いて」と言っても、TextFileクラス自体はFileSystemObjectクラスをラップしただけのクラスなので、「FileSystemObjectを用いてプレイリストファイルを編集する」と言った方が正しいんですけどね。

リスト1 標準モジュール
Private Sub testTextFileClass()
  Const SAMPLE_FILE As String = _
        "X:\個人用\はてなブログ用\PlayListEditor\NowPlaying"
  Dim txtFile As TextFile
  Set txtFile = New TextFile
  Call txtFile.init(SAMPLE_FILE & ".m3u8")
  With txtFile
    Dim i As Long
    For i = 1 To .LineCount
      Call .replaceLine(i, Replace(.Line(i), "O:\Music", "F:\Music"))
    Next
  End With
End Sub

改めて見直すと、TextFileクラスのメソッド名、プロパティ名の付け方がイマイチやなあ……。

ま、それはさておき、自作TextFileクラスのreplaceLineメソッドに、各行のテキストを渡し、組み込みのReplace関数で「O:\Music」を「F:\Music」に置換してファイルに書き込み直している。

んで、こいつをNowPlaying.m3u8というプレイリストファイルに対して実行した。

実行後

f:id:akashi_keirin:20191104090658j:plain

ぐえっ!!!!!!!!

何と、.m3u8ファイルよ、キサーマはUTF-8だったのか……orz

おわりに

再生情報を表すXMLがフツーにFileSystemObjectで編集できたので完全に油断しておりました。

なお、現在は既にUTF-8のテキストファイルを編集するためのクラス(UTF8TextFileクラス)を作成済みなので、この問題は解決済みです。

UTF8TextFileクラスについては、またコチラでご紹介する機会もあろうかと……。

PC初心者なので、ここへ来て初めて文字コードに悩まされましたw

UTF8TextFileクラスについて

コチラをどうぞ。

akashi-keirin.hatenablog.com

あえて組み込みオブジェクトを低機能化させる

あえて組み込みオブジェクトを低機能化させる

f:id:akashi_keirin:20190813075140j:plain

組み込みオブジェクトは優秀である。

プロが作ったものなのだから、当り前である。

しかし、素人にとっては「優秀すぎて良さがわからない」こともしばしば。

そこで、初心者のうちは、

あえて低機能化させたクラス/メソッドを作る

ことをオススメする。

FileDialogオブジェクトの場合

たとえば、Application.FileDialogオブジェクトというものがある。

ファイルやフォルダを選択するとき、「名前を付けて保存」とかするときに出てくるあのウインドウだ。

今でこそApplication.FileDialogオブジェクトの使い方もそこそこわかるので、便利だなーと思うこともできるのだが、初心者の頃はとにかく

な、何てややこしいんだ……

などと思っていたものである。

詳しいことはコチラに譲るとして、とにかくやれメソッドだ、プロパティだ、と、初心者にはわけがわからないはずである。

使いたい機能を絞り込む

そこで、たとえば、

とにかく、ファイル選択ダイアログを表示して、ユーザーにファイルを選ばせる。んで、選んだファイルのフルパスが取得できたらいい。

と、目標を絞るのである。

ユーザーが選んだファイルのフルパスを取得するメソッド

使用者(コードを書く人。つまり自分)が設定するのは、せいぜい〈デフォルトのフォルダパス〉と〈ダイアログのタイトル〉ぐらいにしておくと楽だ。あとはメソッドの中に封印してしまえば良い。

そのような考え方で書いたメソッドが次のもの。

リスト1 標準モジュール
Option Explicit

Private fsObj As New FileSystemObject

Public Function getSelectedFilePath( _
      Optional ByVal defaultFolderPath As String, _
      Optional ByVal Title As String) As String
  Const DEFAULT_TITLE As String = "ファイルを選べやぼけーーー"
  '第1引数省略または存在しないフォルダパスなら既定のフォルダにする'
  If defaultFolderPath = "" Or _
     Not fsObj.FolderExists(defaultFolderPath) Then
    defaultFolderPath = ThisWorkbook.Path & "\"     '"
  Else
    If Right(defaultFolderPath, 1) <> "\" Then _
      defaultFolderPath = defaultFolderPath & "\"
  End If
  '第2引数省略なら既定の値に'
  '(引数リストに書くと長くなるのでプロシージャ内に記述)'
  If Title = "" Then Title = DEFAULT_TITLE
  
  Dim ret As String
  ret = ""
  Dim fileDialog_ As FileDialog
  Set fileDialog_ = Application.FileDialog( _
                                 msoFileDialogFilePicker)
  Dim hasSelected As Boolean
  hasSelected = False
  With fileDialog_
    .Title = Title
    .InitialFileName = defaultFolderPath
    .AllowMultiSelect = False
    hasSelected = .Show
    If hasSelected Then ret = .SelectedItems(1)
  End With
  getSelectedFilePath = ret
End Function

引数が省略されたときにとりあえずマクロを書いたブックのあるフォルダを指定するとか、複数ファイルの選択を不許可にするとか、ユーザーが何も選ばなかったときの対応とか、そういううっとうしい設定はメソッド内に吸収してしまっている。

したがって、このgetSelectedFilePathメソッドの使用者(つまり自分)は、そういうことを何にも考えずに、ただデフォルトのフォルダパスとダイアログのタイトルだけ渡してやれば(何ならそれすら渡さなくても)、ユーザーが選んだファイルのフルパスを取得することができるのだ。

その後どうするか

上で紹介したgetSelectedFilePathメソッドの場合、本来非常に多くの機能を備えているApplication.FileDialogオブジェクトをメソッドの中に封印し、そのうちのごく一部の機能だけを使用者(つまり自分)に公開するというやり方をとった。

こうすることで、単に〈ユーザーにファイルを選ばせ、そのファイルのフルパスを取得したい〉というだけなら、もはやApplication.FileDialogの使い方など一切意識する必要がなくなる。

しかし、少し考えたらわかるように、これは単なる〈逃げ〉に過ぎない。

では、どうするか。

ときどき見直す

これである。

実にあほらしいことのように見えるかも知れないが、これが大切である。

今回の場合なら、getSelectedFilePathメソッドのコードを読み返して処理を追いかけてみるのである。

かならずどこかで、

おおお! FileDialogオブジェクトとはそういうものだったのか!

となるときが来る。必ず来る。絶対に来る。

そうなると強い。

今度は、さらに痒いところに手が届くようにアレンジしたくなる。

これを繰り返すうちに、「あれ? Application.FolderDialogオブジェクトを直接使った方が早くね?」というときが来る。これは、まだ私は経験していないので断言しにくいが、きっと来る。

そのときが、Application.FolderDialogオブジェクトを極めたときなのだと思う。

おわりに

……というわけで、がんばりましょう。

最初は借り物のコードでいいんです。でも、ときどき見返して理解するようにしようぜ!

PropertyとSetter/Getter

PropertyとSetter/Getter

「Propertyプロシージャって、よく考えたら何なんやろ???」と思った。

現に、JavaなんかにはPropertyという仕組みはない。

比較

次のように、ParentHogeクラス、ChildHogeというクラスを作った。

ParentHogeクラスには、ChildHogeオブジェクトを返すChildプロパティがあり、ChildHogeクラスにはNameプロパティがある、という設計。

クラスモジュール ParentHoge
Option Explicit

Private child_ As ChildHoge

'Properties'
Public Property Set Child(ByVal argObj As ChildHoge)
  Set child_ = argObj
End Property
Public Property Get Child() As ChildHoge
  Set Child = child_
End Property

'Setter/Getter'
Public Sub setChild(ByVal argObj As ChildHoge)
  Set child_ = argObj
End Sub
Public Function getChild() As ChildHoge
  Set getChild = child_
End Function

見ての通り、ChildプロパティのProperty SetProperty Getを持たせる一方、同じ機能を提供するSetter/Getterメソッドも実装している。

クラスモジュール ChildHoge
Option Explicit

Private name_ As String

'Properties'
Public Property Let Name(ByVal argValue As String)
  name_ = argValue
End Property
Public Property Get Name() As String
  Name = name_
End Property

'Setter/Getter'
Public Sub setName(ByVal argValue As String)
  name_ = argValue
End Sub
Public Function getName() As String
  getName = name_
End Function

こちらもNameプロパティのProperty LetProperty Getを持たせる一方、同じ機能を提供するSetter/Getterメソッドを実装している。

使ってみる

ParentHogeインスタンスから、配下のChildオブジェクトのNameにアクセスする。

標準モジュール
Private Sub test02()
'(1) Propertyを使用'
  Dim p1 As ParentHoge
  Set p1 = New ParentHoge
  Set p1.Child = New ChildHoge
  p1.Child.Name = "ち~んw"
  Debug.Print p1.Child.Name
'(2) Setter/Getterを使用'
  Dim p2 As ParentHoge
  Set p2 = New ParentHoge
  Call p2.setChild(New ChildHoge)
  Call p2.getChild.setName("( ´_ゝ`)フーン")
  Debug.Print p2.getChild.getName
End Sub

(1)、(2)ともに、やっている内容は同じ。よって、実行すると、イミディエイト ウインドウには

f:id:akashi_keirin:20191025080143j:plain

このように表示される。

おわりに

少なくとも、利用側のコードの可読性という点では、圧倒的にPropertyを使うやり方が上回っていると思う。

何となくSetter/Getterの方がカッコよく思えてしまうのですがw

削除しても復活するワークシートw

削除しても復活するシート

先に断っておきますが、アイディアは、ノンプロ研で出会った id:FukuCyndiP さんのものです。私はただパクっただけw

ただ、某ウ〇ブカツのアホと違って、自己申告するところが人格者でしょ?

閑話休題

このマクロの挙動

元々のプロジェクト エクスプローラーは

f:id:akashi_keirin:20191022173314j:plain

この状態。

んで、三つあるシートのうち、Sh01Aho(シート名はaho)、Sh03Kasu(シート名はkasu)の二つは、削除しても復活するようにした。

で、それぞれのシートの削除を試みた様子がコチラ。

f:id:akashi_keirin:20191022173321g:plain

んで、上の動画の一連の動作をした後のプロジェクト エクスプローラーは

f:id:akashi_keirin:20191022173318j:plain

この状態。

おわりに

面白いでしょ?

コードはまたヒマなときにうpしますw

インターフェースを実装したインターフェースを実装する

インターフェースを実装したインターフェースを実装する

何言ってんだか、わかんねえだろ? おれもよくわかっていねえんだ。

インターフェースを実装したインターフェース

まず、一つインターフェースを作る。

リスト1 クラスモジュール IA
Option Explicit

Public Sub aa()

End Sub

実験用だから、全てが投げやり。IAというインターフェースで、aaというメソッドを指定している。

で、次に、このインターフェースIAを実装したインターフェースを作る。

スト2 クラスモジュール IB
Option Explicit

Implements IA

Public Sub IA_aa()

End Sub

Public Sub bb()

End Sub

インターフェースIAを実装したのだから、IA_aaメソッドは必須。さらに、bbメソッドを指定した。

これでインターフェースの準備は完了。

インターフェースを実装したインターフェースを実装したクラスを作る

では、上で作成したインターフェースIAを実装したインターフェースIBを実装したクラスを作る。

リスト3 クラスモジュール Hoge
Option Explicit

Implements IB

Public Sub IB_IA_aa()
  Call MsgBox("ち~んw")
End Sub

Public Sub IB_bb()
  Call MsgBox("(゚∀゚)アヒャ")
End Sub

実装したインターフェースはIB。ゆえに、インターフェースIBが指定している二つのメソッドIA_aabbを装備すれば良いはずだ。

インターフェース名をメソッド名の頭にスネーク記法で付けるのがルールなので、IB_IA_aaIB_bbとすれば、まさに万全。鉄壁の布陣。八門金鎖の陣を敷いた曹仁も驚きだろう。

Hogeクラスを使ってみる

標準モジュールに次のコードを書いてHogeクラスを使ってみる。

リスト4 標準モジュール
Private Sub test01()
  Dim b As IB
  Set b = New Hoge
  Call b.IA_aa
  Call b.bb
End Sub

コーディング中には、

f:id:akashi_keirin:20191020182225j:plain

こんなふうにヒントも出る。

いやが上にも胸が高鳴るではないか!!!!!!!!!!!!!!!!!

実行

震える手で実行ボタンをクリック……

f:id:akashi_keirin:20191020182229j:plain

な……、なんだってーーー!

あえなくコンパイル エラー……。

(´・ω・`)ショボーン

おわりに

何がいけないのでしょう……。