2011-05-26 71 views
0

我創建了一個自定義UserControl,它是某種虛擬鍵盤按鈕。我正在尋找以下功能:設計模式 - WPF自動添加AttachedProperty到家長

  • 任何面板應該包含所有它的鍵盤按鈕孩子的集合。
  • 任何其他也是這個面板的孩子的控件應該能夠枚舉他父母的鍵盤按鈕。

這不是實現這個功能的大問題。我可以創建List類型的Attached-DependencyProperty並手動管理這個列表。這種方法的問題是,它很容易出錯並且不方便。

是否有可能在創建時將鍵盤按鈕自動附加到父級的AttachedProperty?

回答

0

我已經爲我的問題創建了一個很好的解決方案。下面的代碼顯示了我實現:

// KeyboardButton.cs 
/// <remarks> 
/// This class does contain a lot of more code which isn't related to the actual problem. 
/// </remarks> 
public partial class KeyboardButton 
{ 
    [DefaultValue(Key.NoName)] 
    public Key EmulatedKey { get; set; } 
} 


// KeyboardPanel.cs 
public class KeyboardButtonCollection : Collection<KeyboardButton> { } 


public static class KeyboardPanel 
{ 
    internal const string ButtonsPropertyName = "Buttons"; 


    static KeyboardPanel() 
    { 
     ButtonsProperty = DependencyProperty.RegisterAttached 
     (
      ButtonsPropertyName, 
      typeof(KeyboardButtonCollection), 
      typeof(KeyboardPanel), 
      new UIPropertyMetadata(null, OnButtonsChanged) 
     ); 
    } 


    public static KeyboardButtonCollection GetButtons(DependencyObject dependencyObject) 
    { 
     return (KeyboardButtonCollection)dependencyObject.GetValue(ButtonsProperty); 
    } 

    public static void SetButtons(DependencyObject dependencyObject, KeyboardButtonCollection value) 
    { 
     dependencyObject.SetValue(ButtonsProperty, value); 
    } 


    public static bool IsKeyboardPanel(DependencyObject dependencyObject) 
    { 
     return GetButtons(dependencyObject).Count != 0; 
    } 


    public static readonly DependencyProperty ButtonsProperty; 


    private static void OnButtonsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) 
    { 
     bool isSupportedType = true; 

     if (dependencyObject is Panel) 
     { 
      var panel = (Panel)dependencyObject; 
      foreach (var button in (KeyboardButtonCollection)e.NewValue) 
       panel.Children.Add(button); 
     } 
     else 
      isSupportedType = false; 

     if (!isSupportedType) 
      throw new NotSupportedException(string.Format("Type {0} is not supported as KeyboardPanel.", dependencyObject.GetType())); 
    } 
} 


<!-- MainWindow.xaml --> 
<Grid> 
    <controls:KeyboardPanel.Buttons> 
     <controls:KeyboardButtonCollection> 
      <controls:KeyboardButton Content="Enter" EmulatedKey="Enter"/> 
      <controls:KeyboardButton Grid.Row="1" Content="Some Key"/> 
      <controls:KeyboardButton Grid.Row="2" Content="Another Key"/> 
     </controls:KeyboardButtonCollection> 
    </controls:KeyboardPanel.Buttons> 

    <Grid.RowDefinitions> 
     <RowDefinition/> 
     <RowDefinition/> 
     <RowDefinition/> 
     <RowDefinition/> 
    </Grid.RowDefinitions> 

    <Button Grid.Row="3" Content="Test" Loaded="Button_Loaded"/> 
</Grid> 


// MainWindow.xaml.cs 
public partial class MainWindow : Window 
{ 
    private void Button_Loaded(object sender, RoutedEventArgs e) 
    { 
     var button = (Button)sender; 
     if (KeyboardPanel.IsKeyboardPanel(button.Parent)) 
     { 
      var enterButton = KeyboardPanel.GetButtons(button.Parent) 
              .FirstOrDefault(b => b.EmulatedKey == Key.Enter); 

      if (enterButton != null) 
       MessageBox.Show("KeyboardPanel contains an EnterButton"); 
      else 
       MessageBox.Show("KeyboardPanel doesn't contain an EnterButton"); 
     } 
    } 
} 

我一般不會在.NET編程,但 - 不時 - 我「被迫」用它創建一些工具。然而,你們中的一些人可能知道我如何消除此代碼的弱點:它不支持VisualStudio或Expression Blend Designer。因此,這些控件不能在設計時修改或者看到。

+0

我剛剛發現,當沒有其他元素添加到該面板時,設計者確實會顯示附加的控件。似乎是一個設計師的錯誤。 – 0xbadf00d 2011-05-28 14:32:20

0

如上所述,維護列表容易出錯 - 您需要保持與WPF可視化樹同步。您可以從給定的根遍歷可視樹,而不是維護一個列表。在你的情況下,它聽起來像根是Panel

遍歷視覺樹非常簡單。 This post顯示了一些例子。一個我覺得相關的是這一個:

public static IEnumerable<DependencyObject> GetVisualTree(this DependencyObject element) 
{ 
    int childrenCount = VisualTreeHelper.GetChildrenCount(element); 

    for (int i = 0; i < childrenCount; i++) 
    { 
     var visualChild = VisualTreeHelper.GetChild(element, i); 

     yield return visualChild; 

     foreach (var visualChildren in GetVisualTree(visualChild)) 
     { 
      yield return visualChildren; 
     } 
    } 
} 

這確實給定DependencyObject所有的視覺兒童的深度優先遍歷。因此,對於任何給定Panel,您可以調用此方法。由於它是IEnumerable,因此您可以使用LINQ將結果過濾到您的自定義UserControl。

對於您的其他要求(「任何其他控件也是此面板的子項應該能夠枚舉其父項的鍵盤按鈕」):給定Panel的子項,向上漫遊Visual Tree以找到第一個Panel並使用相同的過程來獲取所有相關的孩子。

如果你真的需要這個作爲DependencyProperty(例如數據綁定),我認爲你可以使它只讀,並仍然在getter中使用Visual Tree Traversal方法,但我不確定。