2016-04-11 13 views
0

Belirli türlerdeki çocukları bulmak için TabControl üzerinden geçen bir yöntem var. Yalnızca bir TabItem seçildiğinde bu yöntemi çağırdığımda, bu TabItem'un yalnızca Controls olmasını sağlarım. Her bir TabItem'u bir kez seçip FindVisualChildrenOfMultipleTypes()'u çağırırsam, Controls görsel ağaca yüklenir ve ben Controls'un tümünü TabItems alırım. Tüm TabItems yüklemek için TabControl benim zorlama nasıl yapabilirim? Gördüğünüz gibiTüm TabItems'i yüklemek için Force TabControl

private void tabControlTest(System.Windows.Controls.TabControl tabControl) 
{ 
    var tabControlView = View.GetParentView(tabControl); 

    var types = new List<Type>() 
    { 
     typeof(CheckBox), 
     typeof(RadioButton), 
     typeof(TextBox) 
    }; 

    tabControl.Template.LoadContent(); 
    tabControl.ApplyTemplate(); 
    tabControl.UpdateLayout(); 

    var children = UIHelper.FindVisualChildrenOfMultipleTypes(types, tabControl);  
} 

UIHelper.cs yöntem

public static IEnumerable<UIElement> FindVisualChildrenOfMultipleTypes(List<Type> types, DependencyObject dependencyObj) 
{ 
    if (dependencyObj != null) 
    { 
     for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObj); i++) 
     { 
      DependencyObject child = VisualTreeHelper.GetChild(dependencyObj, i); 
      if (child != null && types.Contains(child.GetType())) 
      { 
       yield return child as UIElement; 
      } 

      foreach (var childOfChild in FindVisualChildrenOfMultipleTypes(types, child)) 
      { 
       yield return childOfChild; 
      } 
     } 
    } 
} 

Denedim:

tabControl.Template.LoadContent(); 
tabControl.ApplyTemplate(); 
tabControl.UpdateLayout(); 

Ama bu satırların hiçbiri çalışır.

+2

Bunu neden yapmak istiyorsunuz? Ör. MVVM size * neredeyse * bir şey yapmak için model (ViewModel) öğeleri almak için görseller arasında geçiş yapmak zorunda kalmazsınız. – Sinatr

+0

@Sinatr Model öğeleri ile ilgilenmiyorum ama benim TabControl – Sybren

+0

içindeki UIElement öğeleri için UIElement 'ne ihtiyacınız var? [X problem] 'i (http://meta.stackexchange.com/q/66377/299295) açıklayın, bunu yapmak zorunda olmayan bir çözüm olabilir * karmaşık * yol. – Sinatr

cevap

0

@Sinatr bir yorumda belirtildiği gibi, yaptığınız şey muhtemelen kötü bir fikirdir. Yapmaya çalıştığınız her şey, MVVM'yi düzgün bir şekilde kullanmak için muhtemelen önemsizdir. Bunu neden yapmanız gerektiğini açıklarsanız, gerçek hedefiniz nedir, muhtemelen oraya ulaşmak için daha temiz ve daha az acı verici (ve daha sağlam ve daha sürdürülebilir) bir yol vardır. Bununla birlikte, karşılaştığınız roadblock, WPF TabControl ile gerçek bir sorundur ve bunun çözümü vardır.

Sanallaştırma nedeniyle: TabControl, TabControl.ItemsSource aracılığıyla doldurulduğunda, yalnızca şu anda seçili TabItem var. Kullanıcı farklı bir sekmeyi seçtiğinde, bu öğe seçilen sekmenin DataContext'u için değiştirilir, şablon uygulanır, vb.

Bu benim tasarım kusurumdır; Bir TabControl, sanallaştırmayı gerekli yapmak için yeterli sekmeye sahipse, kullanılabilir olması için çok fazla sekme var. Ama işte bu, ve kullandığım ve sağlam olduğu bilinen bir çözüm var: Ivan Krivyakov's TabContent.IsCached attached property. CodeProject sigara dumanı içinde kaybolur durumunda,

<TabControl 
    xmlns:ikriv="clr-namespace:IKriv.Windows.Controls.Behaviors" 
    ikriv:TabContent.IsCached="True" 
    ItemsSource="{Binding DocumentList.Documents}" 
    SelectedItem="{Binding DocumentList.ActiveDocument}" 
    /> 

İşte tam kaynağıdır:

XAML bu Görünüşe

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

Cevabınız için teşekkürler. 'TabControl'' TabControl.ItemsSource' aracılığıyla doldurulmaz. TabItem kontrollerini almak için başka seçenekler var mı, belki LogicalTree ile mi? – Sybren

+0

@Sybren Eğer yoksa, onları hiçbir şekilde elde edemezsiniz. Bu çözüm işe yaramadı mı? –

+0

@Sybren Ayrıca, gerçek hedefin burada ne olduğunu açıklamalısınız. Kural olarak, iki gün korku kod hunisi alan her şey XAML'de, belki bir değer dönüştürücü veya ekli bir özellik ile, yarım saat içinde yapılabilir. WPF, kullandığınız şekilde kullanılacak şekilde tasarlanmamıştır. –

0

kaç" gibi istatistik toplamak istiyorsanız Bu değer değiştirildi ", sonra MVVM kullanarak oldukça kolay organize edilebilir. Başlangıç ​​için her yerde bağlama kullanmaya başlamak ve doğrudan UI ile doğrudan işlem yapmak için yeterlidir. İşte

"metin değişikliği" odakta TextBox farklı değer olarak tanımlanır nerede, metin değişikliklerini saymak (En basit durumda) kaybetti nasıl, bir örnektir.

Görünüm:

<TextBox Text="{Binding SomeText, UpdateSourceTrigger=LostFocus}" ... /> 

ViewModel:

class SomeTabPageViewModel: INotifyPropertyChanged 
{ 
    ... 
    string _someText; 
    int _someTextChanged = 0; // how many times text was changed by user 
    public string SomeText 
    { 
     get { return _someText; } 
     set 
     { 
      _someText = value; 
      _someTextChanged++; // when TextBox lost focus after user changed value 
      OnPropertyChanged(); 
     } 
    } 
} 

ViewModel (işleme sonucu ilk defa veya gibi) SomeText değerini belirlediğinde bunu alanını kullanarak bunu yapmak gerekir (aksi sayaç artırılır , bunu istemiyorsunuz), bu durumda Güncellemenin kendisini görüntüle'ye bildirmek için OnPropertyChanged(nameof(SomeText)); kullanılmalıdır.