Wpf Xbap TreeView VirtualizingStackPanel bug? - wpf

I have a TreeView with VirtualizingStackPanel on. The TreeView has a "Level 1" TreeViewItem, and I bind this TreeViewItem to a 10k items list. Each children is also another TreeViewItem.
Virtualization works well, and the performance is great, but there is a big issue. Let's say I am at the top of the page and press Ctrl-End to get to the bottom, browser goes blank. It will reappear if I scroll the mouse a bit or resize the browser.
Another big issue is: when i scroll very fast to the middle or the bottom of the tree. let's say I stop at item number 5000. Then I can't expand the child treeviewitem, the browser just
shows nothing until I scroll or resize a bit.
Any help is very much appreciated. Below is the sample xaml & code:
<Page x:Class="WpfBrowserApplication3.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="clr-namespace:WpfBrowserApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1" DataContext="{StaticResource _mainViewModel}">
<Page.Resources>
<s:SingleToCollectionConverter x:Key="_collectionConverter"></s:SingleToCollectionConverter>
<DataTemplate x:Key="_level2Template">
<TreeViewItem Header="{Binding Order}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Order: "></TextBlock>
<TextBox Text="{Binding Order}"></TextBox>
</StackPanel>
</TreeViewItem>
</DataTemplate>
</Page.Resources>
<TreeView VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Standard">
<TreeViewItem Header="Level 1" ItemsSource="{Binding Level2List}" ItemTemplate="{StaticResource _level2Template}"></TreeViewItem>
</TreeView>
</Page>
public class MainViewModel
{
public MainViewModel()
{
Level2List = new List<Level2>();
for (int i = 0; i < 10000; i++)
{
Level2List.Add(new Level2(i));
}
}
public List<Level2> Level2List { get; set; }
}
public class Level2
{
public Level2(int order)
{
Order = order;
}
public int Order { get; set; }
}
I use Visual Studio 2010 with .Net 4. Plus I did notice if I set the Height & Width for the TreeViewItem under _level2Template, the issue is gone. But setting the Height is not an option in my case, cause the Height varies in the real Application.
Updated: It seems quite obvious to me that this issue happened because the height of the child treeviewitem may vary. Perhaps that's the reason why VirtualizingStackPanel is not turned on by default in TreeView, but it is turned on by default in DataGrid & ListBox. Needless to say the Height of a datagrid or listbox item is usually unchanged.
Updated: I downloaded a free trial of telerik RadTreeView and tested the virtualization. This problem does not appear at all in telerik radtreeview. May test the telerik one a little more then probably go with it then.

Found this: TreeView Virtualization
Same issue, and no solution for TreeView. Only way around is to use the ListBox instead of TreeView as suggested in http://www.beacosta.com/blog/?p=45
Pretty sure this is TreeView bug. I have tried Telerik RadTreeView and it also has its own bug when turn on Virtulization. I will change to use ListBox instead.

Related

Xaml: design a layout with dynamically visible components

In a mvvm application some areas inside a window (in reality it is a UserControl inside MainWindow) are dynamically displayed according to the user selections.
The changing blocks are inside Stackpanels, I have 4 of them and only one at a time is displayed. This is accomplished binding Visibility to a bool property and using the BooleanToVisibilityConverter.
I put all the alternate StackPanel inside parent control. It works correctly, but during design phase in Visual Studio I see all of them, so I have problems in figuring the final layout.
How can I easily create the layout having more controls which share the same window area and are displayed one at a time ?
Setting A Design Time Only Data Context
Developing XAML in the Studio Designer can be greatly simplified by setting the Design-Time Data Context.
One implementation is based on setting a duplicate DataContext which will be ignored during the final compilation.
To implement the switching, add to the ViewModel, a property that will inform the designer whether it can be used in Development Mode or not.
I use an MVVMLight situation for this example, but for this declared instance property IsInDesignMode and static property ViewModelBase.IsInDesignModeStatic.
Example:
using System.ComponentModel;
namespace DataContextDesignTime.Example
{
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _flag;
public bool Flag
{
get => _flag;
set
{
if (!Equals(_flag, value))
{
_flag = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Flag)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NotFlag)));
}
}
}
public bool NotFlag => !Flag;
}
}
<Window x:Class="DataContextDesignTime.Example.ExamleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataContextDesignTime.Example"
mc:Ignorable="d"
Title="ExamleWindow" Height="450" Width="800">
<d:Window.DataContext>
<local:MyViewModel Flag="True" NotFlag="True"/>
</d:Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<Border Background="LightBlue" Height="200"
Visibility="{Binding Flag, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Border Background="LightGreen" Height="400"
Visibility="{Binding NotFlag, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
</Window>
In this example, you can change property values in XAML or in the Property Browser.
And you will immediately see the work of your bindings, triggers, how the display for certain data changes.
Note
This may fail on more complex VMs/packages, but in general by setting the DataContext at design time is not difficult.
I need to recompile the project to see the changes in the properties.
The XAML Designer panel has an «Enable/Disable Project Code» button.
, but during design phase in Visual Studio I see all of them, so I have problems in figuring the final layout.
This problem is easily resolved by bringing up the Document Outline tab in visual studio. Once open, navigate to the visible tree and toggle the eyeball to visibly hide/unhide the control[s] one is not interested in; during design time only.

TabControl becoming very laggy

We're using a Tabcontrol to display a number of items with rather expensive content, and the issue we're having is that as you iterate over the tabs (selecting them one by one), the responsiveness of the application becomes slower and slower.
This behavior is unexpected as from what I understand, as the selected tab changes, the previously selected tabs content is unloaded first, so that you're only paying the price for one tabs content at a time.
I've managed to simulate the behaviour with the code below. To reproduce :
Run the application
Launch the selected tabs contextmenu (the tabs header contextmenu), it will be responsive
From left to right, go through each tab, selecting one by one
By the time you reach tab ~10, the responsiveness of its contextmenu is now very laggy, as you click a checkbox, its animation takes a few seconds to run through
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style TargetType="{x:Type TabItem}"
BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<CheckBox Content="CheckBox" />
<CheckBox Content="CheckBox" />
<CheckBox Content="CheckBox" />
<CheckBox Content="CheckBox" />
<CheckBox Content="CheckBox" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<TabControl Name="tabControl" />
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 25; i++)
{
CreateTab();
}
}
void CreateTab()
{
var itemsControl = new ItemsControl();
for (int i = 0; i < 1000; ++i)
{
itemsControl.Items.Add(new TextBox());
}
tabControl.Items.Add(new TabItem()
{
Header = string.Format("Tab{0}", tabControl.Items.Count),
Content = itemsControl
});
}
}
I am not sure about your complex scenario what you have but for posted sample, issue is not in tabControl but instead in ItemsControl.
ItemsControl by default does not support UI virtualization, you have to make it UI virtualized i.e. whenever TabItem gets loaded, all UI containers to host items will be created i.e. 1000 items will be created.
You can verify that by replacing ItemsControl with ListBox and you can see considerable increase in performance because ListBox by default support UI virtualization and only containers for visible items will be created (may be 100 at a time).
Replace
var itemsControl = new ItemsControl();
with
var itemsControl = new ListBox();
and you will see difference in performance.
In case you want some performance with ItemsControl, you have to make it UI virtualized. Refer to the answer here to make it UI virtualized.
UPDATE
For comment:
The problem is that the application becomes slower and slower as you
select different tabs. This is unexpected. Due to each item being
unloaded before loading a new item and due to each item having the
same content, I'd expect the responsiveness to remain constant.
Yeah you are right that Unloaded event gets called for content of last selected tab item but it only disconnect ItemsControl from Visual Tree. However its containers remains intact and remains in memory. So, with every switch new containers are getting created in memory. That I guess is fair reason for sluggishness of your application.
That you can verify by hooking onto StatusChanged event:
itemsControl.ItemContainerGenerator.StatusChanged += (s, e) => { };
You will see that it gets called twice on every switch to new tabItem but doesn't gets called on switch to already visited tabItem.

TreeView Issue with expanding non-selected item

I have an application with a treeview and a textbox:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="550" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TreeView Width="500" Height="500" Name="TestTreeView">
</TreeView>
<TextBox Grid.Row="1"></TextBox>
</Grid>
</Window>
In the constructor for the application I generate 1000 items, each with 1 sub-item and add it to the TreeView:
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 1000; i++)
{
TreeViewItem item = new TreeViewItem();
item.Header = "Hey there " + i.ToString();
TreeViewItem subItem = new TreeViewItem();
subItem.Header = "Hi there";
item.Items.Add(subItem);
TestTreeView.Items.Add(item);
}
}
In my scenario, I select the second item of the TreeView, then click into the TextBox to take focus away from the TreeView. I then scroll down the TreeView with my mouse, bringing the selected item out of the viewport. I then expand another item without selecting it. My expected result is that my scroll stays in its current position, however instead it scrolls the selected item back into view, causing me to lose the item I was expanding.
This doesn't occur if the TreeView already has the focus. If I were to select an item, then scroll down and expand another item this behaviour of jumping back to the selected item won't happen.
Is this normal behaviour? If I perform these same selection steps in the Solution Explorer of Visual Studio, for instance, I don't get this kind of behaviour. Is there some way to tell it not to scroll back to the selected item like this?
I understand that I can simply set the e.Handled of the RequestBringIntoView to true, however the sample I'm giving is just a simple explanation of my problem. This is an example of an issue with a TreeView I'm using in a much bigger application where I do want the item brought into view under certain conditions.
The issue is related to the logical scope concept called FocusScope.
What you want is to set IsFocusScope for your TreeView to true:
<TreeView Width="500" Height="500" Name="TestTreeView" FocusManager.IsFocusScope="true">
</TreeView>
Here is an article about it http://www.codeproject.com/Articles/38507/Using-the-WPF-FocusScope
Here is the .net 4.5 doc for it: http://msdn.microsoft.com/en-us/library/aa969768.aspx
As it explains it better than I can and which may be more up to date then the codeproject article

MVVM way to use different controls for editing different objects

I need to design a form with a treeview in the left and a container for some other control in the remaining area. Whenever user selects an item in the treeview some custom control appears on the right. For example, let's say that the treeview contains values "Audio settings" and "Video settings", and I have two controls that can be bound to these settings and I want to display them on the form when needed.
Now, from what I've read about MVVM, I shouldn't have properties that will return UserControls or DataTemplates, am I right? It will be messing with "VM shouldn't know implementation details of the view" as I see it. So, how do I handle this situation properly in terms of MVVM? Should I maybe use converters for this and if so, how would it look?
I can't provide any code at the moment (mostly because there isn't any), but I will try to clarify the problem if needed.
Thanks in advance.
This is where the WPF templating system helps out.
The main idea is to have a ContentControl display the appropriate view depending on the selected value in the TreeView.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<ListBox DockPanel.Dock="Left" ItemsSource="{Binding Editors}" SelectedItem="{Binding SelectedEditor}" />
<ContentControl Content="{Binding SelectedEditor}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type l:VideoViewModel}">
<l:VideoView />
</DataTemplate>
<DataTemplate DataType="{x:Type l:AudioViewModel}">
<l:AudioView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</Window>
AudioView and VideoView are UserControls.
As you can see, the ContentControl's content is bound to the SelectedEditor property in the ViewModel, which is also bound to the SelectedItem of the Listbox.
So the ViewModel for the main view looks like this.
public class MainWindowViewModel : INotifyPropertyChanged
{
public IEnumerable<object> Editors
{
get
{
yield return new VideoViewModel();
yield return new AudioViewModel();
}
}
private object selectedEditor;
public object SelectedEditor
{
get { return selectedEditor; }
set
{
if (selectedEditor == value)
return;
selectedEditor = value;
OnPropertyChanged("SelectedEditor");
}
}
}
So you can see that the main ViewModel has no GUI data in it.
To handle hooking up a TreeView to a SelectedItem property in a ViewModel see Data binding to SelectedItem in a WPF Treeview
You can implement it throw two properties: ShowAudioSettings and ShowVideoSettings in ViewModel and bind it on Visibility of your controls.
Also, you can make it with VisualStateManager.

Use UIElements as ItemsSource of ListBox in Silverlight

I've noticed that if you have anything deriving from UIElement as items in a ListBox in Silverlight it renders the object as is and isn't paying any attention to settings of DisplayMemberPath and/or ListBox.ItemTemplate.
For example if you have XAML like this:
<ListBox Width="200" Height="300" DisplayMemberPath="Tag">
<TextBlock Tag="tag1">text1</TextBlock>
<TextBlock Tag="tag2">text2</TextBlock>
<TextBlock Tag="tag3">text3</TextBlock>
</ListBox>
In Siverlight this produces a ListBox with items like this:
text1
text2
text3
However in WPF (and I think this is correct behavior) it lists tags as expected:
tag1
tag2
tag3
If I use objects that aren't inherited from UIElement everything works as expected:
<ListBox Width="200" Height="300" DisplayMemberPath="[0]">
<sys:String>abcde</sys:String>
<sys:String>fgh</sys:String>
</ListBox>
Produces:
a
f
Is there any way to use UIElements as ItemsSource in Silverlight the same way as any other objects? Or am I missing something?
It looks like the issue is in the PrepareContainerForItemOverride method in ItemsControlBase class. If you look at that method in reflector you will see that if the item is a UIElement then the logic to populate the items using the DisplayMemberPath doesn't get called.
If you want to get the behavior you are after you would need to subclass the ListBox control and override this method and set the values you want set on the ListBoxItems.
Here is an example:
public class MyListBox : ListBox
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
if (!object.ReferenceEquals(element, item))
{
ContentControl control = element as ContentControl;
if (control == null || this.ItemTemplate == null)
{
return;
}
control.Content = item;
control.ContentTemplate = this.ItemTemplate;
}
}
}
And you need to have an ItemTemplate for this to work. The DisplayMemberPath property is a little more complicated to implement.
<local:MyListBox Width="200" Height="300" DisplayMemberPath="Tag">
<local:MyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Tag}" />
</DataTemplate>
</local:MyListBox.ItemTemplate>
<TextBlock Tag="tag1">text1</TextBlock>
<TextBlock Tag="tag2">text2</TextBlock>
<TextBlock Tag="tag3">text3</TextBlock>
</local:MyListBox>
Don't forget to add the xmlns for the local and set it to your assembly that implements the control.
Good luck!
Silverlight and WPF both are differently coded by microsoft, for example yet lot of functionalities of dependency properties are still missing in silverlight 3.0
Now looking at your code, simply means that DisplayMemberPath in silverlight isnt working correctly for dependency objects, but it works better for pure clr objects only for now. However they might come up with an update if you post bug at microsoft connect web site.
Dependency properties are still new in SL 3.0 so we hope to see some improvement in SL 4.0. If you use reflector, you will see that everything like stackpanel and all basic controls differe a lot in implementation in both places.

Resources