2009-05-20 17 views
54

XAMLを使用してCanvas.Childrenのバインディングを設定することはできないことに少し驚いています。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> 

がありますコードビハインドアプローチに頼ることなくこれを達成する方法?私は主題についていくつかの検索をしましたが、この特定の問題についてはあまり出ていません。

ViewModelをViewModelに認識させることで、私の素敵なModel-View-ViewModelを台無しにするので、私の現在のアプローチが嫌いです。

答えて

8

私はChildrenプロパティでバインディングを使用することはできません。私は実際にそれをやろうとしましたが、それはあなたのように私の上で起こりました。

キャンバスは非常に基本的なコンテナです。これは実際にこの種の作業のために設計されたものではありません。あなたは、多くのItemsControlsの1つを調べる必要があります。 ViewModelのデータモデルのObservableCollectionをItemsSourceプロパティにバインドし、DataTemplatesを使用して、各アイテムがコントロールでレンダリングされる方法を処理できます。

アイテムを満足のいく方法でレンダリングするItemsControlが見つからない場合は、必要なものを実行するカスタムコントロールを作成する必要があります。

18

ItemsControlは、UI以外のデータコレクションであっても、他のコレクションからのUIコントロールの動的なコレクションを作成するために設計されています。

ItemsControlをテンプレートにして、Canvasに描画することができます。理想的な方法は、バッキングパネルをCanvasに設定し、直下の子にCanvas.LeftCanvas.Topのプロパティを設定することです。 ItemsControlはコンテナでその子をラップしており、これらのコンテナにはCanvasのプロパティを設定することができないため、これを動作させることができませんでした。

代わりに、Gridをすべてのアイテムのビンとして使用し、それぞれ独自のCanvasで描画します。このアプローチにはいくつかのオーバーヘッドがあります。

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バージョンと同じように動作するカスタムクラスです:

<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> 

は、ここで私はソースコレクションを設定するために使用されたものの背後にあるコードです。独自のカスタムクラスを使用できることを示すために作成しました。

最終的な詳細:ItemsSourceプロパティを任意のコレクションにバインドできます。たとえば:ItemsControlに、どのように動作についての詳細は

<ItemsControls ItemsSource="{Binding Document.Items}"><!--etc, etc...--> 

、これらの文書をチェックアウト:MSDN Library ReferenceData Templating; Dr WPF's series on ItemsControl

+2

これは、パフォーマンスの問題を持っていませんか? – Benjamin

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を設定することができます。これが行く方法です。 –

+13

あなたのソリューションはアイテムごとに個別のキャンバスを作成しません。このソリューションは、すべてのアイテムをレンダリングするために1つのキャンバスだけが使用されるので、はるかにエレガントで効率的です – Xcalibur

+0

{Binding Path = Circles} 。ウィンドウクラスpublicプロパティにバインドするときは、{Binding Path = Circles ElementName = $ WindowName $}のようにする必要があります。それ以外の場合は、動作しません(少なくともVS 2012では)。 – Nebril

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}"/> 
関連する問題