web-dev-qa-db-ja.com

VBAおよびMS-AccessのBang表記とDot表記

私が documenting であるアプリケーションを熟読しているときに、オブジェクトのプロパティ/メソッドなどにアクセスする際のbang表記の例に出くわしました。他の場所では、同じように見えるものにドット表記を使用しています。目的。

どちらか一方を使用することに違いや好みはありますか?いくつかの単純なグーグルは、主題に関する限られた情報しか明らかにせず、反対の場合に実際にそれを使用する人もいます。おそらく、狂気の方法を示すMSのコーディング標準セクションがどこかにありますか?

26
Nitrodist

この質問に対する(以前は)受け入れられた回答にもかかわらず、バングは実際にはメンバーまたはコレクションアクセスオペレーターではありません。これは、1つの単純で具体的なことを行います。bang演算子は、bang演算子に続くリテラル名を文字列引数としてデフォルトのメンバーに渡すことにより、オブジェクトのデフォルトのメンバーへの遅延アクセスを提供します。 。

それでおしまい。オブジェクトはコレクションである必要はありません。 Itemというメソッドやプロパティを持っている必要はありません。必要なのはProperty GetまたはFunctionは、最初の引数として文字列を受け入れることができます。

詳細と証拠については、これについて説明しているブログ投稿を参照してください: The Bang!(Exclamation Operator)in VBA

35
Joshua Honig

Bang演算子(_!_)は、Collectionのメンバー、または_ADODB.Recordset_のFieldsプロパティなどの他の列挙可能なオブジェクトにアクセスするための省略形です。

たとえば、Collectionを作成し、それにいくつかのキー付きアイテムを追加できます。

_Dim coll As Collection
Set coll = New Collection

coll.Add "First Item", "Item1"
coll.Add "Second Item", "Item2"
coll.Add "Third  Item", "Item3"
_

このコレクションのアイテムには、次の3つの方法でキーを使用してアクセスできます。

  1. coll.Item("Item2")
    これは最も明示的な形式です。

  2. coll("Item2")
    これは、ItemCollectionクラスのデフォルトのメソッドであるため機能するため、省略できます。

  3. _coll!Item2_
    これは、上記の両方の形式の省略形です。実行時に、VB6はバングの後のテキストを受け取り、それをパラメーターとしてItemメソッドに渡します。

人々はこれを本来よりも複雑にしているようです。そのため、簡単な説明を見つけるのは難しいのです。通常、複雑さまたは「バング演算子を使用しない理由」は、それが実際にどれほど単純であるかについての誤解から生じます。誰かがバング演算子に問題を抱えているとき、彼らは彼らが抱えている問題の本当の原因ではなく、それを非難する傾向があります。それはしばしばより微妙です。

たとえば、フォームのコントロールにアクセスするためにbang演算子を使用しないことを推奨する人もいます。したがって、_Me.txtPhone_は_Me!txtPhone_よりも優先されます。これが悪いと見なされる「理由」は、コンパイル時に_Me.txtPhone_が正しいかどうかがチェックされるが、_Me!txtPhone_はチェックされないことです。

最初のケースでは、コードを_Me.txtFone_と誤って入力し、その名前のコントロールがない場合、コードはコンパイルされません。 2番目のケースでは、_Me!txtFone_を記述した場合、コンパイルエラーは発生しません。代わりに、_Me!txtFone_を使用したコード行に到達すると、コードは実行時エラーで爆発します。

Bang演算子に対する議論の問題は、この問題がbang演算子自体とは何の関係もないということです。想定どおりに動作しています。

フォームにコントロールを追加すると、VBは、追加したコントロールと同じ名前のプロパティをフォームに自動的に追加します。このプロパティはフォームのクラスの一部であるため、コンパイラはチェックできます。ドット( "。")演算子を使用してコントロールにアクセスする場合、コンパイル時のタイプミスの場合(VBが名前付きコントロールプロパティを作成したため、ドット演算子を使用してアクセスできます)。

_Me!ControlName_は実際にはMe.Controls("ControlName")の省略形であるため1、コントロール名の入力ミスに対するコンパイル時のチェックが行われないことは驚くべきことではありません。

言い換えると、バング演算子が「悪い」で、ドット演算子が「良い」場合、あなたは考えるかもしれません

_Me.Controls("ControlName")
_

よりも良い

_Me!ControlName
_

最初のバージョンはドットを使用しているためですが、この場合、パラメーターを介してコントロール名にアクセスしているため、ドットはまったく良くありません。コンパイル時のチェックを取得するようにコードを記述する別の方法がある場合にのみ、「より良い」ものになります。これは、VB各コントロールのプロパティを作成するため、コントロールの場合に発生します。そのため、_Me.ControlName_よりも_Me!ControlName_が推奨される場合があります。


  1. 当初、ControlsプロパティはFormクラスのデフォルトのプロパティであると述べていましたが、Davidはコメントで、ControlsFormのデフォルトのプロパティではないと指摘しました。実際のデフォルトプロパティは、includes _Me.Controls_の内容であるコレクションを返します。これが、bangの省略形が引き続き機能する理由です。
27
Mike Spross

すでに投稿されている2つの例外的な回答への補遺として役立つカップルの落とし穴:

フォームとレポートのレコードセットフィールドへのアクセス
AccessのFormオブジェクトのデフォルト項目は、フォームのControlsコレクションとフォームレコードセットのFieldsコレクションの和集合です。コントロールの名前がフィールドの名前と競合する場合、どのオブジェクトが実際に返されるかわかりません。フィールドとコントロールの両方のデフォルトのプロパティは_.Value_であるため、多くの場合、「違いのない区別」です。言い換えれば、フィールドとコントロールの値が同じであることが多いため、通常はどちらであるかは気にしません。

名前の競合に注意してください!
この状況は、Accessのフォームおよびレポートデザイナが、バインドされているレコードセットフィールドと同じ名前のバインドされたコントロールをデフォルトで設定することによって悪化します。私は個人的に、コントロールタイプのプレフィックスを使用してコントロールの名前を変更する規則を採用しました(たとえば、LastNameフィールドにバインドされたテキストボックスの場合はtbLastName)。

レポートレコードセットフィールドがありません!
前に、Formオブジェクトのデフォルトアイテムはコントロールとフィールドのコレクションであると言いました。ただし、Reportオブジェクトのデフォルト項目は、コントロールのコレクションのみです。したがって、bang演算子を使用してレコードセットフィールドを参照する場合は、そのフィールドを(必要に応じて非表示の)バインドされたコントロールのソースとして含める必要があります。

明示的なフォーム/レポートプロパティとの競合に注意してください
フォームまたはレポートにコントロールを追加すると、Accessはこれらのコントロールを参照するプロパティを自動的に作成します。たとえば、tbLastNameという名前のコントロールは、_Me.tbLastName_を参照することでフォームのコードモジュールから利用できます。ただし、Accessは、既存のフォームまたはレポートプロパティと競合する場合、そのようなプロパティを作成しません。たとえば、Pagesという名前のコントロールを追加するとします。フォームのコードモジュールで_Me.Pages_を参照すると、「Pages」という名前のコントロールではなく、フォームのPagesプロパティが返されます。

この例では、Me.Controls("Pages")を使用して明示的に、またはbang演算子_Me!Pages_を使用して暗黙的に「Pages」コントロールにアクセスできます。ただし、bang演算子を使用すると、フォームのレコードセットに「Pages」という名前のフィールドが存在する場合、Accessが代わりに「Pages」という名前のフィールドを返す可能性があることに注意してください。

。Valueはどうですか?
質問では明示的に言及されていませんが、このトピックは上記のコメントで取り上げられました。フィールドオブジェクトとほとんどの「データバインド可能」¹コントロールオブジェクトのデフォルトプロパティは_.Value_です。これはデフォルトのプロパティであるため、通常、常に明示的に含めることは不必要に冗長であると見なされます。したがって、これを行うのが標準的な方法です。

_Dim EmployeeLastName As String
EmployeeLastName = Me.tbLastName
_

の代わりに:

_EmployeeLastName = Me.tbLastName.Value
_

辞書にキーを設定するときの微妙な.Valueバグに注意してください
この規則によって微妙なバグが発生する場合があります。私が実際に実際に遭遇した最も注目すべき(そして、メモリが機能する場合のみ)のは、フィールド/コントロールの値を辞書キーとして使用する場合です。

_Set EmployeePhoneNums = CreateObject("Scripting.Dictionary")
Me.tbLastName.Value = "Jones"
EmployeePhoneNums.Add Key:=Me.tbLastName, Item:="555-1234"
Me.tbLastName.Value = "Smith"
EmployeePhoneNums.Add Key:=Me.tbLastName, Item:="555-6789"
_

上記のコードがEmployeePhoneNumsディクショナリに2つのエントリを作成すると予想されるでしょう。代わりに、重複するキーを追加しようとしているため、最後の行にエラーがスローされます。つまり、tbLastNameコントロールオブジェクト自体がキーであり、コントロールの値ではありません。このコンテキストでは、コントロールの値は重要ではありません。

実際、オブジェクトのメモリアドレス(ObjPtr(Me.tbLastName))は、辞書のインデックスを作成するために舞台裏で使用されている可能性が高いと思います。私はこれを裏付けるように思われる簡単なテストをしました。

_'Standard module:
Public testDict As New Scripting.Dictionary
Sub QuickTest()
    Dim key As Variant
    For Each key In testDict.Keys
        Debug.Print ObjPtr(key), testDict.Item(key)
    Next key
End Sub

'Form module:
Private Sub Form_Current()
    testDict(Me.tbLastName) = Me.tbLastName.Value
    Debug.Print ObjPtr(Me.tbLastName); "..."; Me.tbLastName
End Sub
_

上記のコードを実行すると、フォームを閉じて再度開くたびに、1つの辞書アイテムが追加されます。レコードからレコードに移動する(したがって、Form_Currentルーチンを複数回呼び出す)と、新しいディクショナリアイテムは追加されません。これは、コントロールオブジェクト自体がディクショナリにインデックスを付けており、コントロールの値ではないためです。

私の個人的な推奨事項/コーディング規約
何年にもわたって、私は次のプラクティスを採用してきました、YMMV:

  • フォーム/レポートのコントロール名にコントロールタイプインジケーターを付けます(例:tbTextBoxlblLabelなど)
  • _Me._表記を使用してコード内のフォーム/レポートコントロールを参照してください(例:_Me.tbLastName_)
  • そもそも 問題のある名前 でテーブル/クエリフィールドを作成することは避けてください
  • レガシーアプリケーション(例:_Me!_)との競合がある場合は、_Me!Pages_表記を使用します
  • レポートレコードセットフィールド値にアクセスするための非表示のレポートコントロールを含める
  • 状況が追加された冗長性(辞書キーなど)を正当化する場合にのみ、明示的に_.Value_を含めます。

¹「データバインド可能」コントロールとは何ですか?
基本的に、TextBoxやComboBoxなどのControlSourceプロパティを持つコントロール。バインドできないコントロールは、LabelやCommandButtonのようなものです。 TextBoxとComboBoxの両方のデフォルトプロパティは_.Value_です。ラベルとコマンドボタンにはデフォルトのプロパティがありません。

3
mwolfe02