Подтвердить что ты не робот

Можно ли связать свойство 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.

4b9b3361

Ответ 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}"/>