2016-09-23 3 views
0

TabControl 및 tabitems가 ObservableCollection<ViewModel> 인 MVVM 설정이 있습니다.TabItem이 바로로드되지 않습니다.

나는 파일을 열고 TabItem에서 해당 파일로 만든 모델을로드 : 그것은 HeaderContent의에 대한

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

TabItem이 DataTemplate이 있습니다.
Content은 별도의 UserControl에 정의되어 있으며 Header은 주 파일 자체에 있습니다.

헤더를 실행하면 머리글을 클릭 할 때만로드 된 이벤트가 발생하고 내용이 표시됩니다.

즉시로드 될 것으로 예상했는데 왜 그렇지 않습니까? 다음 첫 번째 탭의로드 이벤트가 화재가하는

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에서 유일한 표시 항목은 선택한 항목입니다. 나는 그것이 탭 컨트롤의 가장 보편적 인 사용을위한 이상적인 디자인 선택이라고 생각하지 않지만, 여기 있습니다.

내가 찾은 가장 좋은 해결책은 단계별로 들어간 속성을 추가하여 ItemsSource의 각 항목에 실제 TabItem을 생성하는 것입니다. this woefully unappreciated answer에서 this CodeProject thing by Ivan Krivyakov을 발견했습니다. 나는 그것을 사용하고 그것은 작동합니다.

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

이것은 C# 코드의 285 줄이지만 인터넷상의 것들은 사라집니다. 여기있다 :

// 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

당신이 컬렉션의 어떤 요소를 가리키는 것을해야을 TabControl의 SelectedItem 특성에 당신의 ViewModel 당신이 바인딩해야 원하는 것을 달성하기 위해. 그것은 다음과 같아야합니다 XAML

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

뷰 모델

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이 DataGrid와 같은 컨텐츠를 가지고있을 때,이 DataGrid가 즉시 표시되는지 여부를 시험해보십시오. – Gerard

관련 문제