Связывание с элементами UserControl с пользовательским свойством коллекции - PullRequest
5 голосов
/ 25 сентября 2011

Этот вопрос является "продолжением" этого вопроса (я применил ответ, но он все равно не будет работать).

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

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

У меня есть следующий код XAML:

<Window Name="myWindow" DataContext="{Binding ElementName=myWindow}" >
    <DockPanel>
        <tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}>
            <tb:ToolBar.Items>
                <tb:ToolBarControl Priority="-3">
                    <tb:ToolBarControl.Content>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock>Maps:</TextBlock>
                            <ComboBox ItemsSource="{Binding SomeProperty, ElementName=myWindow}">

Некоторая информация о типах:

  • tb:ToolBar - это UserControl со свойством зависимости Items типа FreezableCollection<ToolBarControl>.

  • tb:ToolBarControl - это UserControl с шаблоном, очень похожим на шаблон ContentControl .

Проблема в том, что связывание в ComboBox не выполняется (с обычным «Не удается найти источник для привязки со ссылкой»), поскольку его DataContext имеет значение null.

Почему?

РЕДАКТИРОВАТЬ: Суть вопроса «Почему DataContext не наследуется?», Без него привязки не могут работать.

EDIT2:

Вот XAML для tb:ToolBar:

<UserControl ... Name="toolBarControl">
    <ToolBarTray>
        <ToolBar ItemsSource="{Binding Items, ElementName=toolBarControl}" Name="toolBar" ToolBarTray.IsLocked="True" VerticalAlignment="Top" Height="26">

РЕДАКТИРОВАТЬ 3:

Я опубликовал пример того, что работает, а что нет: http://pastebin.com/Tyt1Xtvg

Спасибо за ваши ответы.

Ответы [ 4 ]

3 голосов
/ 23 ноября 2011

Мне лично не нравится идея установки DataContext в элементах управления.Я думаю, что выполнение этого каким-то образом нарушит наследование контекста данных.Пожалуйста, посмотрите на этот пост .Я думаю, что Саймон объяснил это довольно хорошо.

По крайней мере, попробуйте удалить

DataContext="{Binding ElementName=myWindow}"

из

<tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}> 

и посмотрите, как это происходит.

ОБНОВЛЕНИЕ

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

<ComboBox ItemsSource="{Binding SomeProperty, ElementName=myWindow}"> 

на

<ComboBox ItemsSource="{Binding DataContext.SomeProperty}"> 

и посмотрите, работает ли он.

Я думаю, из-за того, как вы структурируете свои элементы управления, ComboBox находится на том же уровне / уровне, что и tb:ToolBarControl и tb:ToolBar.Это означает, что все они используют один и тот же DataContext, поэтому вам не требуется привязка ElementName или RelativeSource, чтобы попытаться найти своего родителя / предка.

Если вы удалите DataContext="{Binding ElementName=myWindow} изtb:ToolBar, вы даже можете избавиться от префикса DataContext в привязке.И это действительно все, что вам нужно.

<ComboBox ItemsSource="{Binding SomeProperty}"> 

ОБНОВЛЕНИЕ 2 , чтобы ответить на ваш Изменить 3

Это потому, что ваша коллекция Itemsв вашем tb:ToolBar usercontrol это просто свойство.Это не в логическом и визуальном дереве, и я считаю, что ElementName привязка использует логическое дерево.

Вот почему оно не работает.

Добавить в логическое дерево

Я думаю, чтобы добавить Items в логическое дерево, вам нужно сделать две вещи.

Сначала вам нужно переопределить LogicalChildren в вашем tb:ToolBar пользовательском контроле.

    protected override System.Collections.IEnumerator LogicalChildren
    {
        get
        {
            if (Items.Count == 0)
            {
                yield break;
            }

            foreach (var item in Items)
            {
                yield return item;
            }
        }
    }

Тогда всякий раз, когда вы добавляете новый tb:ToolBarControl, вам нужно позвонить

AddLogicalChild(item);

Дайте ему шанс.

Это РАБОТАЕТ ...

Немного поиграв с этим, я думаю, что того, что я показал вам выше, недостаточно.Вам также необходимо добавить эти ToolBarControls в область имен вашего основного окна, чтобы включить привязку ElementName.Я предполагаю, что именно так вы определили свойство зависимости Items.

public static DependencyProperty ItemsProperty =
    DependencyProperty.Register("Items",
                                typeof(ToolBarControlCollection),
                                typeof(ToolBar),
                                new FrameworkPropertyMetadata(new ToolBarControlCollection(), Callback));

В обратном вызове это место, где вы добавляете его в область имен.

private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var toolbar = (ToolBar)d;
    var items = toolbar.Items;

    foreach (var item in items)
    {
        // the panel that contains your ToolBar usercontrol, in the code that you provided it is a DockPanel
        var panel = (Panel)toolbar.Parent;
        // your main window
        var window = panel.Parent;
        // add this ToolBarControl to the main window's name scope
        NameScope.SetNameScope(item, NameScope.GetNameScope(window));

        // ** not needed if you only want ElementName binding **
        // this enables bubbling (navigating up) in the visual tree
        //toolbar.AddLogicalChild(item);
    }
}

Также, если вы хотите наследовать свойства, вам потребуется

// ** not needed if you only want ElementName binding **
// this enables tunneling (navigating down) in the visual tree, e.g. property inheritance
//protected override System.Collections.IEnumerator LogicalChildren
//{
//    get
//    {
//        if (Items.Count == 0)
//        {
//            yield break;
//        }

//        foreach (var item in Items)
//        {
//            yield return item;
//        }
//    }
//}

Я проверил код, и он отлично работает.

2 голосов
/ 24 ноября 2011

Я взял фрагменты Xaml, которые вы опубликовали, и попытался воспроизвести вашу проблему.

Кажется, что DataContext очень хорошо наследует то, что я могу сказать.Тем не менее, ElementName привязки терпят неудачу, и я думаю, что это связано с тем фактом, что даже если вы добавите ComboBox в Window, он окажется в другой области видимости. (сначала он добавляется в свойство Items пользовательского ToolBar, а затем заполняется в структуру ToolBar с привязкой)

A RelativeSource Связывание вместоElementName Binding, кажется, работает нормально.

Но если вы действительно хотите использовать имя элемента управления в Binding, вы можете проверить Отличную реализацию ObjectReference Dr.WPF

Это выглядело бычто-то вроде этого

<Window ...
        tb:ObjectReference.Declaration="{tb:ObjectReference myWindow}">
<!--...-->
<ComboBox ItemsSource="{Binding Path=SomeProperty,
                                Source={tb:ObjectReference myWindow}}"

Я загрузил небольшой пример проекта, в котором здесь успешно используются RelativeSource и ObjectReference: https://www.dropbox.com/s/tx5vdqlm8mywgzw/ToolBarTest.zip?dl=0

Пользовательская часть ToolBar, как я приблизительноэто выглядит так в Window.
ElementName Привязка не выполняется, но RelativeSource и ObjectReference Привязки работают

<Window ...
    Name="myWindow"
    tb:ObjectReference.Declaration="{tb:ObjectReference myWindow}">
<!--...-->
<tb:ToolBar x:Name="toolbar"
            DockPanel.Dock="Top"
            DataContext="{Binding ElementName=myWindow}">
    <tb:ToolBar.Items>
        <tb:ContentControlCollection>
            <ContentControl>
                <ContentControl.Content>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock>Maps:</TextBlock>
                        <ComboBox ItemsSource="{Binding Path=StringList,
                                                        ElementName=myWindow}"
                                    SelectedIndex="0"/>
                        <ComboBox ItemsSource="{Binding Path=StringList,
                                                        Source={tb:ObjectReference myWindow}}"
                                    SelectedIndex="0"/>
                        <ComboBox ItemsSource="{Binding Path=StringList,
                                                        RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                                    SelectedIndex="0"/>
                    </StackPanel>
                </ContentControl.Content>
            </ContentControl>
        </tb:ContentControlCollection>
    </tb:ToolBar.Items>
</tb:ToolBar>
1 голос
/ 25 сентября 2011

Часто, если нет DataContext, то ElementName также не будет работать.Одна вещь, которую вы можете попробовать, если позволяет ситуация, использует x:Reference.

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

<Window Name="myWindow" DataContext="{Binding ElementName=myWindow}" >
    <Window.Resources>
        <ComboBox x:Key="cb"
                  ItemsSource="{Binding SomeProperty,
                                        Source={x:Reference myWindow}}"/>
    </Window.Resources>
    <DockPanel>
        <tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}>
            <tb:ToolBar.Items>
                <tb:ToolBarControl Priority="-3">
                    <tb:ToolBarControl.Content>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock>Maps:</TextBlock>
                            <StaticResource ResourceKey="cb"/>
0 голосов
/ 16 августа 2012

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

Напишите свой собственный Binding MarkupExtension, который возвращает вас к корневому элементу вашего файла XAML. Этот код не был скомпилирован, так как я взломал свой настоящий код, чтобы опубликовать это.

[MarkupExtensionReturnType(typeof(object))]
public class RootBindingExtension : MarkupExtension
{
    public string Path { get; set; }

    public RootElementBinding(string path)
    {
        Path = path;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        IRootObjectProvider rootObjectProvider =
            (IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider));

        Binding binding = new Binding(this.Path);
        binding.Source = rootObjectProvider.RootObject;

        // Return raw binding if we are in a non-DP object, like a Style
        if (service.TargetObject is DependencyObject == false)
            return binding;

        // Otherwise, return what a normal binding would
        object providedValue = binding.ProvideValue(serviceProvider);

        return providedValue;
    }
}

Использование:

<ComboBox ItemsSource={myBindings:RootBinding DataContext.SomeProperty} />
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...