Повторная установка свойств в DataTemplate при смене активной вкладки в TabControl

287
25 июля 2017, 16:08

В программе используется TabControl и каждая его вкладка представлена юзерконтролом с таблицей DataGrid.

Когда активная вкладка меняется, то свойство ItemsSource каждый раз устанавливается заново, что приводит к таким неприятностям как потеря предыдущего состояния полосы прокрутки и всех выделенных строк в таблице, да и просто приходится каждый раз ожидать какое-то время для подгрузки данных.

В коде при этом ничего не изменяется.

В чем проблема?

MainWindow:

<TabControl ItemsSource="{Binding Tabs}">
    <TabControl.ItemContainerStyle>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Header" Value="{Binding Title}" />
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <wpfTemp:MyDataGrid ItemsSource="{Binding Items}" />
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </TabControl.ItemContainerStyle>
</TabControl>

UserControl:

<DataGrid ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
          SelectionMode="Extended"
          AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Number" Binding="{Binding}" />
    </DataGrid.Columns>
</DataGrid>
Answer 1

Проблема решена благодаря этому ответу на английской версии SO. Спасибо @Monk.

XAML:

<TabControl ikriv:TabContent.IsCached="True">
    <ikriv:TabContent.Template>
        <DataTemplate>
            <!-- custom content template goes here -->
        </DataTemplate>
    </ikriv:TabContent.Template>
</TabControl>

Code-behind:

public static class TabContent
{
    public static bool GetIsCached(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsCachedProperty);
    }
    public static void SetIsCached(DependencyObject obj, bool value)
    {
        obj.SetValue(IsCachedProperty, value);
    }
    /// <summary>
    /// Controls whether tab content is cached or not
    /// </summary>
    /// <remarks>When TabContent.IsCached is true, visual state of each tab is preserved (cached), even when the tab is hidden</remarks>
    public static readonly DependencyProperty IsCachedProperty =
        DependencyProperty.RegisterAttached("IsCached", typeof(bool), typeof(TabContent), new UIPropertyMetadata(false, OnIsCachedChanged));

    public static DataTemplate GetTemplate(DependencyObject obj)
    {
        return (DataTemplate)obj.GetValue(TemplateProperty);
    }
    public static void SetTemplate(DependencyObject obj, DataTemplate value)
    {
        obj.SetValue(TemplateProperty, value);
    }
    /// <summary>
    /// Used instead of TabControl.ContentTemplate for cached tabs
    /// </summary>
    public static readonly DependencyProperty TemplateProperty =
        DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(TabContent), new UIPropertyMetadata(null));

    public static DataTemplateSelector GetTemplateSelector(DependencyObject obj)
    {
        return (DataTemplateSelector)obj.GetValue(TemplateSelectorProperty);
    }
    public static void SetTemplateSelector(DependencyObject obj, DataTemplateSelector value)
    {
        obj.SetValue(TemplateSelectorProperty, value);
    }
    /// <summary>
    /// Used instead of TabControl.ContentTemplateSelector for cached tabs
    /// </summary>
    public static readonly DependencyProperty TemplateSelectorProperty =
        DependencyProperty.RegisterAttached("TemplateSelector", typeof(DataTemplateSelector), typeof(TabContent), new UIPropertyMetadata(null));
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static TabControl GetInternalTabControl(DependencyObject obj)
    {
        return (TabControl)obj.GetValue(InternalTabControlProperty);
    }
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static void SetInternalTabControl(DependencyObject obj, TabControl value)
    {
        obj.SetValue(InternalTabControlProperty, value);
    }
    // Using a DependencyProperty as the backing store for InternalTabControl.  This enables animation, styling, binding, etc...
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static readonly DependencyProperty InternalTabControlProperty =
        DependencyProperty.RegisterAttached("InternalTabControl", typeof(TabControl), typeof(TabContent), new UIPropertyMetadata(null, OnInternalTabControlChanged));

    [EditorBrowsable(EditorBrowsableState.Never)]
    public static ContentControl GetInternalCachedContent(DependencyObject obj)
    {
        return (ContentControl)obj.GetValue(InternalCachedContentProperty);
    }
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static void SetInternalCachedContent(DependencyObject obj, ContentControl value)
    {
        obj.SetValue(InternalCachedContentProperty, value);
    }
    // Using a DependencyProperty as the backing store for InternalCachedContent.  This enables animation, styling, binding, etc...
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static readonly DependencyProperty InternalCachedContentProperty =
        DependencyProperty.RegisterAttached("InternalCachedContent", typeof(ContentControl), typeof(TabContent), new UIPropertyMetadata(null));
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static object GetInternalContentManager(DependencyObject obj)
    {
        return (object)obj.GetValue(InternalContentManagerProperty);
    }
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static void SetInternalContentManager(DependencyObject obj, object value)
    {
        obj.SetValue(InternalContentManagerProperty, value);
    }
    // Using a DependencyProperty as the backing store for InternalContentManager.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty InternalContentManagerProperty =
        DependencyProperty.RegisterAttached("InternalContentManager", typeof(object), typeof(TabContent), new UIPropertyMetadata(null));
    private static void OnIsCachedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (obj == null) return;
        var tabControl = obj as TabControl;
        if (tabControl == null)
        {
            throw new InvalidOperationException("Cannot set TabContent.IsCached on object of type " + args.NewValue.GetType().Name +
                ". Only objects of type TabControl can have TabContent.IsCached property.");
        }
        bool newValue = (bool)args.NewValue;
        if (!newValue)
        {
            if (args.OldValue != null && ((bool)args.OldValue))
            {
                throw new NotImplementedException("Cannot change TabContent.IsCached from True to False. Turning tab caching off is not implemented");
            }
            return;
        }
        EnsureContentTemplateIsNull(tabControl);
        tabControl.ContentTemplate = CreateContentTemplate();
    }
    private static DataTemplate CreateContentTemplate()
    {
        const string xaml =
            "<DataTemplate><Border b:TabContent.InternalTabControl=\"{Binding RelativeSource={RelativeSource AncestorType=TabControl}}\" /></DataTemplate>";
        var context = new ParserContext();
        context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
        context.XamlTypeMapper.AddMappingProcessingInstruction("b", typeof(TabContent).Namespace, typeof(TabContent).Assembly.FullName);
        context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
        context.XmlnsDictionary.Add("b", "b");
        var template = (DataTemplate)XamlReader.Parse(xaml, context);
        return template;
    }
    private static void OnInternalTabControlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (obj == null) return;
        var container = obj as Decorator;
        if (container == null)
        {
            var message = "Cannot set TabContent.InternalTabControl on object of type " + obj.GetType().Name +
                ". Only controls that derive from Decorator, such as Border can have a TabContent.InternalTabControl.";
            throw new InvalidOperationException(message);
        }
        if (args.NewValue == null) return;
        if (!(args.NewValue is TabControl))
        {
            throw new InvalidOperationException("Value of TabContent.InternalTabControl cannot be of type " + args.NewValue.GetType().Name + ", it must be of type TabControl");
        }
        var tabControl = (TabControl)args.NewValue;
        var contentManager = GetContentManager(tabControl, container);
        contentManager.UpdateSelectedTab();
    }
    private static ContentManager GetContentManager(TabControl tabControl, Decorator container)
    {
        var contentManager = (ContentManager)GetInternalContentManager(tabControl);
        if (contentManager != null)
        {
            /*
             * Content manager already exists for the tab control. This means that tab content template is applied 
             * again, and new instance of the Border control (container) has been created. The old container 
             * referenced by the content manager is no longer visible and needs to be replaced
             */
            contentManager.ReplaceContainer(container);
        }
        else
        {
            // create content manager for the first time
            contentManager = new ContentManager(tabControl, container);
            SetInternalContentManager(tabControl, contentManager);
        }
        return contentManager;
    }
    private static void EnsureContentTemplateIsNull(TabControl tabControl)
    {
        if (tabControl.ContentTemplate != null)
        {
            throw new InvalidOperationException("TabControl.ContentTemplate value is not null. If TabContent.IsCached is True, use TabContent.Template instead of ContentTemplate");
        }
    }
    public class ContentManager
    {
        TabControl _tabControl;
        Decorator _border;
        public ContentManager(TabControl tabControl, Decorator border)
        {
            _tabControl = tabControl;
            _border = border;
            _tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
        }
        public void ReplaceContainer(Decorator newBorder)
        {
            if (Object.ReferenceEquals(_border, newBorder)) return;
            _border.Child = null; // detach any tab content that old border may hold
            _border = newBorder;
        }
        public void UpdateSelectedTab()
        {
            _border.Child = GetCurrentContent();
        }
        private ContentControl GetCurrentContent()
        {
            var item = _tabControl.SelectedItem;
            if (item == null) return null;
            var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
            if (tabItem == null) return null;
            var cachedContent = TabContent.GetInternalCachedContent(tabItem);
            if (cachedContent == null)
            {
                cachedContent = new ContentControl
                {
                    DataContext = item,
                    ContentTemplate = TabContent.GetTemplate(_tabControl),
                    ContentTemplateSelector = TabContent.GetTemplateSelector(_tabControl)
                };
                cachedContent.SetBinding(ContentControl.ContentProperty, new Binding());
                TabContent.SetInternalCachedContent(tabItem, cachedContent);
            }
            return cachedContent;
        }
    }
}
READ ALSO
работающий Data source на всех windows

работающий Data source на всех windows

Какой указывать data source, чтобы без установок localdb, mssqlserver и прочих работало на "чистых" windows (или вместо db нужно использовать а-ля xml?)?

215
Перенос обработчика события

Перенос обработчика события

При создании обработчика события в xaml разметке, он появляется в файле namexaml

181
Чтение данных из Excel в С#

Чтение данных из Excel в С#

Доброго всем дня! Подскажите как обратится к уже открытой книге Excel если она открыта в отдельном окне, то-есть Excel файлы могут быть открыты...

443
c# древо папок visual studio

c# древо папок visual studio

Народ подскажите есть задача, есть папка где есть подпапки и в них ссылки, мне нужно сделать wpf приложение разделенное визуально на две части,...

494