2009-05-20 127 views
54

我有點驚訝,無法通過XAML爲Canvas.Children設置綁定。我不得不求助於代碼隱藏的辦法,看起來是這樣的:是否可以在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> 

是否有無需訴諸代碼隱藏方法即可完成此任務?我在這個主題上做了一些Google搜索,但對於這個特定的問題還沒有提出太多。

我不喜歡我當前的方法,因爲它通過使視圖察覺到它的ViewModel來隱藏我的不錯的Model-View-ViewModel。

回答

8

我不相信它可能使用綁定與Children屬性。我其實今天試圖這樣做,它就像你一樣對我產生了誤解。

畫布是一個非常基本的容器。它確實不是爲這種工作而設計的。你應該看看其中一個ItemsControls。您可以將ViewModel的ObservableCollection數據模型綁定到它們的ItemsSource屬性,並使用DataTemplates來處理每個項目在控件中的呈現方式。

如果您找不到令人滿意地渲染項目的ItemsControl,則可能需要創建一個自定義控件來執行所需的操作。

18

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 Reference; Data Templating; Dr WPF's series on ItemsControl

+2

這是否有性能問題,因爲你增加了對要繪製的每個點一個全新的印刷品嗎? – Benjamin 2016-03-08 19:20:12

137
<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> 
+0

...或者你可以在我提到的容器上設置Canvas.Left和Canvas.Top。這是要走的路。 – 2010-03-09 21:40:49

24

其他人已經給出了關於如何做你已經想要做的事情的可擴展回覆。我只是解釋你爲什麼不能直接綁定Children

問題很簡單 - 數據綁定目標不能是隻讀屬性,並且Panel.Children是隻讀的。對那裏的收藏沒有特別的處理。相比之下,ItemsControl.ItemsSource是一個讀/寫屬性,儘管它是集合類型 - 這是.NET類的罕見事件,但是需要支持綁定方案。

11
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}"/> 
相關問題