重ねる
得意先のコンボボックスを配置している。
名前が良く似ているため、一部を入力したらリストが絞り込まれる支援をしたい。
フォームが単票であれば容易に作成できた。
でも、やりたいのは帳票フォームに配置したコンボボックスで。
コンボボックスに指定したテーブルには、
得意先ID(主キー), 得意先名, よみ
があり、得意先名を一覧として表示している。
検索用テキストボックス「txt検索」に入力したら、「よみ」の一部と一致するものだけを表示したい。
動きとしては、絞り込まれた内容を自動的にドロップダウン表示に。
※※ 私が解釈した内容で書き換えています。
名前が良く似ているため、一部を入力したらリストが絞り込まれる支援をしたい。
フォームが単票であれば容易に作成できた。
でも、やりたいのは帳票フォームに配置したコンボボックスで。
コンボボックスに指定したテーブルには、
得意先ID(主キー), 得意先名, よみ
があり、得意先名を一覧として表示している。
検索用テキストボックス「txt検索」に入力したら、「よみ」の一部と一致するものだけを表示したい。
動きとしては、絞り込まれた内容を自動的にドロップダウン表示に。
※※ 私が解釈した内容で書き換えています。
検索したいフィールドが複数あるのであれば、コンボ vs リスト vs メイン/サブ も考えられるかと思います。
ただ、今回は1フィールドだけ・・・・
Yu-Tang さんの コンボボックスの一覧を入力時のみ制限する方法〜帳票形式編〜 が適当と思い
回答してみたんですけど・・・・
フォーム上でコントロールを重ねる・・・・・
これ、何年か前(知恵袋にいたころ)に hatana さんに教えていただいたものなんですが・・・・
(そのころの私は、帳票では実現できない・・・って思ってました)
何故重ねるのか、大きく3つあるような気がします。
・隠したい
・下の方を見せたい
・直接触らせたくない
回答後、検索用の「txt検索」ってどこに配置しているんだろう・・・
コンボボックスにコンボボックスを重ねるのは、必ず何かを見せたいんだよね。
「txt検索」が、もし詳細に配置されてたら、全レコードに同じ表示がされて邪魔と感じるのかなぁ・・・
ここで、不要な表示(処理中のレコード以外の表示)を隠すチョッと違う用途での「重ねる」を含めて
具体的に回答してみるかな・・・・
って、うまく伝えられなかったのか、環境が違ったのか、問題あってダメだったようです。
反省の意味も込めて、回答を検証しながら、サンプルを作ってみます。
続きを読んでみようかな ---≫
まず、どういう回答をしていたのか、以下に(テーブル名、フィールド名部分はチョッと変更しています)
以降、この回答に沿ってフォームサンプルを作っていきたいと思います。
使用するテーブル情報になりますが
テーブル「T得意先」
テーブル「T受付」
※「得意先ID」部分にルックアップを設定
表示コントロール: コンボボックス
値集合タイプ: テーブル/クエリ
値集合ソース:
SELECT T得意先.得意先ID, T得意先.得意先名, T得意先.よみ FROM T得意先 ORDER BY T得意先.よみ;
連結列: 1
列数: 3
列幅: 0cm;3cm;0cm
入力チェック: はい
このルックアップを設定しておくと、
フォームウィザードでフォームを作成した際、得意先ID 部分をコンボボックスで作成してくれます。
※ 回答では「よみ」部分を対象外にしてましたが、本サンプルでは表示はしないものの抽出対象に。
※ フィールド「an」は本来は不要です。
※ が、回答後半でオートナンバを使った時の例があるので追加しておきます。
なお、「T得意先」の内容は以下の様になってます(雰囲気で)
フォーム「F1」
まずは、単票フォームで動きを確かめますか・・・という事で、
「T受付」を元にフォームウィザードで、表形式(横に並べたかっただけ)として作成後、単票に変更。
配置を調整し、ヘッダ部に検索用テキストボックス「txt検索」を配置します。
「受付ID」部分は、VBAで採番するので「編集ロック」を「はい」としておきます。
コンボボックス「得意先ID」の値集合ソースを以下に変更します
VBAで以下を記述します
「受付ID」の処理はどちらか一方の記述で良いですが、
・既定値の表示は、あくまで候補
・更新前で新規なら正式に採番する
という意味合いになってます。
フォーム「F2」
フォーム「F1」を帳票フォームにしていきます。
フォーム「F1」を「F2」としてコピー後、既定のビューを帳票フォームとします。
いきなりコンボボックスを重ねる・・・
どちらの表示がどうなっているかわかりやすいように、縦に並べておくこととします。
コンボボックス「得意先ID」をコピーして、下側に配置します。
今までの上側コンボボックス名を「コンボ前」に変更し、背景スタイルを透明に変更します。
コピーしてできた下側のコンボボックスを変更していきます。
名前: コンボ後
値集合ソース: SELECT 得意先ID, 得意先名, よみ FROM T得意先 ORDER BY よみ;
タブストップ: いいえ
画面上の修正は以上ですが、コンボボックス名「得意先ID」を「コンボ前」に変更したことによる
VBA記述は自動で置き換わってくれないようです。
自分で修正します。
起動後、新規行に移って、検索文字に「CD」を入力して Enter キー押下すると以下の様な表示に。

受付ID=1 の「コンボ前」(上側コンボボックス)の表示はなくなりましたね。
フォーム「F3」
では次に、検索文字用の非連結テキストボックス「txt検索」をヘッダ部から詳細に位置変更します。
フォーム「F2」を「F3」にコピー後、テキストボックスのラベル部分と切り離し、本体部分を詳細に・・・
なお、タブ移動順を見た目左から順に移動するように変更しておきます。(必須ではありません)
VBA記述は変更ありません。
起動後、新規行に移って、検索文字に「CD」を入力して Enter キー押下すると以下の様な表示に。

全レコード同じ表示になるので、新規行以外が邪魔かぁ〜〜
フォーム「F4」
そこで、処理しているレコード以外の表示を隠しましょう・・・・と
ここから、帳票フォームにしているテーブル「T受付」のオートナンバ「an」の出番になります。
フォーム「F3」を「F4」にコピーします。
ヘッダ部に非表示のテキストボックス「txt0」を配置します。
詳細部分に「txt検索」より、ひと回り大きいテキストボックス「txt1」を配置します。
「txt1」のプロパティを変更していきます。
背景スタイル:透明
境界線スタイル:透明
フォント名:Webdings
フォントサイズ:28
前景色:詳細の背景と同じ色(#FFFFFF)
コントロールソース:
=IIf(IsNull([an]) And IsNull([txt0]),"",IIf([txt0]=[an],"","gggg"))
この「txt1」を「txt検索」に重ねて、「txt1」が上(前面)になるようにします。
で、VBA記述したのが以下
起動後、新規行に移って、検索文字に「CD」を入力して Enter キー押下すると以下の様な表示に。

レコードを移動していくと、移動した先だけに「txt検索」が表示されるようになります。
ただ、注意事項として記述していた、新規行で編集状態に突入した際の表示に難点があり

(図左)「備考」に入力すると、編集状態になり「txt検索」表示は、さらなる新規行に移る
(図中)あるべきところをクリックすると入力することはできる
(図右)Enter 後は、消えて絞り込んだ表示はされるけど
ここまでの動きでオートナンバ「an」を使ってきましたが、実際に必要なのは次の改善策において。
レコードを特定できる主キー「受付ID」があるので、上記「an」を「受付ID」に変更しても動くはずです。
フォーム「F5」
新規行が編集状態に移行した際の「txt検索」表示位置を改善していきます。
フォーム「F4」を「F5」でコピーします。
画面上の変更はないので、VBA 記述を追加していきます。
起動後、新規行に移って、「備考」に入力し編集状態に移行しても表示は意図どおりに。

オートナンバが、いつ採番されるのかのタイミングを使っただけで、
新規行に移動した時「an」は Null
編集状態に移行する前の Form_BeforeInsert / Form_Dirty 内でも「an」は Null
Form_Dirty を戻って、次に何かをやろうとした時には 「an」は Not Null に。
Form_BeforeInsert / Form_Dirty は連続して発生すると思っているので、終わった後を Timer で・・・
主キーである「受付ID」を使おうとしたら、Form_BeforeInsert 時に採番して、それを「txt0」に設定・・・
従来の Form_BeforeUpdate での採番はやめる・・・
で、うまくいくと思います(未検証)
もちろん「an」となっているところは全て「受付ID」に変更します。
フォーム「F5完」
フォーム「F5」で動きとして完成しました。
フォーム「F5」を「F5完」としてコピー後、コンボボックス「コンボ前」「コンボ後」を重ねます。
どちらのコンボボックス表示が見えているか、重ねてもわかりやすいように
「コンボ前」の前景色を赤(#FF0000)に、「コンボ後」の前景色を緑(#00FF00)に設定し、
「コンボ後」を「コンボ前」に重ね、「コンボ後」を最背面に設定します。
空いた詳細部分を詰めます。
起動後、新規行に移って、検索文字に「CD」を入力して Enter キー押下すると以下の様な表示に。

フォーム「F6」
このフォームでは、
・「txt検索」は、普段表示しておかない
・「txt検索」があるべきところをクリックした時に表示しましょう
・コンボボックスに絞り込み表示されたら「txt検索」は消えても良いです
確認用なので、フォーム「F5」を「F6」としてコピーします。
ヘッダ部の非表示テキストボックス「txt0」を削除します。
詳細にあるテキストボックス「txt1」を、コントロールの変更でラベルにします。
名前を「lab1」に変更、背景スタイルを「普通」に変更します。
(文字は表示しないのでフォント名は何でもいいです。そのままでも。MS Pゴシックに変更しましたけど)
VBAの記述を以下にします。
動き的には、こんな感じかなぁ〜っていう位でしょうか。
動きとして「F5」「F6」の大きな違いは、
レコード移動する時、「txt検索」があるべきところをクリックした場合・・・・
・「F5」ではレコード移動できる
・「F6」ではレコード移動できない
単独のラベルをクリックしてもフォーカス移動しない
何かにくっ付いているラベルは、クリックすることでくっ付いている先にフォーカスが移動する
「F5」で「txt検索」を覆っているのはテキストボックス本体・・・フォーカス移動が発生する
の差になるのだと思います。
今回作ってきたフォームのサンプルと、回答内容は大きく違ってないような・・・
(私が両方やっているので、思い込みなのかも・・・)
気をつけていこう。
今回の収穫
2007 で Form_Undo を使うものを 2000 形式で保存。
これを Win2k + Access 2000 で動かすと Form_Undo は動かない。
でも、この mdb を 2007 で動かすと、Form_Undo は動く。
Win2k + Access 2000 で新規に作って、Form_Undo を組み込んでおくと、2007 で動くのかなぁ
後でやってみよう。
おまけ
新規登録中、次の「受付ID」が編集中の「受付ID」と同じ数値になっている件を改善したものになります。
フォームのサンプルはありません。(VBA記述のみ)そこそこ動くと思います。
オートナンバ「an」を使った時
オートナンバ「an」を使わずに、主キー「受付ID」を使った時
また、「txt1」のコントロールソース内の [an] を [受付ID] に変更します
(動いているように見えるけど、詳しくは追っていないので無理/無駄部分があるかも・・・・)
Form_Undo の際、「an」を使用していた時には
今回の「受付ID」では既定値を設定しているので、新規行には値が振られている状態になっているようです。
なので、値を戻す処理は不要そうです。
余談
絞り込み表示をしたい・・・・絞り込まれたもの以外表示すべきではないと思っています。
できないのなら、そのほかの方法を模索すべきと思います。
もし、Form.Filter で、絞り込まれたもの/でないものを、並び順だけで表示されても・・・・
私はそう思う・・・・っていうだけですけど。
※ Form_Undo を使っているので、2000 ではうまく動きません。
≪--- 続きを閉じちゃえ
まず、どういう回答をしていたのか、以下に(テーブル名、フィールド名部分はチョッと変更しています)
帳票フォームの「得意先ID」を入力する部分がコンボボックスになっている。
つまり、コントロールソースが 得意先ID
コンボボックスには、得意先名が表示されている、とします。
下側に配置するコンボボックス名を「コンボ後」と仮定します。
値集合ソースを
SELECT 得意先ID, 得意先名 FROM T得意先;
列数:2
連結列:1
列幅:0cm;3cm (得意先ID は表示しない様に)
入力チェック:はい
コントロールソースは 得意先ID
検索文字列を入力する非連結テキストボックス名を「txt検索」と仮定します。
上記で出来上がった「コンボ後」をコピーし「コンボ前」とします。
「コンボ前」のプロパティを変更していきます。
値集合ソースを以下に変更
SELECT 得意先ID, 得意先名 FROM T得意先
WHERE IIF(IsNull([txt検索]),True,よみ LIKE '*' & [txt検索] & '*');
背景スタイルを透明にします。
「コンボ後」のタブストップを「いいえ」にした後、
「コンボ後」「コンボ前」を重ねます。(「コンボ前」が上(前面)になるように)
そして、以下のイベントで処理します。
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
なぜ、重ねるか
「コンボ前」は絞り込まれた内容になります。
そこに、連結した「得意先ID」がなければ何も表示されません。
ですが、
背景スタイルが透明になっているので、透けて「コンボ後」の表示が見えます。
「得意先ID」があれば、同じ文字列を表示することになるので、
重ね方がズレていなければおかしくはなりません。
「コンボ前」は、フォーカスを得ると透けなくなるようです。
「txt検索」が帳票フォームのどの位置に配置されているか、ですが、
・ヘッダ/フッタ部分であれば上記で終了です。
コンボボックスに並べて、詳細に配置しているのなら、
処理しているレコード(行)以外での表示は邪魔になると思います。
邪魔と感じなければ上記で終了です。
以下は、「txt検索」を隠す方法になります。
ただ、レコードを特定できるもの(オートナンバ等)がなければできません。
この帳票で表示しているレコードにオートナンバ「an」が存在すると仮定します。
ヘッダ部に非表示のテキストボックス「txt0」を配置します。
詳細部分に「txt検索」より、ひと回り大きいテキストボックス「txt1」を配置します。
「txt1」のプロパティを変更していきます。
背景スタイル:透明
境界線スタイル:透明
フォント名:Webdings
フォントサイズ:「txt検索」の倍以上
前景色:詳細の背景と同じ色
コントロールソース:
=IIf(IsNull([an]) And IsNull([txt0]),"",IIf([txt0]=[an],"","gggg"))
この「txt1」を「txt検索」に重ねて、「txt1」が上(前面)になるようにします。
上記でのイベントに加え、以下のイベントを追記します。
Private Sub Form_Current()
Me.txt0 = Me.an
' Me.txt検索 = Null
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
現在選ばれているレコードを特定できる「an」を「txt0」に設定します。
「txt1」では、選ばれているレコードであれば何も表示しません。
背景スタイルを透明にしているので「txt検索」が透けて見えるようになります。
選ばれているレコードでなければ、文字 "gggg" を表示します。
フォント「Webdings」の時の "g" は塗潰し文字(■の親戚?)になります。
それを表示する時の色が、詳細の色と同じであれば「txt検索」を隠せます。
やってみて隠せなかったら、フォントのサイズ、"g" の個数を変更していきます。
「txt1」にフォーカスが移ろうとした時、透けて「txt検索」が見えているはずなので
「txt検索」を入力/変更できるように「txt検索」へフォーカスを移動します。
フォーカスを得た「txt検索」は、下側に配置したにもかかわらず最前面に出てきます。
フォーカスを失うと、下側に戻っていきます。
レコードを移動した時に「txt検索」をクリアしたかったら、Form_Current で。
ただ、上記のままでは新規登録の際、表示ズレが発生します。
新規行に入力し始めると、「txt検索」が見えるのは、さらなる新規行に。
編集中の新規行では、「txt検索」は見えなくなります。
見えなくなりますが、あるべきところをクリックすると「txt検索」がでてきます。
「txt検索」は、フォーカスを失うと、また消えてしまいます。
この動きは嫌だと思います。
上記オートナンバを使った時には、以下の処理を入れることで改善されます。
Private Sub Form_Timer()
Me.TimerInterval = 0
Me.txt0 = Me.an
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.TimerInterval = 10
End Sub
Private Sub Form_Undo(Cancel As Integer)
If (Me.NewRecord) Then Me.txt0 = Null
End Sub
この所は、レコードを特定するものに何が使えるかで変わってくると思います。
それによっては、「txt検索」は隠さなくて良いや・・・とか、
ヘッダ/フッタ側に配置替えってことも・・・
つまり、コントロールソースが 得意先ID
コンボボックスには、得意先名が表示されている、とします。
下側に配置するコンボボックス名を「コンボ後」と仮定します。
値集合ソースを
SELECT 得意先ID, 得意先名 FROM T得意先;
列数:2
連結列:1
列幅:0cm;3cm (得意先ID は表示しない様に)
入力チェック:はい
コントロールソースは 得意先ID
検索文字列を入力する非連結テキストボックス名を「txt検索」と仮定します。
上記で出来上がった「コンボ後」をコピーし「コンボ前」とします。
「コンボ前」のプロパティを変更していきます。
値集合ソースを以下に変更
SELECT 得意先ID, 得意先名 FROM T得意先
WHERE IIF(IsNull([txt検索]),True,よみ LIKE '*' & [txt検索] & '*');
背景スタイルを透明にします。
「コンボ後」のタブストップを「いいえ」にした後、
「コンボ後」「コンボ前」を重ねます。(「コンボ前」が上(前面)になるように)
そして、以下のイベントで処理します。
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
なぜ、重ねるか
「コンボ前」は絞り込まれた内容になります。
そこに、連結した「得意先ID」がなければ何も表示されません。
ですが、
背景スタイルが透明になっているので、透けて「コンボ後」の表示が見えます。
「得意先ID」があれば、同じ文字列を表示することになるので、
重ね方がズレていなければおかしくはなりません。
「コンボ前」は、フォーカスを得ると透けなくなるようです。
「txt検索」が帳票フォームのどの位置に配置されているか、ですが、
・ヘッダ/フッタ部分であれば上記で終了です。
コンボボックスに並べて、詳細に配置しているのなら、
処理しているレコード(行)以外での表示は邪魔になると思います。
邪魔と感じなければ上記で終了です。
以下は、「txt検索」を隠す方法になります。
ただ、レコードを特定できるもの(オートナンバ等)がなければできません。
この帳票で表示しているレコードにオートナンバ「an」が存在すると仮定します。
ヘッダ部に非表示のテキストボックス「txt0」を配置します。
詳細部分に「txt検索」より、ひと回り大きいテキストボックス「txt1」を配置します。
「txt1」のプロパティを変更していきます。
背景スタイル:透明
境界線スタイル:透明
フォント名:Webdings
フォントサイズ:「txt検索」の倍以上
前景色:詳細の背景と同じ色
コントロールソース:
=IIf(IsNull([an]) And IsNull([txt0]),"",IIf([txt0]=[an],"","gggg"))
この「txt1」を「txt検索」に重ねて、「txt1」が上(前面)になるようにします。
上記でのイベントに加え、以下のイベントを追記します。
Private Sub Form_Current()
Me.txt0 = Me.an
' Me.txt検索 = Null
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
現在選ばれているレコードを特定できる「an」を「txt0」に設定します。
「txt1」では、選ばれているレコードであれば何も表示しません。
背景スタイルを透明にしているので「txt検索」が透けて見えるようになります。
選ばれているレコードでなければ、文字 "gggg" を表示します。
フォント「Webdings」の時の "g" は塗潰し文字(■の親戚?)になります。
それを表示する時の色が、詳細の色と同じであれば「txt検索」を隠せます。
やってみて隠せなかったら、フォントのサイズ、"g" の個数を変更していきます。
「txt1」にフォーカスが移ろうとした時、透けて「txt検索」が見えているはずなので
「txt検索」を入力/変更できるように「txt検索」へフォーカスを移動します。
フォーカスを得た「txt検索」は、下側に配置したにもかかわらず最前面に出てきます。
フォーカスを失うと、下側に戻っていきます。
レコードを移動した時に「txt検索」をクリアしたかったら、Form_Current で。
ただ、上記のままでは新規登録の際、表示ズレが発生します。
新規行に入力し始めると、「txt検索」が見えるのは、さらなる新規行に。
編集中の新規行では、「txt検索」は見えなくなります。
見えなくなりますが、あるべきところをクリックすると「txt検索」がでてきます。
「txt検索」は、フォーカスを失うと、また消えてしまいます。
この動きは嫌だと思います。
上記オートナンバを使った時には、以下の処理を入れることで改善されます。
Private Sub Form_Timer()
Me.TimerInterval = 0
Me.txt0 = Me.an
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.TimerInterval = 10
End Sub
Private Sub Form_Undo(Cancel As Integer)
If (Me.NewRecord) Then Me.txt0 = Null
End Sub
この所は、レコードを特定するものに何が使えるかで変わってくると思います。
それによっては、「txt検索」は隠さなくて良いや・・・とか、
ヘッダ/フッタ側に配置替えってことも・・・
以降、この回答に沿ってフォームサンプルを作っていきたいと思います。
使用するテーブル情報になりますが
テーブル「T得意先」
| フィールド | 型 等々 |
|---|---|
| 得意先ID | オートナンバ (主キー) |
| 得意先名 | テキスト |
| よみ | テキスト |
テーブル「T受付」
| フィールド | 型 等々 |
|---|---|
| an | オートナンバ |
| 受付ID | 長整数 (主キー) |
| 得意先ID | 長整数 「T得意先」の得意先IDと |
| 年月日 | 日付/時刻 |
| 備考 | テキスト |
※「得意先ID」部分にルックアップを設定
表示コントロール: コンボボックス
値集合タイプ: テーブル/クエリ
値集合ソース:
SELECT T得意先.得意先ID, T得意先.得意先名, T得意先.よみ FROM T得意先 ORDER BY T得意先.よみ;
連結列: 1
列数: 3
列幅: 0cm;3cm;0cm
入力チェック: はい
このルックアップを設定しておくと、
フォームウィザードでフォームを作成した際、得意先ID 部分をコンボボックスで作成してくれます。
※ 回答では「よみ」部分を対象外にしてましたが、本サンプルでは表示はしないものの抽出対象に。
※ フィールド「an」は本来は不要です。
※ が、回答後半でオートナンバを使った時の例があるので追加しておきます。
なお、「T得意先」の内容は以下の様になってます(雰囲気で)
| 得意先ID | 得意先名 | よみ |
|---|---|---|
| 1 | 狩野 A | かあ ABC |
| 2 | 京本 B | かきい BCD |
| 3 | 熊谷 C | かくう CDE |
| 4 | 監物 D | かけえ DEF |
| 5 | 小林 E | かこお EFG |
| 6 | 安藤 A | あ ABC |
| 7 | 伊藤 B | あい BCD |
| 8 | 上野 C | あう CDE |
| 9 | 江頭 D | あえ DEF |
| 10 | 奥村 E | あお EFG |
| 11 | 高橋 A | たあ ABC |
| 12 | 千葉 B | たちい BCD |
| ・・・ | ・・・ | ・・・ |
フォーム「F1」
まずは、単票フォームで動きを確かめますか・・・という事で、
「T受付」を元にフォームウィザードで、表形式(横に並べたかっただけ)として作成後、単票に変更。
配置を調整し、ヘッダ部に検索用テキストボックス「txt検索」を配置します。
「受付ID」部分は、VBAで採番するので「編集ロック」を「はい」としておきます。
コンボボックス「得意先ID」の値集合ソースを以下に変更します
SELECT 得意先ID, 得意先名, よみ FROM T得意先
WHERE IIF(IsNull([txt検索]),True,よみ Like '*' & [txt検索] & '*') ORDER BY よみ;
WHERE IIF(IsNull([txt検索]),True,よみ Like '*' & [txt検索] & '*') ORDER BY よみ;
VBAで以下を記述します
Private Sub Form_Current()
Me.txt検索 = Null
Me.得意先ID.Requery
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.得意先ID.SetFocus
End Sub
Private Sub 得意先ID_Enter()
Me.得意先ID.Requery
End Sub
Private Sub 得意先ID_GotFocus()
Me.得意先ID.Dropdown
End Sub
Me.txt検索 = Null
Me.得意先ID.Requery
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.得意先ID.SetFocus
End Sub
Private Sub 得意先ID_Enter()
Me.得意先ID.Requery
End Sub
Private Sub 得意先ID_GotFocus()
Me.得意先ID.Dropdown
End Sub
「受付ID」の処理はどちらか一方の記述で良いですが、
・既定値の表示は、あくまで候補
・更新前で新規なら正式に採番する
という意味合いになってます。
フォーム「F2」
フォーム「F1」を帳票フォームにしていきます。
フォーム「F1」を「F2」としてコピー後、既定のビューを帳票フォームとします。
いきなりコンボボックスを重ねる・・・
どちらの表示がどうなっているかわかりやすいように、縦に並べておくこととします。
コンボボックス「得意先ID」をコピーして、下側に配置します。
今までの上側コンボボックス名を「コンボ前」に変更し、背景スタイルを透明に変更します。
コピーしてできた下側のコンボボックスを変更していきます。
名前: コンボ後
値集合ソース: SELECT 得意先ID, 得意先名, よみ FROM T得意先 ORDER BY よみ;
タブストップ: いいえ
画面上の修正は以上ですが、コンボボックス名「得意先ID」を「コンボ前」に変更したことによる
VBA記述は自動で置き換わってくれないようです。
自分で修正します。
Private Sub Form_Current()
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
起動後、新規行に移って、検索文字に「CD」を入力して Enter キー押下すると以下の様な表示に。

受付ID=1 の「コンボ前」(上側コンボボックス)の表示はなくなりましたね。
フォーム「F3」
では次に、検索文字用の非連結テキストボックス「txt検索」をヘッダ部から詳細に位置変更します。
フォーム「F2」を「F3」にコピー後、テキストボックスのラベル部分と切り離し、本体部分を詳細に・・・
なお、タブ移動順を見た目左から順に移動するように変更しておきます。(必須ではありません)
VBA記述は変更ありません。
起動後、新規行に移って、検索文字に「CD」を入力して Enter キー押下すると以下の様な表示に。

全レコード同じ表示になるので、新規行以外が邪魔かぁ〜〜
フォーム「F4」
そこで、処理しているレコード以外の表示を隠しましょう・・・・と
ここから、帳票フォームにしているテーブル「T受付」のオートナンバ「an」の出番になります。
フォーム「F3」を「F4」にコピーします。
ヘッダ部に非表示のテキストボックス「txt0」を配置します。
詳細部分に「txt検索」より、ひと回り大きいテキストボックス「txt1」を配置します。
「txt1」のプロパティを変更していきます。
背景スタイル:透明
境界線スタイル:透明
フォント名:Webdings
フォントサイズ:28
前景色:詳細の背景と同じ色(#FFFFFF)
コントロールソース:
=IIf(IsNull([an]) And IsNull([txt0]),"",IIf([txt0]=[an],"","gggg"))
この「txt1」を「txt検索」に重ねて、「txt1」が上(前面)になるようにします。
で、VBA記述したのが以下
Private Sub Form_Current()
Me.txt0 = Me.an
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
Me.txt0 = Me.an
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
起動後、新規行に移って、検索文字に「CD」を入力して Enter キー押下すると以下の様な表示に。

レコードを移動していくと、移動した先だけに「txt検索」が表示されるようになります。
ただ、注意事項として記述していた、新規行で編集状態に突入した際の表示に難点があり

(図左)「備考」に入力すると、編集状態になり「txt検索」表示は、さらなる新規行に移る
(図中)あるべきところをクリックすると入力することはできる
(図右)Enter 後は、消えて絞り込んだ表示はされるけど
ここまでの動きでオートナンバ「an」を使ってきましたが、実際に必要なのは次の改善策において。
レコードを特定できる主キー「受付ID」があるので、上記「an」を「受付ID」に変更しても動くはずです。
フォーム「F5」
新規行が編集状態に移行した際の「txt検索」表示位置を改善していきます。
フォーム「F4」を「F5」でコピーします。
画面上の変更はないので、VBA 記述を追加していきます。
Private Sub Form_Current()
Me.txt0 = Me.an
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
Private Sub Form_Timer()
Me.TimerInterval = 0
Me.txt0 = Me.an
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.TimerInterval = 10
End Sub
Private Sub Form_Undo(Cancel As Integer)
If (Me.NewRecord) Then Me.txt0 = Null
End Sub
Me.txt0 = Me.an
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
Private Sub Form_Timer()
Me.TimerInterval = 0
Me.txt0 = Me.an
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.TimerInterval = 10
End Sub
Private Sub Form_Undo(Cancel As Integer)
If (Me.NewRecord) Then Me.txt0 = Null
End Sub
起動後、新規行に移って、「備考」に入力し編集状態に移行しても表示は意図どおりに。

オートナンバが、いつ採番されるのかのタイミングを使っただけで、
新規行に移動した時「an」は Null
編集状態に移行する前の Form_BeforeInsert / Form_Dirty 内でも「an」は Null
Form_Dirty を戻って、次に何かをやろうとした時には 「an」は Not Null に。
Form_BeforeInsert / Form_Dirty は連続して発生すると思っているので、終わった後を Timer で・・・
主キーである「受付ID」を使おうとしたら、Form_BeforeInsert 時に採番して、それを「txt0」に設定・・・
従来の Form_BeforeUpdate での採番はやめる・・・
で、うまくいくと思います(未検証)
もちろん「an」となっているところは全て「受付ID」に変更します。
フォーム「F5完」
フォーム「F5」で動きとして完成しました。
フォーム「F5」を「F5完」としてコピー後、コンボボックス「コンボ前」「コンボ後」を重ねます。
どちらのコンボボックス表示が見えているか、重ねてもわかりやすいように
「コンボ前」の前景色を赤(#FF0000)に、「コンボ後」の前景色を緑(#00FF00)に設定し、
「コンボ後」を「コンボ前」に重ね、「コンボ後」を最背面に設定します。
空いた詳細部分を詰めます。
起動後、新規行に移って、検索文字に「CD」を入力して Enter キー押下すると以下の様な表示に。

フォーム「F6」
このフォームでは、
・「txt検索」は、普段表示しておかない
・「txt検索」があるべきところをクリックした時に表示しましょう
・コンボボックスに絞り込み表示されたら「txt検索」は消えても良いです
確認用なので、フォーム「F5」を「F6」としてコピーします。
ヘッダ部の非表示テキストボックス「txt0」を削除します。
詳細にあるテキストボックス「txt1」を、コントロールの変更でラベルにします。
名前を「lab1」に変更、背景スタイルを「普通」に変更します。
(文字は表示しないのでフォント名は何でもいいです。そのままでも。MS Pゴシックに変更しましたけど)
VBAの記述を以下にします。
Private Sub Form_Current()
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub lab1_Click()
Me.txt検索.SetFocus
End Sub
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub lab1_Click()
Me.txt検索.SetFocus
End Sub
動き的には、こんな感じかなぁ〜っていう位でしょうか。
動きとして「F5」「F6」の大きな違いは、
レコード移動する時、「txt検索」があるべきところをクリックした場合・・・・
・「F5」ではレコード移動できる
・「F6」ではレコード移動できない
単独のラベルをクリックしてもフォーカス移動しない
何かにくっ付いているラベルは、クリックすることでくっ付いている先にフォーカスが移動する
「F5」で「txt検索」を覆っているのはテキストボックス本体・・・フォーカス移動が発生する
の差になるのだと思います。
今回作ってきたフォームのサンプルと、回答内容は大きく違ってないような・・・
(私が両方やっているので、思い込みなのかも・・・)
気をつけていこう。
今回の収穫
2007 で Form_Undo を使うものを 2000 形式で保存。
これを Win2k + Access 2000 で動かすと Form_Undo は動かない。
でも、この mdb を 2007 で動かすと、Form_Undo は動く。
Win2k + Access 2000 で新規に作って、Form_Undo を組み込んでおくと、2007 で動くのかなぁ
後でやってみよう。
おまけ
新規登録中、次の「受付ID」が編集中の「受付ID」と同じ数値になっている件を改善したものになります。
フォームのサンプルはありません。(VBA記述のみ)そこそこ動くと思います。
オートナンバ「an」を使った時
Private Sub Form_Current()
Me.txt0 = Me.an
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
Private Sub Form_Timer()
Me.TimerInterval = 0
Me.txt0 = Me.an
Me.受付ID.DefaultValue = Me.受付ID + 1
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.TimerInterval = 10
End Sub
Private Sub Form_Undo(Cancel As Integer)
If (Me.NewRecord) Then Me.txt0 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Me.txt0 = Me.an
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (Me.NewRecord) Then Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
Private Sub Form_Timer()
Me.TimerInterval = 0
Me.txt0 = Me.an
Me.受付ID.DefaultValue = Me.受付ID + 1
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.TimerInterval = 10
End Sub
Private Sub Form_Undo(Cancel As Integer)
If (Me.NewRecord) Then Me.txt0 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
オートナンバ「an」を使わずに、主キー「受付ID」を使った時
Private Sub Form_Current()
Me.txt0 = Me.受付ID
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
Me.txt0 = Me.受付ID
Me.受付ID.DefaultValue = Me.受付ID + 1
End Sub
Private Sub Form_Undo(Cancel As Integer)
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Me.txt0 = Me.受付ID
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
Private Sub txt検索_AfterUpdate()
Me.コンボ前.SetFocus
End Sub
Private Sub コンボ前_Enter()
Me.コンボ前.Requery
End Sub
Private Sub コンボ前_GotFocus()
Me.コンボ前.Dropdown
End Sub
Private Sub txt1_Enter()
Me.txt検索.SetFocus
End Sub
Private Sub Form_BeforeInsert(Cancel As Integer)
Me.受付ID = Nz(DMax("受付ID", "T受付"), 0) + 1
Me.txt0 = Me.受付ID
Me.受付ID.DefaultValue = Me.受付ID + 1
End Sub
Private Sub Form_Undo(Cancel As Integer)
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
また、「txt1」のコントロールソース内の [an] を [受付ID] に変更します
(動いているように見えるけど、詳しくは追っていないので無理/無駄部分があるかも・・・・)
Form_Undo の際、「an」を使用していた時には
If (Me.NewRecord) Then Me.txt0 = Null
と txt0 に Null を設定していましたが、今回の「受付ID」では既定値を設定しているので、新規行には値が振られている状態になっているようです。
なので、値を戻す処理は不要そうです。
Private Sub Form_Current()
Me.txt0 = Me.受付ID
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
黄色い部分は、入れ替えた方が良いのかもMe.txt0 = Me.受付ID
Me.txt検索 = Null
Me.受付ID.DefaultValue = Nz(DMax("受付ID", "T受付"), 0) + 1
End Sub
余談
絞り込み表示をしたい・・・・絞り込まれたもの以外表示すべきではないと思っています。
できないのなら、そのほかの方法を模索すべきと思います。
もし、Form.Filter で、絞り込まれたもの/でないものを、並び順だけで表示されても・・・・
私はそう思う・・・・っていうだけですけど。
| サンプルは以下 | ||||||||||||
| ||||||||||||
| ※ ファイルは zip 形式 | ||||||||||||
| ※ 2007 以外は、2007 保存時に変換 & 各バージョンで動作確認 & 最適化 |
※ Form_Undo を使っているので、2000 ではうまく動きません。
≪--- 続きを閉じちゃえ
2012/05/16
Category: やってみる
どの方法が良いのだろう
あるQAで、以下の条件があったとして、フォームを作るには・・・
テーブル「Tチーム」
テーブル「T試合」
テーブル「T選手」
テーブル「T出場」
つまり、自チームの選手「T選手」が、どの対戦相手の時に出場していたか・・・・
フォームのイメージとしては、
・「T試合」を登録/修正時に、
・「T選手」の全選手を表示して(100くらい)
チェックボックス形式で入力操作したい。(「T出場」を同時に作りこみたい)
| フィールド | 型 等々 |
|---|---|
| チームID | オートナンバ (主キー) |
| チーム名 | テキスト |
テーブル「T試合」
| フィールド | 型 等々 |
|---|---|
| 試合ID | オートナンバ (主キー) |
| 試合日 | 日付/時刻 |
| 天候 | テキスト |
| 相手チーム | 長整数 「Tチーム」のチームID |
| 試合場所 | テキスト |
テーブル「T選手」
| フィールド | 型 等々 |
|---|---|
| 選手ID | オートナンバ (主キー) |
| 選手名 | テキスト |
| よみ | テキスト |
テーブル「T出場」
| フィールド | 型 等々 |
|---|---|
| an | オートナンバ (主キー) |
| 試合ID | 長整数 「T試合」の試合ID |
| 選手ID | 長整数 「T選手」の選手ID |
つまり、自チームの選手「T選手」が、どの対戦相手の時に出場していたか・・・・
フォームのイメージとしては、
・「T試合」を登録/修正時に、
・「T選手」の全選手を表示して(100くらい)
チェックボックス形式で入力操作したい。(「T出場」を同時に作りこみたい)
ザッと考えてみたフォームは9つ
1)帳票フォームに帳票サブフォーム(F1M/F1S)

2)単票に2つの帳票サブフォーム(F2M/F2S1/F2S2)

3)単票に10個のサブフォーム(F3M/F3S)(あ行、か行・・・毎にサブフォーム)

4)単票に6個のサブフォーム(F4M/F4S)(1つのサブフォームでは20人表示)

5)上記フォームのワークテーブル使用バージョン(F5M/F5S)

6)単票に多数の非連結チェックボックス(F6M)

上記フォームのVBA記述量削減・操作限定バージョン(F6M2)

7)上記フォームの表示変更バージョン(F7M)

8)単票にタブコントロール配置(F8M)(タブページで、あ行、か行・・・)
実際に操作するのは、多数の非連結チェックボックス

9)単票に単票サブフォーム(F9M/F9S) (ワークテーブル使用)

で、6)を回答。
回答からブログの記事にするまで、いろいろ勉強させられました。
2007 で作成して、2003 / 2000 形式に変換して確認しているわけですが・・・
これに手間取っていました。
※ 2000 には、Form_Undo がない
※ サブフォームで組み込んだフォームの Form_Open / Form_Load は、
2000 の場合は連続して実行されない時がある。
特に分ける必要がない場合には、Form_Open 1つに記述した方が無難
※ サブフォームコントロールで、SourceObject にフォームを設定したタイミングで
LinkMasterFields / LinkChildFields が自動設定される時がある。
私が知らなかっただけなのかも・・・・
今回の記事はチョッくら長いです。
続きを読んでみようかな ---≫
テーブル「T選手」内の「よみ」は、必ず設定されているものとして記述していきます。
また、「T試合」を表示しているフォームでは、削除操作がないものとしてします。
なので、フォームでの「削除の許可」は「いいえ」としておきます。
これは、「T試合」のレコードを削除した際、「T出場」のレコードをどういう方法で削除するか・・・
自力で?
使った事ないけど、リレーションシップで「連鎖削除」を設定しておく?
今回、削除については触れないという事で・・・・
1)帳票フォームに帳票サブフォーム(F1M/F1S)

メインのフォーム「F1M」は「T試合」を元にフォームウィザードで「試合ID」以外を表示するように。
「試合ID」はオートナンバなので、表示はいらないでしょう・・・という事で。
サブフォームがフォームのイベントを取得できるように、プロパティ「コード保持」を「はい」に。
サブフォームになる「F1S」は、フォームデザインで作っていきます。
レコードソースを SELECT T選手.選手ID, T選手.選手名, T選手.よみ FROM T選手 ORDER BY T選手.よみ;
ヘッダ部分に非表示のテキストボックス「txt1」
これの用途は、親で選ばれた試合に出場した選手の「選手ID」が「,」(カンマ)区切りで・・・
詳細部分には、
・チェックボックス「ck」
このチェックボックスでは、コントロールソースを使用しますがVBAで設定するので、そのままに。
ラベル部分はいらないので削除
タブストップは「いいえ」
・そのチェックボックスを覆う形で(前面側に)、コマンドボタン「btn1」
背景スタイルを透明
このボタンがクリックされた=チェックボックスの チェック 切り換えと解釈
タブ移動順を先頭に
・選手名用テキストボックス「選手名」
ラベル部分はいらないので削除
コントロールソースを 選手名 に設定
念のため、タブストップを「いいえ」
このテキストボックスは、チェックボックスにくっ付いているラベルの様な動きにします。
チェックボックスにチェックが入っていたら、背景色を変更します。
この変更に、条件付き書式を使いますが、VBAで設定するので、そのままに。
フォームは帳票フォームにして、
・レコードセレクタ「いいえ」
・移動ボタン「いいえ」
・スクロール「垂直のみ」
・追加/削除/更新の許可は、すべて「いいえ」
フォームのデザインはこれで終わりです。
見栄え上のことですが、チェックボックスが多数並ぶのでチェックだけでは分かりづらい???
ということで、チェックしたら選手名のところの背景色を変更しましょう・・・という事にしました。
VBAを記述していくわけですが、処理概要をまず。
親で「試合ID」が変化したら「T出場」から、その試合に出ていた「選手ID」を求めます。
この【親で「試合ID」が変化したら】は、親のレコード移動時を検知するようにします。
その求まった「選手ID」を「,」(カンマ)区切りで羅列したものを「txt1」に設定します。
チェックボックスでは、その設定された「txt1」内に、自レコードの「選手ID」があるか判別表示します。
チェックボックスのコントロールソース記述で
=IIF(InStr([txt1],"," & [選手ID] & ",")>0,True,False)
これと同じような判別で、テキストボックス「選手名」の背景色を条件付き書式で変更します。
式が、InStr([txt1],"," & [選手ID] & ",")>0 で、背景色を RGB(255, 240, 240)
チェックボックスがクリック(実際にはコマンドボタン「btn1」のクリック)されたら、
「txt1」に自レコードの「選手ID」があれば、その部分を削除、なければ新たに追加・・・
本来、文字列の操作ですが、面倒だったので Dictionary で選手IDを管理することに。
この変更のタイミングで、「T出場」のレコードを操作することに。
また、テキストボックス「選手名」にフォーカス移動(実際にはマウスのクリック)された場合は、
フォーカスを「btn1」に移し、「btn1」がクリックされた時の処理を・・・・
これにより、テキストボックス「選手名」はチェックボックス「ck」のラベル的動きに・・・・???
記述したのは以下
で、出来上がった「F1S」を「F1M」のフッタ部分にドラッグ&ドロップします。
この時、「F1M」は単票に変更されますが、再度帳票に変更して終了。
(このメイン/サブ構成は自己責任で・・・・)
この処理で、親の更新後処理で表示し直す必要があるのでは・・・・・については、
更新後処理で「試合ID」が変化するケースは、新規登録された場合だけになるので、
元々その試合には出場している選手は存在しないので、取得し直すことは不要になります。
(親が新規行にレコード移動した際に Dictionary はクリアされているので)
(以降のフォームでは、親の更新後処理を検知するものもあります)
その他の部分で、疑問を持たれる方がいるかも・・・・・ん、どの部分??
選手IDを Dictionary で管理しているのなら、チェックボックス、条件付き書式のところで
Dictionary にあるか・・・チェックすれば、非表示の「txt1」は不要なのでは・・・・・
これ用に上記を変更するとしたら以下の様になります。(変更する部分のみ)
Me.Recalc しないと、初期表示のまま何も変化がありません。
コントロールソースや条件付き書式の式で指定したものに変更がないと、変化しないようです。
なので、その指定した部分([txt1]が変更された・・・)を使うようにしてました。
でも、チラつきについては同じなのかなぁ・・・・雰囲気は違うみたいなんだけどなぁ・・・・
あと、記述に不満というか・・・・ Sub の関数を呼ぶ時、私は Call を付けて記述しています。
以下の部分には Call が付いているものと思ってください。(記述漏れでした)
2)単票に2つの帳票サブフォーム(F2M/F2S1/F2S2)

このフォームでは、帳票+帳票のメイン/サブフォーム構成ではなく、一般的に使える構成になると思います。
大元の非連結の単票フォーム(F2M)に、
サブフォームコントロール「FSUB1」として「T試合」用の帳票サブフォーム(F2S1)、
サブフォームコントロール「FSUB2」として「T選手」用の帳票サブフォーム(F2S2)を配置します。
フォーム「F2S2」は、前のフォーム「F1S」をコピーして中の記述をチョッとだけいじります。
(フォーム「F2S1」と連動するように)
フォーム「F2S1」は新しく「T試合」を元にフォームウィザードを使って、まず、単票として作成し、
(フィールドを縦に並べたかっただけ)それを帳票フォームに変更します。
削除は処理対象にしないので、削除の許可は「いいえ」に、「コード保持」は「はい」に。
親フォーム「F2M」は Form_Load 時、サブフォーム「F2S1」の情報を「F2S2」へ通知するだけ。
タイミング的には、親が Load された時点で、サブフォーム側の初期処理は終わっているようなので・・・
(「F2S2」単独で「F2S1」情報を取ろうとした時、「F2S1」が居なかったりする???)
「F2S2」では親からもらった「F2S1」情報を元に、「F2S1」のレコード移動時イベントを検知するように・・・
親では、「F2S2」の設定が終わったら、「F2S1」を Requery してレコード移動時イベントを作ってやる。
親フォーム「F2M」に記述したのは
フォーム「F2S1」にはVBA記述なし。単独で起動されても問題ないかなぁ・・・
フォーム「F2S2」は「F1S」をコピーしたものだったので、変更した箇所を以下に
(「F1S」の Form_Open 部分を 2つの関数に分けただけです)
サブフォームとして組み込まれていなければ、起動しない様に
親からフォーム「F2S1」(「T試合」を表示している帳票フォーム)のフォームを教えてもらう。
で、レコード移動時を検知できるように・・・・
3)単票に10個のサブフォーム(F3M/F3S)(あ行、か行・・・毎にサブフォーム)

このフォームでは、あ行、か行・・・別で表示しましょう・・・・というもの。
フォーム「F1M」を「F3M」としてコピーし、フッタ部分のサブフォームコントロールを削除。
フッタ部分の領域をなしにして、詳細部分を広げます。
その詳細部分にサブフォームコントロール「FSUB0」〜「FSUB9」の10個配置します。
このサブフォームコントロールに、前作ったフォーム「F1S」を入れて、行別表示に仕立て上げます。
処理を少し変更するので、「F1S」を「F3S」としてコピーします。
サブフォームコントロールのソースオブジェクトは空欄にしておきます。
これは1つのフォーム「F3S」を使い回しする為の1つの方法だと思っています。
FSUB0 では「あ行」を表示して・・・・ FSUB1 では「か行」を表示して・・・・って指示したかったので
この指示に、親フォームのタグを連絡用に使い、ソースオブジェクトに「F3S」を設定
「F3S」では、情報を親のタグから入手し、指示に従った表示をするように。
サブフォームコントロールは、10人分を表示できる高さにデザインで設定しておきます。
親フォーム「F3M」に記述したのは以下
子フォーム「F3S」として「F1S」から変更した部分は以下
でも、2000 / 2003 では、常に縦のスクロールバーが表示される。
サブフォームコントロール側では10人分表示できるように設定しているので、
共通の処理として、表示対象が10人以下ならスクロールバーを表示しない様に・・・・
表示を見てみましたが、何のための空白部分か・・・何か違和感ありありです。
あ行、か行 の表示はやめて、20人単位で表示しましょうか・・・・っていうのが次(F4M/F4S)に
4)単票に6個のサブフォーム(F4M/F4S)(1つのサブフォームでは20人表示)

このフォームでは、20人単位でサブフォームとして表示しましょう・・・というもの
基本的な考え方/処理は前の(F3M/F3S)と同じなので、「F3M」を「F4M」、「F3S」を「F4S」にコピー
前のフォームでは、 よみ Like '%1' の %1 にあたる文字列を指定していましたが、
今回のフォームでは 選手ID IN (%1) の %1 にあたる文字列を指定するようにします。
フォーム「F4M」で、サブフォームコントロールを「FSUB1」〜「FSUB6」の6つに
選手20人を表示できるようにしておきます。
処理の概要としては、選手を「よみ」順で20人単位でサブフォームを設定していきますが、
最後の「FSUB6」だけは20人を超えても作り続けるようにします。
その時に、もしかして・・・人数が多すぎて・・・・・タグの文字数制限に引っ掛かるようになるかも・・・
(その時にはその時で別の手段を使うように変更しますか・・・・)
親フォーム「F4M」に記述したのは以下
子フォーム「F4S」で「F3S」から変更した部分は以下
ここまでのフォームでは、帳票フォームで非連結のチェックボックスを使ってきました。
この方法からチョッと離れて考えてみよう・・・・ということで、以降、違う方法になっていきます。
5)上記フォームのワークテーブル使用バージョン(F5M/F5S)

前回のフォーム構成を使って、サブフォーム側のレコードソースをワークテーブルにしてみます。
テーブル「Tワーク出場5」として
を使ってみます。
1試合分のデータだけを扱うようにします。
試合ID は不要?なのかもしれません。
データを「Tワーク出場5」「T出場」間でやり取りしやすかったので・・・
(これが後で問題に・・・・・・・・・後述)
フォーム「F4M」を「F5M」としてコピーします。
サブフォームの個数、20人までの表示は前と変わりません。
変わるのは、ワークテーブル「Tワーク出場5」から本テーブル「T出場」へ戻すタイミングを作ること・・・・
更新用のボタン「btn1」と、
戻す(本テーブル「T出場」からワークテーブル「Tワーク出場5」へ再展開)用のボタン「btn2」を配置
サブフォーム用のフォームは「F4S」を「F5S」としてコピーします。
レコードソースは表示する際に書き換えるものの、
標準で「Tワーク出場5」と「T選手」とで選手名を表示できるようにしたものを設定しておきます。
ヘッダ部にあった「txt1」、詳細部分でチェックボックスを覆っていた「btn1」は削除します。
チェックボックス「ck」のコントロールソースに「ck」を設定し、タブストップを「はい」に。
チェックボックスをクリックして更新するので、フォームの「更新の許可」だけ「はい」に変更。
処理の概要を。
メインフォーム「F5M」では、Load 時、「Tワーク出場5」に全選手の選手IDを作成します。
「T選手」の「よみ」順で20人ずつサブフォームに割り当てていきます。
(「FSUB1」〜順にソースオブジェクトにサブフォームを割り当てていきます)
レコード移動時に、「Tワーク出場5」の 試合ID と ck を更新します。
更新後処理では、新規で試合ID が割り当てられることがあるので、レコード移動時の処理をもう一度。
更新ボタン「btn1」がクリックされたら、「Tワーク出場5」を元にその試合に該当するものを作り直します。
戻すボタン「btn2」がクリックされたら、その試合にあったデータを「Tワーク出場5」に作り直します。
ここで、新規登録(試合IDがNull)の場合、「Tワーク出場5」の試合ID は 0 に設定。
サブフォーム側では、「Tワーク出場5」に対して「ck」を設定していきます。
設定する際、親に配置した更新ボタン「btn1」の文字色を変更するようにします。
親フォーム「F5M」に記述したのは以下
子フォーム「F5S」に記述したのは以下
上記で動いているようですが、この1つ前の記述では、
新規の試合を入力しようとした場合、サブフォーム部分に何も表示されない現象が発生。
1つ前の記述と言うと、サブフォームを設定する際の記述になりますが
(以下の黄色い部分の記述がありませんでした)
Form_Open が呼ばれる前に LinkMasterFields / LinkChildFields が "試合ID" に自動設定。
Form_Open の初めの処理で、LinkMasterFields / LinkChildFields をクリア(空文字設定)しても、
その後の、RecordSource 設定直後にまた、LinkMasterFields / LinkChildFields が "試合ID" に。
リレーションシップは設定していないし・・・・
ま、確かに、親の「試合ID」は主キーに設定していましたが・・・・
あ、ルックアップは設定していたかな・・・
Access さんのどこかのオプション設定で回避できるのかもしれないけど、その設定を強要する???
あれ、や・・・、名前の自動修正?? これってそのDBを限定できたっっっっっかも・・・(未確認)
でも、親側で SourceObject 設定後に、LinkMasterFields / LinkChildFields をクリアすれば
動くようなので、この記述を採用しました。
主キーと同じフィールド名があったから??・・・・次回から気をつけよう。
6)単票に多数の非連結チェックボックス(F6M)

これが回答に使った、そのものになります。
元にするフォーム「F6M_BASE」を作っておきます。(「T試合」を元にした単票フォーム)
「F5M」をコピーしてサブフォーム部分を削除、VBA記述を全削除したものにしておきます。
また、
・「登録」ボタンが押された時に登録する
・・・・ということが抜けていたので、それに対応するように、登録ボタン「btn1」を配置。
チェックボックスを作成していきますが、必要人数以上のチェックボックスを不可視で作っておいて、
表示する時に必要な分だけ、可視(Visible=True)に変更します。
チェックボックス名の命名規則として、「"ck" & 連番」とします。
(100くらい、ということだったので "ck1" 〜 "ck120" )
手作業で多数のチェックボックスを作成して名前を変更・・・
これ面倒なので、VBAで作成します。
1列20個で、計120個(6列分)のチェックボックスと、それにひっつくラベル
このラベル部分に選手名を表示するようにします。
標準モジュール「M6Make」を用意しました。
チェックボックスを追加する対象のフォーム「F6M_BASE」を、一旦「F6M_BASE_」にコピーして、
作り終わったら「F6M」に名称を変更 という内容になってます。
フォーム「F6M」にVBAを記述していきますが、概要をまず。
・チェックボックスクリック時
「T試合」の新規レコードであったら、見た目チェックを無効とするようにします
チェックボックスは非連結なので、変更してもレコード移動等のタイミングは分かりません。
なので、編集状態にしておくことで、レコード移動のタイミングを検知することが出来るようになります。
そこで、「天候」を同じ値で設定して編集状態に・・・
チェックボックスの変更状態を更新します。
・フォームが起動された時
「T選手」から選手情報をチェックボックスに割り当てていきます。
割り当てたチェックボックスのラベルに選手名を、+可視に
チェックボックスの Tag に、選手ID を登録しておきます。
これにより、クリックされた時、簡単に選手を特定できるようになります。
・レコード移動時
レコード移動によって「試合ID」が変化するので、その時の「試合ID」で
「T出場」にある選手を抽出し、チェックボックスを変更していきます。
・更新前
登録ボタン「btn1」クリックでのみ更新/登録できるようにするので、
意図しないタイミングでの更新はできない様に、常に Cancel = True とします
・取り消し時(2000では動かない)
チェックボックスを初期状態に戻し、レコード移動時に取得した選手情報でチェックボックスを再設定
・登録ボタン
「T試合」への登録チェックが必要であればこのタイミングで
一度、更新前処理を無効としてレコードを登録します。
「T出場」に対して、チェックボックスの状態を反映します。
dicOld は、レコード移動時に取得したその試合での選手情報で、
チェックボックスでの変更状況は dicNew にて。
レコード移動直後は、dicOld dicNew は同じ内容となっています。
チェックボックスの操作により、dicOld dicNew の差分で DELETE したり、INSERT したりします。
登録操作後、dicOld dicNew の内容を同じにするため、レコード移動時の処理を。
記述したVBAは以下
上記フォームのVBA記述量削減・操作限定バージョン(F6M2)

このフォームでは、「F6M」の処理/操作を限定して、極力VBA記述を少なくしたものになります。
限定したのは、
・「T試合」の新規登録時のみ
・選手を追加したらフォームの手直しが必要
・この用途以外にチェックボックスがない事
チェックボックスは非連結で自由に配置しますが、以下の設定が必要です。
・チェックボックスの「タグ」には、「選手ID」を設定しておく
・チェックボックスのラベルには、その選手の「選手名」を表示するように
また、チェックボックス名は何でも構わない、個数も限定しない。
で、これを確認するフォームを作成するのですが、手修正も面倒なので・・・・
標準モジュール「M6M62」を用意しました。
(フォーム「F6M」を元に、上記条件に修正していくものです)
で、割り当てられていない不要なチェックボックスを削除するなりします。
(3列削除しましたが、配置はそのままにしたのが現状の「F6M2」です)
フォーム「F6M2」に記述したのは以下
新規登録時に、まずチェックボックスのチェックを外しておきます。
「登録」ボタンがクリックされた時に、チェックがあるものを探し出し、レコードを追加します。
チェック自体は「T試合」のものを入力していない状態でも、自由に ON / OFF できます。
なお、VBA記述を少なくするために、ラベル部分の背景色は変更していません。
登録ボタン「btn1」の最後で、Me.Requery しているのは、
新規登録を連続して行った場合、前のレコードに戻れたような気がして・・・・
もし、戻ったとした場合、チェックボックスの表示を処理できない・・・・
なので、Me.Requery して、新規レコードのみにするように・・・・
7)上記フォームの表示変更バージョン(F7M)

このフォームでは、「よみ」順に割り当てていく際に、あ行、か行・・・・行が変わったら1つ空けましょう。
フォーム「F6M」を「F7M」でコピーし、以下の記述を追加/修正します。
また、チェックボックスをクリアする以下記述2か所を変更します。
これは、チェックボックスは連続して Visible = True になっているわけではないので・・・・
(フォーム「F6M」でも、下側記述で OK です)
8)単票にタブコントロール配置(F8M)(タブページで、あ行、か行・・・)
実際に操作するのは、多数の非連結チェックボックス

このフォームでは、多数のチェックボックスをタブコントロールを使って、
あ行、か行・・・・をタブページで分けて表示しましょう・・・・というものです。
タブページには、非表示のチェックボックスを各30個配置しておきます。
(表示する時に使う分だけ可視に変更するのは、今まで通りです)
タブページ0には、標題「あ」、チェックボックス「ck1」〜「ck30」
タブページ1には、標題「か」、チェックボックス「ck101」〜「ck130」
・・・・
タブページ9には、標題「わ」、チェックボックス「ck901」〜「ck930」
という規則を設けます。
また、今回はチェックボックスは横から採番しましょう・・・・
フォーム「F8M_BASE」(フォーム「F6M_BASE」をコピー)を用意し、
標準モジュール「M8Make」に用意した M8MakeProc を実行すると雰囲気フォームが出来上がります。
それをきれいに配置し直して保存します。
この実行は結構時間がかかります。(初めは、おかしくなったのかと思い止めたりしてました)
タブコントロールの位置決めは難しいですね。(Top が今一つ決まりません)
フォーム「F8M」用のVBAを転記します(同じ標準モジュール内に用意済み)
フォーム「F8M」のVBA記述は「F7M」と大半同じで、以下 Form_Load の黄色部分が異なるだけです。
2000 のタブコントロール表示は、結構暗いですね。
9)単票に単票サブフォーム(F9M/F9S) (ワークテーブル使用)

VBA は記述したくない・・・ということだったので、テーブル「T出場」を以下の様にすると
チェックボックスを自由に配置できて、操作は楽に(VBA記述なし)なる・・・と言ったものの
これ以降に控えている操作等に支障が出てくると思います。
何故か・・・
「ck1」はAさん用、「ck2」はBさん用 等、テーブル内にない取り決めが必要になります。
じゃ、これに近いテーブルをワークテーブル「Tワーク出場9」として使いましょう。
(・・・って VBA 必要になるんじゃ・・・・・横に置いとくとして)
【追記 5/15】
上記テーブルのフィールド「an」はありません。
「ck1」は XX さん用という取り決めが必要なので、テーブル「T選手」に情報を持ちましょう・・・・
テーブル「T選手」を「T選手9」にコピーして、フィールドを追加します。
標準モジュール「S9Make」にテーブル、連結した単票フォームを作成するVBAを用意しました。
ここで出来上がった「F9S」に以下を記述します。
メインとなるフォーム「F9M」はフォーム「F6M_BASE」をコピーしたもの。
そこに、この「F9S」をドラッグ&ドロップしてサブフォームとして組み込み、
リンク親/子フィールドは削除(空欄に)します。
どの選手が、どの「ck」を使うか、テーブル「T選手9」の「ckno」を変更しない限り、
その選手はその番号を使う事になるので、連結されたチェックボックス/テキストボックスを
(ペアにして)自由に移動配置できます。
なお、このワークテーブル「Tワーク出場9」のレコードは1件だけ。
それを UPDATE で使い回しします。
(処理対象でない試合のものに対して、レコードをいじりたくないので)
連結にしてみたものの、何か遠まわりしているような・・・・していないような・・・
でも、ガ〜〜ってチェックして、それを取り消すのは楽かな。
メイン/サブの構成になったので、「登録/修正」用のボタンはなし。
今までのフォームと表示が異なっていますが、気がつかれたでしょうか。
今までのフォームの表示順は、「よみ」順になっていましたが、
このフォームでは選手の登録順(ckno の順)になっています。
(配置を移動していなかったので、そう見えたという事だけですけど)
そうそう
このフォームだったと思うけど、Form_Open / Form_Load が連続して呼ばれない・・・
当初 Form_Open / Form_Load は以下のような感じ
サブフォームとして起動されていなかったら表示しない・・・・
でも、この後の
★★★1 部分だったかで「カレントレコードがない」エラー(だったか)
★★★2 部分だったかで frm がどうたらだったか・・・
で、2000 の方でチョッといじっていると、動いてみたり・・・
同じVBA記述構成の他のフォームは動いていたり・・・・
なので、サブフォームになる Form_Open / Form_Load は Open 1つに(全部書き直し)
【追記 5/15】
ここで示した箇所の例は嘘ですね。
でも、どこかのフォームでなっていたんですよ・・・・
ま、私の中での出来事と言う事で・・・・・
注意事項)
・メイン/サブの構成では、サブ側でメインのイベントを受け取る設定をするので
メイン側フォームのプロパティ「コード保持」は「はい」としておきます。
(メイン側に VBA 記述がなくても)
(F2M/F2S1/F2S2 では、「T試合」を表示している F2S1 が対象)
・フォーム「F6M」以降のフォームで、選手を割付ける際、割り付けきらなかったとか・・・・
以降の処理でエラーになる可能性・・・大・大・大
どの方法が良いのだろう・・・・
≪--- 続きを閉じちゃえ
テーブル「T選手」内の「よみ」は、必ず設定されているものとして記述していきます。
また、「T試合」を表示しているフォームでは、削除操作がないものとしてします。
なので、フォームでの「削除の許可」は「いいえ」としておきます。
これは、「T試合」のレコードを削除した際、「T出場」のレコードをどういう方法で削除するか・・・
自力で?
使った事ないけど、リレーションシップで「連鎖削除」を設定しておく?
今回、削除については触れないという事で・・・・
1)帳票フォームに帳票サブフォーム(F1M/F1S)

メインのフォーム「F1M」は「T試合」を元にフォームウィザードで「試合ID」以外を表示するように。
「試合ID」はオートナンバなので、表示はいらないでしょう・・・という事で。
サブフォームがフォームのイベントを取得できるように、プロパティ「コード保持」を「はい」に。
サブフォームになる「F1S」は、フォームデザインで作っていきます。
レコードソースを SELECT T選手.選手ID, T選手.選手名, T選手.よみ FROM T選手 ORDER BY T選手.よみ;
ヘッダ部分に非表示のテキストボックス「txt1」
これの用途は、親で選ばれた試合に出場した選手の「選手ID」が「,」(カンマ)区切りで・・・
詳細部分には、
・チェックボックス「ck」
このチェックボックスでは、コントロールソースを使用しますがVBAで設定するので、そのままに。
ラベル部分はいらないので削除
タブストップは「いいえ」
・そのチェックボックスを覆う形で(前面側に)、コマンドボタン「btn1」
背景スタイルを透明
このボタンがクリックされた=チェックボックスの チェック 切り換えと解釈
タブ移動順を先頭に
・選手名用テキストボックス「選手名」
ラベル部分はいらないので削除
コントロールソースを 選手名 に設定
念のため、タブストップを「いいえ」
このテキストボックスは、チェックボックスにくっ付いているラベルの様な動きにします。
チェックボックスにチェックが入っていたら、背景色を変更します。
この変更に、条件付き書式を使いますが、VBAで設定するので、そのままに。
フォームは帳票フォームにして、
・レコードセレクタ「いいえ」
・移動ボタン「いいえ」
・スクロール「垂直のみ」
・追加/削除/更新の許可は、すべて「いいえ」
フォームのデザインはこれで終わりです。
見栄え上のことですが、チェックボックスが多数並ぶのでチェックだけでは分かりづらい???
ということで、チェックしたら選手名のところの背景色を変更しましょう・・・という事にしました。
VBAを記述していくわけですが、処理概要をまず。
親で「試合ID」が変化したら「T出場」から、その試合に出ていた「選手ID」を求めます。
この【親で「試合ID」が変化したら】は、親のレコード移動時を検知するようにします。
その求まった「選手ID」を「,」(カンマ)区切りで羅列したものを「txt1」に設定します。
チェックボックスでは、その設定された「txt1」内に、自レコードの「選手ID」があるか判別表示します。
チェックボックスのコントロールソース記述で
=IIF(InStr([txt1],"," & [選手ID] & ",")>0,True,False)
これと同じような判別で、テキストボックス「選手名」の背景色を条件付き書式で変更します。
式が、InStr([txt1],"," & [選手ID] & ",")>0 で、背景色を RGB(255, 240, 240)
チェックボックスがクリック(実際にはコマンドボタン「btn1」のクリック)されたら、
「txt1」に自レコードの「選手ID」があれば、その部分を削除、なければ新たに追加・・・
本来、文字列の操作ですが、面倒だったので Dictionary で選手IDを管理することに。
この変更のタイミングで、「T出場」のレコードを操作することに。
また、テキストボックス「選手名」にフォーカス移動(実際にはマウスのクリック)された場合は、
フォーカスを「btn1」に移し、「btn1」がクリックされた時の処理を・・・・
これにより、テキストボックス「選手名」はチェックボックス「ck」のラベル的動きに・・・・???
記述したのは以下
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Dim WithEvents frm As Form
Dim dic As Object
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
frm.OnCurrent = EVENT_PROCEDURE
Me.ck.ControlSource = _
"=IIF(InStr([txt1],"","" & [選手ID] & "","")>0,True,False)"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "InStr([txt1],"","" & [選手ID] & "","")>0")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub Txt1ValueSet()
If (dic.Count > 0) Then
Me.txt1 = "," & Join(dic.Keys, ",") & ","
Else
Me.txt1 = ",,"
End If
End Sub
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset
dic.RemoveAll
If (Not IsNull(frm.試合ID)) Then
sSql = "SELECT * FROM T出場 WHERE 試合ID = " & frm.試合ID & ";"
Set rs = CurrentDb.OpenRecordset(sSql)
While (Not rs.EOF)
dic.Item(rs("選手ID").Value) = Null
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
End If
Call Txt1ValueSet
End Sub
Private Sub btn1_Click()
Dim sSql As String
Dim i As Long
If (IsNull(frm.試合ID)) Then Exit Sub
i = Me.選手ID
If (dic.Exists(i)) Then
sSql = "DELETE * FROM T出場 WHERE 試合ID = " _
& frm.試合ID & " AND 選手ID = " & i & ";"
CurrentDb.Execute sSql
dic.Remove i
Else
sSql = "INSERT INTO T出場(試合ID, 選手ID) VALUES (" _
& frm.試合ID & "," & i & ");"
CurrentDb.Execute sSql
dic.Item(i) = Null
End If
Call Txt1ValueSet
End Sub
Private Sub 選手名_Enter()
Me.btn1.SetFocus
btn1_Click
End Sub
Private Sub Form_Close()
Set frm = Nothing
Set dic = Nothing
End Sub
Dim WithEvents frm As Form
Dim dic As Object
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
frm.OnCurrent = EVENT_PROCEDURE
Me.ck.ControlSource = _
"=IIF(InStr([txt1],"","" & [選手ID] & "","")>0,True,False)"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "InStr([txt1],"","" & [選手ID] & "","")>0")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub Txt1ValueSet()
If (dic.Count > 0) Then
Me.txt1 = "," & Join(dic.Keys, ",") & ","
Else
Me.txt1 = ",,"
End If
End Sub
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset
dic.RemoveAll
If (Not IsNull(frm.試合ID)) Then
sSql = "SELECT * FROM T出場 WHERE 試合ID = " & frm.試合ID & ";"
Set rs = CurrentDb.OpenRecordset(sSql)
While (Not rs.EOF)
dic.Item(rs("選手ID").Value) = Null
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
End If
Call Txt1ValueSet
End Sub
Private Sub btn1_Click()
Dim sSql As String
Dim i As Long
If (IsNull(frm.試合ID)) Then Exit Sub
i = Me.選手ID
If (dic.Exists(i)) Then
sSql = "DELETE * FROM T出場 WHERE 試合ID = " _
& frm.試合ID & " AND 選手ID = " & i & ";"
CurrentDb.Execute sSql
dic.Remove i
Else
sSql = "INSERT INTO T出場(試合ID, 選手ID) VALUES (" _
& frm.試合ID & "," & i & ");"
CurrentDb.Execute sSql
dic.Item(i) = Null
End If
Call Txt1ValueSet
End Sub
Private Sub 選手名_Enter()
Me.btn1.SetFocus
btn1_Click
End Sub
Private Sub Form_Close()
Set frm = Nothing
Set dic = Nothing
End Sub
で、出来上がった「F1S」を「F1M」のフッタ部分にドラッグ&ドロップします。
この時、「F1M」は単票に変更されますが、再度帳票に変更して終了。
(このメイン/サブ構成は自己責任で・・・・)
この処理で、親の更新後処理で表示し直す必要があるのでは・・・・・については、
更新後処理で「試合ID」が変化するケースは、新規登録された場合だけになるので、
元々その試合には出場している選手は存在しないので、取得し直すことは不要になります。
(親が新規行にレコード移動した際に Dictionary はクリアされているので)
(以降のフォームでは、親の更新後処理を検知するものもあります)
その他の部分で、疑問を持たれる方がいるかも・・・・・ん、どの部分??
選手IDを Dictionary で管理しているのなら、チェックボックス、条件付き書式のところで
Dictionary にあるか・・・チェックすれば、非表示の「txt1」は不要なのでは・・・・・
これ用に上記を変更するとしたら以下の様になります。(変更する部分のみ)
Private Function DicChk(v As Variant) As Boolean
DicChk = dic.Exists(v.Value)
End Function
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
frm.OnCurrent = EVENT_PROCEDURE
Me.ck.ControlSource = "=DicChk([選手ID])"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "DicChk([選手ID])")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub Txt1ValueSet()
Me.Recalc
End Sub
これでも動作するんですが、変更したら毎回 Me.Recalc が必要になって、チラつきが大きくなるんですね。DicChk = dic.Exists(v.Value)
End Function
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
frm.OnCurrent = EVENT_PROCEDURE
Me.ck.ControlSource = "=DicChk([選手ID])"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "DicChk([選手ID])")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub Txt1ValueSet()
Me.Recalc
End Sub
Me.Recalc しないと、初期表示のまま何も変化がありません。
コントロールソースや条件付き書式の式で指定したものに変更がないと、変化しないようです。
なので、その指定した部分([txt1]が変更された・・・)を使うようにしてました。
でも、チラつきについては同じなのかなぁ・・・・雰囲気は違うみたいなんだけどなぁ・・・・
Private Sub Txt1ValueSet()
Me.Painting = False
Me.Recalc
Me.Painting = True
End Sub
と、Painting で挟んだらチョッと改善されたけど・・・・Me.Painting = False
Me.Recalc
Me.Painting = True
End Sub
あと、記述に不満というか・・・・ Sub の関数を呼ぶ時、私は Call を付けて記述しています。
以下の部分には Call が付いているものと思ってください。(記述漏れでした)
Private Sub 選手名_Enter()
Me.btn1.SetFocus
Call btn1_Click
End Sub
Me.btn1.SetFocus
Call btn1_Click
End Sub
2)単票に2つの帳票サブフォーム(F2M/F2S1/F2S2)

このフォームでは、帳票+帳票のメイン/サブフォーム構成ではなく、一般的に使える構成になると思います。
大元の非連結の単票フォーム(F2M)に、
サブフォームコントロール「FSUB1」として「T試合」用の帳票サブフォーム(F2S1)、
サブフォームコントロール「FSUB2」として「T選手」用の帳票サブフォーム(F2S2)を配置します。
フォーム「F2S2」は、前のフォーム「F1S」をコピーして中の記述をチョッとだけいじります。
(フォーム「F2S1」と連動するように)
フォーム「F2S1」は新しく「T試合」を元にフォームウィザードを使って、まず、単票として作成し、
(フィールドを縦に並べたかっただけ)それを帳票フォームに変更します。
削除は処理対象にしないので、削除の許可は「いいえ」に、「コード保持」は「はい」に。
親フォーム「F2M」は Form_Load 時、サブフォーム「F2S1」の情報を「F2S2」へ通知するだけ。
タイミング的には、親が Load された時点で、サブフォーム側の初期処理は終わっているようなので・・・
(「F2S2」単独で「F2S1」情報を取ろうとした時、「F2S1」が居なかったりする???)
「F2S2」では親からもらった「F2S1」情報を元に、「F2S1」のレコード移動時イベントを検知するように・・・
親では、「F2S2」の設定が終わったら、「F2S1」を Requery してレコード移動時イベントを作ってやる。
親フォーム「F2M」に記述したのは
Private Sub Form_Load()
With Me.FSUB1
Call Me.FSUB2.Form.frm_Set(.Form)
.Form.Requery
End With
End Sub
With Me.FSUB1
Call Me.FSUB2.Form.frm_Set(.Form)
.Form.Requery
End With
End Sub
フォーム「F2S1」にはVBA記述なし。単独で起動されても問題ないかなぁ・・・
フォーム「F2S2」は「F1S」をコピーしたものだったので、変更した箇所を以下に
(「F1S」の Form_Open 部分を 2つの関数に分けただけです)
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
End Sub
Public Sub frm_Set(fm As Form)
Set frm = fm
frm.OnCurrent = EVENT_PROCEDURE
Me.ck.ControlSource = _
"=IIF(InStr([txt1],"","" & [選手ID] & "","")>0,True,False)"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "InStr([txt1],"","" & [選手ID] & "","")>0")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
End Sub
Public Sub frm_Set(fm As Form)
Set frm = fm
frm.OnCurrent = EVENT_PROCEDURE
Me.ck.ControlSource = _
"=IIF(InStr([txt1],"","" & [選手ID] & "","")>0,True,False)"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "InStr([txt1],"","" & [選手ID] & "","")>0")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
サブフォームとして組み込まれていなければ、起動しない様に
親からフォーム「F2S1」(「T試合」を表示している帳票フォーム)のフォームを教えてもらう。
で、レコード移動時を検知できるように・・・・
3)単票に10個のサブフォーム(F3M/F3S)(あ行、か行・・・毎にサブフォーム)

このフォームでは、あ行、か行・・・別で表示しましょう・・・・というもの。
フォーム「F1M」を「F3M」としてコピーし、フッタ部分のサブフォームコントロールを削除。
フッタ部分の領域をなしにして、詳細部分を広げます。
その詳細部分にサブフォームコントロール「FSUB0」〜「FSUB9」の10個配置します。
このサブフォームコントロールに、前作ったフォーム「F1S」を入れて、行別表示に仕立て上げます。
処理を少し変更するので、「F1S」を「F3S」としてコピーします。
サブフォームコントロールのソースオブジェクトは空欄にしておきます。
これは1つのフォーム「F3S」を使い回しする為の1つの方法だと思っています。
FSUB0 では「あ行」を表示して・・・・ FSUB1 では「か行」を表示して・・・・って指示したかったので
この指示に、親フォームのタグを連絡用に使い、ソースオブジェクトに「F3S」を設定
「F3S」では、情報を親のタグから入手し、指示に従った表示をするように。
サブフォームコントロールは、10人分を表示できる高さにデザインで設定しておきます。
親フォーム「F3M」に記述したのは以下
Private Const sFilter As String = _
"[あ-お]*,[か-ご]*,[さ-ぞ]*,[た-ど]*,[な-の]*," _
& "[は-ぽ]*,[ま-も]*,[や-よ]*,[ら-ろ]*,[わ-ん]*"
Private Sub Form_Load()
Dim sAry() As String
Dim i As Long
sAry = Split(sFilter, ",")
For i = 0 To UBound(sAry)
Me.Tag = sAry(i)
Me("FSUB" & i).SourceObject = "F3S"
Next
End Sub
"[あ-お]*,[か-ご]*,[さ-ぞ]*,[た-ど]*,[な-の]*," _
& "[は-ぽ]*,[ま-も]*,[や-よ]*,[ら-ろ]*,[わ-ん]*"
Private Sub Form_Load()
Dim sAry() As String
Dim i As Long
sAry = Split(sFilter, ",")
For i = 0 To UBound(sAry)
Me.Tag = sAry(i)
Me("FSUB" & i).SourceObject = "F3S"
Next
End Sub
子フォーム「F3S」として「F1S」から変更した部分は以下
Private Const sSource As String = "SELECT * FROM T選手 WHERE よみ Like '%1' ORDER BY よみ;"
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Dim WithEvents frm As Form
Dim sFilter As String
Dim dic As Object
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
frm.OnCurrent = EVENT_PROCEDURE
sFilter = frm.Tag
Me.RecordSource = Replace(sSource, "%1", sFilter)
If (Me.Recordset.RecordCount <= 10) Then Me.ScrollBars = 0
Me.ck.ControlSource = _
"=IIF(InStr([txt1],"","" & [選手ID] & "","")>0,True,False)"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "InStr([txt1],"","" & [選手ID] & "","")>0")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset
dic.RemoveAll
If (Not IsNull(frm.試合ID)) Then
sSql = "SELECT * FROM T出場 WHERE 試合ID = " & frm.試合ID _
& " AND 選手ID IN (SELECT 選手ID FROM T選手 WHERE よみ LIKE '" _
& sFilter & "');"
Set rs = CurrentDb.OpenRecordset(sSql)
While (Not rs.EOF)
dic.Item(rs("選手ID").Value) = Null
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
End If
Call Txt1ValueSet
End Sub
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Dim WithEvents frm As Form
Dim sFilter As String
Dim dic As Object
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
frm.OnCurrent = EVENT_PROCEDURE
sFilter = frm.Tag
Me.RecordSource = Replace(sSource, "%1", sFilter)
If (Me.Recordset.RecordCount <= 10) Then Me.ScrollBars = 0
Me.ck.ControlSource = _
"=IIF(InStr([txt1],"","" & [選手ID] & "","")>0,True,False)"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "InStr([txt1],"","" & [選手ID] & "","")>0")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset
dic.RemoveAll
If (Not IsNull(frm.試合ID)) Then
sSql = "SELECT * FROM T出場 WHERE 試合ID = " & frm.試合ID _
& " AND 選手ID IN (SELECT 選手ID FROM T選手 WHERE よみ LIKE '" _
& sFilter & "');"
Set rs = CurrentDb.OpenRecordset(sSql)
While (Not rs.EOF)
dic.Item(rs("選手ID").Value) = Null
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
End If
Call Txt1ValueSet
End Sub
If (Me.Recordset.RecordCount <= 10) Then Me.ScrollBars = 0
この記述ですが、表示選手が10人に満たない場合、2007 ではスクロールバー表示はありませんでした。でも、2000 / 2003 では、常に縦のスクロールバーが表示される。
サブフォームコントロール側では10人分表示できるように設定しているので、
共通の処理として、表示対象が10人以下ならスクロールバーを表示しない様に・・・・
表示を見てみましたが、何のための空白部分か・・・何か違和感ありありです。
あ行、か行 の表示はやめて、20人単位で表示しましょうか・・・・っていうのが次(F4M/F4S)に
4)単票に6個のサブフォーム(F4M/F4S)(1つのサブフォームでは20人表示)

このフォームでは、20人単位でサブフォームとして表示しましょう・・・というもの
基本的な考え方/処理は前の(F3M/F3S)と同じなので、「F3M」を「F4M」、「F3S」を「F4S」にコピー
前のフォームでは、 よみ Like '%1' の %1 にあたる文字列を指定していましたが、
今回のフォームでは 選手ID IN (%1) の %1 にあたる文字列を指定するようにします。
フォーム「F4M」で、サブフォームコントロールを「FSUB1」〜「FSUB6」の6つに
選手20人を表示できるようにしておきます。
処理の概要としては、選手を「よみ」順で20人単位でサブフォームを設定していきますが、
最後の「FSUB6」だけは20人を超えても作り続けるようにします。
その時に、もしかして・・・人数が多すぎて・・・・・タグの文字数制限に引っ掛かるようになるかも・・・
(その時にはその時で別の手段を使うように変更しますか・・・・)
親フォーム「F4M」に記述したのは以下
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Dim iFSUB As Long
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
iFSUB = 0
i = 0
sSql = ""
While (Not rs.EOF)
sSql = sSql & "," & rs("選手ID")
i = i + 1
If (i >= 20) Then
If (iFSUB < 5) Then
iFSUB = iFSUB + 1
Me.Tag = Mid(sSql, 2)
Me("FSUB" & iFSUB).SourceObject = "F4S"
sSql = ""
End If
i = 0
End If
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
If (Len(sSql) > 0) Then
Me.Tag = Mid(sSql, 2)
Me("FSUB" & iFSUB + 1).SourceObject = "F4S"
End If
End Sub
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Dim iFSUB As Long
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
iFSUB = 0
i = 0
sSql = ""
While (Not rs.EOF)
sSql = sSql & "," & rs("選手ID")
i = i + 1
If (i >= 20) Then
If (iFSUB < 5) Then
iFSUB = iFSUB + 1
Me.Tag = Mid(sSql, 2)
Me("FSUB" & iFSUB).SourceObject = "F4S"
sSql = ""
End If
i = 0
End If
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
If (Len(sSql) > 0) Then
Me.Tag = Mid(sSql, 2)
Me("FSUB" & iFSUB + 1).SourceObject = "F4S"
End If
End Sub
子フォーム「F4S」で「F3S」から変更した部分は以下
Private Const sSource As String = "SELECT * FROM T選手 WHERE 選手ID IN (%1) ORDER BY よみ;"
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Dim WithEvents frm As Form
Dim sFilter As String
Dim dic As Object
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
frm.OnCurrent = EVENT_PROCEDURE
sFilter = frm.Tag
Me.RecordSource = Replace(sSource, "%1", sFilter)
If (Me.Recordset.RecordCount <= 20) Then Me.ScrollBars = 0
Me.ck.ControlSource = _
"=IIF(InStr([txt1],"","" & [選手ID] & "","")>0,True,False)"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "InStr([txt1],"","" & [選手ID] & "","")>0")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset
dic.RemoveAll
If (Not IsNull(frm.試合ID)) Then
sSql = "SELECT * FROM T出場 WHERE 試合ID = " & frm.試合ID _
& " AND 選手ID IN (" & sFilter & ");"
Set rs = CurrentDb.OpenRecordset(sSql)
While (Not rs.EOF)
dic.Item(rs("選手ID").Value) = Null
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
End If
Call Txt1ValueSet
End Sub
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Dim WithEvents frm As Form
Dim sFilter As String
Dim dic As Object
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Set dic = CreateObject("Scripting.Dictionary")
frm.OnCurrent = EVENT_PROCEDURE
sFilter = frm.Tag
Me.RecordSource = Replace(sSource, "%1", sFilter)
If (Me.Recordset.RecordCount <= 20) Then Me.ScrollBars = 0
Me.ck.ControlSource = _
"=IIF(InStr([txt1],"","" & [選手ID] & "","")>0,True,False)"
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "InStr([txt1],"","" & [選手ID] & "","")>0")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset
dic.RemoveAll
If (Not IsNull(frm.試合ID)) Then
sSql = "SELECT * FROM T出場 WHERE 試合ID = " & frm.試合ID _
& " AND 選手ID IN (" & sFilter & ");"
Set rs = CurrentDb.OpenRecordset(sSql)
While (Not rs.EOF)
dic.Item(rs("選手ID").Value) = Null
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
End If
Call Txt1ValueSet
End Sub
ここまでのフォームでは、帳票フォームで非連結のチェックボックスを使ってきました。
この方法からチョッと離れて考えてみよう・・・・ということで、以降、違う方法になっていきます。
5)上記フォームのワークテーブル使用バージョン(F5M/F5S)

前回のフォーム構成を使って、サブフォーム側のレコードソースをワークテーブルにしてみます。
テーブル「Tワーク出場5」として
| フィールド | 型 等々 |
|---|---|
| an | オートナンバ (主キー) |
| 試合ID | 長整数 「T試合」の試合ID |
| 選手ID | 長整数 「T選手」の選手ID |
| ck | Yes/No型 |
1試合分のデータだけを扱うようにします。
試合ID は不要?なのかもしれません。
データを「Tワーク出場5」「T出場」間でやり取りしやすかったので・・・
(これが後で問題に・・・・・・・・・後述)
フォーム「F4M」を「F5M」としてコピーします。
サブフォームの個数、20人までの表示は前と変わりません。
変わるのは、ワークテーブル「Tワーク出場5」から本テーブル「T出場」へ戻すタイミングを作ること・・・・
更新用のボタン「btn1」と、
戻す(本テーブル「T出場」からワークテーブル「Tワーク出場5」へ再展開)用のボタン「btn2」を配置
サブフォーム用のフォームは「F4S」を「F5S」としてコピーします。
レコードソースは表示する際に書き換えるものの、
標準で「Tワーク出場5」と「T選手」とで選手名を表示できるようにしたものを設定しておきます。
ヘッダ部にあった「txt1」、詳細部分でチェックボックスを覆っていた「btn1」は削除します。
チェックボックス「ck」のコントロールソースに「ck」を設定し、タブストップを「はい」に。
チェックボックスをクリックして更新するので、フォームの「更新の許可」だけ「はい」に変更。
処理の概要を。
メインフォーム「F5M」では、Load 時、「Tワーク出場5」に全選手の選手IDを作成します。
「T選手」の「よみ」順で20人ずつサブフォームに割り当てていきます。
(「FSUB1」〜順にソースオブジェクトにサブフォームを割り当てていきます)
レコード移動時に、「Tワーク出場5」の 試合ID と ck を更新します。
更新後処理では、新規で試合ID が割り当てられることがあるので、レコード移動時の処理をもう一度。
更新ボタン「btn1」がクリックされたら、「Tワーク出場5」を元にその試合に該当するものを作り直します。
戻すボタン「btn2」がクリックされたら、その試合にあったデータを「Tワーク出場5」に作り直します。
ここで、新規登録(試合IDがNull)の場合、「Tワーク出場5」の試合ID は 0 に設定。
サブフォーム側では、「Tワーク出場5」に対して「ck」を設定していきます。
設定する際、親に配置した更新ボタン「btn1」の文字色を変更するようにします。
親フォーム「F5M」に記述したのは以下
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Dim iFSUB As Long
sSql = "DELETE * FROM Tワーク出場5;"
CurrentDb.Execute sSql
sSql = "INSERT INTO Tワーク出場5(選手ID) " _
& "SELECT 選手ID FROM T選手;"
CurrentDb.Execute sSql
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
iFSUB = 0
i = 0
sSql = ""
While (Not rs.EOF)
sSql = sSql & "," & rs("選手ID")
i = i + 1
If (i >= 20) Then
If (iFSUB < 5) Then
iFSUB = iFSUB + 1
Me.Tag = Mid(sSql, 2)
With Me("FSUB" & iFSUB)
.SourceObject = "F5S"
.LinkMasterFields = ""
.LinkChildFields = ""
End With
sSql = ""
End If
i = 0
End If
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
If (Len(sSql) > 0) Then
Me.Tag = Mid(sSql, 2)
With Me("FSUB" & iFSUB + 1)
.SourceObject = "F5S"
.LinkMasterFields = ""
.LinkChildFields = ""
End With
End If
End Sub
Private Sub FSUBrequery()
Dim i As Long
For i = 1 To 6
With Me("FSUB" & i)
If (Len(.SourceObject) = 0) Then Exit For
.Form.Requery
End With
Next
Me.btn1.ForeColor = RGB(0, 0, 0)
End Sub
Private Sub Form_Current()
Dim sSql As String
sSql = "UPDATE Tワーク出場5 SET 試合ID = " & Nz(Me.試合ID, 0) _
& ", ck = False;"
CurrentDb.Execute sSql
sSql = "UPDATE Tワーク出場5 INNER JOIN T出場 ON " _
& "Tワーク出場5.試合ID = T出場.試合ID AND Tワーク出場5.選手ID = T出場.選手ID " _
& "SET Tワーク出場5.ck = True;"
CurrentDb.Execute sSql
Call FSUBrequery
End Sub
Private Sub Form_AfterUpdate()
Call Form_Current
End Sub
Private Sub btn1_Click()
Dim sSql As String
If (Me.btn1.ForeColor = RGB(0, 0, 0)) Then Exit Sub
sSql = "DELETE * FROM T出場 WHERE 試合ID = " & Me.試合ID & ";"
CurrentDb.Execute sSql
sSql = "INSERT INTO T出場(試合ID, 選手ID) " _
& "SELECT 試合ID, 選手ID FROM Tワーク出場5 WHERE ck = True;"
CurrentDb.Execute sSql
Call FSUBrequery
End Sub
Private Sub btn2_Click()
Dim sSql As String
If (Me.btn1.ForeColor = RGB(0, 0, 0)) Then Exit Sub
sSql = "UPDATE Tワーク出場5 SET ck = False;"
CurrentDb.Execute sSql
sSql = "UPDATE Tワーク出場5 INNER JOIN T出場 ON " _
& "Tワーク出場5.試合ID = T出場.試合ID AND Tワーク出場5.選手ID = T出場.選手ID " _
& "SET Tワーク出場5.ck = True;"
CurrentDb.Execute sSql
Call FSUBrequery
End Sub
Private Sub Form_Close()
Dim sSql As String
sSql = "DELETE * FROM Tワーク出場5;"
CurrentDb.Execute sSql
End Sub
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Dim iFSUB As Long
sSql = "DELETE * FROM Tワーク出場5;"
CurrentDb.Execute sSql
sSql = "INSERT INTO Tワーク出場5(選手ID) " _
& "SELECT 選手ID FROM T選手;"
CurrentDb.Execute sSql
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
iFSUB = 0
i = 0
sSql = ""
While (Not rs.EOF)
sSql = sSql & "," & rs("選手ID")
i = i + 1
If (i >= 20) Then
If (iFSUB < 5) Then
iFSUB = iFSUB + 1
Me.Tag = Mid(sSql, 2)
With Me("FSUB" & iFSUB)
.SourceObject = "F5S"
.LinkMasterFields = ""
.LinkChildFields = ""
End With
sSql = ""
End If
i = 0
End If
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
If (Len(sSql) > 0) Then
Me.Tag = Mid(sSql, 2)
With Me("FSUB" & iFSUB + 1)
.SourceObject = "F5S"
.LinkMasterFields = ""
.LinkChildFields = ""
End With
End If
End Sub
Private Sub FSUBrequery()
Dim i As Long
For i = 1 To 6
With Me("FSUB" & i)
If (Len(.SourceObject) = 0) Then Exit For
.Form.Requery
End With
Next
Me.btn1.ForeColor = RGB(0, 0, 0)
End Sub
Private Sub Form_Current()
Dim sSql As String
sSql = "UPDATE Tワーク出場5 SET 試合ID = " & Nz(Me.試合ID, 0) _
& ", ck = False;"
CurrentDb.Execute sSql
sSql = "UPDATE Tワーク出場5 INNER JOIN T出場 ON " _
& "Tワーク出場5.試合ID = T出場.試合ID AND Tワーク出場5.選手ID = T出場.選手ID " _
& "SET Tワーク出場5.ck = True;"
CurrentDb.Execute sSql
Call FSUBrequery
End Sub
Private Sub Form_AfterUpdate()
Call Form_Current
End Sub
Private Sub btn1_Click()
Dim sSql As String
If (Me.btn1.ForeColor = RGB(0, 0, 0)) Then Exit Sub
sSql = "DELETE * FROM T出場 WHERE 試合ID = " & Me.試合ID & ";"
CurrentDb.Execute sSql
sSql = "INSERT INTO T出場(試合ID, 選手ID) " _
& "SELECT 試合ID, 選手ID FROM Tワーク出場5 WHERE ck = True;"
CurrentDb.Execute sSql
Call FSUBrequery
End Sub
Private Sub btn2_Click()
Dim sSql As String
If (Me.btn1.ForeColor = RGB(0, 0, 0)) Then Exit Sub
sSql = "UPDATE Tワーク出場5 SET ck = False;"
CurrentDb.Execute sSql
sSql = "UPDATE Tワーク出場5 INNER JOIN T出場 ON " _
& "Tワーク出場5.試合ID = T出場.試合ID AND Tワーク出場5.選手ID = T出場.選手ID " _
& "SET Tワーク出場5.ck = True;"
CurrentDb.Execute sSql
Call FSUBrequery
End Sub
Private Sub Form_Close()
Dim sSql As String
sSql = "DELETE * FROM Tワーク出場5;"
CurrentDb.Execute sSql
End Sub
子フォーム「F5S」に記述したのは以下
Private Const sSource As String = _
"SELECT Q1.*, Q2.選手名 FROM Tワーク出場5 AS Q1 INNER JOIN T選手 AS Q2 " _
& "ON Q1.選手ID = Q2.選手ID WHERE Q1.選手ID IN (%1) ORDER BY Q2.よみ;"
Dim frm As Form
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Me.RecordSource = Replace(sSource, "%1", frm.Tag)
If (Me.Recordset.RecordCount <= 20) Then Me.ScrollBars = 0
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "[ck]=True")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub Form_Dirty(Cancel As Integer)
If (Me.試合ID = 0) Then Cancel = True
End Sub
Private Sub ck_Click()
frm.btn1.ForeColor = RGB(255, 0, 0)
End Sub
Private Sub 選手名_Enter()
Me.ck.SetFocus
If (Me.試合ID <> 0) Then
Me.ck = Not Me.ck
Call ck_Click
End If
End Sub
Private Sub Form_Close()
Set frm = Nothing
End Sub
"SELECT Q1.*, Q2.選手名 FROM Tワーク出場5 AS Q1 INNER JOIN T選手 AS Q2 " _
& "ON Q1.選手ID = Q2.選手ID WHERE Q1.選手ID IN (%1) ORDER BY Q2.よみ;"
Dim frm As Form
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
Me.RecordSource = Replace(sSource, "%1", frm.Tag)
If (Me.Recordset.RecordCount <= 20) Then Me.ScrollBars = 0
With Me.選手名.FormatConditions
.Delete
With .Add(acExpression, , "[ck]=True")
.BackColor = RGB(255, 240, 240)
End With
End With
End Sub
Private Sub Form_Dirty(Cancel As Integer)
If (Me.試合ID = 0) Then Cancel = True
End Sub
Private Sub ck_Click()
frm.btn1.ForeColor = RGB(255, 0, 0)
End Sub
Private Sub 選手名_Enter()
Me.ck.SetFocus
If (Me.試合ID <> 0) Then
Me.ck = Not Me.ck
Call ck_Click
End If
End Sub
Private Sub Form_Close()
Set frm = Nothing
End Sub
上記で動いているようですが、この1つ前の記述では、
新規の試合を入力しようとした場合、サブフォーム部分に何も表示されない現象が発生。
1つ前の記述と言うと、サブフォームを設定する際の記述になりますが
(以下の黄色い部分の記述がありませんでした)
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Dim iFSUB As Long
sSql = "DELETE * FROM Tワーク出場5;"
CurrentDb.Execute sSql
sSql = "INSERT INTO Tワーク出場5(選手ID) " _
& "SELECT 選手ID FROM T選手;"
CurrentDb.Execute sSql
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
iFSUB = 0
i = 0
sSql = ""
While (Not rs.EOF)
sSql = sSql & "," & rs("選手ID")
i = i + 1
If (i >= 20) Then
If (iFSUB < 5) Then
iFSUB = iFSUB + 1
Me.Tag = Mid(sSql, 2)
With Me("FSUB" & iFSUB)
.SourceObject = "F5S"
.LinkMasterFields = ""
.LinkChildFields = ""
End With
sSql = ""
End If
i = 0
End If
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
If (Len(sSql) > 0) Then
Me.Tag = Mid(sSql, 2)
With Me("FSUB" & iFSUB + 1)
.SourceObject = "F5S"
.LinkMasterFields = ""
.LinkChildFields = ""
End With
End If
End Sub
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Dim iFSUB As Long
sSql = "DELETE * FROM Tワーク出場5;"
CurrentDb.Execute sSql
sSql = "INSERT INTO Tワーク出場5(選手ID) " _
& "SELECT 選手ID FROM T選手;"
CurrentDb.Execute sSql
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
iFSUB = 0
i = 0
sSql = ""
While (Not rs.EOF)
sSql = sSql & "," & rs("選手ID")
i = i + 1
If (i >= 20) Then
If (iFSUB < 5) Then
iFSUB = iFSUB + 1
Me.Tag = Mid(sSql, 2)
With Me("FSUB" & iFSUB)
.SourceObject = "F5S"
.LinkMasterFields = ""
.LinkChildFields = ""
End With
sSql = ""
End If
i = 0
End If
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
If (Len(sSql) > 0) Then
Me.Tag = Mid(sSql, 2)
With Me("FSUB" & iFSUB + 1)
.SourceObject = "F5S"
.LinkMasterFields = ""
.LinkChildFields = ""
End With
End If
End Sub
.SourceObject = "F5S"
でサブフォームの処理が走り始めるわけですが、Form_Open が呼ばれる前に LinkMasterFields / LinkChildFields が "試合ID" に自動設定。
Form_Open の初めの処理で、LinkMasterFields / LinkChildFields をクリア(空文字設定)しても、
その後の、RecordSource 設定直後にまた、LinkMasterFields / LinkChildFields が "試合ID" に。
リレーションシップは設定していないし・・・・
ま、確かに、親の「試合ID」は主キーに設定していましたが・・・・
あ、ルックアップは設定していたかな・・・
Access さんのどこかのオプション設定で回避できるのかもしれないけど、その設定を強要する???
あれ、や・・・、名前の自動修正?? これってそのDBを限定できたっっっっっかも・・・(未確認)
でも、親側で SourceObject 設定後に、LinkMasterFields / LinkChildFields をクリアすれば
動くようなので、この記述を採用しました。
主キーと同じフィールド名があったから??・・・・次回から気をつけよう。
6)単票に多数の非連結チェックボックス(F6M)

これが回答に使った、そのものになります。
元にするフォーム「F6M_BASE」を作っておきます。(「T試合」を元にした単票フォーム)
「F5M」をコピーしてサブフォーム部分を削除、VBA記述を全削除したものにしておきます。
また、
・「登録」ボタンが押された時に登録する
・・・・ということが抜けていたので、それに対応するように、登録ボタン「btn1」を配置。
チェックボックスを作成していきますが、必要人数以上のチェックボックスを不可視で作っておいて、
表示する時に必要な分だけ、可視(Visible=True)に変更します。
チェックボックス名の命名規則として、「"ck" & 連番」とします。
(100くらい、ということだったので "ck1" 〜 "ck120" )
手作業で多数のチェックボックスを作成して名前を変更・・・
これ面倒なので、VBAで作成します。
1列20個で、計120個(6列分)のチェックボックスと、それにひっつくラベル
このラベル部分に選手名を表示するようにします。
標準モジュール「M6Make」を用意しました。
チェックボックスを追加する対象のフォーム「F6M_BASE」を、一旦「F6M_BASE_」にコピーして、
作り終わったら「F6M」に名称を変更 という内容になってます。
Private Const sFname As String = "F6M_BASE"
Private Const sFnew As String = "F6M"
Private Const IPX As Long = 567
Private Const iRowCount As Long = 20
Public Const iM6Count As Long = 120
Public Sub M6MakeProc()
Dim i As Long
Dim sN As String, sNc As String
Dim iRow As Long, iCol As Long
On Error Resume Next
sN = sFname & "_"
DoCmd.DeleteObject acForm, sN
DoCmd.CopyObject , sN, acForm, sFname
DoCmd.OpenForm sN, acDesign
With Forms(sN)
iRow = IPX * 1
iCol = IPX * 0.27
For i = 0 To iM6Count - 1
With CreateControl(sN, acCheckBox, acDetail)
sNc = "ck" & i + 1
.Name = sNc
.Top = iRow + (i Mod iRowCount) * IPX * 0.5
.Left = (i \ iRowCount) * IPX * 3.2 + iCol
.Width = IPX * 0.4
.Height = IPX * 0.4
.DefaultValue = "0"
.TabStop = False
.Visible = False
End With
With CreateControl(sN, acLabel, acDetail, sNc)
.Top = iRow + (i Mod iRowCount) * IPX * 0.5
.Left = (i \ iRowCount) * IPX * 3.2 + (IPX * 0.42) + iCol
.Width = IPX * 2.6
.Height = IPX * 0.42
.BorderStyle = 1
.BorderWidth = 1
.BorderColor = RGB(0, 0, 0)
.BackStyle = 0
.BackColor = RGB(255, 240, 240)
End With
Next
End With
DoCmd.Close acForm, sN, acSaveYes
DoCmd.DeleteObject acForm, sFnew
DoCmd.Rename sFnew, acForm, sN
End Sub
Private Const sFnew As String = "F6M"
Private Const IPX As Long = 567
Private Const iRowCount As Long = 20
Public Const iM6Count As Long = 120
Public Sub M6MakeProc()
Dim i As Long
Dim sN As String, sNc As String
Dim iRow As Long, iCol As Long
On Error Resume Next
sN = sFname & "_"
DoCmd.DeleteObject acForm, sN
DoCmd.CopyObject , sN, acForm, sFname
DoCmd.OpenForm sN, acDesign
With Forms(sN)
iRow = IPX * 1
iCol = IPX * 0.27
For i = 0 To iM6Count - 1
With CreateControl(sN, acCheckBox, acDetail)
sNc = "ck" & i + 1
.Name = sNc
.Top = iRow + (i Mod iRowCount) * IPX * 0.5
.Left = (i \ iRowCount) * IPX * 3.2 + iCol
.Width = IPX * 0.4
.Height = IPX * 0.4
.DefaultValue = "0"
.TabStop = False
.Visible = False
End With
With CreateControl(sN, acLabel, acDetail, sNc)
.Top = iRow + (i Mod iRowCount) * IPX * 0.5
.Left = (i \ iRowCount) * IPX * 3.2 + (IPX * 0.42) + iCol
.Width = IPX * 2.6
.Height = IPX * 0.42
.BorderStyle = 1
.BorderWidth = 1
.BorderColor = RGB(0, 0, 0)
.BackStyle = 0
.BackColor = RGB(255, 240, 240)
End With
Next
End With
DoCmd.Close acForm, sN, acSaveYes
DoCmd.DeleteObject acForm, sFnew
DoCmd.Rename sFnew, acForm, sN
End Sub
フォーム「F6M」にVBAを記述していきますが、概要をまず。
・チェックボックスクリック時
「T試合」の新規レコードであったら、見た目チェックを無効とするようにします
チェックボックスは非連結なので、変更してもレコード移動等のタイミングは分かりません。
なので、編集状態にしておくことで、レコード移動のタイミングを検知することが出来るようになります。
そこで、「天候」を同じ値で設定して編集状態に・・・
チェックボックスの変更状態を更新します。
・フォームが起動された時
「T選手」から選手情報をチェックボックスに割り当てていきます。
割り当てたチェックボックスのラベルに選手名を、+可視に
チェックボックスの Tag に、選手ID を登録しておきます。
これにより、クリックされた時、簡単に選手を特定できるようになります。
・レコード移動時
レコード移動によって「試合ID」が変化するので、その時の「試合ID」で
「T出場」にある選手を抽出し、チェックボックスを変更していきます。
・更新前
登録ボタン「btn1」クリックでのみ更新/登録できるようにするので、
意図しないタイミングでの更新はできない様に、常に Cancel = True とします
・取り消し時(2000では動かない)
チェックボックスを初期状態に戻し、レコード移動時に取得した選手情報でチェックボックスを再設定
・登録ボタン
「T試合」への登録チェックが必要であればこのタイミングで
一度、更新前処理を無効としてレコードを登録します。
「T出場」に対して、チェックボックスの状態を反映します。
dicOld は、レコード移動時に取得したその試合での選手情報で、
チェックボックスでの変更状況は dicNew にて。
レコード移動直後は、dicOld dicNew は同じ内容となっています。
チェックボックスの操作により、dicOld dicNew の差分で DELETE したり、INSERT したりします。
登録操作後、dicOld dicNew の内容を同じにするため、レコード移動時の処理を。
記述したVBAは以下
Dim dic As Object ' どの選手にどのチェックボックスを割り当てたか
Dim dicOld As Object ' 編集前の選手割り当て状況 (初期では dicOld = dicNew)
Dim dicNew As Object ' いじっていた選手割り当て状況
Private Function ChkClick()
If (IsNull(Me.試合ID)) Then
Me.ActiveControl = Not Me.ActiveControl
Exit Function
End If
Me.天候 = Me.天候 '編集状態にしたいため
With Me.ActiveControl
If (.Value) Then
.Controls(0).BackStyle = 1
dicNew.Item(CLng(.Tag)) = Null
Else
.Controls(0).BackStyle = 0
dicNew.Remove CLng(.Tag)
End If
End With
End Function
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Dim sN As String
Set dic = CreateObject("Scripting.Dictionary")
Set dicOld = CreateObject("Scripting.Dictionary")
Set dicNew = CreateObject("Scripting.Dictionary")
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
i = 0
Do While (Not rs.EOF)
i = i + 1
If (i > iM6Count) Then Exit Do
sN = "ck" & i
dic.Item(rs("選手ID").Value) = sN
With Me(sN)
.Value = False
.Tag = rs("選手ID")
With .Controls(0)
.Caption = rs("選手名")
.BackStyle = 0
End With
.OnClick = "=ChkClick()"
.Visible = True
End With
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
End Sub
Private Sub Form_Current()
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Me.Painting = False
For i = 1 To iM6Count
With Me("ck" & i)
If (Not .Visible) Then Exit For
.Value = False
.Controls(0).BackStyle = 0
End With
Next
dicOld.RemoveAll
dicNew.RemoveAll
sSql = "SELECT * FROM T出場 WHERE 試合ID = " & Nz(Me.試合ID, 0) & ";"
Set rs = CurrentDb.OpenRecordset(sSql)
While (Not rs.EOF)
i = rs("選手ID")
dicOld.Item(i) = Null
dicNew.Item(i) = Null
With Me(dic.Item(i))
.Value = True
.Controls(0).BackStyle = 1
End With
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
Me.Painting = True
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
Cancel = True
MsgBox "修正したら登録ボタンで確定", vbCritical
End Sub
Private Sub Form_Undo(Cancel As Integer)
Dim v As Variant
Dim i As Long
Me.Painting = False
For i = 1 To iM6Count
With Me("ck" & i)
If (Not .Visible) Then Exit For
.Value = False
.Controls(0).BackStyle = 0
End With
Next
dicNew.RemoveAll
If (dicOld.Count > 0) Then
For Each v In dicOld.Keys
dicNew.Item(v) = Null
With Me(dic.Item(v))
.Value = True
.Controls(0).BackStyle = 1
End With
Next
End If
Me.Painting = True
End Sub
Private Sub btn1_Click()
Dim sSql As String
Dim sS As String
Dim v As Variant
If (Not Me.Dirty) Then Exit Sub
' 試合日など入力チェックをするのなら、この場所で
'
sS = Me.BeforeUpdate
Me.BeforeUpdate = ""
Me.Dirty = False
Me.BeforeUpdate = sS
If (dicOld.Count > 0) Then
For Each v In dicOld.Keys
If (Not dicNew.Exists(v)) Then
sSql = "DELETE * FROM T出場 WHERE 試合ID = " & Me.試合ID _
& " AND 選手ID = " & v & ";"
CurrentDb.Execute sSql
End If
Next
End If
If (dicNew.Count > 0) Then
For Each v In dicNew.Keys
If (Not dicOld.Exists(v)) Then
sSql = "INSERT INTO T出場(試合ID, 選手ID) VALUES (" _
& Me.試合ID & "," & v & ");"
CurrentDb.Execute sSql
End If
Next
End If
Call Form_Current
End Sub
Private Sub Form_Close()
Set dic = Nothing
Set dicOld = Nothing
Set dicNew = Nothing
End Sub
Dim dicOld As Object ' 編集前の選手割り当て状況 (初期では dicOld = dicNew)
Dim dicNew As Object ' いじっていた選手割り当て状況
Private Function ChkClick()
If (IsNull(Me.試合ID)) Then
Me.ActiveControl = Not Me.ActiveControl
Exit Function
End If
Me.天候 = Me.天候 '編集状態にしたいため
With Me.ActiveControl
If (.Value) Then
.Controls(0).BackStyle = 1
dicNew.Item(CLng(.Tag)) = Null
Else
.Controls(0).BackStyle = 0
dicNew.Remove CLng(.Tag)
End If
End With
End Function
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Dim sN As String
Set dic = CreateObject("Scripting.Dictionary")
Set dicOld = CreateObject("Scripting.Dictionary")
Set dicNew = CreateObject("Scripting.Dictionary")
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
i = 0
Do While (Not rs.EOF)
i = i + 1
If (i > iM6Count) Then Exit Do
sN = "ck" & i
dic.Item(rs("選手ID").Value) = sN
With Me(sN)
.Value = False
.Tag = rs("選手ID")
With .Controls(0)
.Caption = rs("選手名")
.BackStyle = 0
End With
.OnClick = "=ChkClick()"
.Visible = True
End With
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
End Sub
Private Sub Form_Current()
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Me.Painting = False
For i = 1 To iM6Count
With Me("ck" & i)
If (Not .Visible) Then Exit For
.Value = False
.Controls(0).BackStyle = 0
End With
Next
dicOld.RemoveAll
dicNew.RemoveAll
sSql = "SELECT * FROM T出場 WHERE 試合ID = " & Nz(Me.試合ID, 0) & ";"
Set rs = CurrentDb.OpenRecordset(sSql)
While (Not rs.EOF)
i = rs("選手ID")
dicOld.Item(i) = Null
dicNew.Item(i) = Null
With Me(dic.Item(i))
.Value = True
.Controls(0).BackStyle = 1
End With
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
Me.Painting = True
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
Cancel = True
MsgBox "修正したら登録ボタンで確定", vbCritical
End Sub
Private Sub Form_Undo(Cancel As Integer)
Dim v As Variant
Dim i As Long
Me.Painting = False
For i = 1 To iM6Count
With Me("ck" & i)
If (Not .Visible) Then Exit For
.Value = False
.Controls(0).BackStyle = 0
End With
Next
dicNew.RemoveAll
If (dicOld.Count > 0) Then
For Each v In dicOld.Keys
dicNew.Item(v) = Null
With Me(dic.Item(v))
.Value = True
.Controls(0).BackStyle = 1
End With
Next
End If
Me.Painting = True
End Sub
Private Sub btn1_Click()
Dim sSql As String
Dim sS As String
Dim v As Variant
If (Not Me.Dirty) Then Exit Sub
' 試合日など入力チェックをするのなら、この場所で
'
sS = Me.BeforeUpdate
Me.BeforeUpdate = ""
Me.Dirty = False
Me.BeforeUpdate = sS
If (dicOld.Count > 0) Then
For Each v In dicOld.Keys
If (Not dicNew.Exists(v)) Then
sSql = "DELETE * FROM T出場 WHERE 試合ID = " & Me.試合ID _
& " AND 選手ID = " & v & ";"
CurrentDb.Execute sSql
End If
Next
End If
If (dicNew.Count > 0) Then
For Each v In dicNew.Keys
If (Not dicOld.Exists(v)) Then
sSql = "INSERT INTO T出場(試合ID, 選手ID) VALUES (" _
& Me.試合ID & "," & v & ");"
CurrentDb.Execute sSql
End If
Next
End If
Call Form_Current
End Sub
Private Sub Form_Close()
Set dic = Nothing
Set dicOld = Nothing
Set dicNew = Nothing
End Sub
上記フォームのVBA記述量削減・操作限定バージョン(F6M2)

このフォームでは、「F6M」の処理/操作を限定して、極力VBA記述を少なくしたものになります。
限定したのは、
・「T試合」の新規登録時のみ
・選手を追加したらフォームの手直しが必要
・この用途以外にチェックボックスがない事
チェックボックスは非連結で自由に配置しますが、以下の設定が必要です。
・チェックボックスの「タグ」には、「選手ID」を設定しておく
・チェックボックスのラベルには、その選手の「選手名」を表示するように
また、チェックボックス名は何でも構わない、個数も限定しない。
で、これを確認するフォームを作成するのですが、手修正も面倒なので・・・・
標準モジュール「M6M62」を用意しました。
(フォーム「F6M」を元に、上記条件に修正していくものです)
Public Sub M6toM62()
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Const sSrcFname As String = "F6M"
Const sFname As String = "F6M2"
On Error Resume Next
DoCmd.DeleteObject acForm, sFname
DoCmd.CopyObject , sFname, acForm, sSrcFname
DoCmd.OpenForm sFname, acDesign
With Forms(sFname)
.Caption = sFname
.DataEntry = True
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
i = 0
While (Not rs.EOF)
i = i + 1
With .Controls("ck" & i)
.Visible = True
.Tag = rs("選手ID")
.Controls(0).Caption = rs("選手名")
End With
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
End With
End Sub
Dim sSql As String
Dim rs As DAO.Recordset
Dim i As Long
Const sSrcFname As String = "F6M"
Const sFname As String = "F6M2"
On Error Resume Next
DoCmd.DeleteObject acForm, sFname
DoCmd.CopyObject , sFname, acForm, sSrcFname
DoCmd.OpenForm sFname, acDesign
With Forms(sFname)
.Caption = sFname
.DataEntry = True
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
i = 0
While (Not rs.EOF)
i = i + 1
With .Controls("ck" & i)
.Visible = True
.Tag = rs("選手ID")
.Controls(0).Caption = rs("選手名")
End With
rs.MoveNext
Wend
rs.Close
Set rs = Nothing
End With
End Sub
で、割り当てられていない不要なチェックボックスを削除するなりします。
(3列削除しましたが、配置はそのままにしたのが現状の「F6M2」です)
フォーム「F6M2」に記述したのは以下
Private Sub Form_Current()
Dim ctl As Control
For Each ctl In Me.Controls
With ctl
If (.ControlType = acCheckBox) Then
.Value = False
End If
End With
Next
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
Cancel = True
MsgBox "登録ボタンで確定", vbCritical
End Sub
Private Sub btn1_Click()
Dim sSql As String
Dim sS As String
Dim ctl As Control
If (Not Me.Dirty) Then Exit Sub
' 試合日など入力チェックをするのなら、この場所で
'
sS = Me.BeforeUpdate
Me.BeforeUpdate = ""
Me.Dirty = False
Me.BeforeUpdate = sS
For Each ctl In Me.Controls
With ctl
If (.ControlType = acCheckBox And Len(.Tag) > 0) Then
If (.Value) Then
sSql = "INSERT INTO T出場(試合ID, 選手ID) VALUES (" _
& Me.試合ID & "," & .Tag & ");"
CurrentDb.Execute sSql
End If
End If
End With
Next
Me.Requery
End Sub
Dim ctl As Control
For Each ctl In Me.Controls
With ctl
If (.ControlType = acCheckBox) Then
.Value = False
End If
End With
Next
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
Cancel = True
MsgBox "登録ボタンで確定", vbCritical
End Sub
Private Sub btn1_Click()
Dim sSql As String
Dim sS As String
Dim ctl As Control
If (Not Me.Dirty) Then Exit Sub
' 試合日など入力チェックをするのなら、この場所で
'
sS = Me.BeforeUpdate
Me.BeforeUpdate = ""
Me.Dirty = False
Me.BeforeUpdate = sS
For Each ctl In Me.Controls
With ctl
If (.ControlType = acCheckBox And Len(.Tag) > 0) Then
If (.Value) Then
sSql = "INSERT INTO T出場(試合ID, 選手ID) VALUES (" _
& Me.試合ID & "," & .Tag & ");"
CurrentDb.Execute sSql
End If
End If
End With
Next
Me.Requery
End Sub
新規登録時に、まずチェックボックスのチェックを外しておきます。
「登録」ボタンがクリックされた時に、チェックがあるものを探し出し、レコードを追加します。
チェック自体は「T試合」のものを入力していない状態でも、自由に ON / OFF できます。
なお、VBA記述を少なくするために、ラベル部分の背景色は変更していません。
登録ボタン「btn1」の最後で、Me.Requery しているのは、
新規登録を連続して行った場合、前のレコードに戻れたような気がして・・・・
もし、戻ったとした場合、チェックボックスの表示を処理できない・・・・
なので、Me.Requery して、新規レコードのみにするように・・・・
7)上記フォームの表示変更バージョン(F7M)

このフォームでは、「よみ」順に割り当てていく際に、あ行、か行・・・・行が変わったら1つ空けましょう。
フォーム「F6M」を「F7M」でコピーし、以下の記述を追加/修正します。
Private Function GyouNum(sSrc As String) As Long
Select Case True
Case sSrc Like "[あ-お]*": GyouNum = 0
Case sSrc Like "[か-ご]*": GyouNum = 1
Case sSrc Like "[さ-ぞ]*": GyouNum = 2
Case sSrc Like "[た-ど]*": GyouNum = 3
Case sSrc Like "[な-の]*": GyouNum = 4
Case sSrc Like "[は-ぽ]*": GyouNum = 5
Case sSrc Like "[ま-も]*": GyouNum = 6
Case sSrc Like "[や-よ]*": GyouNum = 7
Case sSrc Like "[ら-ろ]*": GyouNum = 8
Case sSrc Like "[わ-ん]*": GyouNum = 9
Case Else: GyouNum = 20
End Select
End Function
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset
Dim iGyou As Long, iGyouNew As Long
Dim i As Long
Dim sN As String
Set dic = CreateObject("Scripting.Dictionary")
Set dicOld = CreateObject("Scripting.Dictionary")
Set dicNew = CreateObject("Scripting.Dictionary")
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
i = 0
Do While (Not rs.EOF)
iGyouNew = GyouNum(rs("よみ"))
If (i = 0) Then iGyou = iGyouNew
i = i + 1
If (iGyou <> iGyouNew) Then i = i + 1
iGyou = iGyouNew
If (i > iM6Count) Then Exit Do
sN = "ck" & i
dic.Item(rs("選手ID").Value) = sN
Select Case True
Case sSrc Like "[あ-お]*": GyouNum = 0
Case sSrc Like "[か-ご]*": GyouNum = 1
Case sSrc Like "[さ-ぞ]*": GyouNum = 2
Case sSrc Like "[た-ど]*": GyouNum = 3
Case sSrc Like "[な-の]*": GyouNum = 4
Case sSrc Like "[は-ぽ]*": GyouNum = 5
Case sSrc Like "[ま-も]*": GyouNum = 6
Case sSrc Like "[や-よ]*": GyouNum = 7
Case sSrc Like "[ら-ろ]*": GyouNum = 8
Case sSrc Like "[わ-ん]*": GyouNum = 9
Case Else: GyouNum = 20
End Select
End Function
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset
Dim iGyou As Long, iGyouNew As Long
Dim i As Long
Dim sN As String
Set dic = CreateObject("Scripting.Dictionary")
Set dicOld = CreateObject("Scripting.Dictionary")
Set dicNew = CreateObject("Scripting.Dictionary")
sSql = "SELECT * FROM T選手 ORDER BY よみ;"
Set rs = CurrentDb.OpenRecordset(sSql)
i = 0
Do While (Not rs.EOF)
iGyouNew = GyouNum(rs("よみ"))
If (i = 0) Then iGyou = iGyouNew
i = i + 1
If (iGyou <> iGyouNew) Then i = i + 1
iGyou = iGyouNew
If (i > iM6Count) Then Exit Do
sN = "ck" & i
dic.Item(rs("選手ID").Value) = sN
また、チェックボックスをクリアする以下記述2か所を変更します。
For i = 1 To iM6Count
With Me("ck" & i)
If (Not .Visible) Then Exit For
.Value = False
.Controls(0).BackStyle = 0
End With
Next
をWith Me("ck" & i)
If (Not .Visible) Then Exit For
.Value = False
.Controls(0).BackStyle = 0
End With
Next
Dim v As Variant
For Each v In dic.Items
With Me(v)
.Value = False
.Controls(0).BackStyle = 0
End With
Next
に。For Each v In dic.Items
With Me(v)
.Value = False
.Controls(0).BackStyle = 0
End With
Next
これは、チェックボックスは連続して Visible = True になっているわけではないので・・・・
(フォーム「F6M」でも、下側記述で OK です)
8)単票にタブコントロール配置(F8M)(タブページで、あ行、か行・・・)
実際に操作するのは、多数の非連結チェックボックス

このフォームでは、多数のチェックボックスをタブコントロールを使って、
あ行、か行・・・・をタブページで分けて表示しましょう・・・・というものです。
タブページには、非表示のチェックボックスを各30個配置しておきます。
(表示する時に使う分だけ可視に変更するのは、今まで通りです)
タブページ0には、標題「あ」、チェックボックス「ck1」〜「ck30」
タブページ1には、標題「か」、チェックボックス「ck101」〜「ck130」
・・・・
タブページ9には、標題「わ」、チェックボックス「ck901」〜「ck930」
という規則を設けます。
また、今回はチェックボックスは横から採番しましょう・・・・
フォーム「F8M_BASE」(フォーム「F6M_BASE」をコピー)を用意し、
標準モジュール「M8Make」に用意した M8MakeProc を実行すると雰囲気フォームが出来上がります。
それをきれいに配置し直して保存します。
Private Const sFname As String = "F8M_BASE"
Private Const sFnew As String = "F8M"
Private Const IPX As Long = 567
Private Const iColCount As Long = 3
Public Const iM8Count As Long = 30
Public Sub M8MakeProc()
Dim i As Long, j As Long
Dim sN As String, sNc As String
Dim iRow As Long, iCol As Long
Dim ctl As Control
Dim sAry() As String
Const sPageCaption As String = "あ,か,さ,た,な,は,ま,や,ら,わ"
On Error Resume Next
sN = sFname & "_"
DoCmd.DeleteObject acForm, sN
DoCmd.CopyObject , sN, acForm, sFname
DoCmd.OpenForm sN, acDesign
With CreateControl(sN, acTabCtl, acDetail)
.Name = "tb0"
sAry = Split(sPageCaption, ",")
While (.Controls.Count <= UBound(sAry))
Call CreateControl(sN, acPage, acDetail, .Name)
Wend
For i = UBound(sAry) To 0 Step -1
With .Controls(i)
.Name = "tbp" & i
.Caption = sAry(i)
iRow = IPX * 1.5
iCol = IPX * 0.5
For j = 0 To iM8Count - 1
With CreateControl(sN, acCheckBox, acDetail, .Name)
sNc = "ck" & i * 100 + j + 1
.Name = sNc
.Top = (j \ iColCount) * IPX * 0.5 + iRow
.Left = (j Mod iColCount) * IPX * 5 + iCol
.Width = IPX * 0.4
.Height = IPX * 0.4
.DefaultValue = "0"
.TabStop = False
.Visible = False
End With
With CreateControl(sN, acLabel, acDetail, sNc)
.Top = (j \ iColCount) * IPX * 0.5 + iRow
.Left = (j Mod iColCount) * IPX * 5 + (IPX * 0.42) + iCol
.Width = IPX * 2.6
.Height = IPX * 0.42
.BorderStyle = 1
.BorderWidth = 1
.BorderColor = RGB(0, 0, 0)
.BackStyle = 0
.BackColor = RGB(255, 240, 240)
End With
Next
End With
Next
.Top = IPX * 1.5
.Left = IPX * 0.5
.Width = IPX * 14
.Height = IPX * 6
End With
DoCmd.Close acForm, sN, acSaveYes
DoCmd.DeleteObject acForm, sFnew
DoCmd.Rename sFnew, acForm, sN
DoCmd.OpenForm sFnew, acDesign
End Sub
Private Const sFnew As String = "F8M"
Private Const IPX As Long = 567
Private Const iColCount As Long = 3
Public Const iM8Count As Long = 30
Public Sub M8MakeProc()
Dim i As Long, j As Long
Dim sN As String, sNc As String
Dim iRow As Long, iCol As Long
Dim ctl As Control
Dim sAry() As String
Const sPageCaption As String = "あ,か,さ,た,な,は,ま,や,ら,わ"
On Error Resume Next
sN = sFname & "_"
DoCmd.DeleteObject acForm, sN
DoCmd.CopyObject , sN, acForm, sFname
DoCmd.OpenForm sN, acDesign
With CreateControl(sN, acTabCtl, acDetail)
.Name = "tb0"
sAry = Split(sPageCaption, ",")
While (.Controls.Count <= UBound(sAry))
Call CreateControl(sN, acPage, acDetail, .Name)
Wend
For i = UBound(sAry) To 0 Step -1
With .Controls(i)
.Name = "tbp" & i
.Caption = sAry(i)
iRow = IPX * 1.5
iCol = IPX * 0.5
For j = 0 To iM8Count - 1
With CreateControl(sN, acCheckBox, acDetail, .Name)
sNc = "ck" & i * 100 + j + 1
.Name = sNc
.Top = (j \ iColCount) * IPX * 0.5 + iRow
.Left = (j Mod iColCount) * IPX * 5 + iCol
.Width = IPX * 0.4
.Height = IPX * 0.4
.DefaultValue = "0"
.TabStop = False
.Visible = False
End With
With CreateControl(sN, acLabel, acDetail, sNc)
.Top = (j \ iColCount) * IPX * 0.5 + iRow
.Left = (j Mod iColCount) * IPX * 5 + (IPX * 0.42) + iCol
.Width = IPX * 2.6
.Height = IPX * 0.42
.BorderStyle = 1
.BorderWidth = 1
.BorderColor = RGB(0, 0, 0)
.BackStyle = 0
.BackColor = RGB(255, 240, 240)
End With
Next
End With
Next
.Top = IPX * 1.5
.Left = IPX * 0.5
.Width = IPX * 14
.Height = IPX * 6
End With
DoCmd.Close acForm, sN, acSaveYes
DoCmd.DeleteObject acForm, sFnew
DoCmd.Rename sFnew, acForm, sN
DoCmd.OpenForm sFnew, acDesign
End Sub
この実行は結構時間がかかります。(初めは、おかしくなったのかと思い止めたりしてました)
タブコントロールの位置決めは難しいですね。(Top が今一つ決まりません)
フォーム「F8M」用のVBAを転記します(同じ標準モジュール内に用意済み)
フォーム「F8M」のVBA記述は「F7M」と大半同じで、以下 Form_Load の黄色部分が異なるだけです。
Do While (Not rs.EOF)
iGyouNew = GyouNum(rs("よみ"))
If (i = 0) Then iGyou = iGyouNew
i = i + 1
If (iGyou <> iGyouNew) Then i = 1
iGyou = iGyouNew
If (iGyou > 10) Then Exit Do
If (i > iM8Count) Then Exit Do
sN = "ck" & iGyou * 100 + i
dic.Item(rs("選手ID").Value) = sN
iGyouNew = GyouNum(rs("よみ"))
If (i = 0) Then iGyou = iGyouNew
i = i + 1
If (iGyou <> iGyouNew) Then i = 1
iGyou = iGyouNew
If (iGyou > 10) Then Exit Do
If (i > iM8Count) Then Exit Do
sN = "ck" & iGyou * 100 + i
dic.Item(rs("選手ID").Value) = sN
2000 のタブコントロール表示は、結構暗いですね。
9)単票に単票サブフォーム(F9M/F9S) (ワークテーブル使用)

VBA は記述したくない・・・ということだったので、テーブル「T出場」を以下の様にすると
チェックボックスを自由に配置できて、操作は楽に(VBA記述なし)なる・・・と言ったものの
これ以降に控えている操作等に支障が出てくると思います。
| フィールド | 型 等々 |
|---|---|
| an | オートナンバ (主キー) |
| 試合ID | 長整数 「T試合」の試合ID |
| ck1 | Yes/No型 ルックアップはチェックボックス |
| ck2 | Yes/No型 |
| ・・・ | |
| ck120 | Yes/No型 |
何故か・・・
「ck1」はAさん用、「ck2」はBさん用 等、テーブル内にない取り決めが必要になります。
じゃ、これに近いテーブルをワークテーブル「Tワーク出場9」として使いましょう。
(・・・って VBA 必要になるんじゃ・・・・・横に置いとくとして)
| フィールド | 型 等々 |
|---|---|
| | |
| 試合ID | 長整数 「T試合」の試合ID |
| ck1 | Yes/No型 ルックアップはチェックボックス |
| ckn1 | テキスト 選手名 |
| ck2 | Yes/No型 |
| ckn2 | テキスト 選手名 |
| ・・・ | |
| ck120 | Yes/No型 |
| ckn120 | テキスト 選手名 |
【追記 5/15】
上記テーブルのフィールド「an」はありません。
「ck1」は XX さん用という取り決めが必要なので、テーブル「T選手」に情報を持ちましょう・・・・
テーブル「T選手」を「T選手9」にコピーして、フィールドを追加します。
| フィールド | 型 等々 |
|---|---|
| 選手ID | オートナンバ (主キー) |
| 選手名 | テキスト |
| よみ | テキスト |
| ckno | 数値 使用する ck の番号 |
標準モジュール「S9Make」にテーブル、連結した単票フォームを作成するVBAを用意しました。
Private Const sS9Table As String = "Tワーク出場9"
Private Const sCK As String = "ck"
Private Const sCKN As String = "ckn"
Private Const sFname As String = "F9S"
Private Const IPX As Long = 567
Private Const iRowCount As Long = 20
Public Const iS9Count As Long = 120
' サブフォーム F9S 参照のワークテーブルを作成する
'
Public Sub MakeWorkTable()
Dim tdf As DAO.TableDef
Dim i As Long
On Error Resume Next
With CurrentDb
.TableDefs.Delete sS9Table
Set tdf = .CreateTableDef(sS9Table)
With tdf
.Fields.Append .CreateField("試合ID", dbLong)
For i = 1 To iS9Count
.Fields.Append .CreateField(sCK & i, dbBoolean)
.Fields.Append .CreateField(sCKN & i, dbText, 20)
Next
End With
.TableDefs.Append tdf
Set tdf = Nothing
.TableDefs.Refresh
With .TableDefs(sS9Table)
For i = 1 To iS9Count
With .Fields(sCK & i)
.DefaultValue = "0"
.Properties.Append .CreateProperty("DisplayControl", dbInteger, acCheckBox)
End With
Next
End With
End With
RefreshDatabaseWindow
End Sub
' サブフォーム F9S 作成
'
Public Sub S9MakeProc()
Dim i As Long
Dim sN As String, sNc As String, sNt As String
Dim iRow As Long, iCol As Long
On Error Resume Next
With CreateForm
sN = .Name
.RecordSource = sS9Table
.RecordSelectors = False
.NavigationButtons = False
.AllowAdditions = False
.AllowDeletions = False
.ScrollBars = 0
iRow = IPX * 0.27
iCol = IPX * 0.27
For i = 0 To iS9Count - 1
sNc = sCK & i + 1
sNt = sCKN & i + 1
With CreateControl(sN, acCheckBox, acDetail, , sNc)
.Name = sNc
.Top = (i Mod iRowCount) * IPX * 0.55 + iRow
.Left = (i \ iRowCount) * IPX * 3 + iCol
.Width = IPX * 0.4
.Height = IPX * 0.4
.DefaultValue = "0"
.TabStop = False
.Visible = False
End With
With CreateControl(sN, acTextBox, acDetail, , sNt)
.Name = sNt
.Top = (i Mod iRowCount) * IPX * 0.55 + iRow
.Left = (i \ iRowCount) * IPX * 3 + (IPX * 0.42) + iCol
.Width = IPX * 2
.Height = IPX * 0.45
.Locked = True
.TabStop = False
.Visible = False
.BackStyle = 1
.BackColor = RGB(255, 255, 255)
End With
Next
End With
DoCmd.Close acForm, sN, acSaveYes
DoCmd.DeleteObject acForm, sFname
DoCmd.Rename sFname, acForm, sN
End Sub
Private Const sCK As String = "ck"
Private Const sCKN As String = "ckn"
Private Const sFname As String = "F9S"
Private Const IPX As Long = 567
Private Const iRowCount As Long = 20
Public Const iS9Count As Long = 120
' サブフォーム F9S 参照のワークテーブルを作成する
'
Public Sub MakeWorkTable()
Dim tdf As DAO.TableDef
Dim i As Long
On Error Resume Next
With CurrentDb
.TableDefs.Delete sS9Table
Set tdf = .CreateTableDef(sS9Table)
With tdf
.Fields.Append .CreateField("試合ID", dbLong)
For i = 1 To iS9Count
.Fields.Append .CreateField(sCK & i, dbBoolean)
.Fields.Append .CreateField(sCKN & i, dbText, 20)
Next
End With
.TableDefs.Append tdf
Set tdf = Nothing
.TableDefs.Refresh
With .TableDefs(sS9Table)
For i = 1 To iS9Count
With .Fields(sCK & i)
.DefaultValue = "0"
.Properties.Append .CreateProperty("DisplayControl", dbInteger, acCheckBox)
End With
Next
End With
End With
RefreshDatabaseWindow
End Sub
' サブフォーム F9S 作成
'
Public Sub S9MakeProc()
Dim i As Long
Dim sN As String, sNc As String, sNt As String
Dim iRow As Long, iCol As Long
On Error Resume Next
With CreateForm
sN = .Name
.RecordSource = sS9Table
.RecordSelectors = False
.NavigationButtons = False
.AllowAdditions = False
.AllowDeletions = False
.ScrollBars = 0
iRow = IPX * 0.27
iCol = IPX * 0.27
For i = 0 To iS9Count - 1
sNc = sCK & i + 1
sNt = sCKN & i + 1
With CreateControl(sN, acCheckBox, acDetail, , sNc)
.Name = sNc
.Top = (i Mod iRowCount) * IPX * 0.55 + iRow
.Left = (i \ iRowCount) * IPX * 3 + iCol
.Width = IPX * 0.4
.Height = IPX * 0.4
.DefaultValue = "0"
.TabStop = False
.Visible = False
End With
With CreateControl(sN, acTextBox, acDetail, , sNt)
.Name = sNt
.Top = (i Mod iRowCount) * IPX * 0.55 + iRow
.Left = (i \ iRowCount) * IPX * 3 + (IPX * 0.42) + iCol
.Width = IPX * 2
.Height = IPX * 0.45
.Locked = True
.TabStop = False
.Visible = False
.BackStyle = 1
.BackColor = RGB(255, 255, 255)
End With
Next
End With
DoCmd.Close acForm, sN, acSaveYes
DoCmd.DeleteObject acForm, sFname
DoCmd.Rename sFname, acForm, sN
End Sub
ここで出来上がった「F9S」に以下を記述します。
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Dim WithEvents frm As Form
Dim dic As Object
Private Function TxtEnter(iNum As Long)
With Me("ck" & iNum)
.SetFocus
If (Me.試合ID <> 0) Then .Value = Not .Value
End With
End Function
Private Sub Form_Open(Cancel As Integer)
Dim sSql As String
Dim rs As DAO.Recordset, rsFrom As DAO.Recordset
Dim i As Long
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
frm.OnCurrent = EVENT_PROCEDURE
frm.AfterUpdate = EVENT_PROCEDURE
Set dic = CreateObject("Scripting.Dictionary")
sSql = "DELETE * FROM Tワーク出場9;"
CurrentDb.Execute sSql
Set rs = CurrentDb.OpenRecordset("Tワーク出場9")
rs.AddNew
sSql = "SELECT * FROM T選手9 WHERE ckno > 0 AND ckno <= " & iS9Count & ";"
Set rsFrom = CurrentDb.OpenRecordset(sSql)
While (Not rsFrom.EOF)
i = rsFrom("ckno")
dic.Item(rsFrom("選手ID").Value) = "ck" & i
Me("ck" & i).Visible = True
With Me("ckn" & i)
With .FormatConditions
.Delete
With .Add(acExpression, , "[ck" & i & "]=True")
.BackColor = RGB(255, 240, 240)
End With
End With
.OnEnter = "=TxtEnter(" & i & ")"
.Visible = True
End With
rs("ckn" & i) = rsFrom("選手名")
rsFrom.MoveNext
Wend
rsFrom.Close
Set rsFrom = Nothing
rs.Update
rs.Close
Set rs = Nothing
End Sub
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset, rsFrom As DAO.Recordset
Dim i As Long
Dim id As Long
Set rs = CurrentDb.OpenRecordset("Tワーク出場9")
rs.Edit
For i = 1 To iS9Count
rs("ck" & i) = False
Next
id = Nz(frm.試合ID)
rs("試合ID") = id
If (id <> 0) Then
sSql = "SELECT ckno FROM T出場 INNER JOIN T選手9 " _
& "ON T出場.選手ID = T選手9.選手ID " _
& "WHERE T出場.試合ID = " & id & ";"
Set rsFrom = CurrentDb.OpenRecordset(sSql)
While (Not rsFrom.EOF)
i = rsFrom(0)
If (i > 0 And i <= iS9Count) Then rs("ck" & i) = True
rsFrom.MoveNext
Wend
rsFrom.Close
Set rsFrom = Nothing
End If
rs.Update
rs.Close
Set rs = Nothing
Me.Requery
End Sub
Private Sub frm_AfterUpdate()
Call frm_Current
End Sub
Private Sub Form_Dirty(Cancel As Integer)
If (Me.試合ID = 0) Then Cancel = True
End Sub
Private Sub Form_AfterUpdate()
Dim sSql As String
Dim v As Variant
Dim i As Long
sSql = "DELETE * FROM T出場 WHERE 試合ID = " & Me.試合ID & ";"
CurrentDb.Execute sSql
For Each v In dic.Keys
With Me(dic.Item(v))
If (.Value) Then
sSql = "INSERT INTO T出場(試合ID, 選手ID) VALUES (" _
& Me.試合ID & "," & v & ");"
CurrentDb.Execute sSql
End If
End With
Next
End Sub
Private Sub Form_Close()
Set frm = Nothing
End Sub
Dim WithEvents frm As Form
Dim dic As Object
Private Function TxtEnter(iNum As Long)
With Me("ck" & iNum)
.SetFocus
If (Me.試合ID <> 0) Then .Value = Not .Value
End With
End Function
Private Sub Form_Open(Cancel As Integer)
Dim sSql As String
Dim rs As DAO.Recordset, rsFrom As DAO.Recordset
Dim i As Long
On Error Resume Next
Set frm = Me.Parent
If (frm Is Nothing) Then
Cancel = True
Exit Sub
End If
frm.OnCurrent = EVENT_PROCEDURE
frm.AfterUpdate = EVENT_PROCEDURE
Set dic = CreateObject("Scripting.Dictionary")
sSql = "DELETE * FROM Tワーク出場9;"
CurrentDb.Execute sSql
Set rs = CurrentDb.OpenRecordset("Tワーク出場9")
rs.AddNew
sSql = "SELECT * FROM T選手9 WHERE ckno > 0 AND ckno <= " & iS9Count & ";"
Set rsFrom = CurrentDb.OpenRecordset(sSql)
While (Not rsFrom.EOF)
i = rsFrom("ckno")
dic.Item(rsFrom("選手ID").Value) = "ck" & i
Me("ck" & i).Visible = True
With Me("ckn" & i)
With .FormatConditions
.Delete
With .Add(acExpression, , "[ck" & i & "]=True")
.BackColor = RGB(255, 240, 240)
End With
End With
.OnEnter = "=TxtEnter(" & i & ")"
.Visible = True
End With
rs("ckn" & i) = rsFrom("選手名")
rsFrom.MoveNext
Wend
rsFrom.Close
Set rsFrom = Nothing
rs.Update
rs.Close
Set rs = Nothing
End Sub
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset, rsFrom As DAO.Recordset
Dim i As Long
Dim id As Long
Set rs = CurrentDb.OpenRecordset("Tワーク出場9")
rs.Edit
For i = 1 To iS9Count
rs("ck" & i) = False
Next
id = Nz(frm.試合ID)
rs("試合ID") = id
If (id <> 0) Then
sSql = "SELECT ckno FROM T出場 INNER JOIN T選手9 " _
& "ON T出場.選手ID = T選手9.選手ID " _
& "WHERE T出場.試合ID = " & id & ";"
Set rsFrom = CurrentDb.OpenRecordset(sSql)
While (Not rsFrom.EOF)
i = rsFrom(0)
If (i > 0 And i <= iS9Count) Then rs("ck" & i) = True
rsFrom.MoveNext
Wend
rsFrom.Close
Set rsFrom = Nothing
End If
rs.Update
rs.Close
Set rs = Nothing
Me.Requery
End Sub
Private Sub frm_AfterUpdate()
Call frm_Current
End Sub
Private Sub Form_Dirty(Cancel As Integer)
If (Me.試合ID = 0) Then Cancel = True
End Sub
Private Sub Form_AfterUpdate()
Dim sSql As String
Dim v As Variant
Dim i As Long
sSql = "DELETE * FROM T出場 WHERE 試合ID = " & Me.試合ID & ";"
CurrentDb.Execute sSql
For Each v In dic.Keys
With Me(dic.Item(v))
If (.Value) Then
sSql = "INSERT INTO T出場(試合ID, 選手ID) VALUES (" _
& Me.試合ID & "," & v & ");"
CurrentDb.Execute sSql
End If
End With
Next
End Sub
Private Sub Form_Close()
Set frm = Nothing
End Sub
メインとなるフォーム「F9M」はフォーム「F6M_BASE」をコピーしたもの。
そこに、この「F9S」をドラッグ&ドロップしてサブフォームとして組み込み、
リンク親/子フィールドは削除(空欄に)します。
どの選手が、どの「ck」を使うか、テーブル「T選手9」の「ckno」を変更しない限り、
その選手はその番号を使う事になるので、連結されたチェックボックス/テキストボックスを
(ペアにして)自由に移動配置できます。
なお、このワークテーブル「Tワーク出場9」のレコードは1件だけ。
それを UPDATE で使い回しします。
(処理対象でない試合のものに対して、レコードをいじりたくないので)
連結にしてみたものの、何か遠まわりしているような・・・・していないような・・・
でも、ガ〜〜ってチェックして、それを取り消すのは楽かな。
メイン/サブの構成になったので、「登録/修正」用のボタンはなし。
今までのフォームと表示が異なっていますが、気がつかれたでしょうか。
今までのフォームの表示順は、「よみ」順になっていましたが、
このフォームでは選手の登録順(ckno の順)になっています。
(配置を移動していなかったので、そう見えたという事だけですけど)
そうそう
このフォームだったと思うけど、Form_Open / Form_Load が連続して呼ばれない・・・
当初 Form_Open / Form_Load は以下のような感じ
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset, rsFrom As DAO.Recordset
Dim i As Long
Set frm = Me.Parent
frm.OnCurrent = EVENT_PROCEDURE
frm.AfterUpdate = EVENT_PROCEDURE
Set dic = CreateObject("Scripting.Dictionary")
・・・・
Form_Open でやっているのは、過去記事にもいろいろと書いてましたが、On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_Load()
Dim sSql As String
Dim rs As DAO.Recordset, rsFrom As DAO.Recordset
Dim i As Long
Set frm = Me.Parent
frm.OnCurrent = EVENT_PROCEDURE
frm.AfterUpdate = EVENT_PROCEDURE
Set dic = CreateObject("Scripting.Dictionary")
・・・・
サブフォームとして起動されていなかったら表示しない・・・・
でも、この後の
Private Sub frm_Current()
Dim sSql As String
Dim rs As DAO.Recordset, rsFrom As DAO.Recordset
Dim i As Long
Dim id As Long
Set rs = CurrentDb.OpenRecordset("Tワーク出場9")
rs.Edit ' ★★★1
For i = 1 To iS9Count
rs("ck" & i) = False
Next
id = Nz(frm.試合ID) ' ★★★2
rs("試合ID") = id
・・・・
のDim sSql As String
Dim rs As DAO.Recordset, rsFrom As DAO.Recordset
Dim i As Long
Dim id As Long
Set rs = CurrentDb.OpenRecordset("Tワーク出場9")
rs.Edit ' ★★★1
For i = 1 To iS9Count
rs("ck" & i) = False
Next
id = Nz(frm.試合ID) ' ★★★2
rs("試合ID") = id
・・・・
★★★1 部分だったかで「カレントレコードがない」エラー(だったか)
★★★2 部分だったかで frm がどうたらだったか・・・
で、2000 の方でチョッといじっていると、動いてみたり・・・
同じVBA記述構成の他のフォームは動いていたり・・・・
なので、サブフォームになる Form_Open / Form_Load は Open 1つに(全部書き直し)
【追記 5/15】
ここで示した箇所の例は嘘ですね。
Set frm = Me.Parent
frm.OnCurrent = EVENT_PROCEDURE
されていないと、frm_Current は動きませんよね・・・・frm.OnCurrent = EVENT_PROCEDURE
でも、どこかのフォームでなっていたんですよ・・・・
ま、私の中での出来事と言う事で・・・・・
注意事項)
・メイン/サブの構成では、サブ側でメインのイベントを受け取る設定をするので
メイン側フォームのプロパティ「コード保持」は「はい」としておきます。
(メイン側に VBA 記述がなくても)
(F2M/F2S1/F2S2 では、「T試合」を表示している F2S1 が対象)
・フォーム「F6M」以降のフォームで、選手を割付ける際、割り付けきらなかったとか・・・・
以降の処理でエラーになる可能性・・・大・大・大
どの方法が良いのだろう・・・・
| サンプルは以下 | ||||||||||||
| ||||||||||||
| ※ ファイルは zip 形式 | ||||||||||||
| ※ 2007 以外は、2007 保存時に変換 & 各バージョンで動作確認 & 最適化 |
≪--- 続きを閉じちゃえ
2012/05/14
Category: サンプルかな
Excel VBA をやってみた その3(合計値検索)
前の記事で完敗したものを、Excel で確認できるようにしたものです。
前の記事と言うと 再帰処理にはまる(その4 乾杯!!) になるのですが・・・・
ここで、問題を再度
テーブル「T1」があったとします。
ここで、このデータのすべての組み合わせの中から、指定した合計の組み合わせを求めなさい。
例えば、合計が、1574 になる組み合わせは、
3, 5
2, 5, 12, 14
など
| ID | F1 |
|---|---|
| 1 | 381 |
| 2 | 650 |
| 3 | 809 |
| 4 | 870 |
| 5 | 765 |
| 6 | 777 |
| 7 | 838 |
| 8 | 806 |
| 9 | 133 |
| 10 | 397 |
| 11 | 62 |
| 12 | 132 |
| 13 | 415 |
| 14 | 27 |
| 15 | 993 |
| 16 | 121 |
| 17 | 348 |
| 18 | 768 |
| 19 | 394 |
| 20 | 297 |
ここで、このデータのすべての組み合わせの中から、指定した合計の組み合わせを求めなさい。
例えば、合計が、1574 になる組み合わせは、
3, 5
2, 5, 12, 14
など
Excel でやろうとした時には、
・テーブルは関係ないし、
・ID なんて、そもそもいらない(値のみの羅列で十分)
等あって、以下の様なシートを考えてみました。

つまり、
・A列には値群
・何個使って求めるか・・・・(C1 で指定・・・・空白ならできる限り使って)
・B列に求めたい合計値を入力
で、結果は E1 以降に表示するように・・・・・
(横に展開していくのではなく、下方向に展開していくように・・・・)
(何故って・・・・2000 とか列最大数を超える組合せがあったから・・・)
ただ、合計値を入力する「行」がクセモノ( B列に限定 )
例えば、 21行目( B21 )に合計値を入力したら
A21 から前行に向かって数字じゃないものが現れるまで処理対象の値と解釈します。
(A21 数字か → はい → A20 数字か → はい → A19 数字か → ・・・・・・)
数字じゃないとか、空白ならそこで値解釈は終了・・・・ってな具合で
前回触れていませんでしたが、値は > 0 が暗黙的な前提であります。
用意したシートは「T1」「T1改」「T2」「T2改」と「パターン」の5つ
「T1」と「T2」、「T1改」と「T2改」はそれぞれVBA記述は同じもので値の記述内容が
前回 再帰処理にはまる(その4 乾杯!!) の各テーブルの「F1」(値)になっているところです。
処理的には、前回の「Module31」をベースに修正を行っています。
前回は Access のリスト表示に合わせるために、いろいろとソート云々を組み込んでいましたが、
Excel でってことなので・・・・あまり考える必要はないかな・・・・・
「T1」「T2」での表示では、
結果表示は値を昇順に並べ替えて・・・・処理の過程等把握しやすいかな・・・・見やすいかな・・・・
「T1改」「T2改」での表示では、
A列の並び順を尊重して横も同じ順で・・・・
って違いだけです。
なお、実行速度としては、Access でのソート処理等を省くことが出来た(?)ので
1/6 程度に短縮できたような気もします。(最終的な結果表示まで)
続きを読んでみようかな ---≫
シート「T1」「T2」への記述
VBA で記述した内容は一緒で、シート上のデータ(A列)が異なるだけです。

B列が変更された時点(Worksheet_Change)で処理を実行します。
(関数名等、前記事のものをそのまま使ったりしています。詳しくは前記事を参照してください)
シート「T1改」「T2改」への記述
VBA で記述した内容は一緒で、シート上のデータ(A列)が異なるだけです。
また、「T1」「T2」と異なるのは、結果を表示する際の値の並び順だけになります。

B列が変更された時点(Worksheet_Change)で処理を実行します。
(「T1」「T2」記述をベースに異なった部分を黄色表示で)
さほど変更箇所はありません。
なお、「T2」「T2改」で確認する際は、指定する値に注意してください。
合計値 4500 を指定すると、8分弱で 39483 組・・・・・
安易に値を入力すると悲惨な目にあいます・・・・・
4200 の時が 3分チョッとだったから・・・・・・ どんな感じで時間は膨れていくのでしょうか ??
大きな値を指定したい時には、まず、使用個数を小さくしてから実行してみてください。
あ、そうそう
Excel 化にあたって、前回の Access 版より大きく変更した点があって
Dest 部分に格納する方法・・・・・・
Dest には Src の配列番号のみを格納するように変更しました。
現在解釈中のものを示すことに大差はなく、なんか軽そうだったので・・・・
≪--- 続きを閉じちゃえ
シート「T1」「T2」への記述
VBA で記述した内容は一緒で、シート上のデータ(A列)が異なるだけです。

B列が変更された時点(Worksheet_Change)で処理を実行します。
(関数名等、前記事のものをそのまま使ったりしています。詳しくは前記事を参照してください)
Private Type AryData
F1 As Long
iCol As Long
iRow As Long
End Type
Private Type PosData
fx_pos As Long
mv_Apos As Long
mv_Bpos As Long
End Type
Dim iRowBase As Long
Dim iColBase As Long
Dim iMvRow As Long
Dim iMvCol As Long
Private Const sMatch As String = "○"
Private Function RecRead(iRow As Long, iCol As Long, tArySrc() As AryData) As Boolean
Dim tTmp As AryData
Dim i As Long, j As Long
Dim bChg As Boolean
RecRead = False
j = -1
ReDim tArySrc(0)
For i = iRow To 1 Step -1
If (IsEmpty(Cells(i, iCol))) Then Exit For
If (Not IsNumeric(Cells(i, iCol))) Then Exit For
j = j + 1
If (j <> 0) Then ReDim Preserve tArySrc(j)
tArySrc(j).F1 = Cells(i, iCol)
tArySrc(j).iRow = i
tArySrc(j).iCol = iCol
Next
If (j < 0) Then Exit Function
Do
bChg = False
For i = 0 To j - 1
If (tArySrc(i).F1 > tArySrc(i + 1).F1) Then
tTmp = tArySrc(i)
tArySrc(i) = tArySrc(i + 1)
tArySrc(i + 1) = tTmp
bChg = True
ElseIf (tArySrc(i).F1 = tArySrc(i + 1).F1) Then
If (tArySrc(i).iRow > tArySrc(i + 1).iRow) Then
tTmp = tArySrc(i)
tArySrc(i) = tArySrc(i + 1)
tArySrc(i + 1) = tTmp
bChg = True
End If
End If
Next
Loop While (bChg)
RecRead = True
End Function
Private Sub ShowRowBase(tArySrc() As AryData)
Dim sS As String
Dim i As Long
Cells(iRowBase, iColBase) = "No"
Cells(iRowBase, iColBase + 1) = "個数"
For i = 0 To UBound(tArySrc)
sS = "=R[" & tArySrc(i).iRow - iRowBase & "]" _
& "C[" & tArySrc(i).iCol - (iColBase + i + 2) & "]"
Cells(iRowBase, iColBase + i + 2).FormulaR1C1 = sS
' Cells(iRowBase, iColBase + i + 2) = tArySrc(i).F1
Next
With Range(Cells(iRowBase, iColBase + 2), Cells(iRowBase, iColBase + i + 1))
With .Interior
.Pattern = xlSolid
.ColorIndex = 15
End With
End With
iMvCol = iColBase + i + 1
End Sub
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) <> iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
Private Sub ReCallSum(iNst As Long, iNum As Long _
, tPosSrc As PosData, tArySrc() As AryData, iAryDest() As Long)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
If (iNst < 1) Then Exit Sub
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
iAryDest(.fx_pos) = .mv_Apos
If (iNumNew = 0) Then
If (iRowBase = iMvRow) Then Call ShowRowBase(tArySrc)
iMvRow = iMvRow + 1
Cells(iMvRow, iColBase) = iMvRow - iRowBase
For i = 0 To .fx_pos
Cells(iMvRow, iColBase + iAryDest(i) + 2) = sMatch
Next
Cells(iMvRow, iColBase + 1).FormulaR1C1 = _
"=COUNTIF(RC[1]:RC[" & iMvCol - (iColBase + 1) & "],""" & sMatch & """)"
ElseIf (iNst > 1) Then
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNst - 1, iNumNew, tPosToSrc, tArySrc, iAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Private Sub SumSearch(iNst As Long, iRow As Long, iCol As Long, iNum As Long)
Dim tArySrc() As AryData
Dim iAryDest() As Long
Dim tPosSrc As PosData
Dim i As Long
If (Not RecRead(iRow, iCol, tArySrc)) Then Exit Sub
ReDim iAryDest(UBound(tArySrc))
With tPosSrc
.fx_pos = -1
.mv_Apos = 0
.mv_Bpos = UBound(tArySrc)
End With
Call ReCallSum(iNst, iNum, tPosSrc, tArySrc, iAryDest)
If (iRowBase <> iMvRow) Then
For i = iRowBase + 1 To iMvRow
If ((i Mod 2) = 0) Then
With Range(Cells(i, iColBase), Cells(i, iMvCol)).Interior
.Pattern = xlSolid
.ColorIndex = 36
End With
End If
Next
With Range(Cells(iRowBase, iColBase), Cells(iMvRow, iMvCol))
With .Borders
.LineStyle = xlContinuous
.Weight = xlThin
End With
.HorizontalAlignment = xlCenter
.EntireColumn.AutoFit
End With
End If
End Sub
Private Sub SheetInit(iRow As Long, iCol As Long)
Dim i As Long, j As Long, k As Long
i = iRow - 1
If (i < 1) Then i = 1
j = iCol - 1
If (j < 1) Then j = 1
With Cells.SpecialCells(xlCellTypeLastCell)
k = .Column
If (k < iCol) Then k = iCol
Range(Cells(i, j), Cells(.Row, k)).Clear
End With
iColBase = iCol
iRowBase = iRow
iMvRow = iRowBase
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim iNum As Long, iNst As Long
On Error Resume Next
If (Target.Count <> 1) Then Exit Sub
If (Target.Column <> 2) Then Exit Sub
iNum = Target
If (iNum <= 0) Then Exit Sub
If (IsEmpty(Target.Offset(0, -1))) Then Exit Sub
iNst = Cells(1, 3)
If (iNst <= 0) Then iNst = Target.Row
Application.EnableEvents = False
Application.ScreenUpdating = False
Call SheetInit(1, 5)
Call SumSearch(iNst, Target.Row, 1, iNum)
Target.Select
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
F1 As Long
iCol As Long
iRow As Long
End Type
Private Type PosData
fx_pos As Long
mv_Apos As Long
mv_Bpos As Long
End Type
Dim iRowBase As Long
Dim iColBase As Long
Dim iMvRow As Long
Dim iMvCol As Long
Private Const sMatch As String = "○"
Private Function RecRead(iRow As Long, iCol As Long, tArySrc() As AryData) As Boolean
Dim tTmp As AryData
Dim i As Long, j As Long
Dim bChg As Boolean
RecRead = False
j = -1
ReDim tArySrc(0)
For i = iRow To 1 Step -1
If (IsEmpty(Cells(i, iCol))) Then Exit For
If (Not IsNumeric(Cells(i, iCol))) Then Exit For
j = j + 1
If (j <> 0) Then ReDim Preserve tArySrc(j)
tArySrc(j).F1 = Cells(i, iCol)
tArySrc(j).iRow = i
tArySrc(j).iCol = iCol
Next
If (j < 0) Then Exit Function
Do
bChg = False
For i = 0 To j - 1
If (tArySrc(i).F1 > tArySrc(i + 1).F1) Then
tTmp = tArySrc(i)
tArySrc(i) = tArySrc(i + 1)
tArySrc(i + 1) = tTmp
bChg = True
ElseIf (tArySrc(i).F1 = tArySrc(i + 1).F1) Then
If (tArySrc(i).iRow > tArySrc(i + 1).iRow) Then
tTmp = tArySrc(i)
tArySrc(i) = tArySrc(i + 1)
tArySrc(i + 1) = tTmp
bChg = True
End If
End If
Next
Loop While (bChg)
RecRead = True
End Function
Private Sub ShowRowBase(tArySrc() As AryData)
Dim sS As String
Dim i As Long
Cells(iRowBase, iColBase) = "No"
Cells(iRowBase, iColBase + 1) = "個数"
For i = 0 To UBound(tArySrc)
sS = "=R[" & tArySrc(i).iRow - iRowBase & "]" _
& "C[" & tArySrc(i).iCol - (iColBase + i + 2) & "]"
Cells(iRowBase, iColBase + i + 2).FormulaR1C1 = sS
' Cells(iRowBase, iColBase + i + 2) = tArySrc(i).F1
Next
With Range(Cells(iRowBase, iColBase + 2), Cells(iRowBase, iColBase + i + 1))
With .Interior
.Pattern = xlSolid
.ColorIndex = 15
End With
End With
iMvCol = iColBase + i + 1
End Sub
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) <> iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
Private Sub ReCallSum(iNst As Long, iNum As Long _
, tPosSrc As PosData, tArySrc() As AryData, iAryDest() As Long)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
If (iNst < 1) Then Exit Sub
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
iAryDest(.fx_pos) = .mv_Apos
If (iNumNew = 0) Then
If (iRowBase = iMvRow) Then Call ShowRowBase(tArySrc)
iMvRow = iMvRow + 1
Cells(iMvRow, iColBase) = iMvRow - iRowBase
For i = 0 To .fx_pos
Cells(iMvRow, iColBase + iAryDest(i) + 2) = sMatch
Next
Cells(iMvRow, iColBase + 1).FormulaR1C1 = _
"=COUNTIF(RC[1]:RC[" & iMvCol - (iColBase + 1) & "],""" & sMatch & """)"
ElseIf (iNst > 1) Then
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNst - 1, iNumNew, tPosToSrc, tArySrc, iAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Private Sub SumSearch(iNst As Long, iRow As Long, iCol As Long, iNum As Long)
Dim tArySrc() As AryData
Dim iAryDest() As Long
Dim tPosSrc As PosData
Dim i As Long
If (Not RecRead(iRow, iCol, tArySrc)) Then Exit Sub
ReDim iAryDest(UBound(tArySrc))
With tPosSrc
.fx_pos = -1
.mv_Apos = 0
.mv_Bpos = UBound(tArySrc)
End With
Call ReCallSum(iNst, iNum, tPosSrc, tArySrc, iAryDest)
If (iRowBase <> iMvRow) Then
For i = iRowBase + 1 To iMvRow
If ((i Mod 2) = 0) Then
With Range(Cells(i, iColBase), Cells(i, iMvCol)).Interior
.Pattern = xlSolid
.ColorIndex = 36
End With
End If
Next
With Range(Cells(iRowBase, iColBase), Cells(iMvRow, iMvCol))
With .Borders
.LineStyle = xlContinuous
.Weight = xlThin
End With
.HorizontalAlignment = xlCenter
.EntireColumn.AutoFit
End With
End If
End Sub
Private Sub SheetInit(iRow As Long, iCol As Long)
Dim i As Long, j As Long, k As Long
i = iRow - 1
If (i < 1) Then i = 1
j = iCol - 1
If (j < 1) Then j = 1
With Cells.SpecialCells(xlCellTypeLastCell)
k = .Column
If (k < iCol) Then k = iCol
Range(Cells(i, j), Cells(.Row, k)).Clear
End With
iColBase = iCol
iRowBase = iRow
iMvRow = iRowBase
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim iNum As Long, iNst As Long
On Error Resume Next
If (Target.Count <> 1) Then Exit Sub
If (Target.Column <> 2) Then Exit Sub
iNum = Target
If (iNum <= 0) Then Exit Sub
If (IsEmpty(Target.Offset(0, -1))) Then Exit Sub
iNst = Cells(1, 3)
If (iNst <= 0) Then iNst = Target.Row
Application.EnableEvents = False
Application.ScreenUpdating = False
Call SheetInit(1, 5)
Call SumSearch(iNst, Target.Row, 1, iNum)
Target.Select
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
シート「T1改」「T2改」への記述
VBA で記述した内容は一緒で、シート上のデータ(A列)が異なるだけです。
また、「T1」「T2」と異なるのは、結果を表示する際の値の並び順だけになります。

B列が変更された時点(Worksheet_Change)で処理を実行します。
(「T1」「T2」記述をベースに異なった部分を黄色表示で)
Private Type AryData
F1 As Long
ID As Long
iCol As Long
iRow As Long
End Type
Private Type PosData
fx_pos As Long
mv_Apos As Long
mv_Bpos As Long
End Type
Dim iRowBase As Long
Dim iColBase As Long
Dim iMvRow As Long
Dim iMvCol As Long
Private Const sMatch As String = "○"
Private Function RecRead(iRow As Long, iCol As Long, tArySrc() As AryData) As Boolean
Dim tTmp As AryData
Dim i As Long, j As Long
Dim bChg As Boolean
RecRead = False
j = -1
ReDim tArySrc(0)
For i = iRow To 1 Step -1
If (IsEmpty(Cells(i, iCol))) Then Exit For
If (Not IsNumeric(Cells(i, iCol))) Then Exit For
j = j + 1
If (j <> 0) Then ReDim Preserve tArySrc(j)
tArySrc(j).F1 = Cells(i, iCol)
tArySrc(j).ID = -j
tArySrc(j).iRow = i
tArySrc(j).iCol = iCol
Next
If (j < 0) Then Exit Function
For i = 0 To j
tArySrc(i).ID = tArySrc(i).ID + j
Next
Do
bChg = False
For i = 0 To j - 1
If (tArySrc(i).F1 > tArySrc(i + 1).F1) Then
tTmp = tArySrc(i)
tArySrc(i) = tArySrc(i + 1)
tArySrc(i + 1) = tTmp
bChg = True
ElseIf (tArySrc(i).F1 = tArySrc(i + 1).F1) Then
If (tArySrc(i).iRow > tArySrc(i + 1).iRow) Then
tTmp = tArySrc(i)
tArySrc(i) = tArySrc(i + 1)
tArySrc(i + 1) = tTmp
bChg = True
End If
End If
Next
Loop While (bChg)
RecRead = True
End Function
Private Sub ShowRowBase(tArySrc() As AryData)
Dim sS As String
Dim i As Long, j As Long
Cells(iRowBase, iColBase) = "No"
Cells(iRowBase, iColBase + 1) = "個数"
For i = 0 To UBound(tArySrc)
j = tArySrc(i).ID
sS = "=R[" & tArySrc(i).iRow - iRowBase & "]" _
& "C[" & tArySrc(i).iCol - (iColBase + j + 2) & "]"
Cells(iRowBase, iColBase + j + 2).FormulaR1C1 = sS
' Cells(iRowBase, iColBase + j + 2) = tArySrc(i).F1
Next
With Range(Cells(iRowBase, iColBase + 2), Cells(iRowBase, iColBase + i + 1))
With .Interior
.Pattern = xlSolid
.ColorIndex = 15
End With
End With
iMvCol = iColBase + i + 1
End Sub
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) <> iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
Private Sub ReCallSum(iNst As Long, iNum As Long _
, tPosSrc As PosData, tArySrc() As AryData, iAryDest() As Long)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
If (iNst < 1) Then Exit Sub
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
iAryDest(.fx_pos) = .mv_Apos
If (iNumNew = 0) Then
If (iRowBase = iMvRow) Then Call ShowRowBase(tArySrc)
iMvRow = iMvRow + 1
Cells(iMvRow, iColBase) = iMvRow - iRowBase
For i = 0 To .fx_pos
Cells(iMvRow, iColBase + tArySrc(iAryDest(i)).ID + 2) = sMatch
Next
Cells(iMvRow, iColBase + 1).FormulaR1C1 = _
"=COUNTIF(RC[1]:RC[" & iMvCol - (iColBase + 1) & "],""" & sMatch & """)"
ElseIf (iNst > 1) Then
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNst - 1, iNumNew, tPosToSrc, tArySrc, iAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Private Sub SumSearch(iNst As Long, iRow As Long, iCol As Long, iNum As Long)
Dim tArySrc() As AryData
Dim iAryDest() As Long
Dim tPosSrc As PosData
Dim i As Long
If (Not RecRead(iRow, iCol, tArySrc)) Then Exit Sub
ReDim iAryDest(UBound(tArySrc))
With tPosSrc
.fx_pos = -1
.mv_Apos = 0
.mv_Bpos = UBound(tArySrc)
End With
Call ReCallSum(iNst, iNum, tPosSrc, tArySrc, iAryDest)
If (iRowBase <> iMvRow) Then
For i = iRowBase + 1 To iMvRow
If ((i Mod 2) = 0) Then
With Range(Cells(i, iColBase), Cells(i, iMvCol)).Interior
.Pattern = xlSolid
.ColorIndex = 36
End With
End If
Next
With Range(Cells(iRowBase, iColBase), Cells(iMvRow, iMvCol))
With .Borders
.LineStyle = xlContinuous
.Weight = xlThin
End With
.HorizontalAlignment = xlCenter
.EntireColumn.AutoFit
End With
End If
End Sub
Private Sub SheetInit(iRow As Long, iCol As Long)
Dim i As Long, j As Long, k As Long
i = iRow - 1
If (i < 1) Then i = 1
j = iCol - 1
If (j < 1) Then j = 1
With Cells.SpecialCells(xlCellTypeLastCell)
k = .Column
If (k < iCol) Then k = iCol
Range(Cells(i, j), Cells(.Row, k)).Clear
End With
iColBase = iCol
iRowBase = iRow
iMvRow = iRowBase
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim iNum As Long, iNst As Long
On Error Resume Next
If (Target.Count <> 1) Then Exit Sub
If (Target.Column <> 2) Then Exit Sub
iNum = Target
If (iNum <= 0) Then Exit Sub
If (IsEmpty(Target.Offset(0, -1))) Then Exit Sub
iNst = Cells(1, 3)
If (iNst <= 0) Then iNst = Target.Row
Application.EnableEvents = False
Application.ScreenUpdating = False
Call SheetInit(1, 5)
Call SumSearch(iNst, Target.Row, 1, iNum)
Target.Select
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
F1 As Long
ID As Long
iCol As Long
iRow As Long
End Type
Private Type PosData
fx_pos As Long
mv_Apos As Long
mv_Bpos As Long
End Type
Dim iRowBase As Long
Dim iColBase As Long
Dim iMvRow As Long
Dim iMvCol As Long
Private Const sMatch As String = "○"
Private Function RecRead(iRow As Long, iCol As Long, tArySrc() As AryData) As Boolean
Dim tTmp As AryData
Dim i As Long, j As Long
Dim bChg As Boolean
RecRead = False
j = -1
ReDim tArySrc(0)
For i = iRow To 1 Step -1
If (IsEmpty(Cells(i, iCol))) Then Exit For
If (Not IsNumeric(Cells(i, iCol))) Then Exit For
j = j + 1
If (j <> 0) Then ReDim Preserve tArySrc(j)
tArySrc(j).F1 = Cells(i, iCol)
tArySrc(j).ID = -j
tArySrc(j).iRow = i
tArySrc(j).iCol = iCol
Next
If (j < 0) Then Exit Function
For i = 0 To j
tArySrc(i).ID = tArySrc(i).ID + j
Next
Do
bChg = False
For i = 0 To j - 1
If (tArySrc(i).F1 > tArySrc(i + 1).F1) Then
tTmp = tArySrc(i)
tArySrc(i) = tArySrc(i + 1)
tArySrc(i + 1) = tTmp
bChg = True
ElseIf (tArySrc(i).F1 = tArySrc(i + 1).F1) Then
If (tArySrc(i).iRow > tArySrc(i + 1).iRow) Then
tTmp = tArySrc(i)
tArySrc(i) = tArySrc(i + 1)
tArySrc(i + 1) = tTmp
bChg = True
End If
End If
Next
Loop While (bChg)
RecRead = True
End Function
Private Sub ShowRowBase(tArySrc() As AryData)
Dim sS As String
Dim i As Long, j As Long
Cells(iRowBase, iColBase) = "No"
Cells(iRowBase, iColBase + 1) = "個数"
For i = 0 To UBound(tArySrc)
j = tArySrc(i).ID
sS = "=R[" & tArySrc(i).iRow - iRowBase & "]" _
& "C[" & tArySrc(i).iCol - (iColBase + j + 2) & "]"
Cells(iRowBase, iColBase + j + 2).FormulaR1C1 = sS
' Cells(iRowBase, iColBase + j + 2) = tArySrc(i).F1
Next
With Range(Cells(iRowBase, iColBase + 2), Cells(iRowBase, iColBase + i + 1))
With .Interior
.Pattern = xlSolid
.ColorIndex = 15
End With
End With
iMvCol = iColBase + i + 1
End Sub
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) <> iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
Private Sub ReCallSum(iNst As Long, iNum As Long _
, tPosSrc As PosData, tArySrc() As AryData, iAryDest() As Long)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
If (iNst < 1) Then Exit Sub
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
iAryDest(.fx_pos) = .mv_Apos
If (iNumNew = 0) Then
If (iRowBase = iMvRow) Then Call ShowRowBase(tArySrc)
iMvRow = iMvRow + 1
Cells(iMvRow, iColBase) = iMvRow - iRowBase
For i = 0 To .fx_pos
Cells(iMvRow, iColBase + tArySrc(iAryDest(i)).ID + 2) = sMatch
Next
Cells(iMvRow, iColBase + 1).FormulaR1C1 = _
"=COUNTIF(RC[1]:RC[" & iMvCol - (iColBase + 1) & "],""" & sMatch & """)"
ElseIf (iNst > 1) Then
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNst - 1, iNumNew, tPosToSrc, tArySrc, iAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Private Sub SumSearch(iNst As Long, iRow As Long, iCol As Long, iNum As Long)
Dim tArySrc() As AryData
Dim iAryDest() As Long
Dim tPosSrc As PosData
Dim i As Long
If (Not RecRead(iRow, iCol, tArySrc)) Then Exit Sub
ReDim iAryDest(UBound(tArySrc))
With tPosSrc
.fx_pos = -1
.mv_Apos = 0
.mv_Bpos = UBound(tArySrc)
End With
Call ReCallSum(iNst, iNum, tPosSrc, tArySrc, iAryDest)
If (iRowBase <> iMvRow) Then
For i = iRowBase + 1 To iMvRow
If ((i Mod 2) = 0) Then
With Range(Cells(i, iColBase), Cells(i, iMvCol)).Interior
.Pattern = xlSolid
.ColorIndex = 36
End With
End If
Next
With Range(Cells(iRowBase, iColBase), Cells(iMvRow, iMvCol))
With .Borders
.LineStyle = xlContinuous
.Weight = xlThin
End With
.HorizontalAlignment = xlCenter
.EntireColumn.AutoFit
End With
End If
End Sub
Private Sub SheetInit(iRow As Long, iCol As Long)
Dim i As Long, j As Long, k As Long
i = iRow - 1
If (i < 1) Then i = 1
j = iCol - 1
If (j < 1) Then j = 1
With Cells.SpecialCells(xlCellTypeLastCell)
k = .Column
If (k < iCol) Then k = iCol
Range(Cells(i, j), Cells(.Row, k)).Clear
End With
iColBase = iCol
iRowBase = iRow
iMvRow = iRowBase
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim iNum As Long, iNst As Long
On Error Resume Next
If (Target.Count <> 1) Then Exit Sub
If (Target.Column <> 2) Then Exit Sub
iNum = Target
If (iNum <= 0) Then Exit Sub
If (IsEmpty(Target.Offset(0, -1))) Then Exit Sub
iNst = Cells(1, 3)
If (iNst <= 0) Then iNst = Target.Row
Application.EnableEvents = False
Application.ScreenUpdating = False
Call SheetInit(1, 5)
Call SumSearch(iNst, Target.Row, 1, iNum)
Target.Select
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
さほど変更箇所はありません。
なお、「T2」「T2改」で確認する際は、指定する値に注意してください。
合計値 4500 を指定すると、8分弱で 39483 組・・・・・
安易に値を入力すると悲惨な目にあいます・・・・・
4200 の時が 3分チョッとだったから・・・・・・ どんな感じで時間は膨れていくのでしょうか ??
大きな値を指定したい時には、まず、使用個数を小さくしてから実行してみてください。
あ、そうそう
Excel 化にあたって、前回の Access 版より大きく変更した点があって
Dest 部分に格納する方法・・・・・・
Dest には Src の配列番号のみを格納するように変更しました。
現在解釈中のものを示すことに大差はなく、なんか軽そうだったので・・・・
| サンプルは以下 | ||||||
| ||||||
| ※ ファイルは zip 形式 | ||||||
| ※ 2000 で作成した Excel ファイル |
≪--- 続きを閉じちゃえ
2012/04/25
Category: やってみる
再帰処理にはまる(その4 乾杯!!)
乾杯?
これは誤変換ですね、「完敗」が正しいものになります。
No989.合計探索問題、みなさんの頭脳に挑戦!!^^;
http://www.accessclub.jp/bbs7/0002/bbs989.html
これを見てから、いろいろと挑戦してみていました。
そこそこは良さそうなんだけど、そもそもの考え方を変えないと超えれないんですかね・・・・
ここで、問題を再度
テーブル「T1」があったとします。
ここで、このデータのすべての組み合わせの中から、指定した合計の組み合わせを求めなさい。
例えば、合計が、1574 になる組み合わせは、
3, 5
2, 5, 12, 14
など
| ID | F1 |
|---|---|
| 1 | 381 |
| 2 | 650 |
| 3 | 809 |
| 4 | 870 |
| 5 | 765 |
| 6 | 777 |
| 7 | 838 |
| 8 | 806 |
| 9 | 133 |
| 10 | 397 |
| 11 | 62 |
| 12 | 132 |
| 13 | 415 |
| 14 | 27 |
| 15 | 993 |
| 16 | 121 |
| 17 | 348 |
| 18 | 768 |
| 19 | 394 |
| 20 | 297 |
ここで、このデータのすべての組み合わせの中から、指定した合計の組み合わせを求めなさい。
例えば、合計が、1574 になる組み合わせは、
3, 5
2, 5, 12, 14
など
最近 moug の Excel VBA でも似たようなものがありましたね。
私が考えてみたのは7つ
・データごと配列に展開し直してやってみる(Module11 〜 Module14)

・データの展開先にインメモリレコードセットを使ってみる(Module21 〜 Module22)

・個数を制限して、その個数内で求めてみる(Module31)

どの方法でも近づけなかったですね・・・・・
そもそもの考え方は1つですから・・・・・・・・・・
これを確認する為のフォームを作っています。

ここでテーブル「T2」としてレコード数を 20 件から 30 件に増やし、重複する「F1」を1つ
テーブル「T2」で確認する時には、検索値をジワジワと増やしていっていてください。
いきなり増やすと「応答なし」になったりします。
用意したフォームは「F11」「F12」「F13」「F14」「F21」「F22」「F31」の7つ&「F11B」1つ
フォーム名「F」以降は、処理を記述した Module の後ろと同じになっています。
(「11B」は「11」のベタ記述版(再帰部分が遅くなっているのか・・・・・さほど変わりなし(記述方法?)))
また、確認フォームでリスト内をダブルクリックした時には確認用として

のフォームが起動されるようになってます。
(個数分を、スクロールしなくてもピッタリと表示できるように工夫)
2007 以降の時には「帳票フォーム」形式(左の図)、その前では「リストボックス」形式(右の図)にて・・・・
( InStr(CurrentProject.Connection.Provider, "ACE") = 0 で起動フォーム切り替え)
( F_SHOW(帳票フォーム) か F_SHOW2003(リスト表示) か)
起動しているバージョンをどう判別するか・・・・これ、わからなかったので・・・・・
処理の速い順となると「F31」≧「F11B」≒「F11」>「F14」>「F13」>「F12」>「F22」>「F21」かな
なお、投稿内にある
hatena さん、YU-TANG さん(クラス および TYPE )の内容を確認できるようにしています。
(コピーして私なりのインデントを付加/テーブル名を変更した形で標準モジュールにて)
(インデントがないと理解しにくいので・・・・って、理解できてませんが・・・・・)
※ このような場合、どのような許可を取れば良いのでしょうか???
同じ環境で起動できるので、違いをみるには良いのかと・・・・・・・・・
続きを読んでみようかな ---≫
確認用テーブルは、設問にあった内容のテーブル「T1」
レコード数を 30 件に増やし、重複の値が1つあるテーブル「T2」
(標準モジュール「T2Make」でいろいろ作成できます)
後述の標準モジュール「Module11」の処理経過を計測格納するテーブル「T3」
(標準モジュール「T3Make」で内容は作成されます)
ただし、1万件のレコードになるのでサンプル上は空(サイズがでかくなるので)
でも、実行すれば作成できるので・・・・ってことに。
※ 私の環境で「T3」の内容(1万件)作成するまで、 25 分弱
(Intel Core(TM)2 Duo CPU T7250 @2.00 GHz メモリ 2.5GB Vista Ultimate SP2 Office2007Pro SP3)
なお、出来上がったテーブル「T3」を Excel 出力する標準モジュール「ToExcel」もあり・・・・
確認用画面はほとんど共通です。

テーブルを選択するオプショングループ「op0」
そのテーブルのレコード数を表示するテキストボックス =DCount("*","T" & [op0])
そのテーブルの「F1」の総計を表示するテキストボックス =DSum("F1","T" & [op0])
表示を選ぶオプショングループ「op1」
検索値を入力するテキストボックス「txt3」
パターン数を表示するテキストボックス =[lst1].[ListCount]
検索したパターンを表示するリストボックス「lst1」
リストボックス「lst1」は、「値集合タイプ」を「値リスト」に設定
検索した結果を VBA で展開して、リストボックスへ表示するようにします。
記述したのは以下で、黄色部分が処理によって異なるだけです。
値検索の実行は、検索値入力部分で「Enter」キーを押下することで行われます。
なので、極力検索値入力テキストボックスにフォーカスを設定するようにしています。
また、「タブストップ」は検索値入力テキストボックス「txt3」のみ「はい」としておきます。
標準モジュールに記述した関数を呼び出し、
その結果をリストボックスへ「値リスト」として設定していきますが、
設定できる文字数制限がある様で、特に 2000 で設定エラーが多く発生します。
その時には、設定のエラーであること+件数のみを表示するようにします。
リストボックス「lst1」をダブルクリックされた時、抽出したものが正しいものなのか・・・・
これを表示するフォームを起動するようにします。
(リストボックスで選ばれているものがあったら・・・・・)
(私はこの判別方法を良くやってましたけど・・・・・ ListIndex の方が良いんでしょうか・・・・)
フォームを起動しますが、起動されたフォームの Form_Open で Cancel = True を返すことがあるので、
エラーの発生を無視するように On Error Resume Next を記述しておきます。
※ ここで新しい発見あり
リストボックスを「値リスト」で、かつ「ダブルクリックでフォーム起動」・・・・・
これ、あまりやったことがなかったのですが、
起動するフォームを「ポップアップ」を「はい」、「作業ウィンドウ固定」を「はい」に設定していても
起動後、リストボックス付近をクリックすると、起動したフォームが裏に隠れる現象が発生・・・・
ダブルクリックの Cancel = True しても変化なし・・・・
いろいろやってみると・・・・
リストボックスの「値リストの編集許可」が「はい」となっていると起きるようです。
「値リストの編集許可」を「いいえ」とすると裏に隠れる様な事はなくなりました。
(2007 にて・・・・・ 2007 で作っていたので他のバージョンでは未確認)
(この対処が良かったのかは・・・・・・・)
起動されるフォーム「F_SHOW」

このフォームでは、
自分が生きて帳票形式で表示するか、
自分を終了してリストボックス表示の「F_SHOW2003」を起動するか処理します。
表示したレコード自体はいじらせたくないので、各テキストボックスの「編集ロック」は「はい」に。
また、追加/更新/削除が出来ない様にしておきます。
Form_Open ではお決まりの、直に起動された時には表示しない様にしておいて・・・
起動元のリストボックス表示が正しいか・・・・
CurrentProject.Connection.Provider 文字列内に "ACE" があるか・・・・
このフォーム「F_SHOW」(帳票形式)では、
レコードセットに ADODBインメモリレコードセットを使うので、過去記事で 2000 で使えなかったこともあり
起動バージョンで切り替えるように・・・・・
2003 をどうしようか考えたけど表示に大差はないよね・・・・・ってことで、上記判別にて・・・・
表示に関しては、表示する件数を取得したら、
「詳細」の高さx件数+α を加味したフォームの高さを再設定します。
また、数字が表示されている部分をダブルクリックされたら、フォームを閉じるように・・・
起動されるフォーム「F_SHOW2003」

フォーム「F_SHOW」を「F_SHOW2003」でコピーし、「既定のビュー」は単票フォームにしておきます。
「詳細」に配置してあったものすべてを削除し、リストボックス「lst1」を配置し直します。
また、「値集合タイプ」を「値リスト」にし、固定幅フォントを指定しておきます。
リストボックス形式で表示する時に起動されます。
ここでも、お決まりのように、直に起動されない様にしておいて・・・・
数値を右詰で作成す時には、 RSet を使用し調整します。
表示する件数によりリストの高さを変更していきますが、
初めに「詳細」部分の高さをリストの高さ以上に設定してから行います。
「詳細」の高さをリストの高さに関係なく30cm とか大きく設定しても、
InsideHeight 設定時、指定した高さ - ヘッダの高さ - フッタの高さ で、
「詳細」の高さが決まるような雰囲気です。
リストボックスの高さ変更時、「詳細」を超えたらエラーになった様な・・・・気がしますが・・・未確認
また、数字が表示されている部分のダブルクリックで、フォームを閉じるように・・・・
ここでは「Module11」「Module21」について記述してみます。
他の記述も似通っているので、興味あればサンプルファイル内を見てください。
Module11

上記、処理イメージに全部書いた様な気もします。
検索値が見つかったら、";" 区切りで、何のデータか先頭に "ID" or "F1" を付加して・・・・
「F1」の値をソートしたものと、「ID」をソートしたものを Dictionary に格納することにしました。
表示した時、ソートされているのが見やすいかな・・・・・ここは妥協できないかなぁ
格納する時に各値を4桁の文字に揃えて・・・・
(なので5桁以上の値の場合、おかしくなるかと思います)
これは、格納先に Dictionary を選んだので、重複があったら削ってくれる・・・・
(F1 の値に重複があった場合、値表示時には重複を削除したもので・・・・)
(ID 表示の場合は重複するものがないので、そのまま表示されると思います)
例)
というデータがあった場合に 100 を求めたいとすると
・F1(値)での表示では
20 80
の1件になると思いますが
・ID での表示では
1(20) 3(80) と 3(80) 4(20)
の2件になります。
こういう事をやりたかった・・・・っていうのもあって Dictionary を選んだ・・・・・とも言えるかなっと
再帰呼出しの心臓部分、以下がどのような感じで動いているのか調べてみました。
(標準モジュール「T3Make」で、調査結果をテーブル「T3」に作成します)
計測した結果は以下の通りとなりました。
検索値 500 ごとの結果は以下になりました。
ただ、私が作成した Module11 でのものなので参考になるのかどうか・・・・
特に「組合せ数」は検証してください。
追記 4/21
--
黄色部分の判別が足りてませんでした。
上記を変更したことで「処理数」「組合せ数」に変化はありませんでしたが「関数呼」は、
最大、「検索値」6732 の 149934 回を 111738 回へと 38196 減らすことが出来ました。
(時間的目安は 0.336 から 0.320 に改善・・・・)
「関数呼」改善差 TOP 10
なお、求める動作自体には影響ないので、サンプルファイルは未修正
--
ちなみに使用個数を限定した場合「Module31」での変更箇所は少なく、以下の黄色部分
(iNst が使用個数を指定するもの)
もちろんパラメータ iNst が増えた分、呼び出し側にも変更はありますが・・・・
Module21

標準モジュール「__Sample確認」に記述しています。
各記述は、「_」で始まる名称で格納しています。
結果はイミディエイトウィンドウに表示されます。
同じ土俵にのるために記述したのが「_kiku」(Module11 を元に修正)
※ いや、こういう考え方した方が良いよ・・・・等、教えてください。
≪--- 続きを閉じちゃえ
確認用テーブル
確認用テーブルは、設問にあった内容のテーブル「T1」
レコード数を 30 件に増やし、重複の値が1つあるテーブル「T2」
(標準モジュール「T2Make」でいろいろ作成できます)
後述の標準モジュール「Module11」の処理経過を計測格納するテーブル「T3」
(標準モジュール「T3Make」で内容は作成されます)
ただし、1万件のレコードになるのでサンプル上は空(サイズがでかくなるので)
でも、実行すれば作成できるので・・・・ってことに。
※ 私の環境で「T3」の内容(1万件)作成するまで、 25 分弱
(Intel Core(TM)2 Duo CPU T7250 @2.00 GHz メモリ 2.5GB Vista Ultimate SP2 Office2007Pro SP3)
なお、出来上がったテーブル「T3」を Excel 出力する標準モジュール「ToExcel」もあり・・・・
確認用画面
確認用画面はほとんど共通です。

テーブルを選択するオプショングループ「op0」
そのテーブルのレコード数を表示するテキストボックス =DCount("*","T" & [op0])
そのテーブルの「F1」の総計を表示するテキストボックス =DSum("F1","T" & [op0])
表示を選ぶオプショングループ「op1」
検索値を入力するテキストボックス「txt3」
パターン数を表示するテキストボックス =[lst1].[ListCount]
検索したパターンを表示するリストボックス「lst1」
リストボックス「lst1」は、「値集合タイプ」を「値リスト」に設定
検索した結果を VBA で展開して、リストボックスへ表示するようにします。
記述したのは以下で、黄色部分が処理によって異なるだけです。
Private Sub Form_Load()
Call btn1_Click
Me.txt3 = 1574
End Sub
Private Sub op0_Click()
Me.Recalc
Me.txt3.SetFocus
End Sub
Private Sub op1_Click()
Me.txt3.SetFocus
End Sub
Private Sub btn1_Click()
Me.lst1 = Null
Me.lst1.RowSource = ""
Me.lst1.Controls(0).Caption = ""
Me.txt3.SetFocus
End Sub
Private Sub txt3_KeyDown(KeyCode As Integer, Shift As Integer)
Dim st As Single
Dim v As Variant
Select Case KeyCode
Case vbKeyReturn
KeyCode = 0
If (Len(Me.txt3.Text) = 0) Then Exit Sub
If (Not IsNumeric(Me.txt3.Text)) Then Exit Sub
Me.btn1.Enabled = False
Me.Repaint
st = Timer
Select Case Me.op1
Case 1: v = Module11.SumSearch("T" & Me.op0, "ID", CLng(Me.txt3.Text))
Case 2: v = Module11.SumSearch("T" & Me.op0, "F1", CLng(Me.txt3.Text))
End Select
st = Timer - st
Me.lst1.Controls(0).Caption = "処理時間 " & Format(st, "0.000 秒")
If (IsEmpty(v)) Then
Me.lst1.RowSource = ""
Else
On Error Resume Next
Me.lst1.RowSource = """" & Join(v, """;""") & """"
If (Err <> 0) Then
Me.lst1.RowSource = "RowSource 設定でエラー 件数(" & UBound(v) + 1 & ")"
End If
End If
Me.lst1 = Null
Me.Recalc
Me.btn1.Enabled = True
Me.txt3.SelStart = 0
Me.txt3.SelLength = Len(Me.txt3.Text)
End Select
End Sub
Private Sub lst1_DblClick(Cancel As Integer)
On Error Resume Next
If (Not IsNull(Me.lst1.Column(0))) Then DoCmd.OpenForm "F_SHOW"
End Sub
Call btn1_Click
Me.txt3 = 1574
End Sub
Private Sub op0_Click()
Me.Recalc
Me.txt3.SetFocus
End Sub
Private Sub op1_Click()
Me.txt3.SetFocus
End Sub
Private Sub btn1_Click()
Me.lst1 = Null
Me.lst1.RowSource = ""
Me.lst1.Controls(0).Caption = ""
Me.txt3.SetFocus
End Sub
Private Sub txt3_KeyDown(KeyCode As Integer, Shift As Integer)
Dim st As Single
Dim v As Variant
Select Case KeyCode
Case vbKeyReturn
KeyCode = 0
If (Len(Me.txt3.Text) = 0) Then Exit Sub
If (Not IsNumeric(Me.txt3.Text)) Then Exit Sub
Me.btn1.Enabled = False
Me.Repaint
st = Timer
Select Case Me.op1
Case 1: v = Module11.SumSearch("T" & Me.op0, "ID", CLng(Me.txt3.Text))
Case 2: v = Module11.SumSearch("T" & Me.op0, "F1", CLng(Me.txt3.Text))
End Select
st = Timer - st
Me.lst1.Controls(0).Caption = "処理時間 " & Format(st, "0.000 秒")
If (IsEmpty(v)) Then
Me.lst1.RowSource = ""
Else
On Error Resume Next
Me.lst1.RowSource = """" & Join(v, """;""") & """"
If (Err <> 0) Then
Me.lst1.RowSource = "RowSource 設定でエラー 件数(" & UBound(v) + 1 & ")"
End If
End If
Me.lst1 = Null
Me.Recalc
Me.btn1.Enabled = True
Me.txt3.SelStart = 0
Me.txt3.SelLength = Len(Me.txt3.Text)
End Select
End Sub
Private Sub lst1_DblClick(Cancel As Integer)
On Error Resume Next
If (Not IsNull(Me.lst1.Column(0))) Then DoCmd.OpenForm "F_SHOW"
End Sub
値検索の実行は、検索値入力部分で「Enter」キーを押下することで行われます。
なので、極力検索値入力テキストボックスにフォーカスを設定するようにしています。
また、「タブストップ」は検索値入力テキストボックス「txt3」のみ「はい」としておきます。
標準モジュールに記述した関数を呼び出し、
その結果をリストボックスへ「値リスト」として設定していきますが、
設定できる文字数制限がある様で、特に 2000 で設定エラーが多く発生します。
その時には、設定のエラーであること+件数のみを表示するようにします。
リストボックス「lst1」をダブルクリックされた時、抽出したものが正しいものなのか・・・・
これを表示するフォームを起動するようにします。
(リストボックスで選ばれているものがあったら・・・・・)
(私はこの判別方法を良くやってましたけど・・・・・ ListIndex の方が良いんでしょうか・・・・)
フォームを起動しますが、起動されたフォームの Form_Open で Cancel = True を返すことがあるので、
エラーの発生を無視するように On Error Resume Next を記述しておきます。
※ ここで新しい発見あり
リストボックスを「値リスト」で、かつ「ダブルクリックでフォーム起動」・・・・・
これ、あまりやったことがなかったのですが、
起動するフォームを「ポップアップ」を「はい」、「作業ウィンドウ固定」を「はい」に設定していても
起動後、リストボックス付近をクリックすると、起動したフォームが裏に隠れる現象が発生・・・・
ダブルクリックの Cancel = True しても変化なし・・・・
いろいろやってみると・・・・
リストボックスの「値リストの編集許可」が「はい」となっていると起きるようです。
「値リストの編集許可」を「いいえ」とすると裏に隠れる様な事はなくなりました。
(2007 にて・・・・・ 2007 で作っていたので他のバージョンでは未確認)
(この対処が良かったのかは・・・・・・・)
起動されるフォーム「F_SHOW」

このフォームでは、
自分が生きて帳票形式で表示するか、
自分を終了してリストボックス表示の「F_SHOW2003」を起動するか処理します。
表示したレコード自体はいじらせたくないので、各テキストボックスの「編集ロック」は「はい」に。
また、追加/更新/削除が出来ない様にしておきます。
Form_Open ではお決まりの、直に起動された時には表示しない様にしておいて・・・
起動元のリストボックス表示が正しいか・・・・
CurrentProject.Connection.Provider 文字列内に "ACE" があるか・・・・
このフォーム「F_SHOW」(帳票形式)では、
レコードセットに ADODBインメモリレコードセットを使うので、過去記事で 2000 で使えなかったこともあり
起動バージョンで切り替えるように・・・・・
2003 をどうしようか考えたけど表示に大差はないよね・・・・・ってことで、上記判別にて・・・・
表示に関しては、表示する件数を取得したら、
「詳細」の高さx件数+α を加味したフォームの高さを再設定します。
また、数字が表示されている部分をダブルクリックされたら、フォームを閉じるように・・・
Dim rs As ADODB.Recordset
Dim sS As String
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Screen.ActiveControl.Name = "") Then
Cancel = True
Else
sS = Screen.ActiveControl
If (sS Like "*[!0-9( )]*") Then Cancel = True
End If
If (Not Cancel) Then
If (InStr(CurrentProject.Connection.Provider, "ACE") = 0) Then
DoCmd.OpenForm "F_SHOW2003"
Cancel = True
End If
End If
End Sub
Private Function ClsFrm()
DoCmd.Close acForm, Me.Name, acSaveNo
End Function
Private Sub Form_Load()
Dim v As Variant
Dim iTotal As Long
Const F1 As String = "F1"
Const F2 As String = "F2"
Const F3 As String = "F3"
Set rs = New ADODB.Recordset
With rs
With .Fields
.Append F1, adInteger
.Append F2, adInteger
.Append F3, adInteger
End With
.LockType = adLockOptimistic
.Open
End With
iTotal = 0
sS = Replace(sS, ")", "")
For Each v In Split(sS, " ")
v = Split(v, "(")
rs.AddNew
rs(0) = iTotal
rs(1) = CLng(v(UBound(v)))
iTotal = iTotal + rs(1)
rs(2) = iTotal
rs.Update
Next
Me.InsideHeight = Me.Section(acHeader).Height _
+ Me.Section(acDetail).Height * rs.RecordCount _
+ Me.Section(acFooter).Height _
+ rs.RecordCount * 3
Set Me.Recordset = rs
Me.txt1.ControlSource = F1
Me.txt2.ControlSource = F2
Me.txt3.ControlSource = F3
Me.txt11 = rs.RecordCount
Me.txt12 = iTotal
Me.txt1.OnDblClick = "=ClsFrm()"
Me.txt2.OnDblClick = "=ClsFrm()"
Me.txt3.OnDblClick = "=ClsFrm()"
Me.txt11.OnDblClick = "=ClsFrm()"
Me.txt12.OnDblClick = "=ClsFrm()"
End Sub
Private Sub Form_Close()
If (Not rs Is Nothing) Then rs.Close
Set rs = Nothing
End Sub
Dim sS As String
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Screen.ActiveControl.Name = "") Then
Cancel = True
Else
sS = Screen.ActiveControl
If (sS Like "*[!0-9( )]*") Then Cancel = True
End If
If (Not Cancel) Then
If (InStr(CurrentProject.Connection.Provider, "ACE") = 0) Then
DoCmd.OpenForm "F_SHOW2003"
Cancel = True
End If
End If
End Sub
Private Function ClsFrm()
DoCmd.Close acForm, Me.Name, acSaveNo
End Function
Private Sub Form_Load()
Dim v As Variant
Dim iTotal As Long
Const F1 As String = "F1"
Const F2 As String = "F2"
Const F3 As String = "F3"
Set rs = New ADODB.Recordset
With rs
With .Fields
.Append F1, adInteger
.Append F2, adInteger
.Append F3, adInteger
End With
.LockType = adLockOptimistic
.Open
End With
iTotal = 0
sS = Replace(sS, ")", "")
For Each v In Split(sS, " ")
v = Split(v, "(")
rs.AddNew
rs(0) = iTotal
rs(1) = CLng(v(UBound(v)))
iTotal = iTotal + rs(1)
rs(2) = iTotal
rs.Update
Next
Me.InsideHeight = Me.Section(acHeader).Height _
+ Me.Section(acDetail).Height * rs.RecordCount _
+ Me.Section(acFooter).Height _
+ rs.RecordCount * 3
Set Me.Recordset = rs
Me.txt1.ControlSource = F1
Me.txt2.ControlSource = F2
Me.txt3.ControlSource = F3
Me.txt11 = rs.RecordCount
Me.txt12 = iTotal
Me.txt1.OnDblClick = "=ClsFrm()"
Me.txt2.OnDblClick = "=ClsFrm()"
Me.txt3.OnDblClick = "=ClsFrm()"
Me.txt11.OnDblClick = "=ClsFrm()"
Me.txt12.OnDblClick = "=ClsFrm()"
End Sub
Private Sub Form_Close()
If (Not rs Is Nothing) Then rs.Close
Set rs = Nothing
End Sub
起動されるフォーム「F_SHOW2003」

フォーム「F_SHOW」を「F_SHOW2003」でコピーし、「既定のビュー」は単票フォームにしておきます。
「詳細」に配置してあったものすべてを削除し、リストボックス「lst1」を配置し直します。
また、「値集合タイプ」を「値リスト」にし、固定幅フォントを指定しておきます。
リストボックス形式で表示する時に起動されます。
ここでも、お決まりのように、直に起動されない様にしておいて・・・・
数値を右詰で作成す時には、 RSet を使用し調整します。
表示する件数によりリストの高さを変更していきますが、
初めに「詳細」部分の高さをリストの高さ以上に設定してから行います。
「詳細」の高さをリストの高さに関係なく30cm とか大きく設定しても、
InsideHeight 設定時、指定した高さ - ヘッダの高さ - フッタの高さ で、
「詳細」の高さが決まるような雰囲気です。
リストボックスの高さ変更時、「詳細」を超えたらエラーになった様な・・・・気がしますが・・・未確認
また、数字が表示されている部分のダブルクリックで、フォームを閉じるように・・・・
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Screen.ActiveControl.Name = "") Then
Cancel = True
End If
End Sub
Private Function ClsFrm()
DoCmd.Close acForm, Me.Name, acSaveNo
End Function
Private Sub Form_Load()
Dim sS As String
Dim vLst As Variant
Dim v As Variant
Dim s As String
Dim sTmp As String
Dim iCnt
Dim iTotal As Long
sS = Screen.ActiveControl
iCnt = 0: iTotal = 0: s = Space(7)
sS = Replace(sS, ")", "")
For Each v In Split(sS, " ")
v = Split(v, "(")
RSet s = iTotal
sTmp = s & " + "
RSet s = CLng(v(UBound(v)))
sTmp = sTmp & s & " = "
iTotal = iTotal + CLng(v(UBound(v)))
RSet s = iTotal
sTmp = sTmp & s
iCnt = iCnt + 1
If (IsEmpty(vLst)) Then
ReDim vLst(0)
Else
ReDim Preserve vLst(UBound(vLst) + 1)
End If
vLst(UBound(vLst)) = sTmp
Next
Me.Section(acDetail).Height = Me.lst1.Top + (iCnt * 242) + 100
Me.lst1.Height = iCnt * 242 + 20
Me.InsideHeight = Me.Section(acHeader).Height _
+ Me.lst1.Top + Me.lst1.Height _
+ Me.Section(acFooter).Height _
+ 20
Me.lst1.RowSource = """" & Join(vLst, """;""") & """"
Me.txt11 = iCnt
Me.txt12 = iTotal
Me.lst1.OnDblClick = "=ClsFrm()"
Me.txt11.OnDblClick = "=ClsFrm()"
Me.txt12.OnDblClick = "=ClsFrm()"
End Sub
On Error Resume Next
If (Screen.ActiveControl.Name = "") Then
Cancel = True
End If
End Sub
Private Function ClsFrm()
DoCmd.Close acForm, Me.Name, acSaveNo
End Function
Private Sub Form_Load()
Dim sS As String
Dim vLst As Variant
Dim v As Variant
Dim s As String
Dim sTmp As String
Dim iCnt
Dim iTotal As Long
sS = Screen.ActiveControl
iCnt = 0: iTotal = 0: s = Space(7)
sS = Replace(sS, ")", "")
For Each v In Split(sS, " ")
v = Split(v, "(")
RSet s = iTotal
sTmp = s & " + "
RSet s = CLng(v(UBound(v)))
sTmp = sTmp & s & " = "
iTotal = iTotal + CLng(v(UBound(v)))
RSet s = iTotal
sTmp = sTmp & s
iCnt = iCnt + 1
If (IsEmpty(vLst)) Then
ReDim vLst(0)
Else
ReDim Preserve vLst(UBound(vLst) + 1)
End If
vLst(UBound(vLst)) = sTmp
Next
Me.Section(acDetail).Height = Me.lst1.Top + (iCnt * 242) + 100
Me.lst1.Height = iCnt * 242 + 20
Me.InsideHeight = Me.Section(acHeader).Height _
+ Me.lst1.Top + Me.lst1.Height _
+ Me.Section(acFooter).Height _
+ 20
Me.lst1.RowSource = """" & Join(vLst, """;""") & """"
Me.txt11 = iCnt
Me.txt12 = iTotal
Me.lst1.OnDblClick = "=ClsFrm()"
Me.txt11.OnDblClick = "=ClsFrm()"
Me.txt12.OnDblClick = "=ClsFrm()"
End Sub
モジュール
ここでは「Module11」「Module21」について記述してみます。
他の記述も似通っているので、興味あればサンプルファイル内を見てください。
Module11

上記、処理イメージに全部書いた様な気もします。
Private Type AryData
F1 As Long
ID As Long
End Type
Private Type PosData
fx_pos As Long
mv_Apos As Long
mv_Bpos As Long
End Type
Private Const FLD_ID As String = "ID"
Private Const FLD_F1 As String = "F1"
Dim dic As Object
Private Function RecRead(sTable As String) As AryData()
Dim rs As New ADODB.Recordset
Dim tArySrc() As AryData
Dim i As Long
ReDim tArySrc(0)
i = -1
rs.Source = "SELECT " & FLD_ID & "," & FLD_F1 & " FROM " & sTable _
& " ORDER BY " & FLD_F1 & "," & FLD_ID & ";"
rs.Open , CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
While (Not rs.EOF)
i = i + 1
If (i <> 0) Then ReDim Preserve tArySrc(i)
tArySrc(i).F1 = rs(FLD_F1)
tArySrc(i).ID = rs(FLD_ID)
rs.MoveNext
Wend
rs.Close
RecRead = tArySrc
End Function
Private Function VarSort(sSel As String, tPosSrc As PosData, tAryDest() As AryData) As Variant
Dim vr As Variant
Dim i As Long
Dim v As Variant
ReDim vr(tPosSrc.fx_pos)
Select Case sSel
Case FLD_ID
For i = 0 To tPosSrc.fx_pos
vr(i) = tAryDest(i).ID
Next
Case FLD_F1
For i = 0 To tPosSrc.fx_pos
vr(i) = tAryDest(i).F1
Next
Case Else
Exit Function
End Select
Do
v = Empty
For i = 0 To UBound(vr) - 1
If (vr(i) > vr(i + 1)) Then
v = vr(i)
vr(i) = vr(i + 1)
vr(i + 1) = v
End If
Next
Loop While (Not IsEmpty(v))
VarSort = vr
End Function
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) > iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
Private Sub ReCallSum(iNum As Long, tPosSrc As PosData, tArySrc() As AryData, tAryDest() As AryData)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
Dim vAry As Variant
Dim v As Variant
Dim sS As String
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
tAryDest(.fx_pos) = tArySrc(.mv_Apos)
If (iNumNew = 0) Then
For Each vAry In Array(FLD_F1, FLD_ID)
sS = ""
For Each v In VarSort(CStr(vAry), tPosWsrc, tAryDest)
sS = sS & ";" & Format(v, "0000")
Next
dic.Item(vAry & sS) = Null
Next
Else
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNumNew, tPosToSrc, tArySrc, tAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Private Function SearchF1(sSel As String, iNum As Long, tArySrc() As AryData) As String
Dim i As Integer
SearchF1 = ""
If (sSel = FLD_F1) Then Exit Function
For i = 0 To UBound(tArySrc)
If (tArySrc(i).ID = iNum) Then
SearchF1 = "(" & tArySrc(i).F1 & ")"
Exit For
End If
Next
End Function
Public Function SumSearch(sTable As String, sSel As String, iNum As Long) As Variant
Dim tArySrc() As AryData, tAryDest() As AryData
Dim tPosSrc As PosData
Dim vAry As Variant, vAryR As Variant
Dim v As Variant
Dim i As Long, j As Long
Dim sS As String
tArySrc = RecRead(sTable)
If (tArySrc(0).ID = 0) Then Exit Function
tAryDest = tArySrc
With tPosSrc
.fx_pos = -1
.mv_Apos = 0
.mv_Bpos = UBound(tArySrc)
End With
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallSum(iNum, tPosSrc, tArySrc, tAryDest)
If (dic.Count > 0) Then
vAry = dic.Keys
Do
v = Empty
For i = 0 To UBound(vAry) - 1
If (vAry(i) > vAry(i + 1)) Then
v = vAry(i)
vAry(i) = vAry(i + 1)
vAry(i + 1) = v
End If
Next
Loop While (Not IsEmpty(v))
For i = 0 To UBound(vAry)
sS = ""
v = Split(vAry(i), ";")
If (v(0) = sSel) Then
For j = 1 To UBound(v)
sS = sS & " " & CLng(v(j)) & SearchF1(sSel, CLng(v(j)), tArySrc)
Next
If (IsEmpty(vAryR)) Then
ReDim vAryR(0)
Else
ReDim Preserve vAryR(UBound(vAryR) + 1)
End If
vAryR(UBound(vAryR)) = Mid(sS, 2)
End If
Next
End If
Set dic = Nothing
SumSearch = vAryR
End Function
Public Sub Sample()
Dim sS As String
Dim st As Single
Dim v As Variant
Dim i As Long
Do While (1)
sS = InputBox("合計値を入力してください", "パターン検索", "1574")
If (Len(sS) = 0) Then Exit Do
If (Not IsNumeric(sS)) Then Exit Do
st = Timer
v = SumSearch("T1", FLD_F1, CLng(sS))
If (IsEmpty(v)) Then
Debug.Print ">>> " & sS & " の件数(0)"
Else
For i = 0 To UBound(v)
Debug.Print sS & " = " & v(i)
Next
Debug.Print ">>> " & sS & " の件数(" & UBound(v) + 1 & ")"
End If
Debug.Print "終了------------処理時間:" & Format(Timer - st, "0.000\秒")
Loop
End Sub
F1 As Long
ID As Long
End Type
Private Type PosData
fx_pos As Long
mv_Apos As Long
mv_Bpos As Long
End Type
Private Const FLD_ID As String = "ID"
Private Const FLD_F1 As String = "F1"
Dim dic As Object
Private Function RecRead(sTable As String) As AryData()
Dim rs As New ADODB.Recordset
Dim tArySrc() As AryData
Dim i As Long
ReDim tArySrc(0)
i = -1
rs.Source = "SELECT " & FLD_ID & "," & FLD_F1 & " FROM " & sTable _
& " ORDER BY " & FLD_F1 & "," & FLD_ID & ";"
rs.Open , CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
While (Not rs.EOF)
i = i + 1
If (i <> 0) Then ReDim Preserve tArySrc(i)
tArySrc(i).F1 = rs(FLD_F1)
tArySrc(i).ID = rs(FLD_ID)
rs.MoveNext
Wend
rs.Close
RecRead = tArySrc
End Function
Private Function VarSort(sSel As String, tPosSrc As PosData, tAryDest() As AryData) As Variant
Dim vr As Variant
Dim i As Long
Dim v As Variant
ReDim vr(tPosSrc.fx_pos)
Select Case sSel
Case FLD_ID
For i = 0 To tPosSrc.fx_pos
vr(i) = tAryDest(i).ID
Next
Case FLD_F1
For i = 0 To tPosSrc.fx_pos
vr(i) = tAryDest(i).F1
Next
Case Else
Exit Function
End Select
Do
v = Empty
For i = 0 To UBound(vr) - 1
If (vr(i) > vr(i + 1)) Then
v = vr(i)
vr(i) = vr(i + 1)
vr(i + 1) = v
End If
Next
Loop While (Not IsEmpty(v))
VarSort = vr
End Function
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) > iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
Private Sub ReCallSum(iNum As Long, tPosSrc As PosData, tArySrc() As AryData, tAryDest() As AryData)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
Dim vAry As Variant
Dim v As Variant
Dim sS As String
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
tAryDest(.fx_pos) = tArySrc(.mv_Apos)
If (iNumNew = 0) Then
For Each vAry In Array(FLD_F1, FLD_ID)
sS = ""
For Each v In VarSort(CStr(vAry), tPosWsrc, tAryDest)
sS = sS & ";" & Format(v, "0000")
Next
dic.Item(vAry & sS) = Null
Next
Else
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNumNew, tPosToSrc, tArySrc, tAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Private Function SearchF1(sSel As String, iNum As Long, tArySrc() As AryData) As String
Dim i As Integer
SearchF1 = ""
If (sSel = FLD_F1) Then Exit Function
For i = 0 To UBound(tArySrc)
If (tArySrc(i).ID = iNum) Then
SearchF1 = "(" & tArySrc(i).F1 & ")"
Exit For
End If
Next
End Function
Public Function SumSearch(sTable As String, sSel As String, iNum As Long) As Variant
Dim tArySrc() As AryData, tAryDest() As AryData
Dim tPosSrc As PosData
Dim vAry As Variant, vAryR As Variant
Dim v As Variant
Dim i As Long, j As Long
Dim sS As String
tArySrc = RecRead(sTable)
If (tArySrc(0).ID = 0) Then Exit Function
tAryDest = tArySrc
With tPosSrc
.fx_pos = -1
.mv_Apos = 0
.mv_Bpos = UBound(tArySrc)
End With
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallSum(iNum, tPosSrc, tArySrc, tAryDest)
If (dic.Count > 0) Then
vAry = dic.Keys
Do
v = Empty
For i = 0 To UBound(vAry) - 1
If (vAry(i) > vAry(i + 1)) Then
v = vAry(i)
vAry(i) = vAry(i + 1)
vAry(i + 1) = v
End If
Next
Loop While (Not IsEmpty(v))
For i = 0 To UBound(vAry)
sS = ""
v = Split(vAry(i), ";")
If (v(0) = sSel) Then
For j = 1 To UBound(v)
sS = sS & " " & CLng(v(j)) & SearchF1(sSel, CLng(v(j)), tArySrc)
Next
If (IsEmpty(vAryR)) Then
ReDim vAryR(0)
Else
ReDim Preserve vAryR(UBound(vAryR) + 1)
End If
vAryR(UBound(vAryR)) = Mid(sS, 2)
End If
Next
End If
Set dic = Nothing
SumSearch = vAryR
End Function
Public Sub Sample()
Dim sS As String
Dim st As Single
Dim v As Variant
Dim i As Long
Do While (1)
sS = InputBox("合計値を入力してください", "パターン検索", "1574")
If (Len(sS) = 0) Then Exit Do
If (Not IsNumeric(sS)) Then Exit Do
st = Timer
v = SumSearch("T1", FLD_F1, CLng(sS))
If (IsEmpty(v)) Then
Debug.Print ">>> " & sS & " の件数(0)"
Else
For i = 0 To UBound(v)
Debug.Print sS & " = " & v(i)
Next
Debug.Print ">>> " & sS & " の件数(" & UBound(v) + 1 & ")"
End If
Debug.Print "終了------------処理時間:" & Format(Timer - st, "0.000\秒")
Loop
End Sub
検索値が見つかったら、";" 区切りで、何のデータか先頭に "ID" or "F1" を付加して・・・・
「F1」の値をソートしたものと、「ID」をソートしたものを Dictionary に格納することにしました。
表示した時、ソートされているのが見やすいかな・・・・・ここは妥協できないかなぁ
格納する時に各値を4桁の文字に揃えて・・・・
(なので5桁以上の値の場合、おかしくなるかと思います)
これは、格納先に Dictionary を選んだので、重複があったら削ってくれる・・・・
(F1 の値に重複があった場合、値表示時には重複を削除したもので・・・・)
(ID 表示の場合は重複するものがないので、そのまま表示されると思います)
例)
| ID | F1 |
|---|---|
| 1 | 20 |
| 2 | 30 |
| 3 | 80 |
| 4 | 20 |
| 5 | 40 |
というデータがあった場合に 100 を求めたいとすると
・F1(値)での表示では
20 80
の1件になると思いますが
・ID での表示では
1(20) 3(80) と 3(80) 4(20)
の2件になります。
こういう事をやりたかった・・・・っていうのもあって Dictionary を選んだ・・・・・とも言えるかなっと
再帰呼出しの心臓部分、以下がどのような感じで動いているのか調べてみました。
(標準モジュール「T3Make」で、調査結果をテーブル「T3」に作成します)
Private Sub ReCallSum(iNum As Long, tPosSrc As PosData, tArySrc() As AryData, tAryDest() As AryData)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
Dim vAry As Variant
Dim v As Variant
Dim sS As String
★★ ここで「関数呼」を計測
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
★★ ここで「処理数」を計測
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
tAryDest(.fx_pos) = tArySrc(.mv_Apos)
If (iNumNew = 0) Then
★★ ここで「組合せ数」を計測
For Each vAry In Array(FLD_F1, FLD_ID)
sS = ""
For Each v In VarSort(CStr(vAry), tPosWsrc, tAryDest)
sS = sS & ";" & Format(v, "0000")
Next
dic.Item(vAry & sS) = Null
Next
Else
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNumNew, tPosToSrc, tArySrc, tAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
Dim vAry As Variant
Dim v As Variant
Dim sS As String
★★ ここで「関数呼」を計測
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
★★ ここで「処理数」を計測
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
tAryDest(.fx_pos) = tArySrc(.mv_Apos)
If (iNumNew = 0) Then
★★ ここで「組合せ数」を計測
For Each vAry In Array(FLD_F1, FLD_ID)
sS = ""
For Each v In VarSort(CStr(vAry), tPosWsrc, tAryDest)
sS = sS & ";" & Format(v, "0000")
Next
dic.Item(vAry & sS) = Null
Next
Else
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNumNew, tPosToSrc, tArySrc, tAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
計測した結果は以下の通りとなりました。
最小値、基準値、最大値での結果は
| それぞれでの最大値となるのは
|
検索値 500 ごとの結果は以下になりました。
|
|
ただ、私が作成した Module11 でのものなので参考になるのかどうか・・・・
特に「組合せ数」は検証してください。
追記 4/21
--
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
' If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) > iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) <> iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
極力、再帰呼出し回数を減らすように、上記 Select 文で判別しやすい範囲でチェックしていましたが、Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
' If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) > iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) <> iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
黄色部分の判別が足りてませんでした。
上記を変更したことで「処理数」「組合せ数」に変化はありませんでしたが「関数呼」は、
最大、「検索値」6732 の 149934 回を 111738 回へと 38196 減らすことが出来ました。
(時間的目安は 0.336 から 0.320 に改善・・・・)
「関数呼」改善差 TOP 10
| 検索値 | 変更前 | 変更後 | 差 |
|---|---|---|---|
| 6732 | 149934 | 111738 | 38196 |
| 6756 | 149412 | 111220 | 38192 |
| 6733 | 149935 | 111745 | 38190 |
| 6735 | 149851 | 111661 | 38190 |
| 6734 | 149872 | 111683 | 38189 |
| 6728 | 150055 | 111867 | 38188 |
| 6729 | 150012 | 111824 | 38188 |
| 6730 | 149993 | 111805 | 38188 |
| 6731 | 149969 | 111781 | 38188 |
| 6727 | 150044 | 111857 | 38187 |
なお、求める動作自体には影響ないので、サンプルファイルは未修正
--
ちなみに使用個数を限定した場合「Module31」での変更箇所は少なく、以下の黄色部分
(iNst が使用個数を指定するもの)
Private Sub ReCallSum(iNst As Long, iNum As Long _
, tPosSrc As PosData, tArySrc() As AryData, tAryDest() As AryData)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
Dim vAry As Variant
Dim v As Variant
Dim sS As String
If (iNst < 1) Then Exit Sub
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
tAryDest(.fx_pos) = tArySrc(.mv_Apos)
If (iNumNew = 0) Then
For Each vAry In Array(FLD_F1, FLD_ID)
sS = ""
For Each v In VarSort(CStr(vAry), tPosWsrc, tAryDest)
sS = sS & ";" & Format(v, "0000")
Next
dic.Item(vAry & sS) = Null
Next
ElseIf (iNst > 1) Then
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNst - 1, iNumNew, tPosToSrc, tArySrc, tAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
, tPosSrc As PosData, tArySrc() As AryData, tAryDest() As AryData)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
Dim vAry As Variant
Dim v As Variant
Dim sS As String
If (iNst < 1) Then Exit Sub
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
tAryDest(.fx_pos) = tArySrc(.mv_Apos)
If (iNumNew = 0) Then
For Each vAry In Array(FLD_F1, FLD_ID)
sS = ""
For Each v In VarSort(CStr(vAry), tPosWsrc, tAryDest)
sS = sS & ";" & Format(v, "0000")
Next
dic.Item(vAry & sS) = Null
Next
ElseIf (iNst > 1) Then
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNst - 1, iNumNew, tPosToSrc, tArySrc, tAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
もちろんパラメータ iNst が増えた分、呼び出し側にも変更はありますが・・・・
Module21

Private Const FLD_ID As String = "ID"
Private Const FLD_F1 As String = "F1"
Private Const FLD_F1ID As String = "F1ID"
Private Const FLD_USE As String = "use"
Private Const POS_ID As Long = 0
Private Const POS_F1 As Long = 1
Private Const POS_F1ID As Long = 2
Private Const POS_USE As Long = 3
Dim dic As Object
Private Sub RecRead(sTable As String, rs As ADODB.Recordset)
Dim rsIn As New ADODB.Recordset
Dim i As Long
rsIn.Source = "SELECT " & FLD_ID & "," & FLD_F1 & " FROM " & sTable _
& " ORDER BY " & FLD_F1 & "," & FLD_ID & ";"
rsIn.Open , CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
If (Not rsIn.EOF) Then
Set rs = New ADODB.Recordset
With rs
With .Fields
.Append FLD_ID, adInteger
.Append FLD_F1, adInteger
.Append FLD_F1ID, adInteger
.Append FLD_USE, adInteger
End With
.LockType = adLockOptimistic
.Open
End With
i = 1
While (Not rsIn.EOF)
rs.AddNew
rs(POS_ID) = rsIn(POS_ID)
rs(POS_F1) = rsIn(POS_F1)
rs(POS_F1ID) = i
rs(POS_USE) = 0
rs.Update
i = i + 1
rsIn.MoveNext
Wend
rs.MoveFirst
End If
rsIn.Close
End Sub
Private Sub ReCallSum(iNum As Long, rs As ADODB.Recordset)
Dim rsc As ADODB.Recordset
Dim iNumNew As Long
Dim i As Long
Dim sS As String
i = 0
While (Not rs.EOF)
i = i + rs(POS_F1)
rs.MoveNext
Wend
If ((i = 0) Or (iNum > i)) Then Exit Sub
rs.MoveFirst
Set rsc = rs.Clone
Do While (Not rs.EOF)
iNumNew = iNum - rs(POS_F1)
If (iNumNew < 0) Then Exit Do
rs(POS_USE) = 1
rs.Update
If (iNumNew = 0) Then
rsc.Filter = FLD_USE & " = 1"
sS = ""
rsc.Sort = FLD_F1
While (Not rsc.EOF)
sS = sS & ";" & Format(rsc(POS_F1), "0000")
rsc.MoveNext
Wend
dic.Item(FLD_F1 & sS) = Null
sS = ""
rsc.Sort = FLD_ID
While (Not rsc.EOF)
sS = sS & ";" & Format(rsc(POS_ID), "0000")
rsc.MoveNext
Wend
dic.Item(FLD_ID & sS) = Null
Else
rsc.Filter = "(" & FLD_F1ID & " > " & rs(POS_F1ID) & ")" _
& " AND (" & FLD_F1 & " <= " & iNumNew & ")"
If (rsc.RecordCount > 0) Then Call ReCallSum(iNumNew, rsc)
End If
rs(POS_USE) = 0
rs.Update
rs.MoveNext
Loop
rsc.Close
Set rsc = Nothing
End Sub
Private Function SearchF1(sSel As String, iNum As Long, rs As ADODB.Recordset) As String
SearchF1 = ""
If (sSel = FLD_F1) Then Exit Function
rs.MoveFirst
rs.Find FLD_ID & " = " & iNum
SearchF1 = "(" & rs(POS_F1) & ")"
End Function
Public Function SumSearch(sTable As String, sSel As String, iNum As Long) As Variant
Dim rs As ADODB.Recordset
Dim vAry As Variant, vAryR As Variant
Dim v As Variant
Dim i As Long, j As Long
Dim sS As String
Call RecRead(sTable, rs)
If (rs Is Nothing) Then Exit Function
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallSum(iNum, rs)
If (dic.Count > 0) Then
vAry = dic.Keys
Do
v = Empty
For i = 0 To UBound(vAry) - 1
If (vAry(i) > vAry(i + 1)) Then
v = vAry(i)
vAry(i) = vAry(i + 1)
vAry(i + 1) = v
End If
Next
Loop While (Not IsEmpty(v))
For i = 0 To UBound(vAry)
sS = ""
v = Split(vAry(i), ";")
If (v(0) = sSel) Then
For j = 1 To UBound(v)
sS = sS & " " & CLng(v(j)) & SearchF1(sSel, CLng(v(j)), rs)
Next
If (IsEmpty(vAryR)) Then
ReDim vAryR(0)
Else
ReDim Preserve vAryR(UBound(vAryR) + 1)
End If
vAryR(UBound(vAryR)) = Mid(sS, 2)
End If
Next
End If
rs.Close
Set rs = Nothing
Set dic = Nothing
SumSearch = vAryR
End Function
Public Sub Sample()
Dim sS As String
Dim st As Single
Dim v As Variant
Dim i As Long
Do While (1)
sS = InputBox("合計値を入力してください", "パターン検索", "1574")
If (Len(sS) = 0) Then Exit Do
If (Not IsNumeric(sS)) Then Exit Do
st = Timer
v = SumSearch("T1", FLD_F1, CLng(sS))
If (IsEmpty(v)) Then
Debug.Print ">>> " & sS & " の件数(0)"
Else
For i = 0 To UBound(v)
Debug.Print sS & " = " & v(i)
Next
Debug.Print ">>> " & sS & " の件数(" & UBound(v) + 1 & ")"
End If
Debug.Print "終了------------処理時間:" & Format(Timer - st, "0.000\秒")
Loop
End Sub
Private Const FLD_F1 As String = "F1"
Private Const FLD_F1ID As String = "F1ID"
Private Const FLD_USE As String = "use"
Private Const POS_ID As Long = 0
Private Const POS_F1 As Long = 1
Private Const POS_F1ID As Long = 2
Private Const POS_USE As Long = 3
Dim dic As Object
Private Sub RecRead(sTable As String, rs As ADODB.Recordset)
Dim rsIn As New ADODB.Recordset
Dim i As Long
rsIn.Source = "SELECT " & FLD_ID & "," & FLD_F1 & " FROM " & sTable _
& " ORDER BY " & FLD_F1 & "," & FLD_ID & ";"
rsIn.Open , CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
If (Not rsIn.EOF) Then
Set rs = New ADODB.Recordset
With rs
With .Fields
.Append FLD_ID, adInteger
.Append FLD_F1, adInteger
.Append FLD_F1ID, adInteger
.Append FLD_USE, adInteger
End With
.LockType = adLockOptimistic
.Open
End With
i = 1
While (Not rsIn.EOF)
rs.AddNew
rs(POS_ID) = rsIn(POS_ID)
rs(POS_F1) = rsIn(POS_F1)
rs(POS_F1ID) = i
rs(POS_USE) = 0
rs.Update
i = i + 1
rsIn.MoveNext
Wend
rs.MoveFirst
End If
rsIn.Close
End Sub
Private Sub ReCallSum(iNum As Long, rs As ADODB.Recordset)
Dim rsc As ADODB.Recordset
Dim iNumNew As Long
Dim i As Long
Dim sS As String
i = 0
While (Not rs.EOF)
i = i + rs(POS_F1)
rs.MoveNext
Wend
If ((i = 0) Or (iNum > i)) Then Exit Sub
rs.MoveFirst
Set rsc = rs.Clone
Do While (Not rs.EOF)
iNumNew = iNum - rs(POS_F1)
If (iNumNew < 0) Then Exit Do
rs(POS_USE) = 1
rs.Update
If (iNumNew = 0) Then
rsc.Filter = FLD_USE & " = 1"
sS = ""
rsc.Sort = FLD_F1
While (Not rsc.EOF)
sS = sS & ";" & Format(rsc(POS_F1), "0000")
rsc.MoveNext
Wend
dic.Item(FLD_F1 & sS) = Null
sS = ""
rsc.Sort = FLD_ID
While (Not rsc.EOF)
sS = sS & ";" & Format(rsc(POS_ID), "0000")
rsc.MoveNext
Wend
dic.Item(FLD_ID & sS) = Null
Else
rsc.Filter = "(" & FLD_F1ID & " > " & rs(POS_F1ID) & ")" _
& " AND (" & FLD_F1 & " <= " & iNumNew & ")"
If (rsc.RecordCount > 0) Then Call ReCallSum(iNumNew, rsc)
End If
rs(POS_USE) = 0
rs.Update
rs.MoveNext
Loop
rsc.Close
Set rsc = Nothing
End Sub
Private Function SearchF1(sSel As String, iNum As Long, rs As ADODB.Recordset) As String
SearchF1 = ""
If (sSel = FLD_F1) Then Exit Function
rs.MoveFirst
rs.Find FLD_ID & " = " & iNum
SearchF1 = "(" & rs(POS_F1) & ")"
End Function
Public Function SumSearch(sTable As String, sSel As String, iNum As Long) As Variant
Dim rs As ADODB.Recordset
Dim vAry As Variant, vAryR As Variant
Dim v As Variant
Dim i As Long, j As Long
Dim sS As String
Call RecRead(sTable, rs)
If (rs Is Nothing) Then Exit Function
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallSum(iNum, rs)
If (dic.Count > 0) Then
vAry = dic.Keys
Do
v = Empty
For i = 0 To UBound(vAry) - 1
If (vAry(i) > vAry(i + 1)) Then
v = vAry(i)
vAry(i) = vAry(i + 1)
vAry(i + 1) = v
End If
Next
Loop While (Not IsEmpty(v))
For i = 0 To UBound(vAry)
sS = ""
v = Split(vAry(i), ";")
If (v(0) = sSel) Then
For j = 1 To UBound(v)
sS = sS & " " & CLng(v(j)) & SearchF1(sSel, CLng(v(j)), rs)
Next
If (IsEmpty(vAryR)) Then
ReDim vAryR(0)
Else
ReDim Preserve vAryR(UBound(vAryR) + 1)
End If
vAryR(UBound(vAryR)) = Mid(sS, 2)
End If
Next
End If
rs.Close
Set rs = Nothing
Set dic = Nothing
SumSearch = vAryR
End Function
Public Sub Sample()
Dim sS As String
Dim st As Single
Dim v As Variant
Dim i As Long
Do While (1)
sS = InputBox("合計値を入力してください", "パターン検索", "1574")
If (Len(sS) = 0) Then Exit Do
If (Not IsNumeric(sS)) Then Exit Do
st = Timer
v = SumSearch("T1", FLD_F1, CLng(sS))
If (IsEmpty(v)) Then
Debug.Print ">>> " & sS & " の件数(0)"
Else
For i = 0 To UBound(v)
Debug.Print sS & " = " & v(i)
Next
Debug.Print ">>> " & sS & " の件数(" & UBound(v) + 1 & ")"
End If
Debug.Print "終了------------処理時間:" & Format(Timer - st, "0.000\秒")
Loop
End Sub
同じ土俵での確認
標準モジュール「__Sample確認」に記述しています。
各記述は、「_」で始まる名称で格納しています。
結果はイミディエイトウィンドウに表示されます。
Public Sub Show_kiku() ' ★★★★★ 実行場所 ★★★★★
Call [_kiku].SumSearch(1574)
' Call [_kiku].SumSearch(3000)
' Call [_kiku].SumSearch(5000)
' Call [_kiku].SumSearch(7000)
End Sub
Public Sub Show_hatena() ' ★★★★★ 実行場所 ★★★★★
Call SearchSum(1574)
' Call SearchSum(3000)
' Call SearchSum(5000)
' Call SearchSum(7000)
End Sub
Public Sub Show_Yu_tang_cls() ' ★★★★★ 実行場所 ★★★★★
Call PackEntry(1574)
' Call PackEntry(3000)
' Call PackEntry(5000)
' Call PackEntry(7000)
End Sub
Public Sub Show_Yu_tang_type() ' ★★★★★ 実行場所 ★★★★★
Call SearchMain(1574)
' Call SearchMain(3000)
' Call SearchMain(5000)
' Call SearchMain(7000)
End Sub
Call [_kiku].SumSearch(1574)
' Call [_kiku].SumSearch(3000)
' Call [_kiku].SumSearch(5000)
' Call [_kiku].SumSearch(7000)
End Sub
Public Sub Show_hatena() ' ★★★★★ 実行場所 ★★★★★
Call SearchSum(1574)
' Call SearchSum(3000)
' Call SearchSum(5000)
' Call SearchSum(7000)
End Sub
Public Sub Show_Yu_tang_cls() ' ★★★★★ 実行場所 ★★★★★
Call PackEntry(1574)
' Call PackEntry(3000)
' Call PackEntry(5000)
' Call PackEntry(7000)
End Sub
Public Sub Show_Yu_tang_type() ' ★★★★★ 実行場所 ★★★★★
Call SearchMain(1574)
' Call SearchMain(3000)
' Call SearchMain(5000)
' Call SearchMain(7000)
End Sub
同じ土俵にのるために記述したのが「_kiku」(Module11 を元に修正)
Private Type AryData
F1 As Long
ID As Long
End Type
Private Type PosData
fx_pos As Long
mv_Apos As Long
mv_Bpos As Long
End Type
Private Const FLD_ID As String = "ID"
Private Const FLD_F1 As String = "F1"
Dim sMsg As String
Dim iCnt As Long
Private Function RecRead(sTable As String) As AryData()
Dim rs As New ADODB.Recordset
Dim tArySrc() As AryData
Dim i As Long
ReDim tArySrc(0)
i = -1
rs.Source = "SELECT " & FLD_ID & "," & FLD_F1 & " FROM " & sTable _
& " ORDER BY " & FLD_F1 & "," & FLD_ID & ";"
rs.Open , CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
While (Not rs.EOF)
i = i + 1
If (i <> 0) Then ReDim Preserve tArySrc(i)
tArySrc(i).F1 = rs(FLD_F1)
tArySrc(i).ID = rs(FLD_ID)
rs.MoveNext
Wend
rs.Close
RecRead = tArySrc
End Function
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) > iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
Private Sub ReCallSum(iNum As Long, tPosSrc As PosData, tArySrc() As AryData, tAryDest() As AryData)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
Dim sS As String
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
tAryDest(.fx_pos) = tArySrc(.mv_Apos)
If (iNumNew = 0) Then
sS = ""
For i = 0 To .fx_pos
sS = sS & "," & tAryDest(i).ID
Next
' ここで毎回 Debug.Print してたら遅すぎたので、文字列だけ作って Debug.Print は後
sMsg = sMsg & vbCrLf & "ID: " & Mid(sS, 2)
iCnt = iCnt + 1
Else
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNumNew, tPosToSrc, tArySrc, tAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Public Sub SumSearch(iNum As Long)
Dim tArySrc() As AryData, tAryDest() As AryData
Dim tPosSrc As PosData
Dim st As Single
st = Timer
sMsg = ""
iCnt = 0
tArySrc = RecRead("T1")
If (tArySrc(0).ID = 0) Then Exit Sub
tAryDest = tArySrc
With tPosSrc
.fx_pos = -1
.mv_Apos = 0
.mv_Bpos = UBound(tArySrc)
End With
Call ReCallSum(iNum, tPosSrc, tArySrc, tAryDest)
Debug.Print Mid(sMsg, Len(vbCrLf) + 1)
Debug.Print ">>> " & iNum & " の件数(" & iCnt & ")" _
& " 処理時間:" & Format(Timer - st, "0.000\秒")
End Sub
Public Sub Sample()
Dim sS As String
Do While (1)
sS = InputBox("合計値を入力してください", "パターン検索", "1574")
If (Len(sS) = 0) Then Exit Do
If (Not IsNumeric(sS)) Then Exit Do
Call SumSearch(CLng(sS))
Loop
End Sub
F1 As Long
ID As Long
End Type
Private Type PosData
fx_pos As Long
mv_Apos As Long
mv_Bpos As Long
End Type
Private Const FLD_ID As String = "ID"
Private Const FLD_F1 As String = "F1"
Dim sMsg As String
Dim iCnt As Long
Private Function RecRead(sTable As String) As AryData()
Dim rs As New ADODB.Recordset
Dim tArySrc() As AryData
Dim i As Long
ReDim tArySrc(0)
i = -1
rs.Source = "SELECT " & FLD_ID & "," & FLD_F1 & " FROM " & sTable _
& " ORDER BY " & FLD_F1 & "," & FLD_ID & ";"
rs.Open , CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
While (Not rs.EOF)
i = i + 1
If (i <> 0) Then ReDim Preserve tArySrc(i)
tArySrc(i).F1 = rs(FLD_F1)
tArySrc(i).ID = rs(FLD_ID)
rs.MoveNext
Wend
rs.Close
RecRead = tArySrc
End Function
Private Function NextAryInfo(iNum As Long, tPosSrc As PosData, tArySrc() As AryData) As Long
Dim i As Long
NextAryInfo = 0
With tPosSrc
.mv_Apos = .mv_Apos + 1
If (.mv_Apos > .mv_Bpos) Then Exit Function
If (tArySrc(.mv_Apos).F1 > iNum) Then Exit Function
While (tArySrc(.mv_Bpos).F1 > iNum)
.mv_Bpos = .mv_Bpos - 1
Wend
i = .mv_Bpos - .mv_Apos + 1
Select Case i
Case 1: If (tArySrc(.mv_Apos).F1 <> iNum) Then Exit Function
Case 2
If (tArySrc(.mv_Apos).F1 <> iNum) Then
If ((tArySrc(.mv_Apos).F1 + tArySrc(.mv_Bpos).F1) > iNum) Then
If (tArySrc(.mv_Bpos).F1 <> iNum) Then Exit Function
.mv_Apos = .mv_Bpos
i = 1
End If
End If
End Select
NextAryInfo = i
End With
End Function
Private Sub ReCallSum(iNum As Long, tPosSrc As PosData, tArySrc() As AryData, tAryDest() As AryData)
Dim tPosWsrc As PosData, tPosToSrc As PosData
Dim iNumNew As Long
Dim i As Long, j As Long
Dim sS As String
With tPosSrc
j = 0
For i = .mv_Apos To .mv_Bpos
j = j + tArySrc(i).F1
Next
End With
If ((j = 0) Or (iNum > j)) Then Exit Sub
tPosWsrc = tPosSrc
With tPosWsrc
.fx_pos = .fx_pos + 1
Do While (.mv_Apos <= .mv_Bpos)
iNumNew = iNum - tArySrc(.mv_Apos).F1
If (iNumNew < 0) Then Exit Do
tAryDest(.fx_pos) = tArySrc(.mv_Apos)
If (iNumNew = 0) Then
sS = ""
For i = 0 To .fx_pos
sS = sS & "," & tAryDest(i).ID
Next
' ここで毎回 Debug.Print してたら遅すぎたので、文字列だけ作って Debug.Print は後
sMsg = sMsg & vbCrLf & "ID: " & Mid(sS, 2)
iCnt = iCnt + 1
Else
tPosToSrc = tPosWsrc
If (NextAryInfo(iNumNew, tPosToSrc, tArySrc) <> 0) Then
Call ReCallSum(iNumNew, tPosToSrc, tArySrc, tAryDest)
End If
End If
.mv_Apos = .mv_Apos + 1
Loop
End With
End Sub
Public Sub SumSearch(iNum As Long)
Dim tArySrc() As AryData, tAryDest() As AryData
Dim tPosSrc As PosData
Dim st As Single
st = Timer
sMsg = ""
iCnt = 0
tArySrc = RecRead("T1")
If (tArySrc(0).ID = 0) Then Exit Sub
tAryDest = tArySrc
With tPosSrc
.fx_pos = -1
.mv_Apos = 0
.mv_Bpos = UBound(tArySrc)
End With
Call ReCallSum(iNum, tPosSrc, tArySrc, tAryDest)
Debug.Print Mid(sMsg, Len(vbCrLf) + 1)
Debug.Print ">>> " & iNum & " の件数(" & iCnt & ")" _
& " 処理時間:" & Format(Timer - st, "0.000\秒")
End Sub
Public Sub Sample()
Dim sS As String
Do While (1)
sS = InputBox("合計値を入力してください", "パターン検索", "1574")
If (Len(sS) = 0) Then Exit Do
If (Not IsNumeric(sS)) Then Exit Do
Call SumSearch(CLng(sS))
Loop
End Sub
※ いや、こういう考え方した方が良いよ・・・・等、教えてください。
| サンプルは以下 | ||||||||||||
| ||||||||||||
| ※ ファイルは zip 形式 | ||||||||||||
| ※ 2007 以外は、2007 保存時に変換 & 各バージョンで動作確認 & 最適化 |
| 付録は以下 | |||
| |||
| ※ ファイルは zip 形式 | |||
| ※ 処理イメージの PDF と、「T3」の内容(xlsファイル) | |||
| ※ 追記 4/21 の変更後の結果「T4」(xlsファイル)を追加 |
≪--- 続きを閉じちゃえ
2012/04/20
Category: やってみる
Excel VBA をやってみた その2
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | |||
| 2 | 終了日 | 2012/04/30 | |||
| 3 | 日数 | 30 | |||
| 4 | |||||
| 5 |
という基本となるものがあります。
開始日 <= 終了日 で、日数 > 0 の規則があるとします。
処理列は限定せず1〜3行に入力した時点で自動的に計算結果を埋め込みたい。
例えば、
C1 に開始日を埋めただけでは計算できないので、
その後、C2 なり C3 を埋めた時点で空いているところを自動算出したい。
埋められる順の規則はなく、
C2 → C3 / C3 → C2 なら C1
C1 → C3 / C3 → C1 なら C2
C1 → C2 / C2 → C1 なら C3
また、すべて埋まった状態での変更では、C1 / C2 なら C3 を C3 なら C2 を求め直す。
・その部分が削除されたら計算はしない
(C列全てが埋まっていたとして C1 を削除しても C2 / C3 から C1 を求め直すことはしない)
・開始日 <= 終了日 でない場合、 日数 は空白に
・開始日があって日数を変更した時、日数 <= 0 なら 終了日 を空白に
と、勝手に追加
Worksheet_Change を使ってみました。
2007 で確認していたのですが、いろいろな動作を確認できて収穫が多かったです。
続きを読んでみようかな ---≫
まず、以下を記述してみました
Worksheet_Change 内でセルに値を設定すると、また Worksheet_Change が呼ばれるようなので、
処理で設定した後の Worksheet_Change はすぐに戻るように、上記黄色部分で細工。
Worksheet_Change イベント自体を抑止する方法もあるようですが・・・・今回はこれで。
処理的には、どの行が変更されたか・・・・
これを求める前に1〜3行の状況を取得しておきます。
変数を Variant にしておいて、Empty かで判別するように・・・・
元々の値が Empty だったら、IsNumeric は True で、Int は 0 を返すので2段で判別するように・・・
へ〜〜1つ目
日数のところに &H0A を入力してみると IsNumeric は True で、Int は 10 になるんですね。
何か、それなりに動いてくれますね・・・コピー&ペーストされても大丈夫そう・・・・・・・・・
へ〜〜2つ目
列を挿入すると悲惨な事に・・・・・
列を挿入すると、挿入されたセル全てが Target で得られるようです。
(そのセル分 For Next が Loop しちゃいますね)
処理する行1〜3に限定した動きをするようにしましょう・・・ということで
この記述部分を以下に変更します。
黄色部分が追加/変更した部分になります。
Intersect を使って1〜3行に絞り込みます。
動きとしては、そこそこ良くなったんではなかろうか・・・・
と思っていじっていたら・・・・あれ?・・・あれれ・・・・てなことが
へ〜〜3つ目
という表示があったとします。
C3:D4 は、貼り付けとかで失敗したのでしょうか。
この C3:D4 部分を C2:D3 へ持っていく時の方法ですが、C3:D4 を選択して枠部分をドラックして
・コピーすると(Ctrlキーを押しながら C2:D3 へ)
・移動すると(そのまま C2:D3 へ)
私のイメージとしては、移動してもコピーの結果になって欲しかったな・・・・・
この場合の移動では、Worksheet_Change が2回発生するようです。
移動した直後では、
となっているようですが、
1回目の時の Target は以下の部分が通知されました。
日数の変更を通知されたので、開始日があることから終了日を求め直し設定します。
2回目の通知は、その変更した内容で以下の部分となりました。
この動きは通知された Target を元に行っているので、どうしようもないことの様に思えます。
(対策はないのかな・・・・・)
複数のセル(Target)を処理しようとすると無理なのかな・・・・・
コピー&ペーストでの複数処理・・・・・あきらめるのかなぁ・・・・
Target.Count = 1 を判別すべきだろうか・・・・
でも、コピー&ペーストは捨てがたいし・・・・
おかしな動きをする時がある・・・・という事で、そのままで行こうかなっと。
コピー&ペーストは処理するという事にしましたが、例えば C1:D3 に貼り付けると
For Each で得られる Range (Target) の順は
の様な順になるようです。
ここで、1番目に得られる C1 を処理する時、C2 / C3 の部分も書き変わっているようです。
ということは、列に対して1回処理すればよいことになる???
そこで、処理している列情報を Dictionary で管理することにして以下
黄色い部分が処理を追加したところになります。
ただ、この処理にも弊害があって、以下の様なデータを
C1:D3 に貼り付けると
ということになります。
・貼り付けた先頭行で処理する
こと(が仕様)になります。
これは手間になりますが、考えながら貼り付けることをして頂戴・・・・ってなことで・・・・本当にいいのかな?
これが嫌なら、Dictionary 処理を追加する前のものとすれば、抜けなく書き変わると思います。
今回、サンプルファイルはありません。
こういう方法にすれば良いよ・・・・・教えてください。
≪--- 続きを閉じちゃえ
まず、以下を記述してみました
Private Sub Worksheet_Change(ByVal Target As Range)
Static iCol As Long
Dim t As Range
Dim vDt1 As Variant, vDt2 As Variant, vDt3 As Variant
If (iCol <> 0) Then Exit Sub
For Each t In Target
iCol = t.Column
vDt1 = Empty: vDt2 = Empty: vDt3 = Empty
If (IsDate(Cells(1, iCol))) Then vDt1 = CDate(Cells(1, iCol))
If (IsDate(Cells(2, iCol))) Then vDt2 = CDate(Cells(2, iCol))
If (Not IsEmpty(Cells(3, iCol))) Then
If (IsNumeric(Cells(3, iCol))) Then vDt3 = Int(Cells(3, iCol))
End If
Select Case t.Row
Case 1
Select Case True
Case IsEmpty(vDt1)
Case IsEmpty(vDt2) And IsEmpty(vDt3)
Case IsEmpty(vDt2)
If (vDt3 > 0) Then Cells(2, iCol) = DateAdd("d", vDt3 - 1, vDt1)
Case Else
If (vDt1 > vDt2) Then
Cells(3, iCol) = Empty
Else
Cells(3, iCol) = DateDiff("d", vDt1, vDt2) + 1
End If
End Select
Case 2
Select Case True
Case IsEmpty(vDt2)
Case IsEmpty(vDt1) And IsEmpty(vDt3)
Case IsEmpty(vDt1)
If (vDt3 > 0) Then Cells(1, iCol) = DateAdd("d", -(vDt3 - 1), vDt2)
Case Else
If (vDt1 > vDt2) Then
Cells(3, iCol) = Empty
Else
Cells(3, iCol) = DateDiff("d", vDt1, vDt2) + 1
End If
End Select
Case 3
Select Case True
Case IsEmpty(vDt3)
Case IsEmpty(vDt1) And IsEmpty(vDt2)
Case IsEmpty(vDt1)
If (vDt3 > 0) Then Cells(1, iCol) = DateAdd("d", -(vDt3 - 1), vDt2)
Case Else
If (vDt3 <= 0) Then
Cells(2, iCol) = Empty
Else
Cells(2, iCol) = DateAdd("d", vDt3 - 1, vDt1)
End If
End Select
End Select
Next
iCol = 0
End Sub
Static iCol As Long
Dim t As Range
Dim vDt1 As Variant, vDt2 As Variant, vDt3 As Variant
If (iCol <> 0) Then Exit Sub
For Each t In Target
iCol = t.Column
vDt1 = Empty: vDt2 = Empty: vDt3 = Empty
If (IsDate(Cells(1, iCol))) Then vDt1 = CDate(Cells(1, iCol))
If (IsDate(Cells(2, iCol))) Then vDt2 = CDate(Cells(2, iCol))
If (Not IsEmpty(Cells(3, iCol))) Then
If (IsNumeric(Cells(3, iCol))) Then vDt3 = Int(Cells(3, iCol))
End If
Select Case t.Row
Case 1
Select Case True
Case IsEmpty(vDt1)
Case IsEmpty(vDt2) And IsEmpty(vDt3)
Case IsEmpty(vDt2)
If (vDt3 > 0) Then Cells(2, iCol) = DateAdd("d", vDt3 - 1, vDt1)
Case Else
If (vDt1 > vDt2) Then
Cells(3, iCol) = Empty
Else
Cells(3, iCol) = DateDiff("d", vDt1, vDt2) + 1
End If
End Select
Case 2
Select Case True
Case IsEmpty(vDt2)
Case IsEmpty(vDt1) And IsEmpty(vDt3)
Case IsEmpty(vDt1)
If (vDt3 > 0) Then Cells(1, iCol) = DateAdd("d", -(vDt3 - 1), vDt2)
Case Else
If (vDt1 > vDt2) Then
Cells(3, iCol) = Empty
Else
Cells(3, iCol) = DateDiff("d", vDt1, vDt2) + 1
End If
End Select
Case 3
Select Case True
Case IsEmpty(vDt3)
Case IsEmpty(vDt1) And IsEmpty(vDt2)
Case IsEmpty(vDt1)
If (vDt3 > 0) Then Cells(1, iCol) = DateAdd("d", -(vDt3 - 1), vDt2)
Case Else
If (vDt3 <= 0) Then
Cells(2, iCol) = Empty
Else
Cells(2, iCol) = DateAdd("d", vDt3 - 1, vDt1)
End If
End Select
End Select
Next
iCol = 0
End Sub
Worksheet_Change 内でセルに値を設定すると、また Worksheet_Change が呼ばれるようなので、
処理で設定した後の Worksheet_Change はすぐに戻るように、上記黄色部分で細工。
Worksheet_Change イベント自体を抑止する方法もあるようですが・・・・今回はこれで。
処理的には、どの行が変更されたか・・・・
これを求める前に1〜3行の状況を取得しておきます。
変数を Variant にしておいて、Empty かで判別するように・・・・
元々の値が Empty だったら、IsNumeric は True で、Int は 0 を返すので2段で判別するように・・・
へ〜〜1つ目
日数のところに &H0A を入力してみると IsNumeric は True で、Int は 10 になるんですね。
何か、それなりに動いてくれますね・・・コピー&ペーストされても大丈夫そう・・・・・・・・・
へ〜〜2つ目
列を挿入すると悲惨な事に・・・・・
列を挿入すると、挿入されたセル全てが Target で得られるようです。
(そのセル分 For Next が Loop しちゃいますね)
処理する行1〜3に限定した動きをするようにしましょう・・・ということで
Private Sub Worksheet_Change(ByVal Target As Range)
Static iCol As Long
Dim t As Range
Dim vDt1 As Variant, vDt2 As Variant, vDt3 As Variant
If (iCol <> 0) Then Exit Sub
For Each t In Target
Static iCol As Long
Dim t As Range
Dim vDt1 As Variant, vDt2 As Variant, vDt3 As Variant
If (iCol <> 0) Then Exit Sub
For Each t In Target
この記述部分を以下に変更します。
Private Sub Worksheet_Change(ByVal Target As Range)
Static iCol As Long
Dim inTarget As Range, t As Range
Dim vDt1 As Variant, vDt2 As Variant, vDt3 As Variant
If (iCol <> 0) Then Exit Sub
Set inTarget = Intersect(Target, Rows("1:3"))
If (inTarget Is Nothing) Then Exit Sub
For Each t In inTarget
と、戻る前でStatic iCol As Long
Dim inTarget As Range, t As Range
Dim vDt1 As Variant, vDt2 As Variant, vDt3 As Variant
If (iCol <> 0) Then Exit Sub
Set inTarget = Intersect(Target, Rows("1:3"))
If (inTarget Is Nothing) Then Exit Sub
For Each t In inTarget
Next
Set inTarget = Nothing
iCol = 0
End Sub
Set inTarget = Nothing
iCol = 0
End Sub
黄色部分が追加/変更した部分になります。
Intersect を使って1〜3行に絞り込みます。
動きとしては、そこそこ良くなったんではなかろうか・・・・
と思っていじっていたら・・・・あれ?・・・あれれ・・・・てなことが
へ〜〜3つ目
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | 2012/04/01 | 2012/05/01 | |
| 2 | 終了日 | 2012/04/30 | |||
| 3 | 日数 | 30 | 2012/05/01 | 2012/06/01 | |
| 4 | 20 | 20 | |||
| 5 |
という表示があったとします。
C3:D4 は、貼り付けとかで失敗したのでしょうか。
この C3:D4 部分を C2:D3 へ持っていく時の方法ですが、C3:D4 を選択して枠部分をドラックして
・コピーすると(Ctrlキーを押しながら C2:D3 へ)
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | 2012/04/01 | 2012/05/01 | |
| 2 | 終了日 | 2012/04/30 | 2012/05/01 | 2012/06/01 | |
| 3 | 日数 | 30 | 31 | 32 | |
| 4 | 20 | 20 | |||
| 5 |
・移動すると(そのまま C2:D3 へ)
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | 2012/04/01 | 2012/05/01 | |
| 2 | 終了日 | 2012/04/30 | 2012/04/20 | 2012/05/20 | |
| 3 | 日数 | 30 | 20 | 20 | |
| 4 | |||||
| 5 |
私のイメージとしては、移動してもコピーの結果になって欲しかったな・・・・・
この場合の移動では、Worksheet_Change が2回発生するようです。
移動した直後では、
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | 2012/04/01 | 2012/05/01 | |
| 2 | 終了日 | 2012/04/30 | 2012/05/01 | 2012/06/01 | |
| 3 | 日数 | 30 | 20 | 20 | |
| 4 | |||||
| 5 |
となっているようですが、
1回目の時の Target は以下の部分が通知されました。
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | 2012/04/01 | 2012/05/01 | |
| 2 | 終了日 | 2012/04/30 | 2012/05/01 | 2012/06/01 | |
| 3 | 日数 | 30 | 20 | 20 | |
| 4 | (Empty) | (Empty) | |||
| 5 |
日数の変更を通知されたので、開始日があることから終了日を求め直し設定します。
2回目の通知は、その変更した内容で以下の部分となりました。
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | 2012/04/01 | 2012/05/01 | |
| 2 | 終了日 | 2012/04/30 | 2012/04/20 | 2012/05/20 | |
| 3 | 日数 | 30 | 20 | 20 | |
| 4 | |||||
| 5 |
この動きは通知された Target を元に行っているので、どうしようもないことの様に思えます。
(対策はないのかな・・・・・)
複数のセル(Target)を処理しようとすると無理なのかな・・・・・
コピー&ペーストでの複数処理・・・・・あきらめるのかなぁ・・・・
Target.Count = 1 を判別すべきだろうか・・・・
でも、コピー&ペーストは捨てがたいし・・・・
おかしな動きをする時がある・・・・という事で、そのままで行こうかなっと。
コピー&ペーストは処理するという事にしましたが、例えば C1:D3 に貼り付けると
For Each で得られる Range (Target) の順は
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | 1 | 2 | |
| 2 | 終了日 | 2012/04/30 | 3 | 4 | |
| 3 | 日数 | 30 | 5 | 6 | |
| 4 | |||||
| 5 |
の様な順になるようです。
ここで、1番目に得られる C1 を処理する時、C2 / C3 の部分も書き変わっているようです。
ということは、列に対して1回処理すればよいことになる???
そこで、処理している列情報を Dictionary で管理することにして以下
Private Sub Worksheet_Change(ByVal Target As Range)
Static iCol As Long
Static dic As Object
Dim inTarget As Range, t As Range
Dim vDt1 As Variant, vDt2 As Variant, vDt3 As Variant
If (iCol <> 0) Then Exit Sub
Set inTarget = Intersect(Target, Rows("1:3"))
If (inTarget Is Nothing) Then Exit Sub
If (dic Is Nothing) Then Set dic = CreateObject("Scripting.Dictionary")
dic.RemoveAll
For Each t In inTarget
iCol = t.Column
If (Not dic.Exists(iCol)) Then
dic.Item(iCol) = Null
vDt1 = Empty: vDt2 = Empty: vDt3 = Empty
If (IsDate(Cells(1, iCol))) Then vDt1 = CDate(Cells(1, iCol))
If (IsDate(Cells(2, iCol))) Then vDt2 = CDate(Cells(2, iCol))
If (Not IsEmpty(Cells(3, iCol))) Then
If (IsNumeric(Cells(3, iCol))) Then vDt3 = Int(Cells(3, iCol))
End If
Select Case t.Row
Case 1
Select Case True
Case IsEmpty(vDt1)
Case IsEmpty(vDt2) And IsEmpty(vDt3)
Case IsEmpty(vDt2)
If (vDt3 > 0) Then Cells(2, iCol) = DateAdd("d", vDt3 - 1, vDt1)
Case Else
If (vDt1 > vDt2) Then
Cells(3, iCol) = Empty
Else
Cells(3, iCol) = DateDiff("d", vDt1, vDt2) + 1
End If
End Select
Case 2
Select Case True
Case IsEmpty(vDt2)
Case IsEmpty(vDt1) And IsEmpty(vDt3)
Case IsEmpty(vDt1)
If (vDt3 > 0) Then Cells(1, iCol) = DateAdd("d", -(vDt3 - 1), vDt2)
Case Else
If (vDt1 > vDt2) Then
Cells(3, iCol) = Empty
Else
Cells(3, iCol) = DateDiff("d", vDt1, vDt2) + 1
End If
End Select
Case 3
Select Case True
Case IsEmpty(vDt3)
Case IsEmpty(vDt1) And IsEmpty(vDt2)
Case IsEmpty(vDt1)
If (vDt3 > 0) Then Cells(1, iCol) = DateAdd("d", -(vDt3 - 1), vDt2)
Case Else
If (vDt3 <= 0) Then
Cells(2, iCol) = Empty
Else
Cells(2, iCol) = DateAdd("d", vDt3 - 1, vDt1)
End If
End Select
End Select
End If
Next
Set inTarget = Nothing
iCol = 0
End Sub
Static iCol As Long
Static dic As Object
Dim inTarget As Range, t As Range
Dim vDt1 As Variant, vDt2 As Variant, vDt3 As Variant
If (iCol <> 0) Then Exit Sub
Set inTarget = Intersect(Target, Rows("1:3"))
If (inTarget Is Nothing) Then Exit Sub
If (dic Is Nothing) Then Set dic = CreateObject("Scripting.Dictionary")
dic.RemoveAll
For Each t In inTarget
iCol = t.Column
If (Not dic.Exists(iCol)) Then
dic.Item(iCol) = Null
vDt1 = Empty: vDt2 = Empty: vDt3 = Empty
If (IsDate(Cells(1, iCol))) Then vDt1 = CDate(Cells(1, iCol))
If (IsDate(Cells(2, iCol))) Then vDt2 = CDate(Cells(2, iCol))
If (Not IsEmpty(Cells(3, iCol))) Then
If (IsNumeric(Cells(3, iCol))) Then vDt3 = Int(Cells(3, iCol))
End If
Select Case t.Row
Case 1
Select Case True
Case IsEmpty(vDt1)
Case IsEmpty(vDt2) And IsEmpty(vDt3)
Case IsEmpty(vDt2)
If (vDt3 > 0) Then Cells(2, iCol) = DateAdd("d", vDt3 - 1, vDt1)
Case Else
If (vDt1 > vDt2) Then
Cells(3, iCol) = Empty
Else
Cells(3, iCol) = DateDiff("d", vDt1, vDt2) + 1
End If
End Select
Case 2
Select Case True
Case IsEmpty(vDt2)
Case IsEmpty(vDt1) And IsEmpty(vDt3)
Case IsEmpty(vDt1)
If (vDt3 > 0) Then Cells(1, iCol) = DateAdd("d", -(vDt3 - 1), vDt2)
Case Else
If (vDt1 > vDt2) Then
Cells(3, iCol) = Empty
Else
Cells(3, iCol) = DateDiff("d", vDt1, vDt2) + 1
End If
End Select
Case 3
Select Case True
Case IsEmpty(vDt3)
Case IsEmpty(vDt1) And IsEmpty(vDt2)
Case IsEmpty(vDt1)
If (vDt3 > 0) Then Cells(1, iCol) = DateAdd("d", -(vDt3 - 1), vDt2)
Case Else
If (vDt3 <= 0) Then
Cells(2, iCol) = Empty
Else
Cells(2, iCol) = DateAdd("d", vDt3 - 1, vDt1)
End If
End Select
End Select
End If
Next
Set inTarget = Nothing
iCol = 0
End Sub
黄色い部分が処理を追加したところになります。
ただ、この処理にも弊害があって、以下の様なデータを
| 2012/05/01 | bbbb |
| aaaa | 2012/06/20 |
| 20 | 20 |
C1:D3 に貼り付けると
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 開始日 | 2012/04/01 | 2012/05/01 | bbbb | |
| 2 | 終了日 | 2012/04/30 | 2012/05/20 | 2012/06/20 | |
| 3 | 日数 | 30 | 20 | 20 | |
| 4 | |||||
| 5 |
ということになります。
・貼り付けた先頭行で処理する
こと(が仕様)になります。
これは手間になりますが、考えながら貼り付けることをして頂戴・・・・ってなことで・・・・本当にいいのかな?
これが嫌なら、Dictionary 処理を追加する前のものとすれば、抜けなく書き変わると思います。
今回、サンプルファイルはありません。
こういう方法にすれば良いよ・・・・・教えてください。
≪--- 続きを閉じちゃえ
2012/04/10
Category: やってみる
コンボ vs リスト vs メイン/サブ
コンボボックスで候補を選択する(1件選ぶ)
候補自体が 20 件とかなら選択する操作は楽なのかも・・・・
件数が多くなっても、直に入力すれば前方一致で候補に飛んでくれます。
でも、部分的にしか覚えていない・・・・とか
表示する件数が かなりある・・・・とか
そこで登場するのが
・リストボックス表示で絞り込み・・・・
・メイン/サブの帳票表示で絞り込み・・・・
また、よくリストボックスの表示で、あるフィールド部分を右詰にしたい・・・
(文字列の部分と、数値部分の表示配置を分けたい・・・・)
こういう時には、リストボックス表示に似せた帳票サブフォームで実現したら・・・・・・・
ってなことを言う時があります。
そこで、今回はそれぞれの表示/操作を比較しながら同じような事をしてみたいと・・・
(無理な部分もありますが)
なお、
リストボックス、
メイン/サブフォーム構成では、各フィールドでの曖昧検索を組み込んでみたいと思います。
(コンボボックスでは表示している部分での前方一致だけ・・・・と言っても機能そのまま)
コンボボックスでの画面は、

リストボックス、メイン/サブフォームでの画面では、操作も近いようにしてみる

(この画面では、入力するたびに曖昧検索して絞り込みできやすくするように)
続きを読んでみようかな ---≫
テーブル「T商品」を用意しました。
「商品ID」が主キーで、他のフィールドに重複ありのインデックスを設定してみました。
(曖昧検索を主に確認するので、インデックスを設定しても意味無いのかも)
「商品ID」はいらなくて、「商品CD」を主キーで・・・・っていうのもあるかもしれません。
・「商品CD」は今後見直しによって変更があるかもしれない
であれば、変更の対象は1テーブルにとどめておきたいかなぁ・・・・・
・他テーブルと結びつける際、テキスト型より数値型の方が処理上速いんではないかなぁ・・・・・・
・マスタ側になる主キーにはオートナンバは使いたくないかなぁ・・・・・
(私の中での雰囲気です)
このテーブルには、1000 件のデータを入れておきました。
(標準モジュール「Module2」内の関数実行で、指定件数分のダミーデータを作成できるように)
また、テーブル「T商品B」も同じ構成で、データ3件だけ(基本的なフォーム動作確認用として)
用意したフォームは以下
F0 系のフォームは、単独で起動確認できるもの
F1 系のフォームは、コンボボックスの連結列を「商品ID」として、
・コンボボックスの入力は「商品CD」
・必要に応じて他フォームを起動
・他フォームからはコンボボックスに対して、選択された「商品ID」を設定
(F0 系に起動元フォームとの連携組み込み)
F2 系のフォームは、コンボボックスの連結列を「商品CD」として、
・コンボボックスの入力は「商品CD」
・必要に応じて他フォームを起動
・他フォームからはコンボボックスに対して、選択された「商品CD」を設定
F3 系のフォームは、コンボボックスの連結列を「商品ID」として、
・コンボボックスの入力は「商品CD」
・必要に応じて他フォームを起動
・他フォームからはコンボボックスに対して、選択された「商品ID」を設定
(この選択する時に F1 系での方法を若干変更)
リストボックスや帳票フォーム表示で1件選択する操作を、
その行がダブルクリックされた時に選択されたものとする・・・と決めました。
リストボックス動作「F0_LB」

フォームをデザインから作成していきます。
単票/非連結として作成するので、レコードセレクタ等表示しない様にしておきます。
どのフィールドに対して曖昧検索するか、オプショングループ「op1」で選択できるようにします。
「op1」は、値 1 〜 6 を取るようにします。
オプショングループが変更された場合、文字の色を「赤」に変更したいかな・・・・・
ってんで、フォーム依存しない共通の処理なので、標準モジュール「Module1」に以下を記述しておきます。
これは、チェックボックス/オプションボタンの構成の場合は、くっついているラベルの文字色を・・・・
なので、ラベルがくっついていないとエラーになります。
検索文字を入力する為のテキストボックス「txt1」を配置します。
その下にリストボックス「lst1」を配置します。
リストボックス「lst1」に表示する際、絞り込みをしていくわけですが、
値集合ソースを直接書き換える方法もありますが、今回は値集合ソースは固定してやってみます。
値集合ソースは
つまり、オプショングループの値と連携した、非表示のテキストボックス「tx1」〜「tx6」を配置します。
オプショングループ「op1」が 1 なら、「tx1」に検索文字を設定しますよ・・・・
この時、同じフォーム上の為に名前が解決するのか、
[Forms]![F0_LB]![tx1] と記述しなくても良いようです。
連結列は 1 としておきます。列幅は相応に
VBAで記述したのは以下
※※ さて、ここでですが
オプショングループがクリックされた時、対象のフィールドのみ初期設定する為に

フォームが表示される前に1回通るのですが、その時にはエラーは発生せず。
フォーム表示後、オプショングループをクリックすると、その都度エラーとなるのです。
処理としてはチャンと動いているので、On Error Resume Next でエラーを無視するようにしました。
処理の仕方が悪い・・・・・等、ご指摘いただければと思います。
なお、オプショングループで切り換えた検索対象フィールドの初期表示は、Null 以外を表示するように。
メイン/サブ動作「F0_M」「F0_S」

フォーム「F0_LB」の表示に似せるために、そのフォームを「F0_M」名でコピーします。
リストボックス「lst1」を削除し、サブフォームコントロール「FSUB」を同じ大きさで作成します。
帳票フォームとなるサブフォームを絞り込む方法として、フォームの Filter を使うようにしました。
(サブフォームのレコードソースを書き換える方法もあるかと思いますが)
検索文字列を Filter に設定する際、前後の文字を op1_Click 時に作っておくように・・・・
それ用に、非表示で配置していたテキストボックス「tx1」「tx2」を使いましょうか・・・・
(VBA内の変数に作っておいても良いかも)
「tx3」〜「tx6」を削除します。
サブ用の帳票フォームが出来上がったら、それを「ソースオブジェクト」に設定して作成は完了になります。
記述したVBAは以下
帳票フォーム「F0_S」は、テーブル「T商品B」を元に、フォームウイザードで表形式作成。
レコードソースを変更して「商品ID」昇順を指定しておきます。
レコードセレクタ/移動ボタンを表示しないようにします。
誤ってレコード等をいじれない様に、追加/削除/更新の許可を「いいえ」にしておきます。
フォーム「F0_LB」のリストボックス表示の列幅に合わせて配置し直します。
ここからチョッと細工を・・・・
ヘッダ部に非表示のテキストボックス「txt1」を配置します。
詳細にある全テキストボックスの「タブストップ」を「いいえ」に変更します。
詳細にあるテキストボックス全てを覆うようにコマンドボタン「btn1」を配置します。
「透明」を「はい」として、タブ移動順を一番先頭に変更します。
リストボックス内をクリックした時に反転する処理は、全テキストボックスで条件付き書式を使用します。
この時の判定に、ヘッダ部に配置した「txt1」に、
コマンドボタン「btn1」がクリックされたところの「商品ID」を格納し、
「txt1」と表示行の「商品ID」が一致するかで・・・・
コマンドボタン「btn1」を配置したことで、下側のテキストボックスをクリックし辛くなったので
VBAで条件付き書式を設定するように。(デザイン時の操作でクリックしにくい)
また、リストボックスの選択を解除するマネとして、「txt1」をクリアする関数を Public にし、
メインで Filter をかける際に呼んでもらうように・・・・
記述したVBAは以下
これで、リストボックス風の動きになると思います。
表示しているデータは違いますが、2000 / 2003 で表示してみた感じは以下の様になります。

動きとして、
・レコードをクリックすると反転表示
その後、「↑」「↓」キーや「Page Up」「Page Down」でも移動できるかと思います。
また、リストボックスでの動きとは異なり、「tab」キーでも移動できます。
ただ、チョッとチラつきますね(大目にみるという事で)
なお、2000 だけ、「Page Up」「Page Down」で動きませんでした。
レコードを選択した・・・・・今回ダブルクリックを用いましたが、どこかに配置したボタンでも良いと思います。
連結列「商品ID」その1
コンボボックス動作「F1_CHK」

コンボボックスの動きだけを確認するのではなく、コンボボックスのダブルクリックで、
リストボックス・メイン/サブで選択した値を受け取れるようにしてみました。
最終的には非連結のフォームになるのですが、
テーブル「T商品」を元にフォームウィザードで単票として作成します。
「レコードソース」を空白にします。
どれをコンボボックスに変更するかですが、「商品CD」を表示したいのかなぁ・・・・ということで、
テキストボックス「商品CD」のコントロールソースを空白にし、コンボボックスに変更します。
値集合ソースを
連結列は 1 とします。列幅はリストボックスの設定値を参考にして・・・
他のテキストボックスの「コントロールソース」を変更し、
常にコンボボックス「商品CD」をみるように変更します。
「商品ID」なら、=[商品CD].[Column](0) と。
コンボボックス選択でじれったくなった場合、
・リストボックスのフォームから値をもらうのか
・メイン/サブの帳票表示フォームから値をもらうのか
選択するオプショングループ「op1」を配置します。
(コンボボックスでも、直に入力すれば先頭一致でそこんところに飛んでくれますね)
設定値として何をもらいたいのか tag に設定して、選択したフォームを起動します。
起動操作は、コンボボックス「商品CD」をダブルクリックした時・・・とします。
コンボボックスの連結列を 1 としたので、「商品ID」が欲しい tag = 0 を設定します。
(tag の値の取り決めとして、リストボックス(コンボボックス)に設定した Column(x) の x とする)
記述したのは以下
リストボックス動作「F1_LB」
フォーム「F0_LB」を「F1_LB」名でコピーします。
リストボックス「lst1」の対象テーブル名を「T商品B」から「T商品」に、「商品CD」の昇順に変更します。
値を戻す先を覚えておいて、覚えていたら設定してフォームを閉じます。
(値を戻す時、リストボックス表示の何列目を設定するかは設定先の Tag が持っている)
VBAで以下の黄色部分を追加/修正します。
メイン/サブ動作「F1_M」「F1_S」
フォーム「F0_M」を「F1_M」名でコピーします。
フォーム「F1_LB」と同様に、起動元とのやり取り部分を追加します。
ただ、「F1_LB」と異なるのは、ダブルクリックされるのはサブフォーム側であること・・・・
サブフォームに配置したコントロールのイベントを直接取得する方法もありますが、
今回は、サブフォーム側でダブルクリックを検知したら関数を呼んでもらうように・・・
その時、欲しいフィールド値をサブフォームから取得できるように・・・・
メイン側に LastCall 関数を用意し、
サブ側にリストボックス風の書き方になるように Column 関数を用意しました。
サブフォームコントロール「FSUB」のソースオブジェクトを変更したサブに変更します。
フォーム「F1_M」に記述したのは以下(「F0_M」に追加/変更した部分を黄色で)
フォーム「F0_S」を「F1_S」名でコピーします。
レコードソースの対象テーブル名を「T商品B」から「T商品」に、「商品CD」の昇順に変更します。
また、コマンドボタン「btn1」がダブルクリックされた時点でメインの関数を呼び出すので、
単独起動後ダブルクリックされると必然とエラーが発生するので、
単独起動されない様に Form_Open で起動可否判別を追加・・・
(過去記事でも、いろいろと判別方法を変えながら書いてました。で、今回の判別はこれで・・・)
フォーム「F1_S」に記述したのは以下(「F0_S」に追加/変更した部分を黄色で)
連結列「商品CD」
コンボボックス動作「F2_CHK」
フォーム「F1_CHK」を「F2_CHK」名でコピーします。
サンプルテーブル「T商品」の「商品CD」に重複がなかったようなので、
他フォームから設定される値を「商品CD」としてみます。
コンボボックスの「連結列」を 2 に変更し、VBA記述以下を変更します。
(戻り値に「商品CD」が欲しいことを、tag に設定するだけ)
連結列「商品ID」その2
ここでは、F0 / F1 系で曖昧検索する時には常に前後が曖昧で検索されていました。
検索する文字を入力するところで、自分でどの部分を曖昧にするか指定したい・・・・
頭が「S」で最後が「7」だった・・・・「 S*7 」これを直接指定したい・・・ということで
コンボボックス動作「F3_CHK」
フォーム「F1_CHK」を「F3_CHK」名でコピーします。
起動するフォーム名を変更するだけです
リストボックス動作「F3_LB」
フォーム「F1_LB」を「F3_LB」名でコピーします。
リストボックス「lst1」の値集合ソースを変更します。
VBAでは、以下の黄色部分を変更します。
メイン/サブ動作「F3_M」「F3_S」
フォーム「F1_S」を「F3_S」名でコピーします。
(変更箇所はないので、そのままでも良かったんですが・・・・)
フォーム「F1_M」を「F3_M」名でコピーします。
サブフォームコントロール「FSUB」のソースオブジェクトを「F3_S」に変更します。
以下黄色部分を変更します。
標準モジュール「Module2」にある関数 MakeTableData を実行します
エラー時の処理をチョッと盛り込んでみましたが・・・・・
(でも、あまりこういう書き方はしないのかなっっと。 動きました、というレベルでしょうか・・・)
なお、記事によっては同じことをする時でも書き方を変えてみたりしていますので、
1つの記事を読んで、書き方はこう、という判断はしないでください。
参考にする等々、すべて自己責任でお願いします。
余談
テーブル「T商品」にテストデータを作った後、連続する「商品ID」があるか ??・・・・
これ、クエリを作って確認していたのですが、作り方によっては表示までの時間が違いますね・・・・
遅かったのは、
そこそこ速かったのは
今回の収穫でした。
もっといい方法知ってるよ・・・・教えてください。
≪--- 続きを閉じちゃえ
テーブル「T商品」を用意しました。
| フィールド名 | 型 |
|---|---|
| 商品ID | 数値(長整数) |
| 商品CD | テキスト |
| 商品名 | テキスト |
| 単価 | 通貨 |
| 作成日 | 日付/時刻 |
| 備考 | テキスト |
「商品ID」が主キーで、他のフィールドに重複ありのインデックスを設定してみました。
(曖昧検索を主に確認するので、インデックスを設定しても意味無いのかも)
「商品ID」はいらなくて、「商品CD」を主キーで・・・・っていうのもあるかもしれません。
・「商品CD」は今後見直しによって変更があるかもしれない
であれば、変更の対象は1テーブルにとどめておきたいかなぁ・・・・・
・他テーブルと結びつける際、テキスト型より数値型の方が処理上速いんではないかなぁ・・・・・・
・マスタ側になる主キーにはオートナンバは使いたくないかなぁ・・・・・
(私の中での雰囲気です)
このテーブルには、1000 件のデータを入れておきました。
(標準モジュール「Module2」内の関数実行で、指定件数分のダミーデータを作成できるように)
また、テーブル「T商品B」も同じ構成で、データ3件だけ(基本的なフォーム動作確認用として)
用意したフォームは以下
| 参照 テーブル | フォーム | 内容 |
|---|---|---|
| T商品B | F0_LB | リストボックスでの絞り込み |
| F0_M | 帳票フォームでの絞り込み(メイン側) | |
| F0_S | 帳票フォームでの絞り込み(サブ側) | |
| T商品 | F1_CHK | コンボボックス+他フォームで絞り込み |
| F1_LB | リストボックスでの絞り込み | |
| F1_M | 帳票フォームでの絞り込み(メイン側) | |
| F1_S | 帳票フォームでの絞り込み(サブ側) | |
| F2_CHK | コンボボックス+他フォームで絞り込み | |
| F3_CHK | コンボボックス+他フォームで絞り込み | |
| F3_LB | リストボックスでの絞り込み | |
| F3_M | 帳票フォームでの絞り込み(メイン側) | |
| F3_S | 帳票フォームでの絞り込み(サブ側) |
F0 系のフォームは、単独で起動確認できるもの
F1 系のフォームは、コンボボックスの連結列を「商品ID」として、
・コンボボックスの入力は「商品CD」
・必要に応じて他フォームを起動
・他フォームからはコンボボックスに対して、選択された「商品ID」を設定
(F0 系に起動元フォームとの連携組み込み)
F2 系のフォームは、コンボボックスの連結列を「商品CD」として、
・コンボボックスの入力は「商品CD」
・必要に応じて他フォームを起動
・他フォームからはコンボボックスに対して、選択された「商品CD」を設定
F3 系のフォームは、コンボボックスの連結列を「商品ID」として、
・コンボボックスの入力は「商品CD」
・必要に応じて他フォームを起動
・他フォームからはコンボボックスに対して、選択された「商品ID」を設定
(この選択する時に F1 系での方法を若干変更)
単独起動確認
リストボックスや帳票フォーム表示で1件選択する操作を、
その行がダブルクリックされた時に選択されたものとする・・・と決めました。
リストボックス動作「F0_LB」

フォームをデザインから作成していきます。
単票/非連結として作成するので、レコードセレクタ等表示しない様にしておきます。
どのフィールドに対して曖昧検索するか、オプショングループ「op1」で選択できるようにします。
「op1」は、値 1 〜 6 を取るようにします。
オプショングループが変更された場合、文字の色を「赤」に変更したいかな・・・・・
ってんで、フォーム依存しない共通の処理なので、標準モジュール「Module1」に以下を記述しておきます。
Public Sub OpgMojiColor(ctlOpg As OptionGroup)
Dim ctl As Control
For Each ctl In ctlOpg.Controls
With ctl
Select Case .ControlType
Case acCheckBox, acOptionButton
If (.OptionValue = ctlOpg.Value) Then
.Controls(0).ForeColor = RGB(255, 0, 0)
Else
.Controls(0).ForeColor = RGB(0, 0, 0)
End If
Case acToggleButton
If (.OptionValue = ctlOpg.Value) Then
.ForeColor = RGB(255, 0, 0)
Else
.ForeColor = RGB(0, 0, 0)
End If
End Select
End With
Next
End Sub
Dim ctl As Control
For Each ctl In ctlOpg.Controls
With ctl
Select Case .ControlType
Case acCheckBox, acOptionButton
If (.OptionValue = ctlOpg.Value) Then
.Controls(0).ForeColor = RGB(255, 0, 0)
Else
.Controls(0).ForeColor = RGB(0, 0, 0)
End If
Case acToggleButton
If (.OptionValue = ctlOpg.Value) Then
.ForeColor = RGB(255, 0, 0)
Else
.ForeColor = RGB(0, 0, 0)
End If
End Select
End With
Next
End Sub
これは、チェックボックス/オプションボタンの構成の場合は、くっついているラベルの文字色を・・・・
なので、ラベルがくっついていないとエラーになります。
検索文字を入力する為のテキストボックス「txt1」を配置します。
その下にリストボックス「lst1」を配置します。
リストボックス「lst1」に表示する際、絞り込みをしていくわけですが、
値集合ソースを直接書き換える方法もありますが、今回は値集合ソースは固定してやってみます。
値集合ソースは
SELECT 商品ID, 商品CD, 商品名, 単価, 作成日, 備考
FROM T商品B
WHERE
IIF(IsNull([tx1]),True,商品ID Like '*' & [tx1] & '*') AND
IIF(IsNull([tx2]),True,商品CD Like '*' & [tx2] & '*') AND
IIF(IsNull([tx3]),True,商品名 Like '*' & [tx3] & '*') AND
IIF(IsNull([tx4]),True,単価 Like '*' & [tx4] & '*') AND
IIF(IsNull([tx5]),True,作成日 Like '*' & [tx5] & '*') AND
IIF(IsNull([tx6]),True,備考 Like '*' & [tx6] & '*')
ORDER BY 商品ID;
としておきます。FROM T商品B
WHERE
IIF(IsNull([tx1]),True,商品ID Like '*' & [tx1] & '*') AND
IIF(IsNull([tx2]),True,商品CD Like '*' & [tx2] & '*') AND
IIF(IsNull([tx3]),True,商品名 Like '*' & [tx3] & '*') AND
IIF(IsNull([tx4]),True,単価 Like '*' & [tx4] & '*') AND
IIF(IsNull([tx5]),True,作成日 Like '*' & [tx5] & '*') AND
IIF(IsNull([tx6]),True,備考 Like '*' & [tx6] & '*')
ORDER BY 商品ID;
つまり、オプショングループの値と連携した、非表示のテキストボックス「tx1」〜「tx6」を配置します。
オプショングループ「op1」が 1 なら、「tx1」に検索文字を設定しますよ・・・・
この時、同じフォーム上の為に名前が解決するのか、
[Forms]![F0_LB]![tx1] と記述しなくても良いようです。
連結列は 1 としておきます。列幅は相応に
VBAで記述したのは以下
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Dim i As Long
Call OpgMojiColor(Me.op1)
For i = 1 To 6
Me("tx" & i) = Null
Next
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = ""
End Sub
Private Sub txt1_Change()
Me("tx" & Me.op1) = Me.txt1.Text
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub lst1_DblClick(Cancel As Integer)
MsgBox "ダブルクリック: " & Me.lst1
End Sub
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Dim i As Long
Call OpgMojiColor(Me.op1)
For i = 1 To 6
Me("tx" & i) = Null
Next
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = ""
End Sub
Private Sub txt1_Change()
Me("tx" & Me.op1) = Me.txt1.Text
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub lst1_DblClick(Cancel As Integer)
MsgBox "ダブルクリック: " & Me.lst1
End Sub
※※ さて、ここでですが
オプショングループがクリックされた時、対象のフィールドのみ初期設定する為に
Me.txt1.SetFocus
Me.txt1.Text = "" ' ★
として txt1_Change を動かしているわけですが、★ の処理で戻ってきた後でなぜかエラーが発生します。Me.txt1.Text = "" ' ★

フォームが表示される前に1回通るのですが、その時にはエラーは発生せず。
フォーム表示後、オプショングループをクリックすると、その都度エラーとなるのです。
処理としてはチャンと動いているので、On Error Resume Next でエラーを無視するようにしました。
処理の仕方が悪い・・・・・等、ご指摘いただければと思います。
なお、オプショングループで切り換えた検索対象フィールドの初期表示は、Null 以外を表示するように。
メイン/サブ動作「F0_M」「F0_S」

フォーム「F0_LB」の表示に似せるために、そのフォームを「F0_M」名でコピーします。
リストボックス「lst1」を削除し、サブフォームコントロール「FSUB」を同じ大きさで作成します。
帳票フォームとなるサブフォームを絞り込む方法として、フォームの Filter を使うようにしました。
(サブフォームのレコードソースを書き換える方法もあるかと思いますが)
検索文字列を Filter に設定する際、前後の文字を op1_Click 時に作っておくように・・・・
それ用に、非表示で配置していたテキストボックス「tx1」「tx2」を使いましょうか・・・・
(VBA内の変数に作っておいても良いかも)
「tx3」〜「tx6」を削除します。
サブ用の帳票フォームが出来上がったら、それを「ソースオブジェクト」に設定して作成は完了になります。
記述したVBAは以下
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
Select Case Me.op1
Case 1: Me.tx1 = "商品ID Like '*": Me.tx2 = "*'"
Case 2: Me.tx1 = "商品CD Like '*": Me.tx2 = "*'"
Case 3: Me.tx1 = "商品名 Like '*": Me.tx2 = "*'"
Case 4: Me.tx1 = "単価 Like '*": Me.tx2 = "*'"
Case 5: Me.tx1 = "作成日 Like '*": Me.tx2 = "*'"
Case 6: Me.tx1 = "備考 Like '*": Me.tx2 = "*'"
End Select
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = ""
End Sub
Private Sub txt1_Change()
With Me.FSUB.Form
Call .init
.Filter = Me.tx1 & Me.txt1.Text & Me.tx2
.FilterOn = True
End With
End Sub
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
Select Case Me.op1
Case 1: Me.tx1 = "商品ID Like '*": Me.tx2 = "*'"
Case 2: Me.tx1 = "商品CD Like '*": Me.tx2 = "*'"
Case 3: Me.tx1 = "商品名 Like '*": Me.tx2 = "*'"
Case 4: Me.tx1 = "単価 Like '*": Me.tx2 = "*'"
Case 5: Me.tx1 = "作成日 Like '*": Me.tx2 = "*'"
Case 6: Me.tx1 = "備考 Like '*": Me.tx2 = "*'"
End Select
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = ""
End Sub
Private Sub txt1_Change()
With Me.FSUB.Form
Call .init
.Filter = Me.tx1 & Me.txt1.Text & Me.tx2
.FilterOn = True
End With
End Sub
帳票フォーム「F0_S」は、テーブル「T商品B」を元に、フォームウイザードで表形式作成。
レコードソースを変更して「商品ID」昇順を指定しておきます。
レコードセレクタ/移動ボタンを表示しないようにします。
誤ってレコード等をいじれない様に、追加/削除/更新の許可を「いいえ」にしておきます。
フォーム「F0_LB」のリストボックス表示の列幅に合わせて配置し直します。
ここからチョッと細工を・・・・
ヘッダ部に非表示のテキストボックス「txt1」を配置します。
詳細にある全テキストボックスの「タブストップ」を「いいえ」に変更します。
詳細にあるテキストボックス全てを覆うようにコマンドボタン「btn1」を配置します。
「透明」を「はい」として、タブ移動順を一番先頭に変更します。
リストボックス内をクリックした時に反転する処理は、全テキストボックスで条件付き書式を使用します。
この時の判定に、ヘッダ部に配置した「txt1」に、
コマンドボタン「btn1」がクリックされたところの「商品ID」を格納し、
「txt1」と表示行の「商品ID」が一致するかで・・・・
コマンドボタン「btn1」を配置したことで、下側のテキストボックスをクリックし辛くなったので
VBAで条件付き書式を設定するように。(デザイン時の操作でクリックしにくい)
また、リストボックスの選択を解除するマネとして、「txt1」をクリアする関数を Public にし、
メインで Filter をかける際に呼んでもらうように・・・・
記述したVBAは以下
Public Sub init()
Me.txt1 = 0
End Sub
Private Function initChk() As Boolean
initChk = Me.txt1 <> 0
End Function
Private Sub Form_Load()
Dim ctl As Control
For Each ctl In Me.Section(acDetail).Controls
If (ctl.ControlType = acTextBox) Then
ctl.FormatConditions.Delete
With ctl.FormatConditions.Add(acExpression, , "[txt1]=[商品ID]")
.BackColor = RGB(0, 0, 0)
.ForeColor = RGB(255, 255, 255)
End With
End If
Next
Call init
End Sub
Private Sub Form_Current()
If (initChk) Then Me.txt1 = Me.商品ID
End Sub
Private Sub btn1_Click()
Me.txt1 = Me.商品ID
Me.Recalc
End Sub
Private Sub btn1_DblClick(Cancel As Integer)
MsgBox "ダブルクリック: " & Me.商品ID
End Sub
Me.txt1 = 0
End Sub
Private Function initChk() As Boolean
initChk = Me.txt1 <> 0
End Function
Private Sub Form_Load()
Dim ctl As Control
For Each ctl In Me.Section(acDetail).Controls
If (ctl.ControlType = acTextBox) Then
ctl.FormatConditions.Delete
With ctl.FormatConditions.Add(acExpression, , "[txt1]=[商品ID]")
.BackColor = RGB(0, 0, 0)
.ForeColor = RGB(255, 255, 255)
End With
End If
Next
Call init
End Sub
Private Sub Form_Current()
If (initChk) Then Me.txt1 = Me.商品ID
End Sub
Private Sub btn1_Click()
Me.txt1 = Me.商品ID
Me.Recalc
End Sub
Private Sub btn1_DblClick(Cancel As Integer)
MsgBox "ダブルクリック: " & Me.商品ID
End Sub
これで、リストボックス風の動きになると思います。
表示しているデータは違いますが、2000 / 2003 で表示してみた感じは以下の様になります。

動きとして、
・レコードをクリックすると反転表示
その後、「↑」「↓」キーや「Page Up」「Page Down」でも移動できるかと思います。
また、リストボックスでの動きとは異なり、「tab」キーでも移動できます。
ただ、チョッとチラつきますね(大目にみるという事で)
なお、2000 だけ、「Page Up」「Page Down」で動きませんでした。
レコードを選択した・・・・・今回ダブルクリックを用いましたが、どこかに配置したボタンでも良いと思います。
連携確認
連結列「商品ID」その1
コンボボックス動作「F1_CHK」

コンボボックスの動きだけを確認するのではなく、コンボボックスのダブルクリックで、
リストボックス・メイン/サブで選択した値を受け取れるようにしてみました。
最終的には非連結のフォームになるのですが、
テーブル「T商品」を元にフォームウィザードで単票として作成します。
「レコードソース」を空白にします。
どれをコンボボックスに変更するかですが、「商品CD」を表示したいのかなぁ・・・・ということで、
テキストボックス「商品CD」のコントロールソースを空白にし、コンボボックスに変更します。
値集合ソースを
SELECT 商品ID, 商品CD, 商品名, 単価, 作成日, 備考 FROM T商品 ORDER BY 商品CD;
として、「商品ID」以外を表示するようにします。(「商品CD」で昇順に)連結列は 1 とします。列幅はリストボックスの設定値を参考にして・・・
他のテキストボックスの「コントロールソース」を変更し、
常にコンボボックス「商品CD」をみるように変更します。
「商品ID」なら、=[商品CD].[Column](0) と。
コンボボックス選択でじれったくなった場合、
・リストボックスのフォームから値をもらうのか
・メイン/サブの帳票表示フォームから値をもらうのか
選択するオプショングループ「op1」を配置します。
(コンボボックスでも、直に入力すれば先頭一致でそこんところに飛んでくれますね)
設定値として何をもらいたいのか tag に設定して、選択したフォームを起動します。
起動操作は、コンボボックス「商品CD」をダブルクリックした時・・・とします。
コンボボックスの連結列を 1 としたので、「商品ID」が欲しい tag = 0 を設定します。
(tag の値の取り決めとして、リストボックス(コンボボックス)に設定した Column(x) の x とする)
記述したのは以下
Private Sub Form_Load()
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
End Sub
Private Sub 商品CD_DblClick(Cancel As Integer)
Dim sFN As String
Select Case Me.op1
Case 1: sFN = "F1_LB"
Case 2: sFN = "F1_M"
Case Else
Exit Sub
End Select
Me.商品CD.Tag = 0
DoCmd.OpenForm sFN
Cancel = True
End Sub
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
End Sub
Private Sub 商品CD_DblClick(Cancel As Integer)
Dim sFN As String
Select Case Me.op1
Case 1: sFN = "F1_LB"
Case 2: sFN = "F1_M"
Case Else
Exit Sub
End Select
Me.商品CD.Tag = 0
DoCmd.OpenForm sFN
Cancel = True
End Sub
リストボックス動作「F1_LB」
フォーム「F0_LB」を「F1_LB」名でコピーします。
リストボックス「lst1」の対象テーブル名を「T商品B」から「T商品」に、「商品CD」の昇順に変更します。
値を戻す先を覚えておいて、覚えていたら設定してフォームを閉じます。
(値を戻す時、リストボックス表示の何列目を設定するかは設定先の Tag が持っている)
VBAで以下の黄色部分を追加/修正します。
Dim ctlRet As Control
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set ctlRet = Screen.ActiveControl
End Sub
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Dim i As Long
Call OpgMojiColor(Me.op1)
For i = 1 To 6
Me("tx" & i) = Null
Next
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = ""
End Sub
Private Sub txt1_Change()
Me("tx" & Me.op1) = Me.txt1.Text
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub lst1_DblClick(Cancel As Integer)
Me.Visible = False
If (Not ctlRet Is Nothing) Then ctlRet = Me.lst1.Column(ctlRet.Tag)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
Private Sub Form_Close()
Set ctlRet = Nothing
End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set ctlRet = Screen.ActiveControl
End Sub
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Dim i As Long
Call OpgMojiColor(Me.op1)
For i = 1 To 6
Me("tx" & i) = Null
Next
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = ""
End Sub
Private Sub txt1_Change()
Me("tx" & Me.op1) = Me.txt1.Text
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub lst1_DblClick(Cancel As Integer)
Me.Visible = False
If (Not ctlRet Is Nothing) Then ctlRet = Me.lst1.Column(ctlRet.Tag)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
Private Sub Form_Close()
Set ctlRet = Nothing
End Sub
メイン/サブ動作「F1_M」「F1_S」
フォーム「F0_M」を「F1_M」名でコピーします。
フォーム「F1_LB」と同様に、起動元とのやり取り部分を追加します。
ただ、「F1_LB」と異なるのは、ダブルクリックされるのはサブフォーム側であること・・・・
サブフォームに配置したコントロールのイベントを直接取得する方法もありますが、
今回は、サブフォーム側でダブルクリックを検知したら関数を呼んでもらうように・・・
その時、欲しいフィールド値をサブフォームから取得できるように・・・・
メイン側に LastCall 関数を用意し、
サブ側にリストボックス風の書き方になるように Column 関数を用意しました。
サブフォームコントロール「FSUB」のソースオブジェクトを変更したサブに変更します。
フォーム「F1_M」に記述したのは以下(「F0_M」に追加/変更した部分を黄色で)
Dim ctlRet As Control
Public Sub LastCall()
Me.Visible = False
If (Not ctlRet Is Nothing) Then ctlRet = Me.FSUB.Form.Column(ctlRet.Tag)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set ctlRet = Screen.ActiveControl
End Sub
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
Select Case Me.op1
Case 1: Me.tx1 = "商品ID Like '*": Me.tx2 = "*'"
Case 2: Me.tx1 = "商品CD Like '*": Me.tx2 = "*'"
Case 3: Me.tx1 = "商品名 Like '*": Me.tx2 = "*'"
Case 4: Me.tx1 = "単価 Like '*": Me.tx2 = "*'"
Case 5: Me.tx1 = "作成日 Like '*": Me.tx2 = "*'"
Case 6: Me.tx1 = "備考 Like '*": Me.tx2 = "*'"
End Select
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = ""
End Sub
Private Sub txt1_Change()
With Me.FSUB.Form
Call .init
.Filter = Me.tx1 & Me.txt1.Text & Me.tx2
.FilterOn = True
End With
End Sub
Private Sub Form_Close()
Set ctlRet = Nothing
End Sub
Public Sub LastCall()
Me.Visible = False
If (Not ctlRet Is Nothing) Then ctlRet = Me.FSUB.Form.Column(ctlRet.Tag)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set ctlRet = Screen.ActiveControl
End Sub
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
Select Case Me.op1
Case 1: Me.tx1 = "商品ID Like '*": Me.tx2 = "*'"
Case 2: Me.tx1 = "商品CD Like '*": Me.tx2 = "*'"
Case 3: Me.tx1 = "商品名 Like '*": Me.tx2 = "*'"
Case 4: Me.tx1 = "単価 Like '*": Me.tx2 = "*'"
Case 5: Me.tx1 = "作成日 Like '*": Me.tx2 = "*'"
Case 6: Me.tx1 = "備考 Like '*": Me.tx2 = "*'"
End Select
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = ""
End Sub
Private Sub txt1_Change()
With Me.FSUB.Form
Call .init
.Filter = Me.tx1 & Me.txt1.Text & Me.tx2
.FilterOn = True
End With
End Sub
Private Sub Form_Close()
Set ctlRet = Nothing
End Sub
フォーム「F0_S」を「F1_S」名でコピーします。
レコードソースの対象テーブル名を「T商品B」から「T商品」に、「商品CD」の昇順に変更します。
また、コマンドボタン「btn1」がダブルクリックされた時点でメインの関数を呼び出すので、
単独起動後ダブルクリックされると必然とエラーが発生するので、
単独起動されない様に Form_Open で起動可否判別を追加・・・
(過去記事でも、いろいろと判別方法を変えながら書いてました。で、今回の判別はこれで・・・)
フォーム「F1_S」に記述したのは以下(「F0_S」に追加/変更した部分を黄色で)
Public Function Column(iNum As Long) As Variant
Select Case iNum
Case 0: Column = Me.商品ID.Value
Case 1: Column = Me.商品CD.Value
Case 2: Column = Me.商品名.Value
Case 3: Column = Me.単価.Value
Case 4: Column = Me.作成日.Value
Case 5: Column = Me.備考.Value
Case Else
Column = Null
End Select
End Function
Public Sub init()
Me.txt1 = 0
End Sub
Private Function initChk() As Boolean
initChk = Me.txt1 <> 0
End Function
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_Load()
Dim ctl As Control
For Each ctl In Me.Section(acDetail).Controls
If (ctl.ControlType = acTextBox) Then
ctl.FormatConditions.Delete
With ctl.FormatConditions.Add(acExpression, , "[txt1]=[商品ID]")
.BackColor = RGB(0, 0, 0)
.ForeColor = RGB(255, 255, 255)
End With
End If
Next
Call init
End Sub
Private Sub Form_Current()
If (initChk) Then Me.txt1 = Me.商品ID
End Sub
Private Sub btn1_Click()
Me.txt1 = Me.商品ID
Me.Recalc
End Sub
Private Sub btn1_DblClick(Cancel As Integer)
Call Me.Parent.LastCall
End Sub
Select Case iNum
Case 0: Column = Me.商品ID.Value
Case 1: Column = Me.商品CD.Value
Case 2: Column = Me.商品名.Value
Case 3: Column = Me.単価.Value
Case 4: Column = Me.作成日.Value
Case 5: Column = Me.備考.Value
Case Else
Column = Null
End Select
End Function
Public Sub init()
Me.txt1 = 0
End Sub
Private Function initChk() As Boolean
initChk = Me.txt1 <> 0
End Function
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_Load()
Dim ctl As Control
For Each ctl In Me.Section(acDetail).Controls
If (ctl.ControlType = acTextBox) Then
ctl.FormatConditions.Delete
With ctl.FormatConditions.Add(acExpression, , "[txt1]=[商品ID]")
.BackColor = RGB(0, 0, 0)
.ForeColor = RGB(255, 255, 255)
End With
End If
Next
Call init
End Sub
Private Sub Form_Current()
If (initChk) Then Me.txt1 = Me.商品ID
End Sub
Private Sub btn1_Click()
Me.txt1 = Me.商品ID
Me.Recalc
End Sub
Private Sub btn1_DblClick(Cancel As Integer)
Call Me.Parent.LastCall
End Sub
連結列「商品CD」
コンボボックス動作「F2_CHK」
フォーム「F1_CHK」を「F2_CHK」名でコピーします。
サンプルテーブル「T商品」の「商品CD」に重複がなかったようなので、
他フォームから設定される値を「商品CD」としてみます。
コンボボックスの「連結列」を 2 に変更し、VBA記述以下を変更します。
(戻り値に「商品CD」が欲しいことを、tag に設定するだけ)
Private Sub Form_Load()
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
End Sub
Private Sub 商品CD_DblClick(Cancel As Integer)
Dim sFN As String
Select Case Me.op1
Case 1: sFN = "F1_LB"
Case 2: sFN = "F1_M"
Case Else
Exit Sub
End Select
Me.商品CD.Tag = 1
DoCmd.OpenForm sFN
Cancel = True
End Sub
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
End Sub
Private Sub 商品CD_DblClick(Cancel As Integer)
Dim sFN As String
Select Case Me.op1
Case 1: sFN = "F1_LB"
Case 2: sFN = "F1_M"
Case Else
Exit Sub
End Select
Me.商品CD.Tag = 1
DoCmd.OpenForm sFN
Cancel = True
End Sub
連結列「商品ID」その2
ここでは、F0 / F1 系で曖昧検索する時には常に前後が曖昧で検索されていました。
検索する文字を入力するところで、自分でどの部分を曖昧にするか指定したい・・・・
頭が「S」で最後が「7」だった・・・・「 S*7 」これを直接指定したい・・・ということで
コンボボックス動作「F3_CHK」
フォーム「F1_CHK」を「F3_CHK」名でコピーします。
起動するフォーム名を変更するだけです
Private Sub Form_Load()
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
End Sub
Private Sub 商品CD_DblClick(Cancel As Integer)
Dim sFN As String
Select Case Me.op1
Case 1: sFN = "F3_LB"
Case 2: sFN = "F3_M"
Case Else
Exit Sub
End Select
Me.商品CD.Tag = 0
DoCmd.OpenForm sFN
Cancel = True
End Sub
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
End Sub
Private Sub 商品CD_DblClick(Cancel As Integer)
Dim sFN As String
Select Case Me.op1
Case 1: sFN = "F3_LB"
Case 2: sFN = "F3_M"
Case Else
Exit Sub
End Select
Me.商品CD.Tag = 0
DoCmd.OpenForm sFN
Cancel = True
End Sub
リストボックス動作「F3_LB」
フォーム「F1_LB」を「F3_LB」名でコピーします。
リストボックス「lst1」の値集合ソースを変更します。
SELECT 商品ID, 商品CD, 商品名, 単価, 作成日, 備考
FROM T商品
WHERE
IIF(IsNull([tx1]),True,商品ID Like [tx1]) AND
IIF(IsNull([tx2]),True,商品CD Like [tx2]) AND
IIF(IsNull([tx3]),True,商品名 Like [tx3]) AND
IIF(IsNull([tx4]),True,単価 Like [tx4]) AND
IIF(IsNull([tx5]),True,作成日 Like [tx5]) AND
IIF(IsNull([tx6]),True,備考 Like [tx6])
ORDER BY 商品CD;
FROM T商品
WHERE
IIF(IsNull([tx1]),True,商品ID Like [tx1]) AND
IIF(IsNull([tx2]),True,商品CD Like [tx2]) AND
IIF(IsNull([tx3]),True,商品名 Like [tx3]) AND
IIF(IsNull([tx4]),True,単価 Like [tx4]) AND
IIF(IsNull([tx5]),True,作成日 Like [tx5]) AND
IIF(IsNull([tx6]),True,備考 Like [tx6])
ORDER BY 商品CD;
VBAでは、以下の黄色部分を変更します。
Dim ctlRet As Control
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set ctlRet = Screen.ActiveControl
End Sub
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Dim i As Long
Call OpgMojiColor(Me.op1)
For i = 1 To 6
Me("tx" & i) = Null
Next
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = "**"
Me.txt1.SelStart = 1
End Sub
Private Sub txt1_Change()
Me("tx" & Me.op1) = Me.txt1.Text
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub lst1_DblClick(Cancel As Integer)
Me.Visible = False
If (Not ctlRet Is Nothing) Then ctlRet = Me.lst1.Column(ctlRet.Tag)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
Private Sub Form_Close()
Set ctlRet = Nothing
End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set ctlRet = Screen.ActiveControl
End Sub
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Dim i As Long
Call OpgMojiColor(Me.op1)
For i = 1 To 6
Me("tx" & i) = Null
Next
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = "**"
Me.txt1.SelStart = 1
End Sub
Private Sub txt1_Change()
Me("tx" & Me.op1) = Me.txt1.Text
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub lst1_DblClick(Cancel As Integer)
Me.Visible = False
If (Not ctlRet Is Nothing) Then ctlRet = Me.lst1.Column(ctlRet.Tag)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
Private Sub Form_Close()
Set ctlRet = Nothing
End Sub
メイン/サブ動作「F3_M」「F3_S」
フォーム「F1_S」を「F3_S」名でコピーします。
(変更箇所はないので、そのままでも良かったんですが・・・・)
フォーム「F1_M」を「F3_M」名でコピーします。
サブフォームコントロール「FSUB」のソースオブジェクトを「F3_S」に変更します。
以下黄色部分を変更します。
Dim ctlRet As Control
Public Sub LastCall()
Me.Visible = False
If (Not ctlRet Is Nothing) Then ctlRet = Me.FSUB.Form.Column(ctlRet.Tag)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set ctlRet = Screen.ActiveControl
End Sub
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
Select Case Me.op1
Case 1: Me.tx1 = "商品ID Like '": Me.tx2 = "'"
Case 2: Me.tx1 = "商品CD Like '": Me.tx2 = "'"
Case 3: Me.tx1 = "商品名 Like '": Me.tx2 = "'"
Case 4: Me.tx1 = "単価 Like '": Me.tx2 = "'"
Case 5: Me.tx1 = "作成日 Like '": Me.tx2 = "'"
Case 6: Me.tx1 = "備考 Like '": Me.tx2 = "'"
End Select
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = "**"
Me.txt1.SelStart = 1
End Sub
Private Sub txt1_Change()
With Me.FSUB.Form
Call .init
.Filter = Me.tx1 & Me.txt1.Text & Me.tx2
.FilterOn = True
End With
End Sub
Private Sub Form_Close()
Set ctlRet = Nothing
End Sub
Public Sub LastCall()
Me.Visible = False
If (Not ctlRet Is Nothing) Then ctlRet = Me.FSUB.Form.Column(ctlRet.Tag)
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
Set ctlRet = Screen.ActiveControl
End Sub
Private Sub Form_Load()
Me.op1 = 2
Call op1_Click
End Sub
Private Sub op1_Click()
Call OpgMojiColor(Me.op1)
Select Case Me.op1
Case 1: Me.tx1 = "商品ID Like '": Me.tx2 = "'"
Case 2: Me.tx1 = "商品CD Like '": Me.tx2 = "'"
Case 3: Me.tx1 = "商品名 Like '": Me.tx2 = "'"
Case 4: Me.tx1 = "単価 Like '": Me.tx2 = "'"
Case 5: Me.tx1 = "作成日 Like '": Me.tx2 = "'"
Case 6: Me.tx1 = "備考 Like '": Me.tx2 = "'"
End Select
On Error Resume Next
Me.txt1.SetFocus
Me.txt1.Text = "**"
Me.txt1.SelStart = 1
End Sub
Private Sub txt1_Change()
With Me.FSUB.Form
Call .init
.Filter = Me.tx1 & Me.txt1.Text & Me.tx2
.FilterOn = True
End With
End Sub
Private Sub Form_Close()
Set ctlRet = Nothing
End Sub
サンプルデータ作成
標準モジュール「Module2」にある関数 MakeTableData を実行します
Public Sub MakeTableData()
If (MakeMain(1000)) Then ' 作成レコード数が引数
MsgBox "データ作成成功", vbInformation
Else
MsgBox "データ作成失敗" & vbCrLf & MakeMain(0), vbCritical
End If
End Sub
Private Function MakeMain(iNum As Long) As Variant
Static sMsg As String
Dim rs As New ADODB.Recordset
Dim iErrCnt As Long
Dim i As Long
Const iErrMax As Long = 3
If (iNum < 1) Then
MakeMain = sMsg
Exit Function
End If
sMsg = ""
Randomize
iErrCnt = 0
Do While (iErrCnt < iErrMax)
On Error GoTo ERR_HND
CurrentProject.Connection.Execute "DELETE * FROM T商品;"
rs.Open "T商品", CurrentProject.Connection, adOpenForwardOnly, adLockOptimistic
For i = 1 To iNum
rs.AddNew
rs("商品ID") = MakeRec1
rs("商品CD") = MakeRec2
rs("商品名") = MakeRec3
rs("単価") = MakeRec4
rs("作成日") = MakeRec5
rs("備考") = MakeRec6
rs.Update
Next
rs.Close
Exit Do
ERR_HND:
If (Err = -2147217887) Then
Debug.Print "重複 商品ID = " & rs("商品ID") ' サンプルファイルには記述ないかも
rs("商品ID") = rs("商品ID") + 1
Resume
End If
sMsg = Err.Number & " : " & Err.Description
iErrCnt = iErrCnt + 1
Resume ERR_NEXT
ERR_NEXT:
On Error Resume Next
If (rs.State = adStateOpen) Then
If (rs.EditMode = adEditAdd) Then rs.CancelUpdate
rs.Close
End If
Loop
MakeMain = iErrCnt < iErrMax
End Function
Private Function MakeRec1() As Long
MakeRec1 = Int(Rnd() * 99999) + 1
End Function
Private Function MojiOne() As String
MojiOne = Chr(Asc("A") + Int(Rnd() * (Asc("Z") - Asc("A") + 1)))
End Function
Private Function MakeRec2() As String
MakeRec2 = MojiOne & MojiOne & Format(Int(Rnd() * 99999) + 1, "00000")
End Function
Private Function MakeRec3() As String
Dim sS As String
Dim iCnt As Long
Dim i As Long
iCnt = Int(Rnd() * 11) + 5
sS = ""
For i = 1 To iCnt
sS = sS & MojiOne
Next
MakeRec3 = sS
End Function
Private Function MakeRec4() As Currency
MakeRec4 = (Int(Rnd() * 999) + 1) * 10
End Function
Private Function MakeRec5() As Date
MakeRec5 = DateAdd("d", -Int(Rnd() * 3650), Date)
End Function
Private Function MakeRec6() As Variant
Dim vAry As Variant
Dim sS As String
Dim iCnt As Long
Dim i As Long
MakeRec6 = Null
If (Int(Rnd() + 0.2) = 0) Then Exit Function
vAry = Array("北海道", "青森", "岩手", "秋田", "宮城" _
, "山形", "福島", "宮崎", "富山", "東京", "京都")
iCnt = Int(Rnd() * 3) + 1
sS = ""
For i = 1 To iCnt
sS = sS & " " & vAry(Int(Rnd() * (UBound(vAry) + 1)))
Next
MakeRec6 = Trim(sS)
End Function
If (MakeMain(1000)) Then ' 作成レコード数が引数
MsgBox "データ作成成功", vbInformation
Else
MsgBox "データ作成失敗" & vbCrLf & MakeMain(0), vbCritical
End If
End Sub
Private Function MakeMain(iNum As Long) As Variant
Static sMsg As String
Dim rs As New ADODB.Recordset
Dim iErrCnt As Long
Dim i As Long
Const iErrMax As Long = 3
If (iNum < 1) Then
MakeMain = sMsg
Exit Function
End If
sMsg = ""
Randomize
iErrCnt = 0
Do While (iErrCnt < iErrMax)
On Error GoTo ERR_HND
CurrentProject.Connection.Execute "DELETE * FROM T商品;"
rs.Open "T商品", CurrentProject.Connection, adOpenForwardOnly, adLockOptimistic
For i = 1 To iNum
rs.AddNew
rs("商品ID") = MakeRec1
rs("商品CD") = MakeRec2
rs("商品名") = MakeRec3
rs("単価") = MakeRec4
rs("作成日") = MakeRec5
rs("備考") = MakeRec6
rs.Update
Next
rs.Close
Exit Do
ERR_HND:
If (Err = -2147217887) Then
Debug.Print "重複 商品ID = " & rs("商品ID") ' サンプルファイルには記述ないかも
rs("商品ID") = rs("商品ID") + 1
Resume
End If
sMsg = Err.Number & " : " & Err.Description
iErrCnt = iErrCnt + 1
Resume ERR_NEXT
ERR_NEXT:
On Error Resume Next
If (rs.State = adStateOpen) Then
If (rs.EditMode = adEditAdd) Then rs.CancelUpdate
rs.Close
End If
Loop
MakeMain = iErrCnt < iErrMax
End Function
Private Function MakeRec1() As Long
MakeRec1 = Int(Rnd() * 99999) + 1
End Function
Private Function MojiOne() As String
MojiOne = Chr(Asc("A") + Int(Rnd() * (Asc("Z") - Asc("A") + 1)))
End Function
Private Function MakeRec2() As String
MakeRec2 = MojiOne & MojiOne & Format(Int(Rnd() * 99999) + 1, "00000")
End Function
Private Function MakeRec3() As String
Dim sS As String
Dim iCnt As Long
Dim i As Long
iCnt = Int(Rnd() * 11) + 5
sS = ""
For i = 1 To iCnt
sS = sS & MojiOne
Next
MakeRec3 = sS
End Function
Private Function MakeRec4() As Currency
MakeRec4 = (Int(Rnd() * 999) + 1) * 10
End Function
Private Function MakeRec5() As Date
MakeRec5 = DateAdd("d", -Int(Rnd() * 3650), Date)
End Function
Private Function MakeRec6() As Variant
Dim vAry As Variant
Dim sS As String
Dim iCnt As Long
Dim i As Long
MakeRec6 = Null
If (Int(Rnd() + 0.2) = 0) Then Exit Function
vAry = Array("北海道", "青森", "岩手", "秋田", "宮城" _
, "山形", "福島", "宮崎", "富山", "東京", "京都")
iCnt = Int(Rnd() * 3) + 1
sS = ""
For i = 1 To iCnt
sS = sS & " " & vAry(Int(Rnd() * (UBound(vAry) + 1)))
Next
MakeRec6 = Trim(sS)
End Function
エラー時の処理をチョッと盛り込んでみましたが・・・・・
(でも、あまりこういう書き方はしないのかなっっと。 動きました、というレベルでしょうか・・・)
なお、記事によっては同じことをする時でも書き方を変えてみたりしていますので、
1つの記事を読んで、書き方はこう、という判断はしないでください。
参考にする等々、すべて自己責任でお願いします。
余談
テーブル「T商品」にテストデータを作った後、連続する「商品ID」があるか ??・・・・
これ、クエリを作って確認していたのですが、作り方によっては表示までの時間が違いますね・・・・
遅かったのは、
SELECT * FROM T商品 AS A WHERE
EXISTS (SELECT 1 FROM T商品 AS B WHERE (B.商品ID = A.商品ID+1) OR (B.商品ID = A.商品ID-1))
ORDER BY 商品ID;
EXISTS (SELECT 1 FROM T商品 AS B WHERE (B.商品ID = A.商品ID+1) OR (B.商品ID = A.商品ID-1))
ORDER BY 商品ID;
そこそこ速かったのは
SELECT * FROM T商品 AS A WHERE
EXISTS (SELECT 1 FROM T商品 AS B WHERE B.商品ID = A.商品ID+1) OR
EXISTS (SELECT 1 FROM T商品 AS B WHERE B.商品ID = A.商品ID-1)
ORDER BY 商品ID;
EXISTS (SELECT 1 FROM T商品 AS B WHERE B.商品ID = A.商品ID+1) OR
EXISTS (SELECT 1 FROM T商品 AS B WHERE B.商品ID = A.商品ID-1)
ORDER BY 商品ID;
今回の収穫でした。
もっといい方法知ってるよ・・・・教えてください。
| サンプルは以下 | ||||||||||||
| ||||||||||||
| ※ ファイルは zip 形式 | ||||||||||||
| ※ 2007 以外は、2007 保存時に変換 & 各バージョンで動作確認 & 最適化 |
≪--- 続きを閉じちゃえ
2012/04/09
Category: サンプルかな
Form_Delete 以降のイベント
ある QA で、
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
End Sub
と、確認用 MsgBox を記述してみたが、Form_Delete 以降のイベントが発生しない
(Form_BeforeDelConfirm / Form_AfterDelConfirm の MsgBox が表示されない)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
End Sub
と、確認用 MsgBox を記述してみたが、Form_Delete 以降のイベントが発生しない
(Form_BeforeDelConfirm / Form_AfterDelConfirm の MsgBox が表示されない)
という事がありました。
過去記事でも、削除の操作を更新操作に置換えるために上記イベントを使っていました。
どのような構成にすると、イベントは発生しなくなるんだろうか・・・・
これを考えてみました。
また、実際には子(サブ)で削除されたタイミングで、親(メイン)の表示を変更したいという事で、
自力で処理するようにしてみてはどうだろうか・・・・というものも・・・
確認するフォームは親子(メイン/サブ)構成のフォームで、
親(メイン):非連結 子(サブ)の合計を表示するもの
子(サブ):連結の帳票フォーム

確認のパターンは以下の6つ 以下()内の記述は(親フォーム名/子フォーム名)
0)イベント確認 (F_T0M / F_T0)
1)クラス 改良前 (F_T1M / F_T1)
2)自力 改良前 (F_T2M / F_T2)
3)クラス 改良後 (F_T3M / F_T3)
4)自力 改良後 (F_T4M / F_T4)
5)クラス 改良その2 (F_T5M / F_T5)
なお、上記画像はフォーム「F_ALL」で、オプショングループで対象サブフォームを切り替えるもの
確認操作は、
・サブフォームにレコードを追加して
・レコードセレクタを使って
・「Delete」キーで削除してみる
という流れになります。
続きを読んでみようかな ---≫
Form_Delete 以降が呼ばれない・・・・
これ、単純に考えると、Form_Delete のパラメータ Cancel に True を設定することで
以降のイベントは発生しないようですが、Cancel を True に設定しているわけではない。
考えられるのは、Form_Delete のイベントを誰かが検知して Cancel = True としているのでは・・・
考えやすいのは、クラスを作って、そのクラスの中でイベントを取り込んで・・・・
処理を統一したいとか・・・の場合、クラスを作ってやると各フォームでの記述は見やすくなりますね。
また、VBAで直にレコードを削除した時には Form_Delete は呼ばれないようです。
自力で削除した後で、Form_Delete を呼び出す・・・・これは結構考えにくいのでしょうか。
クラス側から Private Sub Form_Delete(Cancel As Integer) を呼び出せなかった??
同じフォーム内で自力削除して、Form_Delete を呼び出す・・・・
これだったら、Form_Delete のタイミングを使いたいってことにはならない・・・・と思います。
連結表示する為のテーブル「T1」を用意します。
an:オートナンバー
商品名:テキスト型
単価:通貨型(書式:通貨)
数量:通貨型(書式:数値)
フォームウィザードを使って、表形式で作成します。
「単価*数量」表示用のテキストボックスを作成し、コントロールソースを =[単価]*[数量] とします。
総合計表示用のテキストボックス「txt1」をヘッダ部分に作成し、
コントロールソースを =Sum([単価]*[数量]) とします。
「an」と上記2つのテキストボックスは表示だけで良いので、
「編集ロック」を「はい」、「使用可能」を「いいえ」に変更します。
サブフォーム用フォームの見栄えはこれで完了です。(もうチョッとあるかも)
上記フォームをサブフォームとする親フォームをデザインから作成します。
何行目を削除するか・・・・
これを指定するテキストボックス「txt0」コマンドボタン「btn1」を左側上に
サブフォームの総合計を表示するものを右側上に
(コントロールソースでサブフォームの総合計「txt1」を参照するように)
※ 実際の QA では、サブフォームで削除した時、親フォームの関数を呼び出し、
その関数内で DSum 結果をテキストボックスに設定・・・・という流れでしたが、今回はこれで
サブフォームコントロール「FSUB」を配置し、ソースオブジェクトにサブ用フォームを指定
これで、親(メイン)フォームのベースは完了です。
親(メイン)フォームへのVBA記述は共通です。
何行目の削除は、何行目・・・・の「an」を求めて、自力削除。
その後、サブフォームからも呼ばれる可能性のあるサブフォームの再クエリを・・・・
※ このサブフォームの再クエリで、
通常(?)は
0)イベント確認 (F_T0M / F_T0)
ここでは、イベントの発生を確認してみます
サブフォームに記述したのは以下
起動時、サブフォームとして組み込まれていなければ立ち上がらないようにしておいて・・・
各イベントで MsgBox 表示するように・・・
Access さんからの「○○件削除しますか」メッセージは、Form_BeforeDelConfirm 後ですよね。
1)クラス 改良前 (F_T1M / F_T1)
誰かが Form_Delete のイベントを検知して、別途処理しているのでは・・・・
ということで、クラスの構成を作ってみました。
※ 一応、回答してみたのはこの1)と2)になりますが、新規行が選ばれていた場合正常に動きません。
新規行も選ばれていた場合でも動作するものにしたのが、3)4)になります。
なので、1)と3)、2)と4)は大差ありません。
クラス「clsFrm」として記述したのは以下
クラスを New された時点で、どのフォームで・・・・を覚えておき、
そのフォームの「レコード削除時」( Delete )イベントを受け取るようにします。
frm_Delete は選択されたレコードセレクタ数分呼ばれます。(新規行分除く)
サブフォームで記述した Form_Delete と、クラスで記述した frm_Delete がどの順で呼ばれるか・・・
これを分かりやすくするために、最初でメッセージを出して、最後で削除処理するようにしています。
また、変数 iCount は何をやっているのか説明しなくても読めばわかるかな・・・・って
また、最後に呼ばれた frm_Delete のタイミングでレコードをいじりたい・・・
Cancel = True で戻る前にやりたい!!。
CancelEvent なら発行した時点で Cancel 動作させることが出来るのでは・・・
で、やってみたら Accessさんに怒られることもなく、できてしまった。
※ Cancel = True で戻るパターンは、後述5)にて
サブフォームに記述したのは以下
ここで、注意する箇所が1つ。
サンプル上ではコメントにしましたが、
2003 / 2007 では正常に処理されますが、2000 では Form_Close からAccessさんに戻った時に
Accessさんがエラー終了してしまいます。
Form_Close を Form_Unload に変更しても同様でした。
クラスの
フォームを閉じただけでは、Class_Terminate は呼ばれないようです。
(確認の仕方が悪かったのかも??)
そもそも、Set frm = Nothing を気にしなくても良い???
2)自力 改良前 (F_T2M / F_T2)
ここでは、Form_Delete 発生条件である「Delete」キーのイベントを取り込んで、
Accessさんに削除処理自体をさせない・・・・
削除処理自体がなくなるので、1)のようなクラスを作っていても動かないはず・・・
サブフォームに記述したのは以下
3)クラス 改良後 (F_T3M / F_T3)
1)の改良版で、レコードセレクタで選択した中に新規行があっても動くように・・・
また、メッセージの表示タイミングを、最後に呼ばれた時に変更してみました。
クラス「clsFrmKai」として記述したのは以下
サブフォームに記述したのは以下
4)自力 改良後 (F_T4M / F_T4)
サブフォームに記述したのは以下
5)クラス 改良その2 (F_T5M / F_T5)
このクラスでは、frm_Delete で Cancel = True を返すようにし、
削除操作は「タイマ時」を利用するように・・・・
クラス「clsFrmKai2」として記述したのは以下
サブフォームに記述したのは以下
6)フォーム「F_ALL」
前述してきた 親(メイン)からのサブフォーム参照 / 子(サブ)からの親フォーム参照
これ、全ての確認パターンで共通になっているので、サブフォームをコロコロと切り換える・・・
この切り替えをオプショングループ「op1」を使って操作できるように・・・・
以下の黄色い部分が追加した記述になります。
サブフォームコントロール「FSUB」にくっ付いているラベルの表示を変えながら、
ソースオブジェクトを切り替えます。
※ テキストボックスにくっ付いているラベル表示変更は
Me.テキストボックス名.Controls(0).Caption で設定先がわかりますが、
サブフォームコントロールでは上記の様な辿り方はできないようです。
Controls(0) としてしまうと、サブフォーム内のコントロールが求まってしまうので・・・・
※※ 削除操作で Form_BeforeDelConfirm / Form_AfterDelConfirm が呼ばれない
これ、上記以外に推測/想定できますでしょうか・・・・
※※※ こういう想定が出来る・・・云々、教えてください。
≪--- 続きを閉じちゃえ
Form_Delete 以降が呼ばれない・・・・
これ、単純に考えると、Form_Delete のパラメータ Cancel に True を設定することで
以降のイベントは発生しないようですが、Cancel を True に設定しているわけではない。
考えられるのは、Form_Delete のイベントを誰かが検知して Cancel = True としているのでは・・・
考えやすいのは、クラスを作って、そのクラスの中でイベントを取り込んで・・・・
処理を統一したいとか・・・の場合、クラスを作ってやると各フォームでの記述は見やすくなりますね。
また、VBAで直にレコードを削除した時には Form_Delete は呼ばれないようです。
自力で削除した後で、Form_Delete を呼び出す・・・・これは結構考えにくいのでしょうか。
クラス側から Private Sub Form_Delete(Cancel As Integer) を呼び出せなかった??
同じフォーム内で自力削除して、Form_Delete を呼び出す・・・・
これだったら、Form_Delete のタイミングを使いたいってことにはならない・・・・と思います。
連結表示する為のテーブル「T1」を用意します。
an:オートナンバー
商品名:テキスト型
単価:通貨型(書式:通貨)
数量:通貨型(書式:数値)
フォームウィザードを使って、表形式で作成します。
「単価*数量」表示用のテキストボックスを作成し、コントロールソースを =[単価]*[数量] とします。
総合計表示用のテキストボックス「txt1」をヘッダ部分に作成し、
コントロールソースを =Sum([単価]*[数量]) とします。
「an」と上記2つのテキストボックスは表示だけで良いので、
「編集ロック」を「はい」、「使用可能」を「いいえ」に変更します。
サブフォーム用フォームの見栄えはこれで完了です。(もうチョッとあるかも)
上記フォームをサブフォームとする親フォームをデザインから作成します。
何行目を削除するか・・・・
これを指定するテキストボックス「txt0」コマンドボタン「btn1」を左側上に
サブフォームの総合計を表示するものを右側上に
(コントロールソースでサブフォームの総合計「txt1」を参照するように)
※ 実際の QA では、サブフォームで削除した時、親フォームの関数を呼び出し、
その関数内で DSum 結果をテキストボックスに設定・・・・という流れでしたが、今回はこれで
サブフォームコントロール「FSUB」を配置し、ソースオブジェクトにサブ用フォームを指定
これで、親(メイン)フォームのベースは完了です。
親(メイン)フォームへのVBA記述は共通です。
Private Sub btn1_Click()
If (IsNull(Me.txt0)) Then Exit Sub
With Me.FSUB.Form.RecordsetClone
If (Me.txt0 > .RecordCount) Then Exit Sub
.AbsolutePosition = Me.txt0 - 1
CurrentProject.Connection.Execute "DELETE * FROM T1 WHERE an = " & .Fields("an")
Call Req
End With
End Sub
Public Sub Req()
' Me.FSUB.Form.Requery
Me.FSUB.Requery
End Sub
If (IsNull(Me.txt0)) Then Exit Sub
With Me.FSUB.Form.RecordsetClone
If (Me.txt0 > .RecordCount) Then Exit Sub
.AbsolutePosition = Me.txt0 - 1
CurrentProject.Connection.Execute "DELETE * FROM T1 WHERE an = " & .Fields("an")
Call Req
End With
End Sub
Public Sub Req()
' Me.FSUB.Form.Requery
Me.FSUB.Requery
End Sub
何行目の削除は、何行目・・・・の「an」を求めて、自力削除。
その後、サブフォームからも呼ばれる可能性のあるサブフォームの再クエリを・・・・
※ このサブフォームの再クエリで、
Me.FSUB.Requery
とすると表示を先頭に戻すことなく、再クエリ動作させることが出来るようです。通常(?)は
' Me.FSUB.Form.Requery
の方だと思います。0)イベント確認 (F_T0M / F_T0)
ここでは、イベントの発生を確認してみます
サブフォームに記述したのは以下
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
起動時、サブフォームとして組み込まれていなければ立ち上がらないようにしておいて・・・
各イベントで MsgBox 表示するように・・・
Access さんからの「○○件削除しますか」メッセージは、Form_BeforeDelConfirm 後ですよね。
1)クラス 改良前 (F_T1M / F_T1)
誰かが Form_Delete のイベントを検知して、別途処理しているのでは・・・・
ということで、クラスの構成を作ってみました。
※ 一応、回答してみたのはこの1)と2)になりますが、新規行が選ばれていた場合正常に動きません。
新規行も選ばれていた場合でも動作するものにしたのが、3)4)になります。
なので、1)と3)、2)と4)は大差ありません。
クラス「clsFrm」として記述したのは以下
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Private WithEvents frm As Form
Private iCount As Long
Private bDel As Boolean
Private Sub Class_Initialize()
Set frm = CodeContextObject
frm.OnDelete = EVENT_PROCEDURE
iCount = -1
bDel = False
End Sub
Private Sub Class_Terminate()
Set frm = Nothing
End Sub
Private Sub frm_Delete(Cancel As Integer)
Dim i As Long
DoCmd.CancelEvent
If (frm.SelHeight < 1) Then Exit Sub
If (iCount < 1) Then iCount = frm.SelHeight
If (iCount = frm.SelHeight) Then
bDel = MsgBox(frm.SelHeight & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes
End If
iCount = iCount - 1
If (iCount = 0) Then
If (bDel) Then
With frm.Recordset
For i = 1 To frm.SelHeight
.Delete
.MoveNext
Next
End With
frm.SelHeight = 0
End If
bDel = False
End If
End Sub
Private WithEvents frm As Form
Private iCount As Long
Private bDel As Boolean
Private Sub Class_Initialize()
Set frm = CodeContextObject
frm.OnDelete = EVENT_PROCEDURE
iCount = -1
bDel = False
End Sub
Private Sub Class_Terminate()
Set frm = Nothing
End Sub
Private Sub frm_Delete(Cancel As Integer)
Dim i As Long
DoCmd.CancelEvent
If (frm.SelHeight < 1) Then Exit Sub
If (iCount < 1) Then iCount = frm.SelHeight
If (iCount = frm.SelHeight) Then
bDel = MsgBox(frm.SelHeight & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes
End If
iCount = iCount - 1
If (iCount = 0) Then
If (bDel) Then
With frm.Recordset
For i = 1 To frm.SelHeight
.Delete
.MoveNext
Next
End With
frm.SelHeight = 0
End If
bDel = False
End If
End Sub
クラスを New された時点で、どのフォームで・・・・を覚えておき、
そのフォームの「レコード削除時」( Delete )イベントを受け取るようにします。
frm_Delete は選択されたレコードセレクタ数分呼ばれます。(新規行分除く)
サブフォームで記述した Form_Delete と、クラスで記述した frm_Delete がどの順で呼ばれるか・・・
これを分かりやすくするために、最初でメッセージを出して、最後で削除処理するようにしています。
また、変数 iCount は何をやっているのか説明しなくても読めばわかるかな・・・・って
DoCmd.CancelEvent
としているのは、Accessさんが削除処理している間、そのレコードをいじるとエラーになって・・・また、最後に呼ばれた frm_Delete のタイミングでレコードをいじりたい・・・
Cancel = True で戻る前にやりたい!!。
CancelEvent なら発行した時点で Cancel 動作させることが出来るのでは・・・
で、やってみたら Accessさんに怒られることもなく、できてしまった。
※ Cancel = True で戻るパターンは、後述5)にて
サブフォームに記述したのは以下
Dim frm As clsFrm
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_Load()
Set frm = New clsFrm
End Sub
'Private Sub Form_Close()
' Set frm = Nothing
'End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_Load()
Set frm = New clsFrm
End Sub
'Private Sub Form_Close()
' Set frm = Nothing
'End Sub
ここで、注意する箇所が1つ。
サンプル上ではコメントにしましたが、
'Private Sub Form_Close()
' Set frm = Nothing
'End Sub
部分。' Set frm = Nothing
'End Sub
2003 / 2007 では正常に処理されますが、2000 では Form_Close からAccessさんに戻った時に
Accessさんがエラー終了してしまいます。
Form_Close を Form_Unload に変更しても同様でした。
クラスの
Private Sub Class_Terminate()
Set frm = Nothing
End Sub
を動かしたいためなんですが・・・・Set frm = Nothing
End Sub
フォームを閉じただけでは、Class_Terminate は呼ばれないようです。
(確認の仕方が悪かったのかも??)
そもそも、Set frm = Nothing を気にしなくても良い???
2)自力 改良前 (F_T2M / F_T2)
ここでは、Form_Delete 発生条件である「Delete」キーのイベントを取り込んで、
Accessさんに削除処理自体をさせない・・・・
削除処理自体がなくなるので、1)のようなクラスを作っていても動かないはず・・・
サブフォームに記述したのは以下
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Dim i As Long
Select Case KeyCode
Case vbKeyDelete
If (Me.SelHeight < 1) Then Exit Sub
KeyCode = 0
If (MsgBox(Me.SelHeight & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes) Then
With Me.Recordset
For i = 1 To Me.SelHeight
.Delete
.MoveNext
Next
End With
Me.SelHeight = 0
Call Me.Parent.Req
End If
End Select
End Sub
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Dim i As Long
Select Case KeyCode
Case vbKeyDelete
If (Me.SelHeight < 1) Then Exit Sub
KeyCode = 0
If (MsgBox(Me.SelHeight & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes) Then
With Me.Recordset
For i = 1 To Me.SelHeight
.Delete
.MoveNext
Next
End With
Me.SelHeight = 0
Call Me.Parent.Req
End If
End Select
End Sub
3)クラス 改良後 (F_T3M / F_T3)
1)の改良版で、レコードセレクタで選択した中に新規行があっても動くように・・・
また、メッセージの表示タイミングを、最後に呼ばれた時に変更してみました。
クラス「clsFrmKai」として記述したのは以下
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Private WithEvents frm As Form
Private iCount As Long
Private bNew As Boolean
Private Sub Class_Initialize()
Set frm = CodeContextObject
frm.OnDelete = EVENT_PROCEDURE
iCount = -1
bNew = False
End Sub
Private Sub Class_Terminate()
Set frm = Nothing
End Sub
Private Sub frm_Delete(Cancel As Integer)
Dim i As Long
DoCmd.CancelEvent
If (frm.SelHeight < 1) Then Exit Sub
If (iCount < 1) Then iCount = frm.SelHeight
If (iCount = frm.SelHeight) Then
bNew = ((frm.SelTop - 1) + frm.SelHeight) > frm.Recordset.RecordCount
If (bNew) Then iCount = iCount - 1
End If
iCount = iCount - 1
If (iCount = 0) Then
If (MsgBox(frm.SelHeight + bNew & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes) Then
With frm.Recordset
For i = 1 To frm.SelHeight + bNew
.Delete
.MoveNext
Next
End With
frm.SelHeight = 0
End If
End If
End Sub
Private WithEvents frm As Form
Private iCount As Long
Private bNew As Boolean
Private Sub Class_Initialize()
Set frm = CodeContextObject
frm.OnDelete = EVENT_PROCEDURE
iCount = -1
bNew = False
End Sub
Private Sub Class_Terminate()
Set frm = Nothing
End Sub
Private Sub frm_Delete(Cancel As Integer)
Dim i As Long
DoCmd.CancelEvent
If (frm.SelHeight < 1) Then Exit Sub
If (iCount < 1) Then iCount = frm.SelHeight
If (iCount = frm.SelHeight) Then
bNew = ((frm.SelTop - 1) + frm.SelHeight) > frm.Recordset.RecordCount
If (bNew) Then iCount = iCount - 1
End If
iCount = iCount - 1
If (iCount = 0) Then
If (MsgBox(frm.SelHeight + bNew & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes) Then
With frm.Recordset
For i = 1 To frm.SelHeight + bNew
.Delete
.MoveNext
Next
End With
frm.SelHeight = 0
End If
End If
End Sub
サブフォームに記述したのは以下
Dim frm As clsFrmKai
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_Load()
Set frm = New clsFrmKai
End Sub
'Private Sub Form_Close()
' Set frm = Nothing
'End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_Load()
Set frm = New clsFrmKai
End Sub
'Private Sub Form_Close()
' Set frm = Nothing
'End Sub
4)自力 改良後 (F_T4M / F_T4)
サブフォームに記述したのは以下
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Dim i As Long
Dim b As Boolean
Select Case KeyCode
Case vbKeyDelete
If (Me.SelHeight < 1) Then Exit Sub
KeyCode = 0
b = ((Me.SelTop - 1) + Me.SelHeight) > Me.Recordset.RecordCount
If ((Me.SelHeight + b) = 0) Then Exit Sub
If (MsgBox(Me.SelHeight + b & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes) Then
With Me.Recordset
For i = 1 To Me.SelHeight + b
.Delete
.MoveNext
Next
End With
Me.SelHeight = 0
Call Me.Parent.Req
End If
End Select
End Sub
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Dim i As Long
Dim b As Boolean
Select Case KeyCode
Case vbKeyDelete
If (Me.SelHeight < 1) Then Exit Sub
KeyCode = 0
b = ((Me.SelTop - 1) + Me.SelHeight) > Me.Recordset.RecordCount
If ((Me.SelHeight + b) = 0) Then Exit Sub
If (MsgBox(Me.SelHeight + b & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes) Then
With Me.Recordset
For i = 1 To Me.SelHeight + b
.Delete
.MoveNext
Next
End With
Me.SelHeight = 0
Call Me.Parent.Req
End If
End Select
End Sub
5)クラス 改良その2 (F_T5M / F_T5)
このクラスでは、frm_Delete で Cancel = True を返すようにし、
削除操作は「タイマ時」を利用するように・・・・
クラス「clsFrmKai2」として記述したのは以下
Private Const EVENT_PROCEDURE As String = "[EVENT PROCEDURE]"
Private WithEvents frm As Form
Private iCount As Long
Private bNew As Boolean
Private Sub Class_Initialize()
Set frm = CodeContextObject
frm.OnTimer = EVENT_PROCEDURE
frm.OnDelete = EVENT_PROCEDURE
iCount = -1
bNew = False
End Sub
Private Sub Class_Terminate()
Set frm = Nothing
End Sub
Private Sub frm_Timer()
Dim i As Long
frm.TimerInterval = 0
With frm.Recordset
For i = 1 To frm.SelHeight + bNew
.Delete
.MoveNext
Next
End With
frm.SelHeight = 0
End Sub
Private Sub frm_Delete(Cancel As Integer)
Cancel = True
If (frm.SelHeight < 1) Then Exit Sub
If (iCount < 1) Then iCount = frm.SelHeight
If (iCount = frm.SelHeight) Then
bNew = ((frm.SelTop - 1) + frm.SelHeight) > frm.Recordset.RecordCount
If (bNew) Then iCount = iCount - 1
End If
iCount = iCount - 1
If (iCount = 0) Then
If (MsgBox(frm.SelHeight + bNew & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes) Then
frm.TimerInterval = 10
End If
End If
End Sub
Private WithEvents frm As Form
Private iCount As Long
Private bNew As Boolean
Private Sub Class_Initialize()
Set frm = CodeContextObject
frm.OnTimer = EVENT_PROCEDURE
frm.OnDelete = EVENT_PROCEDURE
iCount = -1
bNew = False
End Sub
Private Sub Class_Terminate()
Set frm = Nothing
End Sub
Private Sub frm_Timer()
Dim i As Long
frm.TimerInterval = 0
With frm.Recordset
For i = 1 To frm.SelHeight + bNew
.Delete
.MoveNext
Next
End With
frm.SelHeight = 0
End Sub
Private Sub frm_Delete(Cancel As Integer)
Cancel = True
If (frm.SelHeight < 1) Then Exit Sub
If (iCount < 1) Then iCount = frm.SelHeight
If (iCount = frm.SelHeight) Then
bNew = ((frm.SelTop - 1) + frm.SelHeight) > frm.Recordset.RecordCount
If (bNew) Then iCount = iCount - 1
End If
iCount = iCount - 1
If (iCount = 0) Then
If (MsgBox(frm.SelHeight + bNew & "件 削除しますか?" _
, vbQuestion + vbYesNo, "確認") = vbYes) Then
frm.TimerInterval = 10
End If
End If
End Sub
サブフォームに記述したのは以下
Dim frm As clsFrmKai2
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_Load()
Set frm = New clsFrmKai2
End Sub
'Private Sub Form_Close()
' Set frm = Nothing
'End Sub
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If (Me.Parent.Name = "") Then
Cancel = True
End If
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
MsgBox "Form_AfterDelConfirm"
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
MsgBox "Form_BeforeDelConfirm"
End Sub
Private Sub Form_Current()
' MsgBox "Form_Current"
End Sub
Private Sub Form_Delete(Cancel As Integer)
MsgBox "Form_Delete"
' Call Me.Parent.Req
End Sub
Private Sub Form_Load()
Set frm = New clsFrmKai2
End Sub
'Private Sub Form_Close()
' Set frm = Nothing
'End Sub
6)フォーム「F_ALL」
前述してきた 親(メイン)からのサブフォーム参照 / 子(サブ)からの親フォーム参照
これ、全ての確認パターンで共通になっているので、サブフォームをコロコロと切り換える・・・
この切り替えをオプショングループ「op1」を使って操作できるように・・・・
以下の黄色い部分が追加した記述になります。
Private Sub op1_Click()
Dim sS As String
Dim ctl As Control
sS = "F_T" & Me.op1
For Each ctl In Me.Controls
If (ctl.ControlType = acLabel) Then
If (ctl.Parent Is Me.FSUB) Then
ctl.Caption = sS
Exit For
End If
End If
Next
Me.FSUB.SourceObject = sS
End Sub
Private Sub Form_Load()
Me.op1 = 0
Call op1_Click
End Sub
Private Sub btn1_Click()
If (IsNull(Me.txt0)) Then Exit Sub
With Me.FSUB.Form.RecordsetClone
If (Me.txt0 > .RecordCount) Then Exit Sub
.AbsolutePosition = Me.txt0 - 1
CurrentProject.Connection.Execute "DELETE * FROM T1 WHERE an = " & .Fields("an")
Call Req
End With
End Sub
Public Sub Req()
' Me.FSUB.Form.Requery
Me.FSUB.Requery
End Sub
Dim sS As String
Dim ctl As Control
sS = "F_T" & Me.op1
For Each ctl In Me.Controls
If (ctl.ControlType = acLabel) Then
If (ctl.Parent Is Me.FSUB) Then
ctl.Caption = sS
Exit For
End If
End If
Next
Me.FSUB.SourceObject = sS
End Sub
Private Sub Form_Load()
Me.op1 = 0
Call op1_Click
End Sub
Private Sub btn1_Click()
If (IsNull(Me.txt0)) Then Exit Sub
With Me.FSUB.Form.RecordsetClone
If (Me.txt0 > .RecordCount) Then Exit Sub
.AbsolutePosition = Me.txt0 - 1
CurrentProject.Connection.Execute "DELETE * FROM T1 WHERE an = " & .Fields("an")
Call Req
End With
End Sub
Public Sub Req()
' Me.FSUB.Form.Requery
Me.FSUB.Requery
End Sub
サブフォームコントロール「FSUB」にくっ付いているラベルの表示を変えながら、
ソースオブジェクトを切り替えます。
※ テキストボックスにくっ付いているラベル表示変更は
Me.テキストボックス名.Controls(0).Caption で設定先がわかりますが、
サブフォームコントロールでは上記の様な辿り方はできないようです。
Controls(0) としてしまうと、サブフォーム内のコントロールが求まってしまうので・・・・
※※ 削除操作で Form_BeforeDelConfirm / Form_AfterDelConfirm が呼ばれない
これ、上記以外に推測/想定できますでしょうか・・・・
※※※ こういう想定が出来る・・・云々、教えてください。
| サンプルは以下 | ||||||||||||
| ||||||||||||
| ※ ファイルは zip 形式 | ||||||||||||
| ※ 2007 以外は、2007 保存時に変換 & 各バージョンで動作確認 & 最適化 |
≪--- 続きを閉じちゃえ
2012/03/31
Category: やってみる
リストボックス操作の模索
リストボックスは、コンボボックスと異なり
「複数選択することも出来る」
ものになりますが、操作する上で便利なんだろうか・・・・
マウスでクリックしながら「Shift」「Ctrl」キーを押したり・・・
便利は便利ですけど・・・・
という事で(脈絡ありませんが)、
テーブル「T1」
an: オートナンバー
src: テキスト型
メモ: テキスト型
を用意し、
「src」「メモ」をリストに表示し、選択したレコードの「メモ」に値を設定する
これをやってみたいと思います。
1)リストボックスを2つ並べて、クリックしたら他方に移す

それなりに表示する領域が必要になりますが、
横にリストボックスを並べて表示し、右側に表示された「メモ」に対して設定
この時、
1−1)ワークテーブルを使用する(フォーム:F1)
1−2)Dictionary を使用する(フォーム:F3)
2)複数選択を使ってみる

複数選択した「メモ」に対して設定(フォーム:F2)
私は並べた方が、処理対象はこれ・・・・イメージしやすいかな
選択した後、何をするかによるとは思います。
今回やってみて確認できたこと
CurrentProject.Connection.Execute "SQL文"
の記述だけでは ActiveX の参照設定は、いらないみたい
続きを読んでみようかな ---≫
横にリストボックスを並べて表示し、右側に表示された「メモ」に対して設定
1−1)ワークテーブルを使用する(フォーム:F1)

テンポラリテーブル「T1Tmp」を用意します。フィールドは、「an」数値型(長整数)のみ
左側のリストボックス「lst1」を作成します。
値集合ソースは
列数:3
列幅:0cm;3cm;4.5cm
複数選択:しない
としておきます。
また、「src」追加用にテキストボックス「txt1」コマンドボタン「btn1」を配置します。
右側リストボックス「lst2」は「lst1」をコピーし、値集合ソースを変更します。
以下VBAを記述します。
基本的な動作としては、左側をクリックされたらテンポラリテーブルの「an」に対象の「an」を追加し、
右側をクリックされたらテンポラリテーブルの「an」から対象を削除するものになります。
なぜ「an」なのか・・・・は、レコードを特定できるものだから・・・
その「an」を、リストボックスの連結列に指定して、表示しなくて良いから列幅は 0cm に。
LEFT JOIN なり INNER JOIN なりで結び付けを考えるだけなので、結構楽と言えば楽。
いろいろなサイトで説明されていますね。私も忘れないうちにと言う事で・・・
「メモ」を設定する時に遊びを入れています。
設定する文字列の先頭が "=" であったら、Eval() を介した結果で設定するようにしてみました。
つまり、"=Date()" であったのなら、Eval("Date()") の結果を設定するように・・・・
また、"'" の文字が入力中にあったら、全角の "’" に変更するように・・・
設定対象がテーブルにあるという事で、いろいろクエリ(SQL)で加工できて便利だなぁ・・・と思います
注意する点が1つあって、リストボックスに表示しているレコード数ですが、
列見出しが「はい」となっている時には、ListCount に1件としてカウントされている・・・・
なので、レコードを表示しているのか、どうか
1−2)Dictionary を使用する(フォーム:F3)

テンポラリテーブルを使用しないで、テンポラリテーブルの役目を Dictionary で代用しましょう・・・
Dictionary に追加/削除・・・等々の関数を必要に応じて標準モジュールに用意しておきます。
今回用意したのは以下(標準モジュール「Module1」)
フォーム「F3」としてフォーム「F1」をコピー作成し、値集合ソースを変更します。
左側リストボックス「lst1」
そしてVBAで以下を記述します
複数選択した「メモ」に対して設定(フォーム:F2)

フォーム「F1」を「F2」としてコピー作成後、
・右側リストボックスを削除
・左側リストボックスの設定を変更していきます
値集合ソース
VBA記述を以下に
ふ〜〜ん
1−2)、2)でもそうですが、条件として設定したい場合は、
1−1)では、対象がテーブルに作られているので、
INNER JOIN だとか Exists だとか、それなりに変形した指定が出来そうです。
ただ、リストに表示している件数が多いとか・・・
状況によって変わってくるんでしょうか・・・
それはそうと、
私は ADODB.Recordset をよく記述で用いるので CurrentProject.Connection の方を使ってました。
なので ActiveX の参照設定してました。
が、今回、記述して動いた・・・・で、参照設定してなかったね・・・・
そこそこ収穫あったものになりました。
※ テーブル「T1」は空です
※ 適当にレコード追加して確認してみてください
≪--- 続きを閉じちゃえ
1)リストボックスを2つ並べて、クリックしたら他方に移す
横にリストボックスを並べて表示し、右側に表示された「メモ」に対して設定
1−1)ワークテーブルを使用する(フォーム:F1)

テンポラリテーブル「T1Tmp」を用意します。フィールドは、「an」数値型(長整数)のみ
左側のリストボックス「lst1」を作成します。
値集合ソースは
SELECT T1.an, T1.src, T1.[メモ] FROM T1 LEFT JOIN T1Tmp ON T1.an=T1Tmp.an
WHERE (((T1Tmp.an) Is Null)) ORDER BY T1.src;
連結列:1WHERE (((T1Tmp.an) Is Null)) ORDER BY T1.src;
列数:3
列幅:0cm;3cm;4.5cm
複数選択:しない
としておきます。
また、「src」追加用にテキストボックス「txt1」コマンドボタン「btn1」を配置します。
右側リストボックス「lst2」は「lst1」をコピーし、値集合ソースを変更します。
SELECT T1.an, T1.src, T1.[メモ] FROM T1 INNER JOIN T1Tmp ON T1.an=T1Tmp.an
ORDER BY T1.src;
「メモ」設定用にテキストボックス「txt2」コマンドボタン「btn2」を配置します。ORDER BY T1.src;
以下VBAを記述します。
Private Sub init1()
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub init()
Call init1
Me.lst2 = Null
Me.lst2.Requery
End Sub
Private Sub initd()
CurrentProject.Connection.Execute "DELETE * FROM T1Tmp;"
Call init
End Sub
Private Function Gikkon(iNum As Long)
Dim sSql As String
sSql = ""
Select Case iNum
Case 1: sSql = "INSERT INTO T1Tmp(an) VALUES(" & Me.lst1 & ");"
Case 2: sSql = "DELETE * FROM T1Tmp WHERE an = " & Me.lst2 & ";"
End Select
If (Len(sSql) > 0) Then CurrentProject.Connection.Execute sSql
Call init
End Function
Private Sub Form_Load()
Call initd
Me.lst1.OnClick = "=Gikkon(1)"
Me.lst2.OnClick = "=Gikkon(2)"
End Sub
Private Sub btn1_Click()
Dim sS As String
sS = Trim(Nz(Me.txt1))
If (Len(sS) > 0) Then
CurrentProject.Connection.Execute "INSERT INTO T1(src) VALUES('" & sS & "');"
Call init1
End If
Me.txt1 = Null
End Sub
Private Sub btn2_Click()
Dim v As Variant
Dim sS As String
Dim sSql As String
On Error Resume Next
If (Me.lst2.ListCount <= Abs(Me.lst2.ColumnHeads)) Then Exit Sub
sS = Trim(Nz(Me.txt2))
If (Len(sS) = 0) Then
sS = "Null"
Else
If (Left(sS, 1) = "=") Then
v = Eval(Mid(sS, 2))
If (IsEmpty(v)) Then Exit Sub
sS = CStr(v)
End If
sS = "'" & Replace(sS, "'", "’") & "'"
End If
sSql = "UPDATE T1 SET メモ = " & sS & " WHERE an IN (SELECT an FROM T1Tmp);"
CurrentProject.Connection.Execute sSql
Call initd
End Sub
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub init()
Call init1
Me.lst2 = Null
Me.lst2.Requery
End Sub
Private Sub initd()
CurrentProject.Connection.Execute "DELETE * FROM T1Tmp;"
Call init
End Sub
Private Function Gikkon(iNum As Long)
Dim sSql As String
sSql = ""
Select Case iNum
Case 1: sSql = "INSERT INTO T1Tmp(an) VALUES(" & Me.lst1 & ");"
Case 2: sSql = "DELETE * FROM T1Tmp WHERE an = " & Me.lst2 & ";"
End Select
If (Len(sSql) > 0) Then CurrentProject.Connection.Execute sSql
Call init
End Function
Private Sub Form_Load()
Call initd
Me.lst1.OnClick = "=Gikkon(1)"
Me.lst2.OnClick = "=Gikkon(2)"
End Sub
Private Sub btn1_Click()
Dim sS As String
sS = Trim(Nz(Me.txt1))
If (Len(sS) > 0) Then
CurrentProject.Connection.Execute "INSERT INTO T1(src) VALUES('" & sS & "');"
Call init1
End If
Me.txt1 = Null
End Sub
Private Sub btn2_Click()
Dim v As Variant
Dim sS As String
Dim sSql As String
On Error Resume Next
If (Me.lst2.ListCount <= Abs(Me.lst2.ColumnHeads)) Then Exit Sub
sS = Trim(Nz(Me.txt2))
If (Len(sS) = 0) Then
sS = "Null"
Else
If (Left(sS, 1) = "=") Then
v = Eval(Mid(sS, 2))
If (IsEmpty(v)) Then Exit Sub
sS = CStr(v)
End If
sS = "'" & Replace(sS, "'", "’") & "'"
End If
sSql = "UPDATE T1 SET メモ = " & sS & " WHERE an IN (SELECT an FROM T1Tmp);"
CurrentProject.Connection.Execute sSql
Call initd
End Sub
基本的な動作としては、左側をクリックされたらテンポラリテーブルの「an」に対象の「an」を追加し、
右側をクリックされたらテンポラリテーブルの「an」から対象を削除するものになります。
なぜ「an」なのか・・・・は、レコードを特定できるものだから・・・
その「an」を、リストボックスの連結列に指定して、表示しなくて良いから列幅は 0cm に。
LEFT JOIN なり INNER JOIN なりで結び付けを考えるだけなので、結構楽と言えば楽。
いろいろなサイトで説明されていますね。私も忘れないうちにと言う事で・・・
「メモ」を設定する時に遊びを入れています。
設定する文字列の先頭が "=" であったら、Eval() を介した結果で設定するようにしてみました。
つまり、"=Date()" であったのなら、Eval("Date()") の結果を設定するように・・・・
また、"'" の文字が入力中にあったら、全角の "’" に変更するように・・・
設定対象がテーブルにあるという事で、いろいろクエリ(SQL)で加工できて便利だなぁ・・・と思います
注意する点が1つあって、リストボックスに表示しているレコード数ですが、
列見出しが「はい」となっている時には、ListCount に1件としてカウントされている・・・・
なので、レコードを表示しているのか、どうか
If (Me.lst2.ListCount <= Abs(Me.lst2.ColumnHeads)) Then Exit Sub
という判別をしてみました。1−2)Dictionary を使用する(フォーム:F3)

テンポラリテーブルを使用しないで、テンポラリテーブルの役目を Dictionary で代用しましょう・・・
Dictionary に追加/削除・・・等々の関数を必要に応じて標準モジュールに用意しておきます。
今回用意したのは以下(標準モジュール「Module1」)
Private dic As Object
Public Function DicInit() As Boolean
If (dic Is Nothing) Then Set dic = CreateObject("Scripting.Dictionary")
dic.RemoveAll
DicInit = True
End Function
Public Function DicExists(iNum As Long) As Boolean
DicExists = False
If (dic Is Nothing) Then Exit Function
If (dic.Count = 0) Then Exit Function
DicExists = dic.Exists(iNum)
End Function
Public Function DicCount() As Long
DicCount = 0
If (dic Is Nothing) Then Exit Function
DicCount = dic.Count
End Function
Public Function DicEntry() As Variant
DicEntry = Empty
If (dic Is Nothing) Then Exit Function
If (dic.Count = 0) Then Exit Function
DicEntry = dic.keys
End Function
Public Sub DicAdd(iNum As Long)
If (dic Is Nothing) Then Call DicInit
dic.Item(iNum) = Null
End Sub
Public Sub DicDel(iNum As Long)
On Error Resume Next
If (dic Is Nothing) Then Exit Sub
If (dic.Count = 0) Then Exit Sub
dic.Remove iNum
End Sub
Public Function DicInit() As Boolean
If (dic Is Nothing) Then Set dic = CreateObject("Scripting.Dictionary")
dic.RemoveAll
DicInit = True
End Function
Public Function DicExists(iNum As Long) As Boolean
DicExists = False
If (dic Is Nothing) Then Exit Function
If (dic.Count = 0) Then Exit Function
DicExists = dic.Exists(iNum)
End Function
Public Function DicCount() As Long
DicCount = 0
If (dic Is Nothing) Then Exit Function
DicCount = dic.Count
End Function
Public Function DicEntry() As Variant
DicEntry = Empty
If (dic Is Nothing) Then Exit Function
If (dic.Count = 0) Then Exit Function
DicEntry = dic.keys
End Function
Public Sub DicAdd(iNum As Long)
If (dic Is Nothing) Then Call DicInit
dic.Item(iNum) = Null
End Sub
Public Sub DicDel(iNum As Long)
On Error Resume Next
If (dic Is Nothing) Then Exit Sub
If (dic.Count = 0) Then Exit Sub
dic.Remove iNum
End Sub
フォーム「F3」としてフォーム「F1」をコピー作成し、値集合ソースを変更します。
左側リストボックス「lst1」
SELECT an, src, [メモ] FROM T1 WHERE Not DicExists(an) ORDER BY src;
右側リストボックス「lst2」SELECT an, src, [メモ] FROM T1 WHERE DicExists(an) ORDER BY src;
そしてVBAで以下を記述します
Private Sub init1()
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub init()
Call init1
Me.lst2 = Null
Me.lst2.Requery
End Sub
Private Sub initd()
Call DicInit
Call init
End Sub
Private Function Gikkon(iNum As Long)
Select Case iNum
Case 1: Call DicAdd(Me.lst1)
Case 2: Call DicDel(Me.lst2)
End Select
Call init
End Function
Private Sub Form_Load()
Call initd
Me.lst1.OnClick = "=Gikkon(1)"
Me.lst2.OnClick = "=Gikkon(2)"
End Sub
Private Sub btn1_Click()
Dim sS As String
sS = Trim(Nz(Me.txt1))
If (Len(sS) > 0) Then
CurrentProject.Connection.Execute "INSERT INTO T1(src) VALUES('" & sS & "');"
Call init1
End If
Me.txt1 = Null
End Sub
Private Sub btn2_Click()
Dim v As Variant
Dim sS As String
Dim sSql As String
On Error Resume Next
If (DicCount = 0) Then Exit Sub
v = DicEntry
If (IsEmpty(v)) Then Exit Sub
sS = Join(v, ",")
sSql = Trim(Nz(Me.txt2))
If (Len(sSql) = 0) Then
sSql = "Null"
Else
If (Left(sSql, 1) = "=") Then
v = Empty
v = Eval(Mid(sSql, 2))
If (IsEmpty(v)) Then Exit Sub
sSql = CStr(v)
End If
sSql = "'" & Replace(sSql, "'", "’") & "'"
End If
sSql = "UPDATE T1 SET メモ = " & sSql & " WHERE an IN (" & sS & ");"
CurrentProject.Connection.Execute sSql
Call initd
End Sub
Me.lst1 = Null
Me.lst1.Requery
End Sub
Private Sub init()
Call init1
Me.lst2 = Null
Me.lst2.Requery
End Sub
Private Sub initd()
Call DicInit
Call init
End Sub
Private Function Gikkon(iNum As Long)
Select Case iNum
Case 1: Call DicAdd(Me.lst1)
Case 2: Call DicDel(Me.lst2)
End Select
Call init
End Function
Private Sub Form_Load()
Call initd
Me.lst1.OnClick = "=Gikkon(1)"
Me.lst2.OnClick = "=Gikkon(2)"
End Sub
Private Sub btn1_Click()
Dim sS As String
sS = Trim(Nz(Me.txt1))
If (Len(sS) > 0) Then
CurrentProject.Connection.Execute "INSERT INTO T1(src) VALUES('" & sS & "');"
Call init1
End If
Me.txt1 = Null
End Sub
Private Sub btn2_Click()
Dim v As Variant
Dim sS As String
Dim sSql As String
On Error Resume Next
If (DicCount = 0) Then Exit Sub
v = DicEntry
If (IsEmpty(v)) Then Exit Sub
sS = Join(v, ",")
sSql = Trim(Nz(Me.txt2))
If (Len(sSql) = 0) Then
sSql = "Null"
Else
If (Left(sSql, 1) = "=") Then
v = Empty
v = Eval(Mid(sSql, 2))
If (IsEmpty(v)) Then Exit Sub
sSql = CStr(v)
End If
sSql = "'" & Replace(sSql, "'", "’") & "'"
End If
sSql = "UPDATE T1 SET メモ = " & sSql & " WHERE an IN (" & sS & ");"
CurrentProject.Connection.Execute sSql
Call initd
End Sub
2)複数選択を使ってみる
複数選択した「メモ」に対して設定(フォーム:F2)

フォーム「F1」を「F2」としてコピー作成後、
・右側リストボックスを削除
・左側リストボックスの設定を変更していきます
値集合ソース
SELECT T1.an, T1.src, T1.メモ FROM T1 ORDER BY T1.src;
複数選択:拡張VBA記述を以下に
Private Sub init()
Dim i As Long
' For i = Abs(Me.lst1.ColumnHeads) To Me.lst1.ListCount - 1
' Me.lst1.Selected(i) = False
' Next
Me.lst1.Requery
End Sub
Private Sub Form_Load()
Call init
End Sub
Private Sub btn1_Click()
Dim sS As String
sS = Trim(Nz(Me.txt1))
If (Len(sS) > 0) Then
CurrentProject.Connection.Execute "INSERT INTO T1(src) VALUES('" & sS & "');"
End If
Call init
Me.txt1 = Null
End Sub
Private Sub btn2_Click()
Dim v As Variant
Dim sS As String
Dim sSql As String
On Error Resume Next
sS = ""
For Each v In Me.lst1.ItemsSelected
sS = sS & "," & Me.lst1.ItemData(v)
Next
If (Len(sS) = 0) Then Exit Sub
sS = Mid(sS, 2)
sSql = Trim(Nz(Me.txt2))
If (Len(sSql) = 0) Then
sSql = "Null"
Else
If (Left(sSql, 1) = "=") Then
v = Eval(Mid(sSql, 2))
If (IsEmpty(v)) Then Exit Sub
sSql = CStr(v)
End If
sSql = "'" & Replace(sSql, "'", "’") & "'"
End If
sSql = "UPDATE T1 SET メモ = " & sSql & " WHERE an IN (" & sS & ");"
CurrentProject.Connection.Execute sSql
Call init
End Sub
Dim i As Long
' For i = Abs(Me.lst1.ColumnHeads) To Me.lst1.ListCount - 1
' Me.lst1.Selected(i) = False
' Next
Me.lst1.Requery
End Sub
Private Sub Form_Load()
Call init
End Sub
Private Sub btn1_Click()
Dim sS As String
sS = Trim(Nz(Me.txt1))
If (Len(sS) > 0) Then
CurrentProject.Connection.Execute "INSERT INTO T1(src) VALUES('" & sS & "');"
End If
Call init
Me.txt1 = Null
End Sub
Private Sub btn2_Click()
Dim v As Variant
Dim sS As String
Dim sSql As String
On Error Resume Next
sS = ""
For Each v In Me.lst1.ItemsSelected
sS = sS & "," & Me.lst1.ItemData(v)
Next
If (Len(sS) = 0) Then Exit Sub
sS = Mid(sS, 2)
sSql = Trim(Nz(Me.txt2))
If (Len(sSql) = 0) Then
sSql = "Null"
Else
If (Left(sSql, 1) = "=") Then
v = Eval(Mid(sSql, 2))
If (IsEmpty(v)) Then Exit Sub
sSql = CStr(v)
End If
sSql = "'" & Replace(sSql, "'", "’") & "'"
End If
sSql = "UPDATE T1 SET メモ = " & sSql & " WHERE an IN (" & sS & ");"
CurrentProject.Connection.Execute sSql
Call init
End Sub
ふ〜〜ん
Me.lst1.Requery
した時点で、選択状態は解消されるんですね・・・・1−2)、2)でもそうですが、条件として設定したい場合は、
・・・・ WHERE an IN (" & sS & ");"
ってな記述に限定されるんですかね・・・・1−1)では、対象がテーブルに作られているので、
INNER JOIN だとか Exists だとか、それなりに変形した指定が出来そうです。
ただ、リストに表示している件数が多いとか・・・
状況によって変わってくるんでしょうか・・・
それはそうと、
CurrentProject.Connection.Execute sSql
は CurrentDb.Execute sSql
でも同じだと思います。私は ADODB.Recordset をよく記述で用いるので CurrentProject.Connection の方を使ってました。
なので ActiveX の参照設定してました。
が、今回、記述して動いた・・・・で、参照設定してなかったね・・・・
そこそこ収穫あったものになりました。
| サンプルは以下 | ||||||||||||
| ||||||||||||
| ※ ファイルは zip 形式 | ||||||||||||
| ※ 2007 以外は、2007 保存時に変換 & 各バージョンで動作確認 & 最適化 |
※ テーブル「T1」は空です
※ 適当にレコード追加して確認してみてください
≪--- 続きを閉じちゃえ
2012/03/31
Category: サンプルかな
再帰処理にはまる(その3)
| an | グループ | 記号 | flg |
|---|---|---|---|
| 1 | WX | T | ○ |
| 2 | WX | R | ○ |
| 3 | WX | Z | ○ |
| 4 | ST | J | ○ |
| 5 | XY | M | ○ |
| 6 | FG | S | ○ |
| 7 | FG | X | ○ |
| 8 | OP | Q | ○ |
| 9 | OP | I | ○ |
| 10 | RS | A | ○ |
| 11 | RS | C | ○ |
| 12 | RS | J | ○ |
| 13 | RS | M | ○ |
| 14 | BC | Y | ○ |
| 15 | EF | J | ○ |
| 16 | EF | P | ○ |
| 17 | EF | N | ○ |
| 18 | EF | B | ○ |
| 19 | KL | T | ○ |
| 20 | JK | A | ○ |
| 21 | JK | M | ○ |
| 22 | JK | I | ○ |
というテーブル「TA」があったとして、
まず一回目、「記号」に "A" があるグループの「flg」を "×" にします。
二回目、「flg」が "×" になった「記号」を含むグループの「flg」を "×" にします。
三回目、二回目と同様に「flg」が "×" になった「記号」を含むグループの「flg」を "×" にします。
とした場合、どうしましょうか・・・・
サンプルファイルは 再帰処理にはまる(その1) にあります
再帰処理にはまる(その2) も読んでいただければと
続きを読んでみようかな ---≫
1−1)都度対象を求める
直接テーブルの「flg」を更新するものになります。
一回ごとに、対象のグループを求めて「flg」を更新します。
一回目は、記号 Like '%A%' で対象グループを求めて更新
(ADOでやっているので % を使用)
二回目以降は、「flg」が "×" になっているグループが持つ「記号」を求め、
その「記号」を持つグループの「flg」を "×" に・・・・・
記号 IN ( XXXXXX ) の XXXXXX 部分は、ADO の GetString を使用
二回、三回と処理していくうちに対象の記号に変化がない場合は、処理をやめるように・・・
この判別に Dictionary を使用
1−2)クエリを作成して「flg」を求める
直接テーブルの「flg」をいじるのは簡単に出来ましたが、クエリでやってみますか・・・ということで
抽出の考え方は同じで、サブクエリをネストしていくものになります。
三回位までしか確認していませんが、どの程度ネスト出来るのでしょうか・・・・
既に作られているクエリ「Q_TA」は三回繰り返すものとなっています。
・・・・程度のものです。
iCount >= 1 の設定では不要な行になります。
2−1)Clone を作成して「記号」「グループ」を辿る
クエリで再帰処理?その2 での考え方に近いものとなります。
「記号」で絞り込んだものから「グループ」を求め、その「グループ」にある「記号」を求め直し、
その「グループ」をさらに処理対象とする・・・・・
この時、どの「グループ」を処理したのか管理するものに Dictionary を使用します。
その際のキーは「グループ名」で、Item にネスト位置情報を用います。
「記号」「グループ」を辿っていく際、辿り順によっては処理するネストが異なってくる場合があります。
このグループは既に処理しているけど、深いネストの時のものだったのね。
重複する処理になるけど浅いネストとしてもう一度やり直しましょうか・・・・・
テストパターンがおもわしくない場合、ある程度の指定でデータを作成するものを作ってみました。
そこそこ動くと思いますが、最悪、無限ループになることがあるかも・・・・
≪--- 続きを閉じちゃえ
1)ベタで考える【標準モジュール:Pat3n】
1−1)都度対象を求める
直接テーブルの「flg」を更新するものになります。
一回ごとに、対象のグループを求めて「flg」を更新します。
一回目は、記号 Like '%A%' で対象グループを求めて更新
(ADOでやっているので % を使用)
二回目以降は、「flg」が "×" になっているグループが持つ「記号」を求め、
その「記号」を持つグループの「flg」を "×" に・・・・・
記号 IN ( XXXXXX ) の XXXXXX 部分は、ADO の GetString を使用
二回、三回と処理していくうちに対象の記号に変化がない場合は、処理をやめるように・・・
この判別に Dictionary を使用
Public Sub Sample1()
Dim dic As Object
Dim rs As New ADODB.Recordset
Dim sSql As String
Dim sS As String
Dim i As Long, j As Long
Const sAdoDao As String = "%"
Const sChkChg As String = "flg = '×'"
Const iCount As Long = 3
DoCmd.Close acTable, sTable, acSaveNo
Call Pat3m.ResetTA
Set dic = CreateObject("Scripting.Dictionary")
sS = "A"
For i = 1 To iCount
If (Len(sS) > 0) Then
sS = "Like '" & sAdoDao & sS & sAdoDao & "'"
Else
rs.Source = "SELECT DISTINCT 記号 FROM " & sTable & " WHERE グループ IN " _
& "(SELECT グループ FROM " & sTable & " WHERE " & sChkChg & ");"
rs.Open , CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
If (Not rs.EOF) Then sS = Trim(rs.GetString(adClipString, , "", " "))
rs.Close
If (Len(sS) = 0) Then Exit For
j = dic.Count
dic.Item(sS) = Null
If (dic.Count = j) Then Exit For
sS = "IN ('" & Replace(sS, " ", "','") & "')"
End If
sSql = "UPDATE " & sTable & " SET " & sChkChg & " WHERE グループ IN " _
& "(SELECT グループ FROM " & sTable & " WHERE 記号 " & sS & ");"
CurrentProject.Connection.Execute sSql
sS = ""
Next
Set dic = Nothing
DoCmd.OpenTable sTable
End Sub
Dim dic As Object
Dim rs As New ADODB.Recordset
Dim sSql As String
Dim sS As String
Dim i As Long, j As Long
Const sAdoDao As String = "%"
Const sChkChg As String = "flg = '×'"
Const iCount As Long = 3
DoCmd.Close acTable, sTable, acSaveNo
Call Pat3m.ResetTA
Set dic = CreateObject("Scripting.Dictionary")
sS = "A"
For i = 1 To iCount
If (Len(sS) > 0) Then
sS = "Like '" & sAdoDao & sS & sAdoDao & "'"
Else
rs.Source = "SELECT DISTINCT 記号 FROM " & sTable & " WHERE グループ IN " _
& "(SELECT グループ FROM " & sTable & " WHERE " & sChkChg & ");"
rs.Open , CurrentProject.Connection, adOpenForwardOnly, adLockReadOnly
If (Not rs.EOF) Then sS = Trim(rs.GetString(adClipString, , "", " "))
rs.Close
If (Len(sS) = 0) Then Exit For
j = dic.Count
dic.Item(sS) = Null
If (dic.Count = j) Then Exit For
sS = "IN ('" & Replace(sS, " ", "','") & "')"
End If
sSql = "UPDATE " & sTable & " SET " & sChkChg & " WHERE グループ IN " _
& "(SELECT グループ FROM " & sTable & " WHERE 記号 " & sS & ");"
CurrentProject.Connection.Execute sSql
sS = ""
Next
Set dic = Nothing
DoCmd.OpenTable sTable
End Sub
1−2)クエリを作成して「flg」を求める
直接テーブルの「flg」をいじるのは簡単に出来ましたが、クエリでやってみますか・・・ということで
抽出の考え方は同じで、サブクエリをネストしていくものになります。
三回位までしか確認していませんが、どの程度ネスト出来るのでしょうか・・・・
既に作られているクエリ「Q_TA」は三回繰り返すものとなっています。
Public Sub Sample2()
Dim sSql As String
Dim sS As String
Dim i As Long
Const sAdoDao As String = "*"
Const sQuery As String = "Q_TA"
Const iCount As Long = 3
sSql = "SELECT DISTINCT '0' AS グループ FROM " & sTable
sS = "Like '" & sAdoDao & "A" & sAdoDao & "'"
For i = 1 To iCount
sSql = "SELECT DISTINCT グループ FROM " & sTable & " WHERE 記号 " & sS
sS = "IN (SELECT 記号 FROM " & sTable & " WHERE グループ IN (" & Replace(sSql, "DISTINCT ", "") & "))"
Next
sSql = "SELECT Q1.グループ, Q1.記号, IIF(IsNull(Q2.グループ),'○','×') AS flg FROM " & sTable & " AS Q1 " _
& "LEFT JOIN (" & sSql & ") AS Q2 ON Q1.グループ = Q2.グループ;"
On Error Resume Next
DoCmd.Close acQuery, sQuery, acSaveNo
With CurrentDb
.QueryDefs.Delete sQuery
.CreateQueryDef sQuery, sSql
.QueryDefs.Refresh
End With
RefreshDatabaseWindow
DoCmd.OpenQuery sQuery
End Sub
Dim sSql As String
Dim sS As String
Dim i As Long
Const sAdoDao As String = "*"
Const sQuery As String = "Q_TA"
Const iCount As Long = 3
sSql = "SELECT DISTINCT '0' AS グループ FROM " & sTable
sS = "Like '" & sAdoDao & "A" & sAdoDao & "'"
For i = 1 To iCount
sSql = "SELECT DISTINCT グループ FROM " & sTable & " WHERE 記号 " & sS
sS = "IN (SELECT 記号 FROM " & sTable & " WHERE グループ IN (" & Replace(sSql, "DISTINCT ", "") & "))"
Next
sSql = "SELECT Q1.グループ, Q1.記号, IIF(IsNull(Q2.グループ),'○','×') AS flg FROM " & sTable & " AS Q1 " _
& "LEFT JOIN (" & sSql & ") AS Q2 ON Q1.グループ = Q2.グループ;"
On Error Resume Next
DoCmd.Close acQuery, sQuery, acSaveNo
With CurrentDb
.QueryDefs.Delete sQuery
.CreateQueryDef sQuery, sSql
.QueryDefs.Refresh
End With
RefreshDatabaseWindow
DoCmd.OpenQuery sQuery
End Sub
Const iCount As Long = 3
sSql = "SELECT DISTINCT '0' AS グループ FROM " & sTable
部分での sSql 記述は、不正な iCount を設定された時に、正常な表示だけでもしましょうか・・・sSql = "SELECT DISTINCT '0' AS グループ FROM " & sTable
・・・・程度のものです。
iCount >= 1 の設定では不要な行になります。
2)再帰処理で考える【標準モジュール:Pat3r】
2−1)Clone を作成して「記号」「グループ」を辿る
クエリで再帰処理?その2 での考え方に近いものとなります。
「記号」で絞り込んだものから「グループ」を求め、その「グループ」にある「記号」を求め直し、
その「グループ」をさらに処理対象とする・・・・・
この時、どの「グループ」を処理したのか管理するものに Dictionary を使用します。
その際のキーは「グループ名」で、Item にネスト位置情報を用います。
「記号」「グループ」を辿っていく際、辿り順によっては処理するネストが異なってくる場合があります。
このグループは既に処理しているけど、深いネストの時のものだったのね。
重複する処理になるけど浅いネストとしてもう一度やり直しましょうか・・・・・
Dim dic As Object
Private Sub ReCallRs(iNst As Long, rsP As ADODB.Recordset)
Dim rs As ADODB.Recordset
Dim rs2 As ADODB.Recordset
Dim sS As String
If (iNst <= 0) Then Exit Sub
sS = rsP("グループ")
If (dic.exists(sS)) Then
If (dic.Item(sS) >= iNst) Then Exit Sub
End If
dic.Item(sS) = iNst
Set rs = rsP.Clone
Set rs2 = rs.Clone
rs.Filter = "グループ = '" & sS & "'"
While (Not rs.EOF)
rs("flg") = "×"
rs.Update
If (iNst > 1) Then
rs2.Filter = "記号 = '" & rs("記号") & "'"
While (Not rs2.EOF)
Call ReCallRs(iNst - 1, rs2)
rs2.MoveNext
Wend
End If
rs.MoveNext
Wend
rs2.Close
rs.Close
Set rs2 = Nothing
Set rs = Nothing
End Sub
Public Sub Sample1()
Dim rs As New ADODB.Recordset
Const iCount As Long = 3
DoCmd.Close acTable, sTable, acSaveNo
Call Pat3m.ResetTA
Set dic = CreateObject("Scripting.Dictionary")
rs.Open sTable, CurrentProject.Connection, adOpenStatic, adLockOptimistic
rs.Filter = "記号 Like '%A%'"
While (Not rs.EOF)
Call ReCallRs(iCount, rs)
rs.MoveNext
Wend
rs.Close
Set dic = Nothing
DoCmd.OpenTable sTable
End Sub
Private Sub ReCallRs(iNst As Long, rsP As ADODB.Recordset)
Dim rs As ADODB.Recordset
Dim rs2 As ADODB.Recordset
Dim sS As String
If (iNst <= 0) Then Exit Sub
sS = rsP("グループ")
If (dic.exists(sS)) Then
If (dic.Item(sS) >= iNst) Then Exit Sub
End If
dic.Item(sS) = iNst
Set rs = rsP.Clone
Set rs2 = rs.Clone
rs.Filter = "グループ = '" & sS & "'"
While (Not rs.EOF)
rs("flg") = "×"
rs.Update
If (iNst > 1) Then
rs2.Filter = "記号 = '" & rs("記号") & "'"
While (Not rs2.EOF)
Call ReCallRs(iNst - 1, rs2)
rs2.MoveNext
Wend
End If
rs.MoveNext
Wend
rs2.Close
rs.Close
Set rs2 = Nothing
Set rs = Nothing
End Sub
Public Sub Sample1()
Dim rs As New ADODB.Recordset
Const iCount As Long = 3
DoCmd.Close acTable, sTable, acSaveNo
Call Pat3m.ResetTA
Set dic = CreateObject("Scripting.Dictionary")
rs.Open sTable, CurrentProject.Connection, adOpenStatic, adLockOptimistic
rs.Filter = "記号 Like '%A%'"
While (Not rs.EOF)
Call ReCallRs(iCount, rs)
rs.MoveNext
Wend
rs.Close
Set dic = Nothing
DoCmd.OpenTable sTable
End Sub
3)テーブル「TA」のデータを作る【標準モジュール:Pat3m】
テストパターンがおもわしくない場合、ある程度の指定でデータを作成するものを作ってみました。
そこそこ動くと思いますが、最悪、無限ループになることがあるかも・・・・
Public Const sTable As String = "TA"
Public Sub DataMakeTA()
Dim dic As Object, dic2 As Object
Dim sMrk As String
Dim sG As String, sM As String
Dim iGrpLen As Long, iMrkLen As Long
Dim i As Long, j As Long, k As Long
Dim sSql As String, sS As String
Dim bNoneA As Boolean
Const sGrp As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Const iGrpNum As Long = 10 ' 作成するグループの個数(推奨:20以下)
Const iMaxGrpLen As Long = 2 ' グループ名の最大文字数
Const iGrpEnt As Long = 4 ' 1グループのデータ最大数
Const iMaxMrk As Long = 40 ' 記号を作る参照文字長
Const iMaxMrkLen As Long = 1 ' 記号の最大文字数
Const sFixChr As String = "A" ' 最低限必要な文字
DoCmd.Close acTable, sTable, acSaveNo
Set dic = CreateObject("Scripting.Dictionary")
Set dic2 = CreateObject("Scripting.Dictionary")
Randomize
bNoneA = True
While (bNoneA)
sMrk = String(iMaxMrk, "a")
For i = 1 To iMaxMrk
Mid(sMrk, i, 1) = Mid(sGrp, Int(Rnd() * Len(sGrp)) + 1, 1)
Next
With CurrentProject.Connection
.Execute "DELETE * FROM " & sTable & ";"
dic.RemoveAll
For i = 1 To iGrpNum
Do
' iGrpLen = Int(Rnd() * iMaxGrpLen) + 1
iGrpLen = iMaxGrpLen ' 現在固定長(可変は上記)
k = dic.Count
sG = Mid(sGrp, Int(Rnd() * (Len(sGrp) - (iGrpLen - 1))) + 1, iGrpLen)
dic.Item(sG) = Null
Loop While (dic.Count = k)
sS = "INSERT INTO " & sTable & "(グループ, 記号) VALUES ('" & sG & "','"
dic2.RemoveAll
For j = 0 To Int(Rnd() * iGrpEnt)
Do
iMrkLen = Int(Rnd() * iMaxMrkLen) + 1
sM = Mid(sMrk, Int(Rnd() * (iMaxMrk - (iMrkLen - 1))) + 1, iMrkLen)
k = dic2.Count
dic2.Item(sM) = Null
Loop While (dic2.Count = k)
If (sM Like "*" & sFixChr & "*") Then bNoneA = False
sSql = sS & sM & "');"
.Execute sSql
Next
Next
End With
Wend
Set dic = Nothing
Set dic2 = Nothing
DoCmd.OpenTable sTable
End Sub
Public Sub ResetTA()
CurrentProject.Connection.Execute "UPDATE " & sTable & " SET flg = '○';"
End Sub
Public Sub DataMakeTA()
Dim dic As Object, dic2 As Object
Dim sMrk As String
Dim sG As String, sM As String
Dim iGrpLen As Long, iMrkLen As Long
Dim i As Long, j As Long, k As Long
Dim sSql As String, sS As String
Dim bNoneA As Boolean
Const sGrp As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Const iGrpNum As Long = 10 ' 作成するグループの個数(推奨:20以下)
Const iMaxGrpLen As Long = 2 ' グループ名の最大文字数
Const iGrpEnt As Long = 4 ' 1グループのデータ最大数
Const iMaxMrk As Long = 40 ' 記号を作る参照文字長
Const iMaxMrkLen As Long = 1 ' 記号の最大文字数
Const sFixChr As String = "A" ' 最低限必要な文字
DoCmd.Close acTable, sTable, acSaveNo
Set dic = CreateObject("Scripting.Dictionary")
Set dic2 = CreateObject("Scripting.Dictionary")
Randomize
bNoneA = True
While (bNoneA)
sMrk = String(iMaxMrk, "a")
For i = 1 To iMaxMrk
Mid(sMrk, i, 1) = Mid(sGrp, Int(Rnd() * Len(sGrp)) + 1, 1)
Next
With CurrentProject.Connection
.Execute "DELETE * FROM " & sTable & ";"
dic.RemoveAll
For i = 1 To iGrpNum
Do
' iGrpLen = Int(Rnd() * iMaxGrpLen) + 1
iGrpLen = iMaxGrpLen ' 現在固定長(可変は上記)
k = dic.Count
sG = Mid(sGrp, Int(Rnd() * (Len(sGrp) - (iGrpLen - 1))) + 1, iGrpLen)
dic.Item(sG) = Null
Loop While (dic.Count = k)
sS = "INSERT INTO " & sTable & "(グループ, 記号) VALUES ('" & sG & "','"
dic2.RemoveAll
For j = 0 To Int(Rnd() * iGrpEnt)
Do
iMrkLen = Int(Rnd() * iMaxMrkLen) + 1
sM = Mid(sMrk, Int(Rnd() * (iMaxMrk - (iMrkLen - 1))) + 1, iMrkLen)
k = dic2.Count
dic2.Item(sM) = Null
Loop While (dic2.Count = k)
If (sM Like "*" & sFixChr & "*") Then bNoneA = False
sSql = sS & sM & "');"
.Execute sSql
Next
Next
End With
Wend
Set dic = Nothing
Set dic2 = Nothing
DoCmd.OpenTable sTable
End Sub
Public Sub ResetTA()
CurrentProject.Connection.Execute "UPDATE " & sTable & " SET flg = '○';"
End Sub
≪--- 続きを閉じちゃえ
2012/03/12
Category: やってみる
再帰処理にはまる(その2)
vAry = Array(0, 1.1, 4.4, 3.3, 2.2) という配列があった時、
何組かの同じ配列を使って総当たりで、たし算しましょう。
その結果、重複する数値は排除して小さい順に表示しましょう。
例えば、
A = Array(0, 1.1, 4.4, 3.3, 2.2)
B = Array(0, 1.1, 4.4, 3.3, 2.2)
C = Array(0, 1.1, 4.4, 3.3, 2.2)
の3つを使って、
A(0)+B(0)+C(0)= や A(0)+B(0)+C(1)= や A(0)+B(0)+C(2)= ・・・・・
A(0)+B(1)+C(0)= や A(0)+B(1)+C(1)= や A(0)+B(1)+C(2)= ・・・・・
・・・・
A(4)+B(3)+C(0)= や A(4)+B(3)+C(1)= や A(4)+B(3)+C(2)= ・・・・・
A(4)+B(4)+C(0)= や A(4)+B(4)+C(1)= や A(4)+B(4)+C(2)= ・・・・・
の結果、重複を排除して小さい順に
0
1.1
2.2
3.3
4.4
5.5
6.6
7.7
8.8
9.9
11
12.1
13.2
と
何組かの同じ配列を使って総当たりで、たし算しましょう。
その結果、重複する数値は排除して小さい順に表示しましょう。
例えば、
A = Array(0, 1.1, 4.4, 3.3, 2.2)
B = Array(0, 1.1, 4.4, 3.3, 2.2)
C = Array(0, 1.1, 4.4, 3.3, 2.2)
の3つを使って、
A(0)+B(0)+C(0)= や A(0)+B(0)+C(1)= や A(0)+B(0)+C(2)= ・・・・・
A(0)+B(1)+C(0)= や A(0)+B(1)+C(1)= や A(0)+B(1)+C(2)= ・・・・・
・・・・
A(4)+B(3)+C(0)= や A(4)+B(3)+C(1)= や A(4)+B(3)+C(2)= ・・・・・
A(4)+B(4)+C(0)= や A(4)+B(4)+C(1)= や A(4)+B(4)+C(2)= ・・・・・
の結果、重複を排除して小さい順に
0
1.1
2.2
3.3
4.4
5.5
6.6
7.7
8.8
9.9
11
12.1
13.2
と
サンプルファイルは 再帰処理にはまる(その1) にあります
再帰処理にはまる(その3) も読んでいただければと
続きを読んでみようかな ---≫
1−1)総当たりの結果を格納する配列を用意する
まず、対象を配列で作成したら、計算誤差をなくすために CCur 変換しておきます。
何組使ってやるかは iCount で指定するように
基本の配列を1組分作っておきます。
次の組を処理する際、全ての結果を格納できるように領域を確保して総当たり計算
これを必要な組分繰り返します。
その後、重複分の値を排除する為に Dictionary を使用します。
値をキーとして利用することで、重複を排除できます。
排除後のキーを取得して、昇順に並び変えて表示します。
組数が多くなってくると、全計算結果を格納する領域の確保が問題になってくると思います。
そこで、2組目、3組目と処理をしていく中で、重複を排除した結果だけを覚えておきましょう。
その結果と、次の組を総当たりで・・・・・この時も重複は排除しておきましょう。
それが次の例となります。
1−2)計算結果のみを Dictionary で管理
組数が多くなってくると、この方法が一番良いようです。
VBA記述での例にはなりませんが、せっかく Access を使っているので、
テーブルを用意して、クエリで直積を利用して・・・このクエリを作れるように
作られるクエリは
また、クエリを手動/VBA で作成した時のプロパティの違いも確認したいかなっていうレベルで以下
(テーブルは「T1」として用意しています)
2−1)重複結果を Dictionary のキーで管理
総当たりする部分を再帰処理にします。
計算結果を Dictionary のキーとして利用し重複を排除します。
2−2)重複しない結果を都度整列生成
基本的には2−1)と同じですが、Dictionary 部分の使い方が変わってきます。
重複しない結果が得られたら・・・・・
これ、Dictionary に設定する時に管理している個数に変化があったかで判別しています。
変化あったら重複しない値だった・・・・なので、自分で都度昇順に格納していきます。
2−3)重複しない組み合わせを事前に求める
今回は足し算でそう時間がかかるものではありませんが、考え方として・・・・
総当たりで計算するのではなく、計算が必要なケースを探します。
この時再帰処理を使用します。
元の配列数と同じ中身 0 の変数を用意し、どのケースがあるか調べます。
このケースの管理に Dictionary を使用します。
Dictionary に格納される内容としては、"0,3;1,0;2,0;3,0;4,0" のような文字列になります。
これは、
A(0)+B(0)+C(1)= や A(0)+B(1)+C(0)= や A(1)+B(0)+C(0)=
これら全て計算する必要はなく (0) を2回、(1) を1回のケースですね・・・・
この場合、"0,2;1,1;2,0;3,0;4,0" とするルールを決めました。
";" 区切りで、その中を "," で区切って (0) が配列参照の添え字に、(1) が回数に
(実際に必要な "0,2;1,1" だけの方が良かったのかも・・・・)
必要なケースだけやったとしても、計算結果が重複しない保証はないので、
また Dictionary を使って重複排除
5、6組ぐらいまでなら再帰も使えるかも・・・・・
それ以上だと・・・・・・・・・
この処理では、1−2)が格段に速い、良いですね。
50組でもソコソコの応答があるし・・・・・
≪--- 続きを閉じちゃえ
1)ベタで考える【標準モジュール:Pat2n】
1−1)総当たりの結果を格納する配列を用意する
まず、対象を配列で作成したら、計算誤差をなくすために CCur 変換しておきます。
何組使ってやるかは iCount で指定するように
基本の配列を1組分作っておきます。
次の組を処理する際、全ての結果を格納できるように領域を確保して総当たり計算
これを必要な組分繰り返します。
その後、重複分の値を排除する為に Dictionary を使用します。
値をキーとして利用することで、重複を排除できます。
排除後のキーを取得して、昇順に並び変えて表示します。
Public Sub Sample1()
Dim vAry As Variant
Dim vBase As Variant
Dim vTmp As Variant
Dim dic As Object
Dim i As Long, jB As Long, jA As Long, k As Long
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
Next
vBase = vAry
For i = 2 To iCount
ReDim vTmp((UBound(vBase) + 1) * (UBound(vAry) + 1) - 1)
k = 0
For jB = 0 To UBound(vBase)
For jA = 0 To UBound(vAry)
vTmp(k) = vBase(jB) + vAry(jA)
k = k + 1
Next
Next
vBase = vTmp
vTmp = Empty
Next
Set dic = CreateObject("Scripting.Dictionary")
For i = 0 To UBound(vBase)
dic.Item(vBase(i)) = Null
Next
vBase = dic.keys
Set dic = Nothing
For i = 0 To UBound(vBase) - 1
For k = i + 1 To UBound(vBase)
If (vBase(k) < vBase(i)) Then
vTmp = vBase(i)
vBase(i) = vBase(k)
vBase(k) = vTmp
End If
Next
Next
For i = 0 To UBound(vBase)
Debug.Print vBase(i)
Next
End Sub
Dim vAry As Variant
Dim vBase As Variant
Dim vTmp As Variant
Dim dic As Object
Dim i As Long, jB As Long, jA As Long, k As Long
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
Next
vBase = vAry
For i = 2 To iCount
ReDim vTmp((UBound(vBase) + 1) * (UBound(vAry) + 1) - 1)
k = 0
For jB = 0 To UBound(vBase)
For jA = 0 To UBound(vAry)
vTmp(k) = vBase(jB) + vAry(jA)
k = k + 1
Next
Next
vBase = vTmp
vTmp = Empty
Next
Set dic = CreateObject("Scripting.Dictionary")
For i = 0 To UBound(vBase)
dic.Item(vBase(i)) = Null
Next
vBase = dic.keys
Set dic = Nothing
For i = 0 To UBound(vBase) - 1
For k = i + 1 To UBound(vBase)
If (vBase(k) < vBase(i)) Then
vTmp = vBase(i)
vBase(i) = vBase(k)
vBase(k) = vTmp
End If
Next
Next
For i = 0 To UBound(vBase)
Debug.Print vBase(i)
Next
End Sub
組数が多くなってくると、全計算結果を格納する領域の確保が問題になってくると思います。
そこで、2組目、3組目と処理をしていく中で、重複を排除した結果だけを覚えておきましょう。
その結果と、次の組を総当たりで・・・・・この時も重複は排除しておきましょう。
それが次の例となります。
1−2)計算結果のみを Dictionary で管理
Public Sub Sample2()
Dim vAry As Variant
Dim vBase As Variant
Dim vTmp As Variant
Dim dic As Object
Dim i As Long, j As Long
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
Set dic = CreateObject("Scripting.Dictionary")
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
dic.Item(vAry(i)) = Null
Next
For i = 2 To iCount
vBase = dic.keys
dic.RemoveAll
For Each vTmp In vBase
For j = 0 To UBound(vAry)
dic.Item(vTmp + vAry(j)) = Null
Next
Next
Next
vBase = dic.keys
Set dic = Nothing
For i = 0 To UBound(vBase) - 1
For j = i + 1 To UBound(vBase)
If (vBase(j) < vBase(i)) Then
vTmp = vBase(i)
vBase(i) = vBase(j)
vBase(j) = vTmp
End If
Next
Next
For i = 0 To UBound(vBase)
Debug.Print vBase(i)
Next
End Sub
Dim vAry As Variant
Dim vBase As Variant
Dim vTmp As Variant
Dim dic As Object
Dim i As Long, j As Long
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
Set dic = CreateObject("Scripting.Dictionary")
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
dic.Item(vAry(i)) = Null
Next
For i = 2 To iCount
vBase = dic.keys
dic.RemoveAll
For Each vTmp In vBase
For j = 0 To UBound(vAry)
dic.Item(vTmp + vAry(j)) = Null
Next
Next
Next
vBase = dic.keys
Set dic = Nothing
For i = 0 To UBound(vBase) - 1
For j = i + 1 To UBound(vBase)
If (vBase(j) < vBase(i)) Then
vTmp = vBase(i)
vBase(i) = vBase(j)
vBase(j) = vTmp
End If
Next
Next
For i = 0 To UBound(vBase)
Debug.Print vBase(i)
Next
End Sub
組数が多くなってくると、この方法が一番良いようです。
VBA記述での例にはなりませんが、せっかく Access を使っているので、
テーブルを用意して、クエリで直積を利用して・・・このクエリを作れるように
作られるクエリは
SELECT DISTINCT (Q1.値+Q2.値+Q3.値) AS 値
FROM T1 AS Q1, T1 AS Q2, T1 AS Q3;
てな感じになります。FROM T1 AS Q1, T1 AS Q2, T1 AS Q3;
また、クエリを手動/VBA で作成した時のプロパティの違いも確認したいかなっていうレベルで以下
(テーブルは「T1」として用意しています)
Public Sub DataMakeT1()
Dim vAry As Variant
Dim i As Long
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
Next
With CurrentDb
.Execute "DELETE * FROM T1;"
With .OpenRecordset("T1")
For i = 0 To UBound(vAry)
.AddNew
.Fields("値") = vAry(i)
.Update
Next
.Close
End With
End With
End Sub
Public Sub QueryMakeQ_T1()
Dim sSql As String
Dim sS As String
Dim i As Long
Const sQuery As String = "Q_T1"
Const iCount As Long = 3
sSql = ""
sS = ""
For i = 1 To iCount
sSql = sSql & "+Q" & i & ".値"
sS = sS & ", T1 AS Q" & i
Next
sSql = "(" & Mid(sSql, 2) & ") AS 値"
sS = Mid(sS, 3)
sSql = "SELECT DISTINCT " & sSql & " FROM " & sS & ";"
On Error Resume Next
DoCmd.Close acQuery, sQuery, acSaveNo
With CurrentDb
.QueryDefs.Delete sQuery
.CreateQueryDef sQuery, sSql
.QueryDefs.Refresh
With .QueryDefs(sQuery).Fields("値")
.Properties.Append .CreateProperty("Format", dbText, "General Number")
.Properties.Append .CreateProperty("DecimalPlaces", dbByte, 4)
End With
End With
DoCmd.OpenQuery sQuery
End Sub
Public Sub ChkPrp()
Dim prp As DAO.Property
Const sQuery As String = "Q_T1"
On Error Resume Next
With CurrentDb
With .QueryDefs(sQuery).Fields("値")
For Each prp In .Properties
Debug.Print prp.Name,
Debug.Print prp.Type,
Debug.Print prp.Value,
Debug.Print ""
Next
End With
End With
End Sub
Dim vAry As Variant
Dim i As Long
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
Next
With CurrentDb
.Execute "DELETE * FROM T1;"
With .OpenRecordset("T1")
For i = 0 To UBound(vAry)
.AddNew
.Fields("値") = vAry(i)
.Update
Next
.Close
End With
End With
End Sub
Public Sub QueryMakeQ_T1()
Dim sSql As String
Dim sS As String
Dim i As Long
Const sQuery As String = "Q_T1"
Const iCount As Long = 3
sSql = ""
sS = ""
For i = 1 To iCount
sSql = sSql & "+Q" & i & ".値"
sS = sS & ", T1 AS Q" & i
Next
sSql = "(" & Mid(sSql, 2) & ") AS 値"
sS = Mid(sS, 3)
sSql = "SELECT DISTINCT " & sSql & " FROM " & sS & ";"
On Error Resume Next
DoCmd.Close acQuery, sQuery, acSaveNo
With CurrentDb
.QueryDefs.Delete sQuery
.CreateQueryDef sQuery, sSql
.QueryDefs.Refresh
With .QueryDefs(sQuery).Fields("値")
.Properties.Append .CreateProperty("Format", dbText, "General Number")
.Properties.Append .CreateProperty("DecimalPlaces", dbByte, 4)
End With
End With
DoCmd.OpenQuery sQuery
End Sub
Public Sub ChkPrp()
Dim prp As DAO.Property
Const sQuery As String = "Q_T1"
On Error Resume Next
With CurrentDb
With .QueryDefs(sQuery).Fields("値")
For Each prp In .Properties
Debug.Print prp.Name,
Debug.Print prp.Type,
Debug.Print prp.Value,
Debug.Print ""
Next
End With
End With
End Sub
2)再帰処理で考える【標準モジュール:Pat2r】
2−1)重複結果を Dictionary のキーで管理
総当たりする部分を再帰処理にします。
計算結果を Dictionary のキーとして利用し重複を排除します。
Dim dic As Object
Private Sub ReCallCurAdd(iNst As Long, vAry As Variant, cSrc As Currency)
Dim v As Variant
Dim c As Currency
For Each v In vAry
c = cSrc + v
If (iNst <= 1) Then
dic.Item(c) = Null
Else
Call ReCallCurAdd(iNst - 1, vAry, c)
End If
Next
End Sub
Public Sub Sample1()
Dim vAry As Variant
Dim vBase As Variant
Dim vTmp As Variant
Dim i As Long, jB As Long, jA As Long, k As Long
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
Next
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallCurAdd(iCount, vAry, 0)
vBase = dic.keys
Set dic = Nothing
For i = 0 To UBound(vBase) - 1
For k = i + 1 To UBound(vBase)
If (vBase(k) < vBase(i)) Then
vTmp = vBase(i)
vBase(i) = vBase(k)
vBase(k) = vTmp
End If
Next
Next
For i = 0 To UBound(vBase)
Debug.Print vBase(i)
Next
End Sub
Private Sub ReCallCurAdd(iNst As Long, vAry As Variant, cSrc As Currency)
Dim v As Variant
Dim c As Currency
For Each v In vAry
c = cSrc + v
If (iNst <= 1) Then
dic.Item(c) = Null
Else
Call ReCallCurAdd(iNst - 1, vAry, c)
End If
Next
End Sub
Public Sub Sample1()
Dim vAry As Variant
Dim vBase As Variant
Dim vTmp As Variant
Dim i As Long, jB As Long, jA As Long, k As Long
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
Next
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallCurAdd(iCount, vAry, 0)
vBase = dic.keys
Set dic = Nothing
For i = 0 To UBound(vBase) - 1
For k = i + 1 To UBound(vBase)
If (vBase(k) < vBase(i)) Then
vTmp = vBase(i)
vBase(i) = vBase(k)
vBase(k) = vTmp
End If
Next
Next
For i = 0 To UBound(vBase)
Debug.Print vBase(i)
Next
End Sub
2−2)重複しない結果を都度整列生成
基本的には2−1)と同じですが、Dictionary 部分の使い方が変わってきます。
重複しない結果が得られたら・・・・・
これ、Dictionary に設定する時に管理している個数に変化があったかで判別しています。
変化あったら重複しない値だった・・・・なので、自分で都度昇順に格納していきます。
Dim dic As Object
Dim cAry As Variant ' Sample2 で使用
Private Sub CurAdd2(cSrc As Currency)
Dim i As Long
Dim j As Long
If (IsEmpty(cAry)) Then
ReDim cAry(0)
cAry(0) = cSrc
Else
For i = 0 To UBound(cAry)
If (cAry(i) > cSrc) Then
ReDim Preserve cAry(UBound(cAry) + 1)
For j = UBound(cAry) - 1 To i Step -1
cAry(j + 1) = cAry(j)
Next
cAry(i) = cSrc
Exit For
End If
Next
If (i > UBound(cAry)) Then
ReDim Preserve cAry(i)
cAry(i) = cSrc
End If
End If
End Sub
Private Sub ReCallCurAdd2(iNst As Long, vAry As Variant, cSrc As Currency)
Dim v As Variant
Dim c As Currency
Dim i As Long
For Each v In vAry
c = cSrc + v
If (iNst <= 1) Then
i = dic.Count
dic.Item(c) = Null
If (dic.Count <> i) Then Call CurAdd2(c)
Else
Call ReCallCurAdd2(iNst - 1, vAry, c)
End If
Next
End Sub
Public Sub Sample2()
Dim vAry As Variant
Dim i As Long
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
Next
cAry = Empty
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallCurAdd2(iCount, vAry, 0)
Set dic = Nothing
For i = 0 To UBound(cAry)
Debug.Print cAry(i)
Next
End Sub
Dim cAry As Variant ' Sample2 で使用
Private Sub CurAdd2(cSrc As Currency)
Dim i As Long
Dim j As Long
If (IsEmpty(cAry)) Then
ReDim cAry(0)
cAry(0) = cSrc
Else
For i = 0 To UBound(cAry)
If (cAry(i) > cSrc) Then
ReDim Preserve cAry(UBound(cAry) + 1)
For j = UBound(cAry) - 1 To i Step -1
cAry(j + 1) = cAry(j)
Next
cAry(i) = cSrc
Exit For
End If
Next
If (i > UBound(cAry)) Then
ReDim Preserve cAry(i)
cAry(i) = cSrc
End If
End If
End Sub
Private Sub ReCallCurAdd2(iNst As Long, vAry As Variant, cSrc As Currency)
Dim v As Variant
Dim c As Currency
Dim i As Long
For Each v In vAry
c = cSrc + v
If (iNst <= 1) Then
i = dic.Count
dic.Item(c) = Null
If (dic.Count <> i) Then Call CurAdd2(c)
Else
Call ReCallCurAdd2(iNst - 1, vAry, c)
End If
Next
End Sub
Public Sub Sample2()
Dim vAry As Variant
Dim i As Long
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
For i = 0 To UBound(vAry)
vAry(i) = CCur(vAry(i))
Next
cAry = Empty
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallCurAdd2(iCount, vAry, 0)
Set dic = Nothing
For i = 0 To UBound(cAry)
Debug.Print cAry(i)
Next
End Sub
2−3)重複しない組み合わせを事前に求める
今回は足し算でそう時間がかかるものではありませんが、考え方として・・・・
総当たりで計算するのではなく、計算が必要なケースを探します。
この時再帰処理を使用します。
元の配列数と同じ中身 0 の変数を用意し、どのケースがあるか調べます。
このケースの管理に Dictionary を使用します。
Dictionary に格納される内容としては、"0,3;1,0;2,0;3,0;4,0" のような文字列になります。
これは、
A(0)+B(0)+C(1)= や A(0)+B(1)+C(0)= や A(1)+B(0)+C(0)=
これら全て計算する必要はなく (0) を2回、(1) を1回のケースですね・・・・
この場合、"0,2;1,1;2,0;3,0;4,0" とするルールを決めました。
";" 区切りで、その中を "," で区切って (0) が配列参照の添え字に、(1) が回数に
(実際に必要な "0,2;1,1" だけの方が良かったのかも・・・・)
必要なケースだけやったとしても、計算結果が重複しない保証はないので、
また Dictionary を使って重複排除
Dim dic As Object
Private Sub ReCallMake3(iNst As Long, vAry As Variant)
Dim i As Long, j As Long
Dim sS As String
Dim vWrkAry As Variant
For i = 0 To UBound(vAry)
vWrkAry = vAry
vWrkAry(i) = vWrkAry(i) + 1
If (iNst <= 1) Then
sS = ""
For j = 0 To UBound(vWrkAry)
sS = sS & ";" & j & "," & vWrkAry(j)
Next
dic.Item(Mid(sS, 2)) = Null
Else
Call ReCallMake3(iNst - 1, vWrkAry)
End If
Next
End Sub
Public Sub Sample3()
Dim vAry As Variant
Dim vWrkAry As Variant
Dim vBase As Variant
Dim vTmp As Variant
Dim i As Long, j As Long
Dim vKs As Variant, vK As Variant
Dim vAs As Variant, vA As Variant
Dim c As Currency, cW As Currency
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
vWrkAry = vAry
For i = 0 To UBound(vWrkAry)
vWrkAry(i) = 0
Next
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallMake3(iCount, vWrkAry)
vKs = dic.keys
dic.RemoveAll
For Each vK In vKs
c = 0
For Each vAs In Split(vK, ";")
vA = Split(vAs, ",")
cW = vAry(CLng(vA(0)))
For i = 1 To CLng(vA(1))
c = c + cW
Next
Next
dic.Item(c) = Null
Next
vBase = dic.keys
Set dic = Nothing
For i = 0 To UBound(vBase) - 1
For j = i + 1 To UBound(vBase)
If (vBase(j) < vBase(i)) Then
vTmp = vBase(i)
vBase(i) = vBase(j)
vBase(j) = vTmp
End If
Next
Next
For i = 0 To UBound(vBase)
Debug.Print vBase(i)
Next
End Sub
Private Sub ReCallMake3(iNst As Long, vAry As Variant)
Dim i As Long, j As Long
Dim sS As String
Dim vWrkAry As Variant
For i = 0 To UBound(vAry)
vWrkAry = vAry
vWrkAry(i) = vWrkAry(i) + 1
If (iNst <= 1) Then
sS = ""
For j = 0 To UBound(vWrkAry)
sS = sS & ";" & j & "," & vWrkAry(j)
Next
dic.Item(Mid(sS, 2)) = Null
Else
Call ReCallMake3(iNst - 1, vWrkAry)
End If
Next
End Sub
Public Sub Sample3()
Dim vAry As Variant
Dim vWrkAry As Variant
Dim vBase As Variant
Dim vTmp As Variant
Dim i As Long, j As Long
Dim vKs As Variant, vK As Variant
Dim vAs As Variant, vA As Variant
Dim c As Currency, cW As Currency
Const iCount As Long = 3
vAry = Array(0, 1.1, 4.4, 3.3, 2.2)
' vAry = Array(0, 9.9, 1.1, 8.8, 2.2, 7.7, 3.3, 6.6, 4.4, 5.5)
vWrkAry = vAry
For i = 0 To UBound(vWrkAry)
vWrkAry(i) = 0
Next
Set dic = CreateObject("Scripting.Dictionary")
Call ReCallMake3(iCount, vWrkAry)
vKs = dic.keys
dic.RemoveAll
For Each vK In vKs
c = 0
For Each vAs In Split(vK, ";")
vA = Split(vAs, ",")
cW = vAry(CLng(vA(0)))
For i = 1 To CLng(vA(1))
c = c + cW
Next
Next
dic.Item(c) = Null
Next
vBase = dic.keys
Set dic = Nothing
For i = 0 To UBound(vBase) - 1
For j = i + 1 To UBound(vBase)
If (vBase(j) < vBase(i)) Then
vTmp = vBase(i)
vBase(i) = vBase(j)
vBase(j) = vTmp
End If
Next
Next
For i = 0 To UBound(vBase)
Debug.Print vBase(i)
Next
End Sub
5、6組ぐらいまでなら再帰も使えるかも・・・・・
それ以上だと・・・・・・・・・
この処理では、1−2)が格段に速い、良いですね。
50組でもソコソコの応答があるし・・・・・
≪--- 続きを閉じちゃえ
2012/03/12
Category: やってみる

