2017-04-26 47 views
2

就我所知,通常Visibility={Binding SomeValue, Converter={…}}仍將節點保留在視覺和邏輯樹中,即使它不可見。但是如果我想完全刪除它並同時保持語法輕便呢?WPF:根據輸入值顯示特定孩子的父母

現在,我做了一個叫Switch讓我做這樣的東西類:

<Switch Value="{Binding Status}"> 
    <TextBlock Switch.When="{x:Static Status.NotFound}" Text="Not found" /> 
    <Button Switch.When="{x:Static Status.ConnectionError}" Text="Connection error. Try again?" /> 
    <Grid Switch.When="{x:Static Status.Loaded}">…</Grid> 
</Switch> 

Here是那個Switch事情的源代碼。

我喜歡它的外觀和工作原理,但有時會出現一些錯誤。例如,雖然很少從以前的位置移除視覺小孩,但System.InvalidOperationException: Cannot modify the logical children for this node at this time because a tree walk is in progress.可能會發生。像這樣的例子是可以解決的,但與他們一起的事情讓我覺得我做錯了一些事情。會是什麼呢?也許整個想法完全不符合WPF?或者,也許我只是錯過了一些東西(比如我必須重寫IEnumerator LogicalChildren { get; }才能使它正常工作)?

+0

查看更新的答案。 –

回答

2

我認爲答案可能是您試圖以正確的方式重置孩子控件。餿主意!他們只是任意的內容;不要把它們當作控件。我的代碼中的實際重新制作是通過在模板中隱藏ContentPresenter魔術來完成的。我們所有的控制類代碼都只是將它們像土豆一樣扔在一邊。

這是一個不應該給你任何迴響的控件的工作版本。但請注意,我無法比較盒裝枚舉值彼此。我對你如何解決這個問題感興趣。

Switch.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Markup; 

namespace SwitchTestProject 
{ 
    [ContentProperty("Items")] 
    public class Switch : Control 
    { 
     public Switch() 
     { 
      Items = new List<DependencyObject>(); 
     } 

     static Switch() 
     { 
      DefaultStyleKeyProperty.OverrideMetadata(typeof(Switch), new FrameworkPropertyMetadata(typeof(Switch))); 
     } 

     public override void OnApplyTemplate() 
     { 
      base.OnApplyTemplate(); 

      OnValueChanged(null); 
     } 

     #region Switch.When Attached Property 
     public static Object GetWhen(DependencyObject obj) 
     { 
      return (Object)obj.GetValue(WhenProperty); 
     } 

     public static void SetWhen(DependencyObject obj, Object value) 
     { 
      obj.SetValue(WhenProperty, value); 
     } 

     public static readonly DependencyProperty WhenProperty = 
      DependencyProperty.RegisterAttached("When", typeof(Object), typeof(Switch), 
       new PropertyMetadata(null)); 
     #endregion Switch.When Attached Property 

     #region Content Property 
     public Object Content 
     { 
      get { return (Object)GetValue(ContentProperty); } 
      protected set { SetValue(ContentPropertyKey, value); } 
     } 

     internal static readonly DependencyPropertyKey ContentPropertyKey = 
      DependencyProperty.RegisterReadOnly(nameof(Content), typeof(Object), typeof(Switch), 
       new PropertyMetadata(null)); 

     public static readonly DependencyProperty ContentProperty = ContentPropertyKey.DependencyProperty; 
     #endregion Content Property 

     #region Value Property 
     public Object Value 
     { 
      get { return (Object)GetValue(ValueProperty); } 
      set { SetValue(ValueProperty, value); } 
     } 

     public static readonly DependencyProperty ValueProperty = 
      DependencyProperty.Register(nameof(Value), typeof(Object), typeof(Switch), 
       new FrameworkPropertyMetadata(null, Value_PropertyChanged)); 

     protected static void Value_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
     { 
      (d as Switch).OnValueChanged(e.OldValue); 
     } 

     private void OnValueChanged(object oldValue) 
     { 
      if (Value is IComparable) 
      { 
       // Boxed value types have to be a special case. 
       // Unless I jumped to an unwarranted conclusion about == not working. 

       var icompval = Value as IComparable; 

       foreach (var item in Items) 
       { 
        var icompwhen = GetWhen(item) as IComparable; 

        if (icompwhen != null && icompval.CompareTo(icompwhen) == 0) 
        { 
         Content = item; 
         return; 
        } 
       } 
      } 
      else 
      { 
       Content = Items.FirstOrDefault(item => GetWhen(item) == Value); 
      } 
     } 
     #endregion Value Property 

     #region Items Property 
     public List<DependencyObject> Items 
     { 
      get { return (List<DependencyObject>)GetValue(ItemsProperty); } 
      protected set { SetValue(ItemsPropertyKey, value); } 
     } 

     internal static readonly DependencyPropertyKey ItemsPropertyKey = 
      DependencyProperty.RegisterReadOnly(nameof(Items), typeof(List<DependencyObject>), typeof(Switch), 
       new PropertyMetadata(null)); 

     public static readonly DependencyProperty ItemsProperty = ItemsPropertyKey.DependencyProperty; 
     #endregion Items Property 
    } 
} 

的App.xaml或主題\ Generic.xaml

你可以做更多的事情在這裏造型父。

<Style TargetType="local:Switch"> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="local:Switch"> 
       <ContentPresenter 
        /> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 

用法:

<local:Switch 
    Value="{Binding Status}" 
    > 
    <TextBlock 
     local:Switch.When="{x:Static local:Status.NotFound}" 
     >This is a test</TextBlock> 
    <TextBlock 
     local:Switch.When="{x:Static local:Status.ConnectionError}" 
     >There was an error in the connection</TextBlock> 
</local:Switch> 

純XAML替代

Switch控制該缺陷可能是可以解決的,但是這將可靠地工作,沒有任何廢話(比所有其他冗長)。

<ContentControl> 
    <ContentControl.Style> 
     <Style TargetType="ContentControl"> 
      <Style.Triggers> 
       <DataTrigger Binding="{Binding Status}" Value="NotFound"> 
        <Setter Property="ContentTemplate"> 
         <Setter.Value> 
          <DataTemplate> 
           <TextBlock Text="Not found" /> 
          </DataTemplate> 
         </Setter.Value> 
        </Setter> 
       </DataTrigger> 
       <DataTrigger Binding="{Binding Status}" Value="ConnectionError"> 
        <Setter Property="ContentTemplate"> 
         <Setter.Value> 
          <DataTemplate> 
           <Button Content="Connection error. Try again?" /> 
          </DataTemplate> 
         </Setter.Value> 
        </Setter> 
       </DataTrigger> 
       <DataTrigger Binding="{Binding Status}" Value="Loaded"> 
        <Setter Property="ContentTemplate"> 
         <Setter.Value> 
          <DataTemplate> 
           <Ellipse 
            Height="32" 
            Width="32" 
            Fill="DeepSkyBlue" 
            /> 
          </DataTemplate> 
         </Setter.Value> 
        </Setter> 
       </DataTrigger> 
      </Style.Triggers> 
     </Style> 
    </ContentControl.Style> 
</ContentControl> 

我們使用DataTemplates,而不是直接設置Content屬性,因爲如果我們採用後一種方法,每個子控件的一個實例將永遠存在,所以我們就無法因素的樣式設置內容作爲一種資源並重用它。

而且,模板是在XAML中創建新控件的規範方法。

+0

謝謝,效果很棒!綁定和可視化初始化,一切似乎只在需要時纔會發生。 現在,我用[非常原始的方式]比較值(https://gist.github.com/groove/87867496fede43818a490808d608f688#file-converterhelper-cs-L65),但它並沒有讓我失望。雖然,我打算稍後修復它(現在,「Value」綁定到類型化變量,但是「來自XAML的時間」)通常只是字符串,這就是爲什麼這個片段看起來像這樣)。 –

+0

@SurfinBird酷。你如何比較枚舉值? –

+0

我在XAML中引用它們作爲'{x:Static EnumType.EnumValue}'(爲了避免重構時出現混淆),並且沒有標誌枚舉(還有嗎?),所以簡單的Equals通常工作得很好。 –