【问题标题】:Is it possible to bind a Canvas's Children property in XAML?是否可以在 XAML 中绑定 Canvas 的 Children 属性?
【发布时间】:2010-10-27 18:01:55
【问题描述】:

我有点惊讶,无法通过 XAML 为 Canvas.Children 设置绑定。我不得不求助于看起来像这样的代码隐藏方法:

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>

有没有办法在不采用代码隐藏方法的情况下完成此任务?我已经对这个主题进行了一些谷歌搜索,但没有针对这个特定问题提出太多建议。

我不喜欢我目前的方法,因为它让 View 意识到它是 ViewModel,从而破坏了我漂亮的 Model-View-ViewModel。

【问题讨论】:

    标签: c# wpf data-binding .net-3.5 canvas


    【解决方案1】:
    <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>
    

    【讨论】:

    • ...或者您可以在我提到的容器上设置 Canvas.Left 和 Canvas.Top 。这是要走的路。
    • 几乎没有 - 您的解决方案为每个项目创建单独的画布 - 此解决方案更加优雅和高效,因为只有一个画布用于呈现所有项目
    • 一件事让我花了 3 个小时寻找错误:{Binding Path=Circles}。当您绑定到窗口类公共属性时,您应该这样做:{Binding Path=Circles ElementName=$WindowName$}。否则它将无法正常工作(至少在 VS 2012 中)。
    • 以防万一有人像我一样尝试在 WinRT 上执行此操作:那里的设置器不支持绑定 - stackoverflow.com/questions/11857505/…
    【解决方案2】:

    其他人已经就如何做你真正想做的事情给出了可扩展的回答。我只是解释为什么你不能直接绑定Children

    问题很简单——数据绑定目标不能是只读属性,而Panel.Children是只读的。那里的收藏没有特殊处理。相比之下,ItemsControl.ItemsSource 是一个读/写属性,尽管它是集合类型 - 对于 .NET 类来说很少出现,但为了支持绑定场景是必需的。

    【讨论】:

      【解决方案3】:

      ItemsControl 设计用于从其他集合(甚至非 UI 数据集合)创建 UI 控件的动态集合。

      您可以模板 ItemsControl 以在 Canvas 上绘图。理想的方法是将支持面板设置为Canvas,然后在直接子级上设置Canvas.LeftCanvas.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 ReferenceData Templating; Dr WPF's series on ItemsControl.

      【讨论】:

      • 这是否存在性能问题,因为您要为每个要绘制的点添加一个全新的画布?
      【解决方案4】:
      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}"/>
      

      【讨论】:

        【解决方案5】:

        我认为不可能对 Children 属性使用绑定。实际上我今天尝试这样做,但它像你一样对我犯了错误。

        Canvas 是一个非常基本的容器。它真的不是为这种工作而设计的。您应该查看许多 ItemsControls 之一。您可以将 ViewModel 的数据模型 ObservableCollection 绑定到它们的 ItemsSource 属性,并使用 DataTemplates 来处理每个项目在控件中的呈现方式。

        如果您找不到以令人满意的方式呈现您的项目的 ItemsControl,您可能必须创建一个自定义控件来满足您的需要。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-30
          • 1970-01-01
          • 1970-01-01
          • 2019-08-18
          • 1970-01-01
          相关资源
          最近更新 更多