I am developing a WPF app for my final university project (thesis). One of the goals is to be able to dynamically change the language of the UI. I suppose that it is not a very good idea to do it the way I am trying as it requires a lot of code, but for the sake of learning, I will go through with it. The idea is to bind text properties in the XAML to properties in the LanguageViewModel and when the user changes the current language from a dropdown menu, all strings in the UI should change to the chosen language. With the code bellow, the OnPropertyChanged method is executed, but the UI elements don't update and after a lot of debugging, I still don't have any clue why.
I'm currently experimenting with a single TextBlock control. I have placed it between comments to make it easier to find.
This is my MainWindow.xaml
<Window x:Class="AdjustrixWPF.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:local="clr-namespace:AdjustrixWPF"
xmlns:userControls="clr-namespace:AdjustrixWPF.View.UserControls"
mc:Ignorable="d"
Title="MainWindow" WindowState="Maximized" Height="650" Width="850" WindowStyle="None"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
Background="{DynamicResource BackgroundBrush}">
<Grid Name="MainGrid"
Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="25*"/>
<RowDefinition Height="75*"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0" Width="{Binding ElementName=MainGrid, Path=ActualWidth}"
HorizontalAlignment="Left" Grid.RowSpan="2" Background="{DynamicResource BackgroundBrush}" Style="{StaticResource TabControlStyle}">
<TabItem Background="{DynamicResource BackgroundBrush}"
Style="{StaticResource TabItemStyle}">
<TabItem.Header>
<userControls:TabHeader DataContext="{Binding languageViewModel}"
Text="File"
IsMouseOver="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=TabItem}}"
IsSelected="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TabItem}}"
Style="{StaticResource TabHeaderStyle}"
Height="20"/>
</TabItem.Header>
<StackPanel Orientation="Horizontal">
<Border BorderBrush="{DynamicResource ForegroundBrush}"
BorderThickness="0 0 1 0"
Margin="5">
<Grid Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=StackPanel}}"
Width="300"
x:Name="TabPanelGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="25*"/>
</Grid.ColumnDefinitions>
<Grid Height="{Binding ElementName=TabPanelGrid, Path=ActualHeight}">
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="45*"/>
<RowDefinition Height="45*"/>
</Grid.RowDefinitions>
<!-- =========================================================== -->
<!-- This is the control I am currently experimenting with-->
<TextBlock x:Name="AppearanceBlock"
Style="{StaticResource TextBlockTabPanel}"
Text="{Binding languageViewModel.Appearance, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 0 3"/>
<!-- =========================================================== -->
<userControls:LabeledComboBox LabelText="Language"
ComboBoxItemsSource="{Binding languageViewModel.Languages}"
ComboBoxSelectedItem="{Binding languageViewModel.CurrentLanguage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1"
x:Name="LanguageBox"/>
<userControls:LabeledComboBox LabelText="Theme"
ComboBoxItemsSource="{Binding Path=themeViewModel.Themes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ComboBoxSelectedItem="{Binding Path=themeViewModel.SelectedTheme, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="2"
x:Name="ThemeBox"/>
</Grid>
</Grid>
</Border>
</StackPanel>
</TabItem>
<TabItem Style="{StaticResource TabItemStyle}">
<TabItem.Header>
<userControls:TabHeader Text="Data"
IsMouseOver="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=TabItem}}"
IsSelected="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TabItem}}"
Style="{StaticResource TabHeaderStyle}"
Height="20"/>
</TabItem.Header>
<StackPanel>
</StackPanel>
</TabItem>
</TabControl>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="_" Style="{DynamicResource TitleBarButtonStyle}" Name="Minimize" Click="Minimize_Click"/>
<Button Content="🗖" Style="{DynamicResource TitleBarButtonStyle}" Name="Restore" Click="Restore_Click"/>
<Button Content="╳" Style="{DynamicResource TitleBarButtonStyle}" Name="Close" Click="Close_Click"/>
</StackPanel>
</Grid>
This is the code behind where the data context is being set:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
MainWindowViewModel mainWindowViewModel = new();
this.DataContext = mainWindowViewModel;
}
This is the MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
public ThemeViewModel themeViewModel { get; set; }
public LanguageViewModel languageViewModel { get; set; }
public MainWindowViewModel()
{
themeViewModel = new ThemeViewModel();
languageViewModel = new LanguageViewModel();
}
}
This is the language view model:
internal class StringEntry
{
public string Bulgarian { get; set; }
public string English { get; set; }
public StringEntry(string bg, string en)
{
Bulgarian = bg;
English = en;
}
public string GetString(Language currentLanguage)
{
if (currentLanguage == Language.English)
{
return English;
}
return Bulgarian;
}
}
public class LanguageViewModel : ViewModelBase
{
private StringEntry file = new("Файл", "File");
private StringEntry data = new("Данни", "Data");
private StringEntry appearance = new("Изглед", "Appearance");
private Language currentLanguage;
public LanguageViewModel()
{
File = file.GetString(currentLanguage);
Data = data.GetString(currentLanguage);
Appearance = appearance.GetString(currentLanguage);
//todo: later load it from AppData/Local
currentLanguage = Language.English;
}
public string CurrentLanguage
{
get
{
return currentLanguage.ToString();
}
set
{
currentLanguage = Language.English.ToString() == value ? Language.English : Language.Bulgarian;
File = Language.English == App.Language ? file.English : file.Bulgarian;
Data = data.GetString(currentLanguage);
Appearance = appearance.GetString(currentLanguage);
OnPropertyChanged();
OnPropertyChanged(nameof(File));
}
}
public string File
{
get { return file.GetString(currentLanguage); }
set
{
_ = value;
OnPropertyChanged();
}
}
public string Data
{
get { return data.GetString(currentLanguage); }
set
{
_ = value;
OnPropertyChanged();
}
}
public string Appearance
{
get { return appearance.GetString(currentLanguage); }
set { _ = value; OnPropertyChanged(); }
}
}
And this is the base view model class:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string member = null)
{
PropertyChanged?.Invoke(member, new PropertyChangedEventArgs(member));
}
}
Any help will be greatly appreciated!
Your OnPropertyChanged implementation is wrong. Instead of the property name you must pass this
as first argument to the Invoke method:
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}