- PR -

MDI子フォームを最背面に移動する

1
投稿者投稿内容
あぶぽん
大ベテラン
会議室デビュー日: 2005/10/20
投稿数: 205
投稿日時: 2005-10-27 10:20
皆さま、いつもお世話になっております。

件名の通り、MDIインターフェイスのアプリケーションで、
子フォームを複数開いているとき、(具体的にはクローズボタンを
押したタイミングで)最前面の子フォームを最背面に移動させたい
と考えております。

MDI親フォームのToolBarを切り替えるには?
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=25340&forum=7&4

のスレを読んで頂いた方には分かるかと思いますが、
MDI子フォームのHide()にバグがありますので(VS2003)、
その代わりに最背面に移動するという仕様となったのです。

まずは単純にForm#Closingイベントで、Control#SendToBack()を実行しました。

子フォームは最背面に移動しましたが、
次の画面がActivatedになりません。

特にフォームを最大化している場合、薄っすらと透けたように、
後ろに隠れていたウィンドウが標準化状態で表示されます。

# これはその中から好きなウィンドウを選べるようにという仕様なの
# でしょうか???

また、明示的にControl#Focus()しようと思っても、
Zオーダーが取れないので次にどのフォームをFocus()すれば良いのか
分かりません。

因みにそのときのForm#ActiveMdiChildの値は最背面に移動したフォーム
のままです。

コーディングでZオーダーを管理すれば簡単に解決できそうですが、
本筋ではなさそうです。

.NETの流儀というか標準ではどのような方法をとるべきなのでしょうか?

# 私が根本的にやり方を間違えているのかも知れません。

コード:
private void FormA_Closing(object sender, 
	System.ComponentModel.CancelEventArgs e)
{
	e.Cancel = true;
	this.SendToBack();
}

まどか
ぬし
会議室デビュー日: 2005/09/06
投稿数: 372
お住まい・勤務地: ますのすし管区
投稿日時: 2005-10-27 14:05
まどかです。

引用:

また、明示的にControl#Focus()しようと思っても、
Zオーダーが取れないので次にどのフォームをFocus()すれば良いのか
分かりません。


API関数GetNextWindowが使えないでしょうか?

引用:

.NETの流儀というか標準ではどのような方法をとるべきなのでしょうか?
コード:
private void FormA_Closing(object sender, 
	System.ComponentModel.CancelEventArgs e)
{
	e.Cancel = true;
	this.SendToBack();
}




#言われることが全般的なことか、文中でやろうとしてることについてなのかで変わりますが。。。
個人的には、「閉じようとしている」「最中」にフォーカスうんぬんというのは違和感があります。
極論を言えば、MDIのGUIとしての性質からHideにすること自体好ましくないかもしれません。
#子フォームはユーザーが自由に操作できるべき
#ということは、子フォームが自身で処理が閉じていて、そこからHideする仕様は生まれないはず

Hideによりしようとしてるのは値の保持でしょうか?他のフォームとの連携ですか?
あぶぽん
大ベテラン
会議室デビュー日: 2005/10/20
投稿数: 205
投稿日時: 2005-10-28 09:55
まどかさん、ありがとうございます。

引用:

引用:

また、明示的にControl#Focus()しようと思っても、
Zオーダーが取れないので次にどのフォームをFocus()すれば良いのか
分かりません。


API関数GetNextWindowが使えないでしょうか?


調べてみたので報告します。

GetNextWindowですがGetWindowをGW_HWNDNEXTかGW_HWNDPREV
で呼び出すのと等価なようです。

P/Invokeという手法らしいですね。

コード:
public class FormA
{
    enum GetWindow_Cmd {
        GW_HWNDFIRST = 0,
        GW_HWNDLAST = 1,
        GW_HWNDNEXT = 2,
        GW_HWNDPREV = 3,
        GW_OWNER = 4,
        GW_CHILD = 5,
        GW_ENABLEDPOPUP = 6
    }
    
    [DllImport("user32.dll")]
    static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
    
    private void FormA_Closing(object sender, 
        System.ComponentModel.CancelEventArgs e)
    {
        // (省略)
        IntPtr hwnd =GetWindow(this.Handle, GetWindow_Cmd.GW_HWNDNEXT);
        // (省略)
    }
}



引用:

引用:

.NETの流儀というか標準ではどのような方法をとるべきなのでしょうか?


#言われることが全般的なことか、文中でやろうとしてることについてなのかで変わりますが。。。


例えば、Hide()のバグがあるのは仕方ないとして、それを回避するために
どういった作法でアプローチするのか、
そういうインターフェイス(裏口のようなもの)が用意されているのか否か、

もうひとつはControl#SendToBack()というメソッドがあるのだから、最背面に
移動したフォームの代わりに次のフォームにフォーカスをあてるということが、
当然、考えられる訳で、そのための方法(こちらはバグではないので、できること
ならWin32APIを使用せずに)があるのかどうか、

# しかし、Control#SendToBack()をMDI子フォームを最背面に移動するために
# 使用するのが正当な方法とはどこにも書いていませんし、おそらく、
# 間違っているのだろうと思いますが。

そういうことを言いたかったのです。

引用:

個人的には、「閉じようとしている」「最中」にフォーカスうんぬんというのは違和感があります。
極論を言えば、MDIのGUIとしての性質からHideにすること自体好ましくないかもしれません。
#子フォームはユーザーが自由に操作できるべき
#ということは、子フォームが自身で処理が閉じていて、そこからHideする仕様は生まれないはず

Hideによりしようとしてるのは値の保持でしょうか?他のフォームとの連携ですか?


Hide()にする理由は、「値の保持」と「表示の高速化」、「リソースの再利用」、
「メモリ管理をガーベジコレクタに任せたくない」、「既存システムがそういう設計なので」、
です。

また、.NETの流儀ではどうか分からないですが、少なくともJavaやVC++などでは、
ウィンドウクローズの変わりに非表示(Hide)にするのは定石だと思います。

.NETでも2005では直っているということは、
「MDI子フォームを非表示(Hide)することは推奨しない」という態度では
ないように思われますよ。

じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2005-10-28 10:05
引用:

あぶぽんさんの書き込み (2005-10-28 09:55) より:

.NETでも2005では直っているということは、
「MDI子フォームを非表示(Hide)することは推奨しない」という態度では
ないように思われますよ。


もともと、値の保持は別で任せるべきだと思うんですが。
2003 よりももっと前から、分離して考える手法は取られています。

表題の件ですが、SendToBack してから何をしようと Active にはならないっぽいです。
別の MDI の話であったように、最初の MDI 子フォームを Active だと勘違いしてるっぽいです。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
まどか
ぬし
会議室デビュー日: 2005/09/06
投稿数: 372
お住まい・勤務地: ますのすし管区
投稿日時: 2005-10-28 12:15
引用:

調べてみたので報告します。


で、うまくいったのでしょうか?(^^;

引用:

Hide()にする理由は、「値の保持」と「表示の高速化」、「リソースの再利用」、
「メモリ管理をガーベジコレクタに任せたくない」、「既存システムがそういう設計なので」、
です。

また、.NETの流儀ではどうか分からないですが、少なくともJavaやVC++などでは、
ウィンドウクローズの変わりに非表示(Hide)にするのは定石だと思います。


個人的にはMDIではおっしゃる「表示の高速化」くらいしか理由が思いつきません。
で、現代のスペックではよっぽど重たい初期処理が無い限り必要性も感じません。
「そうなんだ」と言われればおしまいですが。
#Hideするなと言ってるわけではありません。

ただ、親は表示するだけ、子は自分の中で閉じた処理をするということからすれば
最初の投稿でも書いたように、
・親と子で同一コントロールインスタンスを共有する
・子にPublic変数だけれど他の子をShowする記述がある
というのはお勧めしません。
あぶぽん
大ベテラン
会議室デビュー日: 2005/10/20
投稿数: 205
投稿日時: 2005-10-28 13:20
まどかさん、じゃんぬねっとさん、有り難うございます。

引用:

引用:

調べてみたので報告します。


で、うまくいったのでしょうか?(^^;


いくのではと思ってますが、

今回のプロジェクトではWin32APIを使うことが却下されたので、
試していません。

個人的には試してみたいと思っているのですけど。。。

引用:

個人的にはMDIではおっしゃる「表示の高速化」くらいしか理由が思いつきません。


正直なところ、

じゃんぬねっとさんもおっしゃるように、
引用:

もともと、値の保持は別で任せるべきだと思うんですが。
2003 よりももっと前から、分離して考える手法は取られています。


という意見に賛成なのですが、

「既存システムがそういう設計なので」という拘束が一番大きいです。
いつものことながらジレンマですね。

以下もその通りだと思います。
引用:

ただ、親は表示するだけ、子は自分の中で閉じた処理をするということからすれば
最初の投稿でも書いたように、
・親と子で同一コントロールインスタンスを共有する
・子にPublic変数だけれど他の子をShowする記述がある
というのはお勧めしません。



それで、現在は他の方法を考えています。

とにかく、子フォームを本当にはクローズせず、ユーザには
閉じたかのように見せればいいわけなのですが。。。
まどか
ぬし
会議室デビュー日: 2005/09/06
投稿数: 372
お住まい・勤務地: ますのすし管区
投稿日時: 2005-10-28 13:40
引用:

とにかく、子フォームを本当にはクローズせず、ユーザには
閉じたかのように見せればいいわけなのですが。。。


Hideを使わないことが前提と思われるので、
MDI配下から除外して、極端な座標へ移動するとか。
#試さずに書いてます。
ただ、アプリケーション内でのフォーカス移動を殺す必要があったりしますが。
ただしVisible=Trueの場合、どのような場合もウィンドウリストやウィンドウの列挙に注意が必要ですね。

#ベストはバグのため不可能であることを納得させることですかね。。。
ジブ
大ベテラン
会議室デビュー日: 2005/09/22
投稿数: 135
投稿日時: 2005-10-28 19:17
引用:

今回のプロジェクトではWin32APIを使うことが却下されたので、



こういうことならできるかも

コード:
    Private WithEvents formA As formA
    Private WithEvents formB As formA
    Private WithEvents formC As formA

    Private _MdiChildren As New System.Collections.ArrayList
    Private _MdiZorder As New System.Collections.ArrayList

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        formA = New formA
        formA.MdiParent = Me
        formA.Text = "A"
        formA.Show()
        _MdiChildren.Add(formA)

        formB = New formA
        formB.MdiParent = Me
        formB.Text = "B"
        formB.Show()
        _MdiChildren.Add(formB)

        formC = New formA
        formC.MdiParent = Me
        formC.Text = "C"
        formC.Show()
        _MdiChildren.Add(formC)

    End Sub


    Private Sub Form1_MdiChildActivate(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.MdiChildActivate
        '自前でZorderの変更
        Try
            _MdiZorder.Remove(sender)
        Catch ex As Exception
        End Try
        _MdiZorder.Insert(0, sender)
    End Sub

    Dim _forceClose As Boolean = False
    Private Sub MdiChild_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles formA.Closing, formB.Closing, formC.Closing
        If _forceClose Then Exit Sub

        Dim form As Form = sender
        form.SendToBack()
        Try
            _MdiZorder.Remove(form)
        Catch ex As Exception
        End Try
        _MdiZorder.Add(form)
        _MdiZorder(0).focus()
        e.Cancel = True
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        _forceClose = True
        For Each child As Form In _MdiChildren
            child.Close()
        Next
        e.Cancel = False
    End Sub


1

スキルアップ/キャリアアップ(JOB@IT)