Шаблон проектирования - WPF автоматически добавляет AttachedProperty к родителю - PullRequest
0 голосов
/ 26 мая 2011

Я создал пользовательский элемент управления UserControl, представляющий собой виртуальную клавиатуру.Функционально я ищу следующее:

  • Любая панель должна содержать коллекцию всех дочерних элементов ее клавиатуры.
  • Любой другой элемент управления, который также является дочерним элементом этой панели, должен бытьспособен перечислять кнопки клавиатуры своего родителя.

Это не большая проблема для достижения этого функционально.Я мог бы создать Attached-DependencyProperty типа List и вручную управлять этим списком.Проблема этого подхода заключается в том, что он довольно подвержен ошибкам и неудобен.

Есть ли возможность автоматически присоединить кнопку клавиатуры во время создания к AttachedProperty родительского объекта?

Ответы [ 2 ]

0 голосов
/ 27 мая 2011

Я уже создал довольно хорошее решение для моей проблемы.Следующий код показывает мою реализацию:

// 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 голосов
/ 27 мая 2011

Как вы упомянули, ведение списка подвержено ошибкам - вам нужно будет синхронизировать его с визуальным деревом 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. Таким образом, для любого данного Panel вы можете вызвать этот метод. Поскольку это IEnumerable, вы можете использовать LINQ для фильтрации результатов в свой пользовательский UserControl.

Для вашего другого требования («Любой другой элемент управления, который также является дочерним элементом этой панели, должен иметь возможность перечислять кнопки клавиатуры своего родителя»): Если у вас есть дочерний элемент Panel, пройдите дерево визуалов вверх, чтобы найти первый Panel и используйте тот же процесс, чтобы получить всех соответствующих детей.

Если вам действительно нужно это как DependencyProperty (например, для привязки данных), я думаю, вы могли бы сделать его доступным только для чтения и все еще использовать метод визуального обхода дерева в получателе, но я не уверен.

...