全角ダブルクォーテーションの取り扱いはめんどくさい

全角のダブルクォーテーション

全角のダブルクォーテーションを文字列として扱うのに苦労したので、覚書的に記しておく。

普通に文字列として書いてみる

次のように書いてみる。

str = "“"

f:id:akashi_keirin:20181018220426j:plain

この状態で、[Enter]をふわっと押してみる。すると、

f:id:akashi_keirin:20181018220437j:plain

おわかりだろうか。

勝手に半角の「"」にされてしまった上、ご丁寧にエスケープの「"」まで付加されているではないか。

イミディエイトで調べてみる

今度は、イミディエイト・ウインドウに

?Asc("“")

f:id:akashi_keirin:20181018220447j:plain

と入力して[Enter]に指を叩きつける。

f:id:akashi_keirin:20181018220455j:plain

ファッ!?

仕方がないので、「」にエスケープ用の「"」を添えてみる。

?Asc(""“")

こう打ち込んで、[Enter]を突く。

f:id:akashi_keirin:20181018220504j:plain

34」が返った。しかし、正の数が返ったということは、半角文字だということにならないか?

んで、今度は、

?Chr(34)

と入力して[Enter]をバチコーン!

f:id:akashi_keirin:20181018220510j:plain

ほれ、やっぱり半角の「"」に改竄されてしまった。何安倍首相に忖度しとるねん。お前は財務省か。

文字コードでやってみる

こうなったら、文字コードで直接取得するほかあるまい。

akashi-keirin.hatenablog.com

このときにお世話になったコチラのサイトにある文字コード表を参考にする。

f:id:akashi_keirin:20181018220518j:plain

全角ダブルクォーテーション「」及び「」の文字コードは、16進数でそれぞれ「8167」、「8168」。

よって、イミディエイトに

?Chr(&H8167)

?Chr(&H8168)

と入力して[Enter]をひっぱたく。

f:id:akashi_keirin:20181018220525j:plain

f:id:akashi_keirin:20181018220534j:plain

無事、全角ダブルクォーテーションが取得できた。

おわりに

何で全角ダブルクォーテーションを半角ダブルクォーテーションに自動変換する必要があるのか、いまいちよくわからない。

SentencesコレクションとCharactersコレクション(Word)

SentencesコレクションとCharactersコレクション

SentencesコレクションとCharactersコレクションって、ちょっと変わっているよな、というだけの話。

Sentencesコレクションの要素はSentenceオブジェクトに非ず

たとえば、コード・ウインドウ上で、

Dim targetSentence As Sent

まで打ち込んだときに表示されるヒントは、

f:id:akashi_keirin:20181017071836j:plain

この通り。

つまり、Sentence型のオブジェクトはない。

では、Sentencesコレクションの要素は何型なのか。

イミディエイト・ウインドウに、

?TypeName(ActiveDocument.Sentences(1))

と打ち込んで、[Enter]を激しく殴打。

f:id:akashi_keirin:20181017071846j:plain

Range」が返った。つまり、Sentencesコレクションの要素は、Rangeオブジェクトなのである。

Charactersオブジェクトの要素はCharacterオブジェクトに非ず

同じく、コード・ウインドウ上で、

Dim targetCharacter As Char

まで打ち込んだときに表示されるヒントは、

f:id:akashi_keirin:20181017071859j:plain

この通り。

つまり、Character型のオブジェクトはない。

では、Charactersコレクションの要素は何型なのか。

同じくイミディエイト・ウインドウに、

?TypeName(ActiveDocument.Characters(1))

と打ち込んで、[Enter]をそっと押し込む。

f:id:akashi_keirin:20181017071911j:plain

Range」が返った。つまり、Charactersコレクションの要素もまた、Rangeオブジェクトなのである。

おわりに

だから何やねん、と言われても困るのだが、イマイチ分かりにくいWordVBAのオブジェクトモデルが、少しづつ分かってくるのではないか、という気がしてきた。

Rangeオブジェクトの概念が、Excelと違いすぎる(というか、ExcelRangeオブジェクトが独特すぎるのかな?)のが、分かりにくさの原因なのかも知れない。

追記

ちなみに、Wordsコレクションの要素もWordオブジェクトではなく、Rangeオブジェクトです。

インターフェイスに汚染されたWorksheetオブジェクトの末路

インターフェイスに汚染されたWorksheetオブジェクトの末路

前回

akashi-keirin.hatenablog.com

インターフェイスを実装したせいでおかしくなってしまったWorksheetオブジェクトから、インターフェイスを取り外したらどうなるのか。

一枚の画像に語らせたい。

画像は語る

f:id:akashi_keirin:20181013192451j:plain

おわりに

ひとたびインターフェイスに汚染されたWorksheetオブジェクトは、インターフェイスを取り外しても、もはや元には戻りません。

覆水盆に返らず!

It is no use crying over spilt milk!

諸行無常……。

シートモジュールにインターフェイスを実装するのはやめた方がいい

シートモジュールへのインターフェイスの実装はやめた方がいいかも知れない

以前、

akashi-keirin.hatenablog.com

で、シートモジュールにもインターフェイスを実装することができる、ということを示した。

んで、そのときに使ったブックを今日久しぶりに開いてみたら、ち~んw珍現象が起こったので報告する。

ちなみに、今回ご紹介するブックでは、

f:id:akashi_keirin:20181013190911j:plain

このように、Sheet6モジュールと、

f:id:akashi_keirin:20181013190930j:plain

Sheet7モジュールに、「IA1ValueShowable」というアホみたいなインターフェイスを実装している。

IA1ValueShowableインターフェイスについては、コチラをどうぞ。

シートオブジェクトにアクセスできない

まず、コードウインドウで、

Sheet6.

と打ち込んでみる。当然、

f:id:akashi_keirin:20181013190939j:plain

このようにクイックヒントが出る。

とりあえず、安直にActivateメソッドを選択する。

結果、次のようなプロシージャが出来上がった。

リスト1 標準モジュール
Public Sub testSheetModule()
  Sheet6.Activate
End Sub

Sheet6をアクティブにするだけのコード。

こいつを実行してみる。

f:id:akashi_keirin:20181013191028j:plain

ファッ!?

「予期せぬエラーが発生しました:(32809)。」だと!?

わけわからん……。

次に、プロシージャの中身を書き換える。

スト2 標準モジュール
Public Sub testSheetModule()
  Sheet6.Range("A1").Value = "ち~んw"
End Sub

Sheet6のA1セルに「ち~んw」と書き込むためのコード。

こいつを実行してみる。

f:id:akashi_keirin:20181013191040j:plain

ファッ!?

今度は「実行時エラー '32809':/アプリケーション定義またはオブジェクト定義のエラーです。」と来やがった。

エラー番号はさっきと一緒だが、エラーメッセージは、オブジェクトが取得できていないときのものっぽい。

[デバッグ]ボタンをクリックしてみると、

f:id:akashi_keirin:20181013191049j:plain

Sheet6.Range("A1").Value = "ち~んw"」のところで止まっている(っていうか、ここしか止まるところないけどw)。

どういうことやねん。

今度は、プロシージャの中身を次のようにしてみる。

リスト3 標準モジュール
Public Sub testSheetModule()
  ThisWorkbook.Worksheets("Sheet6").Range("A1").Value = "ち~んw"
End Sub

オブジェクト名で指定するのではなく、Worksheetsコレクションからアクセスしようと試みてみる。

こいつを実行してみる。

f:id:akashi_keirin:20181013191100j:plain

なんと、これもダメ。

f:id:akashi_keirin:20181013191112j:plain

やはり、このコードが実行できていない。

これは、Sheet7でも同じ結果になった。

インターフェイスを実装したシートオブジェクトにはVBAからアクセスできなくなってしまうのだろうか?

別の方法でアクセスを試みる

次のコードで試してみた。

リスト4 標準モジュール
Public Sub testSheetModule()
  Dim Sh As Worksheet
  For Each Sh In ThisWorkbook.Worksheets
    Debug.Print Sh.Range("A1").Value
  Next
End Sub

要するに、WorksheetsコレクションをFor Eachで回してみる、ということ。

こいつを実行してみる。

すると、エラーが出ずに完走。

イミディエイトに

f:id:akashi_keirin:20181013191218j:plain

正しい結果が出力されている。……っつっても、これを見ても正しいかどうかなんて分からないだろうけれどw

おわりに

どうも、シートモジュールにインターフェイスを実装するのはやめといた方がいいみたい。

そもそも、シートモジュールにインターフェイスを実装したところであまりメリットはないだろうし……。

インターフェイスの実装を取りやめたらどうなるのか、という点については、まだ実験していない。またヒマができたらやってみます。

追記

ヒマなので、さっそく実験してみました。

akashi-keirin.hatenablog.com

ThisWorkbookモジュールの謎

ThisWorkbookモジュールの謎

タイトルはちょっと大げさです。

しかも、謎を解き明かす気などさらさらなく、豪快に投げっぱなしジャーマンです。

ThisWorkbookモジュールを改名する

プロジェクト エクスプローラーで、ThisWorkbookモジュールを選択し、プロパティ・ウインドウの「オブジェクト名」を「ThatWorkbook」に書き換えるw

f:id:akashi_keirin:20181006091038j:plain

f:id:akashi_keirin:20181006091046j:plain

バカ丸出しw

イミディエイト・ウインドウで調べてみる

イミディエイト・ウインドウに、次のように打ち込んで、それぞれ[Enter]に拳を叩き込む。

?ThisWorkbook.Parent
?ThatWorkbook.Parent
?TypeName(ThisWorkbook)
?TypeName(ThatWorkbook)

出力結果は、次の通り。

f:id:akashi_keirin:20181006091054j:plain

それぞれ、

Microsoft Excel

Microsoft Excel

Workbook

Workbook

が返っている。

コードウインドウ上での挙動

コードウインドウで、「ThatWorkbook.」と入力してみる。

f:id:akashi_keirin:20181006091104j:plain

こんなヒントが出る……。

何か、見たことのあるリストやぞ……?

f:id:akashi_keirin:20181006091114j:plain

やっぱり、「Workbook」クラスのメンバと同じ……。

おわりに

ThisWorkbookモジュール」というのはよくわからんなあ。

ThisWorkbookとは何者なのか

ThisWorkbookとは何者なのか

案外、初歩的なことがわかっていない、ということを痛感したので、後の戒めのためにも残しておくことにした。

ThisWorkbook」を使うことは非常に多い。VBAの場合、コードが書かれてあるブックが他のブックを操作する場面というのはよくある。そんなとき、コードが書かれてあるブックを容易に取得できる「ThisWorkbook」は非常に便利だ。

で、何者なの?

Parentプロパティを調べてみる

まずは、イミディエイト・ウインドウに

?ThisWorkbook.Parent

と打ち込んで、[Enter]を殴打。

f:id:akashi_keirin:20181006083304j:plain

このように、Microsoft Excelが返った。

オブジェクト ブラウザーで調べてみる

次に、オブジェクト ブラウザーで、ThisWorkbookを検索。

f:id:akashi_keirin:20181006083314j:plain

Applicationクラスのプロパティであることは一目瞭然。

結論

ThisWorkbook」というのは、Applicationオブジェクトのプロパティです。

オブジェクト・プロパティを指定する式を書くときに、Applicationオブジェクトから書くことが滅多にないから盲点になっていた、という話です。

ThisWorkbook.Worksheets("Sheet1")

は、

ApplicationオブジェクトのThisWorkbookプロパティを参照して、コードを書いているこのExcelブックをWorkbookオブジェクトとして取得。

WorkbookオブジェクトのWorksheetsプロパティを参照して、Worksheetsコレクションオブジェクトを取得。

Worksheetsコレクションのインデックスに「Sheet1」を指定して、「Sheet1」シートをWorksheetオブジェクトとして取得。

このように理解すればよいのだろうか。

注意

ここで言う「ThisWorkbook」というのは、「ThisWorkbookモジュール」のことではございません。

プリンタ名を返すProperty

プリンタ名を取得するProperty

前回

akashi-keirin.hatenablog.com

の続き。

前回までに作成したInstalledPrinterクラスにPropertyを追加する。

追加するPropertyは、printerName(ByVal printerIndex As Long)とし、引数にインデックスを渡したら、該当するプリンタの名前を返す、というものにする。

コード

リスト1 クラスモジュール宣言セクション
Private printerNameArray_() As String

まずは、クラスモジュールの宣言セクションに、モジュールレベル変数を置く。

String型の配列変数。取得したプリンタ名を配列として持っておくためのもの。

スト2 クラスモジュール
Public Property Get printerName(ByVal printerIndex As Long) As String
  printerNameArray_() = getInstalledPrinterNameArray()    '……(1)'
  If printerIndex < 0 Or _
     printerIndex >= Me.getPrintersCount Then _
    printerName = "": Exit Property    '……(4)'
  printerName = printerNameArray_(printerIndex)    '……(5)'
End Property

Private Function getInstalledPrinterNameArray() As String()    '……(A)'
  Dim printersCount As Long
  printersCount = Me.getPrintersCount    '……(2)'
  Dim ar() As String
  ReDim ar(getPrintersCount - 1) As String
  Dim n As Long
  n = 0
  Dim shellApp As New Shell    '……(3)'
  Dim targetPrinter As FolderItem
  For Each targetPrinter In shellApp.Namespace(ssfPRINTERS).Items
    ar(n) = targetPrinter.Name
    n = n + 1
  Next
  getInstalledPrinterNameArray = ar
  Set shellApp = Nothing
  Set targetPrinter = Nothing
End Function

Public Function getPrintersCount() As Long    '……(B)'
  Dim shellApp As New Shell
  getPrintersCount = shellApp.Namespace(ssfPRINTERS).Items.Count
  Set shellApp = Nothing
End Function

まず、(1)の

printerNameArray_() = Me.getInstalledPrinterNameArray()

で、モジュールレベル変数「printerNameArray_()」に、(A)のgetInstalledPrinterNameArrayメソッドによってプリンタ名の配列をぶち込む。

(A)のgetInstalledPrinterNameArrayメソッドではまず(2)からの3行

printersCount = Me.getPrintersCount
Dim ar() As String
ReDim ar(getPrintersCount - 1) As String

getPrintersCountを用いて、プリンタの数を取得して要素数を決定し、配列ar()を作成。

あとは、(3)からの6行

Dim shellApp As New Shell
Dim targetPrinter As FolderItem
For Each targetPrinter In shellApp.Namespace(ssfPRINTERS).Items
  ar(n) = targetPrinter.Name
  n = n + 1
Next

で配列ar()にプリンタ名を格納している。

FolderItemオブジェクトとか、Namespace(ssfPRINTERS).Itemsメソッドについては、

akashi-keirin.hatenablog.com

コチラをどうぞ。

ここまでで、モジュールレベル変数「printerNameArray_()」には、プリンタ名が配列として格納されている。

で、処理が(4)の

If printerIndex < 0 Or _
     printerIndex >= Me.getPrintersCount Then _
    printerName = "": Exit Property

に移る。

これはガード節で、引数のチェックをしている。

引数printerIndex0未満だったり、プリンタの数以上だと、「""」を返すようにした。

……って、ガード節なら、こいつがまず一番最初に来るべきだよな。反省。

あとは、(5)の

printerName = printerNameArray_(printerIndex)

で、インデックスに応じたプリンタ名を配列から取得して返す。

実行

f:id:akashi_keirin:20180930163538j:plain

この状態で、次のコードを実行する。

リスト3 標準モジュール
Public Sub Test()
  Dim i As Long
  With InstalledPrinter
    For i = 0 To .getPrintersCount - 1
      Debug.Print .printerName(i)
    Next
  End With
End Sub

プリンタ名を取得して、順にイミディエイト・ウインドウに出力するだけのコード。

こいつを実行すると、

f:id:akashi_keirin:20180930163547j:plain

意図どおりの出力を得た。

おわりに

このPropertyForループで回してAddItemメソッドと組み合わせたら、ユーザーフォームのコンボボックスなんかでユーザーにプリンタを選ばせることができる。