当前位置: 动力学知识库 > 问答 > 编程问答 >

c# - Possible race condition with WPF ItemsCollection.ItemContainerGenerator

问题描述:

In my application, I have a need to acquire the content of an ItemsCollection immediately after the ItemsSource has been changed. Or, at the very least, before the possibility of the content being visually drawn.

I tested something close to the following:

void UserControl_Loaded(object sender, EventArgs eventArgs) {

this.itemsControl.ItemsSource = GetItemsSource();

int ctrIndex = 0;

DependencyObject container;

while((container = this.itemsControl.ItemContainerGenerator.

ContainerFromIndex(ctrIndex++)) != null) {

DoSomething(VisualTreeHelper.GetChild(container, 0));

}

}

The problem is that, at the point DoSomething is called, the value of VisualTreeHelper.GetChildrenCount(container) is 0. If this code is instead called at a later point in time - such as in response to a Button.Click event firing, VisualTreeHelper.GetChildrenCount is the expected value and the code would presumably work.

PS. I've also attempted to conduct the while loop inside of an anonymous function:

this.itemsControl.ItemContainerGenerator.ItemsChanged += (_sender, _ea) => {

int ctrIndex = 0;

DependencyObject container;

while((container = this.itemsControl.ItemContainerGenerator.

ContainerFromIndex(ctrIndex++)) != null) {

DoSomething(VisualTreeHelper.GetChild(container, 0));

}

};

The behaviour is identical, sadly.

edit

I can't believe how many hoops you must jump through for generated content.

I believe I've found the earliest moment that I can safely capture the generated containers. However, I'm still turning up nil on the generated content inside those containers. Observe the following:

this.itemsControl.ItemContainerGenerator.StatusChanged += new EventHandler(StatusChanged);

void StatusChanged(object sender, EventArgs e) {

var cg = this.itemsControl.ItemContainerGenerator;

if(cg.Status == GeneratorStatus.ContainersGenerated && cg.ContainerFromIndex(0) != null) {

DoStuff();

}

}

At the point DoStuff() is called, the containers returned from ContainerFromIndex are not null. However, VisualTreeHelper.GetChildrenCount(container) is 0. I'd still very much like to know if someone has solved this.

网友答案:

I was encountering the same problem several minutes ago. Some little difference is, I needed the exact positioning and size information of the item containers. I tried with your attempt to listen to the StatusChanged event of the ItemContainerGenerator and finally discovered that while the status became ContainersGenerated and the containers were indeed generated, they were not laid out yet.

So I did something really gruff. First I set a flag, say, _updatePending, while the status of the ItemContainerGenerator became ContainersGenerated, then I handled the LayoutUpdated event of the ItemsControl, which would fire quite frequently, to check the _updatePending flag and whether the item containers are laid out:

var firstContainer = this.ItemsContainer.ItemContainerGenerator.ContainerFromIndex(0) as FrameworkElement;
if (_updatePending 
    && firstContainer != null 
    && firstContainer.IsLoaded)
{
    // do stuff
    _updatePending = false;
}

This is brutal but somehow effective.

网友答案:

The problem is that you cannot iterate over a collection while it is being changed. One way to overcome this is to look at the ItemsSource object directly after the data is populated, rather than trying to iterate over the ItemsSource on the control. If you are using an MVVVM pattern, you should be able to populate a collection property (the one you bind to your control's ItemsSource) in your ViewModel and inspect that once the data is returned from the database/service.

分享给朋友:
您可能感兴趣的文章:
随机阅读: