Microsoft Access 掲示板

ファームにある複数のコンボボックスでフィルター

15 コメント
views
4 フォロー

初心レベルの者です。リスト型フォームにフィルター条件用のコンボボックスがあります。コンボはcb1、cb2・・・とします。各コンボの更新後イベントに下記の様なコード記述してます。
Dim strFilter1
strFilter1 = "取引先ID =" & Me.[cb1]
Me.Filter = strFilter1
Me.FilterOn = True
例えばコンボボックスが3ヶある場合、選択有無で組合せは下記になります。(○が選択ありで×が未選択)
cb1  cb2  cb3
 ○   ×   ×
 ○   ○   ×
 ○   ○   ○
 ×   ○   ×
 ×   ○   ○
 ×   ×   ○
これに対応する為にベタな方法でcb1・cb2・cb3の各更新後イベントの条件記述を下記でしてます。
(例)変数strFilter1~3に抽出式を入れて
If Not IsNull(Me.[cb1]) And Not IsNull(Me.[cb2])  and Not IsNull(Me.[cb3])Then 
 Me.Filter = strFilter1 & " AND " & strFilter2  & " AND " & strFilter3
としてフィルター実行させています。If条件の組合せで各コンボに記述しています。条件コンボがもっと増えたりすると記述量も多くなり大変だなと感じてます(それも各コンボに)。
もっとスマートなアイデアがないかなと思い投稿しました。

COCO
作成: 2024/02/15 (木) 13:11:23
通報 ...
1
hiroton 2024/02/15 (木) 13:56:16 48bf2@f966d

プログラミングの基本ですが、共通の処理は関数にまとめるものです

なので、「共通になるような処理を考えてそれを呼び出すようにする」とスマートです

ACCESSのフィルター処理は結構テンプレート的な感じで、ここでも同じような回答を何度かしてますが、自作のプロシージャを作ってそれを呼び出すようにすると良いです

例えば、以下のようにsetFilterプロシージャを作成し、各コンボボックスの更新後処理でそれを呼び出すようにします

Private Sub cb1_AfterUpdate()
    setFilter
End Sub

Private Sub cb2_AfterUpdate()
    setFilter
End Sub

Private Sub cb3_AfterUpdate()
    setFilter
End Sub

Private Sub setFilter()
'フィルタをかける処理
End Sub

各イベントが、プロシージャの呼び出ししかしないのであれば、そのプロシージャを関数にして、ユーザー定義関数の呼び出しの形にするとコードはもっとすっきりしますね

モジュール

Private Function setFilter()
'フィルタをかける処理
End Sub

各コントロールの更新後処理プロパティ

=setFilter()
2

未選択(Null)の場合は条件なしということですね。

条件式変数に選択してあれば条件を追加していくという方法でいいでしょう。

フィールド名は適当、データ型は数値型の場合ですので、実際のものに合わせて修正してください。

Dim strFilter As String

If Me.[cb1].Value <> "" Then
    strFilter = strFilter & " AND フィールド1=" & Me.[cb1].Value
End If
If Me.[cb2].Value <> "" Then
    strFilter = strFilter & " AND フィールド2=" & Me.[cb2].Value
End If
If Me.[cb3].Value <> "" Then
    strFilter = strFilter & " AND フィールド3=" & Me.[cb3].Value
End If

strFilter = Mid(strFilter, 6) '先頭の" AND "を削除
Me.Filter = strFilter
Me.FilterOn = (strFilter <> "")
3
hiroton 2024/02/15 (木) 14:14:06 48bf2@f966d

具体的なフィルタ文字列生成部分に関しては、複数の組み合わせに対して、「直前の出力があった場合だけ"AND"を出力する」ということになりますが、これを毎回チェックするのはめんどくさいので、「出力があればとりあえず" AND "を付ける」「最後に無駄な" AND "を消す」ようにします


これ入力してたらコード例をhatenaさんが投稿されたのでコードはそちらを参考に


このように、「似たような処理」を「共通になるような処理」に落とし込めればすっきりしたコード記述ができるようになります

4
COCO 2024/02/16 (金) 15:22:21 ddfe5@469dd

hirotonさん haetnaさん ありがとうございました。
haetnaさんの回答にある Mid(strFilter, 6) は何かで見た事がありますがやっと意味がわかりました。”はカウントせずに半角スペースはカウントに入るのですね。因みにMe.FilterOn = (strFilter <> "")の右辺はTrueと同じ意味になるのでしょうか?
hirotonさんの回答にある Private Sub setFilter() 'フィルタをかける処理 ですが変数自体にコード記述が出来るのですね? フォーム上のイベントにはそれは出来ないのでVBA画面に直接記述したらいいのでしょうか?
知識不足で、すみません。

5

haetnaさんの回答にある Mid(strFilter, 6) は何かで見た事がありますがやっと意味がわかりました。”はカウントせずに半角スペースはカウントに入るのですね。因みにMe.FilterOn = (strFilter <> "")の右辺はTrueと同じ意味になるのでしょうか?

(strFilter <> "")は、strFilter が ""(空文字列) でなければ True、""(空文字列) なら False になります。
下記のコードと同義になります。

If strFilter <> "" Then
    Me.FilterOn = True
Else
    Me.FilterOn = False
End If

5行のコードが1行で済むのでついつい使ってしまいますが、コードの意味のつかみやすさからいけば、5行で書いた方がいいですね。

hirotonさんの回答にある Private Sub setFilter() 'フィルタをかける処理 ですが変数自体にコード記述が出来るのですね? フォーム上のイベントにはそれは出来ないのでVBA画面に直接記述したらいいのでしょうか?

VBAに直接記述します。
Private Sub setFilter() は変数ではなくプロシージャです。
Subプロシージャ、Functionプロシージャについて調べてください。

#1 のhirotonさんの最初のコードの 'フィルタをかける処理 の部分を #2 の私のコードに置き換えればOKです。

6
COCO 2024/02/19 (月) 10:13:55 ddfe5@71072

hatenaさん ありがとうございます。Subプロシージャ、Functionプロシージャの事は少し調べました。何となくしか分かってませんが実際に試そうと思いまして(別のコードで同じフォームに記述しているものがあるので)下記の様な内容のでプロシージャを作成しました。
Public Sub record_idou()
 If IsNull(Me.[F1]) Then
    MsgBox"F1未入力時はレコード移動できません"
   Exit Sub
 End If
End Sub
これをフォームのレコード移動ボタンに下記を記述しました。
 Call record_idou
  DoCmd.GoToRecord , , acPrevious
試すとF1未入力時・・・・のメッセージは出るのですがレコード移動はしてしまいます。Exit Subが効いてません。
何か記述不足しているのでしょうか?

7
hiroton 2024/02/19 (月) 10:47:52 bec36@f966d

Exit Subは「現在のSubプロシージャを終了する」命令です

 Call record_idou
  DoCmd.GoToRecord , , acPrevious

Public Sub record_idou()
 If IsNull(Me.[F1]) Then
    MsgBox"F1未入力時はレコード移動できません"
   Exit Sub
 End If
End Sub

は、イメージとしては

' Call record_idou
Public Sub record_idou()
 If IsNull(Me.[F1]) Then
    MsgBox"F1未入力時はレコード移動できません"
   Exit Sub    '─┐
 End If         ' │ここに飛ぶ
End Sub          '←┘
  DoCmd.GoToRecord , , acPrevious

となり、record_idouプロシージャが終了して、それに続くDoCmd.GoToRecord , , acPreviousが実行されます

つまり、この例では意味のないExit Subですね(続く処理がないのでどうせ何もせずプロシージャが終了する)

8

理由はhirotonさんから回答済みです。
このような場合の対処法としては、SubではなくFunctionにして戻り値で次の処理を振り分けます。

Public Function record_idou() As Boolean
 If IsNull(Me.[F1]) Then
    MsgBox "F1未入力時はレコード移動できません"
     record_idou = False '戻り値設定
  Else
     record_idou = True  '戻り値設定   
 End If
End Function
Private Sub cmd前へ移動_Click()
    If record_idou() = True Then
        DoCmd.GoToRecord , , acPrevious
    End If
End Sub
9

ただし、F1フィールドを入力必須にしたいのなら、テーブルのデザインビューでF1フィールドのプロパティの「値要求」を「はい」(テキスト型の場合はさらに「空文字列の許可」を「いいえ」)にしておけば済みますので、実際に使う必要性はないですね。

10
COCO 2024/02/19 (月) 11:47:59 ddfe5@71072

hirotonさん haetnaさん ありがとうございました。浅はかでした。
hatenaさんの記述で試して上手くいきました。あと初歩的な質問ですみません参考にまでお聞きします(ルールが分かってないので)、
・Callで呼び出す必要はなかったのですね?
・Public Function record_idou() As BooleanのAsデータ型を()内に記述したものを見た事があるのですが、それはケースbyケースなのでしょうか?

11
hiroton 2024/02/19 (月) 12:30:20 bec36@f966d

・Callで呼び出す必要はなかったのですね?

VBAにはいろいろな記述省略のルールがあります。Callは省略しても良い記述ですね

Call ステートメント
VBAの省略可能な記述について(エクセルの神髄さん)

プロシージャの呼び出しのためのCallは上記リンク先エクセルの神髄さんでは省略しないほうがいいとも言っていますね。見慣れないモノや、わかりにくいモノは省略できても記述しておいたほうが無難かもしれません

・Public Function record_idou() As BooleanのAsデータ型を()内に記述したもの

引数(ひきすう)と言います
Microsoft公式ドキュメントでも解説はありますが、さすがにわかりにくいと思うので別なサイトのリンクを貼っておきます

第107回.プロシージャーの引数(エクセルの神髄さん)

12
COCO 2024/02/19 (月) 12:45:39 ddfe5@71072

hirotonさん ありがとうございます。中々プロシージャは奥の深いものですね。頭がこんがりますが、少しづつ勉強していきます。

13
COCO 2024/02/19 (月) 15:13:27 ddfe5@71072

何度もすみません、最後に一つだけ質問があります。hatenaさんから最初に頂いた回答の下記を使いプロシージャを作成しました(setFilter1として)。
If Me.[cb1].Value <> "" Then
    strFilter = strFilter & " AND フィールド1=" & Me.[cb1].Value
End If
***
****
strFilter = Mid(strFilter, 6) '先頭の" AND "を削除
Me.Filter = strFilter
***
そしてコンボ更新後イベントにCall setFilter1で実行しました⇒上手くいきました。(これはすごく便利です!)
質問はIf Me.[cb1].Value <> "" Thenの右辺です。ここを<>Nullにすると条件に引っかかりません。もう少し様子みるとこのフォームにはフィルター解除ボタンがあり Me.[cb1] = Null *** Me.FilterOn = False
で解除しており、その実行直後なら<>Nullでもいけますが、コンボ値を他の値に選択では<> ""でないと駄目です。
これで機能しているので問題ないのですが、それが不思議で。””とNullの違いはネットで確認しました。
この場合は<>””が妥当なのでしょうか?

14
hiroton 2024/02/19 (月) 16:05:18 bec36@f966d

NULL比較演算子による演算ができません

詳しいことはかなり複雑なので、一通り情報に目を通してみてください
https://www.google.com/search?q=VBA NULL 比較

本来、Nullの可能性がある場合の比較は、特別な処理・特別な判定をする必要がありますが、「VBAにおいてはNullを比較演算に使うと全てFalseとして扱われる」という特徴があります。(コードの実装としてこれを活かすのは本来よろしくありません)

つまり、

Me.[cb1].Value <> Null

とすると、コンボボックスの値に関わらず常にFalseの処理が為されるため、意図しない動作をします

逆に、<>""の判定においてコンボボックスの値がNullの場合は

Null <> "" '//本来やってはいけない演算

Falseを返して欲しいときにはFalseだった場合の処理がされるので表面上は問題なく動作しているというわけです


今回のような場合であれば、コンボボックスの値をチェックしてNull比較をしないようにするのが正解で、事前にコンボボックスの値をチェックしてコンボボックスの値がNullだったら""とみなすようにします。たとえばNz関数を使って

Nz 関数

Nz(Me.[cb1].Value, "") <> ""

蛇足
Nullなら空文字とみなしたいなら「"" & Me!cb1 <> ""」くらいの手抜きで済ませちゃうんですけどね

15
COCO 2024/02/19 (月) 16:29:01 ddfe5@71072

色々とありがとうございます。だからMe.FilterOn = (strFilter <> "")もいけたのですね。
厳密にしようとすると細かい事が一杯ありますね。全然知らないのと少しでも知っているのでは意図しない動作の時に違うなと思いました。これも奥の深いことですね。さらっと出来る方々がすごいです。