2016-09-23 3 views
0

У меня есть установка MVVM с TabControl и ObservableCollection<ViewModel> tabitems.TabItem не загружается немедленно

открыть файл и загрузить модель из этого файла в TabItem:

var model = new ViewModel(data, filename); 
ViewModels.Tabs.Add(model); 

TabItem имеет DataTemplate для его Header и Content.
Content определен в отдельном UserControl, Header находится в основном файле.

Когда я запускаю заголовок, отображается, но только когда я нажимаю на заголовок, отображается загруженное событие запуска tabitem и содержимое.

Я ожидал, что он сразу же загрузится, почему бы и нет?

Когда я добавляю две вкладки одновременно:

var model = new ViewModel(data, filename); 
ViewModels.Tabs.Add(model); 
ViewModels.Tabs.Add(model); 

затем загруженное событие первой вкладке делает огонь и его содержимое отображается.

Как я могу достичь желаемого поведения?

+0

Это виртуализация вещь. Другая обычная проблема заключается в том, что визуальное состояние теряется, когда TabItem не выбран. Решение состоит в том, чтобы избавиться от виртуализации; в действительности, напишите код, чтобы при привязке 'ObservableCollection' к' ItemsSource' он добавит 'TabItem' для каждого элемента в коллекции. Есть версия на codeproject, которую я не могу лично поручиться за: http://stackoverflow.com/a/36209166/424129 –

+0

Исправление: я * могу * поручиться за это лично. Я вспомнил, что сделал то же самое в прошлом году, просто проверил этот проект, и, оказывается, я просто использовал его код. Он работает нормально. –

+0

Я вижу, сложный материал. Ну, я, наверное, просто добавляю 'ViewModels.Tabs.Add (null); ViewModels.Tabs.Remove (null); 'как обходной путь. – Gerard

ответ

1

Это связано с виртуализацией. Как и в любом подклассе Selector, на самом деле существуют только видимые элементы. И в TabControl единственным видимым элементом является выбранный. Я не думаю, что это был идеальный дизайн для большинства распространенных применений элемента управления вкладками, но мы здесь.

Лучшее исправление, которое я нашел, - это добавить вложенное свойство, которое вступает в действие и создает фактический TabItem для каждого элемента в ItemsSource. От this woefully unappreciated answer, я нашел this CodeProject thing by Ivan Krivyakov. Я использовал его, и он работает.

<TabControl 
    xmlns:ikriv="clr-namespace:IKriv.Windows.Controls.Behaviors" 
    ikriv:TabContent.IsCached="True" 

Это 285 строк кода C#, но вещи в Интернете уходят. Вот оно:

// TabContent.cs, version 1.2 
// The code in this file is Copyright (c) Ivan Krivyakov 
// See http://www.ikriv.com/legal.php for more information 
// 
using System; 
using System.ComponentModel; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Markup; 

/// <summary> 
/// http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization 
/// </summary> 
namespace IKriv.Windows.Controls.Behaviors 
{ 
    /// <summary> 
    /// Attached properties for persistent tab control 
    /// </summary> 
    /// <remarks>By default WPF TabControl bound to an ItemsSource destroys visual state of invisible tabs. 
    /// Set ikriv:TabContent.IsCached="True" to preserve visual state of each tab. 
    /// </remarks> 
    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(); 
      EnsureContentTemplateIsNotModified(tabControl); 
     } 

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

     private static void EnsureContentTemplateIsNotModified(TabControl tabControl) 
     { 
      var descriptor = DependencyPropertyDescriptor.FromProperty(TabControl.ContentTemplateProperty, typeof(TabControl)); 
      descriptor.AddValueChanged(tabControl, (sender, args) => 
       { 
        throw new InvalidOperationException("Cannot assign to TabControl.ContentTemplate when TabContent.IsCached is True. Use TabContent.Template instead"); 
       }); 
     } 

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

Чтобы достичь того, чего вы хотите, вы должны привязать к SelectedItem свойству TabControl и в вашем ViewModel вы должны иметь, что указывает на какой-либо элемент вашей коллекции. Он должен выглядеть следующим образом: XAML

<TabControl ItemsSource="{Binding Items}" SelectedItem="{Binding Item}"> 
</TabControl> 

ViewModel

public ViewModel() { 
     SelectedItem = Items.First(); 
    } 

public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item> { 
     new Item("test1", 5), 
     new Item("test2", 2) 
    }; 

public Item SelectedItem { get; set; } //don't forget to implement ChangeNotifications for it 
+0

Я уже сделал это, и он работает для выбора.Но опять же, в моем случае, по крайней мере, tabitem не загружается, пока я не нажму на заголовок. – Gerard

+0

Не уверен, что я понимаю, что вы имеете в виду. Если привязка к SelectedItem настроена правильно, она должна отображать выбранную вами вкладку без необходимости щелчка по ее заголовку. Попробуйте создать новый проект WPF и протестируйте его там. Возможно, в вашем коде есть что-то еще, что предотвращает загрузку ... – 3615

+0

Я имею в виду, что хотя выбранная вкладка действительно отображается сразу, без необходимости щелчка по ее заголовку, содержимое остальной части элемента управления табуляции пуст , Я должен нажать на заголовок, чтобы загрузить этот контент. Итак, попробуйте, когда у вашего tabitem есть контент, такой как datagrids, сразу ли отображаются эти датагрифы? – Gerard