I am creating a resource-intensive dashboard application that will have many areas of data visualization. I am thinking that it would be best to use a frame and load the pages needed one at a time using WPF pages. These pages will also have different data contexts, security restrictions, etc. But, another developer says I can accomplish the same thing using a TabControl.
Does a TabControl load all the items in all the tabs at once, on application startup? Or, can I load them lazily as needed like with WPF pages (page only loads content when navigated to)? Also, can you have different data contexts per each item in a TabControl?
In WPF you can use UI Virtualization which means that only the visible controls are initialized and rendered. As far as I know, the TabControl does not support UI Virtualization by default but maybe you can add it manually or use another control. Maybe you want to have a look at the following article which presents some performance tips. There is also mentioned that there is a difference between UI and Data Virtualization. Not showing the controls does not mean that the underlying data are not in memory. All your binding targets will be loaded, but the controls will not be rendered.
To your second question: Yes, every TabItem can have its own DataContext. If you use a TabControls ItemsSource to bind a list of items, the DataContext for every TabItem will be one item of the list. If you manually add TabItems, you can set the DataContext like that:
<TabControl>
<TabItem DataContext="{Binding Context1}" />
<TabItem DataContext="{Binding Context2}" />
</TabControl>
It is more complex than you would guess. If you bind to Tab Collection (think MVVM) then the tab only get created when it is selected. And with a Collection if you leave a tab and come back it gets built AGAIN. If you create the tabs in XAML then the tabs are all built when the windows loads. Yes you can have different DataContext for each tab. What I do for lazy loads is bind to the TabItem property IsSelected and if it is false all the Properties in the class just return a (fast) static type compliant value. If IsSelected is changed to true then I load the real values and call NotifyPropertyChanged (and I save the real values).
I use the heck out of this were I load a big objects and one tab is a summary. Tabs do not virtualize but if you have big lists then for sure use virtualization in the tab. You can use BackgroundWorker to create properites but once it returns and you bind that returned value the UI locked until the UI control is rendered. For me reuse of a single frame versus tabs is a UI thing. Just to break up code I typically load a tab with a frame and a page (and I typically pass data to the page in the ctor to load dynamic content).
Related
I've been working to improve scrolling performance for an ItemsControl. Initially each item is a simple row of information and when an item is clicked, a detail UserControl is expanded below the item. I'm trying to eliminate the processing being done on the detail UserControl during scrolling. I eventually achieved this with a CustomControl, but I feel like I must be missing a simpler way to do it using the existing framework controls.
Initially, this detail view was a UserControl with Visibility.Collapsed. I should note that Virtualizing and Recycling are enabled for this ItemsControl. Therefore, scrolling performance was poor since each detail view was being bound to the data as the item scrolled into view, even though the detail was not visible.
I then tried using a ContentPresenter where the Content was bound to a DetailViewModel property and the ContentTemplate was set to a keyed DataTemplate. That DetailViewModel property was initialized to Nothing and then set to the appropriate ViewModel object when the item was clicked. This improved scrolling performance because there was no data binding, but I found that the detail UserControl was still getting constructed for each item, and I presume there was some rendering going on as well.
I eventually got the desired behavior with a custom ContentControl that provides dependency properties for GatedContentTemplate and IsGateOpen. When IsGateOpen goes True, the GatedContentTemplate is passed to the ContentTemplate property and the Content is set to the DataContext of the control, which causes the content to then get constructed, rendered, and bound.
<my:GatedContentControl IsGateOpen="{Binding IsDetailVisible}">
<my:GatedContentControl.GatedContentTemplate>
<DataTemplate>
<my:DetailUserControl/>
</DataTemplate>
</my:GatedContentControl.GatedContentTemplate>
</my:GatedContentControl>
I can live with this solution, but it's a little janky, and I wonder if I'm missing some way that WPF intends for this to be done.
Looks like I wasn't missing anything and this is a need that Microsoft is addressing in .net 4.6.
The Content Deferral feature is discussed in this video, starting at 26:30.
http://channel9.msdn.com/Events/dotnetConf/2015/WPF-in-46-and-beyond
Thanks for the comment HighCore.
I have a user control that has a list of items.
If an item is selected, I want to show/replace the view with a panel and some buttons and what not.
When the user has finished with this view by pressing a button or something, I want to revert back to the list again and continue the process.
Essentially, this is a wizard.
What to do?
Sorry, I forgot to mention that I am using MVVM.
Solution:
Thanks to all for the help. My test application wasn't working which prompted me to ask SO.
My test application wasn't working because I had missed the {x:type} in the DataTemplate.
To simply:
I created different datatemplates in the resources with the {x:Type}
My viewmodel:
contained a compositecollection of IWizardPageViewModel.
contained a currentPage property.
contained NextCommand/BackCommand to change the currentPage
I bind the currentPage property to the control and the datatemplates take over.
Because of the {x:Type} it wasn't working.
I don't know whether this is right or wrong, but it works and is mostly controlled by the viewmodel rather than triggers on the view.
Consider a list of usercontrols - one for each page of your wizard. The top level usercontrol (the wizard control) will own this list. For navigation, you can;
Have buttons on the top level wizard usercontrol. When pressed, these notify the children of the navigation so that they can finish their work or cancel the navigation. You will want a common interface for the pages. IWizardPage perhaps?
Use a routed commands to notify the wizard usercontrol http://msdn.microsoft.com/en-us/library/ms752308.aspx#Four_main_Concepts
You can use Triggers and Selectors to update the view (DataTemplate) based on user actions.
Let me know if you need code snippet for the same.
First thing that comes to my mind (and easiest) is to use Visibility property and bind it to some boolean flags in ViewModel that will indicate current UI state. Of course in this case you should apply a Converter to properly convert bool value to Visibility. There are dozens of examples of such kind of convertors.
But this is relevant only in for of small amount of such Controls. In case of really lots of UI elements that should be shown and replaced on a view it's better to use framework like Prism. From scratch it will be not so straightforward, but then you'll feel all power of flexibility.
In case you're not following MVVM culture (or you don't like having such backing properties) you can bind Visibility property of control A that should be shown to Booleaen property of element B which stands for show/hide logic. To make it clear:
<TextBlock x:Name="A" Visibility="{Binding IsChecked, ElementName=B, Converter={StaticResource boolToVisibilityConverter}}" Text="Some text."/>
<ToggleButton x:Name="B" IsChecked="False"/>
Is there a way to get Silverlight databound controls to load in the background to shorten load times during another part of application use? Specifically, I have a tab control containing a datagrid that is slow to load when there are large number of columns and rows. The performance hit occurs the first time I click the tab. Is there a way to force this load on a background thread when app first opens or something similar?
Not sure this is exactly relevant but I just resolved an issue I had where I was firing up a new grid (which was already loaded but not visible). In the process of making it visible I also assign the ItemSource of a datagrid inside which - via a converter - generates controls. What I found was that although the datagrid in silverlight typically only loads rows it needs to (based on visibility) in my case the code sequence to show the grid and bind was happening too quickly and because the grid wasn't yet shown it (silverlight) decided it needed to load all the rows.
Calling UpdateLayout() prior to generating the controls and binding resolved the issue.
I have the following components in a WPF application:
(1) Window
(2) ContentPresenter in the Window that is bound to a property in the underlying ViewModel. This Property references another ViewModel.
(3) A DataTemplate for the ViewModel that will be bound to the ContentPresenter referenced above. This data template instantiates a third-party grid that displays some data.
Whenever the ContentPresenter renders the data from the DataTemplate, it takes approximately three to four seconds for the UI to render. This causes the UI to hang for the duration of the time that it takes to render the content. Since I have little to no control over how the third-party control renders itself - my question involves whether or not it is possible to render content in a way that the UI will not hang.
Please advise.
Thanks.
Chris
How many rows is the grid displaying? And how many of those rows are visible on screen?
I'm asking because it's possible that you've got a UI layout that defeats virtualization. Usually, controls that show a scrollable list of data will perform virtualization. (The built-in ListBox does this, and any 3rd party grid of tolerable quality should do the same.) This is critical for performance, because it means your UI only needs to instantiate those items that are actually visible, rather than everything in your list.
But it's relatively easy to defeat this virtualization by accident. One way is to wrap the list or grid control in a ScrollViewer. You need virtualizing controls to be able to manage their own scrolling for virtualization to work, so the scrolling needs to happen on the inside. Wrapping a control in a ScrollViewer prevents it from doing its own scrolling. Another way it can go wrong is if you plug in a different ItemsPanel. A third possibility is that your list/grid control actually needs to be told to use virtualization.
But if you're using a control that simply takes a long time to render just the stuff you need to show on screen, then there's not much you can do - you'd need to contact the control vendor, or consider using a different vendor...
I am binding my ContentPresenter to a ViewModel that has a type-referential DataTemplate which contains an instance of a third-party control (DevExpress' GridControl). When this control is bound to a modestly sized collection (i.e. 1000 items), the control takes a noticable four or five seconds to load. So, to my question - for controls that take a while to render, can this somehow be done using a BackgroundWorker such that the UI doesn't hang? Keep in mind that I my controls reside in a DataTemplate, so any code-behind is not a desirable option.
Thanks!
Unfortunately creating the actual UI (in your case, creating, positioning, and rendering controls) must be done on the UI thread due to compatibility constraints - all UI components must be created by the UI thread, and they can only be altered by the UI thread as well.
One thing I would recommend looking at is virtualization - if you're not displaying the datatemplate of 1000 items, why create all of the controls? You can find examples around like Virtualized WPF Canvas, or using the built-in VirtualizingStackPanel. Using these techniques will be more work than simply binding a viewmodel to an item with a datatemplate, but will give much, much better performance.
If the actual issue is just that the DevExpress GridControl is super slow with 1000 items, then you'll want to see if you can set it to a virtual mode, or switch to a different 3rd party control.
Your ItemsControl should contain a VirtualizingStackPanel which ListView and ListBox do, but make sure that virtualisation is switched on and you use container recylcling
<ItemsControl
VirtualizingStackPanel.IsVirtualizing="true"
VirtualizingStackPanel.VirtualizationMode="Recycling">