问题
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