GUIはどーなってるの
Excel VBAの話の続きです。Excelの操作性とかそういう話ではなく、GUIプログラムはどういう構成はどうなっているか、という話です。
GUIプログラムにおいて、最初に気になる点は次の4つではないかと思っている。
- コンポーネントのディスパッチャー(イベント送出者)は誰か?
- イベントハンドラは、どのように定義/実装されるか?
- イベントディスパッチャーとイベントハンドラはどのように関係付けるか?
- スレッドモデルは何か?
1番はあまり意識する必要がないので、意味が分からないかもしれないが、早く言うとイベントの発行したオブジェクトは何か、ということである。たとえば、Java GUIプログラムの場合、ボタンを押したならButtonオブジェクト自身ということになる。通常は、ボタンを押したりテキストボックスに値を入れたりしたときは、そのコンポーネント自身がイベント発行者になる。
2番は、JavaでいうところのListenerである。Listenerインターフェイスをimplementしたクラスがイベントハンドラになる。(正確にはイベントハンドラの候補になる、といったほうが良いのかも)
3番目は、イベントハンドラをディスパッチャーに登録する方法である。Javaなら、addXxxListenerでイベントハンドラとディスパッチャーを関連付ける。
4番目は、イベント処理のスレッドモデルである。Java Swingならシングルスレッドである。最近のGUIライブラリなら、大抵はシングルスレッドだろう。
では、Excel VBAはどうなっているだろうか。
1→イベントを発行したコンポーネント自身
2→イベントを発行したコンポーネントが張り付いている、一番親のコンポーネント(通常はシートオブジェクトになる)
3→イベントを発行したオブジェクト(の親オブジェクト)がハンドラになっているから、最初から関連付いている
4→たぶんシングルスレッドだと思われ
なんとまぁ、MFCと同じでつか。特にイベントハンドラがディスパッチャーと同じだから、処理が一箇所にかたまってしまう(平たく言うと、処理がみーんなシートオブジェクトのモジュールに書かれてしまう)。だから、下手なExcel VBAプログラムは、処理がみーんなシートやブックのモジュールに固まっていることで見分けられたりする。…いちよう断っておきますが、これはExcel VBAをアプリケーションで開発する場合での話であって、ちょっとしたツールやドキュメントの整理のために作るExcel VBAは、この話の範囲ではないです。この手のツールは、早く作ることが重要であって、プログラムの見通しの良さは2の次になりますから。
話し戻して、とにかく処理が一箇所に固まってしまうので、何も考えないで作るとプログラムの見通しが悪くなる。ボタンを押してセルになんか色を付ける処理と、ボタンを押してグラフを描く処理が同じモジュールに書けば、そりゃみにくくなるでしょ。
で、この対策だが、やはり処理を委譲するしかない。MFCならCWnd::DefWindowProc()とかそんなメソッドをオーバーライドして、メッセージマップに逝く前に、処理をかっさらったりすることも出来るが、VBAはムリ。だから、とりあえずイベントハンドラはシートモジュールに書いておいて、すぐさま別の(標準)モジュールの関数に処理を渡してしまう(委譲)という書き方になるだろう。が、ここで問題が…
前回も書いたが、Excel VBAは構造化プログラムに近く、モジュール志向であるということだ。つまり、データの受け渡しが苦手/面倒臭いということだ。必要なデータはみんな関数の引数に渡してやる必要がある。だから関数の引数が異様に増える傾向になる。昔なつかしきC言語のようなプログラムだ。だからC言語と同じ過ちがVBAでも繰り返されることになる。つまりグローバル変数地獄だ。だから下手なVBAはグローバル変数が多くなる。
話が散漫になってしまったが、何が言いたいかというと、Excel VBAはGUIプログラムに向かないということだ。経験者にしか分からない例えだが、C言語でX Windowシステムのプログラムを書くのに似ている、ということだ。ちなみに私はX Windowプログラムなんて書いたことないですよ^ ^;
Excel VBA 事始め
他言語で開発経験を積んでいると、Excel VBAが非常にとっつきにくい。実はExcel VBAを『開発言語』と捉えると、とても奇特な環境でなのである。というのは、Excel ワークブックというのは、
- 開発用のエディタ内蔵
- デバッグ環境
- 配布用実行ファイル
の3つを同時に満たしているからだ。通常の開発言語は、『統合IDE』と称して上2つが、一緒になっていることがあるが、さすがに配布ファイルも一緒になることはない。しかし、Excelはそうなるのだ。これは、自分の書いたプログラムも一緒に配布されてしまう、ということを意味する。いちようコードは暗号化できるので、利用者側に見せないようにすることはできるが、なんとも気持ち悪い感じがしてしまう。
また、Excel VBAの特異な点については、ユーザー環境で確実に実行される保証が無い、ということだろうか。これは、1度は見たことがあると思うが、VBA
付きのファイルを開くと、マクロがありますが実行しますか? というダイアログが表示されてしまうことだ。これでNoを選択されてしまうと、どうにもならない。よく、マクロを強制的に実行させる方法はありませんか、と聞かれるが、こればっかりはどうにもならない。
Excel VBAを秀丸マクロのような感覚で利用する分には便利なんだろうけど、ひとたび『アプリケーション』規模で『開発』してしまうと、これほど厄介なものは無い。あぁ、なんでこんなんでアプリケーションを開発しようとか考えるのかな。そりゃ、実行環境を考えなくて良い(どこでもExcelはインストールされているだろうから)からなんだろうけど…
攻略サイトをねこぞぎ奪う
こんなん作った。かなり手抜きだけど
require 'net/http' require 'socket' require 'URL.rb' exit if ARGV.length != 1 def mkdir(dir) Dir.mkdir(dir) unless File.exist?(dir) end def parse_html(url) puts url.to_s tokens = [] begin Net::HTTP.start(url.domain, 80) { |http| str = "" response = http.get(url.site) response.body.split(/([<>])/m).each { |t| case t when /</ tokens.push(str) if str != "" str = t when />/ tokens.push(str + t) str = "" else str += t end } tokens.push(str) if str != "" tokens } rescue puts "[" + url.to_s + "]取得失敗..." end end def put_html(url, tokens) anchor = [] open(url.filename, "w") { |f| tokens.each { |token| case token when /<link/i elements = parse_tag(token) if elements["rel"] == "stylesheet" resource_url = url.make_url(elements["href"]) load_resource(resource_url, "css") elements["href"] = "./css/" + resource_url.filename f.print "<link" + make_tag(elements) + ">" else f.print token end when /<img/i elements = parse_tag(token) resource_url = url.make_url(elements["src"]) # 同一domainのときだけ、リソースを取得する if resource_url.domain == url.domain load_resource(resource_url, "img") elements["src"] = "./img/" + resource_url.filename f.print "<img" + make_tag(elements) + ">" end when /<a/i elements = parse_tag(token) if url.level < 1 && elements.include?("href") anchor_url = url.make_url(elements["href"]) if anchor_url.domain == url.domain anchor.push(anchor_url) elements["href"] = anchor_url.filename f.print "<a" + make_tag(elements) + ">" else f.print token end else f.print token end else f.print token end } } anchor end def parse_tag(str) elements = {} str.scan(/\w+\s*=['"].+?['"]/).each { |token| elements[token.split("=")[0].strip.downcase] = token.split("=")[1].gsub(/['"]/, "").strip } elements end def make_tag(elements) str = "" elements.each { |key, value| str += " #{key}=\"#{value}\"" } str end def load_resource(url, type) open((type.nil?? "" : "./#{type}/") + url.filename, "wb") { |f| Net::HTTP.start(url.domain, 80) { |http| f.print http.get(url.site).body } } end Net::HTTP.version_1_2 # おまじない urls = [URL.new(ARGV[0])] mkdir("img") mkdir("css") urls.each { |url| tokens = parse_html(url) urls.push(put_html(url, tokens)) urls.flatten!; urls.compact! }
URLクラスはこんなんです
class URL def initialize(url, level = 0) @level = level url = url[("http://".length)..-1] if url.length >= "http://".length && url =~ /^http:\/\// @str = url.split(/\//, -1) @str.push("") if @str.length == 1 @str[-1] = @str[-1].split(/\?/)[0] if @str[-1].include?("?") end def domain @str[0] end def site "/" + @str[1..-1].join("/") end def filename if @str[-1] == "" "index.html" else @str[-1] end end def level @level end def make_url(url) case url # 完全URL when /^http:\/\// URL.new(url, self.level + 1) # 絶対URL when /^\// URL.new(self.domain + url, self.level + 1) # 相対URL else URL.new(@str[0...-1].join("/") + "/" + url, self.level + 1) end end def to_s @str.join("/") end end
Windowsでバックアップをとりたい
環境:Windows XP。mswin版のRuby 1.8.2がインストールしてある
やりたいこと:ローカルドライブ(Dドライブ)にある、あるフォルダ以下をネットワーク上にあるコンピューターのあるフォルダにコピーする。そのとき、そのネットワーク上のコンピュータのフォルダをアーカイブにしてzip圧縮しておく。つまり、
D:\Hoge\ ← バックアップもとのフォルダ D:\Hoge\aaa\ ← バックアップ対象 D:\Hoge\bbb.xls ← バックアップ対象 \\Foo\Piyo\ ← バックアップ先のフォルダ \\Foo\Piyo\aaa\ ← 前回バックアップしたフォルダ \\Foo\Piyo\bbb.xls ← 前回バックアップしたファイル \\Foo\Piyo\backup_20050830.zip ← 前々回バックアップしたファイル/フォルダ これを \\Foo\Piyo\ 以下を圧縮して(zipは除く)backup_20050831.zip としてこのフォルダに保存。 zipにしたファイル/フォルダは削除 D:\Hoge\ 配下を \\Foo\Piyo\ にコピー
である。これをWindowsのコマンド(DOSコマンド、か?)とmswin版Rubyでどうやるか。
これ、一見簡単に見えるが、ハマり要素がたくさんある。つーか結論を先にいってしまうと『不可能』である。UNIXでは簡単なのだが…。本当に嫌になりましたorz。
Excel VBA オブジェクト階層図のウソ
今までなんとかVBAを使った開発を逃げてきたのだが、今回ばかりはどうしてもExcel VBAでの開発になってしまった。Excel VBAを触ったことがないわけではないのだが、まともにやるのは今回が初。そこで文法とかAPIとかオブジェクトとか適当に勉強していたのだが、よくわからなかったのが、この「オブジェクト階層図」。これは、こんな風になっているのだが、
┌──────┐ |Application | └──────┘ ┃┌──────┐ ┗|Workbook(s) | └──────┘ ┃┌──────┐ ┗|Worksheet(s)| └──────┘ ┃┌──────┐ ┗|Range | └──────┘
これは、どう見ても RangeはApplicationのサブクラス、に見えるのだが…。しかし、どう考えてもRangeがApplicationであるはずがない。そこで3分でできる!Excel VBA、という本をよく見てみたら、ApllicatonはWorkbook(s)オブジェクトを持っている、とか書いてあるし。つまりExcel VBAのオブジェクト階層図は、すべてhas-a関係を表している、ということだ。だからApplicationがもっているメソッドやらプロパティやらはRangeに継承される、ということはないのである。
こんなことは、VBAを使っている人には当然なのだろうが…。クラスの継承と同じ表記にするなと。上にあるクラスを「親クラス」と呼ぶなと。見た目がMFCとそっくりというのが、なんともMSらしい書き方です。