ポート名(?)付きのプリンタ名を返すFunction


ポート名(?)付きのプリンタ名を返すFunction

前回、

akashi-keirin.hatenablog.com

前々回

akashi-keirin.hatenablog.com

の集大成。

コード

プリンタの名前を渡したら、ポート名(?)付きのプリンタ名を返すFunctionを作ってみた。

前回同様、オブジェクト名は「InstalledPrinter」とし、PredeclaredIdTrueにしている。

リスト1 クラスモジュール
Public Function getPrinterNameWithPort( _
                  ByVal printerName As String) As String
  getPrinterNameWithPort = ""
  Dim printersCount As Long
  printersCount = getPrintersCount
  Dim tmp As String
  tmp = Application.ActivePrinter
  On Error Resume Next
  Dim i As Long
  For i = 0 To printersCount - 1    '……(1)'
    Dim ret As String
    ret = printerName & " on Ne" & Format(i, "0#") & ":"
    If Me.isExistPrinter(ret) Then GoTo Finalizer
    Err.Clear
  Next
  ret = printerName & " on nul:"    '……(2)'
  If Not Me.isExistPrinter(ret) Then ret = ""
Finalizer:
  On Error GoTo 0
  Application.ActivePrinter = tmp
  getPrinterNameWithPort = ret
End Function

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

Public Function isExistPrinter( _
                  ByVal printerName As String) As Boolean
  isExistPrinter = False
  Dim tmp As String
  tmp = Application.ActivePrinter
  On Error Resume Next
  Application.ActivePrinter = printerName
  Application.ActivePrinter = tmp
  If Err.Number > 0 Then Exit Function
  On Error GoTo 0
  isExistPrinter = True
End Function

(1)からの6行

For i = 0 To printersCount - 1
  Dim ret As String
  ret = printerName & " on Ne" & Format(i, "0#") & ":"
  If Me.isExistPrinter(ret) Then GoTo Finalizer
  Err.Clear
Next

Forループで、 on NeXX:の「XX」の部分をインクリメントしながらプリンタ名に附加してisExistPrinterメソッドで判定。

存在するプリンタ名だったら、ループを抜けてreturn。

(2)からの2行

ret = printerName & " on nul:"
  If Not Me.isExistPrinter(ret) Then ret = ""

これは、プリンタ名の末尾が

f:id:akashi_keirin:20180929193941j:plain

のように「 on nul:」となっていることがあるために追加。

Forループを抜けるということは、プリンタ名がヒットしていないということだから、最後の手段としてプリンタ名に「 on nul:」を附加することを試みる。

これでダメなら「""」を返す。

実行

イミディエイト・ウインドウに、それぞれ次のように入力して[Enter]をぶっ叩く。

?InstalledPrinter.getPrinterNameWithPort("Microsoft XPS Document Writer")

?Application.ActivePrinter

?InstalledPrinter.getPrinterNameWithPort("Microsoft Print to PDF")

?InstalledPrinter.getPrinterNameWithPort("Send To OneNote 2013")

実行結果は次の通り。

f:id:akashi_keirin:20180929194023j:plain

f:id:akashi_keirin:20180929194031j:plain

f:id:akashi_keirin:20180929194037j:plain

f:id:akashi_keirin:20180929194045j:plain

おわりに

Shellを使ったら、プリンタ名の取得はできるので、ユーザーフォームでプリンタ名を選ばせるかして、このFunctionと組みあわせたら、端末を問わずにプリンタを適切に指定できるようになるのではなかろうか。

ただ、プリンタのことがよく分かっていないので、ポート名(?)が網羅できているかは自信がない。

プリンタの数を返すFunction

プリンタの数を調べるFunction

Application.ActivePrinterにプリンタ名をセットしたくても、プリンタ名の後ろにくっついている「on NeXX:」の部分がわからないとセットできない。

同じプリンタに接続していても、端末ごとに「NeXX:」の部分が異なるので困ることになる。

そこで、「XX」の部分を「00」からインクリメントしてプリンタ名にヒットするかどうか確認することを考えた。

そのためには、ループ回数を決めるためにインストールされているプリンタの数を取得しないといけない。

プリンタの数自体は、

akashi-keirin.hatenablog.com

このときのように、Shell.Applicationオブジェクトを取得したら簡単に取得できるので、Shell.Applicationオブジェクトをラップしたメソッドを作った。

コード

前回

akashi-keirin.hatenablog.com

isExsistPrinterメソッドとともに、クラスモジュールに書いた。オブジェクト名は「InstalledPrinter」とし、PredeclaredIdTrueにして、Singleton的に運用することにした。

リスト1 クラスモジュール
Public Function getPrintersCount() As Long
  Dim shellApp As New Shell
  getPrintersCount = shellApp.Namespace(ssfPRINTERS).Items.Count
  Set shellApp = Nothing
End Function

参照設定で「Microsoft Shell Controls And Automation」にチェックを入れているからなのか、「Microsoft Scripting Runtime」にチェックを入れているからなのか、CreateObjectを使わなくてもShellオブジェクトをインスタンス化することができる。

あとは、NameSpaceメソッドでFolderオブジェクトを取得し、Items.Countプロパティでインストールされているプリンタの数が取得できる。

使ってみる

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

?InstalledPrinter.getPrintersCount

と打ち込んで、[Enter]をズドン!

f:id:akashi_keirin:20180929184456j:plain

6」が返った。

コントロール・パネルからプリンタの数を見てみると、

f:id:akashi_keirin:20180929184511j:plain

ご名算!

おわりに

ひとまず今回はここまで。

プリンタが存在するかどうかを判定するFunction

プリンタが存在するかを調べるFunction

軽量PDFを作成するためには、ExportAsFixedFormatメソッドを用いていたのでは限界があるので、

akashi-keirin.hatenablog.com

このときのように、プリンタにJUST PDFなどの、PDF出力用プリンタを選択する。

その場合、プリンタ名をApplication.ActivePrinterプロパティにプリンタ名をセットすることになる。

このとき、存在しないプリンタ名をセットしようとすると、当然ながらエラーが出る。

このことを利用して、プリンタが存在するかどうかを判定するFunctionを作成した。

コード

リスト1 標準モジュール
Public Function isExistPrinter( _
                  ByVal printerName As String) As Boolean
  isExistPrinter = False
  Dim tmp As String    '……(1)'
  tmp = Application.ActivePrinter
  On Error Resume Next    '……(2)'
  Application.ActivePrinter = printerName
  Application.ActivePrinter = tmp
  If Err.Number > 0 Then Exit Function
  On Error GoTo 0
  isExistPrinter = True
End Function

まず(1)からの2行

Dim tmp As String
tmp = Application.ActivePrinter

で、変数tmpApplication.ActivePrinterプロパティの値を保持しておく。

次に(2)からの5行

On Error Resume Next
Application.ActivePrinter = printerName
Application.ActivePrinter = tmp
If Err.Number > 0 Then Exit Function
On Error GoTo 0

では、最初に

On Error Resume Next

でエラーが出ても無視するようにしておいて、

Application.ActivePrinter = printerName

で、引数として受け取ったprinterNameApplication.ActivePrinterプロパティに突っ込んでみる。

printerNameが存在するプリンタ名なら、単にActivePrinterが切り替わるだけ。存在しないプリンタ名だとエラーが出る。On Error Resume Nextしているので、エラーが出ていたとしてもプログラムが停止することはないが、エラーが出ていればErr.Number > 0になっており、エラーが出ていなければErr.Number = 0のはずだ。

エラーが出るかどうかさえ分かれば良いので、この後即座に

Application.ActivePrinter = tmp

ActivePrinterを元通りにしておく。

もし、エラーが出ていたのであれば、

If Err.Number > 0 Then Exit Function

のところでreturn。

最初にisExistPrinterFalseにしてあったのだから、Falseが返る。

エラーが出ていなければ、最後にisExistPrinterTrueになるので、Trueが返る。

たったこれだけの簡単なコード。

使ってみる

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

?Application.ActivePrinter

と打ち込んで[Enter]をドン!

f:id:akashi_keirin:20180929181713j:plain

アクティブなプリンタが「JUST PDF 3 on Ne03:」だということがわかる。

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

isExistPrinter("ち~んw")

と入力して[Enter]をポン!

「ち~んw」などというアホな名前のプリンタがあるわけがないので、当然

f:id:akashi_keirin:20180929181720j:plain

Falseが返る。

この状態で、イミディエイト・ウインドウに

?Application.ActivePrinter

と打ち込んで[Enter]をプチッ!

f:id:akashi_keirin:20180929181726j:plain

ご覧のように、アクティブなプリンタは元通りになっている。

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

isExistPrinter("JUST PDF 3 on Ne03:")

と打ち込んで[Enter]をズン!

f:id:akashi_keirin:20180929181742j:plain

キッチリTrueが返っている。

おわりに

ひとまず、ここまで。

「フォームコントロールのボタンオブジェクトのEnabledプロパティって、意味ないんじゃね?」問題

「フォームコントロールのボタンオブジェクトのEnabledプロパティって、意味ないんじゃね?」問題

前回

akashi-keirin.hatenablog.com

の続き。

ボタンのEnabledプロパティをFalseにしてみる

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

activesheet.buttons(1).enabled = false

と打ち込んで、[Enter]をビシッ!

f:id:akashi_keirin:20180924182441j:plain

この画像では何のことか分からんが、イミディエイト・ウインドウに

?activesheet.buttons(1).enabled

と打ち込んで、[Enter]をペチッ!

f:id:akashi_keirin:20180924182449j:plain

このように、ボタンオブジェクトのEnabledプロパティの値がFalseになっていることが分かる。

ボタンはどうなっているか

EnabledプロパティがFalseである以上、ボタンは無効化されていると考えるのが通常だろう。そこで、

f:id:akashi_keirin:20180924182459j:plain

この状態でクリック。

ボタンにはこちらのマクロが仕込まれています。

f:id:akashi_keirin:20180924182507j:plain

フツーにボタンは生きとるじゃん。

おわりに

Enabledプロパティって、何か意味あるのでしょうか?

フォームコントロールのボタンオブジェクトをVBAで捕まえる

フォームコントロールのボタンを捕まえる

マクロを呼び出すためのボタンをシート上に設置することがよくある。

シート上に設置したボタンをVBAで取得するにはどうすればよいのだろうか。

Nameプロパティを取得してみる

ボタンの上で右クリックすると、

f:id:akashi_keirin:20180924180908j:plain

このように、ボタンを選択した状態になる。

この状態で、イミディエイト・ウインドウに

?selection.name

と打ち込んで、[Enter]をバシッ!

f:id:akashi_keirin:20180924180800j:plain

このように、「Button 1」と出た。

何オブジェクトなのかは分からんが、Nameプロパティを持っており、パラメータが「Button 1」であることが分かった。

何型のオブジェクトなのか

今度は、TypeName関数を使ってみる。

同じくボタンが選択された状態で、イミディエイト・ウインドウに

?typename(selection)

と打ち込んで、[Enter]をバチコーン!

f:id:akashi_keirin:20180924180920j:plain

ご覧のように「Button」と出た。

親オブジェクトは何なのか

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

?typename(selection.parent)

と打ち込んで、[Enter]をドオォーーーン!

f:id:akashi_keirin:20180924180935j:plain

今度は、「Worksheet」が返った。

フォームコントロールのボタンオブジェクトを取得する

上の実験により、フォームコントロールのボタンオブジェクトは、

  • Worksheetオブジェクトの配下である
  • Button型のオブジェクトである
  • Nameプロパティを持つ



ということが分かった。

以上のことを念頭に、コーディングしてみる。

すると……、

f:id:akashi_keirin:20180924180955j:plain

なにーーーーっ!

入力候補に「Button」がねえ!

しかし、それでもめげずにコーディングしてみた。

リスト1 標準モジュール
Public Sub testCommandButton()
  Dim btn As Button
  Set btn = ActiveSheet.Buttons(1)    '……(1)'
  btn.Caption = ActiveSheet.Range("A1").Value    '……(2)'
End Sub

(1)の

Set btn = ActiveSheet.Buttons(1)

Button型の変数「btn」にボタンオブジェクトをセット。

Button型」というぐらいだから、「Buttons」コレクションの要素のはず。

んで、ActiveSheetにはボタンは一つしかないのだから、Buttons(1)で良いはず。

あとは、

f:id:akashi_keirin:20180924181005j:plain

コーディング中に、このように入力候補が出るぐらいだから「Caption」プロパティがあるはず。ゆえに、(2)の

btn.Caption = ActiveSheet.Range("A1").Value

で、ボタンのテキストをA1セルの文字列にする。

実行

リスト1testCommandButtonをボタンに登録して、

f:id:akashi_keirin:20180924181014j:plain

この状態でクリック。

f:id:akashi_keirin:20180924181023j:plain

無事にボタンのテキストが書き換わった。

おわりに

Buttonオブジェクトの構造については、オブジェクト・ブラウザーにも出てこないので、ちょっとわかりにくい。

infoment.hatenablog.com

こちらの記事にインスパイヤされて書きました。

続編

akashi-keirin.hatenablog.com

クラスのPrivateメソッドもイミディエイトで実行できる

クラスのPrivateメソッドもイミディエイトで実行できる

再び超小ネタ。

クラスモジュールの準備

クラスモジュールを作る。

リスト1 クラスモジュール
'オブジェクト名は"HiddenBooks"'
Private Function isHiddenBook( _
                   ByVal targetBook As Workbook) As Boolean
  isHiddenBook = True
  If targetBook.IsAddin Then Exit Function
  Dim i As Long
  For i = 1 To targetBook.Windows.Count
    If targetBook.Windows(i).Visible Then _
      isHiddenBook = False: Exit Function
  Next
End Function

isHiddenBookというメソッドを1つ書いただけ。

PredeclaredIdをTrueにする

イミディエイトで実行することを考えて、PredeclaredIdTrueにする。

一旦、クラスモジュールHiddenBooksをエクスポートして、エディタで開く。

んで、上の方を

f:id:akashi_keirin:20180922124937j:plain

こんなふうにする(8行目です。)。

デフォルトでは、

Attribute VB_PredeclaredId = False

になっているので、「False」を「True」に書き換えて保存、再びインポートするだけ。

これで、インスタンス化しなくても[クラス名].[メソッド・プロパティ名]でメソッドやプロパティにアクセスできる。

実験

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

?HiddenBooks.isHiddenBook(Workbooks("PERSONAL.XLSB"))

と入力して[Enter]をぶっ叩くと、

f:id:akashi_keirin:20180922124947j:plain

このように、意図どおりの結果が出る。

「だから何?」と言われても、特に意見はありません。

ちなみに

クラス外から呼ぶのは

f:id:akashi_keirin:20180922130152j:plain

当然不可能ですw

個人用マクロブックはアドインに非ず

個人用マクロブックはアドインに非ず

「当り前だろバカ!」と思ったらスルー推奨。

IsAddinプロパティ

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

?Workbooks("PERSONAL.XLSB").IsAddin

と打ち込んで[Enter]をポチッ。すると、

f:id:akashi_keirin:20180922123534j:plain

Falseが返るのであった。

Windows.Countプロパティ

ちなみに、

?Workbooks("PERSONAL.XLSB").Windows.Count

だと、

f:id:akashi_keirin:20180922123543j:plain

このように、「1」が返る。

[Window].Visibleプロパティ

またまたちなみに、

?Workbooks("PERSONAL.XLSB").Windows(1).Visible

だと、

f:id:akashi_keirin:20180922123551j:plain

このように、「False」が返る。

おわりに

つまり、Workbooksコレクションのメンバが「PERSONAL.XLSB」かどうかは、WorkbookオブジェクトのNameプロパティで確認するのが一番簡単かつ確実ということになるのかなあ。