t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA 「これはマクロ化しない」という決断も必要

VBAを覚えてマクロを作れるようになると、業務を手当たり次第自動化したくなるものだ。

しかし安易な自動化は慎むべきかもしれない。

このように考えるようになったのは、以下の記事がきっかけだった。
自動化をどこまで進めるか|会社で役立つリスクマネジメント|社団法人横浜中法人会

長いので私が特に感銘を受けた部分だけを抜粋する。

まず1つ目はこちら。

過度の自動化は人間が持っている「何かおかしい」といったシステムやハードを疑うというプロセスをなくす面も見逃せないし、更に異常時における緊急対応能力を削ぐ

VBAをフル活用すればボタン一つですべて作業が完了してしまうようなフルオートメーションを作ることもできる。しかし行き過ぎた自動化によって業務理解が疎かになってしまってはいざという時に大きなツケを払う羽目になるかもしれない。

2つ目はこちら。

NASA(米国航空宇宙局)では自動化しない仕事として以下の4つを挙げている
●作業者が特有のスキル、生きがいを感じている仕事
●非常に複雑であるとか、理解困難な仕事
●自動化によって作業現場で覚醒水準が低下するような仕事
●自動化が不具合の時、作業者が解決不可能な仕事

これは本当に目からうろこだった。
これまで私は「何を自動化しないか」という観点で考えたことがなかったからだ。

それから私は効率一辺倒だったこれまでの考え方を改め、個別の案件について本当にマクロ化がベストなのかどうか意識するようになった。

もちろん、マクロを作るという結論にいたる場合も多い。
誰もが嫌がる単純作業の繰り返しはコンピューターの得意分野だから迷わずマクロ化するし、人に業務をお願いする際複雑すぎると受け取ってもらえないのでマクロで単純化したりもする。しかしその場合もフルオートではなく、少しは人間が介在する形にしておきたい。

自分が何をやっているのか自覚できなくなるほどの自動化、つまり「よくわからないけどボタンを押してるだけ」というマクロ完全依存は避けたい。

何をマクロ化して、何をマクロ化しないか。その具体的な指針はまだ自分の中でも模索中で固まっていないのだけれど、効率化以外の視点も常に考えながら日々の業務改善に臨みたい。

最後に、VBAを絶賛学習中の方はこの記事は無視して何でもかんでも手当たり次第自動化してみると良い。学習テーマの確保はなかなか難しいので、みすみすチャンスを潰すこともない。

なんでも作れるようになったら、改めてこの問題について考えていただけると良いかなと思う。

VBA SmartArtを使ってシートごとに作業ステップを表示するバーを作成するマクロ

今回はVBAでSmartArtを使ってシートごとに作業ステップを表示するバーを作成してみようと思う。

作成物のイメージはこのようなもの。
f:id:t-hom:20170119111553p:plain

今回マクロで作成するのは上部のStepを表示するバーの部分だけ。

シート2ならStep2がハイライトされる。
f:id:t-hom:20170119105223p:plain

ひとつの業務に使用する一連のマクロをExcelブックにまとめてしまうことがよくある。
実行順が決まっており、それぞれシートに分かれている場合にこのようにステップが表示されていると気が利くと思われるかもしれない。

手でも作れるけれど、色の塗り分けが面倒なのと、ステップの変更などで何度も入れ替えたりする必要が出てくるかもしれないのでここはマクロでさくっと作れるようにしておきたい。

ということで早速完成コードの紹介。

Sub Stepバー作成()
    Dim sheetNo As Long
    For sheetNo = 1 To ThisWorkbook.Worksheets.Count
        'Stepバー生成
        Dim ws As Worksheet: Set ws _
            = ThisWorkbook.Worksheets(sheetNo)
        Dim sh As Shape: Set sh _
            = ws.Shapes.AddSmartArt(Application.SmartArtLayouts( _
                "urn:microsoft.com/office/officeart/2005/8/layout/hChevron3"), 5, 5)
        sh.Height = 20
        sh.Width = 500
        
        '一旦ノードを全削除
        Dim i As Long
        For i = 1 To sh.SmartArt.Nodes.Count
            sh.SmartArt.Nodes.Item(1).Delete
        Next
        
        'ブックの数だけノードを追記しつつ、
        '対象ブックのStepだけオレンジに、それ以外はグレーアウト
        Dim stepNo As Long
        For stepNo = 1 To ThisWorkbook.Worksheets.Count
            With sh.SmartArt.Nodes.Add
                .Shapes(1).Fill.ForeColor.RGB = IIf(sheetNo = stepNo, _
                    XlRgbColor.rgbOrange, _
                    XlRgbColor.rgbLightGray)
                .TextFrame2.TextRange.Text = "Step" & stepNo
            End With
        Next
    Next
End Sub

使い方は、シートをあらかじめ必要な枚数分追加しておいてこのマクロを実行するだけ。
するとシートの枚数に応じたStep数でバーが作成される。

このマクロはShape型、SmartArt型、SmartArtLayout型、SmartArtNode型の4つの関係を押さえておくと理解しやすい。が、これらについてはMSDNなどに説明を譲る。

オブジェクトブラウザやTypeName関数で、それぞれが持つプロパティがどのようなデータ型を返すのかを把握するのが理解のポイントである。

Application.SmartArtLayoutsにURLみたいな引数を渡している部分があるが、これはSmartArtの種類を示すIDである。覚えられるものではないし、マクロの記録で調べられるのでそこは気にしなくて良い。

あ、後で気づいたけど、この手のマクロはパワポのほうが活用できそう。

VBA デザインしたユーザーフォームを元にNewで複数のフォームインスタンスを作る。

VBAでユーザーフォームはふつう、単一のオブジェクトとして扱う。

たとえば以下のようなフォームを作ったとしよう。
f:id:t-hom:20170117201737p:plain

オブジェクト名は「frm成績」としたので、このフォームを表示させるコードはこうだ。

Sub hoge()
    frm成績.Show
End Sub

この時フォームは、「frm成績」オブジェクトとして扱われいてる。

さて、たとえばこのフォームを3つ同時に起動させたいとする。
どうすればいいか。

まずフォームのShowModalがTrueのままでは3つ同時に表示させることはできないので、これをFalseにしておく。
f:id:t-hom:20170117202340p:plain

ShowModalとは、フォームを表示させた際にそのフォームを閉じるまで次に進めないか、それとも次のコードをそのまま実行させるかというプロパティで、FalseにするとフォームをShowしたあと閉じられるのを待たずに次のコードに進む。

そして記述するコードは次のとおり。

Sub hoge()
    Dim f As frm成績
    Set f = New frm成績
    f.Show
    Set f = New frm成績
    f.Show
    Set f = New frm成績
    f.Show
End Sub

実行するとフォームは1つに見えるが、重なっているだけでずらしてみるとこのとおり。
f:id:t-hom:20170117203051p:plain

つまりユーザーフォームはオブジェクト(インスタンス)でありながら、クラスのようにも振る舞うことができるのだ。なんとも不思議な特性である。クラスモジュールはインスタンス化して初めてつかえる。シートなどのオブジェクトモジュールは単一オブジェクトとしてのみ扱え、Newはできない。対してフォームモジュールは単一オブジェクトとして扱うこともできるし、クラス(オブジェクトのひな形)として扱うこともできるのだ。


さて、これが何に使えるのかというとそこはみなさんの創造力次第。

私がひとつ思いついたのは、データをフォームに読み取ってカードのように並べて表示させたいケース。

単一フォームで対応するのは難しいが、この手法なら大丈夫。

たとえばSheet1に以下のようなデータがあるとする。
f:id:t-hom:20170117203344p:plain

Sheet1モジュールに以下のコードを記入し、

Enum 列
    名前 = 1
    国語
    数学
    理科
    社会
End Enum

Sub FormからFormをNew()
    Dim C As Collection
    Set C = New Collection
    Dim frm As frm成績
    
    Dim i
    For i = 2 To 4
        Set frm = New frm成績
        frm.lblName = Cells(i,.名前).Value
        frm.txt国語 = Cells(i,.国語).Value
        frm.txt数学 = Cells(i,.数学).Value
        frm.txt理科 = Cells(i,.理科).Value
        frm.txt社会 = Cells(i,.社会).Value
        frm.Show
        frm.Left = frm.Left + (i - 3) * frm.Width
    Next
End Sub

実行するとこのとおり。
f:id:t-hom:20170117203710p:plain

あるいはデータを格納するためのPersonクラスを作成し、そのクラスにフォームのインスタンスを持たせるとか。。
そうすれば各Personインスタンスがそれぞれ別個のユーザーインターフェースを持つことができる。

他にも工夫次第で色々と面白いことができそうな気がする。

VBA 参照設定とCreateObjectを定数1つで切り替えるテクニック

VBAで外部のオブジェクトを扱うには、事前バインド方式と遅延バインド方式の2種類がある。事前バインドとはいわゆる参照設定のことで、遅延バインドとはCreateObject関数によるオブジェクト生成を指す。

さて、どちらが良いかと言われると一長一短なので困る。
メリット・デメリットは概ねこんなかんじ。

      事前バインド(参照設定) 遅延バインド(CreateObject)
メリット  型がきっちり決まるのでコーディング中に入力補完の恩恵を受けられる。 コードだけで完結するのでネットで公開するのが楽。
デメリット コードだけで完結しないのでネットにコードを掲載する場合に説明が面倒くさい。ライブラリのバージョン違いなどで別のPCでは参照設定しなおさないと動作しないケースがある。 実行するまで型が決まらないのでコーディング中に入力補完の恩恵を受けられない。

事前バインディングのほうが若干スピードも速いんだけれど、微々たるものなので今回は無視する。

では具体的に例を挙げて説明する。

以下はどちらもFileSystemObjectを利用してCドライブ配下のフォルダ数を取得するプログラムだ。

Sub 事前バインド方式()
    '↓参照設定しているので固有型が使用できる。
    Dim fso As FileSystemObject
    '↓参照設定しているので固有型を用いたNewによるオブジェクト生成ができる。
    Set fso = New FileSystemObject
    '↓コーディング中にプロパティ・メソッドの入力候補が表示される。
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

Sub 遅延バインド方式()
    '↓参照設定していないので固有型は使用できない。
    Dim fso As Object
    '↓参照設定していないので固有型を用いたNewによるオブジェクト生成はできない。
    Set fso = CreateObject("Scripting.FileSystemObject")
    '↓コーディング中にプロパティ・メソッドの入力候補が表示されない。
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

さて、プログラムを書くときは入力候補が表示されたほうが助かるので開発中は参照設定、配布するときはバージョンの違いなどを吸収してほしいのでCreateObjectを使いたいとする。

いちいちコードを書き直すのは面倒なので何か良い手はないか。

そこで今回ご紹介するのは、プリプロセッサディレクティブを用いて定数1つで事前バインド方式と遅延バインド方式を切り替える方法だ。

なにやらややこしい単語であるが、プリ(事前の)プロセッサ(プロセスの)ディレクティブ(指示)ってことで、もう少し簡単にいうと「このマクロを実行する前にあらかじめやっておいてね」という指示になる。

種類もそんなに無いのですぐ覚えられると思う。

ではサンプルコード。

#Const REF = True

Sub 定数REFにより切り替え可能()
#If REF Then
    Dim fso As FileSystemObject
    Set fso = New FileSystemObject
#Else
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
#End If
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

この、ナンバーサイン(#)で始まっている部分がプリプロセッサディレクティブだ。
このマクロは実行の前にまず定数REFによってIf文が判定され、以下のように整形される。
※整形といっても内部的なものなので、実際にコードが書き換わるわけではない。

Sub 定数REFにより切り替え可能()
    Dim fso As FileSystemObject
    Set fso = New FileSystemObject
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

もし#Const REF = Falseとしていたら、こうなる。

Sub 定数REFにより切り替え可能()
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

それから実行されるのだ。

あるいはオブジェクトの生成部分はまとめてしまってもよい。

#Const REF = True

Sub 定数REFにより切り替え可能()
#If REF Then
    Dim fso As FileSystemObject
#Else
    Dim fso As Object
#End If
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

整形するとこのようになる。

Sub 定数REFにより切り替え可能()
    Dim fso As FileSystemObject
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

fsoは固有型で宣言しているが、オブジェクトの生成はCreateObjectでしている。
結局入力補完が効くかどうかは変数の型で決まるのでオブジェクトの生成自体はどちらでも良い。

こうすると参照設定にしておいて、配るときだけ定数REFをFalseにすれば良くなるのでメンテナンスはしやすい。
まぁ、このために行数が増えるのはすこし難点ではあるが。。

VBA ウォッチウインドウとステップ実行でオブジェクト変数の仕組みを学ぶ

以前クラスモジュールの入門記事でオブジェクト変数はタグのようなものだと書いた。
https://thom.hateblo.jp/entry/2016/12/31/013555#オブジェクト変数は箱じゃなくてネームタグでイメージしよう

具体的な仕組みは以下の記事で書いた。
thom.hateblo.jp

とはいえメモリの動きなどは座学ではなかなかイメージしづらいので、今回はウォッチウインドウとステップ実行を使って具体的なメモリのアドレスを確認していこうと思う。

使用するのは、VarPtr関数とObjPtr関数だ。

ウォッチウインドウを使用する前に、基本的な使い方を押さえておこう。
まずVarPtrは、変数が指す実際のメモリアドレスを取得するための関数だ。

次のコードを実行してみてほしい。

Sub 基本型の変数()
    Dim L As Long
    L = 10
    Debug.Print "変数Lはアドレス" & VarPtr(L) & "を指している。"
    Debug.Print "アドレス" & VarPtr(L) & "に" & L & "が入っている。"
End Sub

私の環境ではイミディエイトウインドウに次のように表示された。

変数Lはアドレス1896880を指している。
アドレス1896880に10が入っている。

アドレスの数値は実行環境やタイミングによって変わるので皆さんの環境ではまた違った結果になると思う。

基本型の変数はこのように、「変数→メモリアドレス→中身」という仕組みになっている。
対してオブジェクト型の場合は、「変数→メモリアドレス→メモリアドレス→中身」という仕組みだ。
1つ目のメモリアドレスの中身に2つ目のメモリアドレスが入っていて、間接的に中身を指している。

この2つ目のメモリアドレスを取り出すための関数がObjPtrである。

次のコードで確かめてみよう。

Sub オブジェクト型の変数()
    Dim O As Collection
    Set O = New Collection
    O.Add 10
    Debug.Print "変数Oはアドレス" & VarPtr(O) & "を指している。"
    Debug.Print "アドレス" & VarPtr(O) & "にアドレス" & ObjPtr(O) & "が入っている。"
    Debug.Print "アドレス" & ObjPtr(O) & "に" & TypeName(O) & "が入っている。"
    Debug.Print TypeName(O) & "のItem(1)に" & O.Item(1) & "が入っている。"
End Sub

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

変数Oはアドレス1896880を指している。
アドレス1896880にアドレス88747728が入っている。
アドレス88747728にCollectionが入っている。
CollectionのItem(1)に10が入っている。

つまり「変数O→1896880→88747728→Collection」という構成。

さて間接参照になっているということを理解したところで、ウォッチウインドウでその動きを見ていこう。
対象のオブジェクトは今回は自作することにした。

まずクラスモジュールを挿入し、以下の1行を記入する。今回クラスモジュール名はClass1のまま変更しない。

Public Value As Long

これはValueというパブリック変数のみを持つクラスで実用性はまったくないが今回はオブジェクト変数の仕組みを知るためなのでこれで良い。

次に標準モジュールに以下のコードを書く。

Sub hoge()
    Dim a As Class1
    Set a = New Class1
    a.Value = 10
    
    Dim b As Class1
    Set b = New Class1
    b.Value = 20

    Debug.Print a.Value
    Debug.Print b.Value
End Sub

次に、表示メニューからウォッチウインドウを表示させ、右クリックメニューからウォッチ式の追加をクリックする。
f:id:t-hom:20170116001050p:plain

そして式欄に「VarPtr(a)」と入れ、OKを押す。
f:id:t-hom:20170116001406p:plain

するとこのように一行ウォッチ式が追加される。
f:id:t-hom:20170116001638p:plain

同じようにして「VarPtr(b)」「ObjPtr(a)」「ObjPtr(b)」も追加しよう。

このように表示されたら準備完了。
f:id:t-hom:20170116001842p:plain

F8キーでステップ実行を開始すると、早速VarPtr(a)とVarPtr(b)の値が確定する。
f:id:t-hom:20170116002014p:plain
どうやら変数のアドレスは宣言文より前、実行直後に決まるようだ。

ちなみにステップ実行では、黄色く表示されているところがこれから実行するコードである。すでに実行されたコードと勘違いしやすいので注意。

Set a = New Class1の実行がおわると、ObjPtr(a)にアドレスが入る。
f:id:t-hom:20170116002506p:plain
つまりアドレス88522536に実際のオブジェクトが生成されたということ。
こういう状態:「変数a→1896880→88522536→オブジェクト」

そしてSet b = New Class1の実行が終わると、ObjPtr(b)にもアドレスが入る。
f:id:t-hom:20170116002801p:plain

別のオブジェクトなのでアドレスも異なる。

では次に、コードを書き換えて実行してみる。

Sub hoge()
    Dim a As Class1
    Set a = New Class1
    a.Value = 10
    
    Dim b As Class1
    Set b = New Class1
    b.Value = 20
    
    Dim c As Class1
    Set c = a
    Set a = b
    Set b = c

    Debug.Print a.Value
    Debug.Print b.Value
End Sub

ウォッチ式にも「ObjPtr(c)、VarPtr(c)」をそれぞれ追加しよう。

ステップ実行で下図の位置まですすめる。
f:id:t-hom:20170116003308p:plain
※この時点でSet c = aはまだ未実行であることに注意。

現時点では、変数aが88521736にあるオブジェクトを指していて、変数bは88522536にあるオブジェクトを指している。

次にSet c = aを実行すると、変数cも88521736にあるオブジェクトを指すようになる。
f:id:t-hom:20170116003620p:plain

つまり、変数aと変数cは全く同一のオブジェクトを参照していることになる。

次にSet a = bを実行すると、変数aは88521736を参照するのをやめて、変数bと同じ88522536のオブジェクトを参照するようになった。
f:id:t-hom:20170116003752p:plain

最後にSet b = cが実行されると、変数bは88522536の参照をやめて、変数cと同じ(つまり当初変数aが参照していた)88521736を参照するようになる。
f:id:t-hom:20170116003908p:plain

これで変数aが指すオブジェクトと変数bが指すオブジェクトが入れ替わったことがわかる。

さて、次はコレクションを使って見ていこう。

コードは以下のとおり。

Sub hoge()
    Dim a As Class1
    Dim col As Collection
    Set col = New Collection
    For i = 1 To 3
        Set a = New Class1
        col.Add a
    Next
End Sub

ウォッチ式はいったんすべて削除し、新たに「ObjPtr(a)」「ObjPtr(col.Item(1))」「ObjPtr(col.Item(2))」「ObjPtr(col.Item(3))」「i」を追加しておく。

ステップ実行を1周目のFor分の終わりまですすめる。
f:id:t-hom:20170116004755p:plain
すると変数aとcol.Item(1)は同じオブジェクトを参照していることがわかる。

次に2周目のSet a = New Class1実行直後まですすめる。
f:id:t-hom:20170116004924p:plain
すると変数aが指すオブジェクトが変わったが、col.Item(1)は先ほど保持させたオブジェクトのままである。

次の行を実行するとcol.Item(2)に今aに入っているオブジェクトが保持された。
f:id:t-hom:20170116005043p:plain

以下、3周目の終わり。
f:id:t-hom:20170116005158p:plain

変数aは使い回ししてもコレクションのそれぞれのアイテムは別のオブジェクトを保持していることになる。

「コレクションは変数を保持する」という勘違いがよく見受けられるが、コレクションが保持するのは変数ではなくその変数の中身である。

「コレクション.Add 変数」という書き方が混乱の原因だと思う。

これは基本型の変数で試してみるとわかる。

Sub hoge()
    Dim a As Long
    a = 10
    
    Dim col As Collection
    Set col = New Collection
    
    col.Add a
    a = 20
    
    Debug.Print col.Item(1)
End Sub

もしコレクションが変数aそのものを保持するなら、イミディエイトウインドウには20が出力されるはずだが、実際には10が出力される。

以上

ウォッチウインドウについて

ウォッチウインドウはローカルウインドウほど簡単ではないけれど、ローカルウインドウと比べて、

  • 指定したものだけをウォッチできる
  • 任意の式をウォッチできる

という特徴がある。

ローカルウインドウが基本的に変数しか見られないのに対し、ウォッチウインドウで使える任意の式はかなり強力だ。

ウォッチウインドウをうまく使うには、「式」とは何なのかが分かってないと辛い。
thom.hateblo.jp

私は最初、式って何なのか分かってなかったので、数学風に「a + b =」とか、Excel風に「= i * 2」と入力してエラーがでて使用を諦めていた。分かってしまえば簡単なんだけれど、これらはVBAでいう式ではない。イコールは不要だ。イコールを使用するのは比較演算子としてBoolean型を返す式を作るときだけで、この場合は右辺・左辺が両方必要になる。

その他、具体的な使い方の例。
thom.hateblo.jp

ExcelVBAの開発用アドインをGithubで公開してみた

ExcelVBA開発用のアドインをGithubで公開してみたのでご自由にお使いください。
ただしトラブル発生時は自己責任でお願いします。

URLはこちらです。
https://github.com/thom-jp/thoms_ExcelAddIn_for_VBADeveloper

ファイルの直接ダウンロードはこちらです。
https://github.com/thom-jp/thoms_ExcelAddIn_for_VBADeveloper/raw/master/bin/thoms_VBE_AddIn.xlam

利用するにはマクロのセキュリティで以下のチェックを入れる必要があります。
f:id:t-hom:20170115112424p:plain
ただしここにチェックを付けるとセキュリティが甘くなります。
この行為が何を意味するのか分かる方のみ、チェックして使ってください。

ただ、今のところできることはこれだけしかありません。
f:id:t-hom:20170115100855p:plain

参照設定改は割と使えるんじゃないかと思ってます。
thom.hateblo.jp

カラーパレットはこちらの記事で紹介したものです。
thom.hateblo.jp

インデント調整用のマクロはこちらの記事で紹介したものをベースに、もう少し色々な命令に対応させています。
thom.hateblo.jp

ただ実行すると空行を詰めてしまうので好みは分かれるかもしれません。
以下の設定をしておくと行が詰まってもそれほど見づらくならないのでおすすめです。
thom.hateblo.jp

メニューのベースになっているのはこちらで紹介したマクロです。
thom.hateblo.jp

自分で簡単にマクロを追加できるので、何か良いアイデアがあれば自由に改変していただいて構いません。MITライセンスで公開したので再配布も自由です。ただし改変して再配布する場合はプロジェクト内のどこかしらのモジュールにこのブログのURLをコメントしてください。

以下参考

thom.hateblo.jp

VBAを写経しながらタイピング練習するツールを作成

今回作成したのは、以下のようなツール。
※GIFアニメなので読み込み終わって動き出すまで時間かかるかもしれません。
f:id:t-hom:20170112232253g:plain

サンプルコードが表示され、その通りに入力していくだけのシンプルなものであるが、次に入力すべきキーとそれを押す指の名前が赤く表示される。間違えると入力欄が赤くなる。

経緯

プログラミングを覚えるには、とにかくたくさんコードを手入力することが大切である。
thom.hateblo.jp

ここでキー入力が速いと数をこなすことができるので、上達も早くなる。
しかし最近出会ったとある方はプログラミングをやりたいけれどまだタッチタイプが出来ないという。その方はまだ20代。
若者は皆パソコンを使いこなすと思っていたんだけれど、ひょっとするとスマホの登場でそういう時代はもうとっくに終わったのかもしれない。

昔は一家に一台パソコン、そのあとに一人に一台パソコンの時代(持ってなかった方、ゴメン)と言われていたんだけれど、タッチデバイスの普及によってパソコン初心者は逆に増えているのかもしれない。

でもスマホの普及で逆に万人がアプリ、つまりソフトウェアを日常的に便利に使うようになっているので、自分で作ってみたいという方も増えてくるハズ。

となるとやはりパソコンが使うハメになるわけで、まずはタッチタイプから覚えていくことになる。でもどうせやるならコードを書きながらタッチタイプを覚えたいよね。

というのが作成の経緯。

中身のコード

プロトタイプなので見せられたものではないが、見せてしまおう。
コードはすべてフォームに記載している。この時点で設計としてはよろしくないのだけれど、まぁ今回は適当に。。

フォームのパーツはこんな感じ。
f:id:t-hom:20170112235050p:plain

あれ、キーは?と思われるかもしれないが、キーのラベルは手で配置するのが面倒なのでフォームを起動したときに自動で生成されるようにコードで書いている。

フォームのコードはこんなかんじ。きったなーい。

Private KeyCollection As Collection
Const KeyLabels = "1234567890-^\qwertyuiop@[asdfghjkl;:]zxcvbnm,./\"
Const ShiftLabels = "!""#$%&'() =~|QWERTYUIOP`{ASDFGHJKL+*}ZXCVBNM<>?_"
Const Fingers = "123344556788812344556788812344556788812344556788"
Const FingerNames = "小指 薬指 中指 人差し指 人差し指 中指 薬指 小指"
Private Sub TextBox1_Change()
    x = Mid(Label1.Caption, Len(TextBox1.Text) + 1, 1)
    Call AssignKey(InStr(1, ShiftLabels, x) > 0)
    
    Dim a: a = InStr(1, KeyLabels, x)
    If a = 0 Then a = InStr(1, ShiftLabels, x)
    lblLeftHand.Caption = ""
    lblRightHand.Caption = ""
    If a <> 0 Then
        Index = CLng(Mid(Fingers, a, 1)) - 1
        cap = Split(FingerNames)(Index)
        If Index >= 4 Then
            lblRightHand.Caption = cap
        Else
            lblLeftHand.Caption = cap
        End If
    End If
        
        
    
    Dim C As MSForms.Control
    For Each C In Me.Controls
        If TypeName(C) = "Label" Then
            If Len(C.Caption) = 1 Then
                C.BackColor = vbWhite
                If C.Caption = x Then
                    C.BackColor = vbRed
                End If
            End If
        End If
    Next
    If TextBox1.Text <> Left(Label1.Caption, Len(TextBox1.Text)) Then
        TextBox1.BackColor = vbRed
    Else
        TextBox1.BackColor = vbWhite
    End If
End Sub

Private Sub UserForm_Initialize()
    Set KeyCollection = New Collection
    Call CreateLabels
    Call TextBox1_Change
End Sub

Sub AssignKey(Optional shift As Boolean = False)
    If shift Then
        lblShiftR.BackColor = vbRed
        lblShiftL.BackColor = vbRed
    Else
        lblShiftR.BackColor = vbWhite
        lblShiftL.BackColor = vbWhite
    End If
    Dim str: str = IIf(shift, ShiftLabels, KeyLabels)
    For i = 1 To KeyCollection.Count
        KeyCollection(i).Caption = Mid(str, i, 1)
    Next
End Sub

Sub KeyActivate(C As String)
    Dim loc As Variant
    loc = InStr(1, KeyLabels, C, vbBinaryCompare)
    KeyCollection(loc).BackColor = vbRed
End Sub

Sub CreateLabels()
    OffsetY = 10
    OffsetX = 10
    
    arr = Array(12, 11, 11, 10)
    Dim L As MSForms.Label
    Dim C As MSForms.Control
    For j = 0 To 3
        For i = 0 To arr(j)
            Set L = Me.Controls.Add("Forms.Label.1")
            Set C = L
            L.Font.Size = 30
            L.BackColor = vbWhite
            L.TextAlign = fmTextAlignCenter
            C.Width = 30
            C.Height = 30
            C.Top = j * (C.Height + 5) + OffsetY
            C.Left = i * (C.Width + 5) + OffsetX
            KeyCollection.Add C
        Next
        OffsetX = OffsetX + (C.Width \ 2)
    Next
    Set L = Me.Controls.Add("Forms.Label.1")
    Set C = L
    L.Font.Size = 30
    L.BackColor = vbWhite
    L.TextAlign = fmTextAlignCenter
    L.Caption = " "
    C.Width = 100
    C.Height = 30
    C.Top = j * (C.Height + 5) + OffsetY
    C.Left = 3 * (35) + OffsetX
    
End Sub

変数宣言もしてたりしてなかったり、変数名もあまりよく考えずにつけている。似たようなものを作りたい方がいたらパクってもらって良いけど、汚いコードまでマネしないように。。

気力が乗ったら綺麗に整理しなおそう。

そしてフォームに表示させるサンプルコードはまさかの直書きである。
「次の練習コード」などのボタンをつけるつもりなんだけれど、まだその部分は考えてなくて未実装。

まぁ、試作品みたいなものなので大目に見てもらおう。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。