- ベストアンサー
エクセルVBA ListBox選択を反映させる
- エクセルVBAのユーザーフォームにListBox1とListBox2があります。ListBox1とListBox2の選択内容に応じて、セルに値を入力する処理を作成しましたが、うまく動作しない問題があります。
- 問題の内容として、以下の2点が挙げられます。まず、ListBox1の選択後にListBox2を選択しても実際には選択されていないように見えます。また、ListBox1とListBox2が1年違いまたは2年違いの場合にはセルに値が正しく入力されません。
- 問題の解決策として、ListBox1の変更イベントのコードを修正し、ListBox2の選択を正しく反映するようにしましょう。また、ListBox1とListBox2が1年違いまたは2年違いの場合には適切な処理を行うように修正する必要があります。
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
#1-3、cjです。#3お礼欄・補足欄へのレスです。 疑問点(1)については 補足欄に書かれた方法で十分対処可能と思います。 すべての記述を見て要求仕様を完全に理解できている訳ではないので、 私から断言はできませんが、その対策でうまく機能させることが出来ているのなら (つまり要求に沿う仕様が提供できているのなら) 方法的には間違いはありませんし、もっとも有力な方法だと思います。 疑問点(2)については IntegralHeightプロパティをデフォルトに戻すことが 唯一の正解ですから、間違いありません。 今後も細かいメンテナンスについての疑問について質問することがあるようでしたらば、 この課題については、モジュール全体を提示した方が、話は解り易いかと思います。 でも、#3お礼欄から補足欄での対処の仕方を見る限りでは、 十分に自力で解決に導く力をお持ちだなんだと思います。 頑張ってください。
その他の回答 (3)
- cj_mover
- ベストアンサー率76% (292/381)
(直前の投稿の続きです。) 〓〓〓 > 自分が作成してきたものの訂正で、 > If Mid(ListBox1, Application.Find("(", ListBox1) + 1, 4) + 1 = _ Mid(ListBox2, Application.Find("(", ListBox2) + 1, 4) + 0 Then > と0を追加することで、1年違いや2年違いを反映させたい場合のエラーがなくなりました この記述が必要なものなのか、については次項で触れますが、 Application.Find....などと、わざわざExcelのワークシート関数を使って処理を遅くすることはないです。 VBAでは、文字列値の中から、特定の文字列を検索して位置を返す関数として、 InStr()関数が用意されています。 また、文字列値の先頭にある数字として読める部分だけを、数値として返す関数として、 Val()関数というのもあります。 強いて書くならば、ですが、 Select Case フレーズで、ListBox2とListBox1の年度の差を基準にして、 Select Case Val(Mid(ListBox2, InStr(ListBox2, "(") + 1)) - Val(Mid(ListBox1, InStr(ListBox1, "(") + 1)) Case 0 ' 単年 の場合の処理 Case 1 ' 1年違い の場合の処理 Case 2 ' 2年違い の場合の処理 ' ' 以下同様 End Select とか、 Val()関数の恩恵を強調するなら、 Select Case Val(Right(ListBox2, 5)) - Val(Right(ListBox1, 5)) Case 0 ' 単年 の場合の処理 Case 1 ' 1年違い の場合の処理 Case 2 ' 2年違い の場合の処理 ' ' 以下同様 End Select とか、 ListBoxのインデックスを調べて Select Case (ListBox1.ListCount - ListBox1.ListIndex) - (ListBox2.ListCount - ListBox2.ListIndex) Case 0 ' 単年 の場合の処理 Case 1 ' 1年違い の場合の処理 Case 2 ' 2年違い の場合の処理 ' ' 以下同様 End Select などのように書く方が、より基本に忠実っぽくなります。 〓〓〓 > ちなみに同じ年度を選択した場合は、解決していません こちらで書いたものを動作確認している限りでは、 > ●ListBox1=ListBox2場合、A2にListBox1の値を入力 > ●ListBox1がListBox2と1年違う場合、A2とA3にその間の期間を入力 > ●ListBox1がListBox2と2年違う場合、A2とA4にその間の期間を入力 という要件については問題なく実現されます。 例示が偶々そうなっているだけなのか、それとも、前提がそうなのか、もしかして、 「単年、1年違い、2年違い」=「1~3年間」→3年分より多くは扱わない、 というような限定があるのでしょうか? 仮にそうであったとしても、 ListBox1とListBox2の期間中の年度を列挙するということだけでしたら、 普通は、条件分岐ではなくて、For Next ループを使うものです。 説明されている以外の処理として、 期間が1年、期間が2年、の場合だけは何か特別な処理をしたい、ということならば、 別途、補足が必要です。 そういうことではなく、単にこちらが意図した対策が反映できない、ということでしたらば、 考えられる原因として、 Private flgDisEn As Boolean ' ←★モジュールの宣言部に ! の(Userformモジュールの冒頭に書かれるべき)1行が抜けている、のではないでしょうか? 〓〓〓 > 年度 という言葉が出て来たので、念の為補足しておきますが、 上記「Private Sub UserForm_Initialize() に書き加える記述」のパラメータ指定、 ReDim arrList(1985 To Year(Date)) ' ←■要指定■ の部分は ReDim arrList(1996 To 1998) ' ←■要指定■ のように年度末に書き換えるように戻した方が好いかもです。 年度が変わる月日が解れば、対応可能ですけれど、それは、 今回の課題が片付いてから、余裕のある時に、ということで、、、。 〓〓〓 > (1)A列に期間を入力、B列に別の種類のデータを入力、C列に別の種類のデータを入力・・・ > (2)A列、B列、C列・・・すべてのデータの組み合わせをセルに反映させる > (3)AdvancedFilterでデータ抽出 (2)(3)については、お尋ねのテーマではありませんので、ここでは触れません。 何かあったら、別件でお尋ねください。 〓〓〓 既存の記述に書き加える時の注意としては、 処理を重複させない 処理の順番を把握して、必要な順に記述を配置する の2点だけ意識を高めてとりかかればいいです。 今回の場合は、こちらの記述で処理する部分、ListBox1とListBox2に関する処理 リストを書き換えたり、リストを選択する記述については、既存の記述をよく読んで、 削除するべきものは正しく削除して、処理が重複しないようにして下さい。 〓〓〓 以上です。 返信を急ぐ必要はまったくないですから、じっくり取り組んでください。 ちょっとしんどいかも、ですが、頑張って下さい。
お礼
返信が大変遅れて申し訳ありません 見よう見まねで作成してみました いくつか疑問点が出ましたので、もしお分かりでしたら教えてください (1) ListBox1を選択し→ListBox2を選択すればシート上に、ListBox1とListBox2の間の期間が反映されます しかし、ListBox1を選択し→ListBox2を選択せずにCommandButtonを実行すると、期間は反映されません ListBox2を選択していないのだから当たり前かもしれませんが、ListBox1とListBox2の値が同じ場合、わざわざ選択するのが面倒です そこでListBox1を選択した時点で、ListBox2も同じ値を自動的に選択するということはできるのでしょうか (ex)ListBox1で“H8(1996)”を選択した時点で、ListBox2も自動的に“H8(1996)”を選択させ、わざわざ自分でListBox2で“H8(1996)”を選択せずにCommandButtonをクリックしたら、シート状に“H8”を反映させる (2) 以前はListBox1とListBox2の縦の長さがプロパティのWidthは同じ値で、画面にも同じ長さで表示されていました 従来は以下のコードがPrivate Sub UserForm_Initialize()にありました With Me.ListBox1 .ColumnWidths = .Width - 3 End With With Me.ListBox1 .List = Array("H8 (1996)", "H9 (1997)", "H10 (1998)", "H11 (1999)", "H12 (2000)", "H13 (2001)", _ "H14 (2002)", "H15 (2003)", "H16 (2004)", "H17 (2005)", "H18 (2006)", "H19 (2007)", "H20 (2008)", _ "H21 (2009)", "H22 (2010)", "H23 (2011)", "H24 (2012)", "H25 (2013)") .ListIndex = 0 End With With Me.ListBox2 .ColumnWidths = .Width - 3 End With With Me.ListBox2 Me.ListBox2.ListIndex = 17 End With ところが、教えていただいたコードに変更していることが影響しているのか分からないのですが、プロパティのWidthは同じ値にもかかわらず画面では縦の長さがずれてしまいます 具体的にはListBox2を選択→ListBox1を選択すると、ListBox2の縦の長さが1行分短く表示させてしまいました もし解決方法をご存知でしたら教えてください よろしくお願いします
補足
触っているとうまくいけました (1) Private Sub ListBox1_Change() With ListBox2 .Clear .List = ListBox1.List For i = 0 To ListBox1.ListIndex - 1 .RemoveItem 0 Next i .ListIndex = 0 ←【追加】 End With 【追加】で先頭を選択させることで、うまくいきました (2) 縦幅が変更が変更してまうという不具合ですが、ListBoxのプロパティ→IntegralHeightを“False”にすることで解消しました 以上の訂正をしましたが、間違いでしたら指摘お願いします
- cj_mover
- ベストアンサー率76% (292/381)
#1、cjです。#1お礼欄へのレスです。 〓〓〓 > 具体的にはPrivate Sub UserForm_Initialize()で、ユーザーフォームの大きさなどを調整していたのですが、「名前が適切ではありません」と警告がでました 既存のPrivate Sub UserForm_Initialize()があるということなら、新たにPrivate Sub UserForm_Initialize()を併記することは出来ませんから、 既存のPrivate Sub UserForm_Initialize()の中に、新たな記述を配置してやればいいです。 (基本的なこととして、ひとつのモジュールに同じ名前のSubやFunctionを設定することは出来ませんので、既存のプロシージャに追記します。) 実物見ないと説明は難しいですけれど、 取り敢えず現在の記述の後(End Sub の前)に新たな記述を書き加える方向で、もちろん、ListBox1,ListBox2リスト設定の既存記述は削除します。 それでうまくいかない場合は、新たに書き加える記述が、どんな順番に書かれるべきか、目安を書いておきましたから、参考にして下さい。 【宣言部】と5つのセクション【sect1-5】に分けています。 ' ' ーー Private Sub UserForm_Initialize() に書き加える記述 ーーーー ' ' __________________________________ ' ' 【宣言部】同一変数名で宣言が重複しないように注意。 Dim arrList() As String Dim i As Long ' ' __________________________________ ' ' 【sect1】sect2より前に書けば、どこでもいい。 ' ' List用配列(サイズとインデックスを定義) ' ' (開始年 To 終了年) を数値で指定する。(変数でも指定可。) ReDim arrList(1985 To Year(Date)) ' ←■要指定■ ' ' __________________________________ ' ' 【sect2】sect4より前に書けば、どこでもいい。 ' ' List用配列 For i = LBound(arrList()) To UBound(arrList()) arrList(i) = Left$(Format(CDate(i & "/1/1"), "ge "), 3) & " (" & i & ")" Next i ' ' __________________________________ ' ' 【sect3】sect4(LsitBoxのリスト操作)より前に書けば、どこでもいい ' ' Changeイベントをエスケープするイベント抑止フラグ flgDisEn = True ' ★ ' ' __________________________________ ' ' 【sect4】LsitBoxのリストを前提とした処理があるならば、それより前。 With ListBox1 ' ' 各ListBoxの.Listを設定 .List = arrList() ' ' Listの先頭を選択 .ListIndex = 0 End With With ListBox2 .List = arrList() ' ' Listを選択しない!! ▼ End With ' ' __________________________________ ' ' 【sect5】sect4(LsitBoxのリスト操作)より後に書けば、どこでもいい ' ' イベント抑止フラグを元に戻す flgDisEn = False ' ★ ' ' ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー 〓〓〓 > あと、B列のデータやC列のデータはCommandButtonをクリックすることで、セルにデータを反映させるようにしてたため、A列のみがListBoxを選択した時点で(CommandButtonをクリックする前から)反映されてしまいます それは至極真っ当なやり方です。ただ、それは最初に説明しておいて欲しかったですね。 対応は簡単です。 #1のPrivate Sub ListBox2_Change()を削除して、内容の一部を、改めて、 既存のPrivate Sub CommandButton1_Click() (CommandButtonの名前が違えばプロシージャ名も違いますが) の中に、配置してやればいいです。 【宣言部】と2つのセクション【sectA-B】に分けています。 ' ' ーー Private Sub CommandButton1_Click() に書き加える記述 ーーー ' ' __________________________________ ' ' 【宣言部】同一変数名で宣言が重複しないように注意。 Dim i As Long ' ' __________________________________ ' ' 【sectA】sectBより前に書けば、どこでもいい。 ' ' 出力先のセル値を空に Range("A2:A30").ClearContents ' ←■要指定■ ' ' __________________________________ ' ' 【sectB】LsitBoxのリストを前提とした処理があるならば、それより前。 ' ' ListBox2の先頭から選択位置まで"H#"(GE)をセルに出力 With ListBox2 For i = 0 To .ListIndex Cells(i + 2, "A") = PickUpGE(.List(i, 0)) Next i End With ' ' ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー 〓〓〓 尚、#1に掲載した、その他の記述について、 Private flgDisEn As Boolean ' ←★モジュールの宣言部に ! Private Sub ListBox1_Change() Private Function PickUpGE(ByVal s As String) As String の3カ所はそのまま使います。 〓〓〓 > さらに、自分でリストを作成する場合は、H8(1996)の場合とH10(1998)とで和暦と西暦の間のスペース間隔を変えることで、和暦も西暦も左揃えのように見せていたので、それができなさそうですね(細かいことで、別に気にすることのほどではありませんが) 上記「Private Sub UserForm_Initialize() に書き加える記述」にて対策済です。 arrList(i) = Left$(Format(CDate(i & "/1/1"), "ge "), 3) & " (" & i & ")" 〓〓〓 (長くなったので、次の投稿に続けます。)
- cj_mover
- ベストアンサー率76% (292/381)
こんにちは。 ListBox1で開始(?)年を選択させて、 連動してListBox2のリストは開始(?)年以降だけのものに書き直して、 ListBox2で終了(?)年を選択させて、 連動して、開始(?)年から終了(?)年までの期間、一年毎の年号を A2以下のセル範囲に連続して出力する という理解で合ってます? Userform 読み込み時、 ListBox1、ListBox2、共通で、指定期間の年号を、"GE (YYYY)"表示形式でリスト化 ListBox1_Change ListBox2.List を ListBox1 で選択された年次以降のもののみに書き換え ListBox2_Change 選択期間の年号を連続で出力 既存のプロジェクトを手直しするには、 ご提示の記述を動かすことが出来ないこともあり(sheets1とか、改行とか、) もう少し全体を見てみないと何とも言えないです。 こちらで、サンプル書いてみましたので、試してみてください。 試す為の準備手順は3つ、 Userformを挿入 ListBoxを追加 * 2回 Userformモジュールに下記サンプルコードをコピペ だけですので、既存のプロジェクトに適用する前に、新規のブックで試した方が簡単です。 /// ' ←■要指定■ マークの行は、運用に合わせて指定してください。(仮に適当な指定をしています) ' ◆ マークの行は、Range、Cells、各一ヶ所、必要がならば、親シートを正しく指定してください。 ' ▼ マークの行は、現在抱えている問題の解決に対する直接的な手当てを示しています。 ListBox2の選択をコードからは一切指定しない、ということです。 ' ★ マークの行は、現在抱えている問題の解決にも関わることと思われますが、 併せて潜在的なトラブルの原因にも対策を加える意味もあります。 /// 既存のプロジェクトにどう反映させるか、というのは、 そちらで試行錯誤してみて貰ってからの相談、ってことになりそうです。 例えば、「ListBox2 が常に選択されていることを前提としたプロシージャ」が他に書かれていたとすれば、 それだけで、適応不可ってことになってしまいます。 サンプル自体は自己完結型で書いていますが、 そちらでの現在の問題点に対しては部分的な対処を仮想で示しているに過ぎません。 うまく応用できないようでしたら、補足してみてください。 必要なフィードバックがあり、十分に把握できる内容でしたら再レスします。 冒頭に書いたこちらの理解が、そもそも間違っていたりしたならば、ごめんなさい、です。 ' ' =====Userformモジュール===== ' 8378653 Option Explicit Private flgDisEn As Boolean ' ←★モジュールの宣言部に ! ' ' /// Private Sub UserForm_Initialize() Dim arrList() As String Dim i As Long ' ' List用配列(サイズとインデックスを定義) ' ' (開始年 To 終了年) を数値で指定する。(変数でも指定可。) ReDim arrList(1985 To Year(Date)) ' ←■要指定(年次期間)■ ' ' List用配列 For i = LBound(arrList()) To UBound(arrList()) arrList(i) = Format(CDate(i & "/1/1"), "ge") & " (" & i & ")" Next i ' ' Changeイベントをエスケープするイベント抑止フラグ flgDisEn = True ' ★ With ListBox1 ' ' 各ListBoxの.Listを設定 .List = arrList() ' ' Listの先頭を選択 .ListIndex = 0 End With With ListBox2 .List = arrList() ' ' Listを選択しない!! ▼ End With ' ' イベント抑止フラグを元に戻す flgDisEn = False ' ★ End Sub ' ' /// Private Sub ListBox1_Change() Dim i As Long ' ' イベント抑止フラグが立っている場合は、抜ける If flgDisEn Then Exit Sub ' ★ ' ' Changeイベント(の再帰呼び出し)をエスケープするイベント抑止フラグ flgDisEn = True ' ★ With ListBox2 .Clear ' ▼一旦クリア ' ' 一旦、ListBox1.ListをListBox2にコピー .List = ListBox1.List ' ' ListBox1で選択されている位置より前にあるリストをListBox2から消去 For i = 0 To ListBox1.ListIndex - 1 .RemoveItem 0 Next i ' ' Listを選択しない!! ▼ End With ' ' イベント抑止フラグを元に戻す flgDisEn = False ' ★ End Sub ' ' /// Private Sub ListBox2_Change() Dim i As Long ' ' イベント抑止フラグが立っている場合は、抜ける If flgDisEn Then Exit Sub ' ★ ' ' Changeイベント(の再帰呼び出し)をエスケープするイベント抑止フラグ flgDisEn = True ' ★ ' ' 出力先のセル値を空に Range("A2:A30").ClearContents ' ←■要指定(セル範囲)■ ' ◆シート指定? ' ' ListBox2の先頭から選択位置まで"H#"(GE)をセルに出力 With ListBox2 For i = 0 To .ListIndex Cells(i + 2, "A") = PickUpGE(.List(i, 0)) ' ◆シート指定? Next i End With ' ' イベント抑止フラグを元に戻す flgDisEn = False ' ★ End Sub ' ' /// 文字列"GE (YYYY)" を引数として受け、先頭の"GE"(例:"H25")を文字列で返す関数。 Private Function PickUpGE(ByVal s As String) As String Dim nPos As Long nPos = InStr(s, "(") - 1 PickUpGE = Trim$(Left$(s, nPos)) End Function ' ' /// オマケ:文字列"GE (YYYY)" を引数として受け、Long型の数値として西暦を返す関数。 Private Function PickUpYYYY(ByVal s As String) As Long Dim nPos As Long nPos = InStr(s, "(") + 1 PickUpYYYY = Val(Mid$(s, nPos, Len(s) - nPos)) End Function ' ' ===================
お礼
回答ありがとうございます 試行錯誤していて返信送れてすみません (1) ListBox1で開始、ListBox2で終了を入力させ、その間の期間をセルに入力させるという考えでやっています なので、その理解で正しいです 補足しますと、CommandButtonをクリックすることで、 (1)A列に期間を入力、B列に別の種類のデータを入力、C列に別の種類のデータを入力・・・ (2)A列、B列、C列・・・すべてのデータの組み合わせをセルに反映させる AA列 AB列 AC列 A1 B1 C1 A1 B1 C2 A1 B2 C1 A1 B2 C2 A2 B1 C2 A2 B2 C2 といった流れ (3)AdvancedFilterでデータ抽出 というのが完成イメージです つまりは、データシートと検索条件を入力するシートがあり、ユーザーフォームで選択したキーワードに沿ってデータを抽出するものです 既に(3)AdvancedFilterコードは作成済みで、(1)でつまずいています (2) 作成していただいたコードをユーザーフォームに貼り付けするとうまくいきました 私のやり方では、年度が増えたときに、新たにリストを追加しなければならない等の煩わしさがあるので、教えていただいたやり方のほうが便利なんだと思います(理解して使えればということが前提ですが) ただ、あらかじめ自分で作成していたいくつかのコードが使えない警告が出ました 具体的にはPrivate Sub UserForm_Initialize()で、ユーザーフォームの大きさなどを調整していたのですが、「名前が適切ではありません」と警告がでました あと、B列のデータやC列のデータはCommandButtonをクリックすることで、セルにデータを反映させるようにしてたため、A列のみがListBoxを選択した時点で(CommandButtonをクリックする前から)反映されてしまいます さらに、自分でリストを作成する場合は、H8(1996)の場合とH10(1998)とで和暦と西暦の間のスペース間隔を変えることで、和暦も西暦も左揃えのように見せていたので、それができなさそうですね(細かいことで、別に気にすることのほどではありませんが) (3) 自分が作成してきたものの訂正で、 If Mid(ListBox1, Application.Find("(", ListBox1) + 1, 4) + 1 = _ Mid(ListBox2, Application.Find("(", ListBox2) + 1, 4) + 0 Then と0を追加することで、1年違いや2年違いを反映させたい場合のエラーがなくなりました ちなみに同じ年度を選択した場合は、解決していません よろしくお願いします
お礼
細かく教えていただきありがとうございました