Click here to Skip to main content
14,362,565 members
Rate this:
Please Sign up or sign in to vote.
See more: , +
In my code behind i have a method OnFocusRequested which is called using Interface
from my ViewModel when async search button command execute.

the problem is that it is working fine when i just debug through each step from code. this code is not work in real time.
I believe this is due to async call of that button command from view model
but dont know tp figure out this and fix this.
any suggestion ?

What I have tried:

private void OnFocusRequested(object sender, FocusRequestedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "StatementDate":
                    Application.Current.Dispatcher.BeginInvoke(
                        new Action(() =>
                        {
                            dateStatement.Focus();
                            Keyboard.Focus(dateStatement);
                        }),
                        DispatcherPriority.ContextIdle,
                        null
                    );
                    break;
            }
        }

this is my code behind file method in which i am setting the focus i have tried with or without dispatcher but result is same.
Posted
Updated 20-Aug-18 18:58pm
v2
Comments
Nathan Minier 20-Aug-18 8:55am
   
Control focus is a strictly View function, so I would do it in the view code behind hooked to a completion event on the ViewModel.
Graeme_Grant 21-Aug-18 1:21am
   
too messy ... below is a simple & clean solution ;)

1 solution

Rate this:
Please Sign up or sign in to vote.

Solution 1

You can do it in code behind however can get messy if you have to do it for multiple controls. The answer is, where you need to use the code in more than one time, to use a behavior rather than code behind to encapsulate the code to avoid the same code in multiple places, reduces errors, and simplified maintainability. Then in XAML, bind the control to ViewModel properties. That is what this solution will do...

First, we need to add a core DataBinding mechanism for notifying the view and a Command to communicate from the View to the ViewModel:
public abstract class ObservableBase : INotifyPropertyChanged
{
    public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
    {
        if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue)) && field.Equals(newValue)) return;
        field = newValue;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public abstract class ViewModelBase : ObservableBase
{
    public bool IsInDesignMode
        => (bool) DesignerProperties.IsInDesignModeProperty
            .GetMetadata(typeof(DependencyObject))
            .DefaultValue;
}

public class RelayCommand<T> : ICommand
{
    #region Fields

    private readonly Action<T> execute;
    private readonly Predicate<T> canExecute;

    #endregion

    #region Constructors

    public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
    {
        this.execute = execute ?? throw new ArgumentNullException("execute");
        this.canExecute = canExecute;
    }

    #endregion

    #region ICommand Members

    public bool CanExecute(object parameter)
        => canExecute == null || canExecute((T)parameter);

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public void Execute(object parameter)
        => execute(parameter == null
            ? default(T)
            : (T)Convert.ChangeType(parameter, typeof(T)));

    #endregion
}

Now for the behavior... It attaches to the control that we need to set focus to and binds to a property on the ViewModel to trigger the focus change...
public static class FocusHelper
{
    public static bool GetIsFocused(DependencyObject ctrl)
        => (bool)ctrl.GetValue(IsFocusedProperty);

    public static void SetIsFocused(DependencyObject ctrl, bool value)
        => ctrl.SetValue(IsFocusedProperty, value);

    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached(
            "IsFocused", typeof(bool), typeof(FocusHelper),
            new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));

    private static void OnIsFocusedPropertyChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var ctrl = (UIElement)d;
        if ((bool)e.NewValue)
        {
            ctrl.Focus(); // Don't care about false values.
        }
    }
}

The way that it will work is when a button is pressed, a property is set to notify the behavior to set the focus to the attached control.

So here is the ViewModel:
public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        if (IsInDesignMode)
        {
            // design time only...
        }
        else
        {
            // runtime only...
        }
    }

    #region Relay Command

    public RelayCommand<string> SelectCommand => new RelayCommand<string>(SetSelection);

    private void SetSelection(string value)
    {
        SelectThis = value == "this";
        SelectThat = value == "that";
    }

    #endregion

    #region Properties

    private bool selectThis;
    public bool SelectThis
    {
        get => selectThis;
        set => Set(ref selectThis, value);
    }

    private bool selectThat;
    public bool SelectThat
    {
        get => selectThat;
        set => Set(ref selectThat, value);
    }

    private string thisText = "ThisText";
    public string ThisText
    {
        get => thisText;
        set => Set(ref thisText, value);
    }

    private string thatText = "ThatText";
    public string ThatText
    {
        get => thatText;
        set => Set(ref thatText, value);
    }

    #endregion
}

Lastly, we can now wire up the View:
<Window x:Class="WpfSetFocus.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:l="clr-namespace:WpfSetFocus"

        mc:Ignorable="d"

        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <l:MainViewModel/>
    </Window.DataContext>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.Resources>
            <Style TargetType="Button">
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Padding" Value="10 5"/>
            </Style>
            <Style TargetType="TextBlock">
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Grid.Row" Value="1"/>
            </Style>
            <Style TargetType="TextBox">
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Width" Value="200"/>
                <Setter Property="Grid.Row" Value="2"/>
            </Style>
        </Grid.Resources>

        <Button Content="Select This"

                Command="{Binding SelectCommand}"

                CommandParameter="this"/>
        <TextBlock Text="{Binding SelectThis, StringFormat=Selected: {0}}"/>
        <TextBox l:FocusHelper.IsFocused="{Binding SelectThis}"

                 Text="{Binding ThisText}"/>

        <Button Content="Select That"

                Grid.Column="1"

                Command="{Binding SelectCommand}"

                CommandParameter="that"/>
        <TextBlock Text="{Binding SelectThat, StringFormat=Selected: {0}}"

                   Grid.Column="1"/>
        <TextBox Grid.Column="1"

                 l:FocusHelper.IsFocused="{Binding SelectThat}"

                 Text="{Binding ThatText}"/>

        <TextBlock Text="{Binding IsInDesignMode, StringFormat=Is in Design Mode: {0}}"

                   Grid.ColumnSpan="2"/>
    </Grid>
</Window>

Enjoy! :)
   
v2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100