How to await the generation of containers in an ItemsControl?

拈花ヽ惹草 提交于 2019-12-11 15:20:26

问题


I have a SettingsWindow, in it there is an audio file selector which has a context menu. Some code accesses the MyAudioFileSelector computed property before it can get the AudioFileSelector because the AudioFileSelector is just inside a DataTemplate of an item in an ItemsControl that has not yet generated its containers at that moment. I tried to deffer the access to MyAudioFileSelector using Dispatcher.BeginInvoke with DispatcherPrority.Loaded, but the item containers are still not yet generated at that moment.

The code that accesses the MyAudioFileSelector is the method that applies one of the many settings inside the user-selected data file. This method is called from the Window's Loaded event handler synchronously for each setting in the program's data files' schema.

I am very new to async-await programming, I have read this but I am not sure how this helps me, and I read this page but I am still not sure what to do. I have read this a but the only answer, unaccepted, seems similar to what I already use below:

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

A part of the XAML

(InverseBooleanConv just makes true, false, and 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>
        [...]

Parts of the code-behind

In MainWindow.xaml.cs, the beginning of the Window_Loaded handler

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);
    }
    [...]

In MainWindow.xaml.cs, in the method 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;
    [...]

In the SettingsWindow class

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;
    }
}

The bug is in the two C# comments in one of the code snippets above, null reference exceptions.

I think I could attach in the MainWindow a handler to SettingsWindow's ItemsControl's ItemContainerGenerator's StatusChanged event and then continue the initialization of the window, including the loading of all the settings, but I wonder if there is a more orderly/correct way.

Thank you.


回答1:


If you have access to your ItemsControl in the code-behind under the variable name MyItemsControl, then you can add an event handler for the ContainerGenerator StatusChanged event:

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

I really recommand not to use this if what you're after is simply save/load data from a file, as they are completely unrelated.



来源:https://stackoverflow.com/questions/57971712/how-to-await-the-generation-of-containers-in-an-itemscontrol

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!