设计模式 - WPF 自动将 AttachedProperty 添加到父级

发布于 2024-11-09 15:08:18 字数 275 浏览 3 评论 0原文

我创建了一个自定义用户控件,它是某种虚拟键盘按钮。我正在寻找以下功能:

  • 任何面板都应该包含所有键盘按钮子项的集合。
  • 任何其他也是该面板子级的控件都应该能够枚举其父级的键盘按钮。

从功能上实现这一点并不是什么大问题。我可以创建 List 类型的 Attached-DependencyProperty 并手动管理此列表。这种方法的问题是,它很容易出错并且不方便。

是否有可能在创建时自动将键盘按钮附加到父级的 AttachedProperty ?

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 approach 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夕嗳→ 2024-11-16 15:08:18

正如您所提到的,维护列表很容易出错 - 您需要使其与 WPF 可视化树保持同步。您可以从给定的根遍历可视化树,而不是维护列表。就您而言,听起来根是一个Panel

遍历视觉树非常容易。 这篇文章展示了一些示例。我认为相关的是这个:

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 的所有 Visual Children 进行深度优先遍历。因此,对于任何给定的Panel,您都可以调用此方法。由于它是 IEnumerable,因此您可以使用 LINQ 将结果筛选到自定义 UserControl。

对于您的其他要求(“也是此面板的子级的任何其他控件应该能够枚举其父级的键盘按钮”):给定 Panel 的子级,向上遍历可视化树找到第一个 Panel 并使用相同的过程来获取所有相关的子项。

如果您确实需要将其作为 DependencyProperty (例如用于数据绑定),我认为您可以将其设置为只读,并仍然在 getter 中使用 Visual Tree Traversal 方法,但我不确定。

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.

中性美 2024-11-16 15:08:18

我已经为我的问题创建了一个非常好的解决方案。以下代码显示了我的实现:

// 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。因此,控件在设计时无法修改,甚至无法查看。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文