Expand TreeViewItems in wpf fast - wpf

I know how to expand all nodes from a treeview:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="TreeViewItem.IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
the only problem is that it takes about a minute to expand all nodes... that's probably because there are so many items. How could I speed up this process?
Edit:
So I have a list: List<ScanItems> MyFilesList
ScanItem is a class that has properties such as: FullName, Name, Size, DateCreated, ComparePath and other specific properties that I need that's why I did not used the FileInfo class.
ScanFile is a class that inherits from ScanItems so it is just like it with the addition of other custom properties.
ScanDir is another class that inherits also from ScanItem and it has the following property:
public List<ScanItem> items{get;set;}
the reason why I have included that property is so that I can have another list withing an item.
Look at this question regarding how to populate a treeview from a list of files.
so now I hope I explain my self correctly on how I am binding that list to the treeview.
Now let me explain how I added the files to MyFilesList. I created a recursion method to look for files in a directory. If the curent directory contained a file then add a ScanFile item. If it contained a Folder then add a ScanDir object and call the same method again passing the list of ScanDir. So this process takes about 8 seconds to scan my external hard drive. after that method get executed my list may contain just 4 items but one of those items will contain a list of maybe 20 items and so forth just like a folder may have 5 items and if one of those 5 items happens to be a folder that folder can have additional items.
So when I execute TreeView.DataContext = MyFilesList the treeview gets populated in less than a second. But when I include:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="TreeViewItem.IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
that style inside the treeview the treeview takes to long to load.

Take a look at the posts made by Bea Stollnitz about treeviews and performance.
Relevant post would be:
How can I expand items in a TreeView? Part I / Part II / Part III
Are there any tricks that will help me improve TreeView’s performance? Part I / Part II / Part III
She does a good job explaining in detail all of the options you can try out. Too much content to put it all here.

Have you tried looping through the treeviewitems and expanding them "manually" by setting
IsExpanded = true;
If that doesn't work then try a work around and add to your ScanDir ( I presume that's the only class which you expand? ) a property IsExpanded (or similar) and bind to it in your template. Not the best of solutions practise wise but if it would work...

Related

WPF MVVM dilemma - Where to put the command that acts on collection item?

Let's say I have the MasterViewModel that contain a collection of ItemModel. If I'd like to add new ItemModel to my collection, I'd put the AddItemCommand in that MasterViewModel. Now, if I'd like to display a Delete button next to each item, where would I put this command? Surely not in the ItemModel?!
Would I need to create almost identical copy of ItemModel, for example ItemViewModel? That would sound like a lot of prop-ing work, but even then I'm not sure how the command would be able to remove ItemModel from the collection.
Most obvious approach seems to be AddItemCommand and RemoveItemCommand in the MasterViewModel, but how to do data binding in this case? Control's DataContext is switched to a collection so I no longer have visibility of that commands from individual item level. I saw a binding trick that finds the ancestor's DataContext but that looks so hacky or not intuitive to say at least. Is this the preferred solution or there is a better approach?
AddItemCommand definitely belongs in MasterViewModel as it can't be an operation on an individual item.
RemoveItemCommand also belongs in MasterViewModel as it will update the list of items. However, the binding doesn't follow the basic methodology.
Assuming the the button that triggers the command is part of the item template, then you have to use relative binding to locate the command (is this what you referred to as 'hacky' ?)
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.RemoveItemCommand}"
You have to tell the command which item is to be deleted
CommandParameter="{Binding}"
and make use of this parameter in the method corresponding to your command.

Custom control theme disappear on rebuild

I am busy building a custom control. The Theme has a Generic.xaml that consist of a MergedDictionary referencing 2 resource dictionaries (1 - Generic brush and 2 - ControlTemplate) which is situated in a Generic folder.
I have a tester project (standard Window) added to the solution to test the control.
Issue:
When I add the control for the first time - the look of the control looks correct. However when I do some changes on the control and rebuild it - the control disappear from the window and I have to Unload the Window project and Reload it again to make the control's look reappear again.
When I run debugger the control does appear correctly - it is just in design mode that it becomes difficult to work with.
Is there a solution / workaround for this occurrence that does not involve unloading and reloading the window on each rebuild?
EDIT
I have run a test where I copied all the info from separate resource dictionaries into the Generic.Xaml and commented out the Merge Dictionary. It seems the issue does not lie with MergeDictionary operations as the problem is still there - but perhaps with ComponentResourceKey / or static properties. One of my ResourceDictionaries for instance contain a lot of the following
<SolidColorBrush x:Key="{x:Static keys:Disabled.ForeGroundKey}" Color="Gray"/>
Where ForeGroundKey is linked to a static class with for example:
public static class Normal
{
static ComponentResourceKey _background = new ComponentResourceKey(typeof(G2ListBox),"ContainerBackground");
public static ComponentResourceKey BackGroundKey
{ get { return _background; } }
}
I guess seeing that the theme work sometimes mean that there is nothing wrong with the above approach and there is something wrong with how VS handles the rebuild of the control. What I do not understand though is why doesn't VS recognize either the old values / new values, instead it ignores all values linked to ComponentResourceKey - Ps. during runtime the control works perfectly.
Well it seems like I have 3 options here.
Use the method I have been using all along:
Define ComponentResourceKey in C# Class
public static class Normal
{
static ComponentResourceKey _background = new ComponentResourceKey(typeof(G2ListBox), "Normal.Background");
public static ComponentResourceKey BackGroundKey
{ get { return _background; } }
}
Assign Key in Resource Dictionary
<SolidColorBrush x:Key="{x:Static keys:Normal.BackGroundKey}" Color="Yellow"/>
Use Key in Resource Dictionary:
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static keys:Normal.BorderBrushKey}}"/>
The advantage is that when you have multiple themes to assign and use the resource key seems simplistic and chances of errors occurring in typing over the name is reduced as you have the help of intelisence.
The disadvantage is that when you are working on a large project, and you make a small adjustment to your control - none of the ComponentResourceKeys are loaded and your project looks completely lookless at design time. To fix this issue, one either has to reboot VS or unload the project that uses your control and reload it again. Ps this is only at design time. Running the project will give the correct result. This is a silly problem to have in VS!!
return to the more verbose method of defining Component resource keys in XAML i.e.
Define and Assign the resourceKey.
<SolidColorBrush x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:G2ListBox}, ResourceId=Normal.Background}" Color="Yellow"/>
Use the resourceKey:
<Setter Property="BorderBrush" Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type local:G2ListBox}, ResourceId=Normal.Background}}"/>
The Advantage is VS is now working everytime in design time. Also you do not have to create a separate C# class to hold all your resourcekeys
The Disadvantage is that you have to remember the ResourceId names for each resourceId and type it out as it was defined. Also using this in a control with multiple themes becomes frustrating.
Use a mixture of the 2 above i.e.
You still Define the ResourceKey in C# library
You Assing the ResourceKey as per method 2. But the ResourceId is the "text" field assigned in the C# class and not the x:static method i.e.
public static class Normal
{
static ComponentResourceKey _background = new ComponentResourceKey(typeof(G2ListBox), "Normal.Background");
public static ComponentResourceKey BackGroundKey
{ get { return _background; } }
}
//To assign
<SolidColorBrush x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:G2ListBox}, ResourceId=Normal.Background}" Color="Yellow"/>
//Thus Normal.Background and not Keys:Normal.Background!! where keys = referenced to the C# Class
Then to use
<Setter Property="Background" Value="{DynamicResource {x:Static keys:Normal.BackGroundKey}}"/>
//i.e we now can reference the C# class and have intelisence
The advantage is that 1 you have a static class that hold all the ResourceKeys in C# (goes slightly against lookless philosophy). you also have access to proper intelisence at least in the using side. But best of all VS works perfectly in design time.
This however does not shorten the assign side at all and the disadvantages therefore are still that you need to type out verbose text to assign a color to the resourcekey. Having multiple themes each with its own set of colors means that you have only shorten the Style of the control a bit and seems silly to use this method
Thus if you want the best solution and do not care about the design time look I'd prefer option1.
If you prefer design time visuals then I'd go for method 2 unless you have to define the style at a number of places too, then option 3 will suffice.
Alternatively create a designtime ResouceDictionary and a compile time ResourceDictionary. Where at designtime method 3 is used and compile time method 2 -> Not sure how to do this automatically. I am doing this by use of Merged Dictionaries, and uncommenting the proper dictionary when Control is ready to be compiled, and deleting the designtime ResourceDictionary.
Hope this helps someone, some day as I had to spend the whole day trouble shooting this (I thought there was something wrong with my control - turns out there is something wrong with VS).

WPF : Multiple views, one DataContext

I'm working on a WPF application which must handle multiple screens (two at this this time).
One view can be opened on several screens and user actions must be reflected consistently on all screens.
To achieve this, for a given type of view, a single DataContext is instantiated. Then, when a view is displayed on a screen, the unique DataContext is attached to it. So, one DataContext, several views (same type of view/xaml).
So far so good. It works quite well in most cases.
I do have a problem with a specific view which relies on ItemsControl. These ItemsControl are used to display UIElements dynamically build in the ViewModel/DataContext (C# code). These UIElements are mostly Path objects. Example :
<ItemsControl ItemsSource="{Binding WindVectors}">
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Canvas IsItemsHost="True" />
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
Here, WindVectors is a ObservableCollection<UIElement>.
When the view is opened the first time, everything is fine. The problem is that when the view is opened one another screen, all ItemsControl are removed from the first screen and displayed one the second screen. Other WPF components (TextBlock for instance) on this view react normally and are displayed on both screens.
Any help would be greatly appreciated.
Thanks.
Fabrice
This is the expected behavior (ie been that way since winforms)- this is because the ObservableCollection is a reference. This wont happen with value types, only reference types.
The short answer is 'dont do that'. You could try looking into defining a collection view in the xaml or code a custom data provider and bind to one of those instead.

PRISM + Tabs = Pain

I am having difficulty trying to get a very simple scenario working with PRISM 2.0 for WPF. I want the main workarea of my application to be a TabControl. Each time I add a view, I want it to appear as a TabItem on the TabControl.
Sounds easy right?
My region, which is in my Shell.XAML looks like this:
<Controls:TabControl
Name="MainRegion"
cal:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.TabRegion}"
ItemContainerStyle="{StaticResource ShellTabItemStyle}" />
The style: ShellTabItemStyle looks like this:
<Style x:Key="ShellTabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Content.DataContext.HeaderInfo, RelativeSource={RelativeSource Self}}" />
</Style>
This should set the Header of the TabItem to the HeaderInfo property on the DataContext of the view. (I got this idea from this article) The DataContext of my view is a very simple Presenter which has a HeaderInfo property on it:
public string HeaderInfo = "The Header Text";
My view is a simple WPF usercontrol and looks like this:
<StackPanel>
<TextBox Text="Hello World" Name="MyTextBox"></TextBox>
<Image Source="..SomeImage.PNG" Name="MyImage"></Image>
</StackPanel>
So far, so good. If I add the view to the region I get a tab control and I get a tab with the text set to "The Header Text". My problem is that there is absolutely no content appearing on the tab. My view contains a simple Image and a TextBox, but neither of them show up in the TabItem. If I break out Snoop and look around, there isn't an image in sight.
What am I missing here - is there an easier way?
I was unable to get any of the suggested answers to work. Extensive googling didn't help either. I gave the problem some thought over the weekend and more I thought about it, the more it occured to me that there is a bit of a code smell about this approach. You inject a view into your Tab Region... some magic stuff happens and a tab gets added... you have to add some imcomprehensible dynamic binding to some XAML styling stored in a file somewhere and this may or may not set your header text. If any one single element of this is just a little bit wrong you won't get an error but it just won't work.
In my view this is both brittle (i.e. very easy to break) and pretty inpenetrable unless you have a deep understanding PRISM, the model, and of XAML. Fortunately there is a much nicer and simpler way to do this:
Simply create a view called TabRegionView which contains only a blank TabControl. You probably want to add this to your Shell.xaml. Create an Event called InjectTabView which has a Payload of type UserControl and subscribe to this event in your TabRegionView control. When the event fires in TabRegionView, you create the TabItem manually and add the view to the TabItem like so:
public void TabAdded(UserControl view)
{
var item = new TabItem();
item.Content = view;
item.IsSelected = true;
item.Header = "Header Text";
mainTab.Items.Add(item);
}
When you want to display a view as a new tab, your code looks something like this:
var view = new View(params);
_eventAggregator.GetEvent<InjectTabViewEvent>()
.Publish(view);
This will be picked up by TabRegionView and the view will be added as a new tab. You could easily wrap the View in harness of some type that contains header text, an image and bool to indicate whether or not the tab should be autoselected.
IMHO this technique has the dual advantages of giving you direct control of what is going on AND it is much easier to follow.
I'd be very interested to get an opinion on this from any PRISM officianados.
If you are using RegionManager, you need to Activate your view. There's likely some piece of code where you are adding the view to the region, you just additionally need to tell that region to activate it.
public void AddViewToRegion(IRegion region, object view)
{
region.Add(view);
region.Activate(view);
}
It seems silly, but you get the behavior you are seeing if you don't do this. I found this kinda frustrating, but it was easy enough to fix. The behavior is even stranger when you add multiple tabs if you don't at least Activate the first view you add.
My understanding is that if you don't do this, the view never becomes part of the visual tree (this is a side-effect of the fact that the TabControl deactivates (removes from the visual tree) when a tab isn't "in front". This is good for certain operations, but makes things like this be a little wonky.
Some random thoughts:
try to remove the style from TabControl
check visual tree with help of Snoop tool. You should see your TabItem with view's UserControl under the TabControl. Next you can check what's wrong with that UserControl and its children (your view's content). They are probably hidden for some reason.
other thing to think over - RegionAdapter. RegionAdapters are responsible for adapting regions' views to host UIControl.

Is there an ItemsControl equivalent for text content?

I have some data that I want to present in a FlowDocument. This will basically be a view that explains the data in a friendly way, with section headers, paragraphs of text, etc., and which I will display in a FlowDocumentScrollViewer.
To this end, I would like to create a bulleted list (<List>) out of the contents of an ObservableCollection. I know how to do that with ItemsControl, but how do I do it for ListItem elements in a FlowDocument, since they're part of the TextElement class hierarchy rather than the Control hierarchy? Is there an equivalent of ItemsControl for text content inside a TextBlock or FlowDocument?
Edit: The article Sergey linked to is the perfect starting point. The only problem is that the article's code can only use a Section or a TableRowGroup as the items panel, and doesn't yet support using a <List>. But that was trivial to fix -- just a matter of adding this code at the end of ItemsContent.GenerateContent, just before the final else:
else if (panel is List)
((List) panel).ListItems.Add((ListItem) element);
What you are looking for is possible, but requires significant amount of coding. Fortunately, Vincent Van Den Berghe posted a nice article on the MSDN describing how to Create Flexible UIs With Flow Documents And Data Binding , including the code!
Instead of using a FlowDocument, you can use an ItemsControl and change the panel used to display items to a WrapPanel. This will allow you use the ItemsControl as you want, but change its display semantics to a WrapPanel (which I believe functions like a FlowDocument. You'd do it something like this:
<ItemsControl>
<ItemsControl.ItemsPanelTemplate>
<WrapPanel />
</ItemsControl.ItemsPanelTemplate>
</ItemsControl>
You can set any properties on the inner WrapPanel as you desire.
I think you are looking for the List element:
http://msdn.microsoft.com/en-us/library/system.windows.documents.list.aspx
Bubblewrap points out a few more details. You'd likely bind to the ListItems property and need to use a ValueConverter to convert your source list to a list of type ListItemsCollection.
Bubblewrap points out that this is readonly and that the ListItemsCollection has an internal constructor. So...
I think what you'd have to do is this:
<ContentControl Content="{Binding TheArrayOfText, Converter={StaticResource StringsToListConverter}" />
This is pretty unfortunate, but I think it would work. You'd have to write a converter to create a new List object and call .Add( on each item.

Resources