0

I'm new into WPF, and I'm unfortunately used to work only with WinForms. I'd like to ask for advice with binding.

I have MainView and also MainViewModel

I want to fill menu items for languages like you can see in the picture below

Language menu items

I have obtained dictionary with language name as a key and bool as value in my viewModel:

    internal Dictionary<string,bool> Languages { get; set; }

#endregion
public MainWindowViewModel()
{
    Loc = new Locale(CurrentLanguage);
    Languages = Loc.GetAvailableLanguages();
}

I have handled it by adding them in the code behind of the MainWindowView.xaml file:

public partial class MainWindow : Window
{

    MainWindowViewModel viewModel;
    public MainWindow()
    {
        viewModel = new MainWindowViewModel();
        DataContext = viewModel;
        InitializeComponent();
        PopulateMILanguages();
    }

private void PopulateMILanguages()
{
    foreach (var lng in viewModel.Languages)
    {
        MenuItem mi = new MenuItem();
        mi.IsCheckable = true;
        mi.Header = lng.Key;
        mi.Checked += Mi_Checked;
        MI_Lngs.Items.Add(mi);
        mi.IsChecked = lng.Value;
        if (lng.Key.ToLowerInvariant() == 
            Settings.Default.LastSelectedLanguage.ToLowerInvariant())
        {
            mi.IsChecked = true;
        }
    }
}

private void Mi_Checked(object sender, RoutedEventArgs e)
{
    MenuItem menuItem = sender as MenuItem;
    viewModel.CurrentLanguage = menuItem.Header.ToString()
        .ToLowerInvariant();
    Settings.Default.LastSelectedLanguage=viewModel.CurrentLanguage;
    foreach (MenuItem mi in MI_Lngs.Items)
    {
        if (mi.Header != menuItem.Header)
        {
            mi.IsChecked = false;
        }
    }
}

    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        Settings.Default.Save();
    }
}

I really don't like this solution, but I was not able to handle it to fill collection in the xaml:

<!--    LANGUAGE    -->
                <MenuItem x:Name="MI_Lngs" Header="{Binding Loc[Lng]}"
                          Style="{StaticResource MenuItemToolbar}">
                    <MenuItem.Icon>
                        <Image Source="/Assets/languages.png"/>
                    </MenuItem.Icon>
                    <MenuItem.ToolTip>
                        <ToolTip Content="{Binding Loc[Lng_TT]}"/>
                    </MenuItem.ToolTip>
                </MenuItem>

Could anyone please give me an advice how to handle it to fill menu items from the collection and also be able to catch checked events?

Thanks in advance

Jiri

EldHasp
  • 6,079
  • 2
  • 9
  • 24
JMan
  • 60
  • 7
  • For WPF controls that contain a collection of items, you typically should be able to set that control's ItemsSource property to a collection of items you've listed. Have you tried using the ItemsSource property on the parent Language menu item? From there, you might have to set the style of that parent menu item to get the generated child menu items to look exactly as you intend and also apply the checkmark as needed. The next issue to tackle after that would be handling checked/unchecked events... That's my guess after thinking about it for only a minute. – JaykeBird Oct 05 '22 at 07:19
  • Thank you for your advice. Yes I have used ItemsSource collection and languages were displayed, But ... I was not able to set the style of the items because I don't know how to bind key and value from the dictionary with languages to the checkbox and header of the menu item. Could you please give me a short example how to solve it? – JMan Oct 05 '22 at 07:57

1 Answers1

2

You can use this menu with the below viewmodel to get the desired functionality.

<Menu>
    <MenuItem Header="File"/>
    <MenuItem Header="Settings">
        <MenuItem Header="Application"/>
        <MenuItem Header="Language" ItemsSource="{Binding Languages}">
            <MenuItem.ItemContainerStyle>
                <Style>
                    <Setter Property="MenuItem.IsCheckable" Value="True"/>
                    <Setter Property="MenuItem.Header" Value="{Binding Header, Mode=OneWay}"/>
                    <Setter Property="MenuItem.IsChecked" Value="{Binding IsSelected, Mode=OneWay}"/>
                    <Setter Property="MenuItem.Command" Value="{Binding DataContext.SelectLanguageCommand, RelativeSource={RelativeSource AncestorType=MenuItem}}"/>
                    <Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
                </Style>
            </MenuItem.ItemContainerStyle>
        </MenuItem>
    </MenuItem>
</Menu>

I used CommunityToolkit.Mvvm library to give you some idea here. You can use another library.

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;

namespace WpfApp1.ViewModel
{
    public class MainWindowViewModel : ObservableObject
    {
        private ObservableCollection<Language> _languages = new();
        public ObservableCollection<Language> Languages
        {
            get => _languages;
            set => SetProperty(ref _languages, value);
        }

        public IRelayCommand SelectLanguageCommand { get; }

        public MainWindowViewModel()
        {
            Languages.Add(new Language { Header = "CZ", IsSelected = false });
            Languages.Add(new Language { Header = "EN", IsSelected = true });

            SelectLanguageCommand = new RelayCommand<Language>(l =>
            {
                if (l != null)
                {
                    foreach (Language item in Languages)
                    {
                        item.IsSelected = false;
                    }

                    l.IsSelected = true;
                }
            });
        }
    }

    public class Language : ObservableObject
    {
        private string? _header;
        public string? Header
        {
            get => _header;
            set => SetProperty(ref _header, value);
        }

        private bool _isSelected;
        public bool IsSelected
        {
            get => _isSelected;
            set => SetProperty(ref _isSelected, value);
        }
    }
}
Egemen Çiftci
  • 699
  • 5
  • 13
  • Thank you very much. This explanation was awesome and helped me a lot! Especially the part with xaml. Thanks also for pointing to the communityToolkit.mvvm. Thanks to this tool, I was even able to simplify the viewModel code and get rid of the unnecessary boilerplate code. I have used [ObservableProperty] and [RelayCommand] attributes on top of the properties and commands. That toolkit is really great. I owe you one. – JMan Oct 06 '22 at 06:28