Как изменить словарь ресурсов загруженной темы до или после того, как ItemsControl генерирует свои контейнеры, но не в это время? - PullRequest
0 голосов
/ 19 сентября 2019

Я динамически загружаю словари ресурсов приложения (загружено 2 из 3 одновременно):

  1. словарь базовых ресурсов, всегда
  2. файл темы Light.xaml
  3. файл темы Dark.xaml

Если я обычно изменяю значение свойства MergedDictionaries, когда главное окно уже Loaded, я получаю исключение (стек вызовов здесь ):

System.InvalidOperationException: 'Невозможно вызвать StartAt во время создания контента.'

Если я изменю MergedDictionariesЗначение свойства, использующее Dispatcher.BeginInvoke, когда я использую ресурс в коде от (1), через исключение он говорит, что оно еще не загружено (например, использование ресурса через StaticResource, который не существует).

Я не хочу использовать файл App.xaml, потому что для приложения с одним экземпляром я использую класс, который наследуется от Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase и вызывает код в моем файле App.cs.

Я могу вызвать метод LoadTheme в нескольких местах кода приложения, и я хочусделать его стабильным.

App.cs

(без XAML)

public class App : System.Windows.Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        LoadTheme(AppTheme.Light);
        var w = new MainWindow();
        ShutdownMode = ShutdownMode.OnMainWindowClose;
        MainWindow = w;
        w.Show();
    }
    internal ResourceDictionary MLight = null,
        MDark = null,
        MMain = null;
    internal ResourceDictionary GetLightThemeDictionary()
    {
        if (MLight == null)
        {
            MLight = new ResourceDictionary() { Source = new Uri("Themes/Light.xaml", UriKind.Relative) };
        }
        return MLight;
    }
    internal ResourceDictionary GetDarkThemeDictionary()
    {
        if (MDark == null)
        {
            MDark = new ResourceDictionary() { Source = new Uri("Themes/Dark.xaml", UriKind.Relative) };
        }
        return MDark;
    }
    internal ResourceDictionary GetMainDictionary()
    {
        if (MMain == null)
        {
            MMain = new ResourceDictionary() { Source = new Uri("AppResources.xaml", UriKind.Relative) };
        }
        return MMain;
    }
    internal void LoadTheme(AppTheme t)
    {
        //Dispatcher.BeginInvoke(new Action(() =>
        //{
            if (Resources.MergedDictionaries.Count == 2)
            {
                switch (t)
                {
                    case AppTheme.Dark:
                        Resources.MergedDictionaries[1] = GetDarkThemeDictionary();
                        break;
                    default:
                        Resources.MergedDictionaries[1] = GetLightThemeDictionary();
                        break;
                }
            }
            else if (Resources.MergedDictionaries.Count == 1)
            {
                switch (t)
                {
                    case AppTheme.Dark:
                        Resources.MergedDictionaries.Add(GetDarkThemeDictionary());
                        break;
                    default:
                        Resources.MergedDictionaries.Add(GetLightThemeDictionary());
                        break;
                }
            }
            else
            {
                Resources.MergedDictionaries.Clear();
                Resources.MergedDictionaries.Add(GetMainDictionary());
                LoadTheme(t);
            }
        //}), System.Windows.Threading.DispatcherPriority.Normal); // how to process this after the ItemsControl has generated its elements?
    }
}

Я попытался создать тестовый пример, но мне не удалось - ясоздал программу, которая работает, потому что я устанавливаю ItemsControl.ItemsSource каждый раз, когда применяется шаблон.В моем реальном проекте я установил ItemsSource посредством привязки данных, а иногда и вручную, но я не уверен, что это то, что не хватает в моем реальном проекте.

Я использую .NET Framework 4.7.2, VS 2019, Win10 Pro.

Спасибо.

1 Ответ

0 голосов
/ 19 сентября 2019

Метод OnStartup теперь выглядит следующим образом (я только что загрузил базовый словарь и одну из тем перед построением MainWindow):

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    Resources.MergedDictionaries.Clear();
    Resources.MergedDictionaries.Add(GetMainDictionary());
    Resources.MergedDictionaries.Add(GetLightThemeDictionary());

    LoadTheme(AppTheme.Light);

    var w = new MainWindow();

    ShutdownMode = ShutdownMode.OnMainWindowClose;
    MainWindow = w;

    w.Show();
}

И я раскомментировал 2 комментария (которые делаютиспользование диспетчера):

internal void LoadTheme(AppTheme t)
{
    Dispatcher.BeginInvoke(new Action(() =>
    {
        [...]
    }), System.Windows.Threading.DispatcherPriority.Normal);
}
...