11

わかりました。IsEditingプロパティを持つコントロールがあります。これは、引数のために、通常はテキストブロックであるデフォルトのテンプレートを持っていますが、IsEditingがtrueの場合、インプレース編集のためにテキストボックスにスワップします。これで、コントロールがフォーカスを失ったときに、まだ編集中の場合は、編集モードを終了して、TextBlockテンプレートにスワップバックすることになっています。かなり簡単ですよね?

Windowsエクスプローラーまたはデスクトップでファイルの名前を変更する動作を考えてみてください(これは私が知っているのと同じことです...)それが私たちが望む動作です。

問題は、別のウィンドウ(またはFocusManagerである要素)に切り替えると、コントロールに論理フォーカスがあるためLostFocusが起動せず、機能しないため、LostFocusイベントを使用できないことです。

代わりにLostKeyboardFocusを使用すると、「その他のFocusManager」の問題は解決しますが、新しい問題が発生します。編集中にテキストボックスを右クリックすると、コンテキストメニューが表示されます。これは、コンテキストメニューにキーボードが含まれているためです。フォーカスすると、コントロールはキーボードフォーカスを失い、編集モードを終了してコンテキストメニューを閉じ、ユーザーを混乱させます。

ここで、メニューが開く直前にLostKeyboardFocusを無視するようにフラグを設定し、LostKeyboardFocusイベントでそのフラグを使用して、編集モードを終了するかどうかを決定しようとしましたが、メニューが開いていて、アプリでは、コントロール自体にキーボードフォーカスがなくなったため(メニューにフォーカスがあった)、コントロールが別のLostKeyboardFocusイベントを取得することはなく、編集モードのままになります。(メニューが閉じたときに、フォーカスがあるものを確認するためのチェックを追加し、コントロールでない場合は手動でEditModeからキックアウトする必要がある場合があります。これは有望なようです。)

だから...誰かが私がこの振る舞いをうまくコーディングする方法を知っていますか?

マーク

4

6 に答える 6

10

わかりました...これはProgrammer-funのように「楽しい」ものでした。理解するためのキースターの本当の痛み、しかし私がした私の顔に素敵な大きな笑顔で。(私が自分でそれをとても激しく叩いていることを考慮して、私の肩のためにいくつかのIcyHotを手に入れる時が来ました!:P)

とにかく、それは多段階のことですが、すべてを理解すれば驚くほど簡単です。短いバージョンでは、どちらか一方ではなく、との両方 を使用する必要があります。LostFocus LostKeyboardFocus

LostFocusは簡単だ。そのイベントを受信するたびに、IsEditingfalseに設定してください。完了しました。

コンテキストメニューと失われたキーボードフォーカス

LostKeyboardFocusコントロールのコンテキストメニューがコントロール自体で起動する可能性があるため、少し注意が必要です(つまり、コントロールのコンテキストメニューが開いても、コントロールにはフォーカスがありますが、キーボードフォーカスが失われるため、LostKeyboardFocus起動します)。

この動作を処理するにContextMenuOpeningは、メニューが開いていることを示すクラスレベルのフラグをオーバーライド(またはイベントを処理)して設定します。(私は使用しますbool _ContextMenuIsOpening。)次に、LostKeyboardFocusオーバーライド(またはイベント)で、そのフラグをチェックし、設定されている場合は、単にクリアして他に何もしません。ただし、設定されていない場合は、コンテキストメニューを開く以外の何かが原因で、コントロールがキーボードフォーカスを失っていることを意味します。その場合、IsEditingfalseに設定する必要があります。

すでに-コンテキストメニューを開く

コントロールのコンテキストメニューが開いていて、上記のようにコントロールがすでにキーボードフォーカスを失っている場合、アプリケーションの他の場所をクリックすると、新しいコントロールがフォーカスを取得する前に、コントロールが最初にキーボードフォーカスを取得するという奇妙な動作があります。 、しかしほんの一瞬だけ、それは即座にそれを新しいコントロールに譲ります。

これは実際にはここで有利に機能します。これは、別のLostKeyboardFocusイベントも取得することを意味しますが、今回は_ContextMenuOpeningフラグがfalseに設定され、上記のように、LostKeyboardFocusハンドラーがIsEditingfalseに設定されます。これはまさに私たちが望むものです。私は偶然が大好きです!

これで、最初にコンテキストメニューを所有するコントロールにフォーカスを戻すことなく、クリックしたコントロールにフォーカスが移動しました。次に、ContextMenuClosingイベントをフックして、次にフォーカスを取得するコントロールを確認するなどの操作を行う必要があります。すぐにフォーカスされるコントロールがコンテキストメニューを生成するコントロールではなかった場合にのみfalseに設定IsEditingしたので、基本的にそこで弾丸をかわしました。

警告:デフォルトのコンテキストメニュー

また、テキストボックスのようなものを使用していて、独自のコンテキストメニューを明示的に設定していない場合は、ContextMenuOpeningイベントが発生しないという警告もあります。これは私を驚かせました。ただし、デフォルトのコンテキストメニューと同じ標準コマンド(切り取り、コピー、貼り付けなど)を使用して新しいコンテキストメニューを作成し、それをテキストボックスに割り当てるだけで、簡単に修正できます。見た目はまったく同じですが、フラグを設定する必要のあるイベントが表示されます。

ただし、サードパーティで再利用可能なコントロールを作成していて、そのコントロールのユーザーが独自のコンテキストメニューを使用したいという問題がある場合でも、誤って優先順位を高く設定して、それらのコントロールを上書きする可能性があります。 !!

それを回避する方法は、テキストボックスが実際にはコントロールのIsEditingテンプレート内のアイテムであるため、外部コントロールに新しいDPを追加するだけでIsEditingContextMenu、内部スタイルを介してテキストボックスにバインドし、そのスタイルTextBoxにを追加しました。外部コントロールDataTriggerのの値をチェックし、IsEditingContextMenuそれがnullの場合は、上記で作成したデフォルトのメニューを設定します。これはリソースに保存されます。

テキストボックスの内部スタイルは次のとおりです(「Root」という名前の要素は、ユーザーが実際にXAMLに挿入する外部コントロールを表します)...

<Style x:Key="InlineTextbox" TargetType="TextBox">

    <Setter Property="OverridesDefaultStyle" Value="True"/>
    <Setter Property="FocusVisualStyle"      Value="{x:Null}" />
    <Setter Property="ContextMenu"           Value="{Binding IsEditingContextMenu, ElementName=Root}" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBoxBase}">

                <Border Background="White" BorderBrush="LightGray" BorderThickness="1" CornerRadius="1">
                    <ScrollViewer x:Name="PART_ContentHost" />
                </Border>

            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <Style.Triggers>
        <DataTrigger Binding="{Binding IsEditingContextMenu, RelativeSource={RelativeSource AncestorType=local:EditableTextBlock}}" Value="{x:Null}">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu>
                        <MenuItem Command="ApplicationCommands.Cut" />
                        <MenuItem Command="ApplicationCommands.Copy" />
                        <MenuItem Command="ApplicationCommands.Paste" />
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </DataTrigger>
    </Style.Triggers>

</Style>

テキストボックスに直接ではなく、スタイルで初期コンテキストメニューバインディングを設定する必要があることに注意してください。そうしないと、スタイルのDataTriggerが直接設定された値に置き換えられ、トリガーが使用できなくなり、ユーザーが使用する場合はすぐに正方形に戻ります。コンテキストメニューの場合は「null」。(メニューを抑制したい場合は、とにかく「null」を使用しません。nullは「デフォルトを使用する」を意味するため、空のメニューに設定します)

これで、ユーザーはfalseのContextMenu場合に通常のプロパティを使用できます... IsEditingがtrueの場合に使用できます。指定しなかった場合は、定義した内部デフォルトがテキストボックスに使用されます。テキストボックスのコンテキストメニューが実際にnullになることはないため、常に起動し、この動作をサポートするロジックが機能します。IsEditingIsEditingContextMenuIsEditingContextMenuContextMenuOpening

私が言ったように...これをすべて理解することができる缶の本当の痛み、しかし私がここで本当にクールな達成感を持っていないなら気にしないでください。

これが同じ問題を抱えている他の人たちに役立つことを願っています。ここに返信するか、質問をPMしてください。

マーク

于 2011-05-02T00:54:08.740 に答える
3

残念ながら、あなたは複雑な問題に対する簡単な解決策を探しています。簡単に言えば、問題は、最小限の対話を必要とし、それらから「切り替える」ときに「正しいことを行う」スマートな自動コミットのユーザーインターフェイスコントロールを使用することです。

それが複雑である理由は、正しいことはアプリケーションのコンテキストに依存するためです。WPFが採用するアプローチは、論理フォーカスとキーボードフォーカスの概念を提供し、状況に応じて適切な処理を行う方法を決定できるようにすることです。

コンテキストメニューが開いている場合はどうなりますか?アプリケーションメニューを開いたらどうなりますか?フォーカスが別のアプリケーションに切り替えられた場合はどうなりますか?ローカルコントロールに属するポップアップが開かれた場合はどうなりますか?ユーザーがEnterキーを押してダイアログを閉じるとどうなりますか?これらの状況はすべて処理できますが、コミットボタンがある場合、またはユーザーがEnterキーを押してコミットする必要がある場合は、すべてなくなります。

したがって、3つの選択肢があります。

  • コントロールに論理的なフォーカスがある場合は、コントロールを編集状態のままにします
  • 明示的なコミットまたは適用メカニズムを追加する
  • 自動コミットをサポートしようとしたときに発生するすべての厄介なケースを処理します
于 2011-05-01T22:17:25.093 に答える
1

次のことを行うのは簡単ではないでしょうか。

    void txtBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        TextBox txtBox = (sender as TextBox);

        if (e.NewFocus is ContextMenu && (e.NewFocus as ContextMenu).PlacementTarget == txtBox)
        {
            return;
        }

        // Rest of code for existing edit mode here...
    }
于 2016-05-11T04:53:15.900 に答える
0

コンテキストメニューの問題についてはよくわかりませんが、似たようなことをしようとしていたところ、マウスキャプチャを使用すると、(ほぼ)目的の動作が得られることがわかりました。

ここで答えを参照してください:コントロールは、そのコントロールの外側でマウスクリックをどのように処理できますか?

于 2013-02-01T11:02:36.720 に答える
0

わかりませんが、これは役立つ可能性があります。編集可能なコンボボックスでも同様の問題が発生しました。私の問題は、呼び出されなかったOnLostFocusオーバーライドメソッドを使用していたことでした。LostFocusイベントにコールバックを添付していたので、問題なく動作しました。

于 2015-02-05T01:03:57.110 に答える
0

私は同様の問題の解決策を探すためにここを通り抜けました:私は開いListBoxたときに焦点を失うものを持っていContextMenuます、そして私はそれが起こらないようにしたいです。

私の簡単な解決策は、とその両方に対して、に設定Focusableすることでした。FalseContextMenuMenuItem

<ContextMenu x:Key="QueryResultsMenu" Focusable="False">
    <ContextMenu.Resources>
        <Style TargetType="MenuItem">
            <Setter Property="Focusable" Value="False"/>
        </Style>
    </ContextMenu.Resources>
    <MenuItem ... />
</ContextMenu>

これが将来の探求者に役立つことを願っています...

于 2015-06-23T18:05:36.733 に答える