rsinz’s diary

趣味コードや日記のブログです。文章をうまく書けるようになりたい。

配列をもっと簡単に扱いたい

タイトル通り、VBAの配列を簡単に扱うクラスを公開します。

GitHubからArrayEx.clsをダウンロードして、VBEにインストールしてください。
vba/Class at master · rsinz/vba · GitHub

使い方
一部の機能のサンプルです。

たとえば、以下のようなデータ表があるとします。

f:id:rsinz:20180428160611p:plain
この表の、各商品の「個数の合計」と「合計金額の平均」を算出してみます。

Sub Test()

  ' まずはデータ表全体を取り込みます
    Dim 売上 As New ArrayEx
    売上.CurrentRegion = Range("B3")
    
    ' 一意の商品名リストを取り出します。この場合、"りんご"、"みかん"、"ぶどう"が商品名に格納されます
    Dim 商品名リスト As New ArrayEx
    商品名リスト = 売上.Slicer(1).GroupBy   ' 1列目を縦にスライスして、重複削除しています
    
    ' それぞれの商品における、個数の合計と金額の平均を出してみます
    For Each 商品名 In 商品名リスト.value
        
        Debug.Print 商品名 & "の合計個数 : ";
        Debug.Print 売上.Init.Filter_(商品名, 1).Sum_(2)      ' 1列目を商品名でフィルターして、2列目の合計を出します
        
        Debug.Print 商品名 & "の平均売上高 : ";
        Debug.Print 売上.Init.Filter_(商品名, 1).Average_(4)      ' 1列目を商品名でフィルターして、4列目の平均を出します
        
    Next
    
End Sub

(上記のコードを実行し、イミディエイトウィンドウで確認してみてください)

コードを見てもらうと分かるように、メソッドチェーンをしています。
なので、どうしても内部で計算過程を保持しておく必要があり、初期に取り込んだデータに戻したい場合は[.Init]メソッドを実行してください。



終わりに
ArratExは一次元と二次元の配列を意識して、簡単に処理できるように作りました。
今回紹介したクラスで少しは、配列処理をシンプルに書けるようになると思います。
バグ等ありましたら、コメントにて教えていただけると助かります。

一部、いげ太様のAriawaseを参考にしています。いつもお世話になっております。
github.com

プリミティブ変数型を拡張したい(String編)

VBAのプリミティブ型(IntegerとかStringとか)に.netっぽく書けるように、オリジナルの機能を付けたいと思い、コードを書きました。

GitHubからclsStringをダウンロードして、VBEにインストールしてください。
vba/Class at master · rsinz/vba · GitHub


以下、クラスの機能です

Property
Value: Get/Setプロパティ。デフォルトプロパティに設定しています。
Plus: 内部保持値に結合
PlusAndLine: 内部保持値に改行して結合
ToInt: Integer型にして返す
ToLong: Long型にして返す
ToLower: 大文字 -> 小文字にして返す
ToNarrow: 全角-> 半角 にして返す
ToWide: 半角-> 全角 にして返す
IsString: String型かどうか判定
IsEmpty_: 空文字かどうか判定
Length: 文字列の長さを返す

Method
Mid_: 指定された文字以降(以前)の文字列を抜き出す
Insert: 文字列の挿入
InStr_: 指定文字列の最初(最左)の位置。StringExを直接InStr()に入れるとバグるため。
InStrLast: 指定文字列の最後(最右)の位置。
CountOf: 指定文字列がいくつ含まれているか
GetClipboard: クリップボードのテキスト取得
SetClipboard: クリップボードにテキストを設定
ToText: テキストファイルに文字列追加
Log: Logテキストファイルに出力。Logファイルが無かったら作る
RegexInit: Regexの初期化。正規表現を使いたいときに (ユーザー入力の妥当性チェックとか)
RegexIsMatch: 指定文字列にパターンが含まれているかどうか
RegexMatch: 指定文字列内にパターンが含まれていたら一致した最初の文字列だけ返す
RegexMatches: 指定文字列内にパターンが含まれていたらitemコレクションとして返す。
RegexReplace: 指定文字列内にパターンが含まれていたら指定文字列に置き換えて返す

使い方
一部の機能のサンプルです。

Sub testString()

    Dim str_ As New StringEx
    
    str_ = "abcABC"
    Debug.Print str_        ' abcABC
    
    Debug.Print str_.CountOf("a")       '  1
    
    Debug.Print str_.Insert(4, "_")      ' abc_ABC
    
    Debug.Print str_.InStr_("c")          '  3
    
    Debug.Print str_.ToUpper              ' ABCABC
    
    str_.SetClipboard
    Debug.Print str_.GetClipboard       ' abcABC
    
    str_.ToText ("C:\test.txt")

End Sub

思いつくままにプロパティ/メソッドを書きました。
あと、一部いげ太様のAriawaseを参考にしています。
github.com


※StringExを直接InStr関数に入れるとエラーが出ます。謎です。ReplaceとかTrim関数だとうまく動いてくれます。

内容は以上です。

次回はDate型に挑戦したいと思います。

プリミティブ変数型を拡張したい(Integer編)

VBAのプリミティブ型(IntegerとかStringとか)に.netっぽく書けるように、オリジナルの機能を付けたいと思い、コードを書きました。
 

GitHubからIntegerExをダウンロードして、VBEにインストールしてください。
vba/Class at master · rsinz/vba · GitHub


以下、クラスの機能です。

Property
Value: Get/Setプロパティ。デフォルトプロパティに設定しています。
Plus: 内部保持値に加算
Minus: 内部保持値に減算
ToInt: integer型にして返す
ToLong: long型にして返す
ToString: string型にして返す
IsNaturalNumber: 自然数か判定 (小数点やマイナス値、ゼロを許容しない)

 

Method
Plus1: インクリメント
Minus1: デクリメント
ARound: 数値の丸め込み
BinaryToDecimal: 2進数→10進数変換
DecimalToBinary: 10進数→2進数変換
HexToDecimal: 16進数→10進数変換
DecimalToHex: 10進数→16進数変換

 

使い方
一部の機能のサンプルです。

Sub testInt()

    Dim int_ As New IntegerEx

    int_ = 1

    int_.Plus = 2.1

    Debug.Print int_ ' 3.1

    Debug.Print int_.Plus1 ' 4.1

    Debug.Print int_.IsNaturalNumber ' False

End Sub

vbaを書いているとどうしても、int_ = int_ + 1を書かなきゃいけなくて煩わしく感じてしまいます。(C#の int_++ とか int_ += 1 が羨ましすぎますよね)
vbaでも出来るだけスタイリッシュに書きたいというのが今回の趣旨でした。

 

内容は以上です。
余談ですが、「Plus」の代わりに漢字で「十」を使ってみたりしましたが、ダサすぎたので辞めました。

次回はString型に挑戦したいと思います。

Excel VBAでモダンなForm(枠無しForm)

個人で使っているWindows10に慣れると、どうも会社でのWindows7のAeroにレガシー感を感じてしまいます。
今日は、VBAでWin10風(メトロスタイル?フラットスタイル?)のFormを作ってみようと思います。
最後にダウンロードURLを貼りますので、読むのが面倒な人はスクロールしてください。

0.準備

まずは、モジュールの準備です。Excelを開いて、Alt+F11でエディタを開きます。VBAProjectを選択した状態で、右クリックから挿入→ユーザーフォームとクラスモジュールを追加。

f:id:rsinz:20161230132245p:plain

名前をプロパティから、それぞれ「MainForm」「clsUserForm」に変更しておきます。

f:id:rsinz:20161230132348p:plain

1.枠無しFormをつくる

WinAPIを宣言します。以下のコードをクラスモジュールに記載します。

clsUserForm

Private Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc.dll" (ByVal IAcessible As Object, ByRef hWnd As Long) As Long
Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare PtrSafe Function DrawMenuBar Lib "user32" (ByVal hWnd As Long) As Long
Const GWL_STYLE = (-16&)
Const GWL_EXSTYLE = (-20&)
Const WS_CAPTION = &HC00000
Const WS_EX_DLGMODALFRAME = &H1&

実際の仕事をするファンクションもクラスに書いておきましょう。

Function NonTitleBar(objName As String) As Long

  Dim wnd As Long, formHeight As Double
  Dim uForm As Object

  For Each uf In VBA.UserForms
    If uf.Name = objName Then Set uForm = uf: Exit For
  Next

  formHeight = uForm.InsideHeight
  WindowFromAccessibleObject uForm, wnd

  SetWindowLong wnd, GWL_EXSTYLE, GetWindowLong(wnd, GWL_EXSTYLE) And Not WS_EX_DLGMODALFRAME
  NonTitleBar = SetWindowLong(wnd, GWL_STYLE, GetWindowLong(wnd, GWL_STYLE) And Not WS_CAPTION)

  DrawMenuBar wnd
  uForm.Height = uForm.Height - uForm.InsideHeight + formHeight
End Function

NonTitleBarは失敗すると0を返してきます。
続いてForm側です。

閉じる用のボタンを配置して、名前を「btnClose」にしておきます。
ボタンをダブルクリックして、フォームを閉じる処理を書きます。

f:id:rsinz:20161230132533p:plain

MainForm

Private Sub btnClose_Click()
  Unload Me
End Sub

UserFormのInitialize処理をコンボボックスから追加します。

f:id:rsinz:20161230132720p:plain

Initializeに先ほど作ったクラスを呼び出します。

Private clsForm As New clsUserForm1

Private Sub UserForm_Initialize()
  clsForm.NonTitleBar Me.Name
End Sub

F5で実行すると、画面中央に枠無しフォームが表示されると思います。

f:id:rsinz:20161230132845p:plain

2.ドラッグできるようにする

さらに、Formをドラッグで動かせるようにします。以下のコードをクラスに追加します。

clsUserForm

' 最上部に宣言
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare PtrSafe Sub ReleaseCapture Lib "user32.dll" ()
Private Const WM_NCLBUTTONDOWN = &HA1
Private Const HTCAPTION = 2

Sub FormDrag(objName As String, ByVal Button As Integer)

  Dim hWnd As Long
  Dim uForm As Object

  For Each uf In VBA.UserForms
    If uf.Name = objName Then Set uForm = uf: Exit For
  Next

  If Button = 1 Then
    WindowFromAccessibleObject uForm, hWnd
    ReleaseCapture
    Call SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&)
  End If

End Sub

Form側ではフォーム上でマウスダウンしたときに、FormDragを呼び出します。

MainForm

Private Sub UserForm_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  clsForm.FormDrag Me.Name, Button
End Sub

F5で実行して・・・どうでしょう。Formを掴めましたでしょうか?

3.おわりに

今回は「まず見た目から入りたい!」という人向けに書きました。これを機にVBA&WinAPIを学んでみたいという方が増えてくれることを願います。

最後に、自分が作ったテンプレートフォームのダウンロード用のURLを貼っておきます。
https://github.com/rsinz/vba/releases

こんな感じです。

f:id:rsinz:20161230132938p:plain

Dale Dauten :: 仕事は楽しいかね?

 日記:

仕事は楽しいかね?」を数年ぶりに読み返しました。読み易い翻訳とストーリーで、あっという間に読み終えました。

 年齢を重ねるごとに目標に縛られ、新しい挑戦をすること自体、避けてしまっている人も多いのではと思います。マックスおじいさん曰く、「本の内容を忘れなければ、10回中、8回の失敗で済むね。ガハハッ」だそうです。

良書です。でも8回の失敗続きの状態が辛いんだよなー。

 

 

仕事は楽しいかね?

仕事は楽しいかね?