WPF and MVVM - dynamically change themes

I am working on a WPF project using MVVM, and I am trying to implement a function that dynamically changes the theme. Information about the subject is in separate xaml files (i.e. Theme1.xaml, Theme2.xaml). I want to change the actual theme in the ViewModel class, and not in the file located in the View.xaml file, for various reasons.

I tried a couple of ideas, but can't get anything to work:

  • I tried to bind a ResourceDictionary of View with a variable in the ViewModel, but I was told that the binding could not be set in the Source property of type ResourceDictionary

  • I do not have any View object in my ViewModel class on which the UpdateTheme method is called

Any ideas on how I can change the MergedDictionary link in the View class from the ViewModel class?

Thanks!

+1
wpf mvvm
source share
3 answers

I used to work with the same time problem that I, in my case, can help you.

Copy all theme files (theme1.xaml, theme2.xaml ...) to the Themes folder on your EXE path. and try using the below code example. using bindings

public partial class MainWindow : Window, INotifyPropertyChanged { private FileInfo _SelectTheme; public FileInfo SelectedTheme { get { return _SelectTheme; } set { _SelectTheme = value; OnChanged("SelectedTheme"); ChangeTheme(_SelectTheme); } } private void ChangeTheme(FileInfo _SelectTheme) { App.Current.Resources.Clear(); App.Current.Resources.Source = new Uri(_SelectTheme.FullName, UriKind.Absolute); } private ObservableCollection<FileInfo> _files; public ObservableCollection<FileInfo> Files { get { return _files; } set { _files = value; OnChanged("Files"); } } public MainWindow() { this.InitializeComponent(); Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => { var localthemes = new System.IO.DirectoryInfo("Themes").GetFiles(); if (Files == null) Files = new ObservableCollection<FileInfo>(); foreach (var item in localthemes) { Files.Add(item); } SelectedTheme = Files[0]; })); this.DataContext = this; } public event PropertyChangedEventHandler PropertyChanged; public void OnChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } } <Window x:Class="WPFTheme.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Window" Title="MainWindow" Width="640" Height="480"> <Grid x:Name="LayoutRoot" Background="{DynamicResource DisabledForegroundBrush}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.285*" /> <ColumnDefinition Width="0.365*" /> <ColumnDefinition Width="0.35*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="0.132*" /> <RowDefinition Height="0.162*" /> <RowDefinition Height="0.403*" /> <RowDefinition Height="0.168*" /> <RowDefinition Height="0.135*" /> </Grid.RowDefinitions> <Button Width="57" Margin="15,13,0,10.872" HorizontalAlignment="Left" Content="Enabled" /> <Button Width="72" Margin="0,14,17.12,10.872" HorizontalAlignment="Right" Content="Disabled" IsEnabled="False" /> <TextBlock Grid.Column="1" Width="69" Margin="11.88,15,0,27.872" HorizontalAlignment="Left" Text="TextBlock" TextWrapping="Wrap" /> <TextBox Grid.Column="1" Width="64" Height="21" Margin="9.88,0,0,4.872" HorizontalAlignment="Left" VerticalAlignment="Bottom" Text="TextBox" TextWrapping="Wrap" /> <TextBox Grid.Column="1" Height="21" Margin="88.88,0,35.8,3.872" VerticalAlignment="Bottom" IsEnabled="False" Text="TextBox Disabled" TextWrapping="Wrap" /> <CheckBox Grid.Row="1" Width="71" Height="14" Margin="11,7.128,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="CheckBox" /> <CheckBox Grid.Row="1" Width="71" Height="14" Margin="0,8.128,15.12,0" HorizontalAlignment="Right" VerticalAlignment="Top" Content="Disabled" IsEnabled="False" /> <ComboBox Grid.Column="2" Width="94" Margin="8.2,18,0,11.872" HorizontalAlignment="Left" ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedTheme, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <ComboBox Grid.Column="2" Width="94" Margin="0,17,14,12.872" HorizontalAlignment="Right" IsEnabled="False" ItemsSource="{Binding Files}" /> <DataGrid Grid.Row="2" Grid.Column="1" Margin="8.88,6.876,7.8,62.862" AutoGenerateColumns="True" ItemsSource="{Binding Files}" /> <DatePicker Grid.Row="2" Height="23" Margin="10,0,15,147" VerticalAlignment="Bottom" /> <GroupBox Grid.Row="2" Grid.Column="2" Margin="6.2,2.876,6,5.862" Header="GroupBox"> <ScrollViewer Margin="6,0.723,1,1" ScrollViewer.HorizontalScrollBarVisibility="Visible"> <ListBox Width="161" Height="108" ItemsSource="{Binding Files}" /> </ScrollViewer> </GroupBox> <ListView Grid.Row="2" Grid.Column="1" Height="59" Margin="12.88,0,5.8,-4.138" VerticalAlignment="Bottom" ItemsSource="{Binding Files}"> <ListView.View> <GridView> <GridViewColumn Header="File Name"> <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView> <ProgressBar x:Name="progressBar" Grid.Row="1" Grid.Column="1" Height="20" Margin="5.88,6.128,61.8,0" VerticalAlignment="Top" Value="50" /> <RadioButton Grid.Row="1" Width="64" Margin="11,25.128,0,29.124" HorizontalAlignment="Left" Content="RadioButton" /> <RadioButton Grid.Row="1" Width="51" Margin="0,25.128,33.12,29.124" HorizontalAlignment="Right" Content="RadioButton" IsEnabled="False" /> <Slider Grid.Row="1" Grid.Column="1" Margin="11.88,34.128,38.8,15.124" AutoToolTipPlacement="BottomRight" Maximum="{Binding Maximum, ElementName=progressBar}" Minimum="{Binding Minimum, ElementName=progressBar}" Value="{Binding Value, ElementName=progressBar}" /> <TabControl Grid.Row="1" Grid.Column="2" Margin="7.2,9.128,9,0.124"> <TabItem Header="TabItem"> <Grid Background="#FFE5E5E5" /> </TabItem> <TabItem Header="TabItem"> <Grid Background="#FFE5E5E5" /> </TabItem> </TabControl> <TreeView Grid.Row="3" Margin="8,5.138,12.12,1.79" ItemsSource="{Binding Files}" /> <ToolBar Grid.Row="4" Grid.ColumnSpan="2" Margin="10,9.21,104.8,17"> <Button /> <CheckBox /> <ComboBoxItem /> <MenuItem /> <Separator /> <TabItem /> </ToolBar> </Grid> </Window> 
+4
source share

I handle the startup switch theme in my application like this.

 Application.Current.Resources.MergedDictionaries.Clear(); Application.Current.Resources.MergedDictionaries.Add(Themes.Where(p => p.Value.ThemeName == "MyTheme").SingleOrDefault().Value.Theme); 

First I cleared the Dictionaries to remove any Theme preset. I do this when I use the default theme in the editor, and then during run-time , depending on the configuration of users.

I will restart the application to download a new theme, but when saving states, etc. in ViewModel you have to reload the UI without restarting the application. This, however, was not a requirement for my project, so I never went that far.

Perhaps you can just pass the name of your theme from View , and then parse it using logic with the ViewModel .

+2
source share

Your problem is that you are trying to change the view directly from the ViewModel, which is prohibited. You need to come up with a more passive solution based on property binding.

In my approach, there will be a small piece of code from your main view code that will switch resource files in your combined dictionaries, and the way it does this can be attributed to the property value in your ViewModel associated with. MVVM allows a small amount of code to support View-centric behavior.

+1
source share

All Articles