全角ダブルクォーテーションの取り扱いはめんどくさい
全角のダブルクォーテーション
全角のダブルクォーテーションを文字列として扱うのに苦労したので、覚書的に記しておく。
普通に文字列として書いてみる
次のように書いてみる。
str = "“"
この状態で、[Enter]をふわっと押してみる。すると、
おわかりだろうか。
勝手に半角の「"
」にされてしまった上、ご丁寧にエスケープの「"
」まで付加されているではないか。
イミディエイトで調べてみる
今度は、イミディエイト・ウインドウに
?Asc("“")
と入力して[Enter]に指を叩きつける。
ファッ!?
仕方がないので、「“
」にエスケープ用の「"
」を添えてみる。
?Asc(""“")
こう打ち込んで、[Enter]を突く。
「34
」が返った。しかし、正の数が返ったということは、半角文字だということにならないか?
んで、今度は、
?Chr(34)
と入力して[Enter]をバチコーン!
ほれ、やっぱり半角の「"
」に改竄されてしまった。何安倍首相に忖度しとるねん。お前は財務省か。
文字コードでやってみる
こうなったら、文字コードで直接取得するほかあるまい。
このときにお世話になったコチラのサイトにある文字コード表を参考にする。
全角ダブルクォーテーション「“
」及び「”
」の文字コードは、16進数でそれぞれ「8167
」、「8168
」。
よって、イミディエイトに
?Chr(&H8167)
、
?Chr(&H8168)
と入力して[Enter]をひっぱたく。
無事、全角ダブルクォーテーションが取得できた。
おわりに
何で全角ダブルクォーテーションを半角ダブルクォーテーションに自動変換する必要があるのか、いまいちよくわからない。
SentencesコレクションとCharactersコレクション(Word)
SentencesコレクションとCharactersコレクション
Sentences
コレクションとCharacters
コレクションって、ちょっと変わっているよな、というだけの話。
Sentencesコレクションの要素はSentenceオブジェクトに非ず
たとえば、コード・ウインドウ上で、
Dim targetSentence As Sent
まで打ち込んだときに表示されるヒントは、
この通り。
つまり、Sentence
型のオブジェクトはない。
では、Sentences
コレクションの要素は何型なのか。
イミディエイト・ウインドウに、
?TypeName(ActiveDocument.Sentences(1))
と打ち込んで、[Enter]を激しく殴打。
「Range
」が返った。つまり、Sentences
コレクションの要素は、Range
オブジェクトなのである。
Charactersオブジェクトの要素はCharacterオブジェクトに非ず
同じく、コード・ウインドウ上で、
Dim targetCharacter As Char
まで打ち込んだときに表示されるヒントは、
この通り。
つまり、Character
型のオブジェクトはない。
では、Characters
コレクションの要素は何型なのか。
同じくイミディエイト・ウインドウに、
?TypeName(ActiveDocument.Characters(1))
と打ち込んで、[Enter]をそっと押し込む。
「Range
」が返った。つまり、Characters
コレクションの要素もまた、Range
オブジェクトなのである。
おわりに
だから何やねん、と言われても困るのだが、イマイチ分かりにくいWordVBAのオブジェクトモデルが、少しづつ分かってくるのではないか、という気がしてきた。
Range
オブジェクトの概念が、Excelと違いすぎる(というか、ExcelのRange
オブジェクトが独特すぎるのかな?)のが、分かりにくさの原因なのかも知れない。
追記
ちなみに、Words
コレクションの要素もWord
オブジェクトではなく、Range
オブジェクトです。
シートモジュールにインターフェイスを実装するのはやめた方がいい
シートモジュールへのインターフェイスの実装はやめた方がいいかも知れない
以前、
で、シートモジュールにもインターフェイスを実装することができる、ということを示した。
んで、そのときに使ったブックを今日久しぶりに開いてみたら、ち~んw珍現象が起こったので報告する。
ちなみに、今回ご紹介するブックでは、
このように、Sheet6
モジュールと、
Sheet7
モジュールに、「IA1ValueShowable
」というアホみたいなインターフェイスを実装している。
「IA1ValueShowable
」インターフェイスについては、コチラをどうぞ。
シートオブジェクトにアクセスできない
まず、コードウインドウで、
Sheet6.
と打ち込んでみる。当然、
このようにクイックヒントが出る。
とりあえず、安直にActivate
メソッドを選択する。
結果、次のようなプロシージャが出来上がった。
リスト1 標準モジュール
Public Sub testSheetModule() Sheet6.Activate End Sub
Sheet6
をアクティブにするだけのコード。
こいつを実行してみる。
ファッ!?
「予期せぬエラーが発生しました:(32809)。」だと!?
わけわからん……。
次に、プロシージャの中身を書き換える。
リスト2 標準モジュール
Public Sub testSheetModule() Sheet6.Range("A1").Value = "ち~んw" End Sub
Sheet6
のA1セルに「ち~んw」と書き込むためのコード。
こいつを実行してみる。
ファッ!?
今度は「実行時エラー '32809':/アプリケーション定義またはオブジェクト定義のエラーです。」と来やがった。
エラー番号はさっきと一緒だが、エラーメッセージは、オブジェクトが取得できていないときのものっぽい。
[デバッグ]ボタンをクリックしてみると、
「Sheet6.Range("A1").Value = "ち~んw"
」のところで止まっている(っていうか、ここしか止まるところないけどw)。
どういうことやねん。
今度は、プロシージャの中身を次のようにしてみる。
リスト3 標準モジュール
Public Sub testSheetModule() ThisWorkbook.Worksheets("Sheet6").Range("A1").Value = "ち~んw" End Sub
オブジェクト名で指定するのではなく、Worksheets
コレクションからアクセスしようと試みてみる。
こいつを実行してみる。
なんと、これもダメ。
やはり、このコードが実行できていない。
これは、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
で回してみる、ということ。
こいつを実行してみる。
すると、エラーが出ずに完走。
イミディエイトに
正しい結果が出力されている。……っつっても、これを見ても正しいかどうかなんて分からないだろうけれどw
おわりに
どうも、シートモジュールにインターフェイスを実装するのはやめといた方がいいみたい。
そもそも、シートモジュールにインターフェイスを実装したところであまりメリットはないだろうし……。
インターフェイスの実装を取りやめたらどうなるのか、という点については、まだ実験していない。またヒマができたらやってみます。
追記
ヒマなので、さっそく実験してみました。
ThisWorkbookモジュールの謎
ThisWorkbookモジュールの謎
タイトルはちょっと大げさです。
しかも、謎を解き明かす気などさらさらなく、豪快に投げっぱなしジャーマンです。
ThisWorkbookモジュールを改名する
プロジェクト エクスプローラーで、ThisWorkbook
モジュールを選択し、プロパティ・ウインドウの「オブジェクト名」を「ThatWorkbook
」に書き換えるw
バカ丸出しw
イミディエイト・ウインドウで調べてみる
イミディエイト・ウインドウに、次のように打ち込んで、それぞれ[Enter]に拳を叩き込む。
?ThisWorkbook.Parent
?ThatWorkbook.Parent
?TypeName(ThisWorkbook)
?TypeName(ThatWorkbook)
出力結果は、次の通り。
それぞれ、
Microsoft Excel
Microsoft Excel
Workbook
Workbook
が返っている。
コードウインドウ上での挙動
コードウインドウで、「ThatWorkbook.
」と入力してみる。
こんなヒントが出る……。
何か、見たことのあるリストやぞ……?
やっぱり、「Workbook
」クラスのメンバと同じ……。
おわりに
「ThisWorkbook
モジュール」というのはよくわからんなあ。
ThisWorkbookとは何者なのか
ThisWorkbookとは何者なのか
案外、初歩的なことがわかっていない、ということを痛感したので、後の戒めのためにも残しておくことにした。
「ThisWorkbook
」を使うことは非常に多い。VBAの場合、コードが書かれてあるブックが他のブックを操作する場面というのはよくある。そんなとき、コードが書かれてあるブックを容易に取得できる「ThisWorkbook
」は非常に便利だ。
で、何者なの?
Parentプロパティを調べてみる
まずは、イミディエイト・ウインドウに
?ThisWorkbook.Parent
と打ち込んで、[Enter]を殴打。
このように、Microsoft Excel
が返った。
オブジェクト ブラウザーで調べてみる
次に、オブジェクト ブラウザーで、ThisWorkbook
を検索。
Application
クラスのプロパティであることは一目瞭然。
結論
「ThisWorkbook
」というのは、Application
オブジェクトのプロパティです。
オブジェクト・プロパティを指定する式を書くときに、Application
オブジェクトから書くことが滅多にないから盲点になっていた、という話です。
ThisWorkbook.Worksheets("Sheet1")
は、
Application
オブジェクトのThisWorkbook
プロパティを参照して、コードを書いているこのExcelブックをWorkbook
オブジェクトとして取得。
Workbook
オブジェクトのWorksheets
プロパティを参照して、Worksheets
コレクションオブジェクトを取得。
Worksheets
コレクションのインデックスに「Sheet1
」を指定して、「Sheet1」シートをWorksheet
オブジェクトとして取得。
このように理解すればよいのだろうか。
注意
ここで言う「ThisWorkbook
」というのは、「ThisWorkbook
モジュール」のことではございません。
プリンタ名を返すProperty
プリンタ名を取得するProperty
前回
の続き。
前回までに作成した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
メソッドについては、
コチラをどうぞ。
ここまでで、モジュールレベル変数「printerNameArray_()
」には、プリンタ名が配列として格納されている。
で、処理が(4)の
If printerIndex < 0 Or _ printerIndex >= Me.getPrintersCount Then _ printerName = "": Exit Property
に移る。
これはガード節で、引数のチェックをしている。
引数printerIndex
が0
未満だったり、プリンタの数以上だと、「""
」を返すようにした。
……って、ガード節なら、こいつがまず一番最初に来るべきだよな。反省。
あとは、(5)の
printerName = printerNameArray_(printerIndex)
で、インデックスに応じたプリンタ名を配列から取得して返す。
実行
この状態で、次のコードを実行する。
リスト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
プリンタ名を取得して、順にイミディエイト・ウインドウに出力するだけのコード。
こいつを実行すると、
意図どおりの出力を得た。
おわりに
このProperty
をFor
ループで回してAddItem
メソッドと組み合わせたら、ユーザーフォームのコンボボックスなんかでユーザーにプリンタを選ばせることができる。