Как дождаться создания контейнеров в ItemsControl?

У меня есть SettingsWindow, в нем есть селектор аудиофайлов, у которого есть контекстное меню. Некоторый код обращается кMyAudioFileSelector вычисляемое свойство, прежде чем оно сможет получить AudioFileSelector поскольку AudioFileSelector просто внутри DataTemplate элемента в ItemsControlкоторый на тот момент еще не сгенерировал свои контейнеры. Я пытался отложить доступ кMyAudioFileSelector с помощью Dispatcher.BeginInvoke с DispatcherPrority.Loaded, но на данный момент контейнеры элементов еще не созданы.

Код, который обращается к MyAudioFileSelector- это метод, который применяет одну из многих настроек в выбранном пользователем файле данных. Этот метод вызывается изWindowс Loaded обработчик событий синхронно для каждого параметра в схеме файлов данных программы.

Я новичок в программировании async-await, я читал это, но не уверен, как это мне помогает, и я прочитал эту страницу, но все еще не уверен, что делать. Я прочитал это, но единственный ответ, непринятый, кажется похожим на тот, который я уже использовал ниже:

MySettingsWindow.Dispatcher.BeginInvoke(new Action(() =>
{
    [...]
}), System.Windows.Threading.DispatcherPriority.Loaded);

Часть XAML

(InverseBooleanConv просто делает true, false, а также false, true)

<ItemsControl Grid.ColumnSpan="3" Margin="0,0,-0.6,0" Grid.Row="0"
ItemsSource="{Binding SettingsVMs}" x:Name="MyItemsControl">
    <ItemsControl.Resources>
        <xceed:InverseBoolConverter x:Key="InverseBooleanConv"/>
        <DataTemplate DataType="{x:Type local:AudioFileSettingDataVM}">
            <local:AudioFileSelector MaxHeight="25" Margin="10" FilePath="{Binding EditedValue, Mode=TwoWay}">
                <local:AudioFileSelector.RecentAudioFilesContextMenu>
                    <local:RecentAudioFilesContextMenu
                        PathValidationRequested="RecentAudioFilesContextMenu_PathValidationRequested"
                        StoragePropertyName="RecentAudioFilePaths"
                        EmptyLabel="No recent audio files."/>
                </local:AudioFileSelector.RecentAudioFilesContextMenu>
            </local:AudioFileSelector>
        </DataTemplate>
        [...]

Части кода программной части

В MainWindow.xaml.cs начало Window_Loaded обработчик

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    VM.ClockVMCollection.Model.FiltersVM.Init();
    VM.Settings.IsUnsavedLocked = true;
    VM.ClockVMCollection.Model.IsUnsavedLocked = true;
    foreach (KeyValuePair<string, SettingDataM> k in VM.Settings)
    {
        ApplySetting(k.Value);
    }
    [...]

В MainWindow.xaml.cs в методе ApplySetting

case "AlwaysMute":
    VM.MultiAudioPlayer.Mute = (bool)VM.Settings.GetValue("AlwaysMute");
    break;
case "RecentAudioFilePaths":
    MySettingsWindow.Dispatcher.BeginInvoke(new Action(() =>
    {
        MySettingsWindow.MyRecentAudioFilesContextMenu. // here, MyRecentAudioFilesContextMenu is null, this is the problem
            LoadRecentPathsFromString(VM.Settings.GetValue("RecentAudioFilePaths") as string);
    }), System.Windows.Threading.DispatcherPriority.Loaded);
    break;
case "RecentImageFilePaths":
    MySettingsWindow.Dispatcher.BeginInvoke(new Action(() =>
    {
        MySettingsWindow.MyRecentImageFilesContextMenu. // here, MyRecentImageFilesContextMenu is null, this is the problem
            LoadRecentPathsFromString(
                VM.Settings.GetValue("RecentImageFilePaths") as string);
    }), System.Windows.Threading.DispatcherPriority.Loaded);
    break;
    [...]

в SettingsWindow учебный класс

internal AudioFileSelector MyAudioFileSelector
{
    get
    {
        foreach (SettingDataVM vm in MyItemsControl.ItemsSource)
        {
            if (vm is AudioFileSettingDataVM)
            {
                return (AudioFileSelector)MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm);
            }
        }
        return null;
    }
}
internal ImageFileSelector MyImageFileSelector
{
    get
    {
        foreach (SettingDataVM vm in MyItemsControl.ItemsSource)
        {
            if (vm is ImageFileSettingDataVM)
            {
                return (ImageFileSelector)MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm);
            }
        }
        return null;
    }
}
internal RecentAudioFilesContextMenu MyRecentAudioFilesContextMenu
{
    get
    {
        return MyAudioFileSelector?.RecentAudioFilesContextMenu;
    }
}
internal RecentFilesContextMenu MyRecentImageFilesContextMenu
{
    get
    {
        return MyImageFileSelector?.RecentImageFilesContextMenu;
    }
}

Ошибка находится в двух комментариях C# в одном из приведенных выше фрагментов кода, исключения для нулевых ссылок.

Думаю, я мог бы прикрепить MainWindow обработчик SettingsWindowс ItemsControlс ItemContainerGeneratorс StatusChanged событие, а затем продолжить инициализацию окна, включая загрузку всех настроек, но мне интересно, есть ли более упорядоченный / правильный способ.

Спасибо.

1 ответ

Решение

Если у вас есть доступ к вашему ItemsControl в коде программной части под именем переменной MyItemsControl, то вы можете добавить обработчик событий для ContainerGenerator StatusChanged событие:

private void Window_Loaded(object sender, RoutedEventArgs e) {
    //Subscribe to generated containers event of the ItemsControl
    MyItemsControl.ItemContainerGenerator.StatusChanged += ContainerGenerator_StatusChanged;
}

/// <summary>
/// Handles changed in container generator status.
///</summary>
private void ContainerGenerator_StatusChanged(object sender, EventArgs e) {
    var generator = sender as ItemContainerGenerator;
    //Check that containers have been generated
    if (generator.Status == GeneratorStatus.ContainersGenerated ) {
        //Do stuff
    }
}

Я действительно рекомендую не использовать это, если вам нужно просто сохранить / загрузить данные из файла, поскольку они совершенно не связаны.

Другие вопросы по тегам