【问题标题】:WPF and MVVM - changing themes dynamicallyWPF 和 MVVM - 动态更改主题
【发布时间】:2012-06-23 17:50:33
【问题描述】:

我正在使用 MVVM 开发一个 WPF 项目,并且我正在尝试实现一个动态更改主题的功能。主题信息位于单独的 xaml 文件中(即 Theme1.xaml、Theme2.xaml)。由于各种原因,我想在 ViewModel 类中而不是在 View.xaml 的代码隐藏文件中进行实际的主题更改。

我尝试了几个想法,但没有任何效果:

  • 我尝试将 View 的 ResourceDictionary 绑定到 ViewModel 中的变量,但被告知无法在 ResourceDictionary 类型的 Source 属性上设置绑定

  • 我的 ViewModel 类中没有任何类型的 View 对象可以调用“UpdateTheme”方法

关于如何从 ViewModel 类更改我的 View 类中的 MergedDictionary 引用有什么想法吗?

谢谢!

【问题讨论】:

  • 要成为一名优秀的 MVVM 实践者,VM 应该拥有一个主题 identifier,但应该由 View 来解释和执行它。主题化是一种 UI 活动,因此 VM 应该尽可能少地了解它。

标签: wpf mvvm


【解决方案1】:

我之前在这里处理过相同的时间问题,在我的情况下我所做的可能是它可以帮助你。

将所有主题文件(theme1.xaml、theme2.xaml...)复制到 exe 路径下的 Themes 文件夹中。并尝试使用下面的示例代码。使用绑定

C#:

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));
}

XAML:

<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>

【讨论】:

  • 您确定必须手动调用 OnChanged 吗?我认为您在使用 ObservableCollection 时不需要这样做
【解决方案2】:

我像这样在我的应用程序启动时处理主题切换。

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

我首先清除Dictionaries 以删除任何预设Theme。我这样做是因为我在编辑器中使用默认主题,然后在 run-time 期间根据用户配置进行切换。

我重新启动应用程序以加载新主题,但是当您在 ViewModel 中保存状态等时,您应该能够重新加载 UI 而无需完全重新启动应用程序。然而,这不是我的项目的要求,所以我从来没有走那么远。

您可能只需从View 传递您的主题名称,然后使用您的ViewModel 中的逻辑对其进行解析。

【讨论】:

    【解决方案3】:

    您的问题是您试图直接从您的 ViewModel 更改视图,这是不允许的。你需要想出一个基于属性绑定的更被动的解决方案。

    我的方法是在主视图的代码隐藏中添加一小段代码,用于切换合并字典中的资源文件,并且它的执行方式可以由 ViewModel 中的属性值决定势必。在 MVVM 中允许使用少量代码隐藏来支持以视图为中心的行为。

    【讨论】:

    • 好的,假设我在视图代码后面有一个方法可以切换资源文件...我如何在视图模型类中使用这个方法?我认为这是不允许的?
    • Viewmodel 将成为您的视图数据上下文,因此只需订阅它的 INotifyPropertyChanged 事件
    猜你喜欢
    • 2019-01-31
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-05
    • 2015-02-01
    相关资源
    最近更新 更多