エクセルVBA オブジェクト指向備忘録クラス (コレクションの要素の加算と書き方4パターン)

20240328 エクセルVBAクラス備忘録 

エクセルVBA オブジェクト指向備忘録クラス

 

<前書き>
エクセルVBAオブジェクト指向、初心者です。
VBA自体は、以前から使用していました。
オブジェクト指向というものがあることは、知っていましたが、ネットなどを見てもよく分からず、挫折した過去あり。
<以上前書き>

 

今回の疑問
コレクション(collectionオブジェクト)は加算できるのか?
素人ですみません。ディクショナリは、集計できます。
コレクションて、集計でなく、その内容を変えること(加算とか)はできるのか?
(まあ、できるよね)
ということを、オブジェクト指向のコーディングに近づけて考えてみました。

下記に4個のコードを書いています。同じ挙動ですが、オブジェクト指向の度合いが高くなっていきます。
参考HP:「いつも隣にITのお仕事」様(エクセルVBAでクラスモジュールを使っての独自のコレクションを作る方法他の記事)
(感謝いたします。)
(もし、掲載で不都合ありましたら、すぐに削除いたします。)

時間なかったので、部分部分、Bing様に聞いています。
(感謝いたします。)
(もし、掲載で不都合ありましたら、すぐに削除いたします。)

 

コード1
//////////////////////////////////////
' 野菜クラスモジュール(VegetableClass)
Option Explicit

Private m_Type As String
Private m_Quantity As Double
Private m_Amount As Double


Public Property Let vegeType(ByVal p_Type As String)
    m_Type = p_Type
End Property

Public Property Get vegeType() As String
    vegeType = m_Type
End Property


Public Property Let Quantity(ByVal p_Quantity As Double)
    m_Quantity = p_Quantity
End Property

Public Property Get Quantity() As Double
    Quantity = m_Quantity
End Property

 

Public Property Let Amount(ByVal p_Amount As Double)
    m_Amount = p_Amount
End Property

Public Property Get Amount() As Double
    Amount = m_Amount
End Property


/////////////////////////////////

' 親モジュール
Sub Main()
    Dim veggieCollection As Collection
    Set veggieCollection = New Collection

    ' 人参のインスタンスを作成してコレクションに追加
    Dim carrot As New VegetableClass  'インスタンス
    carrot.vegeType = "人参"
    carrot.Quantity = 100
    carrot.Amount = 100
    veggieCollection.Add carrot, carrot.vegeType

    ' 大根のインスタンスを作成してコレクションに追加
    Dim radish As New VegetableClass      'インスタンス
    radish.vegeType = "大根"
    radish.Quantity = 50
    radish.Amount = 500
    veggieCollection.Add radish, radish.vegeType

    ' 人参の本数を10加算
    carrot.Quantity = carrot.Quantity + 10

    ' 人参の金額を50加算
    carrot.Amount = carrot.Amount + 50

    ' 結果を表示
    Debug.Print "人参の本数: " & carrot.Quantity
    Debug.Print "人参の金額: " & carrot.Amount
    
    Stop    'veggieCollectionでItem1、Item2が取れた。
End Sub

 

--------------------------------------------
コード2
//////////////////////////////////////
' 野菜クラスモジュール(VegetableClass)はコード1と変わらず。

/////////////////////////////////////
'野菜のコレクションを独自コレクションに設定(Vegetables)
Public Items As Collection

////////////////////////////////////
' 親モジュール
Sub Main()
    Dim veggieCollection As Vegetables
    Set veggieCollection = New Vegetables
    
    Set veggieCollection.Items = New Collection  'Itemsをインスタンス

    ' 人参のインスタンスを作成してコレクションに追加
    Dim carrot As New VegetableClass  'インスタンス
    carrot.vegeType = "人参"
    carrot.Quantity = 100
    carrot.Amount = 100
    veggieCollection.Items.Add carrot, carrot.vegeType    'Itemsはcollectionなのでadd使える

    ' 大根のインスタンスを作成してコレクションに追加
    Dim radish As New VegetableClass      'インスタンス
    radish.vegeType = "大根"
    radish.Quantity = 50
    radish.Amount = 500
    veggieCollection.Items.Add radish, radish.vegeType

    ' 人参の本数を10加算
    carrot.Quantity = carrot.Quantity + 10

    ' 人参の金額を50加算
    carrot.Amount = carrot.Amount + 50

    ' 結果を表示
    Debug.Print "人参の本数: " & carrot.Quantity
    Debug.Print "人参の金額: " & carrot.Amount
    
    Stop    'veggieCollectionでItem1、Item2が取れた。しかし独自クラスではない。「いつも隣にIT」様の独自コレクションを読んで直す予定
End Sub

 

--------------------------------------------
コード3(コード2とコード3は微妙な差しかないです。)
//////////////////////////////////////
' 野菜クラスモジュール(VegetableClass)はコード1と変わらず。

/////////////////////////////////////
'野菜のコレクションを独自コレクションに設定(Vegetables)
Public Items As Collection

Private Sub Class_Initialize()  ’独自クラスのinitializeで、Itemsをコレクションしてしまう。

    Set Items = New Collection  
End Sub

/////////////////////////////////////
' 親モジュール
Sub Main()
    Dim veggieCollection As Vegetables
    Set veggieCollection = New Vegetables
    
'    Set veggieCollection.Items = New Collection  'Itemsをインスタンス化 class_initializeにする(ここコメントアウト)

    ' 人参のインスタンスを作成してコレクションに追加
    Dim carrot As New VegetableClass  'インスタンス
    carrot.vegeType = "人参"
    carrot.Quantity = 100
    carrot.Amount = 100
    veggieCollection.Items.Add carrot, carrot.vegeType    'Itemsはcollectionなのでadd使える

    ' 大根のインスタンスを作成してコレクションに追加
    Dim radish As New VegetableClass      'インスタンス
    radish.vegeType = "大根"
    radish.Quantity = 50
    radish.Amount = 500
    veggieCollection.Items.Add radish, radish.vegeType

    ' 人参の本数を10加算
    carrot.Quantity = carrot.Quantity + 10

    ' 人参の金額を50加算
    carrot.Amount = carrot.Amount + 50

    ' 結果を表示
    Debug.Print "人参の本数: " & carrot.Quantity
    Debug.Print "人参の金額: " & carrot.Amount
    
    Stop    'veggieCollectionでItem1、Item2が取れた。
End Sub

 

----------------------------------------------------
コード4
//////////////////////////////////////
' 野菜クラスモジュール(VegetableClass)はコード1と変わらず。

//////////////////////////

'野菜のコレクションを独自コレクションに設定(Vegetables)
Public Items As Collection

Private Sub Class_Initialize()
'
    Set Items = New Collection
    
        ' 人参のインスタンスを作成してコレクションに追加
    Dim carrot As New VegetableClass  'インスタンス
    carrot.vegeType = "人参"
    carrot.Quantity = 100
    carrot.Amount = 100
    Items.Add carrot, carrot.vegeType    'Itemsはcollectionなのでadd使える

    ' 大根のインスタンスを作成してコレクションに追加
    Dim radish As New VegetableClass      'インスタンス
    radish.vegeType = "大根"
    radish.Quantity = 50
    radish.Amount = 500
    Items.Add radish, radish.vegeType

    ' 人参の本数を10加算
    carrot.Quantity = carrot.Quantity + 10

    ' 人参の金額を50加算
    carrot.Amount = carrot.Amount + 50

    ' 結果を表示
    Debug.Print "人参の本数: " & carrot.Quantity
    Debug.Print "人参の金額: " & carrot.Amount
    
End Sub

/////////////////////////////////////////
' 親モジュール
Sub Main()
    Dim veggieCollection As Vegetables
    Set veggieCollection = New Vegetables
    

    
    Stop    'veggieCollectionでItem1、Item2が取れた。
End Sub

=======================================
基本的に4つのコードとも同じ動き。
でも、親モジュールでのstopの時のインスタンスの数は4番目が一番少ない。

 

今の自分は、コード1が基本になってしまう。いきなりコード4を書こうとすると、クラスを分割しない書き方にしてしまうかも。

あと、クラスの基本的な使い方かもしれないが、

     ' 人参のインスタンスを作成してコレクションに追加
    Dim carrot As New VegetableClass  'インスタンス
    carrot.vegeType = "人参"
    carrot.Quantity = 100
    carrot.Amount = 100

この書き方で、クラスが複数作成可能なのに納得できる。

上記の小さいクラスをまとめようとcollectionを使ったが、コレクションへのまとめ方もいろいろありうるのか。

 

データとクラス、コレクションやディクショナリは、相性がよい感じですね。

 

次回も似たようなことを練習するつもりです。

エクセルVBA オブジェクト指向備忘録クラス (メモ)お手本を見ながら、クラスを作って改良する

20240307 エクセルVBAクラス備忘録

エクセルVBA オブジェクト指向備忘録クラス 

 

<前書き>
エクセルVBAオブジェクト指向、初心者です。
VBA自体は、以前から使用していました。
オブジェクト指向というものがあることは、知っていましたが、ネットなどを見てもよく分からず、挫折した過去あり。
<以上前書き>

 

今回の疑問とかやったこと
ネットの「いつも隣にITのお仕事」様の記事
「【初心者でもできる】エクセルVBAで最も簡単なクラスを作る方法」
を利用しながら、コードを書く。(感謝)
参考記事:
・エクセルVBAインスタンス生成時に自動で処理を実行するイベントプロシージャClass_Initialize
・エクセルVBAで自作コレクションのインスタンス生成時に初期データも投入する方法
・エクセルVBAで自作コレクションの要素を取得するプロパティの作り方
(感謝いたします。もし不都合がありましたら、即この記事は削除いたします。)

この記事は、自分用の備忘録なので、正しくないことやメモ程度の内容ばかりです。汗

 

やろうとしたこと
入力シートというシートに、コンボボックスを設置し、そのコンボボックスの候補リストをクラスを使って取得する。
具体的には入力シートにコンボボックスを用意して、「氏名」の一覧が出るようにする。
「氏名」はsimeシートから読み込む。
simeiシートには、通し番号、職員番号、氏名、職名、等々8列入っている。(一部空白セルもあり)
(上記simeシートの氏名以外の列もコンボボックスで氏名選択をすると表示内容が変わるようにする(予定))

(まあ、手続き型なら難しくないけど・・・。)
注:この記事では、やろうとしたことは、完成してません。

 

(1)最初にクラスモジュール「氏名」を作る。
上記項目に沿って、
-------------------------------------------------
Private m通し番号 As String

Public Property Let 通し番号(ByVal p通し番号 As String)
    m通し番号 = p通し番号
End Property

Public Property Get 通し番号() As String
    通し番号 = m通し番号
End Property
-------------------------------------------------
↑こんなのを作る。private変数にしました。(メンバー変数等の書き方は「いつも隣にIT様」とは違います。

これで、1行のデータ(1人分)を保持できる。

 

(2)クラスモジュール「氏名リスト」を作る。
次に、個人データを束ねる「氏名リスト」を作る。
参考:
・エクセルVBAインスタンス生成時に自動で処理を実行するイベントプロシージャClass_Initialize
あたり。

---------------------------------------
Option Explicit

Public Items As Collection

Private Sub Class_Initialize()
    Set Items = New Collection
End Sub
----------------------------------------
こんな感じ。

動かす標準モジュールは↓こんな感じ
------------------------------------------------
Function TEST_氏名リスト()

    Dim v氏名リスト As 氏名リスト
    Set v氏名リスト = New 氏名リスト

    With ThisWorkbook.Sheets("sime")
        Dim i As Long
        i = 1
        Do While .Cells(i, 3).value <> "" '氏名が空になるまで
            Dim v氏名 As 氏名
            Set v氏名 = New 氏名
            v氏名.Initialize .Range(.Cells(i, 1), .Cells(i, 8))
            v氏名リスト.Items.Add v氏名, v氏名.職員番号
            i = i + 1
        Loop
    End With
 
 
    Debug.Print v氏名リスト.Items.Item(1).職員番号
     Debug.Print v氏名リスト.Items.Item("54").氏名
 
    ' 氏名にアクセスして氏名を表示
    Dim v氏名Item As 氏名
    For Each v氏名Item In v氏名リスト.Items
        Debug.Print v氏名Item.職員番号
        Debug.Print v氏名Item.氏名
    Next v氏名Item
Stop
    
End Function
--------------------------------------------
Debug.Printは、もっと別の形でできないかと思ったが、これしか書けなかった。汗。

なお、上記に、
v氏名.Initialize .Range(.Cells(i, 1), .Cells(i, 8))
というコードがあるように、クラスモジュール「氏名」には、
---------------------------------------
Public Sub Initialize(rng As Range)
    m通し番号 = rng(1).value
    m職員番号 = rng(2).value
    m氏名 = rng(3).value
    m職名 = rng(4).value
    

End Sub
(部分的に省略してます。))
---------------------------------------
というメソッドを追加しています。
(注:クラスモジュール「氏名」のclass_initialize()ではありません。)


(3)上記コードの改良
参考:
エクセルVBAで自作コレクションのインスタンス生成時に初期データも投入する方法
あたり。

VBAのclass_initializeは、変数を持たすことができないらしい。
(多分、Private Sub Class_Initialize()のカッコに値をセットできないという意味か?)
そのため、
v氏名.Initialize .Range(.Cells(i, 1), .Cells(i, 8))
といったメソッドを作っている。

このコード自体は、クラスモジュール「氏名」と標準モジュール(function)(範囲の変数)に入れているが、
これらを動かすコード自体を、クラスモジュール「氏名リスト」のclass_initializeから呼び出そうというのが、
次の段階。
----------------------------------
Function TEST_氏名リスト()

    Dim v氏名リスト As 氏名リスト
    Set v氏名リスト = New 氏名リスト

 
 Debug.Print v氏名リスト.Items.Item(1).職員番号
 Debug.Print v氏名リスト.Items.Item("54").氏名
 

   ' 氏名にアクセスして氏名を表示
    Dim v氏名Item As 氏名
    For Each v氏名Item In v氏名リスト.Items
        Debug.Print v氏名Item.職員番号
        Debug.Print v氏名Item.氏名
    Next v氏名Item
    
    Stop
End Function
-------------------------------------
Debug.Printは、もっと別の形でできないかと思ったが、これしか書けなかった。汗。

↓クラスモジュール「氏名リスト」
---------------------------------------
Option Explicit

Public Items As Collection

 

Private Sub Class_Initialize()
    Set Items = New Collection
    
    

    With ThisWorkbook.Sheets("sime")
        Dim i As Long
        i = 1
        Do While .Cells(i, 3).value <> "" '氏名が空になるまで
            Dim v氏名 As 氏名
            Set v氏名 = New 氏名
            v氏名.Initialize .Range(.Cells(i, 1), .Cells(i, 8))
            Items.Add v氏名, v氏名.職員番号
            i = i + 1
        Loop
    End With
End Sub
------------------------------------------

 

(4)上記コードの改良
参考:
エクセルVBAで自作コレクションの要素を取得するプロパティの作り方
あたり。

上記では、
 Debug.Print v氏名リスト.Items.Item(1).職員番号
 Debug.Print v氏名リスト.Items.Item("54").氏名
というように、.Items.Item(1)みたいに間にcollectionを使うことで、少し長い。
そのため、クラスモジュール「氏名リスト」に独自のプロパティを持たせる。

-----------------------------------------------
Public Property Get 独自Item(ByVal pKey As Variant) As 氏名  'pKeyはインデックスでも、キーでも受け取れるようにvariant
    Set 独自Item = Items.Item(pKey)
    
End Property
-----------------------------------------------
(Itemだけだと見にくいので、ここでは 「独自Item」としました。)

 

---------------------------------------------------
Function TEST_氏名リスト()

    Dim v氏名リスト As 氏名リスト
    Set v氏名リスト = New 氏名リスト

 
 
 Debug.Print v氏名リスト.Items.Item(1).職員番号
 Debug.Print v氏名リスト.Items.Item("54").氏名
 
 Debug.Print v氏名リスト.独自Item(1).職員番号
 Debug.Print v氏名リスト.独自Item("54").氏名
 
Stop

   ' 氏名にアクセスして氏名を表示
    Dim v氏名Item As 氏名
    For Each v氏名Item In v氏名リスト.Items
        Debug.Print v氏名Item.職員番号
        Debug.Print v氏名Item.氏名
    Next v氏名Item
    
    
'    ' 氏名にアクセスして氏名を表示
'    Dim v氏名Item As 氏名
'    For Each v氏名Item In v氏名リスト.独自Item  'エラー:引数は省略できない独自Itemsではできない
'        Debug.Print v氏名Item.職員番号
'        Debug.Print v氏名Item.氏名
'    Next v氏名Item

Stop
End Function


-----------------------------------------------------    
上記のように、独自Itemを使ったコードを書きました。
が、一番下のコメントアウトしたfor each文はエラーでした。自分では原因究明できず。(放置)


(5)上記コードの改良
参考:
エクセルVBAで自作コレクションの要素を取得するプロパティの作り方(同上)
あたり。

上記「独自Item」をprivate変数にして隠ぺい。他のモジュールからアクセスできなくします。
(あまり分かっていない、概念・・・汗)

クラスモジュール「氏名リスト」
-------------------------------------------------
Option Explicit

'Public Items As Collection
Private mItems As Collection 


Private Sub Class_Initialize()
    Set mItems = New Collection
    
    

    With ThisWorkbook.Sheets("sime")
        Dim i As Long
        i = 1
        Do While .Cells(i, 3).value <> "" '氏名が空になるまで
            Dim v氏名 As 氏名
            Set v氏名 = New 氏名
            v氏名.Initialize .Range(.Cells(i, 1), .Cells(i, 8))
            mItems.Add v氏名, v氏名.職員番号
            i = i + 1
        Loop
    End With    
End Sub

Public Property Get 独自Item(ByVal pKey As Variant) As 氏名  'pKeyはインデックスでも、キーでも受け取れるようにvariant
    Set 独自Item = mItems.Item(pKey)
    
End Property
--------------------------------------------
(注:宣言部だけでなく、コード内変数も変えています。)

-------------------------------------------    
Function TEST_氏名リスト()

    Dim v氏名リスト As 氏名リスト
    Set v氏名リスト = New 氏名リスト


 
 'itemsをprivateにすると動かない
' Debug.Print v氏名リスト.Items.Item(1).職員番号
' Debug.Print v氏名リスト.Items.Item("54").氏名
 
 Debug.Print v氏名リスト.独自Item(1).職員番号   '独自Itemでしかアクセスできない
 Debug.Print v氏名リスト.独自Item("54").氏名
 

 'itemsをprivateにすると動かない
'   ' 氏名にアクセスして氏名を表示
'    Dim v氏名Item As 氏名
'    For Each v氏名Item In v氏名リスト.Items
'        Debug.Print v氏名Item.職員番号
'        Debug.Print v氏名Item.氏名
'    Next v氏名Item
    
    
'    ' 氏名にアクセスして氏名を表示
'    Dim v氏名Item As 氏名
'    For Each v氏名Item In v氏名リスト.独自Item  'エラー:引数は省略できない独自Itemsではできない
'        Debug.Print v氏名Item.職員番号
'        Debug.Print v氏名Item.氏名
'    Next v氏名Item

Stop
    
'  ' 氏名にアクセスして氏名のプロパティを表示
'    Dim v氏名Item As 氏名
'    For Each v氏名Item In v氏名リスト.独自Item()  '()付けても出来なかった。エラー:引数は省略できない
'        Debug.Print v氏名Item.職員番号
'        Debug.Print v氏名Item.氏名
'    Next v氏名Item
Stop
End Function
-------------------------------------------
private変数にしたせいだと思いますが、
上記の
' Debug.Print v氏名リスト.Items.Item(1).職員番号
' Debug.Print v氏名リスト.Items.Item("54").氏名
もアクセスできなくなりました。
「独自Item」だけしか、入れなくなる。???汗(このcollection「Items」がパブリックでなくなったからか?。)
(クラスモジュール「氏名リスト」内で使う変数は、mItems(private)だからか???)


(6)オマケ
上記だと、debug.printをしても、標準モジュール(function)では、
 Debug.Print v氏名リスト.独自Item(1).職員番号   '独自Itemでしかアクセスできない
 Debug.Print v氏名リスト.独自Item("54").氏名
これしか動きません。(自分の能力不足か・・・)

で、クラスモジュール「氏名リスト」にDebug.Printを置いてみました。
---------------------------------------------
Option Explicit

'Public Items As Collection
Private mItems As Collection  


Private Sub Class_Initialize()
    Set mItems = New Collection
    
    

    With ThisWorkbook.Sheets("sime")
        Dim i As Long
        i = 1
        Do While .Cells(i, 3).value <> "" '氏名が空になるまで
            Dim v氏名 As 氏名
            Set v氏名 = New 氏名
            v氏名.Initialize .Range(.Cells(i, 1), .Cells(i, 8))
            mItems.Add v氏名, v氏名.職員番号
            i = i + 1
        Loop
    End With
    
    'コレクションの要素についてループ
For Each v氏名 In mItems
    Debug.Print v氏名.職員番号, v氏名.氏名  
Next v氏名

    
    
    
End Sub


Public Property Get 独自Item(ByVal pKey As Variant) As 氏名  'pKeyはインデックスでも、キーでも受け取れるようにvariant
    Set 独自Item = mItems.Item(pKey)
    

End Property

------------------------------------------
↑これは、うまく動きました。Debug.Print v氏名.職員番号, v氏名.氏名 

 

(7)その他
for eachを使ったDebug.Print文について、標準モジュールでは、うまくいかなかった点について、
-----------------------------
'Private m独自Item As Collection
'Public Property Let 独自Item(p独自Item As Variant)
'
'    m独自Item = p独自Item
'End Property 
-----------------------------
とか、Property Letを使ってもダメでした。今は放置です。ああ。


ちなみに、今回の記事の処理ですが、クラスはクラスでも、シートからの読み込みなので、collectionでなく、配列でやろうかなと改めて考えています。

追記:コンボボックス、スタックオーバーフローしたので、どうしよう・・・

エクセルVBAクラス備忘録 public使う使わないどうする?

20240303 エクセルVBAクラス備忘録 public使う使わないどうする?

エクセルVBA オブジェクト指向備忘録クラス 

 

<前書き>
エクセルVBAオブジェクト指向、初心者です。
VBA自体は、以前から使用していました。
オブジェクト指向というものがあることは、知っていましたが、ネットなどを見てもよく分からず、挫折した過去あり。
<以上前書き>

 

今回の疑問
クラスのproperty(変数)にprivateを使うか、publicを使うか問題。
まあ、普通に考えれば、privateの方が「安全」なので、privateを使うのがいいのかもしれないが、書くのがめんどくさい。
(使い分けも、クラスの場合ピンと来ていない・・・)

 

===========================================
ファイルA
標準モジュール
' 例: 意図しない変更を防げない場合public
Sub ExampleUsage()
    Dim myExample As New clsExample
    myExample.ExampleValue = 1000 ' 直接変更(入力時候補出る)
    Debug.Print myExample.ExampleValue
    
End Sub

 

クラスclsExample
Public ExampleValue As Integer

======================================
ファイルB
標準モジュール
' 例: 意図しない変更を防げる場合 private
Sub ExampleUsage()
    Dim myExample As New clsExample
    myExample.Value = 2000 ' 値を設定(入力時候補は出る)
    Debug.Print myExample.Value

End Sub

 

クラスclsExample
Private ExampleValue As Integer

Public Property Let Value(newValue As Integer)
    ExampleValue = newValue
End Propert

Public Property Get Value() As Integer
    Value = ExampleValue
End Property

======================================
上記コメントに、「意図しない変更を防げる場合 private」とか書いたが、実際は、同じように書き換えをしている。
コード入力の際も、AもBも、myExample.まで書いたら、次の候補を表示してくれる。


BingAIに質問するとカプセル化・隠ぺいなど説明してくれるが、ピンと来ない。
(手続き型ならpublicの使い分け分かると言うか、publicはほぼ使わない)


publicにしてどこからでも書き換えられると困るというが、インスタンス化のコードを書く必要があるので、そんなに困ることはあるのだろうか?
なら、下記のように、クラス内に値を持たせたらどうか?
(コードは適当です。すみません。汗)

====================================
ファイルC
標準モジュール
' 例: 意図しない変更を防げる場合
Sub ExampleUsage()
    Dim myExample As New clsExample
    myExample.Value = 0   ’仮に置きました
    Debug.Print myExample.Value   ’2000が表示
    
End Sub


Private ExampleValue As Integer

Public Property Let Value(newValue As Integer)
    ExampleValue = 2000
End Property

Public Property Get Value() As Integer
    Value = ExampleValue
End Property

====================================
または、Property Letに入ったら、if文などで制御かけるとか。
(よく分からないけど。)

 

++++++++++++++++++++++++++++++++
その他疑問:

別ファイルから、クラス(変数)が使われてしまうのか、気になった。

例えば、AAというファイルに、下記のコードがあります。(標準モジュールとクラス)

AAファイル

標準モジュール
Sub ExampleUsage()
    Dim myExample As New clsExample
    myExample.ExampleValue = 1000  
    Debug.Print myExample.ExampleValue
    
End Sub


クラスclsExample
Public ExampleValue As Integer


/////////////////////////////////
BBというファイルに、標準モジュールだけあった場合、

BBファイル
標準モジュール
Sub ExampleUsage()
    Dim myExample As New clsExample
    myExample.ExampleValue = 1000 
    Debug.Print myExample.ExampleValue
    
End Sub

///////////////////////////////
BBのファイルは、「クラスが定義されていない」とエラーになります。
そのままでは、他のブックのクラスは、他のファイルのプロジェクトでは使えないようです。


「BBにも、クラスclsExample」を設定しないと動かないのか?BingAI様に聞いたところ、)
①Bにもクラスを置くか

②参照設定を追加する
とのことでした。
「参照設定」について、ネットで調べてみたが、面倒なのでやってません。
参照設定まで使っているエクセルユーザーは自分の周りにいないので、Aのブックから、Bのクラスを呼び出して、おかしな挙動をするというようなことはなさそうです。
(自分の周りでは。)

 

よく分からないが、手続き型を見るみたいに、ステップ実行してみていくと・・・
Property Letなどクラスを使う場合、処理過程でたくさん変数を持つことになり、メモリとかすごく食うのでは?とか、
今回の疑問のように、変数のスコープで問題が起こらないかとか、不安になります。汗

 

・・・やってみるしかないのかな。

 

エクセルVBA オブジェクト指向備忘録クラス 「ドメインオブジェクト全部」を標準モジュールに

20240301エクセルVBA オブジェクト指向備忘録クラス ドメインオブジェクト全部を標準モジュールに


エクセルVBA オブジェクト指向備忘録

<前書き>
エクセルVBAオブジェクト指向、初心者です。
VBA自体は、以前から使用していました。
オブジェクト指向というものがあることは、知っていましたが、ネットなどを見てもよく分からず、挫折した過去あり。

エクセルVBAで、オブジェクト指向を必要とすることもなかったので、諦めていましたが、
youtubeで、「ひとりごとワンダーランド」様のエクセルVBAでのクラスに関する動画を見させていただき、少しだけやる気になっています。感謝。
(まだ、動画は最後まで見ていません。汗)
不都合なら削除いたしますのでよろしくお願いいたします。(動画内容全部は公開していません。気になったところを自分なりに書いています。)
<以上前書き>


「ひとりごとワンダーランド」様のyoutube動画では、オブジェクトの中でも大事なものを「ドメインオブジェクト」と呼んでいます。
youtube「クラスモジュール」13)

売上データ
売上データリスト
集計結果
集計結果リスト

そこでは、上記が「ドメインオブジェクト」。


「ひとりごとワンダーランド」様は、動画の中で、「標準モジュールは、「使う側」に徹し、クラスに「作る処理」を任せればいいということで下記のように作成しています。
(標準モジュールのみ載せました。コード少し違っているかも。)


====================================
Option Explicit

Sub 処理開始2()
    ''----------------------------
    ''シート読み込み

    ''----------------------------
    Dim v売上シート As 売上シート
    Set v売上シート = New 売上シート
    
    Dim v生データ As Variant
    v生データ = v売上シート.読み込み("売上")
    
    ' シートの読み込みが終わったら、v売上シートを解放
    Set v売上シート = Nothing
    
    ''----------------------------
    ''売上データリスト作成

    ''----------------------------
    Dim v売上オブジェクト生成 As 売上オブジェクト生成
    Set v売上オブジェクト生成 = New 売上オブジェクト生成
    
    Dim v売上データリスト As 売上データリスト
    Set v売上データリスト = v売上オブジェクト生成.リスト生成(v生データ)
    
    ' リストの生成が終わったら、v売上オブジェクト生成とv生データを解放
    Set v売上オブジェクト生成 = Nothing
    Erase v生データ
    
    ''集計処理開始
    Dim v集計結果オブジェクト生成 As 集計結果オブジェクト生成
    Set v集計結果オブジェクト生成 = New 集計結果オブジェクト生成
    
    Dim v集計 As 集計
    Set v集計 = New 集計
    
    ''----------------------------
    ''集計処理、集計結果データリスト作成

    ''----------------------------
    Dim v集計結果リスト As 集計結果リスト
    Set v集計結果リスト = v集計結果オブジェクト生成.リスト作成(v売上データリスト, v集計)
    
    ' 集計結果リストの作成が終わったら、v集計結果オブジェクト生成、v集計、v売上データリストを解放
    Set v集計結果オブジェクト生成 = Nothing
    Set v集計 = Nothing
    Set v売上データリスト = Nothing
    
    ''----------------------------
    ''集計シート出力

    ''----------------------------
    Dim v集計シート As 集計シート
    Set v集計シート = New 集計シート
    Call v集計シート.表示("集計", v集計結果リスト)
 Stop
    ' 集計シートの出力が終わったら、v集計シートとv集計結果リストを解放
    Set v集計シート = Nothing
    Set v集計結果リスト = Nothing
End Sub

=================================


「ひとりごとワンダーランド」様の言うように、上記コードでは、色んな作業は、クラスに任せているようです。
(シンプル。)

 

ただ、初心者の自分には、「ドメインオブジェクトが全部標準モジュールで見れたらなあ」と思いました。
(上記コードには、売上データ、集計結果が見えません。)(まあ「○○リスト」があればいいのですが・・・)


で、自分で書き直してみました。正しいかは分かりません。(少し変なメモ入っています。)

++++++++++++++++++++++++++++++
Option Explicit

Sub 処理開始4自分で修正()

    ''----------------------------
    ''シート読み込み

    ''----------------------------
    '//////////////////これクラスいるのか???
    Dim v売上シート As 売上シート
    Set v売上シート = New 売上シート
    
    Dim v生データ As Variant
    v生データ = v売上シート.読み込み("売上")
    
    Set v売上シート = Nothing
    
    
    ''----------------------------
    ''売上データ(ドメインオブジェクト)

    ''----------------------------
    
   
    Dim v売上データリスト As 売上データリスト
    Set v売上データリスト = New 売上データリスト
   
    Dim vRowCount As Long
    For vRowCount = LBound(v生データ) To UBound(v生データ)
        Dim v売上データ As 売上データ
        Set v売上データ = New 売上データ

        v売上データ.名前 = v生データ(vRowCount, 1)
        v売上データ.日付 = v生データ(vRowCount, 2)
        v売上データ.売上金額 = v生データ(vRowCount, 3)

'        v売上データリスト.Add v売上データ      'v売上データリスト使っている
        v売上データリスト.追加 v売上データ
    Next vRowCount

    'Set v売上オブジェクト生成 = Nothing '標準モジュールにはないので不要
    Erase v生データ

 
   Dim v集計結果リスト As 集計結果リスト
    Set v集計結果リスト = New 集計結果リスト
    
    Dim v集計 As 集計
    Set v集計 = New 集計
    


'    Dim v名前別合算Dict As Dictionary
     Dim v名前別合算Dict As Object   'ここも直した
    Set v名前別合算Dict = v集計.名前別合算Dict(v売上データリスト)    '直し

    Dim v売上データ名前 As Variant
    For Each v売上データ名前 In v名前別合算Dict

        Dim v集計結果 As 集計結果        '集計結果は出るが、ぐるぐる回り消えていく
        Set v集計結果 = New 集計結果

        v集計結果.名前 = v売上データ名前
        v集計結果.売上金額 = v名前別合算Dict(v売上データ名前)

        Call v集計結果リスト.追加(v集計結果)

    Next v売上データ名前

' v集計結果リストはOK
    'Set v集計結果オブジェクト生成 = Nothing '標準モジュールにはないので不要
    Set v集計 = Nothing
    Set v売上データリスト = Nothing

    ''----------------------------
    ''集計シート出力

    ''----------------------------
    Dim v集計シート As 集計シート
    Set v集計シート = New 集計シート
    Call v集計シート.表示("集計", v集計結果リスト)
    
    Set v集計シート = Nothing
    Set v集計結果リスト = Nothing
    

Stop

End Sub

+++++++++++++++++++++++++++++++++

かなりくどくなりました。汗。
「ひとりごとワンダーランド」様が何回も言っているように、人によって作り方は千差万別・・・と思えばいいのか・・・。汗汗


(以下、ドメインオブジェクトを標準モジュールに書くにあたっての途中メモ。
 Set v名前別合算Dict = v集計Dict_追加.名前別合算Dict(v売上データリスト)
の部分が、ややこしくて(クラスを二重に使っている)どうすればいいのか考えたメモで、下記はまとまっていません。)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''集計結果をクラスでなく、親(標準モジュール)に書くには?
'
'    Dim v集計Dict_追加 As 集計Dict_追加
'    Set v集計Dict_追加 = New 集計Dict_追加
'
'    Dim v名前別合算Dict As Object    '単なるオブジェクト
'
'    Set v名前別合算Dict = v集計Dict_追加.名前別合算Dict(v売上データリスト)    'v売上データリストがcollectionだと型が一致しない
'
'' v名前別合算Dictが出来た・・・。どこのクラスから呼ばれているか?集計クラス
'
''考え・・・上記だが、クラス「集計」の中に、「名前別合算」と「名前別合算Dict」メソッドがある。
''集計結果を親に書きたいなら、「名前別合算」だけをここに書いて、「名前別合算Dict」はクラスでいいのでは?
''書き直し

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'集計のメソッドに「集計結果」があるので、標準モジュールに置く
'v集計結果リストにデータ取れている
'考え 元の標準モジュールは、
'①集計結果オブジェクト生成のメソッドリスト作成を書いて、
'その中に②集計のメソッド名前別合算を呼び、
'その③集計のプライベートのメソッドで名前別合算Dictを使っている。
'集計結果があるのは②なので、②を標準モジュールに記載。
'③はクラスに置いたままにする。
'(①は後で考える・・・)

******************************************

乾燥:
昔作ったものを自分でリファクタリングするのは、もっと勉強が必要です。
「ひとりごとワンダーランド」様に感謝いたします。

 

パソコン設定備忘録(プリンタのUSB接続・・・できなかった(未解決))

ただのメモです。

(できなかったこと&次やるためのメモです。)

他の職場で、持参したPCをスタンドアローンで使用する機会があった。

 

プリンタが使いたかったので、持参したUSB接続用のプリンタケーブルをPCとプリンタに接続。

NECのプリンターのドライバーを持参したモバイルルーター経由でPCにダウンロード。

で、インストール。

・・・で、使えない。

 

ずっと以前も、USB接続で苦労した覚えがあります。

(うろ覚えですが、その時は、結局、以前、設定したポート設定を指定して使った気がする。)

 

今回は、時間がなかったので、結局、このやり方はあきらめました。

その職場の(LAN接続できている)他の方のPCを借りて、自分のグーグルドライブのファイルを開き、印刷させてもらいました。

 

後日、PCの詳しい方にUSB接続について聞いたところ、ポート設定がうまくいかなかったのではとのこと。その場合、ポートを修正するより、ドライバーを入れ直した方がいいとのことでした。

 

(その人の話から、ドライバーを入れると、ポートまでいろいろ自動設定されるが、その設定がうまくいかないこともあるんだなと思いました。←素人なので、理解が違っているかもしれませんです。)

 

 

ペンタブレット備忘録

20240217ペンタブレット備忘録

PC素人です。タブレット素人です。レンタル素人です。

事務的な仕事でも、今後、ペーパーレスになる雰囲気です。
紙重視の自分ですが、今までのまま、何とか事務作業ができないか考えたいと思います。
(でも、あまりお金はかけたくない。)

で、液晶タブレットを使うかを検討。
「液タブを紙代わりにできないか。」
(絵を描くわけではないので、簡単なものでいい。)

準備
<つなぎ方>
自分のPCは、マルチモニターにしていて、既にHDMIのコネクターは埋まっている。
PCにモニターを増設したい。既に、マルチモニター(モニターA、B別映像)になっていて、HDMIポートは使用済み。
(displayポートはない。)


液タブを3枚目のモニターとして考えるなら、USB3.0ポートに、USB3.0HDMI変換アダプタを使ってつなぐことになるため、
サンワサプライHDMIポート搭載USB3.0ポートハブを購入。(8000円以上します。)

(↑PCにUSB Type-C(3.1)はないため、上のやり方を決断)


<液タブ選定>
サイズやメーカーなど。
使ったことがほぼないので、分からないが、安め→13インチ
安めとはいいながら、信頼性で「WACOM
となると、wacom one。
40000円以上します。

 

<レンタル>
で、今回、液タブは、レンタルすることにしました。
6泊7日で、6500円くらい。
高いけど、仕方ない。


<接続>
実際、購入する場合は、製品登録など必要なようですが、(登録するとソフトが使えるとか。)今回は、ソフトのインストールだけしました。
ソフトは、サイトからダウンロード。インストール。
液タブは、電源、HDMIUSB2.0での接続だったので、上記サンワサプライのアダプタでOKでした。
(電源をUSBから取るのでなく、コンセントからだったので、問題なし。USB2.0は、ペンの位置情報等取得のためだろう。3.0で問題なし)

 

<実際>
実際の職場では、ネット接続ができないのですが、他のPCでドライバをDLし、インストールしました。
(製品登録などしなければ問題ないです。)
インストールすれば、つなぐだけで、使用できました。

エクセルでは「インク」で手書きOK。



ペイントも当然OK。



PDFは、アクロバットリーダーしかないのですが、edgeで開けば書き込みできます。
(PDF自体で書き込み禁止のものは当然不可ですが。)

この液タブは、タッチ操作はできません。


あと、関係ないけど、画面3枚になると、画面操作面倒かも。
画面移動は、shift winマーク 矢印キーで、アクティブなタブを動かせます。
ALT タブ でアクティブウインドウの切替とかも慣れる必要あるか。
(慣れが必要かな。)


もっと、関係ないけど、スマホの大文字入力は、左下のエンターを2回押し。
(すぐに忘れる・・・)


<今後>
せっせと、紙をスキャンするかな???

エクセルVBA オブジェクト指向備忘録クラス004(番外)クラスいろんな書き方やってみました

20240205エクセルVBA オブジェクト指向備忘録クラス004(番外)クラスいろんな書き方やってみました
 
 
エクセルVBA オブジェクト指向備忘録
 
<前書き>
エクセルVBAオブジェクト指向、初心者です。
VBA自体は、以前から使用していました。
オブジェクト指向というものがあることは、知っていましたが、ネットなどを見てもよく分からず、挫折した過去あり。
 
エクセルVBAで、オブジェクト指向を必要とすることもなかったので、諦めていましたが、youtubeで、「ひとりごとワンダーランド」様のエクセルVBAでのクラスに関する動画を見させていただき、少しだけやる気になっています。感謝。
(まだ、動画は最後まで見ていません。汗)
<以上前書き>
 
 
前回:
youtube「ひとりごとワンダーランド」様のクラスモジュールの作法(なるべくprivateでpublicを減らすとか、クラス内変数の書き方とか)を
使って、ネットHPで「いつも隣にITのお仕事」様の記事「エクセルVBAでエクセル表のデータを反映するメソッドとクラスを使うメリット」を
コード化しました。
分かっていないので、間違っているかも。(多分、間違っている。汗)
注:もしこのブログ記事に関し、youtube「ひとりごとワンダーランド」様、ネットHP「いつも隣にITのお仕事」様におかれまして、不都合があり、削除すべき場合は、即削除いたします。
前回は、「いつも隣にITのお仕事」の記事に、ちらっと「ユーザーフォームで扱えるようにすると・・・」と書いてあったので、ユーザーフォームを作ってみました。
 
 
 
 
 
今回:
今回は、はてなブログ「Kの備忘録(仮)」様の記事「VBAにおけるPropertyプロシージャとは何か」で勉強(temperature)しました。
(内容は、変数temperatureに数字(温度)を渡すと、数字に応じたExperience(熱い、ちょうどいい等)を返すコードです。 )
 
前回までと関係ありません。
注:「Kの備忘録(仮)」様におかれましても不都合があれば早急に削除いたします。
今回、クラスのprivete変数など本当に分からないので、上記temperatureを3パターンで書いてみました。(クラスなしも入れると4パターン。)
(手続き型なら簡単なのに、クラスだと同じ機能でも、いろんな書き方ができるようです。)
Bing様にもお手伝いいただきました。(不都合あれば削除します。)
 
////////////////////////////////////////////////////
【パターン0】(クラス使わない)手続き型というのか?普通の書き方。
'標準モジュール
Option Explicit
 
Sub TodaysTemperature()
 
    Dim temperature As Long
    
    temperature = 30
    Debug.Print Experience(temperature)
    
    temperature = 20
    Debug.Print Experience(temperature)
 
End Sub
 
 
 
Function Experience(pTemperature As Long) As String
 
    If pTemperature >= 30 Then
        Experience = "暑い"
    ElseIf pTemperature >= 15 Then
        Experience = "ちょうど良い"
    Else
        Experience = "寒い"
    End If
 
End Function
 
 
 
////////////////////////////////////////////////////
【パターン1】(クラスだけど、private変数使わない)
'標準モジュール
Option Explicit
 
Sub TodaysTemperature()
 
    Dim v判定 As 判定
    Set v判定 = New 判定
    Dim temperature As Long
    
    temperature = 30
    Debug.Print v判定.Experience(temperature)
    
    temperature = 20
    Debug.Print v判定.Experience(temperature)
End Sub
 
'クラス「判定」
Option Explicit
 
Function Experience(pTemperature As Long) As String
 
    If pTemperature >= 30 Then
        Experience = "暑い"
    ElseIf pTemperature >= 15 Then
        Experience = "ちょうど良い"
    Else
        Experience = "寒い"
    End If
 
End Function
 
 
 
////////////////////////////////////////////////////
【パターン2】(Temperatureにprivateを使う)
'標準モジュール
Option Explicit
 
Sub TodaysTemperature()
 
    Dim v判定 As 判定
    Set v判定 = New 判定
    'Dim temperature As Long'(不要になった)
 
    v判定.temperature = 30
    Debug.Print v判定.Experience
    
    v判定.temperature = 20
    Debug.Print v判定.Experience
 
End Sub
 
 
'クラス「判定」
Option Explicit
 
Private mTemperature As Long
 
 
Property Let temperature(pTwmperature As Long)
mTemperature = pTwmperature
End Property
 
 
Function Experience() As String    '引数なし(mTemperatureを使う)
 
    If mTemperature >= 30 Then
        Experience = "暑い"
    ElseIf mTemperature >= 15 Then
        Experience = "ちょうど良い"
    Else
        Experience = "寒い"
    End If
 
End Function
 
 
////////////////////////////////////////////////////
【パターン3】(Temperature、Experienceにprivateを使う)
'標準モジュール
Option Explicit
 
Sub TodaysTemperature()
 
    Dim v判定 As 判定
    Set v判定 = New 判定
 
 
    v判定.temperature = 30
    Debug.Print v判定.Experience
    
    v判定.temperature = 20
    Debug.Print v判定.Experience
 
End Sub
 
 
'クラス「判定」
Option Explicit
 
Private mTemperature As Long
Private mExperience As String
 
 
Property Let temperature(pTwmperature As Long)
        mTemperature = pTwmperature
        Call hantei_Experience
End Property
 
 
Property Get Experience() As String
    Experience = mExperience
End Property
 
 
Private Sub hantei_Experience()
    If mTemperature >= 30 Then
        mExperience = "暑い"
    ElseIf mTemperature >= 15 Then
        mExperience = "ちょうど良い"
    Else
        mExperience = "寒い"
    End If
 
End Sub
 
 
 
*********************************************************
パターン1・・・うーんメリットとしては、なんだろ。インスタンス化が終われば保持した値が消えるということか?
パターン2・・・標準モジュールにTemperatureの宣言不要になった。
うーんメリットとしては、なんだろ。(実感がないが)、mTemperatureとしてprivate化することで、
標準モジュールで、書き換えようとしても、property letを通すことだろうか。
(ありがたみをあまり感じないが、property letで細工をすることも可能か。)
(全く分からないが、どこかで、変数が書き換わってしまうと、なかなかコードの修正やバグの発見がしにくいと聞いたことがある。)
VBAでは、継承とかオーバーライドとかないのか、ありがたみを感じにくいのか?)
パターン3・・・クラス内の変数を全部private化しました。
標準モジュールから入ってくる方(Temperature)は、property letだけ。
標準モジュールへ出ていく方(Experience)は、property getだけ。
上記の変数を使用したサブルーチンを1個作り(functionを改修)、property let内でコール。
 
ちょっと強引だが、クラスを使う場合で、private変数を使う場合は、標準モジュールでは変数の宣言は必要なく、
クラス内で、private変数と合わせて、(標準モジュールでも使える変数用にpropertyを作成ということか。)
 
 
もしかして、大人数で作成する時は、クラスわかれているといいのかな。
 
 
その他:
何となく気になるのが、ローカルウインドウで見ると、クラスにすると(当然だけど)変数が増える。
そんなものなのか。