Using MVVM, you don't want to access controls from the viewModels because it would go against the point of MVVM trying to decouple viewmodels from the view.
If you were to use the access the controls in your viewmodel, then you wouldn't be able to change the view without changing your viewmodel.
When you want to transmit information from your view to your viewModel you can use a Binding.
A possibility here would be to have the information of the view with the focus in your viewModel and to have the view react to the change:
In your viewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
/// <summary>
/// The list of views (the enum doesn't have to be in the viewModel, it can be anywhere)
/// </summary>
public enum Views
{
View1,
View2,
View3
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyChange(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
private Views focusedView;
/// <summary>
/// View with the focus
/// </summary>
public Views FocusedView
{
get
{
return this.focusedView;
}
set
{
this.focusedView = value;
NotifyChange(new PropertyChangedEventArgs("FocusedView"));
}
}
/// <summary>
/// Constructor
/// </summary>
public MainWindowViewModel()
{
this.FocusedView = Views.View1;
}
}
MainWindow.xaml:
<Window.Resources>
<local:MultiValueEqualityConverter x:Key="MultiValueEqualityConverter" />
<Style x:Key="focusedButtonStyle" TargetType="{x:Type Button}">
<Setter Property="BorderBrush" Value="Gray"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<!--It is not possible to make a datatrigger with a Binding in the value Property
so the MultiBinding is a neat trick to avoid having to adapt the style for each Button-->
<MultiBinding Converter="{StaticResource MultiValueEqualityConverter}">
<Binding RelativeSource="{RelativeSource Self}"
Path="Tag" Mode="OneWay"/>
<Binding RelativeSource="{RelativeSource Self}"
Path="DataContext.FocusedView" Mode="OneWay"
UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" Grid.Column="0">
<Button Margin="5" Content="View1" GotFocus="Button_GotFocus"
Tag="{x:Static local:MainWindowViewModel+Views.View1}"
Style="{StaticResource focusedButtonStyle}">
</Button>
<Button Margin="5" Content="View2" GotFocus="Button_GotFocus"
Tag="{x:Static local:MainWindowViewModel+Views.View2}"
Style="{StaticResource focusedButtonStyle}" />
<Button Margin="5" Content="View3" GotFocus="Button_GotFocus"
Tag="{x:Static local:MainWindowViewModel+Views.View3}"
Style="{StaticResource focusedButtonStyle}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="1" MaxHeight="30" VerticalAlignment="Top" >
<Button Margin="5" Content="View1" GotFocus="Button_GotFocus"
Tag="{x:Static local:MainWindowViewModel+Views.View1}"
Style="{StaticResource focusedButtonStyle}" />
<Button Margin="5" Content="View2" GotFocus="Button_GotFocus"
Tag="{x:Static local:MainWindowViewModel+Views.View2}" Style="{StaticResource focusedButtonStyle}"/>
<Button Margin="5" Content="View3" GotFocus="Button_GotFocus"
Tag="{x:Static local:MainWindowViewModel+Views.View3}" Style="{StaticResource focusedButtonStyle}" />
</StackPanel>
</Grid>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
private void Button_GotFocus(object sender, RoutedEventArgs e)
{
if( sender is Button button && this.DataContext is MainWindowViewModel vm)
{
//The information is stored in the tag in order to avoid aving to do as switch or if statement
vm.FocusedView = (MainWindowViewModel.Views)button.Tag;
}
}
}
The MultiBinding (source: https://stackoverflow.com/a/51442634/13448212 )
public class MultiValueEqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values?.All(o => o?.Equals(values[0]) == true) == true || values?.All(o => o == null) == true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Résult here:
You will notice that I don't actually change the "IsFocused" property of the buttons, You actually can set it using Property="FocusManager.FocusedElement".
But I believe you would have to use the name of the element to set the focus so you would have to adapt the Style for every button to refer to the other button by name.
Let me know if this is ok for you, this is my first post so I might have forgotten some things.