1

我使用 aCatel作为我的主要 MVVM 框架。在当前应用程序中,我将 UI 拆分为多个选项卡。每个选项卡都在加载适当的View. 虽然我得到了预期的结果,但我注意到通过更改每个选项卡几乎需要几1~3秒钟才能显示视图。有没有办法加快这个过程?

<TabItem Header="Tools">
    <catel:StackGrid>
        <TabControl>
            <TabItem Header="Worker">
                <Grid>
                    <Views:WorkerReportView />
                </Grid>
            </TabItem>

            <TabItem Header="Business">
                <Grid>
                    <Views:BusinessReportView />
                </Grid>
            </TabItem>
        </TabControl>
    </catel:StackGrid>
</TabItem>
4

2 回答 2

3

有很多方法可以提高 Catel 的性能。您绝对应该看看性能注意事项

在最新版本(夜间构建,即将推出的 4.0 版本)中,团队花费了大量时间调整性能并引入了一个名为ApiCop的新功能。此功能将在附加调试器时生成咨询报告,尤其是在加载视图和启用未使用的功能(默认情况下)时。

于 2014-03-01T13:08:54.830 回答
2

我认为,在这种情况下,问题不在Catel. 每次切换选项卡时的标准TabControl卸载和重新加载 VisualTree 。这意味着每次您转到选项卡时,都会重新渲染 View,因此会影响性能,如果 View 很重,则更是如此。Content

如果您为内容Loaded事件处理程序创建,您可以看到这一点,如下所示:

<TabControl>
    <TabItem Header="Worker">                
        <ContentControl Name="Content1" 
                        Loaded="Content1_Loaded" />                    
    </TabItem>

    <TabItem Header="Business">               
        <ContentControl Name="Content2"
                        Loaded="Content2_Loaded"/>
    </TabItem>
</TabControl>

每次在选项卡之间切换时都会触发这些事件。

作为替代方案,您可以尝试TabControl在程序启动时仅加载一次选项卡内容的实现。我在这个答案中找到了一个:

在 MVVM 应用程序中的视图之间导航时如何保留视图的完整状态?

这是清单:

Class TabControlEx

[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class TabControlEx : TabControl
{
    private Panel ItemsHolderPanel = null;

    public TabControlEx()
        : base()
    {
        // This is necessary so that we get the initial databound selected item
        ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
    }

    /// <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>
    /// Get the ItemsHolder and generate any children
    /// </summary>
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        ItemsHolderPanel = 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 (ItemsHolderPanel == null)
            return;

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Reset:
                ItemsHolderPanel.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)
                            ItemsHolderPanel.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");
        }
    }

    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);
        UpdateSelectedItem();
    }

    private void UpdateSelectedItem()
    {
        if (ItemsHolderPanel == null)
            return;

        // Generate a ContentPresenter if necessary
        TabItem item = GetSelectedTabItem();
        if (item != null)
            CreateChildContentPresenter(item);

        // show the right child
        foreach (ContentPresenter child in ItemsHolderPanel.Children)
            child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
    }

    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));
        ItemsHolderPanel.Children.Add(cp);
        return cp;
    }

    private ContentPresenter FindChildContentPresenter(object data)
    {
        if (data is TabItem)
            data = (data as TabItem).Content;

        if (data == null)
            return null;

        if (ItemsHolderPanel == null)
            return null;

        foreach (ContentPresenter cp in ItemsHolderPanel.Children)
        {
            if (cp.Content == data)
                return cp;
        }

        return null;
    }

    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;
    }
}

Style

为了更好地显示,我对其进行了一些修改:

<Style TargetType="{x:Type this:TabControlEx}">
    <Setter Property="Background" Value="Transparent" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabControl}">
                <Grid Background="{TemplateBinding Background}" 
                        ClipToBounds="True" 
                        KeyboardNavigation.TabNavigation="Local"
                        SnapsToDevicePixels="True">

                    <Grid.ColumnDefinitions>
                        <ColumnDefinition x:Name="ColumnDefinition0" />
                        <ColumnDefinition x:Name="ColumnDefinition1" Width="0" />
                    </Grid.ColumnDefinitions>

                    <Grid.RowDefinitions>
                        <RowDefinition x:Name="RowDefinition0" Height="Auto" />
                        <RowDefinition x:Name="RowDefinition1" Height="*" />
                    </Grid.RowDefinitions>

                    <DockPanel Margin="2,2,0,0" 
                                LastChildFill="False">

                        <TabPanel x:Name="HeaderPanel" 
                                    Margin="0,0,0,-1"
                                    VerticalAlignment="Bottom"
                                    Panel.ZIndex="1" 
                                    DockPanel.Dock="Left"
                                    IsItemsHost="True" 
                                    KeyboardNavigation.TabIndex="1" />
                    </DockPanel>

                    <Border x:Name="ContentPanel"
                            Grid.Row="1" 
                            Grid.Column="0"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            KeyboardNavigation.DirectionalNavigation="Contained"
                            KeyboardNavigation.TabIndex="2" 
                            KeyboardNavigation.TabNavigation="Local">

                        <Grid x:Name="PART_ItemsHolder" 
                                Margin="{TemplateBinding Padding}" 
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Some notes

就个人而言,我总是使用DataTemplateas View。因为 UserControl 是一个“重量级”控件,所以它变得更加动态并且对性能的影响更小,我意识到它非常罕见 - 当需要创建一个单独的控件时,例如DatePicker.

因此,作为另一条建议:如果可能,请在 DataTemplate 下重新制作您的视图并将它们加载到ContentControl. 我认为使用DataTemplateandTabControlEx可以显着提高整个应用程序的性能。

Update

Mr.@Geert van Horrik在评论中提到Catel确实提供了这个实现以及Catel.Windows.Controls.TabControl. 我认为你应该先检查一下。

于 2014-03-01T19:12:23.147 回答