60

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>

コード ビハインド アプローチに頼らずにこれを達成する方法はありますか? この件についてグーグルで調べましたが、この特定の問題についてはあまり思いつきませんでした。

ビューにそれがビューモデルであることを認識させることによって、私の素敵なモデル-ビュー-ビューモデルを台無しにするので、私は現在のアプローチが好きではありません。

4

5 に答える 5

26

他の人は、あなたが実際にやりたいことをどのように行うかについて、拡張可能な回答をしています。直接バインドできなかった理由を説明しますChildren

問題は非常に単純です。データ バインディング ターゲットを読み取り専用プロパティにすることはできず、読み取りPanel.Children専用です。そこにはコレクションの特別な取り扱いはありません。対照的に、ItemsControl.ItemsSourceコレクション型であっても読み取り/書き込みプロパティです。.NET クラスではまれにしか発生しませんが、バインディング シナリオをサポートするために必要です。

于 2009-07-30T21:57:06.170 に答える
20

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

をテンプレート化しItemsControlて に描画できCanvasます。理想的な方法は、バッキング パネルを aCanvasに設定してから、直接の子に プロパティCanvas.Leftとプロパティを設定することです。子をコンテナでラップし、これらのコンテナにプロパティを設定するのが難しいCanvas.Topため、これを機能させることができませんでした。ItemsControlCanvas

代わりに、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;

MyPointSystemバージョンと同じように動作するカスタム クラスです。独自のカスタム クラスを使用できることを示すために作成しました。

最後の詳細: ItemsSource プロパティを任意のコレクションにバインドできます。例えば:

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

ItemsControl とその機能の詳細については、次のドキュメントを参照してください。MSDN ライブラリ リファレンスデータ テンプレート; ItemsControl に関する WPF 博士のシリーズ

于 2009-05-20T20:35:34.240 に答える
14
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}"/>
于 2010-09-20T12:37:00.530 に答える
12

Children プロパティでバインディングを使用できるとは思いません。私は実際に今日それをやろうとしましたが、あなたと同じようにエラーが発生しました.

Canvas は非常に初歩的なコンテナです。この種の作業用には設計されていません。多くの ItemsControls の 1 つを調べる必要があります。データ モデルの ViewModel の ObservableCollection を ItemsSource プロパティにバインドし、DataTemplates を使用して各項目がコントロールでどのようにレンダリングされるかを処理できます。

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

于 2009-05-20T20:05:07.343 に答える