Page 1 of 1

Invoke Storyboard on specific DataTemplate Instance

Posted: 09 Feb 2014, 17:26
by Nir Hasson
I have an ItemsControl that holds multiple items with a custom DataTemplate as the ItemTemplate property.
On my model-view code I have a data manager that holds list of visible items bounded to the ItemsSource property of the ItemsControl.

The display screen can't show all items so I'd like to create some cross fade in/out animation that will be applied on certain item. I would like to play the animation and at the end of it I would like to change the content of the data collection to hold new set of data items to be displayed.
I've manage to create a Storyboard within the scope of my DataTemplate.Resources but from this point I'm kinda lost ... :?

Any suggestions to accomplish this task would be great..

Thanks

Re: Invoke Storyboard on specific DataTemplate Instance

Posted: 09 Feb 2014, 22:34
by TheFabFab

Re: Invoke Storyboard on specific DataTemplate Instance

Posted: 10 Feb 2014, 14:52
by sfernandez
As I explained in a previous post, you can obtain the container of your data item. Then you can apply the storyboard to that container:
void ApplyStoryboard(BaseComponent dataItem, Storyboard storyboard)
{
    FrameworkElement element = FindContainerFromItem(dataItem).As<FrameworkElement>();
    if (element != null)
    {
        storyboard.Completed += this.OnAnimationCompleted;
        storyboard.Begin(element);
    }
}

void OnAnimationCompleted(BaseComponent sender, TimelineEventArgs e)
{
    Storyboard storyboard = sender.As<Storyboard>();
    if (storyboard != null)
    {
        storyboard.Completed -= this.OnAnimationCompleted;
        // do something
    }
}

Re: Invoke Storyboard on specific DataTemplate Instance

Posted: 10 Feb 2014, 17:25
by Nir Hasson
The suggested implementation didn't work for me.
Here is the code I used to grab ListBoxItem from the DataItem.
//-----------------------------------------------------------------------------
Noesis::Gui::ListBoxItem* NoesisGUIUtils::ListBoxItemFromDataItem(Noesis::Gui::ListBox* pListBox, Noesis::Core::BaseComponent* pDataItem)
	{
		// Assuming you named ItemsHost panel in your ListBox template
		// Otherwise, you have to find it iterating ListBox children with
		// VisualTreeHelper,and looking for a Panel with the IsItemsHost
		// property set to true

		Noesis::Gui::Panel* pItemsPanel = NULL;

		for (NsSize nVisualChild=0; nVisualChild < Noesis::Gui::VisualTreeHelper::GetChildrenCount(pListBox); ++nVisualChild)
		{
			Noesis::Gui::Visual* pCurVisualChild = Noesis::Gui::VisualTreeHelper::GetChild(pListBox, nVisualChild);
			Noesis::Gui::Panel* pCurPanel = NsDynamicCast<Noesis::Gui::Panel*>(pCurVisualChild);

			if (pCurPanel != NULL && pCurPanel->GetIsItemsHost())
			{
				pItemsPanel = pCurPanel;
				break;
			}
		}

		if (pItemsPanel == NULL)
		{			
			return NULL;			
		}
		

		Noesis::Gui::UIElementCollection* pCollection = pItemsPanel->GetChildren();

		// Search the requested list box item
		NsSize numItems = pCollection->Count();
		for (NsSize i = 0; i < numItems; ++i)
		{
			Noesis::Core::BaseComponent* container = pCollection->Get(i);
			Noesis::Gui::ListBoxItem* pListBoxItem = NsStaticCast<Noesis::Gui::ListBoxItem*>(container);

			// ListBoxItem contains the instantiation of data template visual tree
			Noesis::Gui::FrameworkElement* element = NsStaticCast<Noesis::Gui::FrameworkElement*>(
				pListBoxItem->GetContent());

			// Data item is set as DataContext of the DataTemplate root element
			if (element != NULL && element->GetDataContext() == pDataItem)
			{
				// Found!! :)
				return pListBoxItem;
			}
		}

		// Data item not found
		return NULL;
	}
When I use it that way the ItemsPanel is never found although I explicitly declare it in the XAML code.
I'm getting a Panel object but the GetIsItemsHost condition fails.

<ListBox x:Name="SourceListBox"
			ItemsSource="{Binding Items}"
			ItemTemplate="{StaticResource MyItemTemplate}" Width="400" HorizontalAlignment="Left" Height="600">
			<ListBox.ItemsPanel>
				<ItemsPanelTemplate x:Name="ItemsHostPanel">
					<StackPanel IsItemsHost="True"/>
				</ItemsPanelTemplate>
			</ListBox.ItemsPanel>
		</ListBox>
When I force the first found Panel to be selected as ItemsPanel I'm getting fixed number of 8 items in the element collection object and it never finds the container item.

Please advice.
Nir

Re: Invoke Storyboard on specific DataTemplate Instance

Posted: 10 Feb 2014, 18:47
by sfernandez
I think I didn't explain well enough how to find the ItemsHost panel.

If you are not able to name the panel yourself because you are using a predefined template for the ItemsControl, then you have to iterate through its children, including all descendants (you don't know how deep in the template's visual tree is the ItemsHost panel):
Panel* GetItemsHost(FrameworkElement* element)
{
    NsSize numChildren = VisualTreeHelper::GetChildrenCount(element);
    for (NsSize i = 0; i < numChildren; ++i)
    {
        FrameworkElement* child = NsDynamicCast<FrameworkElement*>(
            VisualTreeHelper::GetChild(element, i));

        Panel* panel = NsDynamicCast<Panel*>(child);
        if (panel != 0 && panel->GetIsItemsHost())
        {
            return panel;
        }

        panel = GetItemsHost(child);
        if (panel != 0)
        {
            return panel;
        }
    }

    return 0;
}

// ... then use that function over your items control
Panel* itemsHost = GetItemsHost(myListBox);

Re: Invoke Storyboard on specific DataTemplate Instance

Posted: 10 Feb 2014, 22:51
by Nir Hasson
Thank you very much !
It works now :)