开发者

Design Pattern - WPF Automatically add AttachedProperty to parent

开发者 https://www.devze.com 2023-03-08 19:30 出处:网络
I\'ve created a custom UserControl which is some kind of virtual keyboard button. I\'m looking for the following functionally:

I've created a custom UserControl which is some kind of virtual keyboard button. I'm looking for the following functionally:

  • Any Panel should contain a collection of all it's keyboard button childs.
  • Any other control which is also a child of this Panel should be able to enumerate the keyboard buttons of his parent.

It isn't the big problem to achieve this functionally. I could create an Attached-DependencyProperty of type List and manually manage this list. The problem about this approac开发者_StackOverflowh would be, that it's pretty error prone and inconvenient.

Is there any possibility to automatically attach the keyboard button at creation time to the AttachedProperty of the parent?


As you mentioned, maintaining a list is error-prone - you would need to keep it in sync with the WPF Visual Tree. Instead of maintaining a list, you can traverse the Visual Tree from a given root. In your case, it sounds like the root is a Panel.

Traversing the Visual Tree is pretty easy. This post shows some examples. The one I think is relevant is this one:

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

This does a depth-first traversal of all the Visual Children of a given DependencyObject. So for any given Panel, you can call this method. Since it's IEnumerable, you can use LINQ to filter the results to your custom UserControl.

For your other requirement ("Any other control which is also a child of this Panel should be able to enumerate the keyboard buttons of his parent"): Given a child of a Panel, walk the Visual Tree upwards to find the first Panel and use the same process to get all the relevant children.

If you really need this as a DependencyProperty (e.g. for databinding), I think you could make it readonly and still use the Visual Tree Traversal method in the getter, but I'm not sure.


I've already created a quite good solution for my problem. The following code shows my implementation:

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

I'm generally not programming in .NET, but - from time to time - I'm "forced" to create some tools with it. However, some of you guys may know how I can remove a weakness of this code: It doesn't support the VisualStudio or Expression Blend Designer. Thus, the controls cannot be modified or even seen at design-time.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号