How to enumerate controls in TabItem in WPF?

C # XBap Application

I have a TabControl with four TabItems. Two of these TabItems simply contain a DataGrid from WPFToolkit, which pulls a fairly small amount of data from the SQL Server database (100 rows of 4 columns). My problem is that when I download my application and I click on one of the TabItems containing the datagrid. It seems to me that there is a pause for 2-3 seconds before this tab is focused. This only happens the first time the tab is clicked. This seems to be a datagrid rendering.

How can I make these tabs preerender when the application loads, so when the user clicks on the tab, there is no initial initial pause 2-3 seconds before the tab appears.

thanks

+7
c # wpf tabcontrol tabitem
source share
4 answers

We use the standard WPF TabControl, and the problem is that every time you change SelectedItem, VisualTree goes through it.

As a result, we created a special TabControl (I called it TabControlEx), which saves all the displayed elements, but prefers to just show / hide ContentPresenters for TabItems.

Here is the relevant code

using System; using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; namespace MVVM.Demo { /// <summary> /// The standard WPF TabControl is quite bad in the fact that it only /// even contains the current TabItem in the VisualTree, so if you /// have complex views it takes a while to re-create the view each tab /// selection change.Which makes the standard TabControl very sticky to /// work with. This class along with its associated ControlTemplate /// allow all TabItems to remain in the VisualTree without it being Sticky. /// It does this by keeping all TabItem content in the VisualTree but /// hides all inactive TabItem content, and only keeps the active TabItem /// content shown. /// </summary> [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] public class TabControlEx : TabControl { #region Data private Panel itemsHolder = null; #endregion #region Ctor public TabControlEx() : base() { // this is necessary so that we get the initial databound selected item this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; this.Loaded += TabControlEx_Loaded; } #endregion #region Public/Protected Methods /// <summary> /// get the ItemsHolder and generate any children /// </summary> public override void OnApplyTemplate() { base.OnApplyTemplate(); itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel; UpdateSelectedItem(); } /// <summary> /// when the items change we remove any generated panel children and add any new ones as necessary /// </summary> /// <param name="e"></param> protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (itemsHolder == null) { return; } switch (e.Action) { case NotifyCollectionChangedAction.Reset: itemsHolder.Children.Clear(); break; case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Remove: if (e.OldItems != null) { foreach (var item in e.OldItems) { ContentPresenter cp = FindChildContentPresenter(item); if (cp != null) { itemsHolder.Children.Remove(cp); } } } // don't do anything with new items because we don't want to // create visuals that aren't being shown UpdateSelectedItem(); break; case NotifyCollectionChangedAction.Replace: throw new NotImplementedException("Replace not implemented yet"); } } /// <summary> /// update the visible child in the ItemsHolder /// </summary> /// <param name="e"></param> protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); UpdateSelectedItem(); } /// <summary> /// copied from TabControl; wish it were protected in that class instead of private /// </summary> /// <returns></returns> protected TabItem GetSelectedTabItem() { object selectedItem = base.SelectedItem; if (selectedItem == null) { return null; } TabItem item = selectedItem as TabItem; if (item == null) { item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; } return item; } #endregion #region Private Methods /// <summary> /// in some scenarios we need to update when loaded in case the /// ApplyTemplate happens before the databind. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void TabControlEx_Loaded(object sender, RoutedEventArgs e) { UpdateSelectedItem(); } /// <summary> /// if containers are done, generate the selected item /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; UpdateSelectedItem(); } } /// <summary> /// generate a ContentPresenter for the selected item /// </summary> private void UpdateSelectedItem() { if (itemsHolder == null) { return; } // generate a ContentPresenter if necessary TabItem item = GetSelectedTabItem(); if (item != null) { CreateChildContentPresenter(item); } // show the right child foreach (ContentPresenter child in itemsHolder.Children) { child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; } } /// <summary> /// create the child ContentPresenter for the given item (could be data or a TabItem) /// </summary> /// <param name="item"></param> /// <returns></returns> private ContentPresenter CreateChildContentPresenter(object item) { if (item == null) { return null; } ContentPresenter cp = FindChildContentPresenter(item); if (cp != null) { return cp; } // the actual child to be added. cp.Tag is a reference to the TabItem cp = new ContentPresenter(); cp.Content = (item is TabItem) ? (item as TabItem).Content : item; cp.ContentTemplate = this.SelectedContentTemplate; cp.ContentTemplateSelector = this.SelectedContentTemplateSelector; cp.ContentStringFormat = this.SelectedContentStringFormat; cp.Visibility = Visibility.Collapsed; cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); itemsHolder.Children.Add(cp); return cp; } /// <summary> /// Find the CP for the given object. data could be a TabItem or a piece of data /// </summary> /// <param name="data"></param> /// <returns></returns> private ContentPresenter FindChildContentPresenter(object data) { if (data is TabItem) { data = (data as TabItem).Content; } if (data == null) { return null; } if (itemsHolder == null) { return null; } foreach (ContentPresenter cp in itemsHolder.Children) { if (cp.Content == data) { return cp; } } return null; } #endregion } } 

If you want to create a template of this type (you may need to expand it for Left / Right TabStripLocation)

 <ControlTemplate x:Key="MainTabControlTemplateEx" TargetType="{x:Type controls:TabControlEx}"> <Grid> <Grid.RowDefinitions> <RowDefinition x:Name="row0" Height="Auto"/> <RowDefinition x:Name="row1" Height="4"/> <RowDefinition x:Name="row2" Height="*"/> </Grid.RowDefinitions> <TabPanel x:Name="tabpanel" Background="{StaticResource OutlookButtonHighlight}" Margin="0" Grid.Row="0" IsItemsHost="True" /> <Grid x:Name="divider" Grid.Row="1" Background="Black" HorizontalAlignment="Stretch"/> <Grid x:Name="PART_ItemsHolder" Grid.Row="2"/> </Grid> <!-- no content presenter --> <ControlTemplate.Triggers> <Trigger Property="TabStripPlacement" Value="Top"> <Setter TargetName="tabpanel" Property="Grid.Row" Value="0"/> <Setter TargetName="divider" Property="Grid.Row" Value="1"/> <Setter TargetName="PART_ItemsHolder" Property="Grid.Row" Value="2" /> <Setter TargetName="row0" Property="Height" Value="Auto" /> <Setter TargetName="row1" Property="Height" Value="4" /> <Setter TargetName="row2" Property="Height" Value="*" /> </Trigger> <Trigger Property="TabStripPlacement" Value="Bottom"> <Setter TargetName="tabpanel" Property="Grid.Row" Value="2" /> <Setter TargetName="divider" Property="Grid.Row" Value="1" /> <Setter TargetName="PART_ItemsHolder" Property="Grid.Row" Value="0" /> <Setter TargetName="row0" Property="Height" Value="*" /> <Setter TargetName="row1" Property="Height" Value="4" /> <Setter TargetName="row2" Property="Height" Value="Auto" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> 

What could you use like this

 <local:TabControlEx IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Workspaces}" Template="{StaticResource MainTabControlTemplateEx}"> </local:TabControlEx> 

It works very well, and we use it with great effect for a long time.

+6
source share

The problem is not getting the data (you can preload this in a separate stream), but actually creating the visual elements in the datagrid.

If you check with Snoop, you will see that there are many visual elements if you do not need all the functionality of a datagrid that you could use with a simpler view (ListView / ItemsControl / Custom)

0
source share

Load the contents of the tab dynamically when choosing to make the user interface responsive, use code similar to the one below:

  private void tab_Selected(object sender, EventArgs e) { //Get the selected tab Action loadTab = delegate { LoadSelectedTab(tabItem); } Dispatcher.BeginInvoke(DispatcherPriority.Background, loadTab); } public void LoadSelectedTab(TabItem item) { item.Content = new EmployeeTab(); ..... } 

UI replay will be very fast, UI will start loading very fast and you will not see a pause for any delays

0
source share

I can not add comments, but I want to thank sacha for the answer and expand it a little. There was a problem loading this TabControl, so the first tab was not displayed (not in the visual tree). Adding the following code to the Public / Protected Methods area solves the problem.

  /// <summary> /// There was a flaky issue when first tab was uninitialized /// </summary> /// <param name="oldValue"></param> /// <param name="newValue"></param> protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { base.OnItemsSourceChanged(oldValue, newValue); UpdateSelectedItem(); } 
0
source share

All Articles