2012-06-21 148 views
1

我正在使用MVVM開發WPF項目,並試圖實現動態更改主題的功能。主題信息位於單獨的xaml文件(即Theme1.xaml,Theme2.xaml)中。出於各種原因,我想在ViewModel類中更改實際的主題,而不是View.xaml的代碼背後的代碼。WPF和MVVM - 動態更改主題

我試過一對夫婦的想法,但不能得到任何工作:

  • 我想查看的資源字典綁定到視圖模型變量,但我告知,不能在設置綁定類型資源字典的來源屬性

  • 我沒有任何形式查看對象在上我的ViewModel類調用「UpdateTheme」方法

關於如何從ViewModel類中更改View類中的MergedDictionary引用的任何想法?

謝謝!

+0

要成爲一名優秀的MVVM從業者,虛擬機應該擁有一個主題*標識符*,但應由視圖來解釋並採取行動。主題是一個UI活動,所以虛擬機應儘可能少地瞭解它。 – slugster

回答

4

我已經在早些時候與同一時間的問題在這裏工作,我在我的情況下可能會幫助你。

將你所有的主題文件(theme1.xaml,theme2.xaml ...)複製到你的exe路徑下的Themes文件夾中。並嘗試使用下面的示例代碼。使用綁定

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

你的問題是,你正試圖直接從你的ViewModel,這是不允許的更改視圖。您需要基於屬性綁定提出更加被動的解決方案。

我的方法是將一小段代碼放在合併字典中切換資源文件的主視圖的代碼隱藏中,並且可以通過ViewModel中屬性的值來區分它的方式一定會。 MVVM允許少量代碼隱藏以支持以視圖爲中心的行爲。

+0

好吧,所以說我在視圖的代碼中有一個切換資源文件的方法...如何從視圖模型類使用此方法?我認爲這是不允許的? – user1015830

+0

Viewmodel將是您的viewscontentcontext,所以只需訂閱其INotifyPropertyChanged事件 –

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中的邏輯對其進行解析。