Можно ли связать свойство Canvas Children в XAML?
Я немного удивлен, что невозможно установить привязку для Canvas.Children через XAML. Мне приходилось прибегать к подходу с кодом, который выглядит примерно так:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
DesignerViewModel dvm = this.DataContext as DesignerViewModel;
dvm.Document.Items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Items_CollectionChanged);
foreach (UIElement element in dvm.Document.Items)
designerCanvas.Children.Add(element);
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<UIElement> collection = sender as ObservableCollection<UIElement>;
foreach (UIElement element in collection)
if (!designerCanvas.Children.Contains(element))
designerCanvas.Children.Add(element);
List<UIElement> removeList = new List<UIElement>();
foreach (UIElement element in designerCanvas.Children)
if (!collection.Contains(element))
removeList.Add(element);
foreach (UIElement element in removeList)
designerCanvas.Children.Remove(element);
}
Я бы скорее просто установил привязку в XAML следующим образом:
<Canvas x:Name="designerCanvas"
Children="{Binding Document.Items}"
Width="{Binding Document.Width}"
Height="{Binding Document.Height}">
</Canvas>
Есть ли способ сделать это, не прибегая к подходу, основанному на коде? Я сделал некоторые поиски по этой теме, но не придумал много для этой конкретной проблемы.
Мне не нравится мой текущий подход, потому что он объединяет мой красивый Model-View-ViewModel, позволяя View View ViewModel.
Ответы
Ответ 1
Я не считаю возможным использовать привязку к свойству Children. Я на самом деле пытался сделать это сегодня, и это на меня напало, как будто это вы.
Холст - очень рудиментарный контейнер. Он действительно не предназначен для такого рода работ. Вы должны изучить один из многих элементов ItemsControls. Вы можете привязать ViewModel ObservableCollection моделей данных к свойству ItemsSource и использовать DataTemplates для обработки того, как каждый элемент отображается в элементе управления.
Если вы не можете найти элемент ItemsControl, который удовлетворяет удовлетворительные результаты, вам может потребоваться создать настраиваемый элемент управления, который сделает то, что вам нужно.
Ответ 2
<ItemsControl ItemsSource="{Binding Path=Circles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White" Width="500" Height="500" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="{Binding Path=Color, Converter={StaticResource colorBrushConverter}}" Width="25" Height="25" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Ответ 3
Другие дали расширенные ответы о том, как делать то, что вы на самом деле хотите сделать уже. Я просто объясню, почему вы не могли напрямую привязать Children
.
Проблема очень проста: цель привязки данных не может быть только для чтения, а Panel.Children
доступна только для чтения. Там нет специальной обработки для коллекций. Напротив, ItemsControl.ItemsSource
- свойство чтения/записи, даже если оно относится к типу коллекции - редкое явление для .NET-класса, но требуется для поддержки сценария привязки.
Ответ 4
ItemsControl
предназначен для создания динамических коллекций элементов управления пользовательского интерфейса из других коллекций, даже для коллекций данных, отличных от UI.
Вы можете создать шаблон ItemsControl
для рисования на Canvas
. Идеальный способ - установить панель поддержки на Canvas
а затем установить свойства Canvas.Left
и Canvas.Top
для непосредственных детей. Я не мог заставить это работать, потому что ItemsControl
обертывает своих детей контейнерами, и трудно установить свойства Canvas
в этих контейнерах.
Вместо этого я использую Grid
как bin для всех предметов и рисую их каждый на своем Canvas
. При таком подходе есть некоторые накладные расходы.
<ItemsControl x:Name="Collection" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MyPoint}">
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Ellipse Width="10" Height="10" Fill="Black" Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Вот код, который я использовал для создания исходной коллекции:
List<MyPoint> points = new List<MyPoint>();
points.Add(new MyPoint(2, 100));
points.Add(new MyPoint(50, 20));
points.Add(new MyPoint(200, 200));
points.Add(new MyPoint(300, 370));
Collection.ItemsSource = points;
MyPoint
- это настраиваемый класс, который ведет себя как версия System
. Я создал его, чтобы продемонстрировать, что вы можете использовать свои собственные пользовательские классы.
Одна деталь: вы можете привязать свойство ItemsSource к любой коллекции, которую хотите. Например:
<ItemsControls ItemsSource="{Binding Document.Items}"><!--etc, etc...-->
Для получения дополнительной информации о ItemsControl и о том, как это работает, ознакомьтесь с этими документами: Справочник по библиотеке MSDN; Шаблоны данных; Dr WPF series на ItemsControl.
Ответ 5
internal static class CanvasAssistant
{
#region Dependency Properties
public static readonly DependencyProperty BoundChildrenProperty =
DependencyProperty.RegisterAttached("BoundChildren", typeof (object), typeof (CanvasAssistant),
new FrameworkPropertyMetadata(null, onBoundChildrenChanged));
#endregion
public static void SetBoundChildren(DependencyObject dependencyObject, string value)
{
dependencyObject.SetValue(BoundChildrenProperty, value);
}
private static void onBoundChildrenChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if (dependencyObject == null)
{
return;
}
var canvas = dependencyObject as Canvas;
if (canvas == null) return;
var objects = (ObservableCollection<UIElement>) e.NewValue;
if (objects == null)
{
canvas.Children.Clear();
return;
}
//TODO: Create Method for that.
objects.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
foreach (object item in args.NewItems)
{
canvas.Children.Add((UIElement) item);
}
if (args.Action == NotifyCollectionChangedAction.Remove)
foreach (object item in args.OldItems)
{
canvas.Children.Remove((UIElement) item);
}
};
foreach (UIElement item in objects)
{
canvas.Children.Add(item);
}
}
}
И используя:
<Canvas x:Name="PART_SomeCanvas"
Controls:CanvasAssistant.BoundChildren="{TemplateBinding SomeItems}"/>