なぜなにプログラミング みくらす堂 > ソフトウェア工房 に戻る

エクセルでAPI 2004.06.14  各話に加筆修正

1.VBAのユーザーフォームのサブクラス化

VBAのユーザーフォームではリストボックスを使用できます。しかし、マウスのホイールでスクロールさせることができないので実用的ではありません。ホイールに関するリストボックスのイベントもないので、マクロでどうにかすることもできません。結局、ホイールでスクロールさせるためには、ユーザーフォームをサブクラス化して、ユーザーのコールバック関数で処理するしかありません。

ところで、ウィンドウのサブクラス化とは、ウィンドウメッセージを受取って処理するウィンドウプロシージャを既定のものからユーザーのコールバック関数に変更することです。この場合は、ホイールの回転を通知するウィンドウメッセージはあるので、それを受取ったときにリストボックスをスクロールさせるようにします。ただ、このためには、SetWindowLong関数 でウィンドウメッセージを処理する関数の切替をしなくてはならないのですが、引数としてウィンドウハンドルを指定しなくてはなりません。VBの場合は、ウィンドウハンドルがフォームのプロパティとして用意されているので何も問題がありませんが、VBAの場合は、それがないので一筋縄ではいきません。

とはいっても、分からなければ探せばよいだけで、ウィンドウのクラスやキャプションからウィンドウのハンドルを取得できる FindWindow関数 を利用します。VBAのユーザーフォームもキャプションを有するウィンドウには違いないので、この関数でウィンドウハンドルを取得し、SetWindowLong関数 の引数に指定します。

具体的にどうするかは、VBAのユーザーフォーム上のリストボックスをマウスのホイールでスクロールさせるサンプルプログラム(ホイールリストボックス whllstbx.xls 64.5KB)を用意しましたので、コードを参照して下さい。

ところで、SetWindowLong関数 の引数として、ユーザーのコールバック関数のアドレスを指定する必要があるのですが、これには AddressOf演算子 を利用します。この演算子は EXCEL2000/VBA6 からサポートされたため、EXCEL97/VBA5 ではサブクラス化は不可能ですのでご注意ください。

このようにAPI関数を利用すれば、VBAでも、ユーザーフォーム上のリストボックスをマウスのホイールでスクロールさせることができます。しかし、ホイール付きのマウスが標準になっている現状において、ホイールに全く対応していないことには釈然としないものを感じます。VB6も対応していませんし、VBやVBAの仕様において、なぜマウスのホイールが無視されているのかとても気になるところです。

( 2003.05.26 / 2004.06.14 )

2.アクティブなプリンタで選択されている用紙の最小マージンの取得

エクセルで作成した表などを印刷する際に、印刷領域を最大にするために上下左右のマージンを最小にしたい場合がありますが、このような目的でページ設定の左右上下の余白欄に零を入力すると問題なく設定できてしまいます。

しかし、本当に設定できたのかどうか印刷プレビューで余白を表示して確認してみると、余白が零のはずなのに余白を表す点線が用紙の渕より内側に表示されており、ページ設定の余白欄がプリンタドライバの印刷能力を顧慮していないことが分かります。

エクセルを操作して印刷する場合であれば、印刷プレビューで余白を表示して左右上下の余白を表す点線を用紙の渕にドラッグすれば、それぞれのマージンを最小にすることができます。しかし、VBAにはこのような操作に対応するメソッドやステートメントは存在しないため、マクロで普通にプリンタの最小マージンを取得したり設定することはできません。PageSetupオブジェクト の左右上下の Marginプロパティ に零を入力しても、ページ設定の左右上下の余白欄に零を入力するのと同じことなので、最小マージンを設定したことにはなりません。

マクロでプリンタの最小マージンを取得するためには、API関数でプリンタのデバイスコンテキストを取得し、そこからマージンに関する設定値を読み出すしかありません。ここで、デバイスコンテキストとはWindowsで描画をする際のキャンパスのようなもので、画像を出力するプログラムには必ず用意されています。プリンタの場合は、まず、OpenPrinter関数 でプリンタのハンドルを取得し、それを GetPrinterDC関数 の引数に指定してプリンタのデバイスコンテキストを取得します。次に、それを GetDeviceCaps関数 の引数に指定してマージンに関する設定値を取得します。

ただ、ここで気を付けなければいけないのは、プリンタのデバイスコンテキストは用紙毎に異なっていると言うことです。そのため、GetPrinterDC関数 の引数として用紙のサイズも指定しなくては行けません。この用紙のサイズというものがくせ者で、A版などはどのプリンタでも共通ですが、日本固有のB版や葉書・封筒などはプリンタ毎に異なっています。このような用紙について、どのよう値を指定すればよいのか途方に暮れてしまいます。

結局ここでも、分からなかったら探すしかない訳で、DeviceCapabilities関数 でプリンタで使用可能な用紙の情報を取得して、目的の用紙と一致するものを探します。この場合は、用紙名はあてにならないので、用紙の縦横のサイズから一致するものを検索します。

具体的にどうするかは、エクセルでアクティブなプリンタで選択されている用紙の最小マージンを表示するサンプルプログラム(プリンタマージン prntmrgn.xls 85.5KB)を用意しましたのでコードを参照してください。

このようなエクセルに対してワードでは、さすがに左右上下のマージンを零にして設定しようとすると警告が表示されて、適正な最小値が設定されるようになっています。それにしても、エクセルにしろワードにしろバグとは言えないでしょうが、何とも誤解を招きやすい中途半端な仕様です。普段この2大ソフトを何製か気にすることなく使っていますが、珍しく大味なアメリカ製品を思わせる部分です。

( 2003.07.12 / 2004.06.14 )

3.文字列のサイズの取得

エクセルのワークシートにセルの幅より長い文字列を入力する場合、セルの書式設定で縮小表示を設定しておけば、文字サイズが自動的に小さくなって文字列の全部を表示することができます。また、折り返しを設定しておけば、自動的にセルの幅で文字列が折り返されるとともにセルの高さが追加されて、これでも文字列の全部を表示することができます。

このように、縮小表示はセルのサイズを変更せずに文字列全部を表示し、折り返し表示は文字のサイズを変更せずに文字列全部を表示しますが、縮小したり折り返す際に不変の基準が必要なため、この2つの機能を同時に利用することはできません。それゆえ、セルの書式設定では、折り返しを設定すると縮小表示が無効になるようになっています。また、縮小表示を有効にすると折り返し機能が無効になり、文字列に改行が含まれていても折り返し表示がされなくなります。

データの整理などでエクセルで一覧表を作成していると、セルのサイズを変えずに文字列を改行・縮小して全部表示したいことがあります。しかし、このような機能の制限があるため、縮小表示を設定してセルに文字列を入力する際に改行しても、改行によって折り返し機能が有効になるため折り返し表示はなされますが、縮小機能が無効になってしまいます。

結局、セルのサイズを変えずに、改行された文字列を縮小して表示するためには、API関数で文字サイズ毎に文字列のサイズを取得してセルのサイズと比較し、文字列を全部表示できる文字サイズを検索するしかありません。通常、文字列のサイズの取得には、GetTextExtentPoint32関数 を使います。しかし、この関数はカーニングを考慮しないので正確なサイズを取得できません。カーニングとは、文字列を体裁よく表示するために文字位置を調整することで、文字列を表示するプログラムの多くは独自のカーニング処理を行っています。

このようなカーニングがなされた文字列について正確なサイズを取得するには、DrawText関数 を利用します。この関数は、実際に文字を描写する際に使うものですが、文字列を描写するのに必要なサイズを求める機能があるのでそれを使います。表示しないだけで、実際に描写してサイズを求めているので、カーニング処理後のサイズを取得することができます。

この DrawText関数 の引数として、文字列を表示するプログラムのデバイスコンテキストを指定しなくてはいけません。この場合は、エクセルのワークブックのデバイスコンテキストを指定する必要があります。これは、FindWindow関数 でエクセルのウィンドウハンドルを取得、FindWindowEx関数 でワークブックのウィンドウハンドルを取得、GetDC関数 でワークブックのデバイスコンテキストを取得 という流れになるのですが、ワークブックはエクセルの直接の子ウィンドウではないので注意が必要です。間に、XLDESK というクラス名のウィンドウが挟まれているので、このウィンドウに対する検索を忘れないようにします。

文字列のサイズを取得する具体的な方法については、サンプルプログラム(ストリングサイズ getstrsz.xls 126KB)を用意しましたのでコードを参照してください。

ところで、サンプルプログラムで表示される文字列のサイズは、文字列自体のサイズではなく、その文字列全部を表示するのに必要なセルのサイズになっていますのでご了承ください。また、デバイスコンテキストが異なるため、文字列のサイズはワークシートに表示する場合と印刷する場合で異なります。通常、印刷する場合のほうが大きくなり、画面上はセルに収まっている文字列が、印刷プレビューではセルからはみ出している場合があります。

( 2003.09.30 / 2004.06.14)

最終更新日 : 2004.06.14