0

I'm new to C# programming and seeking some insights into my problem.

I have a standard View which holds a navigation menu and by clicking the nav buttons it will display another view within the standard view. My problem is that I need to "synchronize" the nav buttons with some buttons in the view within so that both buttons are focused when one of them is. see the pictures below.

picture 1

picture 2

I'm using MVVM with caliburn.micro but I cant figure out how to access the controls from the view in the viewmodels, like you can from code behind.. I'm thinking I need to set the focused property up with a boolean that both buttons is linked up with but I don't know how.

halfer
  • 19,824
  • 17
  • 99
  • 186

1 Answers1

1

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.

Ostas
  • 839
  • 7
  • 11
  • Great response btw - When we (.NET Team) created MVVM back in the day a lot of devs wanted to bleed their Control logic back into the ViewModel (given Code-behind in .NET was a constant thing). Its important that the ViewModels not know a single thing about the Views... as the Views can be swapped out if the pattern is used correctly without really affecting the ViewModel. – Scott May 01 '20 at 20:46
  • Thank you for your nice respons! You have really done a thorough answer! Since Ive posted this i have taking a different approach. I realized that i need the "buttons within" to be a usercontrol that I can use in multiple views (its kinda a clickable data info box). So I need to apply the border styling to the usercontrol depending on which view its on. I dont hope that u feel like u wasted your time :) – Nikolaj Franch Ølholm Larsen May 04 '20 at 08:49