locked
ItemsControl.PrepareContainerForItemOverride not called for new items RRS feed

  • Question

  • Hi

    I have a problem with a third party control that I'm trying to fix or work around. (ElementControl from http://fluidkit.codeplex.com/)

    That control is a subclass of ItemsControl and overrides PrepareContainerForItemOverride to create a WPF3D wrapper for each item. (The goal is to show a nice, animated 3D gallery of the items.)

    This works correctly the firsttime the ItemsSource is set. However, if the ItemsSource is later replaced with a different list of items, PrepareContainerForItemOverride is apparently not called again. This leads to crashes since the control expects these WPF3D wrappers to exist.

    How can I ensure that PrepareContainerForItemOverride is called for these new items?
    Should the control be changed to use a different approach for creatin the WPF3D wrappers? (PrepareContainerForItemOverride seems to be the only approach that allows using ItemTemplates.)
    Any other advice?

    Thanks.
    Monday, October 18, 2010 8:09 AM

Answers

  • Hi rumxaml,

    That you would like to use DispatcherPriority to "postpone" the calls. It may work. But it is hard to control the time. You could try.

    Yes, I find the same exception, _itemmap is null. Maybe the simple solution is to set the ItemsSource to null before set another collection to ItemsSource. It can work and return the correct result.

       _elementFlow.ItemsSource = null;
       _elementFlow.ItemsSource = othersource;
    
    Bob Bao
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Are you looking for a typical code sample? Please download all in one code framework !
    • Marked as answer by rumxaml Friday, October 29, 2010 11:16 AM
    Wednesday, October 27, 2010 12:00 PM

All replies

  • Hi rumxaml,

    How can I ensure that PrepareContainerForItemOverride is called for these new items?

    WPF calls PrepareContainerForItemOverride on ApplyTemplate method, it is only done once and never be called later if the ItemsSource is changed at the run-time.  Please refer to the issue on the Fluidkit site: http://fluidkit.codeplex.com/workitem/5477 

    It is a question regards to the third part control, you may better to ask it on the Discussions for fluidkit.

    Sincerely,
    Bob Bao

    MSDN Subscriber Support in Forum 

    If you have any feedback on our support, please contact msdnmg@microsoft.com


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Are you looking for a typical code sample? Please download all in one code framework !
    Tuesday, October 19, 2010 5:13 AM
  • Hi Bob

    Yes, that is the issue. Unfortunately no one on the fluidkit project seems to have the knowledge or time to change this. I'm now investigating possible solutions, including changing fluidkit myself.

    You wrote:
    > WPF calls PrepareContainerForItemOverride on ApplyTemplate method, it is only done once and never be called later if the ItemsSource is changed at the run-time.

    This sounds like PrepareContainerForItemOverride can not reliably be used to wrap ItemsSource items in WPF3D elements. What are the alternatives?

    Thanks.

    Tuesday, October 19, 2010 6:55 AM
  • Hi rumxaml,

    Just a suggestion, since it is better to contact the author of this control to know the proposed. You could override the OnItemsSourceChanged method to invoke the PrepareContainerForItemOverride again for preparing to generate the container for the items.

    Sincerely,
    Bob Bao


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Are you looking for a typical code sample? Please download all in one code framework !
    Wednesday, October 20, 2010 10:28 AM
  • Yes, that would help if it's possible.

    Unfortunately, directly calling PrepareContainerForItemOverride seems impossible. It has two parameters:

    "element"

    • Type: DependencyObject
    • MSDN Description: Element used to display the specified item.

    "item"

    • Type: object
    • MSDN Description: Specified item.

    While "item" is just from my ItemsSource, I think "element" is the instance of the DataTemplate(?) for that item.

    How do I get that so I can pass element to PrepareContainerForItemOverride?

     

    Thanks.

    Wednesday, October 20, 2010 11:09 AM
  • Hi,

    element  is the return value of GetContainerForItemOverride(), it is the container element of this ItemsControl. I am not sure if it can work for you, since it may not prepare the independent container for the items. Just try it for the control.

    Sincerely,
    Bob Bao


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Are you looking for a typical code sample? Please download all in one code framework !
    Wednesday, October 20, 2010 11:42 AM
  • Hi,

    I don't understand. GetContainerForItemOverride() does not appear to be related to the item. There is no item parameter.

    Just using the same return value for all items is surely wrong. (In this control element is shown on the 3D models. This should be different for each item.)

    Also, if I use this anyway it leads to a stack overflow where
         WindowsBase.dll!System.Windows.DependencyObject.OnInheritanceContextChanged(System.EventArgs args = {System.EventArgs}) + 0xfa bytes   
    is called recursively over and over.

    Thanks.

     

    Edit: It seems to create new instances each time. But that still does not seem useful here.

    Wednesday, October 20, 2010 1:05 PM
  • Hi runxaml,

    We may enter a wrong direction, let us read your original thread and the issue on the Fluidkit site: http://fluidkit.codeplex.com/workitem/5477.

    Quote:

    Comments

    rumzeus wrote Oct 13 at 4:09 PM
    The ElementControl overrides PrepareContainerForItemOverride and calls PrepareModel to insert a mesh into _modelContainer for each Item.
    Later in ElementFlow.BuildTargetPropertyPath (which is called via ElementFlow.SelectItemCore -> LayoutBase.SelectElement -> ElementFlow.PrepareTemplateStoryboard) it is assumed that such a mesh has been inserted into _modelContainer.
    This exception occurs when the mesh has not been inserted into _modelContainer. WPF calls PrepareContainerForItemOverride on ApplyTemplate. This is only done once. Items added later are never processed like that.

     

    The _modelContainer cannot find the inserted mesh while change the ItemsSource at runtime. I am not sure if it is the root cause. However, I test and check his code, find out when we change the ItemsSource of the ElementFlow, the LayoutBase.SelectElement has an exception in the loop code:

    for (int afterIndex = selectionIndex + 1; afterIndex < Owner.Items.Count; afterIndex++)
    {
     var rightSB = Owner.PrepareTemplateStoryboard(afterIndex);
     PrepareStoryboard(rightSB, GetAfterMotion(afterIndex - selectionIndex));
     rightSB.Begin(Owner.Viewport);
    }
    
    

    The cause is that the container from afterIndex item is not generated while the control is changing its ItemsSource, so it can not begin an animation on the after item. Try to add below code:

    for (int afterIndex = selectionIndex + 1; afterIndex < Owner.Items.Count; afterIndex++)
    {
      if (Owner.ItemContainerGenerator.ContainerFromIndex(afterIndex) == null) break;
      var rightSB = Owner.PrepareTemplateStoryboard(afterIndex);
      PrepareStoryboard(rightSB, GetAfterMotion(afterIndex - selectionIndex));
      rightSB.Begin(Owner.Viewport);
    }

    I tested it, it can work while it is changing the ItemsSource at run-time.

    Hope this helps.

    Sincerely,
    Bob Bao

     


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Are you looking for a typical code sample? Please download all in one code framework !
    Friday, October 22, 2010 9:06 AM
  •  

    Hi Bob

    Thanks for investigating!

    You say LayoutBase.SelectItem throws an exception because the container is not generated. That was also my conclusion and why I asked how to force generating the container (i.e. trigger PrepareCotnainerForItemOverride). Now you suggest instead to "bail out" (i.e. break) when no container exists. If this works, great!

    However, your proposed code doesn't reliably work for me. A NullReferenceException is raised in

    Owner.ItemContainerGenerator.ContainerFromIndex. (maybe because Owner.ItemContainerGenerator._itemMap is null?)

    Edit: Looking at the callstack, I note that this is actually called from PrepareContainerForItemOverride and ClearContainerForItemOverride, which seems to be a bad idea in the first place.

    I would thus suggest as a new approach, "postponing" these calls in ElementFlow.PrepareModel and ClearModel, respectively:


                Dispatcher.BeginInvoke(DispatcherPriority .Normal, new  Action (() =>
                {
                    if  (IsLoaded)
                    {
                        ReflowItems();
                    }
                }));

    and

    // Update SelectedIndex if needed
                Dispatcher.BeginInvoke(DispatcherPriority .Normal, new  Action (() =>
                {
                    if  (SelectedIndex >= 0 && SelectedIndex < Items.Count)
                    {
                        ReflowItems();
                    }
                    else
                    {
                        SelectedIndex = Math .Max(0, Math .Min(SelectedIndex, Items.Count - 1));
                    }
                }));

    What do you think?

    Thanks again.

    Monday, October 25, 2010 7:45 AM
  • Hi rumxaml,

    That you would like to use DispatcherPriority to "postpone" the calls. It may work. But it is hard to control the time. You could try.

    Yes, I find the same exception, _itemmap is null. Maybe the simple solution is to set the ItemsSource to null before set another collection to ItemsSource. It can work and return the correct result.

       _elementFlow.ItemsSource = null;
       _elementFlow.ItemsSource = othersource;
    
    Bob Bao
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Are you looking for a typical code sample? Please download all in one code framework !
    • Marked as answer by rumxaml Friday, October 29, 2010 11:16 AM
    Wednesday, October 27, 2010 12:00 PM