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

VBA 参照設定とCreateObjectは全く別の経路でオブジェクトを参照する。

$
0
0

昨日以下の記事を書いた。
thom.hateblo.jp

要約すると、レジストリのHKEY_CLASSES_ROOT直下にプログラムID(InternetExplorer.Application等)のキーがあり、その下のCLSIDにGUIDが入っているのでそれをまたレジストリから辿ると、TypeLibを辿れるというもの。
f:id:t-hom:20160130160615p:plain

TypeLibまでたどれば参照設定名も分かるので、相互変換できると思い込んでいたのだが、結論としてこの方法が使えるのはIEなどごく一部のプログラムだけだった。

ためしにWordのCLSIDを辿ってみると、そこにTypeLibが無い。
f:id:t-hom:20160131040417p:plain

逆に、TypeLibから辿る方法も使えなかった。

なかなか良い方法だと思ったのに残念。

さて、ではVBAはどうやって参照設定とCreateObjectを対応づけているのかと思い、ProcessMonitorというツールを使ってレジストリファイルシステムの動きを観察してみた。(最初からそうしてれば良かった。反省。)

さて、CreateObjectでIEを起動するケースで、重要なイベントだけピックアップしてみた。
f:id:t-hom:20160131042001p:plain

HKCRというのはHKEY_CLASSES_ROOTの略である。

つまり、CreateObjectに渡されたプログラムIDでまずHKEY_CLASSES_ROOTからCLSIDを引っ張ってきて、そのキーを辿るところまでは予想通りだった。

その後、svchost.exeに処理がわたり、HKCR\CLSID\{GUID}\LocalServer32を参照する。
そのLocalServer32にパスが書かれていて、それをファイルシステムから参照し、IEを起動する。
という流れのようだ。

PowerPoint.Applicationでも試してみたが、大体同じ流れだった。
なぜか、LocalServer32はIEと違って(規定)ではなく、よくわからない暗号みたいな方を参照していたが。。
f:id:t-hom:20160131042839p:plain

参照設定を行った場合はTypeLibの情報からパスを読み込んでくるので、そもそも参照設定とCreateObjectでは全く別の経路でオブジェクトのバイナリを参照していることになる。

ということで、参照設定をCreateObjectに変換するには、地道にHKCRからそれらしい名前を見つけてくるしかなさそうだ。


WSH/JScriptで起動中のIEからURLを取得する。

$
0
0

先日買ったJavaScript本のコードを何度か写経して、そろそろ自分でもコードを書いてみたくなった。
thom.hateblo.jp

そこで、普段仕事でもよく使うIE操作のテクニックの準備段階である、開いているIEのURLを取得するコードを書いてみることにした。

普段はExcelVBScriptでコーディングする。

そこで、VBScriptで書いたコードをJavaScriptに直していくことにした。

以下はVBSで書いたコード。

Call test

Sub test()WithCreateObject("Shell.Application")For i =0To.Windows.Count-1Dim a: a =""OnErrorResumeNext
            a =.Windows(i).document.URL
            OnErrorGoTo0IfLen(a)>0ThenMsgBox a
        NextEndWithEndSub

JavaScriptに直してみたのがこちら。

test();

function test() {var sh = WScript.CreateObject("Shell.Application");
    for (var i = 0; i < sh.Windows().Count; i++) {var a = "";
        try{
            a = sh.Windows(i).document.URL;
        }catch(e) {}if(a) WScript.echo(a);
    }}

出来上がってしまえばなんてことは無いのだが、作ってる最中は実行時エラーとの戦いだった。
セミコロンが抜けてるだのカッコが無いだのと、まぁ文句が多いこと。。
それと、大文字と小文字を厳格に区別するので、ifをIfと書いたり、echoをEchoと書いて動かなかったりした。

ひとつ、ハマってしまったのはsh.Windows().Countの部分。
最初sh.Windows.Countと書いていて、小文字にしても動かず途方に暮れて質問サイトで聞いてみたらあっさり回答が来た。sh.Windowsの後にカッコがいるらしい。

あとこれは自己解決したが、変数aの判定で最初、if(a.length > 0)としていたが、これではダメだった。
VBScriptのOn Error Resume Nextと違い、try ~ catchでは不明なプロパティをaに代入しようとするとundefinedが入ってしまうようだ。

undefinedや""はそのままifで判定するとfalseになり、1文字以上の文字列はtrueになる仕様のようなので、結局if(a)でやりたいことができた。

qiita.com

そういえばVBAも最初始めたころはエラー連発だったのを思い出した。
今でこそ、一気に書き上げてさくっと動いてしまったりするが、初心者の頃はちびちび作りながらコンパイルして、エラーが出て、何時間も悩んで…ということをやっていた。

繰り返し失敗して、悩むことでだんだんプログラミングの勘が養われてくるんだと思う。

JScriptでfor eachの代わりになるEnumeratorオブジェクト

$
0
0

【注意】JScript限定です。JavaScriptでは使えません。

先日、JScriptで起動中のIEからURLを取得するコードを書いた。
thom.hateblo.jp

本当はVBSのFor Eachみたいなもので数を意識せずにループしたかったが、色々調べながらやってみたものの同じような処理はできず、結局sh.Windows().Countを取って0番目のアイテムからループさせた。

以下、うまくいかなかっった方法。

for each (var i in sh.Windows()) {for (var i in sh.Windows()) {for (var i of sh.Windows()) {

ところが今日別の調べものをしていて偶然そのEnumeratorオブジェクトというものを見つけた。
Enumerator オブジェクト (JavaScript)

読み方は、イニュメレーター。

For Eachのようにとはいかないが、これを使えば数がいくつあるかを意識せずにループを回せる。
これはMicrosoftJScript独自の実装のようで、一般的なWebページのJavaScriptなどでは使用するとIE以外のブラウザではうまく動かない。

ただ今回のようにWSHで使うにはうってつけだった。

使い方はMSDNよりも以下の記事が分かりやすい。
d.hatena.ne.jp


できたコードはこちら。

ie();

function ie() {var sh = WScript.CreateObject("Shell.Application");
    var e = new Enumerator(sh.Windows());
    e.moveFirst();
    while(e.atEnd() == false) {var a = "";
        try{
            a = e.item().document.URL;
        }catch(er) {}if(a) WScript.echo(a);
        e.moveNext();
    }}

もともとWSH自体、本来のWebで使用するJavaScriptとは違う分野の処理なので、こうしたJScriptならではの機能は嬉しい。
まあ、そういうのはVBSでやれよって話なんだろうけど。

VBA 参照設定にマクロで根こそぎチェックをいれてみた。

$
0
0

VBEの参照設定の画面はお世辞にもよくできているとは言い難い。
膨大な項目があるのに基本的に目視でスクロールして探す必要があるからだ。

大体使いたいものは「Microsoft~」なので、「m」キーを押すと頭文字Mの最初の行までは飛ぶ。
f:id:t-hom:20160209035636p:plain

これがフォルダとかなら、すばやく「mic」と打てばMicrosoftまでは飛んでくれるのだが、この参照設定では「mi」と入れたタイミングで頭文字Iに飛んでしまうのだ。

はっきり言って、イケてない。せめて検索機能が欲しい。
そうだ、自分で作ろう!

というのが今回の発端。

まだ完成はしていないが、マクロで根こそぎ参照設定するところまでできた。

この参照設定は、レジストリのHKCR\TypeLibを見ている。
VBAレジストリを読めれば、ライブラリの実態ファイルのパスを取り出すことができ、ライブラリのファイルパスが分かればVBAで参照設定できるのだ。

実際にやってみたのがこちらのコード。
ただし、参照設定を外すコードを用意してないので、実行の前に全ての重要なExcelファイルを閉じて、新規のExcelファイルで試すと良い。

Sub根こそぎ参照設定()Dimレジストリ AsObject: Setレジストリ = _CreateObject("WbemScripting.SWbemLocator") _.ConnectServer(,"root\default") _.Get("StdRegProv")Const HKCR =&H80000000
    Dim TypeLibの子, TypeLibの孫,,,値
    
    レジストリ.EnumKey HKCR,"TypeLib", TypeLibの子
    ForEachIn TypeLibの子
        レジストリ.EnumKey HKCR,"TypeLib\"&, TypeLibの孫
        IfNotIsNull(TypeLibの孫)ThenForEachIn TypeLibの孫
                レジストリ.GetStringValue HKCR,"TypeLib\"&&"\"&&"\0\win32",,OnErrorResumeNext
                    ActiveWorkbook.VBProject.References.AddFromFileOnErrorGoTo0NextEndIfNextEndSub

また、実行するためにはセキュリティ設定でVBAプロジェクトオブジェクトモデルへのアクセスを信頼するにチェックをつけておく必要がある。
f:id:t-hom:20160209040445p:plain


さて、実行の結果がこちら。スクロールバーに注目してほしい。
f:id:t-hom:20160209040738p:plain

ご覧のとおり殆どの参照設定にチェックが入っている。
その数なんと674!!

あ…でも根こそぎではなかった。すんません。


参照設定は以下のマクロで書きだすことができる。
MicrosoftVisual Basic for Applications Extensibility 5.3の参照が必要。

Sub参照設定書き出し()Dim Ref As Reference
    i =2ForEach Ref In ThisWorkbook.VBProject.References
        Sheet1.Cells(i,1).Value= Ref.Description
        i = i +1Next Ref
EndSub

Excelに書き出してみたところ。
f:id:t-hom:20160209041522p:plain

10分の1サイズ。
f:id:t-hom:20160209041816p:plain


さて、さすがにこのままでは使い物にならないので、インターフェースを作って取捨選択できるようにしたい。

VBAからレジストリを参照するために参考にしたサイト】
www.wmifun.net
www.wmifun.net

VBA 参照設定でライブラリを探すのが面倒なので、ライブラリを検索できる参照設定ダイアログを自作してみた

$
0
0

VBAの参照設定ダイアログは、大量のライブラリを目視で探していく必要があり、使い勝手が悪い。

そこで、ライブラリ名(厳密にはDescription)で検索できるような参照設定ダイアログを自分で作ってみた。

先日の記事では、とりあえずレジストリからTypeLibraryを読み込んで、根こそぎ参照設定するというところまで紹介した。
thom.hateblo.jp

今回は実際に参照設定ダイアログの作成まで対応したので紹介する。

まず完成イメージはこちら。
f:id:t-hom:20160210233229p:plain

はじめ、リストボックス(上段)には、全てのライブラリが表示されている。
まず左上のコンボボックスから参照設定を追加したいブックを選択し、その隣のテキストボックスでライブラリを検索する。
すると、検索にヒットしたアイテムのみがリストボックス(上段)に残る。

リストボックス(上段)のアイテムをダブルクリックすると下段のリストボックスに追加され、OKボタンで参照設定が完了する。

作成方法

毎回レジストリからTypeLibrary情報を読み込んでいては遅いので、レジストリから読み込んだタイプライブラリの一覧は、このマクロを追加するブックのシート1にキャッシュする仕様にした。

まずはシート1のオブジェクト名をshTypeLibに変更しておく。
f:id:t-hom:20160210234150p:plain

※やり方は過去記事参照
thom.hateblo.jp

そして、標準モジュール「Module1」を追加し、次のコードを張り付ける。

OptionExplicitSub初期設定()
    shTypeLib.Cells.ClearCall Ref
    Call SheetSort
EndSubSub Ref()Dimレジストリ AsObject: Setレジストリ = _CreateObject("WbemScripting.SWbemLocator") _.ConnectServer(,"root\default") _.Get("StdRegProv")Const HKCR =&H80000000
    Dim TypeLibの子, TypeLibの孫,,,,2Dim arr()AsStringReDim arr(1To2,1To1)Dim i AsLong
    i =1レジストリ.EnumKey HKCR,"TypeLib", TypeLibの子
    ForEachIn TypeLibの子
        レジストリ.EnumKey HKCR,"TypeLib\"&, TypeLibの孫
        IfNotIsNull(TypeLibの孫)ThenForEachIn TypeLibの孫
                レジストリ.GetStringValue HKCR,"TypeLib\"&&"\"&&"\0\win32",,値
                レジストリ.GetStringValue HKCR,"TypeLib\"&&"\"&,,2If(NotIsNull(2))And(NotIsNull())Then
                    shTypeLib.Cells(i,1)=2
                    shTypeLib.Cells(i,2)=値
                    i = i +1EndIfNextEndIfNextEndSubSub SheetSort()With shTypeLib.Sort
        With.SortFields
            .Clear.Add Key:=shTypeLib.Range("A1"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
        EndWith.SetRange shTypeLib.Cells(1,1).CurrentRegion
        .Header = xlGuess
        .MatchCase =False.Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
    EndWithEndSub

次にUserForm1を追加し、各コントロールの名称を次のようにする。
f:id:t-hom:20160210234534p:plain

ListBox1, ListBox2ともに、プロパティのColumnCountは2を設定しておく。
ComboBox1のプロパティStyleは、2に設定しておく。

UserFormに記入するコードは次のとおり。

OptionExplicitPrivateSub UserForm_Initialize()
    TextBox1.Text =vbNullStringCall ListReset
    Dim w As Workbook
    ForEach w In Workbooks
        IfNot w Is ThisWorkbook Then
            ComboBox1.AddItem w.NameEndIfNextEndSubPrivateSub ListReset()
    ListBox1.ClearDim arr()AsVariant
    arr = ReadData
    Dim i AsLongFor i =LBound(arr,1)ToUBound(arr,1)Me.ListBox1.AddItem arr(i,1)Me.ListBox1.List(ListBox1.ListCount -1,1)= arr(i,2)NextEndSubFunction ReadData()AsVariantIf shTypeLib.Cells(1,1)=""ThenMsgBox"データがありません。",vbExclamation,"確認"MsgBox"初期設定を実施します。",vbInformation,"確認"
        Module1.初期設定
    EndIf
    ReadData = shTypeLib.Cells(1,1).CurrentRegion.ValueEndFunctionPrivateSub cmdクリア_Click()
    UserForm_Initialize
EndSubPrivateSub cmd検索_Click()Call ListReset
    Dim i AsLongDoWhile i < ListBox1.ListCount
        IfInStr(1, ListBox1.List(i,0), TextBox1.Text,vbTextCompare)=0Then
            ListBox1.RemoveItem(i)Else
            i = i +1EndIfLoopEndSubPrivateSub cmd絞込み_Click()Dim i AsLongDoWhile i < ListBox1.ListCount
        IfInStr(1, ListBox1.List(i,0), TextBox1.Text,vbTextCompare)=0Then
            ListBox1.RemoveItem(i)Else
            i = i +1EndIfLoopEndSubPrivateSub ListBox1_DblClick(ByValCancelAs MSForms.ReturnBoolean)With ListBox1
        ListBox2.AddItem.List(.ListIndex,0)
        ListBox2.List(ListBox2.ListCount -1,1)=.List(.ListIndex,1)EndWithEndSubPrivateSub ListBox2_DblClick(ByValCancelAs MSForms.ReturnBoolean)OnErrorResumeNext'未選択でWクリックした場合のエラーを無視
        ListBox2.RemoveItem ListBox2.ListIndex
    OnErrorGoTo0EndSubPrivateSub cmdOK_Click()With ListBox2
        If.ListCount >0ThenIf ComboBox1.Text <>""ThenDim i AsLong, cnt AsLongFor i =0To.ListCount -1OnErrorResumeNext'参照不可エラーは面倒なのでスキップ
                    Workbooks(ComboBox1.Text).VBProject.References.AddFromFile.List(i,1)If Err.Number =0Then cnt = cnt +1OnErrorGoTo0NextMsgBox"ワークブック「"& Workbooks(ComboBox1.Text).Name&"」に" _& cnt &"件の参照を追加しました。",vbInformation,"完了"UnloadMeElseMsgBox"左上のコンボボックスで対象のブックを選択してください。",vbInformation,"エラー"EndIfEndIfEndWithEndSubPrivateSub cmd初期設定_Click()MsgBox"初期設定では、レジストリからタイプライブラリの情報を読み込みます。",vbInformation,"初期設定について"MsgBox"初回起動時や、ソフトウェアのインストールを行った場合に実施してください。",vbInformation,"初期設定について"MsgBox"この操作は数秒~数十秒かかります。",vbInformation,"初期設定について"IfvbYes=MsgBox("続行しますか。",vbInformation+vbYesNo,"確認")ThenCall Module1.初期設定
        Call UserForm_Initialize
        MsgBox"完了しました。",vbInformation,"完了"ElseMsgBox"キャンセルしました。",vbInformation,"中止"EndIfEndSub

これでフォームを起動すれば、検索機能つきの参照設定ダイアログが使用できる。
初回起動時はシート1(shTypeLib)が空っぽなので、初期設定が実行される。
以降はそのブックを保存してしまえば初期設定する必要はない。

なお、このマクロを実行する前提として、セキュリティ設定でVBAプロジェクトオブジェクトモデルへのアクセスを信頼するにチェックをつけておく必要があるので注意。

最終的にはアドオン化してVBエディタのメニューにフォームを開くコマンドを追加したいけれど、それはまた面倒なので気が向いたらにしようと思う。

以上

VBA フォームデザイナーで作ったフォームを基にして、それと同じフォームを作り出すコードを自動生成するマクロ

$
0
0

VBA関連のブログ記事を書いていて困るのが、フォーム関連である。
普通の標準モジュールなら、コピーして実行してもらうだけであるが、フォームを使うコードを紹介する際などは、フォームデザインとコントロール名や配置を書かなくてはならない。

たとえば、前回の記事では以下のように吹き出しでコントロール名を書いた。
f:id:t-hom:20160210234534p:plain

しかしこのように紹介したところで、どれだけの方がわざわざマクロを試してくれるだろうか。コードを張り付けて実行するだけなら簡単であるが、フォームを使うマクロではわざわざフォームを作成いただかないといけない。

エクスポートしてどこかにアップロードしておくという手もあるが、ダウンロードしてインポートするという一連の作業は割と面倒くさい。

そこで、もっと手軽にフォームを使ったマクロを実行いただくために、フォームを作る部分自体をコード化してしまえば良いのではないかと考えた。

これなら、マクロをコピーしてF5で実行していただくだけである。
ただ前提条件として、マクロのセキュリティ設定から「VBAプロジェクト オブジェクト モデルへのアクセスを信頼する」を有効にしていただく必要はあるが。


さて、いざフォームをコードでデザインしようと思うと、それはそれで面倒くさい。
コントロールの配置にもよるが、基本的にはフォームデザイナーで見たまま編集したほうがデザインしやすい。

ならば、デザイナーで作ったフォームを基にして、それと同じフォームを作り出すコードを自動生成できないかというのが今回の記事である。

とりあえず、フォームデザイナーで適当にフォームをデザインしてみた。
f:id:t-hom:20160213193626p:plain

フォーム名はMasterFormとしておく。
そして、標準モジュールに次のコードを貼りつけて実行する。

Subフォームを作るコードを作るマクロ()Dim srcForm As UserForm
    Set srcForm = ThisWorkbook.VBProject _.VBComponents.Item("MasterForm").Designer

    Debug.Print"Sub CreateForm()"
    Debug.Print"    Dim f As UserForm"
    Debug.Print"    Set f = ThisWorkbook.VBProject.VBComponents.Add(3).Designer"Dim c As Control
    ForEach c In srcForm.Controls
        Debug.Print"    With f.Controls.Add (""Forms."&TypeName(c)&".1"")"
        Debug.Print"        .Name = """& c.Name&""""
        Debug.Print"        .Width = "& c.Width
        Debug.Print"        .height = "& c.Height
        Debug.Print"        .Top = "& c.Top
        Debug.Print"        .Left = "& c.Left
        Debug.Print"        .TabIndex = "& c.TabIndex
        
       'コントロールによって無いかもしれないプロパティはエラースキップOnErrorResumeNext
            Debug.Print"        .Caption = """& c.Caption &""""
            Debug.Print"        .Text = """& c.Text &""""
            Debug.Print"        .Font.Size = "& c.Font.Size
            Debug.Print"        .Font.Name = """& c.Font.Name&""""OnErrorGoTo0

        Debug.Print"    End With"Next
    Debug.Print"End Sub"EndSub

すると、上記のコードはMasterFormのコントロールのプロパティを読み取って、自動で以下のコードを生成してイミディエイトウインドウに出力する。

Sub CreateForm()Dim f As UserForm
    Set f = ThisWorkbook.VBProject.VBComponents.Add(3).Designer
    With f.Controls.Add("Forms.TextBox.1").Name="TextBox1".Width=132.height =18.Top =12.Left=30.TabIndex =0.Text ="".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.TextBox.1").Name="TextBox2".Width=132.height =18.Top =36.Left=30.TabIndex =1.Text ="".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.TextBox.1").Name="TextBox3".Width=132.height =18.Top =60.Left=30.TabIndex =2.Text ="".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.TextBox.1").Name="TextBox4".Width=132.height =18.Top =84.Left=30.TabIndex =3.Text ="".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.CommandButton.1").Name="CommandButton1".Width=60.height =90.Top =12.Left=168.TabIndex =4.Caption ="OK".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.Label.1").Name="Label1".Width=12.height =18.Top =12.Left=12.TabIndex =5.Caption ="A".Font.Size=16.2.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.Label.1").Name="Label2".Width=12.height =18.Top =36.Left=12.TabIndex =6.Caption ="B".Font.Size=16.2.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.Label.1").Name="Label3".Width=12.height =18.Top =60.Left=12.TabIndex =7.Caption ="C".Font.Size=16.2.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.Label.1").Name="Label4".Width=12.height =18.Top =84.Left=12.TabIndex =8.Caption ="D".Font.Size=16.2.Font.Name="MS UI Gothic"EndWithEndSub

あとはこのコードを張り付けて実行するだけで、同じフォームが作成される。
ただし、フォーム自体のサイズを変更する術は分からなかった。
※単純にWidthやHeightを設定すれば良いのかと思ったが、デザイン段階でコードでこれらの値を設定することはできないようだ。

また、この方法はリストボックスに対応していない。
MasterFormのリストボックスのTextプロパティはエラーにならずに読み取れる(空白)にもかかわらず、作成しようとするとTextプロパティへの設定がエラーになってしまう。

というわけでリストボックスに対応させるにはTypeNameにコントロールを渡して判定し、Textプロパティを設定しないように書き換える必要がある。今回は面倒なのでやめておいた。


前回の記事で紹介したライブラリを検索できる参照設定ダイアログのフォームを作成するコードは、
thom.hateblo.jp

こちら。

Sub CreateForm()Dim f As UserForm
    Set f = ThisWorkbook.VBProject.VBComponents.Add(3).Designer
    With f.Controls.Add("Forms.ListBox.1").Name="ListBox1".Width=571.85.Height =154.55.Top =48.Left=6.ColumnCount =2.TabIndex =6.Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.TextBox.1").Name="TextBox1".Width=132.Height =18.Top =6.Left=204.TabIndex =1.Text ="".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.CommandButton.1").Name="cmd絞込み".Width=54.Height =18.Top =6.Left=402.TabIndex =3.Caption ="絞込み".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.CommandButton.1").Name="cmdクリア".Width=54.Height =18.Top =6.Left=462.TabIndex =4.Caption ="クリア".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.ListBox.1").Name="ListBox2".Width=571.25.Height =87.95.Top =229.2.Left=6.ColumnCount =2.TabIndex =8.Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.CommandButton.1").Name="cmd検索".Width=54.Height =18.Top =6.Left=342.TabIndex =2.Caption ="検索".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.Label.1").Name="Label1".Width=132.Height =12.Top =36.Left=6.TabIndex =5.Caption ="ライブラリ一覧(ダブルクリックで選択)".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.Label.1").Name="Label2".Width=180.Height =12.Top =216.Left=6.TabIndex =7.Caption ="選択したライブラリ(ダブルクリックで解除)".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.CommandButton.1").Name="cmdOK".Width=78.Height =18.Top =324.Left=498.TabIndex =9.Caption ="OK".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.ComboBox.1").Name="ComboBox1".Width=168.Height =18.Top =6.Left=30.TabIndex =0.Text ="".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.Label.1").Name="Label3".Width=24.Height =12.Top =12.Left=6.TabIndex =10.Caption ="ブック".Font.Size=9.Font.Name="MS UI Gothic"EndWithWith f.Controls.Add("Forms.CommandButton.1").Name="cmd初期設定".Width=54.Height =18.Top =6.Left=522.TabIndex =11.Caption ="初期設定".Font.Size=9.Font.Name="MS UI Gothic"EndWithEndSub

※このコードは前述のマクロで出力したものにListBoxのTextプロパティ設定部分を削除し、ColumnCountの設定コードを手で付けくわえたものである。

前述したように、フォームサイズは設定できないので適当にデザイナで引き伸ばして欲しい。
また、フォーム自体のコードは前回記事にあるので要コピー。

このブログに掲載しているコードの利用条件について

$
0
0

質問サイトを閲覧していたところ、著作権云々で非常に厳しい意見が目についた。

訴訟がどうたら、アメリカが云々、TPPでどうなる云々…
あーーめんどくせぇ。。

言ってることは至極まともなんだけれど、私はあんまり堅苦しいことは言いたくない。逆に読者が著作権を気にするあまり紹介したコードを自由に使ってもらえないというのでは折角ブログに掲載した意味がないではないか。何のために公開してるんだか。

というわけで、利用を制限する為ではなく、逆に、コードを自由に使ってもらう為に、あえて利用条件について明記しておこうと思う。

利用条件

  1. 掲載しているコードの一部または全部を、質問サイト、ブログ、Webサイト、その他のメディアに再掲載する場合、このブログのURLを併記してください。URLさえ書けば、無断で転載してもらってかまいません。
  2. 著作者名はURLがあれば分かりますので不要です。
  3. 業務で使用するマクロ等に組み込む場合、丸ごとコピーした場合はコメントでURLを記入してください。変数名や構成を変更してテクニックだけを真似る場合は特にURLの記載は不要です。
  4. 作成したマクロの著作権を主張される場合は、私のコードを真似た部分にはこのブログの記事のURLをコメント等で記載してください。
  5. 判断に迷う場合は、良心に従い常識の範囲でご利用いただければと思います。

以上

全然自由じゃない?いやいや、利用条件が書かれていないことに比べれば、遥かに自由だと思う。利用条件が書かれていないコードは怖くて使えない。後から「無断転載禁止」なんて言われたら面倒くさいので、非常に不自由だ。

そもそもの目的

そもそも著作権の本質は、「他人に自分の手柄を横取りされたくない」ということかと思う。
Webに公開しているのに「コピーして自分のコードに組み入れるのはけしからん」なんてのは矛盾していると思うし、公開する行為自体が「よければ使ってください」という意思表示みたいなものだ。

要は、他人が「オレが考えたんだぜ、スゲーだろ、がははは」みたいなことを言い出すと腹が立つということだ。あと、作成されたマクロが独り歩きしていつのまにか他人が作ったことになっていて、逆に自分が訴えられるなんてことにならないように、予防線を張っておきたい。

この意図を汲んでいただけるなら、自由に使っていただいて構わない。

Webサイト作成技術の移り変わり

$
0
0

Webの世界は日進月歩。ついこないだまで最新だった知識もあっというまに古びてしまう。今回は「ホームページ作れます!」というソレが意味するところが、人によって全然違うというところを説明したいと思う。

ちなみにホームページという用語は、ブラウザを開いたときに最初に表示されるページのことなので、本当はウェブサイトという呼び方が正しい。

なんちゃって。

いまだにそんなことをドヤ顔で言っていると、時代に取り残されてしまう。当初は誤用だとして間違いを正そうとする人が多かったけれど、今では「広義のホームページ」はウェブサイトも指すという見解が主流だ。ウェブサイトという呼び方もだいぶ一般に定着しているように思うが、ITエンジニアの方も誤用と承知で一般向けの説明ではあえて「ホームページ」という語を用いることがある。

まあ、それはそれとして、ここからはWebサイトと表記させてもらおう。私はちょうど「ホームページは誤用である」世代なので、何となくホームページと呼ぶのは気持ち悪いのだ。
Webサイトの作成手法は時代とともに移り変わってきた。以下に作成手法の移り変わりについて、自分の主観で語ってみようと思う。

インターネット黎明期(フレームレイアウト時代)

黎明期といってもARPANETまでさかのぼるつもりはない。私がインターネットを使い始めた頃を勝手に黎明期としている。
まあ、ネットにつなぐたびにピーヒョロしてた時代なので、日本人にとっては黎明期と言ってしまって良いだろう。
ホームページという言葉の誤用が広まったのものこの頃だ。

私がインターネットを使い始めたのは17年くらい前で、その頃はまだHTMLの標準が浸透しておらず、ブラウザごとに独自機能を競っていたイメージがある。たとえば、blinkタグで文字を点滅させたり、文字をスクロールさせたり。とにかくゴテゴテに装飾しまくって派手なサイトが多かった。
文字色やサイズ、背景色や文字色などもHTMLタグに直接書き込むスタイルが多かった。

当時のHTMLを例示するとこんな感じ。

<HTML><HEAD><TITLE>黎明期のWebサイト</TITLE></HEAD><BODYBGCOLOR=GREENTEXT=WHITE>
Pタグも理解されてなくて、<BR>こんな風に<FONTCOLOR=RED>そのまま書いている</FONT>サイトが<BR>多かったように思う。
</BODY>

以下はIE11で表示してみたところ。
f:id:t-hom:20160214220249p:plain

また、サイドメニューはフレームで作成するのが一般的だった。いわゆるフレームレイアウトである。

イントラネットサイト(企業の従業員などが見る社内用のサイト)などではフレームはまだ一般的に使用されていて、使い方によってはそれほど悪い技術だとは思わない。

ただ、社内ユーザーのサポートをしていて困ったのが、「ここを開いてくれ」とURLをダイレクトに送れない点だ。フレームなのでページを移動してもURLがそのままなので、ひと手間かけないとURLの案内もできない。

現在はHTMLタグで文字や背景を飾り付けることも、フレームを使用することもどちらも非推奨となっている。

JavaScriptが派手に嫌われていたのもこの時代だったように思う。当時はブラウザもOSも貧弱で、一度クリックしたら最後、延々とポップアップが表示されるというイタズラサイトにあたり、電源を切るしかなくなるといったこともあった。

Perl言語による掲示板全盛期もこの頃だったような記憶がある。

テーブルレイアウト時代

さて、フレームが非推奨となったら、サイドメニューなどを別の手段でレイアウトしないといけない。そこで次にやってきたのがテーブルレイアウトの時代である。

テーブルタグは本来は表を作るためにあるのだが、テーブルの中にテーブルを入れ子にすることで思い通りにレイアウトすることができた。

当時のHTMLはこんなかんじ。

<HTML><HEAD><TITLE>黎明期のWebサイト</TITLE></HEAD><BODYBGCOLOR=WHITE><TABLEWIDTH=100%><TR><TDWIDTH=20%VALIGN=TOPBGCOLOR=LIGHTGREEN><TABLEALIGN = CENTER><TR><TD>メニューA</TD></TR><TR><TD>メニューB</TD></TR><TR><TD>メニューC</TD></TR></TABLE></TD><TDVALIGN=TOPBGCOLOR = BLACKWIDTH=100%><FONTCOLOR=WHITE>ここにコンテンツを書いていくと、<BR>左のメニューとは分離される。
</FONT></TD></TR></TABLE></BODY>

IE11で表示するとこうなる。
f:id:t-hom:20160214220637p:plain

しかしこのテクニックはサイトの見た目にしか配慮されていないのでGoogleなどの検索エンジンからみて文章の構成が分かりづらい。また視覚障害者用の読み上げソフトなどでも対応が困難だった。

そもそもテーブルタグは表を想定した機能なので、レイアウト用に使うのは無理があるし、HTMLも複雑になってしまう。

これも現在は非推奨である。

ちなみにPHPが流行りだしたのは、この頃かと思う。 ただし、まだレンタルサーバーではPerlのみ対応というところが多かった。PHPも探せばあるけどといった感じ。

フラッシュ黄金時代

テーブルレイアウトの後には、AdobeFlashを使ったメニューや、全てFlashで作られたサイトなどが流行った。いや、先だったかな。。いまいち覚えてないが、とにかく10年くらい前だった気がする。

AppleiPhoneiPadFlashをサポートしないとしたことで、今ではすっかり廃れてしまった。

近代(CSSレイアウトの夜明け)

次に登場したのがレイアウトの本命「CSS」である。これはHTMLのdivタグでボックスを作成し、それをCSSの回り込み機能でレイアウトしていくというものである。

このあたりから、素人がさくっと作れるようなものではなくなり、本格的にWebサイトの作り方を勉強しないといけなくなった。もちろん、今でも非推奨の書き方でサイトを公開することはできるが、検索エンジンにまともに取り合ってもらえない。

ブログやSNSなどの情報発信手段が充実してきたことで、一般の方がWebに情報公開するためにHTMLを書く必要は薄れていった。

この頃からはHTMLのタグを小文字で書くのが主流になってきた気がする(もうすこし早かったかもしれない)。もともとどっちでも良いので、わざわざ大文字で書く必要はないのだ。

また、JavaScriptが「使えるヤツ」として復権したのもこの頃である。
いまだにブラウザのJavaScriptを無効で使ってるなんて言っていると、古い人だと思われる。
そしてJQueryも台頭してきて、JavaScriptを使った動きのあるメニューが流行った。

この先にもモバイル対応・レスポンシブデザインなどのトレンドがあるのだが、これらはCSSレイアウトをベースにしたものなので、CSSレイアウトまで付いて来られているなら、今のところは堂々と「ホームページ作れます」と言って良いと思う。

前述のフレームレイアウト・テーブルレイアウトは現在ではまともなWebサイトだとはみなされない。そこまで古いテクニックを使うくらいなら、WordやExcelでデザインしてペッと張り付けてしまった方が楽で良い。Officeでサイトを作るというのは、手を抜けるという意味では現在でも有効な手段だと思う。(ただし検索エンジンからのアクセスはあきらめた方が良いが。)

Perlで作られたMovableTypeが衰退して、PHPによるWordPressが流行り始めたのもこの頃だったと思う。この頃からレンタルサーバーでもPHP対応が増えてきたイメージ。

現代

さて、この後登場したのがHTML5で、これは今までボックス作成にdivタグばかり使っていたものをheader、footer、article、sectionなど、要素に意味づけをするタグに置き換えた。これはセマンティクス(情報の意味付け)と呼ばれる。

ただレイアウト技術はあまり変わっておらず、相変わらずCSSのボックスレイアウトである。

その次のモバイル対応である。「スマホ対応サイト」として個別にデザインする時代があったように思うが一瞬で終わったのであまり覚えてない。

そのあとすぐに「レスポンシブデザイン」が主流になった。これは画面サイズによって自動的にレイアウトが切り替わるようにCSSでブレイクポイント(何ピクセルを切るとスマホ用に切り替わるか)を設定する手法だ。一つのHTMLを端末によって最適なサイズ・レイアウトで見せる技術である。

サーバーサイドでは、Ruby On RailsDjango、Cake PHPなどのWebフレームワークが流行り、レンタルサーバーでもRubyPythonが使えるところが増えてきた。Webアプリケーションも大規模化して、業務で使うアプリもどんどんWeb化されている。

未来

未来といっても、すでに登場している技術である。

TypeScriptやSassは素晴らしい技術であるが、爆発的に普及しているかというと、まだまだこれからな感じがするので、これからの更なる普及に期待したいという意味を込めて未来とした。

TypeScriptはJavaScriptにデータ型を持たせて堅牢・安全にプログラミングできるようにしたものである。コンパイルするとJavaScriptになる。

TypeScriptはJavaScriptに対する後方互換性があるので、これまでどおりのJavaScriptのコードを書けばそれはすなわちTypeScriptのコードということになる。つまりTypeScriptの機能のうち、便利だと思った機能を部分的に取り入れていけば良いのだ。したがってJavaScriptからTypeScriptへの移行自体に学習コストは殆ど無い。

Sass(サス)はCSSをより簡潔・便利に記述する言語で、コンパイルするとCSSになる。

SassはCSSに対する後方互換性があるので、これまでどおりのCSSのコードを書けばそれはすなわちSassのコードということになる。つまりSassの機能のうち、便利だと思った機能を部分的に取り入れていけば良いのだ。したがってCSSからSassへの移行自体に学習コストは殆ど無い。


ちなみに、私の知識は近代で止まっている。

JQueryは解説書もろくに出てなかった時代に一応スライドショーのようなアニメーション効果を使ったことがあるくらいで、殆ど知らない。

HTML5はつい最近サイトを改定するのに使ったが、キャンバスなどのトレンディな機能は使い方を知らない。

サーバーサイドはPHPでなんとかログイン画面を作れるくらいで、セキュリティを考慮した本格的なものは作れない。

WordPressは一応インストールしてテーマを選択くらいはできるけれど、固定ページのデザインなんかはやったことが無いし、ましてPHPによるカスタマイズなんてできない。

レスポンシブデザインには興味があるものの、まだ手を出していない。

Node.jsというサーバーサイドJavaScriptも出てきたが、まったくやったことがない。

サーバーサイド技術ならASP.NETにも興味がある。さらにそれを動かす環境にも。

あぁ、最近はDockerとかVagrantなんてのも面白そうだ。Web関係ないけど。Chefもいいなぁ。Web関係ないけど。ほぅ、PowerShell DSCなんてのも出たのか。。やべぇ興奮してきた。
冪等性(べきとうせい)という概念。フムフム。宣言的プログラミング!関数型言語に通じるものがある!コードが美しい!

あれ、何の記事書いてたっけ。

まぁいいや。

そうやって興味が色々と分散していって、最後には飽和してしまい、「もう無理っ。やっぱVBAでいいや」となる。そして結局何も身につかない。これがいつものパターンである。

でも最近Sassに手を出した。CSSは既存技術として持ってるし、学習コストは低いしちょうど自分のサイトのCSSがごちゃごちゃしてたし。ただ、学習コストが低すぎて一瞬で終わってしまった。(マスターはしてないけど、とりあえず満足するレベルまで。)

さて、次はTypeScriptをやってみたいな。


VBA vbNewLineを沢山書く変わりに、SplitとJoinでスマートに改行しよう…と思ったけどよく考えてみたらReplaceで良かった。

$
0
0

さっき投稿したところであるが、冷静に考えてみたらReplace関数で良かった。早とちり。

今回は小技を紹介する。

VBAでは文字列の改行にvbNewLineを使用するが、文字列の外に書かないといけないので改行が増えてくると面倒だ。

まずは普通に書いてみたのがこちら。

Sub改行が面倒くさい()MsgBox _"文字列中で改行するには通常vbNewLine等の定数を"&vbNewLine& _"文字列の外に書く必要がありますが、"&vbNewLine& _"vbNewLineは結構長いので、正直言って面倒くさいです"&vbNewLine&vbNewLine& _"C言語みたいに\nで書けたらいいのにと思いませんか。"EndSub

やっぱり面倒くさい。

C言語みたいに\nで書けたら良いのに。
…ということで、過去に一度CのprintfをVBAで模倣する関数を作ったのだが、
thom.hateblo.jp

今回はもっと手軽な方法を思いついたので紹介。
それが、以下のコード。

Sub切ってつなげて改行()MsgBoxJoin(Split( _"そこで、Split関数とJoin関数を利用して、-"& _"これを簡潔に書いてみました。--"& _"ハイフンでSplitして、vbNewLineでJoinしています。--"& _"今回はデリミタにハイフンを用いてますが、-"& _"その文字列中で使われてない記号や文字ならなんでもOKです。--"& _"","-"),vbNewLine)EndSub

文字列中に使われていない記号を改行コードの代わりに用いて、Splitのデリミタとして設定することで配列に切り分ける。
それをJoin関数でvbNewLineで繋いでやると、改行された文字列が手に入るというわけだ。

もしセルに入れる文字列を作りたければ、Join関数でvbLfをデリミタとして呼び出せば良い。

と、書いたはいいけど、普通にReplaceの方が早かった。。

Sub Replaceの方が早かった()MsgBoxReplace( _"SplitとJoinを使った方法、画期的だと思ったんですが、-"& _"完全に寝ぼけてましたね。--"& _"どう考えても、Replaceの方が早いです。--","-",vbNewLine)EndSub

うーん。なかなか活躍の場がないな、Join君。

以上

VBA SplitとJoinを使って、文字列型配列の要素数を数える。

$
0
0

先ほどこのような記事を書いた。
thom.hateblo.jp

SplitとJoinで面白いことができると思ったんだけれど、とんだ勘違いだった。まぁ、できるにはできるが、わざわざ2つも関数を持ち出さなくても、Replaceを使った方が良い。

ということでなんかJoinも活躍の場がないかなと探していたところ、文字列型配列の集計マクロに使うことを思いついた。

以前、Dictionaryを使って配列要素を数えるマクロを書いたのだが、これをSplitとJoinでもっと手軽にやってみようと思う。
thom.hateblo.jp

これは、配列("A", "A", "B", "C", "A", "B", "A")があった時に、それぞれA、B、Cの個数を集計するというもの。

コードはこうなった。

Sub Splitでカウント()Dim arr AsVariant: arr =Array("A","A","B","C","A","B","A")DimstrAsString: str=Join(arr,"_")
    Debug.PrintUBound(Split(str,"A"))
    Debug.PrintUBound(Split(str,"B"))
    Debug.PrintUBound(Split(str,"C"))EndSub

まず配列の要素をアンダーバーでくっつけて一つの文字列にしてしまう。
すると、"A_A_B_C_A_B_A"という文字列ができる。

それからその文字列に対し、探したい文字列でSplitする。
例えばAでSplitすると、"", "_", "_B_C_", "_B_", ""という配列ができる。

Uboundとすると4が取れるのでそれがAの個数となる。
※要素は0~4番の5個であるが、デリミタの数を数えるので、4個で良い。

ひと文字じゃなくても対応可能である。

Sub複数文字でも()Dim arr AsVariant: arr =Array("Apple","Apple","Banana","Orange","Apple","Banana","Apple")DimstrAsString: str=Join(arr,"_")
    Debug.PrintUBound(Split(str,"Apple"))
    Debug.PrintUBound(Split(str,"Banana"))
    Debug.PrintUBound(Split(str,"Orange"))EndSub

以上

VBA クラスモジュールのデフォルトプロパティをマクロで設定する

$
0
0

以前、クラスモジュールをエクスポートしてテキストエディタで編集することでデフォルトプロパティを変更できるという記事を書いた
thom.hateblo.jp

しかし、わざわざエクスポートして書き換えるというのも面倒くさい。

VBAはテキストも扱えるので、どうせならエクスポートしてデフォルトプロパティのAttribute(属性)追加してインポートさせるところまで自動化してしまおうというのが今回のネタ。

どうやるかというと、まずクラスモジュールのデフォルトプロパティにしたいプロシージャにコメントで目印を付ける。
今回は「'=Default Property」というコメントを目印として属性を追加するコードを書いた。

プロパティを追加するターゲットとして、OperatableNumberというクラスを作成した。
コードは以下のとおり。Numberプロパティに目印のコメントを入れてある。

Private PNumber AsLongPublic Unit AsStringPrivateSub Class_Initialize()
    PNumber =0EndSubPropertyGet Number()AsLong'=Default Property
    Number = PNumber
EndPropertyPublicSubClear()Call Class_Initialize
EndSubPublicSubAdd(Optional x AsVariant=1)
    PNumber = PNumber + x
EndSubPublicSub CountUp()Me.Add1EndSubPropertyGet WithUnit()
    WithUnit =Format(Number,"#,#")& Unit
EndProperty

そして上記のクラスを加工するコードがこちら。
※実行には以下3点の参照設定と、マクロのセキュリティ設定でVBAプロジェクト オブジェクト モデルへのアクセスを信頼するのチェックが必要

Sub Sample()Call DefaultProperty( _"OperatableNumber", _"C:\Users\thom\ExportedClassFiles\", _
        ThisWorkbook)EndSubSub DefaultProperty( _クラス名 AsString, _出力フォルダ AsString, _対象ブック As Workbook, _Optionalクラスの変更前にバックアップを作成する AsBoolean=True, _Optionalエクスポートした一時ファイルを削除する AsBoolean=True)Dim VBC As VBComponent
    Set VBC =対象ブック.VBProject.VBComponents(クラス名)
    VBC.Export(出力フォルダ &クラス名 &".cls")Dim FSO AsNew FileSystemObject
    Dim TS As TextStream
    
    Set TS = FSO.OpenTextFile(出力フォルダ &クラス名 &".cls", ForReading)Dimテキスト AsStringテキスト = TS.ReadAll
    TS.CloseDim RE AsNew RegExp
    RE.Pattern ="Attribute .*\.VB_UserMemId = 0\r?\n"
    RE.Global =True
    RE.MultiLine =Trueテキスト = RE.Replace(テキスト,"")テキスト =Replace(テキスト,"'=Default Property","Attribute Default.VB_UserMemId = 0"&vbCrLf&"    '=Default Property")Set TS = FSO.OpenTextFile(出力フォルダ &クラス名 &".cls", ForWriting)
    TS.Writeテキスト
    TS.CloseIfクラスの変更前にバックアップを作成する Then
        VBC.Name= VBC.Name&Format(Now,"_yyyymmddhhMMss")Else対象ブック.VBProject.VBComponents.Remove VBC
    EndIf対象ブック.VBProject.VBComponents.Import(出力フォルダ &クラス名 &".cls")Ifエクスポートした一時ファイルを削除する Then
        FSO.DeleteFile(出力フォルダ &クラス名 &".cls")EndIfEndSub

Sampleプロシージャの1つ目の引数はターゲットのクラス名、2つ目は一時的にクラスをエクスポートする先(書き込み権限のあるフォルダを指定する。)、3つ目はクラスがあるワークブックを指定する。

Call DefaultProperty( _"OperatableNumber", _"C:\Users\thom\ExportedClassFiles\", _
        ThisWorkbook)

オプションの引数2つはDefaultPropertyプロシージャを参照。

これらもUserFormを作成して選択させられると楽だが、面倒なので今回はやってない。とりあえず使えるから良い。

コード中で正規表現を使用しているところがある。

    RE.Pattern ="Attribute .*\.VB_UserMemId = 0\r?\n"

これはデフォルトプロパティを途中で買えたいような場合もあるので、別のプロパティがデフォルトプロパティになっている場合に一旦削除するための措置で、正規表現で削除するためのコードである。

今回たまたま気付いたのだが、エクスポートしたクラスに
「Attribute Default.VB_UserMemId = 0」と書いても、インポートするとDefaultの部分がプロパティ名に置き換わってしまうようで、例えばNumberプロパティに書いてインポートしたもの再度エクスポートしてみたら
「Attribute Number.VB_UserMemId = 0」となっていた。

要はDefaultの部分の初期値はなんでも良いみたいで、
「Attribute A.VB_UserMemId = 0」でも、
「Attribute あ.VB_UserMemId = 0」でも、取り込まれてしまえば同じである。ちなみに前回の記事では海外サイトのコードを参考にしたため、Valueとしている。

この可変性はVBAの標準機能で扱うのは少々厄介なので、正規表現を用いた。

さて、実行すると一見何も変化がないが、OperatableNumberクラスのNumberプロパティに見えないコードが埋め込まれてデフォルトプロパティになっている。

以下のコードで正しく実行されるか試してみると、

Subデフォルトプロパティサンプル()Dim x AsNew OperatableNumber
    x.Unit ="円"
    x.Add1000MsgBox x
EndSub

通常MsgBox x.Numberと書くところを、xだけで1000と表示される。

次にOperatableNumberのNumberプロパティから'=Default Propertyのコメントを消し、WithUnitプロパティに付与してみる。

PropertyGet Number()AsLong
    Number = PNumber
EndPropertyPropertyGet WithUnit()'=Default Property
    WithUnit =Format(Number,"#,#")& Unit
EndProperty

そして、Sampleマクロでデフォルトプロパティの付け替えを行った後、再度以下のコードを実行すると、

Subデフォルトプロパティサンプル()Dim x AsNew OperatableNumber
    x.Unit ="円"
    x.Add1000MsgBox x
EndSub

1,000円と表示される。

VBA ユーザーフォームのテキストボックスのサイズを自動で最適化するマクロ

$
0
0

今回の記事は、Excelの表から各列の最大文字数を求め、その文字数に基づいて適切なサイズのテキストボックスを新規フォームに配置するというものである。

記事タイトルからすると既存フォームに対して実行するように思われたかもしれない。
なかなかキャッチーで、かつ簡潔なタイトルが思い浮かばなかったのだ。
だましてごめん。

目次

前談

時々、Excel表をユーザーフォームで扱いたくなる。
特に列数が多いと1レコード見るのに横スクロールで行ったり来たりする必要があるので、フォームがあると便利だ。

イメージとしてはこんな感じ。(普通はラベルも付けるけど今回はパス)
f:id:t-hom:20160225023648p:plain

しかし、表に合わせて最適なフォームをデザインするのは結構面倒だ。何が一番面倒かというと、テキストボックスのサイズと配置である。

最近VBComponentの操作もこなれてきたので、ユーザーフォームのデザインもVBAでやってしまおう。

列ごとの最大文字数を取得する「各列最大文字数」関数を作る。

テキストボックスの大まかなサイズは、中に入れる文字数で決まる。
そこで、各列の最大文字数を取得する関数を作成することにした。

一旦、普通のプロシージャとして作成し、あとで関数化する。

まずは、選択範囲を配列に入れて、ループで列を移動してみよう。
コードは次のとおり。

Sub各列最大文字数_未完()Dim Arr: Arr = Selection.ValueFor i =LBound(Arr,2)ToUBound(Arr,2)
        Debug.Print Arr(1, i)NextEndSub

このように表全体を選択し、
f:id:t-hom:20160225024635p:plain

上記のマクロを実行すると、イミディエイトウィンドウに次のように出力される。

No
列1
列2
列3
列4
列5

次に、これを改良し、列ごとに要素の文字長を出力してみる。

Sub各列最大文字数_未完2()Dim Arr: Arr = Selection.ValueFor i =LBound(Arr,2)ToUBound(Arr,2)
        Debug.Print Arr(1, i)'ヘッダの出力For j =LBound(Arr,1)ToUBound(Arr,1)
            Debug.PrintLen(Arr(j, i));
        Next
        Debug.Print'改行NextEndSub

結果はこうなる。

No
 2  1  1  1  1  1  1  1  1  1  2 
列1
 2  16  13  14  9  9  17  5  17  18  16 
列2
 2  52  62  99  53  69  68  74  57  74  63 
列3
 2  1  1  3  3  2  3  3  1  3  3 
列4
 2  4  1  3  1  1  4  2  1  2  2 
列5
 2  192  167  55  283  200  157  123  41  238  143 

あとは最大数を取るようにして、関数化しておく。

Function各列最大文字数(取得範囲 As Range)AsStringDim ret AsStringDim Arr: Arr =取得範囲
    For i =LBound(Arr,2)ToUBound(Arr,2)
        ret = ret & Arr(1, i)&" "Dim MaxLen: MaxLen =0For j =LBound(Arr,1)ToUBound(Arr,1)If MaxLen <Len(Arr(j, i))Then MaxLen =Len(Arr(j, i))Next
        ret = ret &CStr(MaxLen)&","Next
    ret =Left(ret,Len(ret)-1)各列最大文字数 = ret
EndFunction

「各列最大文字数」関数の使い方

表を選択した状態で、Selectionを引数として関数「各列最大文字数」呼び出してみる。

Sub test()
    Debug.Print各列最大文字数(Selection)EndSub

すると、以下のような文字列が取得できる。

No 2,列1 18,列2 99,列3 3,列4 4,列5 283

この文字列は、2種類の区切り文字(カンマとスペース)がある。
まず最初にカンマで区切ると、

Sub test2()ForEach x InSplit(各列最大文字数(Selection),",")
        Debug.Print x
    NextEndSub

こうなる。

No 2
列1 18
列2 99
列3 3
列4 4
列5 283

これを更にSplitするとまた配列ができるので、直接添え字を書いて出力してみる。

Sub test3()ForEach x InSplit(各列最大文字数(Selection),",")
        Debug.Print"「";
        Debug.PrintSplit(x)(0);
        Debug.Print"」の最大文字数は「";
        Debug.PrintSplit(x)(1);
        Debug.Print"」文字です。"NextEndSub

※区切り文字のデフォルトがスペースなので、スペース区切りの場合は第二引数を書かなくて良い。

結果はこのようになる。

「No」の最大文字数は「2」文字です。
「列1」の最大文字数は「18」文字です。
「列2」の最大文字数は「99」文字です。
「列3」の最大文字数は「3」文字です。
「列4」の最大文字数は「4」文字です。
「列5」の最大文字数は「283」文字です。

フォームの自動生成

ここからは、以下2点の参照設定が必要になる。

また、マクロのセキュリティ設定でVBAプロジェクト オブジェクト モデルへのアクセスを信頼するのチェックが必要。
(…最近毎回この文言書いている気がする。なんか省力化したい。)

フォームの生成自体はお決まりのパターンなので、以下のサンプルを参考に、あとはループで回すなりなんなりといったところ。

Subフォーム生成()Dim f As UserForm
    Set f = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm).Designer
    Dim C As Control
    Set C = f.Controls.Add("Forms.TextBox.1")
    C.Top =10
    C.Left=20
    C.Height =15
    C.Width=300EndSub

実行すると、こんな感じでフォームが出来上がる。
f:id:t-hom:20160225031551p:plain

残念ながらフォームそのもののサイズをマクロで変更する方法は見つかってないので、マウスで端のハンドルを掴んでサイズ変更しよう。

テキストボックスのサイズを算出する

列ごとの最大文字数の求め方が分かったので、テキストボックスのサイズはその文字数を基準に決めれば良い。
まず一行に何文字入れるかを決める。

これは読みやすい文書の文字数ということでいくつかネットに模範解答が出ている。
www.okidata.co.jp

ということで、35文字を採用した。

行数は文字数を35で割れば算出できるが、これも画面サイズの都合があるので表示できる行数は制限しておきたい。
今回は適当に最大7行としておいた。(根拠はない)

コードにするとこんな感じ。(断片なのでこれだけでは動かない)

Const折り返し文字数 =35Const最大表示行数 =7行数 =(文字数 \ 折り返し文字数)+1If文字数 >折り返し文字数 Then文字数 =折り返し文字数
If行数 >最大表示行数 Then行数 =最大表示行数

次に行の高さと文字の幅を調査する。
これは地道に検証を繰り返して、フィット感を確認し、文字幅を9、行高を9.2とした。
画面スケールに影響を受けそうなので、うまくフィットしない場合は別途調整が必要。

完成コード

以下が完成コード。
これだけ見る人もいるかもしれないので繰り返すと、
以下2点の参照設定が必要になる。

また、マクロのセキュリティ設定でVBAプロジェクト オブジェクト モデルへのアクセスを信頼するのチェックが必要。

まずは冒頭で作った関数(以下)を張り付けて、

Function各列最大文字数(取得範囲 As Range)AsStringDim ret AsStringDim Arr: Arr =取得範囲
    For i =LBound(Arr,2)ToUBound(Arr,2)
        ret = ret & Arr(1, i)&" "Dim MaxLen: MaxLen =0For j =LBound(Arr,1)ToUBound(Arr,1)If MaxLen <Len(Arr(j, i))Then MaxLen =Len(Arr(j, i))Next
        ret = ret &CStr(MaxLen)&","Next
    ret =Left(ret,Len(ret)-1)各列最大文字数 = ret
EndFunction

次に、表を選択した状態で、
f:id:t-hom:20160225024635p:plain

以下のコードを実行する。

Sub自動で最適なサイズのテキストボックスを配置()Dim行別文字数 AsString行別文字数 =各列最大文字数(Selection)Const全角文字幅 =9Const行高 =9.2Const折り返し文字数 =35'文字Const最大表示行数 =7'行Constコントロールマージン =5コントロールY座標 =20'(フォーム上端から)コントロールX座標 =20'(フォーム左端から)Dim f As UserForm
    Set f = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm).Designer
    ForEach x InSplit(行別文字数,",")'文字数と行数を算出文字数 =Split(x)(1)行数 =(文字数 \ 折り返し文字数)+1If文字数 >折り返し文字数 Then文字数 =折り返し文字数
        If行数 >最大表示行数 Then行数 =最大表示行数
        
       'コントロールを配置Dim C As Control
        Set C = f.Controls.Add("Forms.TextBox.1")With C
            .Name="txt"&Split(x)(0).Text =Split(x)(0).Width=15+(文字数 *全角文字幅)'15はサイズの微調整If行数 >1Then.MultiLine =TrueEndIf.Top =コントロールY座標
            .Left=コントロールX座標
            .Height =5+(行高 *行数)'5はサイズの微調整コントロールY座標 =コントロールY座標 +.Height +コントロールマージン
        EndWithNextEndSub

すると、こんな感じでフォームが表示されるので、
f:id:t-hom:20160225033231p:plain

ハンドルを引っ張って適当なサイズに変更する。
f:id:t-hom:20160225033325p:plain

これで完成。
f:id:t-hom:20160225023648p:plain

なお、ラベルを配置したい場合はf.Controls.Add("Forms.Label.1")とする。

おまけ

こういう検証をしていると、フォームを作りすぎて消すのに困る。
削除用マクロも用意した。

実行すると「Are you sure?」と聞かれるので、sureと入力すると、そのワークブックにある全てのユーザーフォームが削除される。
ActiveWorkbookではなく、このマクロを配置したブックのフォームがすべて消えるので注意。

Sub全フォーム削除()IfNot"sure"=InputBox("Are you sure?")ThenExitSubDim x As VBComponent
    ForEach x In ThisWorkbook.VBProject.VBComponents
        Set C = x
        If x.Type= vbext_ct_MSForm Then
            ThisWorkbook.VBProject.VBComponents.Remove(C)EndIfNextMsgBox"Done.",vbInformationEndSub

フォーム関連のオススメ書籍

私はこの本で初めて、コードでフォームのコントロールを配置整列できることを知った。
※書籍ではフォームデザイナではなく、FormのInitialize後に整列させる方法が紹介されている。

同じシリーズの以下もおススメ

アプリ作成で学ぶExcelVBAプログラミングユーザーフォーム&コントロール

アプリ作成で学ぶExcelVBAプログラミングユーザーフォーム&コントロール

VBAのフォーム関連ならこの2冊は外せない。

ただ2冊とも一から通しで読むような構成になっているので、それなりに根気がいる。
もう少しざくっと、リファレンス的に使いたいなら以下の書籍がおススメ

以上

VBA Functionプロシージャの動作をテストするコード

$
0
0

堅牢なソフトウェアを作ろうと思ったら、テストは欠かせないプロセスだ。

ソフトウェアテストというのは、要するに「ちゃんと動くかどうか」を確認する作業なのだが、「使ってみました。たぶん問題ありません。」という簡単な話でもない。

私もあまりテストに関して知識が無かったので、以下の書籍を読んだ。

知識ゼロから学ぶソフトウェアテスト 【改訂版】

知識ゼロから学ぶソフトウェアテスト 【改訂版】

語り口が軽快でサクサク読めるので、テストに興味があるけれどまったく初めてという人にお勧め。

さて、この本で学んだ知識(同値分割法・境界値分析法)を基にFizzBuzzのテストを書いてみようと思う。

まずは、普通のFizzBuzzコード

Sub FizzBuzz()For i =1To100SelectCase0Case i Mod3+ i Mod5
                Debug.Print"FizzBuzz"Case i Mod3
                Debug.Print"Fizz"Case i Mod5
                Debug.Print"Buzz"CaseElse
                Debug.Print i
        EndSelectNextEndSub

問題なく動作するが、戻り値が無いのでテストができない。

これをテスタブルにするためには、Functionプロシージャで関数化すれば良い。

Function fnFizzBuzz(x)AsStringDim ret AsStringSelectCase0Case x Mod3+ x Mod5
            ret ="FizzBuzz"Case x Mod3
            ret ="Fizz"Case x Mod5
            ret ="Buzz"CaseElse
            ret =CStr(x)EndSelect
    fnFizzBuzz = ret
EndFunctionSub TestableFizzBuzz()For i =1To100
        Debug.Print fnFizzBuzz(i)NextEndSub

すると、fnFizzBuzz関数のテストを書くことができる。

まずは簡単なテストから。

Sub TestfnFizzBuzz1()
    Debug.Assert fnFizzBuzz(6)="Fizz"
    Debug.Assert fnFizzBuzz(10)="Buzz"
    Debug.Assert fnFizzBuzz(30)="FizzBuzz"
    Debug.Assert fnFizzBuzz(16)="16"MsgBox"テスト完了"EndSub

Debug.Assertは、Falseになるとコードが中断する命令である。
つまり、コードが中断せずに最後のメッセージ「テスト完了」が出たら、テストにパスしたということだ。

テストする値は、同値分割の考え方でFizzになる数値グループ、Buzzになる数値グループ、FizzBuzzになる数値グループ、数値のままに表示される数値グルーブに分けて、グループ代表の数値ひとつをピックアップする考え方である。

逆に、同値分割の考え方だけなら、同じグループでいくつも書く必要はない。
ただし、0はよくバグの元になるので、必ずテストする。
また、3、5、15はそれぞれ最小値のグループの最小値になるので、境界値と考えて良いのだろうか。
あまり自信がないが境界値分析法のつもりで一応入れておく。

また、Fizz以下の最大値2も加えてみた。

Sub TestfnFizzBuzz2()
    Debug.Assert fnFizzBuzz(0)="FizzBuzz"
    Debug.Assert fnFizzBuzz(2)="2"
    Debug.Assert fnFizzBuzz(3)="Fizz"
    Debug.Assert fnFizzBuzz(6)="Fizz"
    Debug.Assert fnFizzBuzz(5)="Buzz"
    Debug.Assert fnFizzBuzz(10)="Buzz"
    Debug.Assert fnFizzBuzz(15)="FizzBuzz"
    Debug.Assert fnFizzBuzz(16)="16"
    Debug.Assert fnFizzBuzz(30)="FizzBuzz"MsgBox"テスト完了"EndSub

これもパス。
次に、負数を与えてみたらどうなるのか。
数学的には、負数の余り算も成り立つので、パスしてくれないと困る。
マイナスゼロなんてのは無いけれど、一応いれてみた。

Sub TestfnFizzBuzz3()'正の数
    Debug.Assert fnFizzBuzz(0)="FizzBuzz"
    Debug.Assert fnFizzBuzz(2)="2"
    Debug.Assert fnFizzBuzz(3)="Fizz"
    Debug.Assert fnFizzBuzz(6)="Fizz"
    Debug.Assert fnFizzBuzz(5)="Buzz"
    Debug.Assert fnFizzBuzz(10)="Buzz"
    Debug.Assert fnFizzBuzz(15)="FizzBuzz"
    Debug.Assert fnFizzBuzz(16)="16"
    Debug.Assert fnFizzBuzz(30)="FizzBuzz"'負の数
    Debug.Assert fnFizzBuzz(-0)="FizzBuzz"
    Debug.Assert fnFizzBuzz(-2)="-2"
    Debug.Assert fnFizzBuzz(-3)="Fizz"
    Debug.Assert fnFizzBuzz(-6)="Fizz"
    Debug.Assert fnFizzBuzz(-5)="Buzz"
    Debug.Assert fnFizzBuzz(-10)="Buzz"
    Debug.Assert fnFizzBuzz(-15)="FizzBuzz"
    Debug.Assert fnFizzBuzz(-16)="-16"
    Debug.Assert fnFizzBuzz(-30)="FizzBuzz"MsgBox"テスト完了"EndSub

これも無事にテストパス。

次にLong型の最大値境界と最小値境界をテスト

Sub TestfnFizzBuzz4()'~既出テストは省略~'Long型の最大値境界
    Debug.Assert fnFizzBuzz(2147483647)="2147483647"
    Debug.Assert fnFizzBuzz(2147483648#)="2147483648"'Long型の最小値境界
    Debug.Assert fnFizzBuzz(-2147483648#)="-2147483648"
    Debug.Assert fnFizzBuzz(-2147483649#)="-2147483649"MsgBox"テスト完了"EndSub

おっと、ここでオーバーフローエラー
f:id:t-hom:20160228170209p:plain
f:id:t-hom:20160228170258p:plain

つまりfnFizzBuzzは、Long型の最大値2147483647を超えるとバグが発生する関数ということ。

ということで、関数本体を以下のように修正。
境界値を超えると"ERROR"という文字列を返すようにした。

Function fnFizzBuzz(x)AsStringDim ret AsStringIf x <=2147483647And x >=-2147483648# ThenSelectCase0Case x Mod3+ x Mod5
                ret ="FizzBuzz"Case x Mod3
                ret ="Fizz"Case x Mod5
                ret ="Buzz"CaseElse
                ret =CStr(x)EndSelectElse
        ret ="ERROR"EndIf
    fnFizzBuzz = ret
EndFunction

テストも以下のように修正する。

Sub TestfnFizzBuzz5()'~既出テストは省略~'Long型の最大値境界
    Debug.Assert fnFizzBuzz(2147483647)="2147483647"
    Debug.Assert fnFizzBuzz(2147483648#)="ERROR"'Long型の最小値境界
    Debug.Assert fnFizzBuzz(-2147483648#)="-2147483648"
    Debug.Assert fnFizzBuzz(-2147483649#)="ERROR"MsgBox"テスト完了"EndSub

これでテストは無事にパス。

次に、悪いデータのテストを行う。
悪いデータとは、本来想定されていないデータのことで、たとえば文字列、日付、小数などを受け取ったときにコードがどう振る舞うかをテストする。

出来上がった最終のテストコードはこちら。

Sub TestfnFizzBuzzFinal()'正の数
    Debug.Assert fnFizzBuzz(0)="FizzBuzz"
    Debug.Assert fnFizzBuzz(2)="2"
    Debug.Assert fnFizzBuzz(3)="Fizz"
    Debug.Assert fnFizzBuzz(6)="Fizz"
    Debug.Assert fnFizzBuzz(5)="Buzz"
    Debug.Assert fnFizzBuzz(10)="Buzz"
    Debug.Assert fnFizzBuzz(15)="FizzBuzz"
    Debug.Assert fnFizzBuzz(16)="16"
    Debug.Assert fnFizzBuzz(30)="FizzBuzz"'負の数
    Debug.Assert fnFizzBuzz(-0)="FizzBuzz"
    Debug.Assert fnFizzBuzz(-2)="-2"
    Debug.Assert fnFizzBuzz(-3)="Fizz"
    Debug.Assert fnFizzBuzz(-6)="Fizz"
    Debug.Assert fnFizzBuzz(-5)="Buzz"
    Debug.Assert fnFizzBuzz(-10)="Buzz"
    Debug.Assert fnFizzBuzz(-15)="FizzBuzz"
    Debug.Assert fnFizzBuzz(-16)="-16"
    Debug.Assert fnFizzBuzz(-30)="FizzBuzz"'Long型の最大値境界
    Debug.Assert fnFizzBuzz(2147483647)="2147483647"
    Debug.Assert fnFizzBuzz(2147483648#)="ERROR"'Long型の最小値境界
    Debug.Assert fnFizzBuzz(-2147483648#)="-2147483648"
    Debug.Assert fnFizzBuzz(-2147483649#)="ERROR"'文字列
    Debug.Assert fnFizzBuzz("15")="FizzBuzz"
    Debug.Assert fnFizzBuzz("150,000")="FizzBuzz"
    Debug.Assert fnFizzBuzz("aa")="ERROR"'日付
    Debug.Assert fnFizzBuzz(Now)="ERROR"
    Debug.Assert fnFizzBuzz(Date)="ERROR"
    Debug.Assert fnFizzBuzz(Time)="ERROR"'小数
    Debug.Assert fnFizzBuzz(0.1)="ERROR"
    Debug.Assert fnFizzBuzz(-0.1)="ERROR"
    Debug.Assert fnFizzBuzz(1E-100)="ERROR"MsgBox"テスト完了"EndSub

一旦本体はそのままでテストしてみると、Nowを渡したときにコードが中断した。
f:id:t-hom:20160228171432p:plain

フム。。

"ERROR"を返すコードは書いてないが、Nowを渡すと例外が発生すると思っていた。
しかし、Now Mod 3とすると、0になる。ちなみに5で割ると4、15で割ると9になった。
なんじゃこりゃ。。DateやTimeに対しても、余り算ができてしまう。

ということで、IsNumericで数値でないものをERRORとするように変更。
また、小数や文字列もパスするように本体を修正すると、こうなった。

Function fnFizzBuzz(x)AsStringDim ret AsStringIfIsNumeric(x)ThenIf x <=2147483647And x >=-2147483648# ThenIfInt(x)=CDbl(x)ThenSelectCase0Case x Mod3+ x Mod5
                        ret ="FizzBuzz"Case x Mod3
                        ret ="Fizz"Case x Mod5
                        ret ="Buzz"CaseElse
                        ret =CStr(x)EndSelectElse
                ret ="ERROR"EndIfElse
            ret ="ERROR"EndIfElse
        ret ="ERROR"EndIf
    fnFizzBuzz = ret
EndFunction

色々考慮したためコードがごちゃごちゃしてしまったが、実用に耐えられるプログラムというのはこうしたテストを経て作られるものだ。

そして、テストコードがあると良いのは、リファクタリングが簡単にできること。リファクタリングとは、挙動を変えずにコードを整理することである。コードの書き換えには常に「間違える」というリスクが伴う。テストコードがあれば、すぐに間違いに気づくことができる。

先ほど作成したfnFizzBuzzはあれで完成しているが、Ifのネストが分かりにくいので、禁断の「GoTo」を使って少しスッキリさせてみた。

Function fnFizzBuzz(x)AsStringDim ret AsString'例外チェックIfNotIsNumeric(x)ThenGoTo Exception
    If x >2147483647ThenGoTo Exception
    If x <-2147483648# ThenGoTo Exception
    IfInt(x)<>CDbl(x)ThenGoTo Exception
    
    SelectCase0Case x Mod3+ x Mod5
            ret ="FizzBuzz"Case x Mod3
            ret ="Fizz"Case x Mod5
            ret ="Buzz"CaseElse
            ret =CStr(x)EndSelectGoTo Fin   '正常終了

Exception:
    ret ="ERROR"
Fin:
    fnFizzBuzz = ret
EndFunction

このような場合でも、先ほどと全く同じテストコードを用いて検証することができる。

今回は実行条件である「If x <= 2147483647 And x >= -2147483648# Then」を、不実行の条件である以下のコードに書き換えている。

If x >2147483647ThenGoTo Exception
    If x <-2147483648# ThenGoTo Exception

不実行なので不等号の向きが逆になり、イコールは外れる。
しかしこれは特に間違えやすいポイントで、イコールを付けたままにしたり、向きを変え忘れたり、Notを付けたにも関わらず向きを変えてしまったりという間違いがよく起こるのだ。

このような間違いは、先ほどのテストコードを実行すればすぐに判明する。

堅牢なコードを書くうえで、テストコードを書いておくことは非常に有用である。

ただ、テストを書くのは結構面倒くさい。今回は説明にちょうど良い規模だったのでFizzBuzzを題材としたが、FizzBuzzのような人畜無害なプログラムならテストなんて作らなくても、冒頭で紹介した以下のSubプロシージャで十分だと思う。

Sub FizzBuzz()For i =1To100SelectCase0Case i Mod3+ i Mod5
                Debug.Print"FizzBuzz"Case i Mod3
                Debug.Print"Fizz"Case i Mod5
                Debug.Print"Buzz"CaseElse
                Debug.Print i
        EndSelectNextEndSub

逆に、お金が絡むような処理、セキュリティに関わる処理など、ビジネスにクリティカルな影響を与える部分は必ずテストを書いておこう。

VBA 他人の書いたコードを読むには、プロシージャの呼び出しマップを作ってみる。

$
0
0

他人の書いたコードを読むのはなかなか難しい。
今回はプロシージャやモジュールが分かれているプログラムを読むときに使える手法「呼び出しマップ」を紹介する。

「呼び出しマップ」というのは私が勝手にそう呼んでいるだけなのだが、どのプロシージャがどのプロシージャを呼び出しているかを図式化したものである。

例えば以前作成した画像の重なり判定を行うマクロを例に挙げる。
これは、アクティブシートのシェイプの重なりを判定するマクロのコードで、実行すると、以下のように表示される。

Picture 1
    Rectangle 4と重なっている
    Rectangle 5と重なっている
Rectangle 4
    Picture 1と重なっている
Rectangle 5
    Picture 1と重なっている

判定結果を基に自動でグループ化させるようなマクロを作りたいときに部品として使った。

目次

コード

標準モジュール[Main]

Sub Shapeの重なり判定()'シェイプをShapeWrapperで包んでコレクションに追加Dim C AsNew Collection, s As Shape, SW As IShapeWrapper
    ForEach s In ActiveSheet.Shapes
        Set SW =New ShapeWrapper
        SW.SetShape s
        C.Add SW
    Next'コレクションの各シェイプ同士の重なり判定Dim SW2 As IShapeWrapper
    ForEach SW In C
        Debug.Print SW.NameForEach SW2 In C
            If SW.IsOverlapped(SW2)Then
                Debug.PrintvbTab& SW2.Name&"と重なっている"EndIfNextNextEndSub

標準モジュール[ShapeWrapperTypeDef]

'ShapeWrapperクラス用のユーザー定義型です。PublicType Node
    x AsSingle
    y AsSingleEndType

クラスモジュール[IShapeWrapper]

'ShapeWrapper用インターフェースPublicFunction IsOverlapped(SW As ShapeWrapper)AsBooleanEndFunctionPublicPropertyGet Self()As IShapeWrapper
EndPropertyPublicSub SetShape(s As Shape)EndSubPublicPropertyGetName()AsStringEndProperty

クラスモジュール[ShapeWrapper]

Implements IShapeWrapper

Private InnerShape As Shape
PrivatePropertyGet IShapeWrapper_Self()As IShapeWrapper
    Set IShapeWrapper_Self =MeEndPropertyPrivatePropertyGet IShapeWrapper_Name()AsString
    IShapeWrapper_Name = InnerShape.NameEndPropertyPrivateSub IShapeWrapper_SetShape(s As Shape)Set InnerShape = s
EndSubPublicPropertyGet Top()AsSingle
    Top = InnerShape.Top
EndPropertyPublicPropertyGet Bottom()AsSingle
    Bottom = InnerShape.Top + InnerShape.Height
EndPropertyPublicPropertyGetLeft()AsSingleLeft= InnerShape.LeftEndPropertyPublicPropertyGetRight()AsSingleRight= InnerShape.Left+ InnerShape.WidthEndPropertyPublicPropertyGet Nodes(Number AsInteger)As Node
    SelectCase Number
        Case1
            Nodes.x =Me.Left
            Nodes.y =Me.Top
        Case2
            Nodes.x =Me.Right
            Nodes.y =Me.Top
        Case3
            Nodes.x =Me.Right
            Nodes.y =Me.Bottom
        Case4
            Nodes.x =Me.Left
            Nodes.y =Me.Bottom
        CaseElse
            Err.Raise1000,,"1~4を指定してください。"EndSelectEndPropertyPrivateFunction IShapeWrapper_IsOverlapped(SW As ShapeWrapper)AsBooleanDim i AsIntegerFor i =1To4Step1
        IShapeWrapper_IsOverlapped = _(SW.Nodes(i).x >Me.LeftAnd _
            SW.Nodes(i).x <Me.RightAnd _
            SW.Nodes(i).y >Me.Top And _
            SW.Nodes(i).y <Me.Bottom) _Or _(Me.Nodes(i).x > SW.LeftAnd _Me.Nodes(i).x < SW.RightAnd _Me.Nodes(i).y > SW.Top And _Me.Nodes(i).y < SW.Bottom)If IShapeWrapper_IsOverlapped ThenExitFunctionNextEndFunction

呼び出しマップ

では、呼び出しマップを書いてみよう。
今回のはあまりプロシージャも多くなく、クラスに纏まっているので、こんな感じになった。
f:id:t-hom:20160303030239p:plain

ShapeWrapperクラスの中身はこんな感じになる。
f:id:t-hom:20160303032823p:plain

こんな風に、何が何を利用しているのかを図にまとめておけば、一か所のコード変更がどこに影響を及ぼすのかが分かりやすい。
また、一見何のために存在しているのか分からないプロシージャも図にまとめるとどこから必要とされているかが分かる。

呼び出しマップを作るツール

パワーポイントでも良いけど、整理段階ではフリーウェアのFrieve Editorが便利
今回の図もこれで作成した。
http://www.frieve.com/feditor/

Windows 10 Mobile「Katana02」のレビュー (DMM mobileで使用)

$
0
0

目次

前談

事の発端は先週の土曜夜8時ごろ。
私はAmazonで注文したコレ↓が届くのを待っていた。

WordPressレッスンブック HTML5&CSS準拠

WordPressレッスンブック HTML5&CSS準拠

そして
「ピンポーン」

やっと来たか!

…ん?

そこにはスーツ姿のお兄さんが。

「わたくしNHKの○○と申します。」

\(゜ロ\)!!

いやいやいや、オマエジャナイ!!

そして、ワチャワチャと問答した挙句、スマホの機種まで聞かれ、バカ正直に答えてしまい、ワンセグでテレビを閲覧できることを暴かれ、まんまと契約させられてしまったわけだ。

誤解のないように断っておくと、私の家にはテレビが無い。
テレビを持っていたころはちゃんとNHKと契約していたし、語学番組とかドキュメンタリーとか世界遺産とかそういうのは割と好きなので、NHKは良く見てた方だと思う。
しかしネット中心の生活になってあまりテレビも見なくなり、ついに処分してしまったのだ。

それからしばらく解約の仕方も分からず、テレビも無いのに1年ほど料金を払い続けていた。どちらかといえば私は優良顧客だったと思う。
そしてある日、節約を思い立って解約方法を調べ、NHKから用紙を取り寄せて受信設備廃棄を理由に正式に解約したのだ。

それ以降もNHKは何度か家に来たけれど、「以前契約していたけど、テレビを捨てて解約しました。」と言えばそれ以上は何も言ってこなかった。
それはそうだろう。テレビがちゃんとあって、NHKもたまに見る癖に契約してない人なんていっぱいいる。それに比べたら正直に契約していた私はよっぽど優良顧客だ。

しかし先週来た男は無慈悲に言い放った。

「携帯電話の機種を教えてもらえますか?」

…まあ、嘘をついても確認する手立てはないんだろうけど、矢継ぎ早に質問をあびせられて回答に窮したのと、ここでの嘘が何か法に触れるとマズいなと思考が働いたのと、まさか海外メーカーのマイスマホワンセグがついてるなんて知らなかったので、正直に答えてしまった。

そして今に至る。

さて、前置きがだいぶ長くなってしまったが、今回のWindows Phone購入は、要するに「ワンセグが見れない機種」に変えてさっさと解約するのが目的である。
まぁ、Androidもそろそろ飽きてきた頃だし、前からWindows Phoneには興味があったし、ちょうど良いかなというのもあった。

レビュー

Windows Phoneが発表されてから長らく、国内では全くデバイスが販売されていなかったが、ここ1~2年で立て続けにデバイスが市場に投入されている。
まだ5機種くらいしかなく、アプリも貧弱だけれど、これから盛り上がりを見せてほしいところだ。

つい先ほど、梅田のヨドバシでFreetel社のWindows 10 Mobile「Katana02」を買ってきた。

ハコはコレ↓。ネットで見たときにもう少し洒落ていた気がするけど、MIYABIと見間違えてたようだ。どうせ捨てるけど。
f:id:t-hom:20160304235256p:plain

こういうベタに和風の名前は嫌いじゃない。価格も2万ちょいとかなり安めの設定。Katana01なら1万ちょっとで買えるけど、さすがにメモリ1Gは心許ないので02にした。

本体裏面はメタリックグレーで、木目調でざらついているのがまた良い。
f:id:t-hom:20160304235843p:plain

オモテはOSが同じならどれも似たようなもんだけれど、一応写真。
f:id:t-hom:20160305003010p:plain
当初Androidが出たときは、まんまiPhoneのパクリだと思ったけれど、MicrosoftはタイルUIでオリジナリティを打ち出してきた。この姿勢は格好いい。正直、PCにタイルは無いわーと思っているけど、タッチデバイスで使う分には大きくてミスタップしにくいので好ましい。

さて、購入の際は店員にDMM mobileは動作検証できてないので使えるか不明と言われたが、Katana01で使えたというユーザーレビューが見つかったので02で改悪はないだろうと思い、店員のアドバイスを無視して購入。ダメならダメで貴重なレビューになるので、人柱のつもりでやってみた。

結果、普通に使えた。ただし、SIMを指してDMMを選ぶだけじゃつながらない。DMMから契約時に送られてくる紙にAPNの設定値が書かれているので、それを端末に設定してやることで使えるようになる。初期設定で一旦通信をあきらめてスキップしないといけないので、ある程度ITに馴染みのある人でないとつらい。

アプリはやはり選択肢が少ない。

LINEはさすがに普通に使える。地図アプリも一昔前は日本が無かったという情報だったが、普通は日本が出てくるし、ルート検索も使える。しかし、Amazon Kindleが今のところAmazon.comのみ対応で、co.jpに対応してない。これは致命的だ。。まあPaperWhiteを持ち歩くか。

ゲームが少ないという情報もあったが、普段全くゲームしないので個人的には問題はない。
よく分からないけれど、ストアのアイコンだけ見るとゲームも結構充実してるイメージ。ただ殆ど英語でタイトルが書かれてるので、日本語のゲームはやはり少ないのだろう。

あと、Excel、Word、PowerPointが最初から入っているが、Excelの操作性は抜群に良かった。もちろん、スマホにしてはという但し書きは付くけれど。

個人的に一番痛いのは、スマホ用サイトが表示できないケースがあること。
ユーザーエージェントで判別しているケースだと、Windows PhoneでアクセスしたときにPC用サイトが表示されてしまうようで、見づらい。はてなブログがまさにそれなのでつらい。

ただ、ブログ作成者側でJavascriptを埋め込んでおけば暫定回避はできるようだ。
blog.shibayan.jp

私のブログは早速対応コードを挿入しておいた。皆さんもぜひ。


WordPressレッスンブックが楽しい

$
0
0

最近メインサイトの方をさっぱり更新していない。ブログで楽々更新できるインターフェースに慣れてしまうと、手打ちのhtmlはなかなか面倒くさい。作成当初は、正しいhtmlでサイトを構築するというモチベーションがあった。単に何かを作るだけではなく、その過程でなんらかの技術が身に付くのが楽しい。

ということで、手打ちのhtmlにも飽きてきたので、メインサイトを既存のデザインのままWordPress化できないかと考えている。ついでにレスポンシブ対応できたら完璧。まぁ、とりあえずマスターしてからの話なので、まだ先のことであるが。

先日、Amazonで買ったWordPressレッスンブックが届いた。

最新版はこちらだけれど、

WordPressレッスンブック HTML5&CSS準拠

WordPressレッスンブック HTML5&CSS準拠

私が買ったのはこちら。

WordPress レッスンブック 3.x対応

WordPress レッスンブック 3.x対応

バージョンは古いけれど基礎を学ぶには十分だし、中古で送料込で600円で手に入ったので良い買い物だったと思う。基本的なことが一通りマスターできたら、別のWordPress本で最新のWordPressを学習しようと思う。

さて、WordPressレッスンブックの内容は、完全な白紙からWordPressテーマを構築するというもの。既存テーマのカスタマイズではなく、一から作るところが良い。これなら既存サイトのデザインを活かしてテーマを作る方法も学べる。

index.phpを普通のhtmlの要領でコーディングして、WordPressが出力するデータをphpタグで埋め込んでいく。もちろんcssも一から作成する。この作業に、今まで学習してきたhtml、cssvim、emmet(旧zen-coding)、sassなどが全部活きてくる。

これは楽しい。

特に最近学習したsassとvim+emmetのおかげでcssとhtmlのコーディングが爆速。数時間でレッスンブックの半分のページを完了した。

プログラミングの学習などでは参考書と画面を交互に見ると疲れるので、書見台を使用している。
私が使っているのはコレ。

カール事務器 ブックスタンダー グリーン BKS-10-G

カール事務器 ブックスタンダー グリーン BKS-10-G

概ね満足しているのだが、難点はページホルダーがちょっと弱いこと。厚めの本だとページの閉じる力に負けてしまうので、最初に思い切り開いて本に癖を付けてから使っている。

また、WordPressレッスンブックに掲載されているコードのフォントサイズはかなり小さいので、書見台だとちょっと距離があって私の視力では厳しい。

コードの差分が赤字で書かれているのだが、これが宜しくない。赤字は確かに目立つのだが、読みやすいかどうかはまた別の話である。黒だとコントラストがはっきりしてなんとか読める字でも赤だと全然読めない。

既存コードをグレーにして、新規分を黒にして欲しかった。

結局この本のために、メガネデビューした。zoffで5000円くらいの、人生初メガネ。
0.5から矯正で1.0までアップ。若干乱視気味だったのも補正されたので、まぁなんとか読める。

今のところ、メガネは自宅でのプログラミング専用である。

Excelでクレジットカード対応の家計簿を作る

$
0
0

家計簿は節約の基本であるが、クレジットカードを使うと家計簿をつけるのが難しい。
普通の家計簿では収支の記帳のみで、クレジットカードのような「負債」をうまく扱えない為だ。
そこで、複式簿記を使いたくなる。いわゆる会計ソフトの類。

しかし個人で使うには会計ソフトは大げさなので、今回はExcelの関数で簡易なものを作ることにした。
とりあえず、資産と負債をざっくり把握でき、特定の取りたい項目は別途取れるように工夫する。

入力

入力シートはシンプルに1行1明細。
f:id:t-hom:20160310065014p:plain
※資産状況は秘密にしておきたいので、金額は適当に入れている。

A、L、I、Eというコードはそれぞれ、Asset(資産)、Liability(負債)、Income(収入)、Expense(支出)の略。

複式簿記が苦手な方は、「右の貸方を使って左の借方を手に入れた」という風に読めば良い。

たとえば、
10行目→給料がH銀行残高になった
11行目→H銀行から5万円引き出した
12行目→現金で外食した(800円)
13行目→銀行から未払い金が引き落とされた

という具合。

カードでコンビニ弁当を買ったら、
借方が区分Eで食費、貸方が区分LでVISAとなる。

集計

同じシートでも違うシートでも良いが、適当な位置にAsset(資産)の集計を作成する。
やり方は単にSUMIF関数で集計するだけ。
まず検索範囲に借方のコード列を指定する。
f:id:t-hom:20160310222356p:plain

次に検索条件は「A」の入った隣のセルを指定。
f:id:t-hom:20160310222430p:plain

最後に合計範囲として金額列を指定。
f:id:t-hom:20160310222522p:plain

これで借方の資産合計は出る。

あとは貸方も同じようにSUMIFで計算して差分を取れば、資産合計が出る。
f:id:t-hom:20160310223030p:plain

逆にLiability(負債)は、貸方が負債の増加(新たな借り入れ等)、借方が減少(借金の返済等)である。

従って、貸方のSUMIFから借方のSUMIFを引いた差分が合計の負債額となる。
f:id:t-hom:20160310223341p:plain

そしてAsset(資産)からLiability(負債)を単純に引けば、Capital(資本)が求まる。
f:id:t-hom:20160310223510p:plain

これで超ミニなバランスシートができた。
f:id:t-hom:20160310223647p:plain

カードで浪費癖があって、手元に現金があるとつい油断してしまう人はこのバランスシートで現実を見よう。

この後、個別集計したい項目はSUMIFで明細列を集計しておく。
f:id:t-hom:20160310225038p:plain

全部やる必要はない。たとえば食費の節約がしたければ食費だけ集計すれば良いと思う。

さて、これだけでは水道光熱費や未引き落としの引き落とし予定の保険料、カードの支払い等がたまってくると、今月どれくらい余裕があるのか分からない。

従って当月未払い金という項目を作った。
貸方も借方もLなので負債は減っていないが、当月未払い金という負債で、VISAの代金のうち32000円を相殺させておけば良い。
f:id:t-hom:20160310224330p:plain

資産から当月未払い金の合計額を引けば、今月の支払い後に残る可処分金が算出できる。

実際にVISA代金の銀行引き落としがあったら、銀行残高と当月未払い金を相殺させる。
f:id:t-hom:20160310224657p:plain

こんな感じで運用を始めてみた。いつまで続くかはわからないが、とりあえず何もつけないよりは大ざっぱでも良いから把握できたほうが安心だ。


さて、このExcel方式の良い点はOneDriveに入れて外出先でも更新できる点だ。
昼の外食ではいちいちレシートまでもらっていないし、帰宅してから一日の消費を思い出して記帳するのも億劫になる。
ちょうど先日Windows 10 Mobileも買ってMobileでもそこそこExcelが使いやすいので出費はすぐ記帳するように習慣づけたいと思う。

VBA フォームのコントロールイベントを共通化する

$
0
0

こちらの記事を読んだところ、同じようなChangeイベントプロシージャがコントロールの数だけできてしまうことにお悩みの様子。
kantoku.hatenablog.com

面白そうなので色々調べながらやってみた。

参考にしたページはこちら
3.4.3 共通イベント処理クラス - EXCEL-VBA開発講座

ateitexe.com


まずEventControlというクラスモジュールを用意した。
コードはこちら

PublicWithEvents chk As MSForms.CheckBox
PublicWithEvents txt As MSForms.TextBox

PublicSubセット(ByRef Con As MSForms.Control)SelectCaseTypeName(Con)Case"CheckBox"Set chk = Con
    Case"TextBox"Set txt = Con
    CaseElse
        Err.Raise1000,"EventControl","想定外のコントロールです。"EndSelectEndSubPrivateSub chk_Change()Call共通イベント(chk)EndSubPrivateSub txt_Change()Call共通イベント(txt)EndSubPrivateSub共通イベント(C As MSForms.Control)MsgBox C.Name&"が変更されました。"EndSub

クラスモジュール内でオブジェクト変数の宣言にWithEventsを付けるとイベントを捕捉できるようになる。
MSForms.Control型というのもあったがこちらではイベント捕捉ができないようなので、諦めてtxtとchkの二種類を用意する。このあたり、ちょっとダサい。。

そうすると、chk_Changeとtxt_Changeのイベントで共通イベントが呼び出される。

メインのサンプルフォームはこんな感じで適当に用意した。
f:id:t-hom:20160311203719p:plain

そしてフォームのコードはこちら

Dimコントロールコレクション As Collection

PrivateSub UserForm_Initialize()Setコントロールコレクション =New Collection
    Dim Con As Control
    Dim EC As EventControl
    ForEach Con InMe.Controls
        SelectCaseTypeName(Con)Case"CheckBox","TextBox"Set EC =New EventControl
            EC.セット Con
            コントロールコレクション.Add EC
        EndSelectNextEndSub

コレクションをモジュールレベル変数としている。
フォームのInitialize時に先ほど作ったEventControl型にコントロールをセットしていく。(ただしテキストボックスとチェックボックスのみ)

これを実行すると、どのチェックボックス・テキストボックスを変更してもEventControlクラスの共通イベントが実行される。
f:id:t-hom:20160311204029p:plain

やや複雑になってはしまうけれど、クラスを汎用的に設計しておけば再利用できて便利だと思う。

自炊なし、昼は外食で1日の食費を1,500円に抑える

$
0
0

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

家計簿をつけ始めてから節約を意識するようになった。

特に食費。

これまでは朝食300~460円、昼食800円、夕食1,000円(酒・スナック菓子など込み)に加え、昼間自販機で5~6本のコーヒーという生活をしてたので、1日あたり2,500円超の食費がかかっていた計算。

一人暮らしでひと月の食費に75,000円。まさに暴飲暴食。そら金が貯まらんわけだ。そら太るわけだ。

ということで、まずは1日の食費を1,500円に抑えることにした。そうすれば30日で45,000円だ。

累計だけだと直観的に分かりづらいので、Excel家計簿で現在の平均食費と今日使える予算を出すようにしている。

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

食費と書いたところが累計。ちゃんとつけ始めたのが3/10頃なので、ちょうど今日で10日目。
平均食費が1,348円と出ているが、これは朝の時点なので、昨晩の平均は1,497円。予算の1500円ギリギリだ。

この方式だと、多少予算を上回っても、「平均を1,500円に戻すためには今日と明日の予算を1000円にする」などちょっとした我慢ですぐに挽回できる。

自炊+弁当が一番安いのはわかっているが、昼食は付き合いもあるので外食メイン。そもそもこれまで料理は何度もトライしては3日坊主だったので、いまさら自分が料理なんてしないことは分かりきっている。そこで夕食をメインの節約ターゲットにした。寝る前に食べると太るのでちょうどいい。

高野豆腐1個(約100円)と、明太スパゲティサラダ小(約100円)、ノンアルコールビール1本(約100円)。お酒が飲みたい日はビールの最小缶(135mlで80円くらい)。

日中の飲み物は、スーパーで買った60円の水500mlが2本。

こんな感じの生活。
夜減らすと寂しいかなと思ったけど、そんなことは無かった。とりあえず酒っぽいのがあれば満足できるらしい。炭酸でお腹が膨れるのもあるかもしれない。


自販機でコーヒーを買わなくなったついでに、今週は脱カフェインもやってみた。
元が慢性カフェイン中毒なので、結構離脱症状がきつい。今週前半は日中の睡魔と頭痛がハンパなかったけれど、ちょうど昨日くらいから調子が戻ってきた。

これまでは夜1時就寝、3時か4時頃に目覚めて二度寝、朝7時半に目覚ましのスヌーズ5~6日でやっと起きたりといった調子だったのが、夜9時頃にはもう眠たくなり、10時には爆睡。だいたい朝5時に勝手に目が覚めるというサイクルで1週間たった。カフェイン恐るべし。

しかしカフェインを避けていたらお茶も飲めない。
まあ、カフェインにも良い効果があるようなので、完全に抜けたらまた中毒にならない程度に飲もうと思う。

さて、1週間こんな調子だったので全然記事も書けなかった。
とりあえず技術ネタが弾切れなので、またしばらくインプットしていこうと思う。

トランプを使ってマージソートを理解する。

$
0
0

これまでアルゴリズムに強くなりたいと思いながら、有名どころは二分探索、バブルソートくらいしか書けなかった。
そこで、マージソートにチャレンジしようと思ったのだがなかなかうまくいかず、今日ようやく動くものができた。

まぁネットで探せばマージソートのコードなんていくらでも転がっているけど、やはり答えを見るんじゃなくて、どうしても自分で考えてコーディングしたかった。そうすることがスキルアップになるような気がしたので。

さて、マージソートとは、配列を半分、そのまた半分、そのまた半分と切り分けていって、これ以上分けられないところまで来たら、小さい順あるいは大きい順にまとめていくやり方だ。

マージソートの「マージ」とは簡単にいえば、「複数のものを一つにまとめる」ことである。
分けて分けて分けて、これ以上分けられなくなったら、マージしてマージしてマージするソートがマージソート

今回はトランプカードを使ってマージソートの方法を紹介したい。
人間にとっては非効率に思えるかもしれないが、コンピューターにやらせるには割と効率の良いやり方である。

まず適当にトランプカード8枚をピックアップする。
f:id:t-hom:20160320175358p:plain

数字が見えるようにずらしてあるが、上から重なり順と考えて欲しい。
最初は「2, J, 4, Q, 6, 4, K, A」の順に並んでいる。

これを半分にわける。
f:id:t-hom:20160320175537p:plain

さらに半分に。
f:id:t-hom:20160320175846p:plain

このあと1枚になるまで分けるのだが、まあのこり2枚だし、分かれているものとしよう。
机のスペースの関係でこれ以上はキビシイ。

次に、隣り合ったカード(1枚vs1枚)をマージする。
このとき、小さいものから順にピックアップする。
f:id:t-hom:20160320180037p:plain

すると並びは「2, J, 4, Q, 4, 6, A, K」となる。

次にまた、隣り合ったグループ(2枚vs2枚)をマージする。
このときに着目するのは、グループ内のカードはすでにソート済みなので、単純に各グループの先頭からピックアップすれば良いという点。

例えば「2, J」vs 「4, 9」のマージでは、

先頭の2と、同じく先頭の4を比較して2をピックアップ。
残りは「J」vs「4, 9」。

先頭のJと、同じく先頭の4を比較して4をピックアップ。
残りは「J」vs「9」

先頭のJと、同じく先頭の9を比較して9をピックアップ。
最後に残ったJをピックアップして、「2, 4, 9, J」の並びができる。

全体の並びは「2, 4, J, Q, A, 4, 6, K」となる。

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

さらに同じルールで隣り合ったグループ(4枚vs4枚)をマージする。
この時もグループ内はすでにソート済みなので、先頭同士の比較で済むことに着目したい。
f:id:t-hom:20160320181217p:plain

人間ならパッと小さい数を見つけてその順に拾っていけばソートが完了するが、いざコンピューターにやらせようと思ったら単純なルールにしておかないとプログラム化が難しい。だから先頭同士の比較であることが重要なのだ。

今回使ったのはUSプレイングカード社の紙製トランプ(BICYCLE)

BICYCLE(バイスクル) 808 ライダーバック STANDARD トランプ 赤 ポーカーサイズ

BICYCLE(バイスクル) 808 ライダーバック STANDARD トランプ 赤 ポーカーサイズ

余談だけど、トランプはプラスチックより紙製のほうが本格的なカードらしい。
当然、紙製のほうが安いのだが。
紙とは言ってもコーティングされてかなり弾力もあるのでシャッフルなどもやりやすい。

カジノなどで使用されているのも紙タイプ。(これじゃないけど)
イカサマ防止で定期的にカードを交換するため、破棄しやすい紙製なのだとどこかで読んだ気がするけど、そもそもプラスチックだとインクの張力でカードが反ってしまうらしい。

それで絵札と数字カードとの違いがバレてしまう可能性があるので、お金をかけた真剣勝負にプラスチック製のカードは使えないんだとか。

【情報ソース】
umi-cafe2.at.webry.info


さて、余談が長くなってしまった。
次回は実際にVBAでのマージソートのコードを紹介したいと思う。

Viewing all 493 articles
Browse latest View live