Visual Basic 中学校 掲示板 投稿内容
タグのない投稿を抽出 統計 RSS

Visual Basic 中学校 > 投稿一覧 >

モーダル表示中に他のフォームを操作したい

タグの編集...

投稿者 アシタカ   (学生)   投稿日時 2021/12/16 15:34:21
いつもお世話になっております。
ちょっと色々試行錯誤していて気になったので教えて下さい。

【前提】
Form1がメイン画面
Form2がモーダルフォーム
Form3がモードレスフォーム
という構成でフォーム画面を作っています。

【実現したい事】
メイン画面からForm3(モードレスフォーム)を表示
その後Form2(モーダルフォーム)を表示
この時にForm3(モードレスフォーム)を操作できるようにしたいです。

よろしくお願いいたします。


投稿者 魔界の仮面弁士   (社会人)   投稿日時 2021/12/16 15:59:45
モーダル側から、モードレス画面のLabel の値を変更したりするぐらいはできますが、
モーダル表示中に非モーダルな画面を、ユーザーが操作可能な形にすることはできません。

ユーザー操作を許可したいのであれば、モーダル画面を使わずに
モードレスで表示するようにしてください。
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=30668

投稿者 アシタカ   (学生)   投稿日時 2021/12/16 17:24:37
下記のような内容を見つけたのですが、
https://teratail.com/questions/34440

これはvb.netだと難しいんでしょうか?

投稿者 (削除されました)   ()   投稿日時 2021/12/16 17:50:53
(削除されました)

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2021/12/16 19:08:05
モーダル表示中に、それ以前に表示されていた既存フォームを操作することはできません。
(モーダル表示中に、新たなモードレスフォームを表示することならばできます)

今回のケースであれば、Form2 をモーダルで呼ばずに、
モードレスとして呼び出すだけで解決できる話だと思うのですが、
それが出来ない理由は何ですか?

投稿者 (削除されました)   ()   投稿日時 2021/12/16 19:55:48
(削除されました)

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2021/12/16 20:08:28
> 今回のケースであれば、Form2 をモーダルで呼ばずに、
> モードレスとして呼び出すだけで解決できる話だと思うのですが、
> それが出来ない理由は何ですか?

当初の質問から推察して、現状は
 ①Form1 から Form3 が呼び出されている(モードレス)
 ②Form2 は Form3 の表示後に表示される画面である
 ③Form2 がどこから呼び出されるのかは明記されていないが
  Form2 は現状、モーダル ダイアログとして呼び出している ★それは何故?
 ④Form2 がモーダルなので、Form2 表示中は Form1 や Form3 を触れない状態である
 ⑤Form2 がモーダルなので、Form2 が閉じられるまで呼び出し元は待機状態となる
 ⑥Form2 が表示された後、先に表示されていた Form3 が触れないのを何とかしたい
という状況なのですよね。


この場合、⑥ の事象は ③ が原因で引き起こされているわけですから、
それならばモードレスにしてはどうでしょうか、というのが私の提案となります。
(以前に表示されていたフォームを触れてしまったら、それは modal とは言えないと思いますし…)


しかしモードレスにすると、④と⑤の振る舞いが変わってきますので、ここで追加確認させてください。

➍ Form2 の表示中に、Form3 だけでなく Form1 も操作可能になると、何か問題がありますか?
➎ Form2 が閉じられるまで、呼び出し元が ShowDialog の結果を待ち合わせる実装で無いと困りますか?


4/5のどちらも必須事項では無いのなら、
Form2 を ShowDialog から Show に変えるだけで済むと思います。

4が問題だというのなら、Form3 を表示する前に Form1 を Enabled = False にしておき、
Form3 が閉じられたときに、Form1 を Enabled = True に戻す処理を書くという手が使えます。
Form1 のコントロールが淡色表示になってしまうのが難点ですけれども。

5が問題なのであれば、Form3 はモードレスで表示しつつ、
フォームが閉じられるのを Async/Await を使って待ち合わせるという手があります。

Partial Public Class Form2
    Inherits Form
    ''' <summary> 
    ''' モードレスだけどモーダル表示っぽく振る舞わせてみる 
    ''' </summary> 
    Public Async Function ShowDialogAsync() As Task(Of DialogResult)
        Dim tcs As New TaskCompletionSource(Of DialogResult)()
        Dim closedHandler As FormClosedEventHandler = Nothing
        closedHandler =
            Sub(sender As Object, e As FormClosedEventArgs)
                RemoveHandler FormClosed, closedHandler
                'フォームが閉じられた時、完了状態に遷移させる 

                'SetResult : 正常終了 
                'SetCanceled : キャンセル 
                'SetException : 例外発生 
                tcs.SetResult(Me.DialogResult)
            End Sub
        AddHandler FormClosed, closedHandler

        Dim hideHandler As EventHandler = Nothing
        hideHandler =
            Sub(sender As Object, e As EventArgs)
                If Me.Visible Then Return
                RemoveHandler VisibleChanged, hideHandler
                'フォームが非表示になった時、完了状態に遷移させる 
                tcs.SetResult(DialogResult.Cancel)
            End Sub
        AddHandler VisibleChanged, hideHandler

        'モーダルではなく、モードレスで表示 
        Me.Show(Owner)

        'Await を使って、完了まで待ち合わせてみる 
        Return Await tcs.Task
    End Function

    '普通のモーダル ダイアログであれば、 
    'デザイン時に Button1.DialogResult 等を設定しておくだけで 
    'Yes/No/Cancel な閉じるボタンの動作となります。 
    
    'しかしモードレスの場合にはその機能が稼働しないため、 
    '自分で DialogResult や Close を呼ぶなどして 
    '同等の機能を作りこむ必要があります。 
#Region "Yes/No/Cancel ボタン"
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Me.DialogResult = DialogResult.Yes
        Me.Close()
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        Me.DialogResult = DialogResult.No
        Me.Close()
    End Sub

    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        Me.DialogResult = DialogResult.Cancel
        Me.Close()
    End Sub
#End Region
End Class



'Form2 の呼び出し元 
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        '普通のモーダルダイアログ呼び出し 
        Dim f As DialogResult
        Using f3 As New Form3()
            f = f3.ShowDialog(Me)
        End Using
        MsgBox(f.ToString())
    End Sub

    Private Async Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        'モードレスだけど疑似的にモーダルっぽく動作させる 
        Me.Enabled = False
        Dim f As DialogResult
        Using f3 As New Form3()
            f = Await f3.ShowDialogAsync()
        End Using
        Me.Enabled = True
        MsgBox(f.ToString())
    End Sub



投稿者 魔界の仮面弁士   (社会人)   投稿日時 2021/12/16 20:44:49
何度か書き直していましたが、まだ間違いが残ってた…。 orz

> Using f3 As New Form3()

済みません。モーダル表示していたいのは、Form3 ではなく Form2 でしたっけね。
上記の「New Form3()」の部分は、「New Form2()」に読み替えてください。


> Public Async Function ShowDialogAsync() As Task(Of DialogResult)

Hide() あるいは Visible = False で閉じられた場合、
本来の ShowDialog であれば DialogResult.Cancel が返されますが、
今回の ShowDialogAsync だと DialogResult.None が返されています。御了承ください。

投稿者 KOZ   (社会人)   投稿日時 2021/12/17 03:03:54
> 下記のような内容を見つけたのですが、
> https://teratail.com/questions/34440
> これはvb.netだと難しいんでしょうか?

VB.NET で記述できますが、アプリケーションフレームワークは使わないほうがいいかもしれません。
作りをざっと見た感じだと、マルチスレッドで動くことを前提に作られていない気がします。

(1) 「アプリケーションフレームワークを有効にする」のチェックを外し、スタートアップオブジェクトを Sub Main にします。

(2) Program.vb というモジュールを作成し以下のコードを書きます。

Public Module Program

    <STAThread>
    Sub Main()
        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.Run(New Form1())
    End Sub

End Module



(3) Form1 にボタンを貼り付けて、以下のコードを書きます。

Public Class Form1

    Private Class OwnerHandle
        Implements IWin32Window

        Public Sub New(ByVal handle As IntPtr)
            Me.Handle = handle
        End Sub

        Public ReadOnly Property Handle As IntPtr Implements IWin32Window.Handle

    End Class

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim owner As New OwnerHandle(Me.Handle)
        Task.Run(Sub()
                     Dim form3 As New Form3()
                     form3.Show(owner)
                     Application.Run(form3)
                 End Sub)
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim form2 As New Form2()
        form2.TopMost = True
        form2.ShowDialog()
    End Sub

End Class