Quantcast
Channel: t-hom’s diary
Viewing all 493 articles
Browse latest View live

VBA WSHとScriptingに存在するFileSystemObjectは同一のバイナリーであることが判明

$
0
0

VBエディタから参照設定で「Windows Script Host Object Model」と、「Microsoft Scripting Runtime」の両方にチェックを入れると、どちらのライブラリにもFileSystemObjectが存在する。
f:id:t-hom:20170204001354p:plain

今回はこの2つが同じものなのか、それとも別物なのかを検証してみた。

発端は、いみひとさんのツイート。

実は私も以前から気にはなってたのだけど、ライブラリが違う以上別物だと思っていた。

Windows Script Host Object Modelの本体はC:\Windows\system32\wshom.ocxである。
一方で、Microsoft Scripting Runtimeの本体はC:\Windows\system32\scrrun.dllである。

まったく同じ機能を備えた、完全な同等品、しかし参照先のバイナリーが違う以上は別物だろうと結論づけていたのだが、相互に代入できるとなってはやはり気になる。

ちなみにこれらのオブジェクトはComponent Object Model(COM)という技術で作成されており、その実態はインターフェースの塊らしい。FileSystemObject型が単なるインターフェースであれば、異なるバイナリーを代入できてもなんら不思議なことではない。

さて、どうしたものかと思ったところで、以前以下の記事でやったファイルを一時的にリネームするという強引な手段を思い出した。
thom.hateblo.jp

そして今回もscrrun.dllをリネームすれば、Scripting.FileSystemObjectが利用できなくなるはず。

それで、実際に試した結果、Scripting.FileSystemObjectに加えてIWshRuntimeLibrary.FileSystemObjectまでもが利用できなくなった。

ということは、WSHのFileSystemObjectは実質scrrun.dllに依存しているということになる。

つまり、この2つのFileSystemObjectは同一ということになる。

WSHを参照設定しているなら、FileSystemObjectを使うためだけにわざわざScriptingを追加で参照設定する必要は無く、IWshRuntimeLibrary内のFileSystemObjectを利用すれば良いということかな。

まぁただ、IWshRuntimeLibrary.FileSystemObjectはなじみが薄いので、私はあえて重複と知ってもScriptingを参照すると思う。。

両方参照している場合、どちらのライブラリのものかはっきりさせるために変数宣言やNewの時にライブラリ名から指定すべきと考えていたが、VBAがどう解釈しようと同じバイナリを指すのだから、これは別にどっちでもいいってことになる。


ちなみにバイナリーってそもそも何?という型はこちら参照。
thom.hateblo.jp


VBA 中級者を悩ませるプロシージャ分割をマスターする極意

$
0
0

タイトルで大きく出てしまった。極意だなんてまあよく恥ずかしげもなく。
「だって教えるプロの~」よりマシか。。なんちゃって。

ま、是非知ってほしい内容ではあるので、釣ってみた感じ。

さて、それなりにVBAを書けるようになった方が次に悩むこととして、プロシージャの分割方法が分からないというものが多い。Functionの使い方が分からないという相談もよくいただくけれど、これもプロシージャ分割の問題。

上級者のコードは1つのマクロで複数のプロシージャを呼び出していたりするので、「ああ自分のコードと違う」と最初に気付くのがこのプロシージャの分割なのかもしれない。

よくある相談として、「どこをどう分けていいかわからない」というものがある。

これ、まずこの日本語を分けよう。

  • どこを分けていいかわからない
  • どう分けていいかわからない

この2つは別物で、前者は設計の話であるし、後者は具体的なコーディングテクニックの話である。これは明確に区別しておきたい。

実際にプロシージャの分割を学ぶ順としては、まずコーディングテクニックから学んだほうがスッと入ってくると思う。「分割できる」という実感がなければ、どこで分けるべきかという設計の話をしてもいまいちピンとこないんじゃないだろうか。だからまずどこでも分割できるだけのテクニックを身につけると良い。

ということで、今回はこの分割のためのテクニックを中心に解説していく。
設計についても書くつもりだったけど、テクニックを書いたら疲れてしまったのでそれはまた別の機会に。。

具体的なプロシージャ分割テクニック

さて、ここからは実際にプロシージャの分割方法について説明していく。
サンプルに使用するコードはこちら。

Subじゃんけん()Dim you AsIntegerDim com AsIntegerDo
        you =CInt(InputBox("じゃんけんの手を数字で入力"&vbNewLine& _"1:グー、2:チョキ、3:パー"))
        com = WorksheetFunction.RandBetween(1,3)SelectCase com
        Case1MsgBox"相手はグーを出しました。"Case2MsgBox"相手はチョキを出しました。"Case3MsgBox"相手はパーを出しました。"EndSelectIf you = com ThenMsgBox"あいこです。もう一度。"ElseIf(you =1And com =2)Or(you =2And com =3)Or(you =3And com =1)ThenMsgBox"あなたの勝ちです。"ElseMsgBox"あなたの負けです。"EndIfLoopWhile you = com
EndSub

まずは空行に注目する。皆さんもコードを書くとき、ある程度処理のまとまりごとに適宜空行を入れていると思う。つまりここが処理が切り替わる地点だと認識しているわけだ。
そもそもこの程度のマクロをわざわざ分割すべきかどうかという話は一旦おいといて、とりあえず分割してみよう。

最初にやることは、マクロをそっくりそのままバックアップしておくこと。マクロを分割するということは、下手をすると動かなくなってしまうからバックアップは大事。
別のモジュールを挿入してコピー&ペーストしておこう。

次にやることは、メインコードと同じモジュールに新しいプロシージャを作ることだ。

Sub手の入力()EndSub

まあこれは当たり前。
次に元のコードから切り出したい部分を、文字通り切り出してくる。

元のコードの該当部分は、作成した「手の入力」プロシージャに変更する。

Sub手の入力()
    you =CInt(InputBox("じゃんけんの手を数字で入力"&vbNewLine& _"1:グー、2:チョキ、3:パー"))
    com = WorksheetFunction.RandBetween(1,3)EndSubSubじゃんけん()Dim you AsIntegerDim com AsIntegerDoCall手の入力
        
        SelectCase com
        Case1MsgBox"相手はグーを出しました。"'----以下略

この時点ではまだ正しく動作しない。じゃんけんプロシージャの変数you、comと手の入力プロシージャの変数you、comはプロシージャを分割した時点でまったく関係がなくなってしまうからだ。単に名前が同じなだけ。

関係ないものを同じ名前にしておくとややこしいので、別の名前に変更してしまおう。

Sub手の入力()
    your_hand =CInt(InputBox("じゃんけんの手を数字で入力"&vbNewLine& _"1:グー、2:チョキ、3:パー"))
    computers_hand = WorksheetFunction.RandBetween(1,3)EndSub

次に外からデータを受け取れるように、手の入力プロシージャのカッコ内にこの変数を入れる。

Sub手の入力(your_hand, computers_hand)

ここに入力した変数は仮引数(かりひきすう)と呼ばれ、外部から渡されたデータが代入される専用の変数として宣言したことになる。
仮引数(かりひきすう)はプロシージャ内ではふつうの変数(専門的にはローカル変数という)と同じように使えるが、一点注意として、特に指定しなければ仮引数(かりひきすう)は参照という方法でデータを受け取るということ。

※いちいち(かりひきすう)と振り仮名を打ってるのは、私が「いんすう」と読む癖がなかなか抜けなかったので。最初の思い込みはなかなか消えないものだ。

手の入力プロシージャを呼び出すときに変数youとcomを渡すと、your_handとcomputers_handはそれぞれyouとcomと同じものとして扱われ、たとえばyour_handに値を代入するとyouにも同じ値が代入される。これが参照渡しである。

参照渡しの詳しい仕組みは以下の記事に書いたので興味があればどうぞ。
thom.hateblo.jp

次に、じゃんけんプロシージャから手の入力を呼び出すコードのカッコ内に、youとcomを記入する。

Call手の入力(you, com)

これは実引数(じつひきすう)と呼ぶ。仮引数(かりひきすう)と実引数(じつひきすう)はどちらも単に引数(ひきすう)と呼ばれることが多いが、説明の都合上分けておいたほうが理解しやすいのであえて用語を紹介した。

さて、これで1箇所分割できた。

他の箇所も分割するとこのようになる。

Sub手の入力(your_hand, computers_hand)
    your_hand =CInt(InputBox("じゃんけんの手を数字で入力"&vbNewLine& _"1:グー、2:チョキ、3:パー"))
    computers_hand = WorksheetFunction.RandBetween(1,3)EndSubSub相手の手を表示(computers_hand)SelectCase computers_hand
    Case1MsgBox"相手はグーを出しました。"Case2MsgBox"相手はチョキを出しました。"Case3MsgBox"相手はパーを出しました。"EndSelectEndSubSub勝敗判定(your_hand, computers_hand)If your_hand = computers_hand ThenMsgBox"あいこです。もう一度。"ElseIf(your_hand =1And computers_hand =2) _Or(your_hand =2And computers_hand =3) _Or(your_hand =3And computers_hand =1)ThenMsgBox"あなたの勝ちです。"ElseMsgBox"あなたの負けです。"EndIfEndSubSubじゃんけん()Dim you AsIntegerDim com AsIntegerDoCall手の入力(you, com)Call相手の手を表示(com)Call勝敗判定(you, com)LoopWhile you = com
EndSub

あ、それと今回はちょっと例が悪いので紹介できないけれど、プロシージャ分割した時点で、元の変数がそのプロシージャ内だけで使う一時的な変数になることもある。つまり仮引数として外部から持ってこなくても、そのプロシージャ内で宣言して、そのプロシージャ内で使い終わるような変数。

これをローカル変数と呼ぶが、その前段として以下の準備が必要になる。
thom.hateblo.jp

関数分割するかどうかにかかわらず、普段から変数は使用する直前で宣言するようにしておくと良い。

プロシージャを関数化するテクニック

VBAにおいて関数というのは、要するにデータを返すプロシージャで、ふつうはFunctionプロシージャで作る。
Functionをどういうときに使うのかという質問もよく受けるけど、その前にプロシージャの分割ができていることが前提となる。
さて、プロシージャの分割までは前項で完了したので、これの一部を関数化していこう。

まずはこちら。

Sub相手の手を表示(computers_hand)SelectCase computers_hand
    Case1MsgBox"相手はグーを出しました。"Case2MsgBox"相手はチョキを出しました。"Case3MsgBox"相手はパーを出しました。"EndSelectEndSub

まずはSubをFunctionに書き換える。

Function相手の手を表示(computers_hand)SelectCase computers_hand
    Case1MsgBox"相手はグーを出しました。"Case2MsgBox"相手はチョキを出しました。"Case3MsgBox"相手はパーを出しました。"EndSelectEndFunction

まだこの時点では値を返すことはできない。

そして、MsgBoxを表示させていたところを、プロシージャ名への代入式に変更する。

Function相手の手を表示(computers_hand)SelectCase computers_hand
    Case1相手の手を表示 ="相手はグーを出しました。"Case2相手の手を表示 ="相手はチョキを出しました。"Case3相手の手を表示 ="相手はパーを出しました。"EndSelectEndFunction

「相手の手を表示」プロシージャの変更はとりあえずこれだけでもOK。

プロシージャに代入ってところがイメージできにくいかもしれないのでもう少し簡単なサンプルで例を示す。

まずはSubで参照渡しを使った値の取得からおさらい。

Subヨブ()Dim ret AsLongCallヨバレール(10, ret)MsgBox ret
EndSubSubヨバレール(a, return_value)
    return_value = a *2EndSub

ヨブを実行すると、ヨバレールに実引数10とretが渡り、仮引数aとreturn_valueで受け取る。
このときヨバレールのreturn_valueはヨブのretと同じものを指しているのでreturn_valueに10*2が代入されるということは、retにも20が入る。

…という面倒な処理を頻繁にしなくて良いように、もうreturn_valueは書かなくても使えるようにしない?って生まれたのがFunction。
Functionを使って書き直すとこうなる。

Subヨブ()Dim ret AsLong
    ret =ヨバレール(10)'呼出しから戻ると見えないreturn_valueがretに入るMsgBox ret
EndSubFunctionヨバレール(a)'見えない仮引数return_valueがある。ヨバレール = a *2EndFunction

プロシージャ名自体が、return_valueのように機能する。
ここで呼出し元に戻す値を「戻り値(もどりち)」と呼ぶ。

戻り値はどんなデータ型を戻すか指定することもできる。
それにはプロシージャ名のカッコの後ろに[As データ型]を付与すれば良い。

Functionヨバレール(a)AsLong

ちなみに引数にもデータ型を指定することができる。

これらを「相手の手を表示」プロシージャに反映させるとこうなる。

Function相手の手を表示(computers_hand AsInteger)AsStringSelectCase computers_hand
    Case1相手の手を表示 ="相手はグーを出しました。"Case2相手の手を表示 ="相手はチョキを出しました。"Case3相手の手を表示 ="相手はパーを出しました。"EndSelectEndFunction

ちなみに私は戻り値は一旦retという変数に入れて最後でプロシージャに代入することが多い。

Function相手の手を表示(computers_hand AsInteger)AsStringDim ret AsStringSelectCase computers_hand
    Case1
        ret ="相手はグーを出しました。"Case2
        ret ="相手はチョキを出しました。"Case3
        ret ="相手はパーを出しました。"EndSelect相手の手を表示 = ret
EndFunction

そうすればプロシージャ名を変更したときに、2箇所の書き換えで済むから。

さて、呼出し側にはString型でメッセージが戻るので、直接MsgBoxに引き渡してやるとそのまま画面表示される。
このようなコードになった。

Subじゃんけん()Dim you AsIntegerDim com AsIntegerDoCall手の入力(you, com)MsgBox相手の手を表示(com)Call勝敗判定(you, com)LoopWhile you = com
EndSub

勝敗判定の関数化は皆さんでやってみてほしい。
いろんなやり方がある。たとえば、

  • メッセージを返す。
  • 結果を1(あいこの場合)、2(勝ちの場合)、3(負けの場合)とLong型で返し、呼出し元のSelect文でメッセージを分ける。
  • 結果を"あいこ"、"勝ち"、"負け"とString型で返し、呼出し元のSelect文でメッセージを分ける。
  • あらかじめ列挙型定数GameResultを宣言し、Win、Even、Loseを含める。呼出し元のSelect文でメッセージを分ける。

などなど。

どれが良いかという議論の前に、いろんなやり方、引き出しを持っておくことが重要だと思う。

どこを別プロシージャに分割するかについての参考書

以下の書籍が非常に参考になった。

ゲームプログラマのためのコーディング技術

ゲームプログラマのためのコーディング技術

書かれているコードはC++なのでVBAしかしない方は購入を躊躇するかもしれないけど、P67~89(初版 第1刷の場合)の関数化のパターンは中級者にとって非常に有益な情報が掲載されている。

一部引用

コードの重複部分をまとめるだけが関数化ではありません。

~ 中略 ~

関数化するポイントにはパターンがあります。ここでは、次の関数化のパターンを初心者でもわかりやすいように紹介します。

・条件式の関数化
・計算式の関数化
・ループの関数化
・ループのブロック内の関数化
・データ変換の関数化
・データ確認の関数化
・配列アクセスの関数化
・コメント部分の関数化

このパターンを身に付けるだけで格段にコードの保守性が高まります。

以降のページでこれらについて詳しく解説されている。

また、多過ぎる引数の問題、小さな関数の必要性、関数化の目的は再利用だけではない、などの非常にためになるトピックを扱っている。

ちなみにこの書籍でいう関数とは、Functionはもちろん、Subプロシージャも含むと思って良い。C++言語の用語ではどちらも関数なのだ。

ゲームプログラマのための」とタイトルについてるけど、具体的にゲームを作る話は出てこず、専らコーディング技術に焦点を当てた書籍なので、「すべてのプログラマのための基本コーディング技術」というタイトルの方が売れたかもしれない。

Amazonレビューでは「今更感の強い内容」といったレビューもあるのだけど、あくまで経験を積んだ現役バリバリのプログラマーにとって今更だというだけで、事務職や運用でVBAやってる方々からしたら目から鱗なお宝が盛り沢山だ。

一方で、クラスに関してはC++を前提にしているのでVBAで参考にできる部分とそうでない部分がある。VBAには継承が存在しないためだ。また、STLラムダ式など、VBAに無い機能を前提に書かれている箇所もあるのですべて参考にできるわけではない。

そのへんを割り切って、コードではなく解説をメインに読むと色々と学べるところがあると思う。

あ、あとインストラクターのネタ帳の伊藤 潔人さんがちょうど先ほどTwitterでタイムリーに以下の記事を紹介されていたのでこれも参考に追記。
www.publickey1.jp

VBAでテンプレートを元にHTMLコードを自動生成する

$
0
0

今回はVBAを利用してHTMLを生成するテクニックを紹介

題材は先日紹介した、参照設定とCreateObjectの対応リスト - You.Activate
thom.hateblo.jp

このページはご覧いただくとわかるように、項目名は同じで内容だけ異なるものが複数回出てくる。
f:id:t-hom:20170206031011p:plain

ひとつのオブジェクトの紹介は以下のHTMLで構成されている。

<h3>ファイルシステムオブジェクト</h3><dlclass="ProgIDList"><dt>説明</dt><dd>ファイル・フォルダの生成・移動・削除やテキストファイルの生成、読み込みなどに使用</dd><dtclass="fl">ProgID</dt><dd>Scripting.FileSystemObject</dd><dtclass="fl">参照設定名</dt><dd>Microsoft Scripting Runtime</dd><dtclass="fl">ライブラリ名</dt><dd>Scripting</dd><dtclass="fl">オブジェクト名</dt><dd>FileSystemObject</dd><dt>アーリーバインディング書式</dt><ddclass="code"><preclass="brush:vb toolbar:false">
    Dim fso As Scripting.FileSystemObject
    Set fso = New Scripting.FileSystemObject
</pre></dd><dt>レイトバインディング書式</dt><ddclass="code"><preclass="brush:vb toolbar:false">
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
</pre></dd>

これをバカ正直にコピー&ペーストで編集しても良いのだけれど、いかんせん効率が悪い。
ということで、これもテンプレートを作ってVBAでやってしまおう。

※本来はDBにデータを格納してPHPで動的にページを生成するのがセオリーだが、たかだか1ページのために仕組みを作るのも面倒だしこれくらいなら得意のVBAで良いかなと。

まず、シートはこんな感じ。
f:id:t-hom:20170206032220p:plain

薄い黄色で塗ってあるのがテンプレートで可変項目は$$$としている。
その右がデータ部で、1列がひとつのオブジェクト紹介。右へ右へとどんどんデータが続くイメージ。

ふつうはデータ方向は下なんだけど、HTMLのテンプレートは縦に書きたかったのでやむなく。
※もっとデータが多ければ、可変項目を「$$1$$」などとナンバリングしたテンプレートを別シートに作り、データは1行1レコードとする方法もある。

コードは以下の通り。シートモジュールに直接貼り付けて実行する。

OptionExplicitSub HTMLOutput()Const MAX_ROW =19Const START_COLUMN =3Const END_COLUMN =5Dim template: template = Range("B1:B"& MAX_ROW).ValueDim j, i
    For j = START_COLUMN To END_COLUMN
        Dim data: data = Range(Cells(1, j), Cells(MAX_ROW, j)).ValueFor i =1To MAX_ROW
            Debug.PrintReplace(template(i,1),"$$$", data(i,1))NextNextEndSub

実行するとイミディエイトウインドウにHTMLコードが生成されるので、あとは切り取ってエディタに貼り付ければ完成。
f:id:t-hom:20170206033502p:plain

最大行、開始列、終了列はハードコーディングしてるので使用時は定数を変更する必要あり。自動取得も簡単だけれど、今回は使い捨てマクロなのでそこは適当に。。

やってることは単純で、テンプレート部とデータ部をそれぞれ別の二次元配列に入れ、あとは1行ずつ$$$をリプレースしながらイミディエイトウインドウに出力している。

ちなみにイミディエイトウインドウは最大200行までしか出力できないので、一気に大量のデータをさばきたいときは変数に入れて最後にクリップボードに送るか、テキストファイルとして出力するなどの工夫が必要。まあ本格的にデータ扱うなら、PHP等で仕組み化した方が良いと思うが。

上記マクロで使っているセル範囲を配列に転記するテクニックはこちらに詳しく書いた。
thom.hateblo.jp

VBA クラスモジュールを使ってセル内の文字を簡単に色づけ

$
0
0

f:id:t-hom:20170207200918p:plain

VBAでセル内のテキストの個別の文字に色をつけるのはわりに面倒くさい。

たとえばこんな風に、着色したいとしよう。
f:id:t-hom:20170207201819p:plain

上のテキストを実現するには、以下のコードを書けば良い。

Sub hoge()
    Sheet1.Range("A1").Value="Red, Green, Blue"
    Sheet1.Range("A1").Font.Color =vbBlack
    Sheet1.Range("A1").Characters(1,3).Font.Color = rgbRed
    Sheet1.Range("A1").Characters(6,5).Font.Color = rgbGreen
    Sheet1.Range("A1").Characters(13,4).Font.Color = rgbBlue
EndSub

ここで面倒なのが、Charactorsプロパティに指定する文字数。
「何文字目から、何文字を」という指定をしないといけないけど、頭がこんがらがる。

これ、もう少しなんとかならんかな。。

というわけで、クラスモジュールを使って少し楽にカラフルな文字列を作れるようにしてみた。

作り方

クラスモジュールを挿入し、モジュール名を「ColorfulStringObject」と名づける。
コードはこちら。

PrivateType ColorText
    TextPart AsString
    ColorPart As XlRgbColor
EndTypePrivate colorTextArray()As ColorText

PrivateSub Class_Initialize()ReDim colorTextArray(0)EndSubSub AddText(txt AsString,Optional col As XlRgbColor = rgbBlack)
    colorTextArray(UBound(colorTextArray)).ColorPart = col
    colorTextArray(UBound(colorTextArray)).TextPart = txt
    ReDimPreserve colorTextArray(UBound(colorTextArray)+1)EndSubFunctionGetText()Dim ret AsStringDim i AsLongFor i =0ToUBound(colorTextArray)-1
        ret = ret & colorTextArray(i).TextPart
    NextGetText= ret
EndFunctionSub WriteToCell(r As Range)
    r.Value=GetTextDim location AsLong: location =1For i =0ToUBound(colorTextArray)-1
        r.Characters(location,Len(colorTextArray(i).TextPart)) _.Font.Color = colorTextArray(i).ColorPart
        location = location +Len(colorTextArray(i).TextPart)NextEndSub

準備はこれだけ。
このクラスを作るにあたって工夫した点として、クラス内部にPrivateのユーザー定義型「ColorText」を宣言し、それを配列に入れているところ。通常クラス内にPublicなユーザー定義型は宣言できないが、Privateなら問題ない。

ユーザー定義型はコレクションに追加できないのが残念だが、コレクションを使いたいだけのために外部にオブジェクトを作るのも面倒なので、ワンモジュールで完結するようにユーザー定義型の配列にした。

使い方

先ほどのRed, Green, Blueを表示させるには、標準モジュール等に以下のように書く。

Sub ColorRGB()Dim colorfulString As ColorfulStringObject
    Set colorfulString =New ColorfulStringObject
    colorfulString.AddText "Red", rgbRed
    colorfulString.AddText ", "
    colorfulString.AddText "Green", rgbGreen
    colorfulString.AddText ", "
    colorfulString.AddText "Blue", rgbBlue
    colorfulString.WriteToCell Sheet1.Range("a1")EndSub

AddTextメソッドに文字列と色を渡すと内部でColorText型の配列に保管される。
つまり、文字列全体を書いてから位置指定で着色するのではなく、最初からこの文字を赤で、この文字を緑でという風に追加していくのだ。
最後にWriteToCellメソッドにRangeを渡すと、そのRangeに実際にカラーで書き込まれる仕組み。

注意点として、VBAではRangeのValueプロパティを触ると色がリセットされてしまう。
そのためAddTextでは直接セルに書かず、最後にWriteToCallを呼ぶ仕様とした。

今回引数としてxlRgbColor列挙型を使用してみた。これは過去に以下の記事で紹介したもの。
thom.hateblo.jp

プロシージャの引数として列挙型を指定してやると、呼び出す側で入力ヒントが出るので便利。
f:id:t-hom:20170207203153p:plain

列挙型の実態はLongなのでRGB関数で作成した色や、vbのcolor定数(vbRedなど)も指定できる。

以下、別のサンプル。

■ランダムな色でHello, VBA!!を表示する。
f:id:t-hom:20170207203352p:plain

Sub RandomColorHelloVBA()Const MESSAGE ="Hello, VBA!!"Dim colorfulString As ColorfulStringObject
    Set colorfulString =New ColorfulStringObject
    Dim i AsLongFor i =1ToLen(MESSAGE)Dim r AsByte, g AsByte, b AsByte
        r = WorksheetFunction.RandBetween(0,255)
        g = WorksheetFunction.RandBetween(0,255)
        b = WorksheetFunction.RandBetween(0,255)
        colorfulString.AddText Mid(MESSAGE, i,1),RGB(r, g, b)Next
    colorfulString.WriteToCell Sheet1.Range("a2")EndSub

SQLの色分け
f:id:t-hom:20170207203443p:plain

Sub ColorSQL()Dim colorfulString As ColorfulStringObject
    Set colorfulString =New ColorfulStringObject
    colorfulString.AddText "select", rgbBlue
    colorfulString.AddText " * "
    colorfulString.AddText "from", rgbBlue
    colorfulString.AddText " people_table "
    colorfulString.AddText "where", rgbBlue
    colorfulString.AddText " age "
    colorfulString.AddText ">=", rgbMaroon
    colorfulString.AddText " 20"
    colorfulString.WriteToCell Sheet1.Range("a3")EndSub

工夫すればRangeへの出力だけでなくHTML出力なんかも作れるかと思う。

以上

プログラミングの入門に必要なのは「おお、すげー!動いた!」という体験。小難しいことは後回し。

$
0
0

こちら、最近たまたま昼休みに書店に立ち寄る機会があり、ふと手に取った書籍。

アイディアを実現させる最高のツール プログラミングをはじめよう

アイディアを実現させる最高のツール プログラミングをはじめよう

ターゲット読者はプログラミングに興味はあるけどやったことがない方。具体的なコードの話はほとんど出てこなくて、プログラミングの楽しさ・面白さを語った本。

自分はまぁターゲットからは外れてるんだけれど、プログラミングが「つまらなさそう」な理由という項目が目につき、パラパラ読んでると面白そうなのでそのまま買って帰った。

ちょうど先ほど読み終えたのだが、この本はとても大事なことを思い出させてくれた。
それは、自分がどうやってプログラミングに入門したのかということ。私も昔はコピペプログラマーだった。プログラマーと呼ぶのもおこがましい。。コピパー?

最近では自分が入門者だった頃の気持ちも忘れかけており、すっかり上級者ヅラをして「基礎がいかに重要か」なんてことをドヤ顔で吹聴しまわってるんだけど、入門者にとってみたら基礎なんてどうでもよくて、とにかく早く、動くもの・面白いものが作りたいんだよね。

(なんだかよくわからんけど)できた!動いた!すげー!」

やっぱ、ここからだろう。入門は。

変数宣言なんて後回し。変数名も適当でいい。力技?どんとこい!
入門以前に、小難しい説明で挫折してしまったら意味ないもんな。

ひょっとして将来その人は素晴らしいコードを書くかもしれないのに、そんなつまらないことで芽を摘んでしまったら勿体ない。

そんなことは入門してから考えたら良い。
おぼろげながらプログラミングというものが分かってきたら、そのとき改めて考えなおそう。

ただしこれだけは伝えておく必要がある。業務でミスできないコードを書くときは、きちんと変数宣言して、型にも気を配って、意味の分かるちゃんとした変数名をつけよう。

あ、あと今回書いたのは「入門以前~入門者」であって、「初心者」ではない。
上手か下手かは別として簡単なプログラムを自力で作れるようになったら、それはもう入門者ではない。

入門者を卒業したら、改めて基礎から学びなおすべし。

ちなみに、私もプログラミングの魅力を語った記事を書いてるので、これから初めてみようかなって方は是非読んでみて。
thom.hateblo.jp

VBA オートシェイプで作った桜のアイコンでユーザーフォームを可愛くデコレーションする

$
0
0

普段からこのブログを読んでくれてる方は、今回のタイトルを見て「ついにthomもVBAのやり過ぎで頭がおかしくなってしまったか」と思われた方もいるかもしれない。

可愛くだなんて。30超えたオッサンが何言うとんねん。
さてさて、今回作ったのはこれ。

でん!
f:id:t-hom:20170212001539p:plain

きゃーカワイイー!

…まじめな話、最初からコレをねらった訳ではなく、こんなふうになってしまったのは偶然の産物である。

きっかけはイラストの重要性に気付いたこと

マクロの機能的にはアイコンなんて何の意味もないと思う方も居るかもしれないけど、ユーザーがそのマクロの使用感に満足を覚えるかどうかという点でデザインは超重要な要素だ。

繰り返す。

デザインは超重要な要素だ。

さて、具体的にアイコンを付けようなどという発想に行き着いたきっかけはこちらのサイト。
ateitexe.com

アイコンの話が出てくるわけではないのだけど、説明にいろいろと可愛らしいイラストが添えてあり、読んでいて楽しい。
これまで私は説明のための図解をすることはあっても、ユーザーを楽しませるためという観点でイラストを書いたことは無かった。

そうだ!イラストだ!

と思い立ったとき、たまたまユーザーフォームのデザインを考えていたのでその2つが結びついた感じ。
それで、「よし、アイコンを付けよう。」となった。

なんでまた桜なのかというと、アイコンを作るにあたってとりあえず適当な画像が手元にないので、オートシェイプで済ませようと書き始めたところ、昼間ジュンク堂でたまたま立ち読みした本に、オートシェイプで桜の花びらを描く方法が載っていたので。

絵心がなくてもできる Wordで素敵なお絵描き

絵心がなくてもできる Wordで素敵なお絵描き

花びらの特徴的な形は一見難しそうに見えるけど、実はハートを挿入して頂点を上にずらすだけのもの。超簡単!たーのしー!
f:id:t-hom:20170212013144p:plain

それで桜を描いてそれに合うようにラベルでヘッダーに色をつけて、それに合う色は、、とやってるうちに気づいたら完全に女子だった。不覚。。

実際にアイコンを付ける方法

さて、アイコンを付けようと思ったはいいが、ひとつ困ったことがある。

基本的にはUserFormにImageコントロールを置いてLoadPictureする感じなんだけど、その画像は外部から持ってこなきゃいけない。するとExcelファイル単体で動作しなくなるので配布に難がある。

そこで、隠しシートに張り付けた画像を読み込むことを思いついた。ただ直接シートからLoadPictureはできないらしく、どうしても一回保存する必要がある。

Excelには幸い環境変数を取得するEnviron関数があるので、Temporaryフォルダを取得してそこに保存するようにしよう。

ここでまた技術的な課題があって、シェイプって直接保存できないんだ。
まぁ、いろいろ調べてたら、ChartObject(つまりグラフ)はExport命令でビットマップに書き出せるらしい。

何もデータを指定せずにグラフを挿入すると空の枠だけできるので、
f:id:t-hom:20170212013841p:plain

縦横比をそろえてからシェイプを張り付ける。
f:id:t-hom:20170212014142p:plain

グラフは標準で枠のサイズにあわせて中身が伸び縮みするので、最初に比率を合わせておかないとぐちゃっとなる。

それからグラフツールのレイアウトタブからグラフ名を「SakuraIcon」としておこう。
f:id:t-hom:20170212014704p:plain

最後にシート名を「Images」として準備完了。
最終的にシートは非表示にすれば良いけど、検証段階では表示させておく。

次にフォームをデザインしていく。

基本形はこちらで紹介したので説明を割愛。
thom.hateblo.jp

今回重要なのはImageコントロール

まずはフォームのアイコン表示位置に配置する。
今回説明用にイメージコントロールの背景色は黒にしたが、どのみち画像が入るので何色でも良い。
f:id:t-hom:20170212015438p:plain

それから今回イメージコントロールに設定するプロパティは以下の2つ。
f:id:t-hom:20170212015923p:plain

アイコンに枠線が入らないようにBorderStyleを0-fmBorderStyleNoneに設定し、画像が切れずにイメージコントロールに収まるようにPictureStyleModeを1-fmPictureStyleModeStretchに設定しておく。

最後にフォームにコードを書く。

PrivateSub UserForm_Initialize()Dim chartObj As ChartObject
    Set chartObj = Sheets("Images").ChartObjects("SakuraIcon")
    chartObj.ShapeRange.Fill.ForeColor.RGB= Label1.BackColor
    chartObj.Chart.ExportEnviron("temp")&"\SakuraIcon.bmp"
    Image1.Picture =LoadPicture(Environ("temp")&"\SakuraIcon.bmp")EndSub

まずImagesシートからSakuraIconグラフオブジェクトをchartObj変数に代入し、背景色をラベル1(今回はヘッダーのラベルがlabel1)と同じに設定している。こうするとヘッダーラベルの色を変更しても自動で画像の背景色が合うので桜の背景が透過的に見える。

次にグラフオブジェクトをテンポラリーフォルダーに保存し、それを読み込んでいるだけ。

bmpファイルを作る際にはグラフのサイズで出力されるため、実際に利用する際はImagesシートに置くグラフはアイコンと同じくらいのサイズまで縮小しておくと良い。ディスク容量の消費が減るというメリットもあるが、サイズが小さいほうが読み込み書き込み共に高速に行える。

Officeに用意されたImageMSOを利用する手も

さて、アイコンを利用したいだけなら自分で作らなくともImageMSOを利用する手もある。

コードを以下のように書き換えると、

PrivateSub UserForm_Initialize()
    Image1.Picture = Application.CommandBars.GetImageMso("HappyFace",80,80)EndSub

こんな感じで殺センセスマイリーが表示される。
f:id:t-hom:20170212022027p:plain

参考にしたのは以下のサイト。
www.ka-net.org

紹介されているのはボタンに表示させるコードだけどオブジェクトブラウザで調べたところボタンのPictureとイメージコントロールのPictureはどちらも同じ型だったのでそのまま流用できた。

ただこの方法だとOffice2010とOffice2013では見え方が違ってくる。
それによく見ると四隅に白色の背景が見える。つまりImageコントロールに読み込むと透過処理が出来ないってことかな。
手軽にアイコンを利用できる点はよさげなので、適宜活用していきたい。

VBA XlRgbColor定数をシート上に色相順、明るさ順で出力する

$
0
0

「あなたの好きな色は何色ですか?」と聞かれたら

赤、青、緑、黄、黒、白、紫…

まあ、普通はこんな感じで答えると思う。

ここで、狐色、若草色、深紅、枯草色といったちょっとこだわった感じの名前を返してくると、「おっ、情緒的でいいな」と思う。ラベンダー、アイボリー、アクアマリンなんて答えも素敵だ。

色にはそれぞれ素敵な名前がついている。その名前はモノを連想させたり、イメージを膨らませる。

さすがに団十郎茶、勿忘草色、空五倍子色、ラベンダーブラッシュ、コーンフラワーブルーなんて言われると「こいつ、ちょっとひねくれてるな」って感じがするけど。


団十郎て誰やねん。

団十郎茶(だんじゅうろうちゃ)とは、江戸時代の歌舞伎役者「市川團十郎」が代々用い…

団十郎茶(だんじゅうろうちゃ)とは?:日本の色・和色

知らんがな。
(いや、もちろん色を扱う特殊な職業の方なら良いと思う。)


さて、今回はVBAの色の話。
VBAではRGB関数を使って簡単に色を作成できるが、面倒くさくてもあえて名前で呼ぶというのは情緒があって良い。

それで、以前こんな記事を書いた。
thom.hateblo.jp

しかし、
案の定、面倒くさい。

何がって、探すのが。
まずもって色名をそんなに知らないうえ、シートに書き出しても英語名でアルファベット順なので。

f:id:t-hom:20170218054058p:plain
こっから選べって言われてもなぁ。。

普通、人間が色を探すときって、赤系とか青系といった色相(しきそう)や明るい、暗いといった輝度(きど)で探す。
※感覚的には濃い・薄いという用語のほうがシックリくるけど、あれは絵の具の話なので、ディスプレイ上は輝度(きど)

彩度も重要な要素だけど私は最初から彩度を基準に探すってことはしないので、一般的にもそうだと思う。

それで今回は、XlRgbColor定数を色相順、輝度順で出力してみたいと思う。

Win32APIにRGB値から色相、輝度、彩度を求めるColorRGBToHLS関数があるので、そちらを利用する。
※HLSはHue(色相)、Luminance(輝度)、Saturation(彩度)の意味

参考:ColorRGBToHLS function (Windows)

まずシート上に色相、輝度、彩度を入力する箇所を設ける。
f:id:t-hom:20170218060449p:plain

次に標準モジュールに以下を貼り付ける。

PublicDeclareSub ColorRGBToHLS Lib"Shlwapi.dll"(ByVal clrRGB AsLong, _
     pwHue AsInteger, pwLuminance AsInteger, pwSaturation AsInteger)

次にシートモジュールに以下を張り付ける。

Enum列
    XlRgbColor定数名 =1定数値
    日本語名
    色相
    輝度
    彩度
EndEnumSub HLS値取得()Dim H, L, S, i
    For i =2To143'←単発モノなのでハードコード
        Debug.Print Cells(i,.定数値).ValueCall ColorRGBToHLS(Cells(i,.定数値).Value, H, L, S)
        Cells(i,.色相)= H
        Cells(i,.輝度)= L
        Cells(i,.彩度)= S
    NextEndSub

そしてHLS値取得を実行すると、色相、輝度、彩度が入力されるので、あとはオートフィルタで並び替えるだけ。

色相順に並べてみた。
f:id:t-hom:20170218061106p:plain

彩度の昇順で並び替えたあと、輝度の昇順で並び替えてみた。
f:id:t-hom:20170218061236p:plain

無彩色でフィルタリングしてみた。
f:id:t-hom:20170218061439p:plain

緑系だけフィルタリングしたのち、輝度・彩度の順に並び替えた。
f:id:t-hom:20170218061903p:plain

これで色がずいぶん探しやすくなった。

というわけで皆、色名も使ってあげて。

VBA アドイン作成で使用するリボンアイコンの組み込み画像(ImageMSO)をBitmapで一括保存する方法

$
0
0

目次

能書き ~ うんたらかんたら

Excel、Word、PowerPointでは作成したマクロをアドインとして保存することができる。また、オリジナルのリボンタブを作ってマクロを登録しておくと配布された側はリボンからボタンを押すだけで使えるので便利だ。

マクロ実行用のボタンにはアイコンを付けることができる。アイコンに使用できる画像はあらかじめOfficeに組み込まれている。種類も豊富なので選び放題。実行するマクロのイメージにマッチしたアイコンを選択することで、まるでプロが作った本格的な製品のように格好いいアドインになる。

と、こ、ろ、が。。

作る方はこれ、超めんどうくさい。(@_@;)

まずファイルを保存して閉じて、拡張子に.zipを付けてからExploreで開き、中にある「.rels」を編集し、さらにCustomUIというフォルダを作成して中に自分で作ったCustomUI.xmlを配置し、一旦Explorerを閉じて拡張子を元に戻してから開きなおすとようやくデザインしたリボンが現れる。

やったことない方は、この時点でちょっと引いてると思う。
まぁ、これはそんなに難しくない。また、リボンを作るための専用のツールもあるので、そういうのをダウンロードできる職場ならまぁ比較的楽に作れるだろう。

ツールを使ったリボンのカスタマイズはこちらがおススメ。
Office 2007/2010・リボンのカスタマイズ 初心者備忘録

ちなみに私の職場ではそうした外部ツールのダウンロードは禁止されているので、やはり手でXMLをいじるしかないのだけど。

Excel2013の方はこちらで手でリボンを作成する方法を動画で紹介した。
thom.hateblo.jp
※訳あって今は2010をメインで使ってて、2010だとxmlが微妙に違うのでこのままでは使えないのだけど、そこは適当に検索してほしい。

さて、しかし。
本当にめんどうなのは、そこじゃないんだ。

ナイスな画像を選ぶ。
これ。

これなんだよ。めんどうくさいのは。

好きなアイコンを選ぶってのは楽しそうに思えるかもしれないけど、Office2010に存在するアイコン名は約8600点。しかも系統別ではなく、アイコンの名称でアルファベット順に並んでいる。

しかも全く同じアイコンが別名で登録されてたりするもんだから、それらがノイズになって更に探しにくい。

アイコンサンプルを紹介しているサイトや図で選べるようなものもあるけれど、系統別に整理されたものは無く、私の知る限りすべてアルファベット順だ。

たとえば、こちらは2013のアイコン一覧。
www.ka-net.org

画像で見て探せるので、少なくともテキストだけよりは断然助かる。非常にありがたい。

しかし人間、欲深いもので、やっぱもっと楽に探したいなぁと思う。
たとえばファイルならファイルのアイコン、DBならDBのアイコンでまとまっていれば求めているものが探しやすい。

まぁ、それをサイトの主に求めるのはお門違いというもの。
それなら自分で作ってしまえ!ということで今回の記事はその前段階であるImageMSOをBitmapで保存するマクロの紹介。

前置きが長くなってしまったが、次項で実際にBitmap保存するマクロを紹介する。

全ImageMSOをBitmapで保存するマクロ

マクロを実行する前に、前準備が必要となる。
まずはImageMSOの一覧をMicrosoftのサイトからダウンロードしてくる。

Download Microsoft Office Document: [MS-CUSTOMUI2] Supporting Documentation from Official Microsoft Download Center
こちらのサイトでDownloadボタンを押すと何をダウンロードするか選択する画面になるので、「imageMSO.txt」にチェックを入れてダウンロードしよう。

そしてメモ帳などのエディタで開き、全選択してコピーする。
f:id:t-hom:20170225183249p:plain

そして、Excelに張り付ける。
f:id:t-hom:20170225183405p:plain

idMsoと書かれた1列目がImageMSOの名前である。
2列目が1ならOffice2010に存在し、3列目が1ならOffice2013に存在するという意味である。
ただ今回存在しないものはOn Errorで処理するので消してしまって良い。

次にImageMSOを保存するフォルダを作る。
今回はC:\work\ImageMSOとした。

そしてB列に保存するファイルのフルパスが入るように以下の数式を挿入する。

="C:\work\ImageMSO\"&A2&".bmp"

f:id:t-hom:20170225183903p:plain

最後にオートフィルで最終行10237まで埋めたら前準備は完成。
f:id:t-hom:20170225184207p:plain

次に、前準備に使用したシートのシートモジュールに以下のマクロを記述する。
今回はSheet1モジュールに記述した。

Sub SaveAllMsoAsBitmap()Dim bmp As IPictureDisp
    Dim cb As CommandBars: Set cb = Application.CommandBars
    Dim arr: arr = Range("A2:B10237").ValueOnErrorResumeNextFor i =LBound(arr,1)ToUBound(arr,1)
            stdole.SavePicture cb.GetImageMso(arr(i,1),32,32), arr(i,2)NextOnErrorGoTo0EndSub

これを実行すると、C:\work\ImageMSOに次々とアイコンが保存される。

f:id:t-hom:20170225190000p:plain

A2:B10237とか、思いっきりハードコードしてるけど所詮一発ものなのでこれで十分。
ポイントはstdole.SavePictureと、cb.GetImageMsoである。

先ほど紹介した以下の記事の末尾にGetImageMsoメソッドの利用例が掲載されていたので、こちらを参考にさせていただいた。
www.ka-net.org

どうやらApplicationのCommandBarsオブジェクトにGetImageMsoというメソッドがあり、ここにImageMSOのID(つまり名前)を渡すと画像が得られるらしい。記事では80×80となっていたが、色々試した結果32×32にするとボケないことが分かったので今回は32×32で取得。

オブジェクトブラウザで確認すると、GetImageMsoはIPictureDispという型を返すようだ。
f:id:t-hom:20170225185356p:plain

それならこれを保存する手はないかと色々検索してたら、標準で参照されているstdoleライブラリにSavePictureという命令があることが分かった。

こちらもオブジェクトブラウザで確認してみる。
f:id:t-hom:20170225185552p:plain

確かにIPictureDisp型の値を引数に取っているようだ。

ということで、これらを組み合わせれば全ImageMSOの保存ができるというわけ。

今回はこれで以上。

次回は重複する画像の消し込みを紹介する。


VBAで2つの画像ファイルを比較して内容が同一かどうかを判定する方法

$
0
0

前回、アドインのリボンで使用できるImageISOをビットマップで保存するという記事を書いた。

thom.hateblo.jp

実際に保存してみたところ、その数8425点。しかし名前が違うだけで同じ画像がたくさんある。

たとえば以下の3つ。
f:id:t-hom:20170225234034p:plain

これらは特定画像を探したいときにノイズになる。

ということで、今回は名前が異なる同じ画像を排除し、ユニークなものだけを選り分ける方法を紹介する。

これを実現するためには、まず2つの画像を比較して同一かどうかを判定できれば良い。それさえ片付けば、あとはループで回すだけ。

正攻法でいくならビットマップのピクセルをそれぞれ比較するという方法があるが、今回はもっと簡単な方法を採用した。

それは、画像をバイナリデータとしてByte型配列に読み込ませた後、String型に変換してイコールで比較演算する方法である。

VBAでバイナリデータを読み込む方法は過去にやったことがある。以下の記事だ。
thom.hateblo.jp

また、String型の実態はByte型配列であることは以下の記事で述べた。
thom.hateblo.jp

それらを応用して作ったのが、ビットマップ画像を文字列型として返す関数。

Function ReadBmpAsString(file_name AsString)AsStringDim bmp()AsByteOpen file_name ForBinaryAs#1ReDim bmp(LOF(1))Get#1,, bmp
    Close#1
    ReadBmpAsString = bmp
EndFunction

もちろん画像は文字列ではない。しかし実はString型には文字として表現できないデータも含めることができるのだ。なぜならその実態はByte型配列だから。バイナリデータをString型に格納することができるのはそういうこと。

配列同士を比較しようと思ったら1要素ずつループさせるしかないが、String型なら単にイコールで比較できる。

では実際に試してみよう。

Subファイルの比較()Const IMAGE_FOLDER ="C:\Work\imageMSO\"Dim fileA AsString: fileA _= ReadBmpAsString(IMAGE_FOLDER &"AcceptProposal.bmp")Dim fileB AsString: fileB _= ReadBmpAsString(IMAGE_FOLDER &"AcceptInvitation.bmp")Dim fileC AsString: fileC _= ReadBmpAsString(IMAGE_FOLDER &"AcceptAndAdvance.bmp")

    Debug.Print fileA = fileB
    Debug.Print fileA = fileC
    Debug.Print fileB = fileC
EndSub

結果はこのようになった。

True
False
False

この結果はつまり、AcceptProposal.bmpとAcceptInvitation.bmpは実質同じ画像であるが、AcceptAndAdvance.bmpは別の画像であるということを示している。

よし、これでいける。。
と思って以下のマクロを組んでみた。

Subユニークファイル抽出()Dim t AsDouble
    t =TimerConst IMAGE_FOLDER ="C:\Work\imageMSO\"Dim fso As FileSystemObject
    Set fso =New FileSystemObject
    
    Dim uniqueImages As Collection
    Set uniqueImages =New Collection
    Dim f As File, bmp AsString, uniqueImage AsVariantForEach f In fso.GetFolder(IMAGE_FOLDER).Files
        bmp = ReadBmpAsString(f.Path)ForEach uniqueImage In uniqueImages
            If bmp = ReadBmpAsString(CStr(uniqueImage))ThenGoTo Continue
            EndIfNext
        uniqueImages.Add f.Path
Continue:
       '待ち時間の目安になるよう画像100個につき1度Printする。Dim cnt AsLong: cnt = cnt +1If cnt Mod100=0Then
            Debug.Print cnt
            DoEventsEndIfNext
    Debug.PrintTimer- t
    t2 =TimerForEach uniqueImage In uniqueImages
        fso.CopyFile uniqueImage, IMAGE_FOLDER &"unique\"Next
    Debug.PrintTimer- t2
    Debug.PrintTimer- t
EndSub

実行前にc:\work\ImageMSO\uniqueフォルダを作成しておく。
かなり時間がかかることが予測されるので100ファイルごとに1回Debug.Printで経過を表示させることに。
さらにトータルの秒数をカウントしてみたところ、、

40分かかった。。orz

メモリが膨らむのを懸念して比較対象のファイルを毎回ReadBmpAsStringで変換させているのだが、これは明らかに失敗だった。よく考えみれば今回のアイコンは1ファイルたかだか3KBなのだ。約8500個すべてメモリにロードしたとしてもトータルで25MBほどにしかならない。

ということでテイク2!

Subユニークファイル抽出Take2()Dim t AsDouble
    t =TimerConst IMAGE_FOLDER ="C:\Work\imageMSO\"Dim fso As FileSystemObject
    Set fso =New FileSystemObject
    
    Dim Images As Collection: Set Images _=New Collection
    Dim f As File
    ForEach f In fso.GetFolder(IMAGE_FOLDER).Files
        Images.AddArray(f.Path, ReadBmpAsString(f.Path))NextDim uniqueImages As Collection: Set uniqueImages _=New Collection
    Dim bmp AsVariant, bmp2 AsVariantForEach bmp In Images
        ForEach bmp2 In uniqueImages
            If bmp(1)= bmp2(1)ThenGoTo Continue
            EndIfNext
        uniqueImages.Add bmp
Continue:
       '待ち時間の目安になるよう画像100個につき1度Printする。Dim cnt AsLong: cnt = cnt +1If cnt Mod100=0Then
            Debug.Print cnt
            DoEventsEndIfNext
    Debug.PrintTimer- t
    t2 =TimerForEach bmp In uniqueImages
        fso.CopyFile bmp(0), IMAGE_FOLDER &"unique\"Next
    Debug.PrintTimer- t2
    Debug.PrintTimer- t
EndSub

なんと!
56秒で終了!!約48倍速!

まぁ考えてみれば当たり前で、最大のネックであるファイルオープン回数が全然違う。
イメージの数を約8500個として、最初のマクロだと、uniqueImagesに1個ある状態なら8500×1回、2個たまれば8500×2回、1000個たまれば8500×1000回と、途方もないファイル読み込みが発生しているが、テイク2ではファイル読み込みは最初の8500個のみ。あとはメモリ上に展開されたコレクション同士の比較で済むのできわめて高速だ。

ざっくり言って、メモリはHDDと比べて1万倍高速、SSDと比べても1000倍高速らしい。
qiita.com

さて、なにはともあれユニークなアイコン画像のみを抽出するところまでできた。
次回はこれを更に自動である程度分類するため、VBA機械学習もどきをやってみようと思う。

あくまで「もどき」である。精度は出ないのであまり期待せずにお待ちを。

VBA クラスモジュールを使って色見本(カラーパレット)を作る

$
0
0

前回の記事ではVBAで2つの画像を比較して内容が同一かどうかを判定した。
「次回はこれを更に自動である程度分類するため、VBA機械学習もどきをやってみようと思う。」と書いたのだが、ちょっとコード分量が多くなりそうなので前半と後半に分けようと思う。

その前半が今回。
それがなんで色見本なんてタイトルになってるかというと、記事単体で検索されたときの利便性を考慮した結果である。

今回は画像をピクセル単位で比較することになるのでまずはピクセルを簡単に比較できるように、色をオブジェクトとして扱うことにした。

ただクラスモジュールだけ紹介して「はいおしまい」ではあんまりなので、そのクラスの使い方のサンプルとしてカラーパレットを作ってみたという話。

まずクラスモジュールを挿入し、オブジェクト名をColorObjectとしておく。

OptionExplicitPrivateDeclareSub ColorRGBToHLS Lib"Shlwapi.dll" _(ByVal clrRGB AsLong, _
    pwHue AsInteger, _
    pwLuminance AsInteger, _
    pwSaturation AsInteger)PrivateDeclareFunction ColorHLSToRGB Lib"Shlwapi.dll" _(ByVal wHue AsInteger, _ByVal wLuminance AsInteger, _ByVal wSaturation AsInteger)AsLongPrivate colorRGB AsLongPrivate hue_ AsIntegerPrivate luminance_ AsIntegerPrivate saturation_ AsIntegerPropertyGet Hue()AsLong
    Hue = hue_
EndPropertyPropertyGet Luminance()AsLong
    Luminance = luminance_
EndPropertyPropertyGet RGBValue()AsLong
    RGBValue = colorRGB
EndPropertyPropertyGet Saturation()AsLong
    Saturation = saturation_
EndPropertyPropertyGet Red()AsLong
    Red = colorRGB \ 256 ^ 0Mod256EndPropertyPropertyGet Green()AsLong
    Green = colorRGB \ 256 ^ 1Mod256EndPropertyPropertyGet Blue()AsLong
    Blue = colorRGB \ 256 ^ 2Mod256EndPropertyPropertyLet RGBValue(rgb_value AsLong)If rgb_value >=vbBlackAnd rgb_value <=vbWhiteThen
        colorRGB = rgb_value
        Call ColorRGBToHLS(colorRGB, hue_, luminance_, saturation_)Else
        Err.RaisevbObjectError,,"不正なRGB値が渡されました。"EndIfEndPropertyFunction SetColorByHLS(h, l, s)Me.RGBValue = ColorHLSToRGB(h, l, s)
    SetColorByHLS = colorRGB
EndFunction

ColorRGBToHLSについては以下の記事で紹介した。
thom.hateblo.jp

今回カラーパレット作成にあたって使うのはこの逆関数ColorHLSToRGBである。

クラスは汎用的に色を扱えるように設計したので今回のサンプルで使用しないプロパティ・メソッドが殆どだけど次回必要になるので書いておくべし。

さて、このColorObjectクラスを使ってカラーパレットを作ってみよう。

実は以前もトライしたことがあったんだけど、当時はColorHLSToRGB関数の存在を知らなかったのでRGBそれぞれに割り振る値を遷移させることでそれっぽい色相を実現していた。
thom.hateblo.jp

ColorHLSToRGBを使えばはるかに簡単。

今回は面倒なのでフォームではなくてシート上に作成する。
Sheet1モジュールに以下を挿入して実行してみよう。

Subカラーパレット()Const SATURATION_VALUE =240Dim i, j
    For i =0To24: For j =0To24WithNew ColorObject
            .SetColorByHLS i *10, j *10, SATURATION_VALUE
            Cells(i +1, j +1).Interior.Color =.RGBValue
        EndWithNext j, i
    Range(Cells(1,1), Cells(25,25)).Borders.LineStyle = xlContinuous
EndSub

結果はこのとおり。
f:id:t-hom:20170226113734p:plain
※セル幅・ズームは手でいじってます。

でもなんか普段みるやつと違う。

↓普段みるやつ
f:id:t-hom:20170226113848p:plain

こうか?

Subカラーパレット2()Const SATURATION_VALUE =240Dim i, j
    For i =0To24: For j =0To24WithNew ColorObject
            .SetColorByHLS i *10, j *10, SATURATION_VALUE
            Cells(25- j,25- i).Interior.Color =.RGBValue
        EndWithNext j, i
    Range(Cells(1,1), Cells(25,25)).Borders.LineStyle = xlContinuous
EndSub

f:id:t-hom:20170226114020p:plain

惜しい。
もう一度普段みるパレットをよく確認してみると、どうやらメイン領域で色相・彩度を扱っていて、隣のバーで明度を扱ってるようだ。
f:id:t-hom:20170226114522p:plain

ちなみに定数をSATURATION_VALUEなんて長ったらしい名前にしたのは、SATURATIONとするとこれに引っ張られてクラスモジュール側に設定したSaturationプロパティまで大文字になってしまうためだ。このあたりの余計なお節介、イケてない。

さて、ふたたび。

これでどうだ。

Subカラーパレット3()Const LUMINANCE_VALUE =120Dim i, j
    For i =0To24: For j =0To24WithNew ColorObject
            .SetColorByHLS i *10, LUMINANCE_VALUE, j *10
            Cells(25- j,25- i).Interior.Color =.RGBValue
        EndWithNext j, i
    Range(Cells(1,1), Cells(25,25)).Borders.LineStyle = xlContinuous
EndSub

f:id:t-hom:20170226115129p:plain
おおっ!

ん?

f:id:t-hom:20170226115246p:plain

なにこれwww

気になるけど無視して24段に減らそう。

Subカラーパレット4()Const LUMINANCE_VALUE =120Dim i, j
    For i =0To24: For j =1To24'jを1スタートに変更WithNew ColorObject
            .SetColorByHLS i *10, LUMINANCE_VALUE, j *10
            Cells(25- j,25- i).Interior.Color =.RGBValue
        EndWithNext j, i
    
   '罫線範囲を変更
    Range(Cells(1,1), Cells(24,25)).Borders.LineStyle = xlContinuous
EndSub

完成!!

はい、今回はここまで。

VBA GDI32で画像をピクセルごとに比較して類似画像を選り分けるマクロ

$
0
0

今回の記事は以下3記事の集大成である。
1) VBA アドイン作成で使用するリボンアイコンの組み込み画像(ImageMSO)をBitmapで一括保存する方法 - t-hom’s diary
2) VBAで2つの画像ファイルを比較して内容が同一かどうかを判定する方法 - t-hom’s diary
3) VBA クラスモジュールを使って色見本(カラーパレット)を作る - t-hom’s diary

実用性の面で上手くいかないところがあり、まだ試行錯誤の途中なのだが一旦骨格はできたので公開することにした。

作成に当たってはこちらのサイトを参考にさせていただいた。感謝!
画像からGetPixelでピクセル情報を取得し、セルの色を変更(Excel VBA) - Bird-Soft Weblog

考え方

まず基準になるアイコン画像を選ぶ。今回はファイルアイコンを抽出したいので、適当にファイルの形状のアイコンをひとつ選んだ。

こちらのCustomFooterGallery.bmpである。
f:id:t-hom:20170302213958p:plain

次に、もう一つファイル型のアイコンを選ぶ。

今回はCustomPageNumberBottomGallery.bmpをチョイスした。
f:id:t-hom:20170302214225p:plain

この2つのそれぞれのピクセルを取得し、輝度と色相が似通った箇所だけ抜き出すと、このようになる。
f:id:t-hom:20170302214411p:plain

※細かい話をすると実際にはこんな感じになるのだけど、ややこしいので のっぺらぼうになることにして話を進める。
f:id:t-hom:20170302214617p:plain

すると、この のっぺらぼうが、類似ファイルを探す際のフィルターになる。
f:id:t-hom:20170302214411p:plain

あとは各ファイルをループで回しながらフィルターと同じ箇所のピクセルを比較し、一致率が一定以上のものを類似画像と判定すればよい。

注意点は、白に近いほど輝度の高いピクセルは無視すること。
でないと余白も比較対処に入ってしまい、大量の類似画像がでてくる。

作り方

ColorObjectクラス

まず前回の記事で作ったColorObjectを利用する。
クラスモジュールを挿入し、オブジェクト名をColorObjectとして以下を貼り付けよう。

OptionExplicitPrivateDeclareSub ColorRGBToHLS Lib"Shlwapi.dll" _(ByVal clrRGB AsLong, _
    pwHue AsInteger, _
    pwLuminance AsInteger, _
    pwSaturation AsInteger)PrivateDeclareFunction ColorHLSToRGB Lib"Shlwapi.dll" _(ByVal wHue AsInteger, _ByVal wLuminance AsInteger, _ByVal wSaturation AsInteger)AsLongPrivate colorRGB AsLongPrivate hue_ AsIntegerPrivate luminance_ AsIntegerPrivate saturation_ AsIntegerPropertyGet Hue()AsLong
    Hue = hue_
EndPropertyPropertyGet Luminance()AsLong
    Luminance = luminance_
EndPropertyPropertyGet RGBValue()AsLong
    RGBValue = colorRGB
EndPropertyPropertyGet Saturation()AsLong
    Saturation = saturation_
EndPropertyPropertyGet Red()AsLong
    Red = colorRGB \ 256 ^ 0Mod256EndPropertyPropertyGet Green()AsLong
    Green = colorRGB \ 256 ^ 1Mod256EndPropertyPropertyGet Blue()AsLong
    Blue = colorRGB \ 256 ^ 2Mod256EndPropertyPropertyLet RGBValue(rgb_value AsLong)If rgb_value >=vbBlackAnd rgb_value <=vbWhiteThen
        colorRGB = rgb_value
        Call ColorRGBToHLS(colorRGB, hue_, luminance_, saturation_)Else
        Err.RaisevbObjectError,,"不正なRGB値が渡されました。"EndIfEndPropertyFunction SetColorByHLS(h, l, s)Me.RGBValue = ColorHLSToRGB(h, l, s)
    SetColorByHLS = colorRGB
EndFunction

BitmapObjectクラス

次にビットマップをオブジェクトとして扱うため、BitmapObjectも作成する。
クラスモジュールを挿入し、オブジェクト名をBitmapObjectとして以下を貼り付ける。

PrivateConst IMAGE_BITMAP AsLong=0PrivateConst LR_LOADFROMFILE AsLong=&H10
PrivateDeclareFunction CreateCompatibleDC _Lib"gdi32"(ByVal hDC AsLong)AsLongPrivateDeclareFunction DeleteDC _Lib"gdi32"(ByVal hDC AsLong)AsLongPrivateDeclareFunction SelectObject _Lib"gdi32"(ByVal hDC AsLong,ByVal hObject AsLong)AsLongPrivateDeclareFunction DeleteObject _Lib"gdi32"(ByVal hObject AsLong)AsLongPrivateDeclareFunction LoadImage _Lib"user32"Alias"LoadImageA"( _ByVal hInst AsLong,ByVal lpsz AsString, _ByVal un1 AsLong,ByVal n1 AsLong, _ByVal n2 AsLong,ByVal un2 AsLong)AsLongPrivateDeclareFunction GetPixel _Lib"gdi32"(ByVal hDC AsLong, _ByVal x AsLong,ByVal y AsLong)AsLongPublic FilePath AsStringPublic ToString AsStringPrivate colorArray()AsLongPrivate mask()AsBooleanPublicPropertyGet Pixel(x, y)As ColorObject
    Dim ret AsNew ColorObject
    ret.RGBValue = colorArray(x, y)Set Pixel = ret
EndPropertyFunction EvalSimilarityScore(target As BitmapObject)AsLongDim hit AsLong, miss AsLongFor i =1To32: For j =1To32If mask(i, j)ThenIfAbs(Me.Pixel(i, j).Luminance - target.Pixel(i, j).Luminance)<10 _AndAbs(Me.Pixel(i, j).Hue - target.Pixel(i, j).Hue)<10 _Then
                hit = hit +1Else
                miss = miss +1EndIfEndIfNext j, i
    EvalSimilarityScore =Round((hit /(hit + miss))*100,0)EndFunctionSub CreateMask(blend As BitmapObject)ReDim mask(1To32,1To32)Dim i AsLong, j AsLongFor i =1To32: For j =1To32IfMe.Pixel(i, j).Luminance <200 _AndAbs(Me.Pixel(i, j).Luminance - blend.Pixel(i, j).Luminance)<10 _Then
            mask(i, j)=TrueElse
            mask(i, j)=FalseEndIfNext j, i
EndSubPublicSubMoveFile(path)WithCreateObject("Scripting.FileSystemObject")If.FolderExists(path)Then.MoveFile FilePath, path
            FilePath = path
        Else
            Err.RaisevbObjectError,"BitmapObject","移動先のパスがありません。"EndIfEndWithEndSubPublicPropertyGet Self()AsObjectSet Self =MeEndPropertyPublicFunction IsSame(bmp As BitmapObject)AsBoolean
    IsSame = bmp.ToString =Me.ToString
EndFunctionPrivateFunction GetBMPPixel()AsLong()Dim hDC AsLong: hDC = CreateCompatibleDC(0)Dim hBMP AsLong: hBMP _= LoadImage(0, FilePath, IMAGE_BITMAP,0,0, LR_LOADFROMFILE)Call SelectObject(hDC, hBMP)Dim ret()AsLongReDim ret(1To32,1To32)Dim x AsLong, y AsLongFor y =1To32: For x =1To32
        ret(x, y)= GetPixel(hDC, x -1, y -1)Next x, y
    GetBMPPixel = ret
    Call DeleteDC(hDC)Call DeleteObject(hBMP)EndFunctionPublicSub SetFile(path AsString)
    FilePath = path
    colorArray = GetBMPPixel
    
    Dim Pics()AsByteOpen path ForBinaryAs#1ReDim Pics(LOF(1))Get#1,, Pics
    Close#1
    ToString = Pics
EndSub

標準モジュール

ここで、FileSystemObjectを使用するため、Microsoft Scripting Runtimeを参照設定しておく。
次に標準モジュールに以下のコードを書いて実行する。※パス等は適宜環境に合わせて設定が必要。

Sub類似画像選り分け()Const基準パス ="C:\Work\imageMSO\unique\"Const振分け先 =基準パス &"File\"Const基準ファイル =振分け先 _&"CustomFooterGallery.bmp"Constフィルタ作成用ファイル =振分け先 _&"CustomPageNumberBottomGallery.bmp"Dim fso As FileSystemObject
    Set fso =New FileSystemObject
    DimbaseAs BitmapObject: Setbase=New BitmapObject
    base.SetFile 基準ファイル
    
    WithNew BitmapObject
        .SetFile フィルタ作成用ファイル
        base.CreateMask .Self
    EndWithDim f As File
    OnErrorResumeNextForEach f In fso.GetFolder(基準パス).FilesWithNew BitmapObject
            .SetFile f.path
            Ifbase.EvalSimilarityScore(.Self)>70Then.MoveFile振分け先
            EndIfEndWithNextOnErrorGoTo0EndSub

実行結果

このとおり、類似画像が集まってきた。
f:id:t-hom:20170302220400p:plain

ただ一部、端が折れてないものも混じってくる。
f:id:t-hom:20170302220448p:plain

類似画像選り分けマクロの基準値を70から60に下げて実行してみると、さらに多くのファイルが取得できたが、関係ないアイコンが混じる確率も上昇する。

Ifbase.EvalSimilarityScore(.Self)>60Then.MoveFile振分け先
            EndIf

さて、一応類似画像を取得できるようになったものの、取れてない画像も相当数ある。
以下は最初の基準でとれなかった画像。
f:id:t-hom:20170302221344p:plain

それもそのはずで、人間はファイルアイコンというカテゴリで一括りできてもコンピューターだと少しでもズレてると難しい。
f:id:t-hom:20170302221929p:plain

そこで改めてExcelのファイルアイコンを基準にパワポのファイルアイコンをフィルター作成用に用いて実行したところ、Office系のファイルアイコンがごっそりとれた。
f:id:t-hom:20170302221644p:plain

というわけで一部の類似アイコン振分けはすこーしだけ楽になった。
ただそのように機械的に振分けするのが難しいアイコンが大量にあるので結局地道な作業は必要になる。

VBA パスカル記法を単語ごとに区切って配列で返すSplitPascal関数を自作する

$
0
0

今回はパスカル記法を単語ごとに区切って配列で返す関数を作成する。

前回このような記事を書いたのだが、
thom.hateblo.jp

この記事を受けて@Dev_Clipsさん(サイト)からツイッター「ImageMsoの"名前"の一致率も類似画像抽出に使えそう」とのヒントを貰ったためだ。

さて、ImageMSO画像のファイル名はパスカル記法になっている。

パスカル記法とは、英単語を並べる際、単語の始まりをすべて大文字にしてスペースを入れずにくっつけた形。

例) ThisIsAPascalNotation

今回作成するのはこれを単語単位に分割し、配列に格納するための関数である。
SplitPascal関数と名付けよう。

まぁただコード書いて終わりではあんまりなので、今回は作成プロセスを追って紹介するスタイルで書く。
くどいほど少しずつ組み立ててみよう。
Functionプロシージャの組み立て方がいまひとつ難しいという方の参考になれば幸いである。

1) 枠組みを作る

Function SplitPascal()EndFunction

2) 引数、戻り値を決める

今回は文字列を渡すのでString型の引数を一つだけ。

Function SplitPascal(expression AsString)EndFunction

戻り値は今回Variant型にするので何も書かない。

3) 戻り値を返す処理を書く

戻り値の型はVariantであるが、そこに含める中身は配列なので、配列型でret変数を作ってとりあえずそれを返す処理にする。

Function SplitPascal(expression AsString)Dim ret()
    SplitPascal = ret
EndFunction

ここまでが定石。どのようなFunctionでもこの流れで作れるのでマスターしよう。
あとは戻り値であるretをどう作りこんでいくかである。

4) 1文字ずつループさせるための、枠組みを作る

1文字ずつ検査して大文字かどうかを見る必要があるので、とりあえず文字数分ループ。

Function SplitPascal(expression AsString)Dim ret(), i
    For i =1ToLen(expression)'処理Next
    SplitPascal = ret
EndFunction

5) 1文字ずつ切り出してプリントしてみる

このあと文字を切り出して、検査・加工するのだが、その前にとりあえず動作がわかるようにプリント文にしておく。

Function SplitPascal(expression AsString)Dim ret(), i
    For i =1ToLen(expression)
        Debug.PrintMid(expression, i,1)Next
    SplitPascal = ret
EndFunction

6) 呼び出してみる

メインコードを書いて呼び出してみる。まだ戻り値も何も使わないけど、とりあえず1文字ずつプリントされるところまで確認。

Sub Main()
    SplitPascal "ThisIsAPascalNotation"EndSub

7) 大文字かどうかの判定

ここで文字コードの知識が活きる。といってもコードは知らなくても大丈夫。A~Zが連番になってることを知ってれば、Asc関数とIf文で切り出した文字がA~Zの範囲に収まっているか調べられる。

Function SplitPascal(expression AsString)Dim ret(), i
    For i =1ToLen(expression)Dim char: char =Mid(expression, i,1)IfAsc("A")<=Asc(char)AndAsc(char)<=Asc("Z")Then
            Debug.Print"★"EndIf
        Debug.Print char
    Next
    SplitPascal = ret
EndFunction

このとき、大文字だったら★をプリントしたのち、charを出力。大文字でなければcharだけ出力される。
イミディエイトはこんな感じ。
f:id:t-hom:20170303234345p:plain

さて、ここで閃いた。これ、一文字ずつ出力しているが、★をスペースに置き換えて一つの文字列に足していったらどうか。

8) スペース区切りで出力

retを配列ではなくてただのString型に変更し、ここに結果文字列を足しこんでいく。

Function SplitPascal(expression AsString)Dim ret AsString, i
    For i =1ToLen(expression)Dim char: char =Mid(expression, i,1)IfAsc("A")<=Asc(char)AndAsc(char)<=Asc("Z")Then
            ret = ret &" "EndIf
        ret = ret & char
    Next
    SplitPascal = ret
EndFunction

メインコードは戻り値を出力する形に変更。

Sub Main()
    Debug.Print SplitPascal("ThisIsAPascalNotation")EndSub

すると、イミディエイトウインドウに「 This Is A Pascal Notation」と出力される。
このままでは先頭に1つスペースが入ってるうえ、当初の目的である配列で返すってのができていない。

9) 最後の仕上げ

まあここまで来ればあとは簡単。
余計なスペースの件はTrim関数で解決するし、配列になってない件はSplit関数で解決する。
ということで、戻り値の代入部分をちょっといじるだけ。

Function SplitPascal(expression AsString)Dim ret AsString, i
    For i =1ToLen(expression)Dim char: char =Mid(expression, i,1)IfAsc("A")<=Asc(char)AndAsc(char)<=Asc("Z")Then
            ret = ret &" "EndIf
        ret = ret & char
    Next
    SplitPascal =Split(Trim(ret))EndFunction

メインコードも配列を処理するよう変更

Sub Main()Dim word
    ForEach word In SplitPascal("ThisIsAPascalNotation")
        Debug.Print word
    NextEndSub

出力結果はこちら

This
Is
A
Pascal
Notation

以上で完成。

ただ当初の目的であったImageMSOへの応用はあまり芳しくなく。。
なんか名前ベースで探しても毛色の違うアイコンが結構ヒットするので苦労中。

VBA 入門書を再評価する ~ チャレンジングな5冊をピックアップして劇甘レビュー

$
0
0

私は普段マニアックな記事ばかり書いてるが、実は入門者向けの教材なんてのも書いている。

さりげないアッピールはいやらしいので、堂々といこう。

宣伝!みんな、見てねっ!

EXCELVBA入門教材 急がば回れ!文法から覚えるやさしいVBA入門

ダウンロードはこちら↓(無料)
ダウンロード - You.Activate


おっと。

今回はそれが本題ではなくて、巷のVBA入門書について。

私の教材のまえがきで、このように書いた。

すでに VBAの入門書はたくさん出回っていますが、私が知る限りはどれも似たような構成になっています。はじめにマクロの自動記録、それからセルの操作などが続いて最後の方に申し訳程度に文法などが紹介されています。
VBA以外の他のプログラミング言語は、まず文法から学習します。文法はプログラミング言語の要ですので、ここをしっかり理解しないことには、役に立つプログラムは作れません。しかしなぜか VBAに限っては文法を中心に解説された入門書が見つからず、前述のような状況です。

さて、自信満々に書いたものの単に私がそれほど多くの入門書を知らないだけということもありうる。
どれも似たような構成って、じっくり読んでもないのになんて失礼なこと言うんだろう、私。

ただ私が「どれも似たような」と書いたのは「構成」についてであって、似たような説明だとも、似たようなデザインとも、似たようなプログラムとも言ってない。それぞれの入門書をじっくり読んでみると、いかに分かりやすく読者に説明するか、涙ぐましい工夫が満載で、本気で「この本でプログラムができるようになってほしい」という想いが伝わってくる。

王道的な書籍も良いけど、私は新しい解説手法にチャレンジした書籍が好きで応援したくなる。

今回は私が特に気にいったチャレンジングな書籍について5冊レビューしよう。
どれも似たような構成と書いてしまったお詫びも込めて、わざとらしいくらい褒めちぎろう。

各書籍ごとのチャレンジをレビュー

ExcelVBA超入門教室 Excel2010/2007/2003対応 (教えて!蔵之介先生シリーズ)

[レビュー]
対話形式で、登場人物の美咲さんがコロコロ表情を変えるので超カワイイ。蔵之介のまったく変化しないシュールな顔も良い。Amazonのなか見検索で見られるので是非!更に題材として複数のブックから一つのリストへ転機するという超実用的なものを扱っており、即戦力になる。いや、そんなことより美咲さんがカワイイ!
すばらしい!

Excel2013/2010限定版 やさしく学ぶ エクセルVBA

Excel2013/2010限定版 やさしく学ぶ エクセルVBA

Excel2013/2010限定版 やさしく学ぶ エクセルVBA

[レビュー]
この書籍も対話形式。しかし私が気に入った工夫点はそこではなく、とにかくプログラムの文字が大きくハッキリしていること。
おそらくConsolasフォントを使ってると思われ、ゼロとオーの取り違えやアイとイチの取り違えもしにくい。
実物を見ると、でかっ!て突っ込んでしまいそうなくらい大きい文字であるが、まったく初めての方が写経するにはちょうど良い。プログラミングではドットやダブルクォーテーションなどの小さな記号をよく使うので、文字が小さいと見落としてハマってしまうことがあるが、この本は大きな文字で見落としにくい。
更に初心者に配慮して異例の日本語変数採用。入力は少し煩わしくなるけど、プログラムの理解しやすさはダントツ。
日本語変数はどちらかといえば否定されがちだけど、少しでも入門者に易しくという配慮はすばらしいチャレンジである。
感動した!

できるExcelマクロ&VBA作業の効率化&スピードアップに役立つ本 2016/2013/2010/2007対応 できるシリーズ

[レビュー]
上級者が鼻で笑う「できる」シリーズ!おっと失礼。ただ私も正直ちゃんと読むまではバカにしてたのだ。ずっと生き残ってるし新しいOfficeが出るたびにすぐ出てくるので売れてるんだろうなとは思っていた。この書籍の良いところは膨大なスクリーンショットの数である。画面そのままなので、吹き出しに従って操作すれば一通り学習を進めることができる。ごちゃっとした印象を受けるのはサイドカラムのヒント情報量の多さによるもので、実際にはメインカラムの画像の指示に従って進めるのでそこまで不便ではない。
日本語変数を採用しているのも初心者向けの配慮として良い。まぁこのシリーズにおける日本語変数はチャレンジングというより割り切りに近いものかなと思うけれど。
それと驚いたのが無料電話サポートが付いている点。VBAを電話でサポート?正気かコイツら。。質問時間に制限はあるもののの、これはすごいチャレンジだ。
グレイト!

自分のペースでゆったり学ぶ ExcelVBA

自分のペースでゆったり学ぶ Excel VBA

自分のペースでゆったり学ぶ Excel VBA

[レビュー]
この書籍の良いところは、「ドット」を日本語の「の・を」であると言い切ってしまったところ。
説明が全般的にわかりやすく、初心者を想定した読みやすい文章で専門書を読んでいるというよりふつうの書籍のようにスラスラ読める。
熊のイラストも良い。熊がカワイイってより、これを描いた女性らしい感性が素敵。見ていてほのぼのする。
グッジョーブ!

ExcelVBA超入門講座 Excel2010/2007対応

ExcelVBA超入門講座 Excel2010/2007対応

ExcelVBA超入門講座 Excel2010/2007対応

[レビュー]
これ、私にとって大本命!楽しさは他の書籍に譲る。硬派なあなたにピッタリの本格的なプログラミング入門書。
というのもこの本、「マクロの記録をしてみましょう」なんて甘っちょろいことはせずに最初から文法の要を説明している。
プログラミングは基本制御構造「順次」「選択」「繰り返し」でできている。つまりこれが要になるわけで、これがわからないといかなる便利なプログラムも組みようがないのだ。これが現実である。
他の書籍は幻想を見せてくれる。だが現実はつらい。乗り越えられる人もいるけど、挫折してしまう人も多い。この書籍では初っ端から現実にご対面だ。下手なごまかしはせず、サバイバルに必要な「道具」を最初から持たせてくれる。
エクセレント!

この書籍に対しては、私は「どれも似たような構成」という前言を撤回せねばならない。
お詫びおよび、最大限の賞賛として「購入」を贈る。

さっきAmazonでポチった。

あとがき

本当は入門書の目次構造をひととおり調べたので、他言語の目次と比較しながら変数や制御構造の説明が登場するタイミングについて書き、Excelプログラミングの特殊性について触れたかったのだけど、その際に入門書にきちんと目を通してみたら以外と良い点、工夫されている点が多く見られたのでお詫びと賞賛を込めていろいろ書いてたら当初の目的がそっちのけになってしまった。

長くなりすぎるのとレビューはレビューで一旦区切っておいたほうが利便性が良いかなと思うので今回はここで終わり。
次回、、、かどうかは分からないけどそのうち「VBA入門書の目次から考察するExcelプログラミングの特殊性」について書こうと思う。

VBA プロシージャのオーバーロード機能(もどき)を自作する

$
0
0

今回のネタは@mmYYmmddさんのつぶやきから生まれた。感謝。

さて、オーバーロードとは。
ふつう、Functionが取れる引数の数や型は固定されていてあまり自由が利かないのだが、引数の数や型によって処理を振り分けたい場合がある。
VariantやOptional、ParamArrayを活用することでこのような処理も可能であるが、一つの関数で処理を振り分けるとごちゃごちゃしてしまいメンテナンス性が落ちる。

これを解決するのがオーバーロードである。オーバーロードとは、引数の数もしくは型が違えば同名のプロシージャをいくつでも作成できる機能で、JavaC#C++などの言語には実装されている。
これを使えばあくまで別の関数として作れるので、複雑にならずにすむ。

残念ながら、VBAにその機能はない。そこで今回はオーバーロード(もどき)を実装してみたい。
業務コードでこれをすると複雑になるだけなので、ライブラリコードでしか使い道はないけど。

まず引数の型を判定するための関数をFunctionプロシージャで作成。

Function GetArgTypeString(ParamArray args())AsStringDim ret AsStringForEach x In args
        SelectCaseTrueCaseIsMissing(x): ret = ret &"M"CaseIsArray(x): ret = ret &"A"CaseIsObject(x): ret = ret &"O"CaseTypeName(x)="String": ret = ret &"S"CaseIsDate(x): ret = ret &"D"CaseIsNumeric(x): ret = ret &"N"CaseElse: ret = ret &"U"EndSelectNext
    GetArgTypeString = ret
EndFunction

この関数にいくつか引数を渡すと、文字列で型が返ってくる。
たとえば、1, #2017/3/5#, "Hello"の順で渡すと、NDSとなる。Number、Date、Stringである。

次に、たとえばAddという関数を作りたい場合、Addに先ほどの文字列をつけた名前で関数を作る。
タイプごとに4つ用意した。

Function AddSSM(a, b)
    AddSSM = a & b
EndFunctionFunction AddSSS(a, b, c)
    AddSSS = a & b & c
EndFunctionFunction AddNNM(a, b)
    AddNNM = a + b
EndFunctionFunction AddDNM(a, b)
    AddDNM = a + b
EndFunction

そして、渡された引数によって実際の関数へ処理を引き渡すための、窓口となる関数を作る。
これが本来呼び出したいAdd関数。

FunctionAdd(a, b,Optional c)Dim typeString AsString
    typeString = GetArgTypeString(a, b, c)Add= Application.Run("Add"& typeString, a, b, c)EndFunction

あとはメインコード。

Sub Main()
    Debug.PrintAdd(1,2)
    Debug.PrintAdd("1","1a")
    Debug.PrintAdd(#3/5/2017#,1)
    Debug.PrintAdd("A","B","C")EndSub

実行するとこのとおり。

 3 
11a
2017/03/06 
ABC

まぁ使い物になるかどうかはわからないが、私が類似の問題を抱えたときにアイデアの叩き台になればと思い、ここに残しておく。

念押しするが、アプリケーションコードでコレは余計複雑になるだけなのでやめておいたほうが賢明。ただライブラリを書く目的ならこういうのもアリかなと思う。

VBA Excel表をHTMLのtableタグに変換するマクロ

$
0
0

先日メインサイトにXlRgbColor定数の一覧を掲載した。

色順に並べたXlRgbColor定数の一覧表 - You.Activate

今回はこれを作る時に使用したExcelマクロを題材に、VBAExcel表からtableタグを出力する方法について記す。

まずHTML化する前のExcel表を用意しておく。

今回使用したのはこちら。
f:id:t-hom:20170306221642p:plain
※7色で画面から切れてるが、実際は137色ある。

これを、冒頭で紹介したようにWebページで表示させるには、HTML形式に変化しなければならない。
具体的には、Excel表を以下のようなHTMLに変換する必要がある。

<table><tr><th>No</th><th></th><th>名前</th><th>RGB値</th><th>説明</th></tr><tr><tdclass="col0">1</td><tdclass="col1"style="background-color:#800000; width:30px;"> </td><tdclass="col2">rgbMaroon</td><tdclass="col3">128, 0, 0</td><tdclass="col4">栗色</td></tr><tr><tdclass="col0">2</td><tdclass="col1"style="background-color:#8b0000; width:30px;"> </td><tdclass="col2">rgbDarkRed</td><tdclass="col3">139, 0, 0</td><tdclass="col4">濃い赤</td></tr><!--以下略 --></table>

Excel表をhtml化するWebツールもあったが、セルの色までは再現してくれなかったので自作方法を覚えておくと役に立つかもしれない。

さて、タイトルでtableタグに変換するマクロと書いたが、実際にマクロで出力するのはtableの中身である、tdタグ
tableタグとthタグ(テーブルのヘッダー)もマクロで作ることはできるが、それほど手間でもないので今回は手入力とした。

マクロと手入力の分担は以下の通り。

<!-- ここから手入力 --><table><tr><th>No</th><th></th><th>名前</th><th>RGB値</th><th>説明</th></tr><!-- ここまで手入力 --><!-- ここからマクロ --><tr><tdclass="col0">1</td><tdclass="col1"style="background-color:#800000; width:30px;"> </td><tdclass="col2">rgbMaroon</td><tdclass="col3">128, 0, 0</td><tdclass="col4">栗色</td></tr><tr><tdclass="col0">2</td><tdclass="col1"style="background-color:#8b0000; width:30px;"> </td><tdclass="col2">rgbDarkRed</td><tdclass="col3">139, 0, 0</td><tdclass="col4">濃い赤</td></tr><!-- ここまでマクロ --><!-- 以下略 --><!-- tableの閉じタグも手入力 --></table>

つまり実際にマクロでは、以下テンプレートの[No]、[色値]、[色名]、[RGB値]、[説明]をループで次々と変更しながら出力すれば良いということ。

<tr><tdclass="col0">[No]</td><tdclass="col1"style="background-color:#[色値]; width:30px;"> </td><tdclass="col2">[色名]</td><tdclass="col3">[RGB値]</td><tdclass="col4">[説明]</td></tr>

※tdタグにそれぞれclassを付けたのは、列ごとに後からフォントサイズを調整しやすくするため。

さて、ここから実際にマクロを作成していく。

検証段階ではとりあえずDebug.PrintでHTMLを出力させるが、イミディエイトウインドウへは200行までしか出力できず、切れてしまう。
後で簡単にテキストファイルへの書き出しに切り替えられるよう一旦WriteLineというプロシージャを作っておこう。

SubWriteLine(message)
    Debug.Print message
EndSub

こうすればマクロが完成した後にWriteLineの中身を書き変えることで出力先をテキストファイルに切り替えられる。
※こうした保守性の観点からのプロシージャ分割も重要。

次に今回はXlRgbColorの色見本を扱うので、テーブルのセルの背景色をそれぞれ個別に指定する必要がある。
出力するのはHTMLなので16進数の色表記を用いる必要があるが、Excel表では単なるRGBのLong値なので、これを16進数の文字列に変換するための関数もあらかじめ作っておく。

以下がその関数。RGB値を渡すと、HTMLで使用する16進数に変換してくれる。

Function RGBToHTMLColor(color_rgb)Dim r: r = color_rgb \ 256 ^ 0Mod256Dim g: g = color_rgb \ 256 ^ 1Mod256Dim b: b = color_rgb \ 256 ^ 2Mod256Dim hexR: hexR =Right("0"&Hex(r),2)Dim hexG: hexG =Right("0"&Hex(g),2)Dim hexB: hexB =Right("0"&Hex(b),2)
    RGBToHTMLColor ="#"& hexR & hexG & hexB
EndFunction

そして、出力したい表のあるエクセルシートのシートモジュールに以下を張り付けて実行すると、イミディエイトウインドウにHTMLが出力される。

Sub表をHTML化()For=2To138WriteLine"<tr>"For=1To5If=2ThenDim backGroundColor AsString
                backGroundColor = RGBToHTMLColor(Cells(,4).Value)WriteLine"<td class=""col1"" style=""background-color:" _& backGroundColor &"; width:30px;""> </td>"ElseWriteLine"<td class=""col"&-1&""">" _& Cells(,).Value&"</td>"EndIfNextWriteLine"</tr>"NextEndSub

注意:(私にとっては)単発ものなので、変数宣言もしてないしループ回数もリテラルべた打ち。良いコードとは言えないので、こちらを参考に汎用的なマクロを作る場合は適宜きちんと書いて。

さて、イミディエイトウインドウでは200行以上出力すると古いものから順に消えてしまうのでテキストファイルへの出力に変更したい。
ここでFileSystemObjectを使用するので、Microsoft Scripting Runtimeを参照設定しておく。

そしてWriteLineを以下のように書き換える。

SubWriteLine(message)WithNew FileSystemObject
        Dim ts As TextStream
        Set ts =.OpenTextFile("c:\work\writeline.txt", ForAppending,True)
        ts.WriteLine message
        ts.CloseEndWithEndSub

※みなさんが利用する場合、パスは適宜変更が必要。また、追記モードで開くので試行錯誤する場合はいったんファイルの中身を消す必要がある。

これでtableの中身がテキストに出力されるので、あとは手入力のtableタグの中に張り付けるだけ。


Pythonスタートブックのレビュー ~徹底して比喩表現にこだわった良書

$
0
0

先日から気になっていた書籍 Pythonスタートブックを買ってきたのでレビューしようと思う。

Pythonスタートブック

Pythonスタートブック

ただ、Python学習の学習が目的ではなく、あくまで「プログラミング入門」として読んでいる。
プログラミング初心者に対してVBAの解説を行う際のヒントにするためだ。

Amazonレビューではかなり高評価が多く、唯一の星1つはKindle版がうまくダウンロードできなかったという書籍の内容とは関係のないものだ。(2017年3月7日時点)

星3つの中には「テーマが時代にそぐわず面白くない。説明がくどい、うざい」という辛口レビューも見受けられたがこれは上級者の意見。上級者にとって自明の事実まで親切丁寧にかかれているのでお節介に感じるのだろう。

逆に難しい・わからないという意見は見当たらず、プログラミング初心者からは概ね5つ星。ということは、まったくの初心者をターゲットにした書籍としては大成功ということになるか。

さて、本書であるが、とにかく徹底して比喩表現を用いている。

私は比喩によるミスリードがますます初心者を混乱させるという例をいくつも知っているので、最近は比喩表現の使用に慎重だった。ひとつ例を挙げると、以前別の方にプログラミングを教わったけど今ひとつ分からないという方に、「変数って浮いてるんですよね?」って質問されたことがある。

浮いてるって。。どこに!?
たぶん箱モデルでうまく伝わらず、教える方もメモリに記録されているということを知らないかあるいはうまく説明できず、目に見えないけどどこかに浮かんでいるといった曖昧な説明でお茶を濁したものと思われる。

浮いているという説明でますます混乱されていたので、メモリの仕組みからきちんと説明したところスッキリ理解していただけた。

このような体験があるため、私は比喩表現を避け、徹底して「事実・実態」の説明に拘るようになった。

ただ最近、事実に拘るあまり逆に難しくて分かりにくい解説になっているのではないかと思うことがある。

比喩表現には誤解のリスクもあるが、初めて出会う概念を既知の概念と関連づけて理解できるという優れた一面もある。適切に用いれば極めて有効な説明の手段である。いたずらに敬遠せず、適宜活用していこうということを悟ったのである。

それでPythonスタートブックである。もう一度言うが、とにかく徹底して比喩表現を用いている。ごてごての比喩表現。

つまりアンチ比喩に大きく振り切れた針をもとに戻すには、絶賛比喩中のこの書籍がよい薬になるのではないかと思って買った。

この書籍ではまずプログラミングの構成要素を、「道具と材料」に見立てて解説している。
以下に一部引用する。

たとえば、木製のイスを作ろうと思ったら、材料になる木と釘、道具としてはノコギリや金槌が必要です。

プログラミングにおいて基本となる材料とは、文字列や数字といった”データ”です。

Pythonにもプログラミングに役立つ道具がたくさんあります。その1つが、関数(かんすう)と呼ばれるものです。

ためしにプログラミング未経験者にこの道具+材料でプログラミングというものを説明してみたところ、すこぶる理解が良い。なるほど、これが比喩の力か。

まず初心者にとって、変数とか関数とか言われてもどれがどれか結びつかないのだ。何せ覚えたての言葉を使って初めて見るコードの構造を把握しようとしているのだ。

f:id:t-hom:20170308002345p:plain
無理とは言わないが難しいことである。

よく慣れ親しんだ言葉でまずはざっくりとプログラムの構造を把握するという手法は有効だと思う。
f:id:t-hom:20170308002422p:plain

道具+材料の表現はこれからもちょくちょく活用していきたい。

あとfor文、While文、If文、エラー処理などの制御を電車の路線に例えているのは面白かった。

この書籍は、徹底した比喩表現にこだわった良書だと思う。

あとがき

なんでまたPythonの本を手に取ったのかというと、ツイッターでコーヘー氏とインストラクターのネタ帳の伊藤さんがPython入門書を褒めてたのを見たので、そんなに良いなら私もソレ買って読もうと思って。

ところが完全に記憶違いで、別の本を買っちゃったという話。

お二人が良いといってたのはこちらの書籍。

独習Python入門――1日でプログラミングに強くなる!

独習Python入門――1日でプログラミングに強くなる!

たしかなんかカラフルな表紙だったなーという程度の記憶でAmazonPython入門書を検索して、「ほう、たしかに評価が高い、これに違いない」という顛末。

まぁ結果的には正解だったと思っている。
特にアンチ比喩に傾きつつあった自分には良い薬になった。

マクロ作成を安請け合いしないという選択

$
0
0

「こんだけVBAの記事を書いておいてよくもまぁ」という声が聞こえてきそうなタイトルである。
しかし安請け合いがよくないというのは常々思っている。

執筆のキッカケになったのはこの記事。
akashi-keirin.hatenablog.com

以下、序文の引用。

最近、

自分には一文の得にもならない、純粋に他人のためのマクロ

を作らされることが多くなって

これを読んで思わず、

。。これ、プログラマーの仕事じゃね?

と突っ込んでしまった。
ここで言ってるのはプログラマーを組める人全般のことじゃなくて、プログラムを開発する目的で正式に雇用された正規プログラマーのこと。

どうやらドツボにはまってしまったようだ。

私も似たような経験をしたことがある。ただ私は業務出向という形態で、出向先の組織が何度か大きく変わったので、幸いにも私はリセットする機会があった。しかしあまりマクロ作成を安請け合いしてしまうと確かにそういうことになるというのは良くわかる。

ただ私の考えは上で紹介した記事ほどマクロ作成を請け負うことを忌避しているわけではない。

まずいのはあくまで「安請け合い」であると考える。

本質的な問題は、依頼者が「減るもんじゃあるまいし、ケチなこと言うな」と考えている点だ。

時にマクロを作る側でさえ「減るもんでもないし、できるんだから、やってあげよう」と考えることがある。

「減らない」という考え。

ここにすべての元凶があるのではないだろうか。

そもそも他の皆が自分の好きなことや遊びに費やしている時間を貴方はプログラミングの習得に費やしたのだ。他の皆が自分の好きなことや遊びに費やしている給料の一部を貴方はプログラミングの書籍購入に充てたのだ。

今貴方が手にしているその能力は、すでに自分の時間やお金を費やした結果である。

もちろんそれだけではない。プログラミングというのは頭脳労働なのだ。実際、頭脳労働は気力を消費する。ゲームでも魔法を使うとMPが減るだろう。気力は定量的に計ることはできないけれど、確かに減るのだ。要するに疲れるのだ。

知識や技術、気力という見えないコストをきちんと理解してもらうためには、元手はタダではないと何度も根気よく説明しなければならない。

普段から、「さっさと帰る日は書店に立ち寄って勉強しているのだ」と吹聴しておこう。実際にしている日も、そうでない日も。
依頼仕事が遅れているときは、「昨日家でもいろいろ考えてたけれど、なかなか思うようにできないのだ。もう少し待ってほしい」と詫びておくとよい。実際に苦労していても、完成の目途が立っていても。

私は単に趣味と一致しているのでプログラミングの学習に費やす時間を苦労とは思ってないけど、実際に見えないところにコストがかかってるのは嘘ではないので、「あいつは見えないところで苦労している」という風に見せておかないと、「能力があるのにケチな奴だ」と思われる。

何の努力もせずに評価されないのはともかく、実際にやってるのだから、どんどんアピールして良い。
自慢のためではない。コストを理解してもらうためである。早く帰っても恨まれないためである。

勘違いしてほしくないのは、極力出し惜しみをしろと言いたいわけではない。

私だって毎日の3時間の仕事が5分になるって言うなら喜んで手伝う。それで皆が楽になるなら大したことではない。
価値ある仕事をしたときは、それ自体が心理的報酬になる。

しかし人間はどこまでも楽をしたがる生き物である。
「マクロってすごい。」こんな風に一度魔法を見てしまうと、ひょっとしてあの仕事も楽になるのではないかと次々とアイデアが浮かぶようになる。次第になんでもかんでも自動化したがるようになる。たとえ、ひと月に1度、30分で終わる仕事であっても。

その自動化は、自分が費やしてきたもの・これから費やすであろうものに見合った価値ある仕事だろうか。
傍から見てそれほど大変な仕事に見えない場合、単に依頼者が楽をしたいために貴方に甘えているだけかもしれない。
何でもかんでも引き受けるわけではないと、お断りしたほうが良い。あるいは自分の抱えている別の仕事と引き換えに引き受けるという手もある。

いずれにしても、つまらない仕事は安請け合いしないことである。請ければ依頼人は喜ぶが、貴方は内心つまらないと思っているのでイライラする。精神的にもよくないし、そのような仕事はたぶん、ビジネス的にも特にメリットはない。

変数・関数・オブジェクトが「いまひとつ分からない」という感覚の正体を考察

$
0
0

プログラミングでは日常生活であまり耳にしない言葉がたくさん出てくる。

たとえば、変数・関数・オブジェクトなど。

説明を聞いてスッと理解できる方もいれば、なかなか意味が分からずに苦労したという方もいるだろう。

さっぱり分からないということはない。そして使ってみろと言われて使えないわけでもない。…にもかかわらず、いまひとつ分からない。なんかもやもやする。スッキリしない。

では一体なにが分からないのだと聞かれても困る。なんだかよく分からないとしか答えようがない。そんな経験はないだろうか。

というか、多くの人がこのように感じながら日々使っているうちに慣れてしまった方が多いのではないかと思う。

そして他の人に教えるときも「慣れるしかない」などとしたり顔で言ってしまうんだ。

でも、本当に慣れるしかないのだろうか。

「慣れるしかない」というのは、場合によっては自分がうまく説明できないのを誤魔化したように聞こえることがある。完璧に説明できなくてもいいから、もう少し納得度の高い答えを示せないだろうか。

それで私はここ数日、この「いまひとつ分からない」という感覚の正体をもう少し正確に言語化できないかと悩んでいた。まず敵を知らなければ対処のしようがないからだ。

それでいろいろ調べていたところ、認知心理学の分野で「概念形成」という言葉を見つけた。

何かを理解したというのは、言い換えると頭の中で概念が形成されたということで、「いまひとつ分からない」というのはつまり、概念形成がうまくできていないという状態なのではないか。

ではどうすればうまく概念を形成することができるのか。
その前に、概念という言葉をはっきり定義しておこう。概念を形成しようというのだから、肝心のそれが何なのかはっきりわかっておく必要がある。

ここで国語辞典を取り出してくる。

岩波国語辞典より

概念-同類のものに対していだく意味内容。
ア)同類のもののそれぞれについての表象から共通部分をぬき出して得た表象。
イ)対象を表す用語について、内容がはっきり決められ、適用範囲も明確な、意味。
ウ)俗に、複雑なものに対する大まかな認識内容のこと。

このうち(ウ)の意味で理解している方も多いと思うが、今回扱うのは(ア)と(イ)である。

で、またここでややこしい言葉が。
表象(ひょうしょう)とは。。。

岩波国語辞典より

現在の瞬間に知覚してはいない事物や現象について、心に描く像。イメージ。

い、、一応知覚も調べておくか。。

岩波国語辞典より

感覚器官を通じて、外界の事物を見分け、とらえる働き。視覚・聴覚・嗅覚・味覚・触覚など。

つまり、たとえば「変数」に対する概念を形成するとは、数ある「変数」に対する脳内イメージのうち、共通するイメージを抜き出したものを脳内に形成するということを指す。

よく変数は箱に例えられる。私は箱だと説明されて「なるほど箱か」と何も疑問に思わなかったので良かったけれど、一定以上の人がこの説明で躓くらしい。

そこでしばしば、「変数=箱」モデルはあまり上手い例えではないとの指摘がなされる。そしてメモに例えたり、名札に例えたり、実際にどうなっているのかメモリの動きを説明したりという試みが行われている。

しかし先ほどの概念形成の定義を考えると、どれが良いということではないような気がしてきた。

もう一度言うと、「変数」に対する概念を形成するとは、数ある「変数」に対する脳内イメージのうち、共通するイメージを抜き出したものを脳内に形成するということを指す。

変数とは箱であるという説明をいくら繰り返しても、共通するイメージは「箱」そのものである。一方、メモであるという説明を繰り返しても、共通するイメージは「メモ」そのものである。

箱という説明で躓いた人がメモという説明でなるほど!となるのは、メモに例えるのが箱に例えるよりも優れているということではなく、複数の事例を見ることで共通する特徴を捉えやすくなるからだろう。

変数がハコにもメモにも似ているのであれば、まず消去法で変数が立方体である可能性は消える。

では具体的なメモリの動きを説明するのはどうか。複数の人に説明してみて、これは変数の概念を形成するのに極めて効果的だった。しかしこれも、ハコ・メモといった既存の説明を受けた後でのことであるから、メモリの説明単体で変数を説明するのは難しいかもしれない。

それに変数におけるメモリの働きはたまたま実装がそうなっているということにすぎず、変数の本質かと言われるとやっぱり違う気がする。

プログラミングにおける変数とは本来、色も形もない純粋な概念なのだろう。だから人に説明するにはハコやメモといった比喩や、実装という現実を使って、事例の形で言語化するしかない。

よく分からなくても使っているうちに慣れるというのは、つまりコーディングを通じて脳内にたくさんの事例をため込めば自然と概念が形成されるということ。

だから、「いまひとつ分からない」から「はっきり理解できる」に移行するために「慣れる」という手段は有効であるが、「慣れるしかない」わけでもない。

いろんな切り口から説明すれば共通項が概念として形成されるし、たとえ話だけで理解されない場合は具体的な実装に踏み込んでみるとスッキリ理解していただけることが多い。

それでもうまく説明できないときは、自分の力不足であることを認めたうえで、申し訳ないけれど実際に使いながら慣れてくださいというと好印象だと思う。

VBA 変数で躓いた方に贈る、くどいくらい丁寧な変数の説明

$
0
0

さて、今回は初心者向けの記事なので、本文を「ですます調」で書くことにする。
途中でこのように緑文字でコメントを入れる。コメントはいつもどおり「だ・である調」で書く。本文は黒文字とする。

プログラミングでは「変数(へんすう)」という道具を利用して一時的にデータを保存することができます。

数学でも変数という用語が出てきますが、いったんそれは忘れてください。数学では不定値を仮に「x」とするなどの使われ方をしますが、プログラミングで変数はデータの保存に使う道具なので、別物だと割り切って考えましょう。

プログラミングで変数に保存するのは「数」に限らず、「人の名前、物の名前、セリフ、日付、正しいか間違いかの判定結果」など様々です。

データを保存するには、「変数名 = データ」の形で記述します。ここでも、数学の「イコール」は一旦忘れてください。このイコールは「この変数にこのデータを保存してください」というコンピューターへの指示に使う記号です。

ではやってみましょう。
「こんにちは」という文字を「あいさつ」という変数に入れるには、このように書きます。

Subサンプルマクロ()あいさつ  ="こんにちは"EndSub

「え?」と思われた方もいるでしょう。変数って「x」とか「y」のことじゃないの?と。いえ、数学の変数とはもはや別物で、好きな名前をつけることができます。英数字しか使えないプログラミング言語が多いので一般的には「greeting」といった英語で命名することが多いですが、VBAでは漢字仮名も使えるので別に日本語の名前をつけても構いません。

変数は人が読んで意味の分かる名前を付けることが推奨されています。

さて、保存した値を利用するには、単に値の代わりに変数を書くだけです。保存と合わせてやってみましょう。

Subサンプルマクロ()あいさつ ="こんにちは"MsgBoxあいさつ
EndSub

これで「こんにちは」というメッセージを表示させることができました。でもこれだと何が嬉しいのかわかりませんよね。だって最初からこう書けばいいじゃないですか。

Subサンプルマクロ()MsgBox"こんにちは"EndSub

では次の例をみてみましょう。

Subサンプルマクロ()
    yourName =InputBox("あなたの名前を入力してください。")MsgBox"こんにちは"& yourName  &"さん"EndSub

このマクロは、ユーザーに名前を入力させてその結果を表示します。

以下のように変数を使わずに書くこともできますが、なんだか長ったらしくなって読みにくいですよね。

Subサンプルマクロ()MsgBox"こんにちは"&InputBox("あなたの名前を入力してください。")&"さん"EndSub

それに、以下のように名前を2回表示させようと思ったら、やっぱり変数を使うしかありません。

Subサンプルマクロ()
    yourName =InputBox("あなたの名前を入力してください。")MsgBox"こんにちは"& yourName  &"さん。"MsgBox yourName  &"さんはお元気ですか?"EndSub

もし変数がなければ、名前を表示させるたびに尋ねないといけませんから。

このように変数は、「データを保存する道具」であると理解してください。そしてデータを保存することを、専門的には「代入」といいます。これも数学から用語だけ持ってきたような代物ですので、「代入」と聞いたら、「ああ、保存することだな」と思っていただければ結構です。

また、これからはデータのことを「値」と呼びます。数学用語ですが、プログラミングでは数値だけではなく、先ほどのように"こんにちは"という言葉も値です。

ですから、「変数にデータを保存する」ことを、一般的には「変数に値を代入する」といいます。数学チックな言い方なので嫌な思い出のある方もいるでしょうけど、書籍などではこの言い回しが一般的なので、慣れてください。所詮、数学とは別物です。

コメント
ここまで変数の本質的な説明を行ってきたので、いまさら箱だのメモだのといった説明は蛇足かもしれない。というか変数には形も色もないのだけれど、「具体的に変数とは何なんだろう」という余計な疑問を呼び起こし、混乱を増長させるような気がする。
ただ前回書いた「概念形成」の話を加味すると、実験的に一旦ここでは箱とかメモとかタグとか、色々な説明を混ぜておきたい。
ここまでの説明で十分納得された方は、「混乱を避けるために続きを読まない」という選択肢もあることにご留意いただければと思う。

変数はよく、箱のようだとか、メモのようだと説明されます。

たとえば「あいさつ」と書かれた箱に「こんにちは」という言葉が入っているところをイメージしてください。一度箱に保存すれば、いつでも中身を確かめることができます。また、中身を入れ替えることもできます。

こんな風に書くと、最初は「こんにちは」、つぎに「こんばんは」が表示されます。

Subサンプルマクロ()あいさつ ="こんにちは"MsgBoxあいさつ
    あいさつ ="こんばんは"MsgBoxあいさつ
EndSub

最初に保存した「こんにちは」は上書きされて無くなってしまいます。この点、サイズの許すかぎり何個でも入る「箱」とは違いますね。

次に、「あいさつ」とタイトルが書かれたメモ用紙に「こんにちは」と書き込むところをイメージしてみましょう。実際のメモは余白が許す限り何個でも書けてしまいますが、実際の変数は上書きされますから、たとえ話はあくまでたとえ話だということを理解しておいてください。

ネームタグに例える説明もあります。たとえば、「あいさつ」と書かれたタグが、「こんにちは」という文に括り付けてあるイメージです。紐をほどいて、「こんばんは」に括りなおすこともできますね。このたとえはなかなか秀逸で、オブジェクト変数にもそのまま使えます。

しかし、いずれにしても所詮たとえ話なのでそこは割り切っておいてください。変数の本質は「データを保存する」という機能であり、何か具体的な色や形があるわけではないのです。

さて、変数のことがなんとなく理解できましたか?
正直いまひとつピンとこないという方も多いと思います。

ではもう少し具体的な話をしましょう。
具体的に、変数に保存したデータはどこにあるのでしょうか。
それは、コンピューターのメモリです。


コメント
またここで、実装に踏み込んだ説明をするべきかどうか悩むが、とりあえず書く。上記の説明でスッキリ理解できている人は読まなくても良いけど、逆にたとえ話で混乱が深まったという方には良い処方箋になるのではないかと思う。

メモリにはアドレス(番地)がついていて、たとえば4GBなら、0番地~21億4748万3647番地まであります。
このどこかに保存されているのです。10桁の数値で表すことができますね。

たとえばこんなプログラムがあったとします。

Subサンプルマクロ()数値1=20数値2=10=数値1+数値2=数値1-数値2=数値1*数値2=数値1/数値2MsgBox数値1&" + "&数値2&" = "&MsgBox数値1&" - "&数値2&" = "&MsgBox数値1&" * "&数値2&" = "&MsgBox数値1&" / "&数値2&" = "&EndSub

するとコンピューターは変数名とその保存場所を示した表をメモリに作り出し、実際の値はメモリ上に保存されています。
メモリのどこに保存されるかはコンピューターが管理しているので、こちらでは指定できません。

たとえばこのあたりだとしましょう。

変数名アドレス
数値12486820
数値22486788
2486756
2486740
2486724
2486708

VBAでは直接メモリのアドレスに値を入れたり取り出したりすることはできず、変数を介して間接的にアクセスするしかありませんが、仮に変数が存在せずアドレスを指定できるとしたら、以下のようなプログラムになるでしょう。

Subサンプルマクロ()
    a2486820 =20
    a2486788 =10
    a2486756 = a2486820 + a2486788
    a2486740 = a2486820 - a2486788
    a2486724 = a2486820 * a2486788
    a2486708 = a2486820 / a2486788
    MsgBox a2486820 &" + "& a2486788 &" = "& a2486756
    MsgBox a2486820 &" - "& a2486788 &" = "& a2486740
    MsgBox a2486820 &" * "& a2486788 &" = "& a2486724
    MsgBox a2486820 &" / "& a2486788 &" = "& a2486708
EndSub

すごく分かりにくいですよね。
我々プログラマーは、データを保存して、再利用したいだけなのに、いちいちどこに保存するか番号で指定しないといけないなんてうんざりしますよね。ですから、保存場所の管理はコンピューターに任せて、我々はデータを名前で呼びましょう。

これが変数です。

さて、ここから先にまたオブジェクト変数という手ごわい奴がいますが、これは別の記事で触れているので割愛します。

さらに詳しいメモリの働きについては、こちらをご参照ください。
thom.hateblo.jp

上記を理解したうえで、オブジェクト変数について以下の記事を読んでいただけるとよく理解できるのではないかと思います。
thom.hateblo.jp

以上。

あとがきコメント
今回の記事は前回書いた以下の考察を実際の説明に生かしてみようと思って書いた。

thom.hateblo.jp


しかし、たとえ話を切り出すタイミングが難しく、それほどうまくいったとは思っていない。
むしろ本質的な変数の機能の説明の後にあえてたとえ話を持ち出す必要もなかったかなという思いが強いのだけれど、何かしらイメージとして形と結びついていたほうが記憶にとどまりやすいかもしれないという考えもあり、結局、形をもつ箱やメモ、タグなどのたとえを紹介することにした。
もう少し表現や説明順序を洗練させていきたいと思う。

VBA マクロが遅い・速いという議論は、要件ありきの話

$
0
0

VBAのコードについて、よく、この手法は遅いから使うなという話を聞く。高速化万歳!
またはその逆で、高速化のためにわかりやすさを犠牲にするなどナンセンスだ!という話も聞く。

この記事では前者を「$バンザイ」、後者を「$ナンセンス」と呼ぼう。

$バンザイと$ナンセンスはどちらも自分の考えが正しいと信じており、否定しようものなら青筋を立てて反論してくることだろう。
問題はそこにある。あまりにも感情移入しすぎている。

私が思うに、$バンザイと$ナンセンスはどちらも正しくない。個々の要件、シチュエーションが考慮されていないからだ。

$バンザイは、マクロが高々1分速くなったところでビジネス的には大して変わらないという観点が抜け落ちている。
実行に15分かかる処理が14分になったところで、気づきもしないだろうし、そのような高速化にあまり意味はないと思う。
また、月に1度しか発生しない作業なら、2分を1分に短縮したところで高々知れている。
$バンザイは、そのような高速化のためにコードをひどく分りにくくしてしまうことがある。これは全くもってナンセンスだ。

一方で$ナンセンスは、たった1分でも人間は待たされることを苦痛に感じ、ストレスを溜める生き物であるという観点が抜け落ちている。
$ナンセンスは、「業務上の影響」だけを意識するため、5分も10分も変わらないじゃないかと思うようになる。確かに月に数回ならね。
しかし毎日何回も繰り返し実行するマクロでは、5分と10分の差はとてつもなく大きい。
5分と6分で感じるストレスは大差ないかもしれない。しかし1分と1秒で感じるストレスは、1分のほうが遥かに大きい。

3秒と10秒の差も無視できない。一瞬と1秒の差も大きい。

打てば響くようにキビキビ動くマクロは使っていて気持ちいいものである。仕事のリズムが良くなり、集中力が増す。
$バンザイの主張にもうなずけるものがある。

このように、マクロが遅い・速いという議論は、ユーザーが求めるスビード感(つまり要件)ありきの話なのだ。だから、個々のコーディングテクニック、作法を指して、これは遅いから絶対ダメ、これは分りにくいから絶対ダメという主張は鵜呑みにしないほうが良い。


さて、私は最近「ハッカーと画家」という書籍を読んだ。

どの章も面白かったが、特に気に入ったのは第11章「百年の言語」だ。

その中でも格別に素晴らしいと感じた箇所を引用する。

インフラが整った現在では、長距離電話をかけている最中に時間を分刻みで気にするなんて些細なことのように思えてくる。資源があれば相手がどこにいようと電話をかけるというのはひとつの均一な行為だと考えるほうがエレガントだ。
良い無駄と悪い無駄があるってことだ。私は良い無駄のほうに関心がある。贅沢に使うことで、より単純なデザインが得られるような無駄だ。新しい、速いハードウェアのマシンサイクルを無駄に使えるというチャンスをどんなふうに利用できるだろう。

本当の非効率性とは、マシンの時間を無駄にすることではなく、プログラマの時間を無駄にすることだ。コンピュータが速くなればなるほど、このことははっきりしてくる。

※マシンの時間とは、おそらく我々の考える「時間」を指して言っているのではない。CPUやメモリの使用率の話だと思う。

私もこの意見に同意する。これは$ナンセンスが言う、5分も10分も変わらないという意見ではない。
時代の進歩とともに、それはやがて1分になり、30秒になり、一瞬になるだろうという話である。

$バンザイが目指す高速化至上主義が時代によって自動的に解決されるのであれば、これからのプログラミングはわかりやすさ、メンテナンスしやすさ、プログラマの効率にフォーカスを当てたものになるだろう。

だから、原則としてはより抽象化されたメンテナブルなコードをデザインすべきだと思う。
$ナンセンスはここでしたり顔で「ほらね」というかもしれない。

でもそれは違う。

現代においては依然としてそのような高速化は実現されておらず、たとえばExcelVBAではセルを個別に読みにいくような処理はすこぶる遅い。
そこで、配列転記のような高速化テクニックが必要になってくるわけだ。

でも最初から高速性ありきで考えるのではない。
まずはエレガント!!なグランドデザインがあり、求められるパフォーマンスが出ないときのチューニングとしての高速化テクニックを使用するのだ。

マクロの速度に関する議論は要件ありき。ただし、高速化テクニックはあくまでパフォーマンスチューニングであることに留意したい。コンピューターの、あるいはExcelの過渡期における必要悪である。決してそれがグランドデザインであってはいけない。

Viewing all 493 articles
Browse latest View live