WPF: добавление UIElements в ListBox, у которого ItemsPanelTemplate является холстом?

Я работаю над ListBox что переопределяет его ItemsPanelTemplate использовать Canvas вместо StackPanel, ListBoxItems иметь DataTemplate который использует конвертер, чтобы определить внешний вид и положение каждого ListBoxItem на холсте. Когда я добавляю элемент в коллекцию, ListBox Я хотел бы иметь возможность добавлять другие UIElementс холстом. Могу ли я сделать это за пределами ListBoxItemконвертер?

мой раздел ресурсов выглядит так:

    <Style TargetType="ListBox">
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <Canvas x:Key="cnvAwesome">

                    </Canvas>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="ListBoxItem">
        <Setter Property="Canvas.Left" Value="{Binding Path=Position.X}" />
        <Setter Property="Canvas.Top" Value="{Binding Path=Position.Y}" />
    </Style>

И ListBox:

    <ListBox x:Name="lstAwesome" ItemsSource="{Binding Source={StaticResource someCollection}}"/>

И коллекция, с которой связан ListBox:

public ObservableCollection<SomeType> someCollection {get; set; }

Итак, если у меня есть метод для добавления элемента в коллекцию someCollection, с которой связан ListBox lstAwesome:

private void AddItemToDataboundCollection(SomeType someItem)
{
    someCollection.Add(someItem)
    // I'd like to add a UIElement to the canvas that the ListBox uses to layout items here.
}

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

Буду очень признателен за любой вклад!

2 ответа

Решение

Я думаю, что у вас есть все в вашем вопросе, чтобы добавить элементы в Canvas вашей ListBox, Я просто собрал простое тестовое приложение WPF, используя код в вашем вопросе, и это просто сработало. Всякий раз, когда я добавил предметы в ObservableCollection это связано с ItemsSource из ListBoxWPF создаст ListBoxItem для этого объекта, и в зависимости от стиля ListBoxItemэлемент будет нарисован в правильном месте Canvas,

Вы не смогли заставить ваш код работать выше? Если это так, какую ошибку вы получили или что не сработало, как вы ожидали?

Изменить: После прочтения вопроса снова, я думаю, что путаница из-за неправильного понимания того, как работает привязка WPF. Когда вы связываете коллекцию с ListBox"s ItemsSourceвсе, что вам нужно сделать, это добавить элементы в эту коллекцию (при условии, что коллекция реализует INotifyCollectionChanged, который ObservableCollection делает). WPF подписывается на события, предоставляемые этим интерфейсом, и создает / уничтожает ListBoxItems на основе изменений, внесенных в список.

Редактировать 2: Хотя было бы лучше использовать DataTemplate для этого, чтобы строка автоматически добавлялась при добавлении элемента в коллекцию, я не уверен, как можно получить положение ранее добавленного элемента, чтобы линия начиналась в нужном месте.

Один вариант, который мог бы работать, был бы подкласс Canvasи установите это как ItemsPanelTemplate для ListBox, Я считаю, что у него есть функция, которую вы можете переопределить, которая вызывается всякий раз, когда к ней добавляется элемент управления. В этой функции вы можете получить позицию ранее добавленного элемента управления (который будет кэшироваться в вашем Canvas подкласс), а затем добавить к элементу управления линии к элементам управления Canvas непосредственно. Это предполагает, что положение органов управления в Canvas не может измениться Если они могут, вам, вероятно, придется подписаться на PropertyChanged событие и обновите строки соответственно.

Редактировать 3: Поскольку привязка не выглядит как вариант из-за ваших требований, наименее плохой вариант - просто добавить дочерние элементы напрямую. Вы можете сделать это, просматривая визуальное дерево с помощью VisualTreeHelper.GetChild ():

private T FindChild<T>(FrameworkElement parent, string name)
    where T : FrameworkElement
{
    FrameworkElement curObject = null;
    T obj = default(T);
    int count = VisualTreeHelper.GetChildrenCount(parent);
    for(int i = 0; i < count; i++)
    {
        curObject = VisualTreeHelper.GetChild(parent, i) as FrameworkElement;
        obj = curObject as T;
        if(null != obj && obj.Name.Equals(name))
        {
            break;
        }

        obj = FindChild<T>(curObject, name);
        if(null != obj)
        {
            break;
        }
    }

    return obj;
}

Используйте это так:

Canvas canvas = FindChild<Canvas>(lstAwesome, "cnvAwesome");

Этот код рекурсивно ищет в визуальном дереве parent для контроля указанного типа и имени. Было бы лучше использовать DependencyObject над FrameworkElement здесь (так как это то, что возвращается VisualTreeHelper.GetChild()), тем не мение Name определяется только на FrameworkElement,

Если вы хотите стать модным, вы даже можете сделать это методом расширения.

Вы определяете способ отображения ваших предметов, устанавливая ItemTemplate, лайк:

<ListBox.ItemTemplate>
    <DataTemplate>
        <Rectangle Width="{Binding Width}" Height="{Binding Height}" />
    </DataTemplate>
</ListBox.ItemTemplate>

Если вы хотите, чтобы ваш ItemTemplate быть измененным в зависимости от вашего элемента, то вы можете установить ItemTemplateSelector и решить, какой ItemTemplate использоваться программно.

Другие вопросы по тегам