60 votes

Est-il possible de lier une Toile les Enfants de la propriété dans le code XAML?

Je suis un peu surpris qu'il n'est pas possible de mettre en place une liaison pour la Toile.Les enfants par le biais de XAML. J'ai dû recourir à un code-behind approche qui ressemble à quelque chose comme ceci:

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);
}

Je préfère juste mis en place une liaison en XAML comme ceci:

<Canvas x:Name="designerCanvas"
        Children="{Binding Document.Items}"
        Width="{Binding Document.Width}"
        Height="{Binding Document.Height}">
</Canvas>

Est-il un moyen pour accomplir cela sans avoir recours à un code-behind approche? J'ai fait quelques recherches sur google sur le sujet, mais ne viens pas avec beaucoup pour ce problème spécifique.

Je n'aime pas mon approche actuelle, car il devient fou mon beau Modèle-Vue-Vuemodèle en faisant le point de Vue conscient du ViewModel.

146voto

qntmfred Points 10246
<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>

26voto

Pavel Minaev Points 60647

D'autres ont donné extensible réponses sur la façon de faire ce que vous voulez vraiment faire déjà. Je vais juste expliquer pourquoi vous ne pouvez pas lier Children directement.

Le problème est très simple - la liaison de données cible ne peut pas être une propriété en lecture seule, et Panel.Children est en lecture seule. Il n'y a pas de traitement particulier pour les collections. En revanche, ItemsControl.ItemsSource est une propriété en lecture/écriture, même si elle est du type de collection rare occurence pour un .NET de la classe, mais nécessaire pour soutenir le scénario de liaison.

20voto

Josh G Points 7547

ItemsControl est conçu pour créer des collections dynamiques de contrôles d'INTERFACE utilisateur à d'autres collections, même non-INTERFACE de données des collections.

Vous pouvez le modèle de l' ItemsControl de dessiner sur un Canvas. L'idéal serait de fixer le support du panneau à un Canvas puis réglage de l' Canvas.Left et Canvas.Top propriétés sur les enfants immédiats. Je ne pouvais pas obtenir que cela fonctionne, car ItemsControl encapsule les enfants avec des conteneurs et il est difficile de définir l' Canvas des propriétés sur ces conteneurs.

Au lieu de cela, j'utilise un Grid comme une poubelle pour tous les éléments et de les dessiner chacun sur leur propre Canvas. Il y a une surcharge, avec cette approche.

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

Voici le code derrière que j'ai utilisé pour configurer la source de la collecte:

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 est une classe personnalisée qui se comporte comme l' System version. Je l'ai créé pour démontrer que vous pouvez utiliser vos propres classes.

Un dernier détail: Vous pouvez lier la propriété ItemsSource à n'importe quelle collection vous le souhaitez. Par exemple:

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

Pour plus de détails sur ItemsControl et comment il fonctionne, consultez les documents suivants: Référence de MSDN Library; les modèles de Données; Dr WPF série sur ItemsControl.

14voto

Ivan Shikht Points 86
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);
        }
    }
}

Et à l'aide de:

<Canvas x:Name="PART_SomeCanvas"
        Controls:CanvasAssistant.BoundChildren="{TemplateBinding SomeItems}"/>

12voto

Will Points 76760

Je ne crois pas qu'il est possible d'utiliser la liaison avec les Enfants de la propriété. J'ai effectivement essayé de le faire aujourd'hui, et il erronées sur moi comme il l'a fait.

La Toile est une très rudimentaire contenant. Il n'est vraiment pas conçu pour ce genre de travail. Vous devriez regarder dans l'un des nombreux ItemsControls. Vous pouvez lier votre ViewModel de l'ObservableCollection de modèles de données, de leur propriété ItemsSource et l'utilisation DataTemplates pour gérer la façon dont chacun des éléments est rendue dans le contrôle.

Si vous ne pouvez pas trouver un ItemsControl qui rend vos articles de façon satisfaisante, vous devrez créer un contrôle personnalisé qui fait ce que vous avez besoin.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X