Question

There is my WPF window in which I placed an ordinary textbox which I would liked to be focused when Ctrl+F is pressed.

As I would like to keep it MVVM-like as much as possible, I use InputBindings on the window to bind that input event to a Command provided in the ViewModel (is that already breaking MVVM pattern because the whole action is only meant to be part of the view? I guess not, as the Command is an object to bind to).

How can the ViewModel communicate with the view to focus the textbox? I read that this already breaks the MVVM pattern, but sometimes simply is necessary as otherwise impossible. However, setting the focus in the ViewModel itself would be totally breaking the MVVM pattern.

I orginally intended to bind the current focused control in the window to a property of the ViewModel but it is quite difficult to even determine the currently focused element in WPF (that always makes me question if it really is the right way to do so).

Was it helpful?

Solution 5

After a few days of getting a better grip on all of this, considering and evaluating all options, I finally found a way to work it out. I add a command binding in my window markup:

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

The command in my ViewModel (I cut the class down to what matters in this case):

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

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

And finally the command itself:

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();
    }
}

This might not be straigt MVVM - but stijn's answer has a good point:

So, just stop caring too much about breaking whatever pattern you use and implement a solution instead.

Normally I take care of keeping stuff organised by conventions, especially when I am still new to something, but I do not see anything wrong regarding this.

OTHER TIPS

In cases like this there's just no way to not 'break' pure MVVM. Then again, I'd hardly call it breaking anything. I don't think any decently sized MVVM app out there is 'pure'. So, just stop caring too much about breaking whatever pattern you use and implement a solution instead.

There are at least two ways here:

  • simply do everything in code behind in the View: check if the key is pressed, if so, set focus. It won't get any simpler than that and you could argue the VM has nothing to do with something that's really all View related
  • else there is obviously going to have to be some communication between VM and View. And this makes everything more complicated: suppose you use the InputBinding, your command can set a boolean property and then the View can bind to it in turn to set focus. That binding can be done like in Sheridan's answer with an attached property.

Generally, when we want to use any UI event while adhering to the MVVM methodology, we create an Attached Property. As I just answered this very same question yesterday, I would advise you to take a look at the how to set focus to a wpf control using mvvm post here on StackOverflow for a full working code example.

The only difference from that question to yours is that you want to focus the element on a key press... I'm going to assume that you know how to do that part, but if you can't, just let me know and I'll give you an example of that too.

when using mvvm and further when you define a viewmodel with:

a viewmodel should not know/reference the view

then you cant set focus through the viewmodel.

but what i do in mvvm is the following in the viewmodel:

set the focus to the element which is bind to the viewmodel property

for this i create a behavior which simply walk through all control in the visual tree and look for the binding expressions path. and if i find a path expression then simply focus the uielement.

EDIT:

xaml usage

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

viemodel in any method

this.FocusToBindingPath = "MyPropertyIWantToFocus";

behavior

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;
    }
}

(is that already breaking MVVM pattern because the whole action is only meant to be part of the view? I guess not, as the Command is an object to bind to)

The Command system in WPF was actually not designed around data-binding, but the UI -- using RoutedCommands, a single command would have different implementations based on the physical position in the UI structure of the element that called the command.

Commanding Overview

Your flow would be:

  • Ctrl+F is pressed
  • command event is raised and bubbles up
  • the event reaches the window, which has a CommandBinding to the command
  • event handler on the window focuses the text box

If the current element is inside a container that wants to handle the command differently, it will stop there before it reaches the window.

This is probably closer to what you want. It may make sense to involve the view model if there is some concept of an "active property" like in blindmeis's answer, but otherwise I think you would just end up with a redundant / circular flow of information e.g. key pressed -> view informs viewmodel of keypress -> viewmodel responds by informing view of keypress.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top