[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『Userformから脱出』(まー)
初歩的な質問で申し訳ありませんが教えてください。 標準モジュールからユーザーフォームを呼び出しコマンドボタン2(キャンセル)をクリックするとユーザーフォームを閉じるようにしています。 標準モジュールではUserform1.showの構文あとにユーザーフォームで処理したあとの処理がつづきます。
sub test () Userform1.show '後処理 end sub
Private Sub CommandButton2_Click() Unload Me End Sub
ユーザーフォームでキャンセル処理後は標準モジュールの後処理も行わず終了したいのですがどのようにしたらよいですか?
< 使用 Excel:Excel2002、使用 OS:WindowsXP >
End つっこむのが一番ラクです でもできれば、キャンセルしない場合に最終的に押下するボタンの処理のあとに後処理を続けて書いてください その方が後々見やすい……と思っているのは私だけかもしれないですね…
(もあ) 2015/05/12(火) 17:11
脱出というか、ボタンを押されたら処理を呼び出す、という形がベターだと思います。 標準モジュールにユーザーフォームを呼び出すだけの処理と、ボタン1を押したときの処理 Option Explicit Sub test1() UserForm1.Show End Sub Sub btn1() 'ここにボタン1が押された時の処理を書く End Sub
ユーザーフォームはボタン1のときに、標準モジュールのbtn1を呼び、 ボタン2のときはただunloadするだけ Private Sub CommandButton1_Click() Call btn1 Unload Me End Sub Private Sub CommandButton2_Click() Unload Me End Sub
が定番の処理ではないでしょうか? (稲葉) 2015/05/12(火) 17:27
説明はしないけど、ユーザーフォームでプライベートスコープの変数を監視して 標準モジュールに返すやり方・・・は煩雑すぎるよね
標準モジュール Option Explicit Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Sub test2() If UserForm1.ShowForm Then MsgBox "ボタン1が押されました" Else MsgBox "キャンセルされました" End If End Sub
UserForm1 Option Explicit Private IsClick As Boolean Private IsCancel As Boolean Public Function ShowForm() As Boolean IsClick = False Me.Show vbModeless Do Until IsClick Sleep 10 DoEvents Loop ShowForm = Not IsCancel Unload Me End Function Private Sub CommandButton1_Click() IsClick = True IsCancel = False End Sub Private Sub CommandButton2_Click() IsClick = True IsCancel = True End Sub (稲葉) 2015/05/12(火) 17:31
私見ですけど、(もあ)さんの 「End」をつっこむ方式は、あとあと、保守フェーズで苦労しそうです。 コードの可読性、保守性の観点から、私個人としては【絶対に End は書かない】主義です。
(稲葉)さんの最初の提言、処理は(そのコードが標準モジュールに書かれていたとしても)ユーザーフォームが閉じられる前に 実行しておくというのが素直な方法ではないでしょうか。
その他には、このユーザーフォームはモーダル表示ですから、
With UserForm1 .Show ここで、UserForm1内のなんらかの参照できるもの(Property仕立てにしたものとか、ユーザーフォーム内に規定したPublic変数とか、等々)を取得 End With
取得した値に基づいて分岐処理。
といったことも考えられます。
(β) 2015/05/12(火) 17:51
βさん なるほど! わざわざ監視しなくても、いいわけですね。 Hideで次の処理に行けるの、実験してて初めて知りました(汗
UserForm2 Option Explicit Public IsCancel As Boolean Private Sub CommandButton1_Click() IsCancel = False Me.Hide End Sub Private Sub CommandButton2_Click() IsCancel = True Me.Hide End Sub Public Sub UnloadMe() Unload Me End Sub
標準モジュール Sub test3() With UserForm2 .Show If .IsCancel Then MsgBox "キャンセルされました" Else '通常の処理 End If .UnloadMe End With End Sub (稲葉) 2015/05/12(火) 18:36
ユーザーフォームのxボタンを押された時の対処のために Query_Close イヴェントを私は よく使いますが、基本的には、βさんや稲葉さんの仕様に賛成です。
ユーザーフォームを表示させてどのボタンが押されたかを呼び出し元のプロシジャーに返すという仕様ですよね
ユーザーフォームがどのボタンを押されようが その情報は、呼び出し側に返すという仕様にすることを 考えてください。 返されたボタンによって分岐する処理は、よびだし側に任せてしまえばよいのです。
ユーザーフォーム側で判断する必要はありません。
何度か引用する言葉ですが、「卵下さい と言われたら 卵を返せばよいのです ゆで卵にして返してはいけません」 私が 自身の戒めになっている かつての上司に言われた言葉です。 こんな考え方を片隅に置いて仕様を考えると、汎用的な 結合度の低い オブジェクトを作る 指針になると思います。
(ichinose) 2015/05/12(火) 18:53
横から失礼します。
もしかしてこういうことですか?
Sub test() Dim f As String Dim YN As Integer YN = MsgBox("ユーザーフォームを表示しますか?", vbYesNoCancel, "どっちでっか?") Select Case YN Case vbYes UserForm1.Show 0 Case vbNo AppActivate Application.Caption MsgBox "いいえ" Case vbCancel AppActivate Application.Caption MsgBox "キャンセル" End Select End Sub
思いっきり外してそう・・・。 (カリーニン) 2015/05/12(火) 19:05
To 稲葉さん
Hide じゃなく Unload でもできますよ。
Public IsCancel As Boolean
Private Sub CommandButton1_Click() IsCancel = False Unload Me End Sub
Private Sub CommandButton2_Click() IsCancel = True Unload Me End Sub
で、標準モジュールに
Sub Test() With UserForm1 .Show MsgBox .IsCancel End With End Sub
(β) 2015/05/12(火) 19:25
>Hide じゃなく Unload でもできますよ。 この記述、このスレッドで私も知りましたが、 [[20120522093647]]
出来ようが出来まいが 理屈として Hideですよね
リンクスレッドでも記述しましたが、
ユーザーフォーム上に
Public aaa as string
の場合は、値が保持されません。
hideにしておくのが無難だと思いますよ
(ichinose) 2015/05/12(火) 20:11
To ichinoseさん
リンクスレッド、ざらっっと拝見。(流し読みですが)
Hide なら、そもそもが議論の対象にはなりませんよね。 オブジェクトがなくなるわけではなく、存在しているわけですから。 ブックやシートを非表示にしても、その参照が(あたりまえですが)できるのと同じですから。
素人ですので、本職のichinose師匠に、どうこういう立場ではありませんが、その素人的な理解からいえば With で、そのフォームオブジェクトを参照している。その参照がおわらないうちは、オブジェクトは消滅しないんだろうと。
まとはずれかもしれませんが、以下のコードで生成されたDictionaryインスタンス。 ★でやっているのは、あくまで DicA という変数をクリアといいますか、そこからの参照をやめただけで インスタンスそのものは DicB という変数で参照され続けていますので、当然、消滅せず、参照可能。 これと、おなじことなんじゃないかなぁと。
Sub Test() Dim dicA As Object Dim dicB As Object
Set dicA = CreateObject("Scripting.Dictionary") dicA("AAA") = "AAA_DATA"
Set dicB = dicA
dicB("BBB") = "BBB_DATA"
MsgBox dicA("BBB")
MsgBox dicB("AAA")
Set dicA = Nothing '★
MsgBox dicB("AAA") MsgBox dicB("BBB")
Set dicB = Nothing
'以下は当然エラー MsgBox dicB("AAA")
End Sub
(β) 2015/05/12(火) 21:54
もう1つ、逆の経験談で、Unload の場合、End With の前でも参照ができないケースがあります。
MsgBox .ComboBox1.Tag なんてのはOKなんですが、MsgBox .Tag とやるとエラーになりますね。
(β) 2015/05/12(火) 22:02
> Hide なら、そもそもが議論の対象にはなりませんよね。
そうですよ!!議論というより、HideのコードにUnload Meを出す意味がわかりません。
現象として 面白い内容であることは リンク先にも記述した通りです。
ユーザーフォーム(UserForm1)にコマンドボタン(CommandButton1)を一つ配置してください。
userform1のモジュール
Option Explicit Private dat As Long Private Sub CommandButton1_Click() dat = 1 Unload Me End Sub Property Get ans() As Long ans = dat End Property
標準モジュール
Option Explicit
Sub test1() With UserForm1 .Show MsgBox .ans End With End Sub
Test1の実行で userform1 が表示 コマンドボタンクリックでエラーになります。
こういう結果を含んでいるので このような場合に Unloadではなく、Hideにした方が 良いですと 申し上げたいのです。
>MsgBox .ComboBox1.Tag なんてのはOKなんですが、MsgBox .Tag とやるとエラーになりますね。
ご自身でもわかっていると思いますが、このようにエラーになるケースがあるのなら尚更です。
面白い現象として記述するならよいですが、
>>Hide じゃなく Unload でもできますよ。
と記述すると UnloadでもHideと同じ効果がある と誤解します。
この例の場合 、 色々なことを想定すれば 記述するなら Hideです。
私が申し上げたいのは、この一点です。
(ichinose ) 2015/05/12(火) 22:28
目を離している間に話が・・・
Unloadでも、Hideと全く同じことはできないが、Unloadで処理する方法もある、ってことでいいんですかね?
話は変わってしまうのですが Withの話の時に、End Withでオブジェクトが解放される・されないの話を見たときに じゃあHideの時は? と考えてあえてEnd Withの前にUnloadさせているのですが この場合、変数にセットして、使い終わったらNothingのほうがいいのでしょうか? いずれにしても、明確に消したほうがいいのかなと思っていたのですが、必要ない・・・? (稲葉) 2015/05/13(水) 00:48
>>面白い現象として記述するならよいですが、 >>>Hide じゃなく Unload でもできますよ。 >>と記述すると UnloadでもHideと同じ効果がある と誤解します。 >>この例の場合 、 色々なことを想定すれば 記述するなら Hideです。
まぁ、いろいろな考え方、あるいは受け取り方があると思いますので。 β的には、極端に Unload した後でも Public変数の参照ができるという事象を述べただけす。 すべてのことができるというようには申し上げていないつもりです。
また、つっこまれそうですが、↑の稲葉さんの問題提起、明確に Nothing にしたほうがいいかどうかについても β的には、そのまま、うっちゃっておきます。 もしかしたら、インスタンスが、ちゃんと解放されているかもしれなし、あるいはされていないかもしれない。 「おそらく」当該プロシジャが終了すれば解放されると思っているんですが、それが解放されなかったとしても 「それが、なんぼのもんじゃ」というのがβの基本スタンスです。
たとえばミッションクリティカルな処理、かつ、24時間356日連続で稼働保証が必要なものであれば別でしょうが しょせん、我々がエクセル上で、VBAで、ちゃちゃちゃ と処理する程度のものであれば、エクセル区画で 動いているはずの膨大な数のオブジェクトの中で、たとえ1つ、そのまま浮遊していたとしても、めくじらたてるまでのこともないと。
ですから、βの書くコード、プロシジャの中でSet したObject変数に対する、終了前の Nothing化は、原則なしです。
(β) 2015/05/13(水) 06:12
> Unloadでも、Hideと全く同じことはできないが、Unloadで処理する方法もある、ってことでいいんですかね?
私は、今回のUnload Meの使い方、非常に危ういコードだと思っています。 これをあたかも実用できるというような「Unloadで処理する方法もある」という記述さえ
大反対です。大^100反対です。
現象としては、大変興味深いし この現象を解明する 又は、予測する という話なら 是非やりましょう という事になりますけどね!!
先のβさんのDictionaryを使ったオブジェクトの参照から、
ユーザーフォームを Withで修飾しているのと 同じという仮説は、Propertyプロシジャーがエラーなったり、他にも 直下のプロパティがエラーになっている結果から 明らかに違う現象です。
前回の私の投稿で
Sub test1() With UserForm1 .Show msgbox userforms.count MsgBox .ans End With End Sub
msgbox userforms.countは、 Unload では、0を返します。
あやういでしょう?
本当に単なる想像にすぎませんが、
With Userform1 という記述で生まれてしまった メモリ上の残骸データが起こした奇跡
尾崎亜美風に言えば、「疲れ切ったあなたUserformの幻をみていたの♪」(音あまり)だと思っています。
ちょっと気になったのは、・・・・。
ユーザーフォームの表示の記述って いくつか方法がありますよね?
普通に
userform1.showに始まって、
dim frm as userform1
set frm=userfom1
frm.show
他にもありますよね!! これらで今度の現象を確認してみたりすると発見もあるかもしれません。
>じゃあHideの時は?
With userform1 .show 'me.hide で戻す '返り値チェック end with unload userform1
としているコードが殆どです。
>使い終わったらNothingのほうがいい
UserForm_Terminateイベントの発生タイミングをこれを先の記述のように (ユーザーフォームの表示の記述って いくつか方法)のいくつかの方法で試してみるのもよいかもしれません。Unloadステートメントを改めて見つめなおすのも良い機会だと思います。
(ichinose ) 2015/05/13(水) 06:19
本件は、是非論からいえば、絶対に ichinoseさんがおっしゃるとおりです。 βのいってることは、たとえば、甘いものの食べすぎはだめだ ということに対して、たま〜に、ちょっとぐらい いいじゃないか というようなもので、どうしても分が悪いです。
なので、本件からは、これで撤退しますが、
たとえば、シートの1行目で一番最後に値があるセルを求めたい なんてことがあれば βは 平気で MsgBox Cells(1, Columns.Count).End(xlToLeft).Address こんなコードを書くでしょうし そういう質問があれば、そう答えます。
そのことに関して、「いや、それはあやうい。もし、最終列までずらっとデータが埋まっていると正しいセルが取得できない」と お叱りを受けたことはありません。
おそらく、皆さんも、この処理のために、最終列まで埋まっているかという制御をいれるコードは、書かないだろうと思います。
なぜ、こと、オブジェクトに関しては、「絶対にダメだ!」という反応になるのかが、β的にはよくわからないところです。
(β) 2015/05/13(水) 08:00
Unloadでも出来てしまう事実があるとして 私が気になったのは、デストラクタの発生タイミングでした。 UnLoadとhide、QueryClose、Terminameのタイミングが異なりますので 気をつけようと思います。
お二人の意見を聞いて、UnloadやNothingといった1行を「入れないこだわり」を 捨てるということで、今後も入れていきたいと考えています。
>ここで、UserForm1内のなんらかの参照できるもの(Property仕立てにしたものとか、 ~~~~~~~~↑~~~~~ ここでhideのことを指していると思ってました
ユーザーフォーム Option Explicit
Public IsCancel As Boolean
Private Sub CommandButton1_Click() IsCancel = False Debug.Print "1クリック" Unload Me Debug.Print "Unload" End Sub
Private Sub CommandButton2_Click() IsCancel = False Debug.Print "2クリック" Me.Hide Debug.Print "Hide" End Sub
Private Sub UserForm_Initialize() Debug.Print "初期化" End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer) If CloseMode = vbFormControlMenu Then IsCancel = True Cancel = True Me.Hide End If Debug.Print "クエリクローズ" End Sub
Private Sub UserForm_Terminate() Debug.Print "解放" End Sub
標準モジュール Option Explicit
Sub Test() With UserForm1 .Show Debug.Print .IsCancel Unload UserForm1 End With End Sub
ボタン1の順序 初期化 1クリック クエリクローズ 解放 Unload False (初期化) (クエリクローズ) (解放)
ボタン2の順序 初期化 2クリック Hide False クエリクローズ 解放
×ボタンの順序 初期化 クエリクローズ True クエリクローズ 解放
最終的に質問者さんのやりたいことをほとんどそのままかなえるなら このように修正すると良いのでは、という結論になりました。 ユーザーフォーム Option Explicit
Public IsCancel As Boolean
Private Sub CommandButton1_Click() IsCancel = False Me.Hide End Sub
Private Sub CommandButton2_Click() IsCancel = True Me.Hide End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer) If CloseMode = vbFormControlMenu Then IsCancel = True Cancel = True Me.Hide End If End Sub 標準モジュール Option Explicit
Sub Test() With UserForm1 .Show If .IsCancel = False Then '処理 End If End With Unload UserForm1 End Sub (稲葉) 2015/05/13(水) 10:20
撤退宣言したのにのこのこと と思いましたので書きこんだレスを消しましたが やっぱりアップしておきます。
Hideなら何も考えることはないとコメントしているとおり、以下でよろしいのでは。 機能テストですから、Xボタンで閉じられたらどうするんだということは無視しています。 からっぽのユーザーフォームを表示してフォームクリックで Hide しています。
Public IsCancel As Boolean Private Sub UserForm_Click() IsCancel = True Me.Hide End Sub
標準モジュール
Sub test() UserForm1.Show MsgBox UserForm1.IsCancel End Sub
(β) 2015/05/13(水) 15:06
稲葉さんが記述された >この場合、変数にセットして、使い終わったらNothingのほうがいいのでしょうか?
ユーザーフォームをオブジェクト変数に入れて 試してみました。 新規ブックにて ユーザーフォーム(UserForm1)を作成し、コマンドボタン(CommandButton1)を配置したものを用意してください。
UserForm1のモジュールに
'============================================= Option Explicit Public aaa As Long Private Sub CommandButton1_Click() aaa = 2 Me.Hide End Sub Private Sub UserForm_Terminate() Debug.Print "term" End Sub
標準モジュールには '================================================================ Option Explicit Sub test1() Dim frm As UserForm1 Set frm = UserForm1 With frm .Show MsgBox .aaa End With Set frm = Nothing End Sub test1を実行すると Set frm = Nothing で、Terminateイベントは発生しません。
ところが
Sub test2() Dim frm As UserForm1 Set frm = New UserForm1 With frm .Show MsgBox .aaa End With Set frm = Nothing Debug.Print "end" End Sub test2では、きちんと Terminateイベントが発生します。 興味深いですよね!!
これらの事から 私は Unloadステートメントを使う事が殆どです。
(ichinose ) 2015/05/14(木) 05:35
>なぜ、こと、オブジェクトに関しては、「絶対にダメだ!」という反応になるのか
今回のUserForm内での Unload Meというコードで呼び出し側に戻り、UserForm内のPublic変数が参照できてしまう現象 そのメカニズムは 今までの投稿から きちんと分かっていませんよね!
もしかしたら バグの逆効果で参照できているのかもしれないですよね? そうだとしたら、いずれこのロジックの変更を余儀なくされます。 他に方法がないのなら、当面の施しとしてはわからないではありません。
が、Me.Hideにすれば、問題がないのに敢えて 根拠のないロジックを選択することを反対しています。
検証が難しい題材なのに何故 と私の方が大きい疑問です。
>With Userform1 という記述で生まれてしまった メモリ上の残骸データが起こした奇跡
こんな想像があたっているとしたら 危険コードです。
Unloadによって、本来なら 削除されクリアされるべきメモリ空間を何らかの原因でデータが残っていて 参照できてしまう、言わば ライセンスを失ったデータを参照する こんなデータを参照したら どんな結果になるか分かりません。
UserFormが出力する情報をプロパティ(Publicデータ)として 取得するという仕様する。
オブジェクトの独立性を高めるための施しです。使い方は、呼び出し側委ねられてもオブジェクトは淡々と 仕様通りの情報を提供する これを実現するために このような仕様にしているのですよね!!
私もこの事が重要だと感じているので 色んなスレッドで Public変数の乱用等には、意見を投稿してきたつもりです。
今回のUnloadステートメント使用について
新規ブックにて ユーザーフォーム(UserForm1)を作成し、コマンドボタン(CommandButton1)を配置したものを用意してください。
UserForm1のモジュールに '============================================= Option Explicit Public aaa As Long Private Sub CommandButton1_Click() aaa = 2 Unload Me End Sub
標準モジュールには '================================================================ Option Explicit Sub test1() With UserForm1 .Show MsgBox .aaa End With End Sub
test1では、確かに aaaが取得できていますが、
Sub test2() With UserForms.Add("userform1") .Show MsgBox .aaa End With End Sub
や
Sub test3() Dim frm As Object Set frm = UserForm1 With frm .Show MsgBox .aaa End With End Sub
では、エラーが発生します。
これでは、独立性を高めても意味が薄れてしまいます。 これらのことが 反対した理由です。
↑ ここでも今回とほとんど同じ話をしています。もう3年前なんです。
このスレッドでも
>これは、あるいみあたりまえですね
Me.Hideで行うロジックに対して 言われた言葉です。
私は、ごくごく当たり前のことしか言っていないのです。難しい話を展開しているつもりは まったくありません。
これは、テーマが違うのでまた別の場面で意見交換させていただくとして
>ですから、βの書くコード、プロシジャの中でSet したObject変数に対する、終了前の Nothing化は、原則なしです。
この件も私は、別意見です。 もっとも私は、よく忘れます。コードレビューでよくツッコまれます。 自分でNothing入れろ って言っておきながら・・・ と。
( ichinose) 2015/05/14(木) 05:56
ichinoseさんの2015/05/14(木) 05:35投稿で、Terminateイベントの発生有無確認致しました。 その後、Set frm = NothingをUnload frmに置き換え、Terminateイベントが発生することも確認できました。 残った変数をローカルウィンドウで確認すると、Userform1の形で残っていたので、 Unload後にNothingをSetするのが筋かなぁと思いました。
当たり前のことですが Unload UserForm1 '1 Unload UserForm1 '2 とすると、2でIniとTermが走り
Unload frm '3 Unload frm '4 では、4でIniとTermが走りませんでした
言いたいことが自分でもわかってないですが、自分の中で新しい発見でした。
(稲葉) 2015/05/14(木) 08:50
[ 一覧(最新更新順) ]
YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki.
Modified by kazu.