silverlight 4 treeview slow on expanding when many items - silverlight

In XAML I have:
<sdk:TreeView x:Name="navigationTreeView" Grid.Column="0" Grid.Row="1" SelectedItemChanged="TreeView_SelectedItemChanged">
<sdk:TreeView.ItemContainerStyle>
<Style TargetType="sdk:TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</sdk:TreeView.ItemContainerStyle>
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel Orientation="Horizontal">
<!--<Image Source="{Binding Path=ImageUri}" />-->
<TextBlock Text="{Binding Path=Title}" ToolTipService.ToolTip="{Binding Path=Title}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
In code behind:
this.navigationTreeView.ItemsSource = nodes;
navigationTreeView.ExpandAll();
There are 1000 items as children of one node. If I'm not expanding elements everything is fine. But when I expand that node it's pretty slow (10 sec maybe). What could I do to speed it up?
Silverlight 4, 2010 april toolkit.

Unlike WPF, Silverlight (until Silverlight 4, not sure about 5) does NOT support UI virtualization for hierarchical data, and this is why when you expand the node, the 1000 items which are inside the HierarchicalDataTemplate are not virtualized and take more than 10 seconds to load.
I believe Telerik's RadTreeView has its built-in UI virtualization, but the control is not free.
The best solution I have found so far is from this site. Please note it is still a ListBox solution, however, because it doesn't use HierarchicalDataTemplate, it is fully virtualized and looks exactly like a TreeView. Also by looking at the source code, it is quite easy to implement.
I know it is not the perfect answer for this question but at least give you some alternatives. :)

This is how the TreeView works. It's virtualized by default: it won't create TreeViewItems until they're needed. However, you're expanding the entire tree which forces all items to be created. That's just inherently slow. If you really need this sort of behavior (where all choices are expanded), I'd suggest something else like a ListBox. Anything that is scrolled off the screen won't be created until it's needed (but see this caveat.)

My bad stack panel is not in the right place...
Ok try this instead:
<sdk:TreeView x:Name="navigationTreeView" Grid.Column="0" Grid.Row="1" SelectedItemChanged="TreeView_SelectedItemChanged">
<sdk:TreeView.ItemContainerStyle>
<Style TargetType="sdk:TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</sdk:TreeView.ItemContainerStyle>
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel Orientation="Horizontal">
<!--<Image Source="{Binding Path=ImageUri}" />-->
<TextBlock Text="{Binding Path=Title}" ToolTipService.ToolTip="{Binding Path=Title}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
<sdk:TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</sdk:TreeView.ItemsPanel>
</sdk:TreeView>
Adding:
<sdk:TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</sdk:TreeView.ItemsPanel>
Let me know the results :D

try this:
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<VirtualizingStackPanel Orientation="Horizontal">
<!--<Image Source="{Binding Path=ImageUri}" />-->
<TextBlock Text="{Binding Path=Title}" ToolTipService.ToolTip="{Binding Path=Title}"/>
</VirtualizingStackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
Replace the normal StackPanel with one VirtualizingStackPanel...

Related

How to create a standard DataTemplate for DataGridTemplateColumn?

I've got a DataTemplate for a DataGridTemplateColum wich looks like this:
<toolkit:DataGridTemplateColumn x:Name="DataGridTextColumnIstVorvorjahr" IsReadOnly="True" SortMemberPath="SummeIstVorvorjahr">
<toolkit:DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Background="Transparent" Margin="0,-5">
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TextBlock Panel.ZIndex="100" Style="{DynamicResource CellText}" Text="{Binding Path=SummeIstVorvorjahrGerundet, Converter={StaticResource numberFormatter}, ConverterParameter='#,0.0 T€'}" DockPanel.Dock="Right"/>
<Image Panel.ZIndex="90" DockPanel.Dock="Left" MouseLeftButtonUp="FilterDataGridAnalyse_MouseDoubleClick" HorizontalAlignment="Left" Margin="5,0,0,0" Width="20" Height="20" Visibility="Hidden" Name="ImageNormal" Source="pack://application:,,,/Cis.Common.Presentation;component/Resources/Images/Lupe.png" />
</DockPanel>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ImageNormal" Property="Visibility" Value="Visible" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</toolkit:DataGridTemplateColumn.CellTemplate>
<toolkit:DataGridTemplateColumn.HeaderTemplate>
<DataTemplate >
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" LastChildFill="False">
<TextBlock x:Name="TextBlockHeaderZeile1" Text="Ist" DockPanel.Dock="Top" />
<WrapPanel DockPanel.Dock="Top">
<TextBlock x:Name="TextBlockHeaderZeile2" Text=""/>
<ContentPresenter x:Name="contentPresenter">
<ContentPresenter.Content>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content" />
</ContentPresenter.Content>
</ContentPresenter>
</WrapPanel>
<Border Style="{DynamicResource borderline}">
<TextBlock VerticalAlignment="Stretch" x:Name="TextBlockSumme" Text="{Binding Path=KumulierteSummeIstVorvorjahr, Converter={StaticResource numberFormatter}, ConverterParameter='#,0.0 T€', RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type cis:ChildWindow}}}"
/>
</Border>
</DockPanel>
</DataTemplate>
</toolkit:DataGridTemplateColumn.HeaderTemplate>
</toolkit:DataGridTemplateColumn>
Now I want to make a StandartTemplate for this Type because I've got many Colums like this, with only differ in the bindings of the texts in the colums as well as in their headers.
As far I've tried to make a Style for this, but this won't work, I tried to create an usercontrol (but I think it's like taking a sledgehammer to crack a nut).
So any help or hint how to solve this problem would be appreciated.
I don't see why you've rejected the UserControl approach. UserControls are pretty lightweight. They add very little overhead at runtime. They are an extra feature in your project of course, but I usually find that to be an improvement - WPF projects with a small number of large Xaml files are typically hard to maintain.
Far from being a 'sledgehammer', they seem like exactly the right solution here to me.
Add the DataTemplate into the Resources and then access it via a StaticResource
<Window>
<Window.Resources>
<DataTemplate x:Key="MyColumnTemplate">
...
</DataTemplate>
<DataTemplate x:Key="MyColumnTemplateHeader">
...
</DataTemplate>
</Window.Resources>
...
<toolkit:DataGridTemplateColumn x:Name="DataGridTextColumnIstVorvorjahr" IsReadOnly="True" SortMemberPath="SummeIstVorvorjahr"
CellTemplate={StaticResource MyColumnTemplate}
HeaderTemplate={StaticResource MyColumnTemplateHeader}
...
</Window>
If I understand you, you try to bind the same column template with different data and to have different header's content relative with column data. So, you may use "dynamic XAML" (XAML used in C# code - that is dynamic) which allows you to use one template for different data.
Here a simple example.
In C# code we create DataGridTemplateColumn object:
DataGridTemplateColumn tc = new DataGridTemplateColumn();
Then we set the CellTemplate property with template which is created dynamically in special function:
tc.CellTemplate = (DataTemplate)XamlReader.Parse(GetTextCellDataTemplate(someText));
Here is a special function which creates our template:
public static string GetTextCellDataTemplate(string bindingPath)
{
return #"
<DataTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" >
<ScrollViewer MaxHeight=""200"" MaxWidth=""250"" VerticalScrollBarVisibility=""Auto"">
<TextBlock Text=""{Binding Path=" + bindingPath + #"}""
TextWrapping=""Wrap"" />
</ScrollViewer>
</DataTemplate>";
}
Now you may send various information in this function as text and get the same template. You may choose the template from the information you want to put in the cell. For this you must write various function which will return various templates.
The same approach can be applied to header template.

WPF - Binding large amount of data in Listbox

I am creating a search page for books. There is lots of data in the database. If the data size is more than 2000 the application hangs. ItemsSource of the listbox having the data but something wrong is happening behind.
Code
<ListBox Grid.Column="1"
x:Name="lbResult"
ItemsSource="{Binding}"
SelectionChanged="lbResult_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="320">
<TextBlock Margin="10">
<InlineUIContainer>
<TextBlock Foreground="DarkKhaki" Text="{Binding Title}"/>
</InlineUIContainer>
<Run Text=" "/><LineBreak/>
<InlineUIContainer>
<TextBlock Text=" By "/>
</InlineUIContainer>
<Run Text=" "/>
<InlineUIContainer>
<TextBlock Text="{Binding Author}"/>
</InlineUIContainer>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
The application is apparently "hanging" because the data load is happening on the UI thread.
You should consider a different model that enables you to load the data in another thread and update the UI periodically or as and when new data arrives.
You can use an ObservableCollection for this.
The background loading thread updates the collection and this fires an event out to the UI thread indicating that an update is required.
There's an example of how to do this on GALA Soft
You create a property (read only in this case) for the collection:
private ObservableCollection<MyDataItem> dataItems;
public ObservableCollection<MyDataItem> DataItems
{
get { return dataItems; }
}
Then in your XAML:
<ListBox ItemsSource="{Binding ElementName=mainWindow, Path=DataItems}"
...>
</ListBox>
One problem that you may have is that you are using a non-virtualizing type of panel (WrapPanel) in your ItemsPanelTemplate. What this means is that all 2000 data items will be loaded even if only a fraction of those are visible. By default ListBox uses a VirtualizingStackPanel as its panel which, as the name indicates, provides virtualisation, so it will only load the visible data set elements.
So an easy fix in terms of performance would be to dispense with the WrapPanel and us a virtualizing panel instead, however this would obviously change the appearance.
If you particularly want a WrapPanel then there is no virtualised equivalent provided by WPF, but there are implementations out there, such as http://virtualwrappanel.codeplex.com/.
Try using a ListView instead, I had the same problem. Now I can load over 7000 items in an instant.
Like this:
<StackPanel Grid.Row="1" Grid.Column="0">
<ListView
Height="100"
Name="lstPlayerList">
<ListView.View>
<GridView>
<GridViewColumn
Width="100"
Header="LastName"
DisplayMemberBinding="{Binding LastName}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>

Using ItemsControl on a multi-leveled TreeView

My co-worker threatened to put me on TheDailyWTF today because of my property I wrote to be used to build a 3-tiered treeview with ItemsControl.
I bear you the footprint:
ObservableCollection<KeyValuePair<string, ObservableCollection<KeyValuePair<string, ObservableCollection<MyType>>>>>;
My goal was to create an ItemsControl that would use the Key as the header, and Value as the ItemsSource for 3 levels:
<Style x:Key="filterTreeStyle" TargetType="ItemsControl">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.Header>
<controlsToolkit:TreeViewItemCheckBox Content="{Binding Key}"/>
</controls:TreeViewItem.Header>
<ItemsControl ItemsSource="{Binding Value}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:TreeViewItem>
<controls:TreeViewItem.Header>
<controlsToolkit:TreeViewItemCheckBox Content="{Binding Key}"/>
</controls:TreeViewItem.Header>
<controlsToolkit:TreeViewItemCheckBox IsChecked="{Binding Enabled}" Content="{Binding FilterTypeText}"/>
</controls:TreeViewItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</controls:TreeViewItem>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Can anyone save me from the clutches of TheDailyWTF? What is a cleaner way to do this. Bonus if we can figure out a way to make the number of levels dynamic.
Uh, maybe I'm being dumb here, but since you want a TreeView... why not use a TreeView? You'll also need to use a HierarchicalDataTemplate instead of a vanilla DataTemplate: the content of the HDT becomes the Header, and the ItemsSource is used to create the child nodes. That will also take care of making the number of levels dynamic.
TreeView is built into WPF and is available in Silverlight as part of the SDK.

How can I make an "Accordion Widget" in WPF?

The goal:
I'm trying to achieve something like this in WPF:
(source: wordpress.org)
An initial solution:
At the moment, I'm trying to use an ItemsControl with an ItemTemplate composed of an Expander.
I want a consistent look for the Header portion of the Expander, but I want the Content portion of the Expander to be completely flexible. So, it's basically a set of "portlets" stacked vertically, where each portlet has a consistent title bar but different content.
The code so far:
This is what I have at the moment:
<ItemsControl
Grid.Row="2"
Grid.Column="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<TextBlock
FontSize="14"
FontWeight="Bold"
Text="Title_Of_Expander_Goes_Here" />
<TextBlock
Margin="10,0,0,0"
FontWeight="Bold"
FontSize="18"
Text="*" />
</StackPanel>
</DataTemplate>
</Expander.HeaderTemplate>
<Expander.Template>
<ControlTemplate
TargetType="Expander">
<Border
BorderThickness="1">
<ContentPresenter />
</Border>
</ControlTemplate>
</Expander.Template>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Items>
<StackPanel>
<TextBlock
FontSize="14"
FontWeight="Bold"
Text="Users:" />
<wt:DataGrid
Margin="0,1,0,0"
AutoGenerateColumns="False"
CanUserAddRows="True"
CanUserDeleteRows="True"
ItemsSource="{Binding Source={StaticResource Main_SystemUsers}, XPath=//Users/*}">
<wt:DataGrid.Columns>
<wt:DataGridTextColumn
Header="User Name"
Binding="{Binding XPath=#UserName}" />
<wt:DataGridComboBoxColumn
Header="Role"
ItemsSource="{Binding Source={StaticResource Main_UserRoles}, XPath=//Roles/*}"
SelectedValueBinding="{Binding XPath=#Role}" />
</wt:DataGrid.Columns>
</wt:DataGrid>
<StackPanel
Margin="0,10,0,0"
Orientation="Horizontal">
<Button
Content="Add New User..." />
<Button
Margin="10,0,0,0"
Content="Delete User..." />
</StackPanel>
</StackPanel>
</ItemsControl.Items>
</ItemsControl>
Discussion:
The only thing that shows up when I run this is the DataGrid of users and the buttons ("Add New User" and "Delete User") below it. There is no Expander or title bar. Also, even if I did see one, I'm not sure how to set up a Binding for the title that appears on the title bar. I know how to do bindings if I use ItemsSource, but I wanted to set my items declaratively.
The question:
How should I go about this? I'm looking for either a fix for what I have now or a clean-sheet solution.
Edit:
What I ended up doing was replacing the ItemsControl with a StackPanel and just writing a style for my expanders. This proved to be much simpler, and there really was no benefit to the ItemsControl since I needed to declare custom content for each item anyway. The one issue remaining was how to achieve a custom title for each expander. That's where #Thomas Levesque's suggestion to use TemplateBinding came in. All I had to do was replace Text="Title_Of_Expander_Goes_Here" in my header's template (see code above) with Text="{TemplateBinding Content}".
You're not seeing the Expander because you redefined its template. This one should work better :
...
<Expander.Template>
<ControlTemplate
TargetType="Expander">
<Border
BorderThickness="1">
<Expander Content="{TemplateBinding Content}" Header="{TemplateBinding Header}"/>
</Border>
</ControlTemplate>
</Expander.Template>
...
Personally I think a TreeView control would give you a much better base to work from, especially if you're using Expression Blend as a basis to create new/blank Templates from for items. Seeing the default Templates is extremely enlightening and gives you much more fine-grained control and better understanding and insight into how things work by default. Then you can go to town on them. It also looks like you're working with Hierchical Data and TreeViews inherently lend themselves well to working with such data.

Force TextBlock to wrap in WPF ListBox

I have a WPF listbox which displays messages. It contains an avatar on the left side and the username and message stacked vertically to the right of the avatar. The layout is fine until the message text should word wrap, but instead I get a horizontal scroll bar on the listbox.
I've Googled and found solutions to similar issues, but none of them worked.
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding Path=FriendsTimeline}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border BorderBrush="DarkBlue" BorderThickness="3" CornerRadius="2" Margin="3" >
<Image Height="32" Width="32" Source="{Binding Path=User.ProfileImageUrl}"/>
</Border>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=User.UserName}"/>
<TextBlock Text="{Binding Path=Text}" TextWrapping="WrapWithOverflow"/> <!-- This is the textblock I'm having issues with. -->
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Contents of the TextBlock can be wrapped using property TextWrapping.
Instead of StackPanel, use DockPanel/Grid.
One more thing - set ScrollViewer.HorizontalScrollBarVisibility property to Disabled value for the ListBox.
Updated Hidden to Disabled based on comment from Matt. Thanks Matt.
The problem might not be located in the ListBox. The TextBlock won't wrap, if one of the parent controls provides enough space, so that it hasn't the need to wrap. This might be caused by a ScrollViewer control.
If you want to prevent TextBlock to grow, and you want it to just fit in the size of the listbox, you should set the width of it explicitly.
In order to change it dynamically, it means not a fix value, but you need to bind it to its proper parent element in the visual tree. You can have something like this:
<ListBox ItemsSource="{Binding MyItems}" Name="MyListBox">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Width"
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollContentPresenter}, Path=ActualWidth}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If it does not work, try to find the proper elements (which has to be binded to what) with the Live Visual Tree in Visual Studio.

Resources