- PR -

モードレスダイアログアクティブ時のショートカットキー

投稿者投稿内容
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2007-06-15 16:24
引用:

KIさんの書き込み (2007-06-15 15:11) より:

ダイアログにテキストボックスが配置されているのですが、入力されているテキストの右端よりも右の部分をクリックしたとき、通常ですとテキストボックス内のカーソルは
テキストの一番右に移動するのですが、全く移動しません。


こういう動きをするとは... 想定の範囲外でした。orz

引用:

あとは表示の問題ですが、ウィンドウのタイトルバーが非アクティブウィンドウの色になるのも気になります。


やはりキーの伝播を考えた方が良いでしょうか。私ももう少し考えたいと思います。orz

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-06-15 16:52
お付き合い頂きありがとうございます。

少し別の現象になりますが、モードレスダイアログがアクティブになっていると、
メインフォームのメニューがワンクリックで展開されません。
つい最近こんな話題がありましたよね。
そういうこともありますので、アクティブにしない方向で処理できるのであれば
そちら方がいいとは思っています。

ヒントは頂けましたので、私も自分なりに考えてみたいと思います。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-06-15 17:00
様々な方法がありますが、
KIさんの最初のやり方のように、キーをなんとかするのであれば
ProcessCmdKeyを用いるのがよいでしょう。

コード:

Public Class CmdKeyTrackForm
Inherits Form

Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
If TypeOf Me.Owner Is CmdKeyTrackForm AndAlso DirectCast(Me.Owner, CmdKeyTrackForm).ProcessCmdKey(msg, keyData) Then
Return True
End If
Return MyBase.ProcessCmdKey(msg, keyData)
End Function
End Class



ちなみに、
MainMenuコントロールは
Form.ProcessCmdKey->MainMenu.ProcessCmdKeyの順に呼ばれ、
そこでショートカット判定を行っています。

ToolStripコントロールの場合は
Control.ProcessCmdKey->ToolStripManager.ProcessCmdKeyの順に呼ばれます。
うまく望みのMenuのProcessCmdKeyを呼べばショートカットを判定してくれます。

ただ、WndProcやProcessCmdKeyなどは、
自分で作ったり、呼んだりするのはお勧めしません。

いろいろ不具合が起きる可能性があり、
その場合原因を調べたり対応策をしらべるのがとても大変です。
Microsoftのお勧めどおりに作るのが一番です。

今回も、ダイアログ側からメインフォームのショートカットが呼ばれないのは
それなりにわけがあります。
ビジネスならよくよく動作確認を行うほうがいいでしょう。

ちなみに、じゃんぬさんのFormを追加してしまう方法ですが、
MDIを使うと問題を解決できます。
.Net2.0では1.1と違いMDIはある程度まともに動きます。


追記:ちょっと不親切なコードだったので修正
更に追記:修正したら間違ってたので修正。

[ メッセージ編集済み 編集者: れい 編集日時 2007-06-15 17:07 ]

[ メッセージ編集済み 編集者: れい 編集日時 2007-06-15 17:54 ]
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-06-15 18:01
お返事ありがとうございます。

引用:

れいさんの書き込み (2007-06-15 17:00) より:

ちなみに、じゃんぬさんのFormを追加してしまう方法ですが、
MDIを使うと問題を解決できます。
.Net2.0では1.1と違いMDIはある程度まともに動きます。



MDIを使うということは、Formを追加するのではなく、
MdiParent にメインフォームを設定して Show するという意味ですよね?

私も前の投稿に書きましたが、MDIの子ウィンドウは理想的な動きをしてくれます。
ですから、これと同じようなことを、MDIの子でないフォームでできないかと思ったのです。

説明していませんでしたが、既にMDIで作成しています。
ドキュメントを開くと、MDIの子ウィンドウを開いてそれを表示します。
今回問題になっているツールボックスのようなウィンドウは、
開いている全てのドキュメントウィンドウから使用されるものです。
このツールボックスのようなウィンドウはMDI子としてではなく、
通常のモードレスダイアログとして開いているという状態です。

そこで試しに、モードレスダイアログとして表示していたこのウィンドウも、
MDIの子に含めてみたところ、ショートカットキーも機能しましたし
ダイアログ上のコントロールの動作も問題ありませんでした。
これで機能的には要件を満たすことはできそうです。

ですが、個人的にはどうも腑に落ちない部分があります。
MDI=Multiple Document Interface という意味からすると、
MDIの子ウィンドウは、ドキュメントと関連付けられたものであるべきで、
ツールボックスのようなものは、ここに含めるべきでないと思うからです。

何か、もっとスマートな方法があればと思うのですが…
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-06-15 19:18
引用:

れいさんの書き込み (2007-06-15 17:00) より:
様々な方法がありますが、
KIさんの最初のやり方のように、キーをなんとかするのであれば
ProcessCmdKeyを用いるのがよいでしょう。

コード:
Public Class CmdKeyTrackForm
    Inherits Form

    Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
        If TypeOf Me.Owner Is CmdKeyTrackForm AndAlso DirectCast(Me.Owner, CmdKeyTrackForm).ProcessCmdKey(msg, keyData) Then
            Return True
        End If
        Return MyBase.ProcessCmdKey(msg, keyData)
    End Function
End Class




すみません。よく読んでいませんでした。
このコードは、Ctrl + S とか個別のキーの判定が不要で、
システムキー入力を先に親フォームに処理させているのですね。
今はすぐ試すことができないのですが、今度試させて頂きます。

「Microsoftのお勧めどおりに作るのが一番」だとは確かに思いますけど、
その Microsoft の製品でも、ツールボックスみたいなものを持ったものがあり、
それがアクティブになっていても、ショートカットキーは有効なのですよね。
例えば Visual Studio のさまざまなウィンドウも、
ドッキングを解除してフロートにした状態であれば、
私の作ろうとしているものとそう変わらないと思うのですが、
ショートカットキーは有効です。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-06-15 19:52
引用:

KIさんの書き込み (2007-06-15 18:01) より:
そこで試しに、モードレスダイアログとして表示していたこのウィンドウも、
MDIの子に含めてみたところ、ショートカットキーも機能しましたし
ダイアログ上のコントロールの動作も問題ありませんでした。
これで機能的には要件を満たすことはできそうです。



MDIは親があります
Control.ProcessCmdKeyで親のProcessCmdKeyを呼んでいるようですので、
ショートカットは動きます。

モードレスダイアログは親がないので、
メインフォームのProcessCmdKeyが呼ばれないので
ショートカットは動きません。

引用:

ですが、個人的にはどうも腑に落ちない部分があります。
MDI=Multiple Document Interface という意味からすると、
MDIの子ウィンドウは、ドキュメントと関連付けられたものであるべきで、
ツールボックスのようなものは、ここに含めるべきでないと思うからです。



どうデザインするかは人それぞれですから、
動けばどんなのもいいんでしょう。
ただ、他人にわかりやすいデザインやプログラムの楽なデザインはあります。

MSの標準的なデザインは、
過去の遺産をひきずりつつ如何にわかりやすく、コーディングしやすくするか、
それなりに考えられています。
探すのがめんどくさいので省略しますが、
どこかにMSのデザインガイドがあると思います。

モードレスダイアログがアクティブなときに
親のショートカットが呼ばれないのは
ショートカットの混乱を防ぐためでしょう。

モードレスダイアログにはなにが表示されているかわかりません。
メインフォームのドキュメントの、サブ項目を表示していたとすると、
Ctrl+Sはメインフォームのドキュメント全体を保存するのか、
サブ項目のみを保存すればいいのか、わからなくなります。

それをプログラマが明示的に支持できるよう、
ProcessCmdKeyはOverridableになっていて、
デフォルトではOwnerのProcessCmdKeyを呼ばないようになっているんだと
解釈しています。

Win32のAcceleratorを使わずにProcessCmdKeyで
KeyCodeをパースしているのもそのせいだと思います。

参考までに、昔まとめた私のデザインガイドです。


そもそもMDIを使わない方法を考える。


すべてのMDIに共通のツールでしたら、
普通はメインフォームのメニューやツールバーに入れる。


入りきらない場合、特殊なレイアウトにしたい場合で、
そのときにやる気があったら
クライアント領域でない、特殊な場所にコントロールを配置する。


メインフォームのツールバーに入りきらない場合、
オプショナルなものなら
アクティブにならないモードレスダイアログを使います。


テキストボックスなどが配置されていて、
アクティブにならないと困る場合は
Ownerを設定したモードレスダイアログを使います。

4、5の場合、
OwnerのProcessCmdKeyを呼ぶようにコーディングします。
ProcessCmdKeyはProtectedなので、普段はFriendなラッパーを使ってます。
モードレスダイアログのタイトルバーは小さいものを使います。

アクティブにしたくないときや小さいタイトルバーを使いたいときは以下のコードを使います。
コード:

Private Const WS_EX_NOACTIVATE As Integer = &H8000000
Private Const WS_EX_TOOLWINDOW As Integer = &H80
Protected Overrides ReadOnly Property CreateParams() As CreateParams
Get
Dim p As CreateParams = MyBase.CreateParams
If Not MyBase.DesignMode Then
p.ExStyle = p.ExStyle Or WS_EX_NOACTIVATE Or WS_EX_TOOLWINDOW
End If
Return p
End Get
End Property





[ メッセージ編集済み 編集者: れい 編集日時 2007-06-15 20:24 ]
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-06-18 11:42
しばらく環境が手元になかったので、返事が遅くなりすみません。
丁寧に説明して頂きありがとうございます。

教えて頂いた ProcessCmdKey をオーバーライドする方法で、実現することができました。

引用:

モードレスダイアログがアクティブなときに
親のショートカットが呼ばれないのは
ショートカットの混乱を防ぐためでしょう。


言われてみれば確かにその通りだと思いました。
ですが、ProcessCmdKey は protected ですので、
これだけの機能を実現するために、ラッパーメソッドを作ったり、
いろいろとイレギュラーなコーディングをする必要があります。
こういう仕様は割と一般的と思われるだけに、
もう少し簡単な方法が用意されていてもいいのに…とは思いました。

引用:

参考までに、昔まとめた私のデザインガイドです。


大変参考になりました。
今回の私のケースは、やっぱり5番っぽいです。
ダイアログのレイアウトや、中のユーザーコントロールに癖がありますので、
ツールバーで実現するのは難しいです。

引用:

アクティブにしたくないときや小さいタイトルバーを使いたいときは以下のコードを使います。


WS_EX_TOOLWINDOW の方は、Form.FormBorderStyle に FixedToolWindow または
SizableToolWindow を指定すれば ExStyle を使わずにできるようです。

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