WPF and MVVM - changing themes dynamically

2019-02-15 23:40发布

I'm working on a WPF project using MVVM and I'm trying to implement a feature that changes the theme dynamically. The theming info is located in separate xaml files (ie Theme1.xaml, Theme2.xaml). I want to do the actual theme changing in the ViewModel class rather than in the code behind file of View.xaml for various reasons.

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

  • I tried binding the ResourceDictionary of View to a variable in ViewModel but am told that a binding cannot be set on the Source property of type ResourceDictionary

  • I don't have any sort of View object in my ViewModel class on which to call a "UpdateTheme" method

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

Thanks!

标签: wpf mvvm
3条回答
贪生不怕死
2楼-- · 2019-02-15 23:53

Your problem is that you are trying to change the View directly from your ViewModel, which is not allowed. You need to come up with a more passive solution based on property bindings.

My approach would be have a small piece of code the your main view's code-behind that switches resource files in your merged dictionaries, and the way it does this can be disctated by the value of a property in your ViewModel that it is bound to. A small amount of code-behind to support View-centric behaviour is allowed in MVVM.

查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-02-15 23:59

I have worked with the same time problem earlier here what i did in my case may be it can help you out.

Copy all you theme files (theme1.xaml, theme2.xaml...) into Themes folder at you exe path. and try with below sample code. 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楼-- · 2019-02-16 00:00

I handle Theme switching at start-up 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);

I first clear the Dictionaries to remove any preset Theme. I do this as I use a default theme in the editor, and then during run-time switch depending on the users configuration.

I restart the application to load the new theme, but as you save the states etc in your ViewModel you should be able to reload the UI without having to completely restart the application. This was however not an requirement for my project, so I never went that far.

You could probably just pass on the name of your theme from the View, and then parse it using logic from your ViewModel.

查看更多
登录 后发表回答