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であることをViewに認識させることでNice Model-View-ViewModelをマックアップするため、現在のアプローチが気に入らない。
Childrenプロパティでバインディングを使用できるとは思わない。私は実際に今日それをやろうとしましたが、あなたがしたようにそれは私にエラーを起こしました。
Canvasは非常に初歩的なコンテナです。それは本当にこの種の仕事のために設計されていません。多くのItemsControlの1つを調べる必要があります。 ViewModelのデータモデルのObservableCollectionを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
は、非UIデータコレクションであっても、他のコレクションからUIコントロールの動的コレクションを作成するために設計されています。
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ライブラリリファレンス ; データのテンプレート化 ; ItemsControlのWPF博士シリーズ 。
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}"/>