Invoke UIElement’s Focus() Method From ViewModel

One of the things WPF developers often notice is inability to SetFocus on a particular control from the ViewModel. This problem can be however easily resolved using an attached property. Let’s examine the code.

We will begin by writing an attached property, for the purpose of example, named as IsFocused.

public class FocusExtension : DependencyObject
{
    public static bool GetIsFocused(DependencyObject dependencyObject) => (bool)dependencyObject.GetValue(IsFocusedProperty);
    public static void SetIsFocused(DependencyObject dependencyObject, bool value) => dependencyObject.SetValue(IsFocusedProperty, value);
    public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(FocusExtension), new PropertyMetadata(false, IsFocusChanged));
    private static void IsFocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
       if ((bool)e.NewValue)
       {
          d as UIElement).Focus();
       }
    }
}

The sole objective of the attached property is to call the Focus() method of the attached UI element when the IsFocused Property Changes. Since the attached property is bindable from ViewModel, we can now set Focus to any control from our View Model. Am using Caliburn.Micro for my MVVM Implementation

View

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Button Grid.Row="0" Grid.Column="0" Content="Set Focus" x:Name="SetFocusOnTextBox1"/>
        <TextBox Grid.Row="0" Grid.Column="1" Text="TextBox 1" ap:FocusExtension.IsFocused="{Binding IsTextBox1Focused}"  />
        <Button Grid.Row="1" Grid.Column="0" Content="Set Focus" x:Name="SetFocusOnTextBox2"/>
        <TextBox Grid.Row="1" Grid.Column="1" Text="TextBox 2" ap:FocusExtension.IsFocused="{Binding IsTextBox2Focused}"  />
        <Button Grid.Row="2" Grid.Column="0" Content="Set Focus" x:Name="SetFocusOnTextBox3"/>
        <TextBox Grid.Row="2" Grid.Column="1" Text="TextBox 3" ap:FocusExtension.IsFocused="{Binding IsTextBox3Focused}"  />
        <Button Grid.Row="3" Grid.Column="0" Content="Set Focus" x:Name="SetFocusOnTextBox4"/>
    </Grid>

ViewModel

public bool IsTextBox1Focused { get; set; }
public bool IsTextBox2Focused { get; set; }
public bool IsTextBox3Focused { get; set; }

public void SetFocusOnTextBox1()
{
   IsTextBox1Focused = true;
   NotifyOfPropertyChange(nameof(IsTextBox1Focused));
}

public void SetFocusOnTextBox2()
{
   IsTextBox2Focused = true;
   NotifyOfPropertyChange(nameof(IsTextBox2Focused));
}

public void SetFocusOnTextBox3()
{
   IsTextBox3Focused = true;
   NotifyOfPropertyChange(nameof(IsTextBox3Focused));
}

SharedSizeGroup: Managing Column Size across Grids

SharedSizeGroup is an underused WPF attribute, but one that can make life lot more easier when design WPF forms with multiple Grids that need to share a column size. Consider the screenshot below. The three pairs of Label and Textbox are in separate grids (with column width set to auto), resulting in an unorganized layout. It would far more better if the column width of first grid resizes itself to width of column in second grid.

Without SharedSizeGroup

You might often have to design more complex scenarios where you would love to retain the column size across Grids. This is where SharedSizeGroup comes into play. Let’s redefine our XAML with the attribute.

<Grid Margin="10,10,10,10">
        <StackPanel Grid.IsSharedSizeScope="True">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="ABC"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="Name"></TextBlock>
                <TextBox Grid.Column="1"/>
            </Grid>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="ABC"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="Full Name"></TextBlock>
                <TextBox Grid.Column="1"/>
            </Grid>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="ABC"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="Last Name"></TextBlock>
                <TextBox Grid.Column="1"/>
            </Grid>
        </StackPanel>
    </Grid>

 
Now the layout looks better organized.

With SharedSizeGroup