2

Ctrl + Fを押したときにフォーカスしたい通常のテキストボックスを配置したWPFウィンドウがあります。

可能な限りMVVMのように保ちたいのでInputBindings、ウィンドウで使用して、その入力イベントをViewModelで提供されるコマンドにバインドします(アクション全体が一部であることを意図しているため、すでにMVVMパターンを壊していますCommand はバインド先のオブジェクトであるため、そうではないと思います)。

ViewModel はどのようにビューと通信してテキストボックスにフォーカスを当てることができますか? これはすでにMVVMパターンを破っていることを読みましたが、それ以外の場合は不可能であるため、単に必要な場合があります。ただし、ViewModel 自体にフォーカスを設定すると、MVVM パターンが完全に壊れてしまいます。

私は当初、ウィンドウ内の現在フォーカスされているコントロールを ViewModel のプロパティにバインドするつもりでしたが、WPF で現在フォーカスされている要素を特定することさえ非常に困難です (それが本当に正しい方法であるかどうかを常に疑問に思っています)。

4

5 に答える 5

3

このような場合、純粋な MVVM を「壊さない」方法はありません。繰り返しになりますが、私はそれを何かを壊すとはほとんど言いません。そこにあるまともなサイズのMVVMアプリは「純粋」ではないと思いますしたがって、使用するパターンを壊すことを気にしすぎるのをやめて、代わりにソリューションを実装してください。

ここには少なくとも 2 つの方法があります。

  • ビューのコードビハインドですべてを実行するだけです。キーが押されているかどうかを確認し、押されている場合はフォーカスを設定します。これほど単純なことはありません。VM は、実際にはすべてビューに関連するものとは何の関係もないと主張することもできます。
  • それ以外の場合は、VM とビューの間で何らかの通信が必要になることは明らかです。これにより、すべてがより複雑になります。InputBinding を使用すると、コマンドはブール値のプロパティを設定でき、ビューはそれにバインドしてフォーカスを設定できます。そのバインディングは、添付プロパティを使用して Sheridan の回答のように行うことができます。
于 2013-10-30T10:02:02.683 に答える
1

mvvm を使用する場合、さらに次のようにビューモデルを定義する場合:

ビューモデルはビューを認識/参照してはなりません

ビューモデルを介してフォーカスを設定することはできません。

しかし、私が mvvm で行うことは、ビューモデルで次のとおりです。

viewmodel プロパティにバインドされている要素にフォーカスを設定します

このために、ビジュアル ツリー内のすべてのコントロールを単純にウォークスルーし、バインディング式のパスを探す動作を作成します。パス式が見つかった場合は、UIElement にフォーカスするだけです。

編集:

xaml の使用

<UserControl>
    <i:Interaction.Behaviors>
    <Behaviors:OnLoadedSetFocusToBindingBehavior BindingName="MyFirstPropertyIWantToFocus" SetFocusToBindingPath="{Binding Path=FocusToBindingPath, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</UserControl>

任意の方法での viemodel

this.FocusToBindingPath = "MyPropertyIWantToFocus";

行動

public class SetFocusToBindingBehavior : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty SetFocusToBindingPathProperty =
      DependencyProperty.Register("SetFocusToBindingPath", typeof(string), typeof(SetFocusToBindingBehavior ), new FrameworkPropertyMetadata(SetFocusToBindingPathPropertyChanged));

    public string SetFocusToBindingPath
    {
        get { return (string)GetValue(SetFocusToBindingPathProperty); }
        set { SetValue(SetFocusToBindingPathProperty, value); }
    }

    private static void SetFocusToBindingPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = d as SetFocusToBindingBehavior;
        var bindingpath = (e.NewValue as string) ?? string.Empty;

        if (behavior == null || string.IsNullOrWhiteSpace(bindingpath))
            return;

        behavior.SetFocusTo(behavior.AssociatedObject, bindingpath);
        //wenn alles vorbei ist dann binding path zurücksetzen auf string.empty, 
        //ansonsten springt PropertyChangedCallback nicht mehr an wenn wieder zum gleichen Propertyname der Focus gesetzt werden soll
        behavior.SetFocusToBindingPath = string.Empty;
    }

    private void SetFocusTo(DependencyObject obj, string bindingpath)
    {
        if (string.IsNullOrWhiteSpace(bindingpath)) 
            return;

        var ctrl = CheckForBinding(obj, bindingpath);

        if (ctrl == null || !(ctrl is IInputElement))
            return;

        var iie = (IInputElement) ctrl;

        ctrl.Dispatcher.BeginInvoke((Action)(() =>
            {
                if (!iie.Focus())
                {
                    //zb. bei IsEditable=true Comboboxen funzt .Focus() nicht, daher Keyboard.Focus probieren
                    Keyboard.Focus(iie);

                    if (!iie.IsKeyboardFocusWithin)
                    {
                        Debug.WriteLine("Focus konnte nicht auf Bindingpath: " + bindingpath + " gesetzt werden.");
                        var tNext = new TraversalRequest(FocusNavigationDirection.Next);
                        var uie = iie as UIElement;

                        if (uie != null)
                        {
                            uie.MoveFocus(tNext);
                        } 
                    }
                }
            }), DispatcherPriority.Background);
    }

    public string BindingName { get; set; }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += AssociatedObjectLoaded;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.Loaded -= AssociatedObjectLoaded;
    }

    private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
        SetFocusTo(AssociatedObject, this.BindingName);
    }

    private DependencyObject CheckForBinding(DependencyObject obj, string bindingpath)
    {
        var properties = TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) });

        if (obj is IInputElement && ((IInputElement) obj).Focusable)
        {
            foreach (PropertyDescriptor property in properties)
            {
                var prop = DependencyPropertyDescriptor.FromProperty(property);

                if (prop == null) continue;

                var ex = BindingOperations.GetBindingExpression(obj, prop.DependencyProperty);
                if (ex == null) continue;

                if (ex.ParentBinding.Path.Path == bindingpath)
                    return obj;

            }
        }

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            var result = CheckForBinding(VisualTreeHelper.GetChild(obj, i),bindingpath);
            if (result != null)
                return result;
        }

        return null;
    }
}
于 2013-10-30T10:22:31.197 に答える
1

一般に、MVVM 方法論に準拠しながら任意の UI イベントを使用する場合は、添付プロパティを作成します。昨日まったく同じ質問に答えたばかりなので、mvvm を使用して wpf コントロールにフォーカスを設定する方法をご覧になることをお勧めします。完全に機能するコード例については、StackOverflow の投稿をご覧ください。

その質問とあなたの質問の唯一の違いは、キーを押すことに要素を集中させたいということです...私はあなたがその部分を行う方法を知っていると仮定しますが、できない場合は私に知らせてください.その例も紹介します。

于 2013-10-30T09:58:32.840 に答える
0

これらすべてをよりよく把握し、すべてのオプションを検討および評価する数日後、私は最終的にそれを解決する方法を見つけました. ウィンドウ マークアップにコマンド バインディングを追加します。

<Window.InputBindings>
    <KeyBinding Command="{Binding Focus}" CommandParameter="{Binding ElementName=SearchBox}" Gesture="CTRL+F" />
</Window.InputBindings>

ViewModel のコマンド (この場合、クラスを重要なものに切り詰めます):

class Overview : Base
{
    public Command.FocusUIElement Focus
    {
        get;
        private set;
    }

    public Overview( )
    {
        this.Focus = new Command.FocusUIElement();
    }
}

そして最後に、コマンド自体:

class FocusUIElement : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute ( object parameter )
    {
        return true;
    }

    public void Execute ( object parameter )
    {
        System.Windows.UIElement UIElement = ( System.Windows.UIElement ) parameter;
        UIElement.Focus();
    }
}

これは単純なMVVMではないかもしれませんが、stijnの答えには良い点があります:

したがって、使用するパターンを壊すことを気にしすぎるのをやめて、代わりにソリューションを実装してください。

通常、特に私がまだ何かに慣れていないときは、規則に従って物事を整理するように気を配っていますが、これに関しては何も問題はありません。

于 2013-11-11T09:00:29.063 に答える
0

(アクション全体がビューの一部であることを意図しているだけなので、MVVMパターンを壊していますか?コマンドはバインドするオブジェクトであるため、そうではないと思います)

WPF のコマンド システムは、実際にはデータ バインディングを中心に設計されたのではなく、UI を中心に設計されました。RoutedCommands を使用すると、コマンドを呼び出した要素の UI 構造内の物理的な位置に基づいて、単一のコマンドが異なる実装を持つことになります。

指揮の概要

フローは次のようになります。

  • Ctrl+F が押された
  • コマンドイベントが発生し、バブルアップ
  • イベントは、コマンドへの CommandBinding を持つウィンドウに到達します。
  • ウィンドウのイベント ハンドラーがテキスト ボックスをフォーカスします。

現在の要素が、コマンドを別の方法で処理するコンテナー内にある場合、ウィンドウに到達する前にそこで停止します。

これはおそらくあなたが望むものに近いです。ブラインドメイスの回答のように「アクティブなプロパティ」の概念がある場合、ビューモデルを含めることは理にかなっているかもしれませんが、そうでなければ、キーが押された->ビューがビューモデルに通知するなど、冗長/循環的な情報の流れになってしまうと思いますキー押下の -> ビューモデルは、キー押下のビューを通知することで応答します。

于 2013-10-30T20:19:10.833 に答える