2017-04-16 72 views
3

我在UWP应用程序中使用自定义主题词典。我在运行时更改ThemeResource的值。这种改变只反映在主视图中,而不反映在其他视图中。即使我在更改资源值后创建新视图,新视图也只使用资源的初始值。有什么我做错了吗?在运行时更改ThemeResource的值并不反映在其他视图中

这是我如何更改资源的价值。

(Application.Current.Resources["BackgroundBrush"] as SolidColorBrush).Color = Windows.UI.Colors.Black; 

我的二级视图的XAML:

<Grid Background="{ThemeResource BackgroundBrush}"/> 

即使我的主视图具有相同的XAML。

这是完整的项目。 Download Repo as zip

+0

我使用风格而不是主题!通过使用风格,我们可以更新外观和感觉与重新启动应用程序。 – Nilay

+0

https://github.com/Kumara-Krishnan/ThemePOC – Razor

+0

@ JayZuo-MSFT即使在更改资源值后,辅助视图仍具有最初在资源字典中指定的值。 – Razor

回答

8

我会认为这是设计。当我们为应用程序创建多个窗口时,每个窗口的行为都是独立的。每个窗口都在自己的线程中运行。每个窗口中使用的Application.Resources也是独立的。

Application.ResourcesResourceDictionary对象和ResourceDictionary类从DependencyObject继承,所以Application.Resources也是DependencyObject实例。

所有DependencyObject实例必须是与当前Window为一个应用程序相关的UI线程上创建。这是由系统执行,并且也有这对于你的代码的两个重要含义:使用API​​从两个DependencyObject实例将始终在同一个线程,它总是在UI线程上运行

  • 代码。在这种情况下,您通常不会遇到线程问题。
  • 未在主UI线程上运行的代码无法直接访问DependencyObject,因为DependencyObject仅对UI线程具有线程关联。只有在UI线程上运行的代码才能更改甚至读取依赖项属性的值。例如,您使用.NET 任务或明确的ThreadPool线程启动的工作线程将无法读取依赖项属性或调用其他API。

欲了解更多信息,请参阅的DependencyObject和DependencyObjectRemarks下穿

所以每个Window有它自己的Application.Resources。在次要视图中,您的ResourceDictionary将重新评估Application.Resources。该BackgroundBrush不会在主视图设置的影响与下面的代码

(Application.Current.Resources["BackgroundBrush"] as SolidColorBrush).Color = Windows.UI.Colors.Black; 

只更改与主视图的Window相关的Application.Current.Resources实例。

如果您希望辅助视图使用与主视图相同的画笔,我认为您可以将此颜色存储在主视图中,然后在创建辅助视图时应用它。例如,我在App类添加一个名为BackgroundBrushColor静态字段,然后使用它像以下:

private void ThemeChanger_Click(object sender, RoutedEventArgs e) 
{ 
    App.BackgroundBrushColor = Windows.UI.Color.FromArgb(Convert.ToByte(random.Next(255)), Convert.ToByte(random.Next(255)), Convert.ToByte(random.Next(255)), Convert.ToByte(random.Next(255))); 

    (Application.Current.Resources["BackgroundBrush"] as SolidColorBrush).Color = App.BackgroundBrushColor; 
} 

private async Task CreateNewViewAsync() 
{ 
    CoreApplicationView newView = CoreApplication.CreateNewView(); 
    int newViewId = 0; 

    await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() => 
    { 
     (Application.Current.Resources["BackgroundBrush"] as SolidColorBrush).Color = App.BackgroundBrushColor; 

     Frame frame = new Frame(); 
     frame.Navigate(typeof(SecondaryPage), null); 
     Window.Current.Content = frame; 
     // You have to activate the window in order to show it later. 
     Window.Current.Activate(); 

     newViewId = ApplicationView.GetForCurrentView().Id; 
    }); 
    bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId); 
} 

我已经改变了代码来修改每个视图中的资源。如果您在更改值之前在SecondaryPage.xaml.cs中的ThemeChanged方法中设置断点,则可以看到资源的值已更改为已更新的值。但它并没有反映在这个角度。

这里的问题是,因为你的ThemeChanged事件在主视图中引发的,因此它在主视图中的线程中运行,因此你在SecondaryPage.xaml.cs使用ThemeManager_ThemeChanged方法也将在主视图的运行线。这导致Application.Current.ResourcesThemeManager_ThemeChanged方法中仍然获得与主视图关联的ResourceDictionary实例。这就是为什么资源的价值已经改变到更新的价值,并不会反映在视图中。为了清楚地看到这一点,您可以在调试时利用Threads Window

为了解决这个问题,你可以使用this.Dispatcher.RunAsync方法在正确的线程类似下面的运行代码:

private async void ThemeManager_ThemeChanged(Utils.ThemeManager theme) 
{ 
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() => 
    { 
     ((SolidColorBrush)Application.Current.Resources["BackgroundBrush"]).Color = theme.HighAccentColorBrush.Color; 
    }); 
} 

但是,有了这个,你仍然会得到这样的错误:“应用程序调用一个接口被编组为一个不同的线程“这是因为SolidColorBrush类也继承DependencyObject。为了解决这个问题,我建议改变从SolidColorBrushHighAccentColorBrush类型Color,然后用它喜欢:

private async void ThemeManager_ThemeChanged(Utils.ThemeManager theme) 
{ 
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() => 
    { 
     ((SolidColorBrush)Application.Current.Resources["BackgroundBrush"]).Color = theme.HighAccentColorBrush; 
    }); 
} 
+1

所以,如果我有100个主题资源,你的意思是说我应该在我的App.xaml.c中有100个静态属性。主题资源概念本身现在变得无效了吗?其次,Application.Resources的含义字面意思是应用程序的资源是否正确?所以对所有观点来说都应该是常见的?或者每个视图被视为不同的应用程序。我在这里看到一些设计级别的缺陷。如果它按照你提到的方式工作。你能否与你的团队核对并回复?默认邮件应用程序处理这种情况?你能告诉我他们如何处理这个问题吗? – Razor

+0

每个窗口中的application.current.resources'might'也是独立的。你能否与团队中的某个人核对并用二进制答复? – Razor

+0

我已更改代码以修改每个视图中的资源。如果您在更改值之前在SecondaryPage.xaml.cs中的ThemeChanged方法中设置断点,则可以看到资源的值已更改为已更新的值。但它并没有反映在这个角度。 PS:查看我的最新代码。 https://github.com/Kumara-Krishnan/ThemePOC – Razor

0

使用这种方式主题化。

在上按钮,你的主题页面点击添加此逻辑

void OnClicked(object sender, System.EventArgs e) 
    { 
     var btn = sender as Button; 

     MessagingCenter.Send(this, "ThemeButtonClicked", btn.BackgroundColor); 

     Application.Current.Properties["Theme"] = btn.BackgroundColor; 
     Application.Current.SavePropertiesAsync(); 

     Navigation.PopAsync(true); 

    } 

在所有其他页面,如下图所示订阅上述通知。

MessagingCenter.Subscribe<Theme, Color>(this, "ThemeButtonClicked", OnThemeChanged); 

当用户通知时添加以下方法执行。

private void OnThemeChanged(Theme source, Color cl) 
    { 
     this.BackgroundColor = cl; 
     layout.BackgroundColor = cl; 

    } 

因此,堆栈上的所有打开的实例也将获得主题更新。希望这回答你的问题。

相关问题