WPF. Какие условия должны быть выполнены для использования привязки данных в элементах содержимого пользовательского элемента управления?

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

<c:PieChart>
    <!-- These dependency properties are never set -->
    <c:Slice Value="{Binding RedCount}" />
    <c:Slice Value="{Binding BlueCount}" />
    <c:Slice Value="{Binding GreenCount}" />
</c:PieChart>

PieChart происходит от Control:

[ContentProperty("Slices")]
public class PieChart : Control
{
    public PieChart()
    {
        Slices = new ObservableCollection<Slice>();
    }

    public ObservableCollection<Slice> Slices { get; private set; }
}

Приведенный выше XAML приводит к тому, что свойство Slices заполняется тремя экземплярами Slice.

Вот фрагмент из Slice:

public class Slice : ContentControl
{
    public static DependencyProperty ValueProperty
        = DependencyProperty.Register(
            "Value", typeof(double), typeof(Slice),
            new PropertyMetadata((p,a) => ((Slice)p).OnValueChanged(a)));

    private void OnValueChanged(DependencyPropertyChangedEventArgs e)
    {
        // This code never called!
    }
}

Проблема в том, что свойство Value для Slice никогда не устанавливается. Если я помещу свойство зависимости в PieChart и скопирую выражение привязки, тогда значение будет установлено, поэтому я уверен, что понимаю основы свойств зависимостей и привязок.

Итак, что мне нужно сделать, чтобы мое свойство зависимости установило значение для дочернего элемента? Поскольку эти предметы находятся в моей собственной коллекции, они почему-то не в логическом дереве, распознаваемом какой-то магией, заставляющей работать привязки?

Простой ответ «сделай это» был бы полезен, но мне бы очень хотелось понять, что здесь происходит не так, поэтому я приму самый проницательный и пояснительный ответ.

РЕДАКТИРОВАТЬ Я не дал понять, но в DataContext из PieChart есть какой-то внешний объект, который содержит свойства RedCount и т. д. В прошлом, когда у меня была опечатка в пути привязки (или какая-то другая ошибка) Я видел предупреждения в окне вывода VS во время выполнения. В данном случае я ничего не вижу.


person Drew Noakes    schedule 27.04.2009    source источник


Ответы (2)


Похоже, элементы управления Slice не подключаются к визуальному дереву. Это просто переменные-члены, и WPF не знает, что он должен отображать их как дочерние элементы класса PieChart. Пара возможных подходов:

Пусть PieChart вызовет AddLogicalChild, чтобы добавить Slice объектов. Вам нужно будет обработать возможность добавления или удаления Slices из коллекции во время выполнения. Это довольно низкий уровень: MSDN сообщает: «Для авторов элементов управления манипулирование логическим деревом на этом уровне не рекомендуется, если только ни одна из моделей содержимого для доступных базовых классов элементов управления не подходит для вашего сценария управления. Рассмотрите возможность создания подклассов на уровне из ContentControl, ItemsControl и HeaderedItemsControl".

Сделайте PieChart Panel. Тогда Slices будут его дочерними элементами, но обратите внимание, что свойство Children имеет слабую типизацию, поэтому люди могут добавлять что угодно к PieChart. Это по своей сути не плохо и не хорошо; это конструктивное соображение (см. примечание ниже). Но это, вероятно, не то, что вы хотите в вашем случае.

Сделайте PieChart ItemsControl и переопределите < a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.getcontainerforitemoverride.aspx" rel="nofollow noreferrer">GetContainerForItemOverride, IsItemItsOwnContainerOverride и, возможно, PrepareContainerForItemOverride для создания/соблюдения Slice элементов управления. (См. ListBox и ListBoxItem, ComboBox и ComboBoxItem и т. д.) Приятной особенностью этого является то, что пользователи могут использовать ItemsSource и DataTemplate (например, ListBox) вместо того, чтобы создавать дочерние элементы управления вручную. (Но пользователи по-прежнему могут создавать фиксированные Slice в XAML в соответствии с вашим синтаксисом, точно так же, как они могут создавать ListBoxItems в XAML.)

Вы даже можете рассмотреть гибридный подход, например. разложение функциональности радиальной компоновки на RadialLayoutPanel и создание сегмента круга на ItemsControl, который использует RadialLayoutPanel в качестве ItemsPanel.

person itowlson    schedule 27.04.2009

Я думаю, что проблема, которую вы видите, заключается в том, что привязки пытаются использовать неправильный источник. Я подозреваю, что что-то вроде этого будет работать для вас:

<c:PieChart>
    <!-- These dependency properties are never set -->
    <c:Slice Value="{Binding RedCount, RelativeSource={AncestorType c:PieChart}}" />
    <c:Slice Value="{Binding BlueCount, RelativeSource={AncestorType c:PieChart}}" />
    <c:Slice Value="{Binding GreenCount, RelativeSource={AncestorType c:PieChart}}" />
</c:PieChart>

Я думаю, что это близко, но я мог бы быть далеко. Я не могу сейчас это посмотреть. Но это звучит хорошо для меня. (Извините, если совсем не так :) )

person MojoFilter    schedule 27.04.2009
comment
Прочитав вопрос еще раз, я не думаю, что эти значения исходят из класса PieChart в первую очередь... что делает это совершенно бессмысленным. Пожалуйста. - person MojoFilter; 28.04.2009
comment
Придирчивый момент - я думаю, что это должно быть {Binding RelativeSource={RelativeSource AncestorType...}} (т. е. вам нужно повторить RelativeSource внутри выражения RelativeSource). Да, раздражает, я знаю. - person itowlson; 28.04.2009
comment
Спасибо, но нет, свойства определены для некоторого объекта контекста внешних данных. Если бы привязки не удались, я бы увидел предупреждающие сообщения в окне вывода VS, но они не отображаются. Я отредактирую пост, чтобы упомянуть об этом. - person Drew Noakes; 28.04.2009