Можно ли связать свойство Canvas's 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 знать о его ViewModel.
5 ответов
Я не верю, что можно использовать связывание со свойством Children. Я на самом деле пытался сделать это сегодня, и он ошибся, как и ты.
Холст - очень элементарный контейнер. Он действительно не предназначен для такой работы. Вы должны заглянуть в один из множества ItemsControls. Вы можете привязать ObservableCollection вашей ViewModel моделей данных к их свойству ItemsSource и использовать DataTemplates для обработки того, как каждый из элементов отображается в элементе управления.
Если вы не можете найти ItemsControl, который удовлетворительным образом отображает ваши элементы, вам, возможно, придется создать собственный элемент управления, который делает то, что вам нужно.
<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>
Другие дали расширяемые ответы о том, как сделать то, что вы на самом деле хотите сделать. Я просто объясню, почему вы не могли связать Children
непосредственно.
Проблема очень проста - цель привязки данных не может быть свойством только для чтения, и Panel.Children
только для чтения. Там нет специальной обработки для коллекций там. По сравнению, ItemsControl.ItemsSource
является свойством чтения / записи, хотя оно имеет тип коллекции - редкий случай для класса.NET, но требуется для поддержки сценария связывания.
ItemsControl
предназначен для создания динамических коллекций элементов управления пользовательского интерфейса из других коллекций, даже коллекций данных, не относящихся к пользовательскому интерфейсу.
Вы можете шаблон ItemsControl
рисовать на Canvas
, Идеальный способ - установить заднюю панель на Canvas
а затем настройку Canvas.Left
а также Canvas.Top
свойства на ближайших детей. Я не мог заставить это работать, потому что ItemsControl
оборачивает своих детей контейнерами, и трудно установить Canvas
свойства на этих контейнерах.
Вместо этого я использую Grid
в качестве корзины для всех предметов и нарисуйте их каждый самостоятельно 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 Library Reference; Шаблонизация данных; Серия доктора WPF по ItemsControl.
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}"/>