在运行时更改 ThemeResource 的值不会反映在其他视图中

Changing value of a ThemeResource at runtime does not reflect in other views

我在我的 UWP 应用程序中使用自定义 themedictionary。我在运行时更改了 ThemeResource 的值。此更改仅反映在主视图中,而不会反映在其他视图中。即使我在更改资源值后创建新视图,新视图也仅使用资源的初始值。我做错了什么吗?

这就是我更改资源值的方式。

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

我的次要视图XAML:

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

连我的主视图都一样XAML。

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

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

Application.Resources is a ResourceDictionary object and ResourceDictionary class inherits from DependencyObject,所以 Application.Resources 也是一个 DependencyObject 实例。

All DependencyObject instances must be created on the UI thread that is associated with the current Window for an app. This is enforced by the system, and there are two important implications of this for your code:

  • Code that uses API from two DependencyObject instances will always be run on the same thread, which is always the UI thread. You don't typically run into threading issues in this scenario.
  • Code that is not running on the main UI thread cannot access a DependencyObject directly because a DependencyObject has thread affinity to the UI thread only. Only code that runs on the UI thread can change or even read the value of a dependency property. For example a worker thread that you've initiated with a .NET Task or an explicit ThreadPool thread won't be able to read dependency properties or call other APIs.

有关详细信息,请参阅 Remarks of DependencyObject 下的 DependencyObject 和线程

所以每个 Window 都有自己的 Application.Resources。在辅助视图中,Application.Resources 是根据您的 ResourceDictionary 重新评估的。 BackgroundBrush 不受主视图中设置的影响,如以下代码

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

您只是在更改与主视图的 Window 关联的 Application.Current.Resources 实例。

如果你想让次视图使用和主视图一样的画笔,我想你可以把这个颜色存储在主视图中,然后在创建次视图时应用它。比如我在Appclass中添加了一个名为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);
}

I have changed the code to modify the resource in each view. if you set a breakpoint in ThemeChanged method in SecondaryPage.xaml.cs before changing the value you can see that the resource's value has already changed to the updated one. But its not reflecting in the view.

这里的问题是因为你的 ThemeChanged 事件是在主视图中触发的,所以它在主视图的线程中 运行ning,因此你在 ThemeManager_ThemeChanged 中使用的方法=57=]SecondaryPage.xaml.cs 也将在主视图的线程中 运行ning。这导致 Application.Current.Resources in ThemeManager_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 class also inherits from DependencyObject. To solve this issue, I'd suggest changing HighAccentColorBrush type from SolidColorBrush to Color 然后像这样使用它:

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

这样使用主题。

在您的主题页面中点击按钮添加此逻辑

    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;

    }

这样堆栈中所有打开的实例也将更新主题。希望这能回答您的问题。