7

Ich habe eine KontrolleWPF - Bindung eine ObservableCollection Abhängigkeitseigenschaft innerhalb eines Usercontrol

Klasse DragGrid: Gitter { ... }

, die aus dem ursprünglichen Raster erbt und ermöglicht Drag und Ändern der Größe seiner untergeordneten Elemente. Ich brauche eine benutzerdefinierte zu binden DP namens WorkItemsProperty zu einer beobachtbaren Sammlung von Typ WorkItem (implementiert INotifyPropertyChanged). Jedes Element im Raster ist an ein Sammlungselement gebunden.

Wenn der Benutzer dynamisch ein neues Element zur Laufzeit hinzufügt (die Elemente können nicht in XAML deklariert werden!) Oder löscht ein Element aus dieser Auflistung, sollte die WorkItems DP auf dem DragGrid aktualisiert werden, und die untergeordneten Elemente im Raster (wobei jedes Kind einen WorkItem Sammelgegenstand darstellt).

Meine Frage ist, wie funktioniert die DP die Steuerung mitteilen, über die untergeordnete Element in dem Gitter entfernt werden muss, geändert (‚Änderung‘ Benutzer ein Element gezogen wird, oder die Größe veränderte es mit der Maus) oder hinzugefügt, und wie würde ich identifizieren, welches der vorhandenen Kinder das ist, das gelöscht oder geändert werden muss. Ich verstehe, dass hier die DependencyPropertyChangedCallback kommt. Aber das wird nur aufgerufen, wenn die DP-Eigenschaft neu festgelegt wird, nicht wenn etwas innerhalb der Sammlung ändert (wie hinzufügen, entfernen Sie Element). Also, am Ende, muss die DragGrid Steuerung irgendwie das CollectionChanged-Ereignis abonnieren? An welchem ​​Punkt würde ich den Event-Handler dafür anschließen?

* EDIT :: Der Grund, ein Gitter in erster Linie für den Einsatz ist, weil ich in der Lage sein wollen, für ein Minimum Delta zu halten, wenn der Benutzer zieht oder passt die Steuerung im Grid. Ein Steuerelement stellt eine Zeitspanne dar, und jede Rasterspalte repräsentiert 15 Minuten (das ist der Mindestwert). Dies in einem Canvas mit Thumbs zu tun war schwierig und fehlerhaft. Das Implementieren eines DragGrid löste meine Benutzerinteraktionsprobleme. Außerdem ist ein Canvas nicht skalierbar, daher müssten die Zeitspannen ständig neu berechnet werden. Mit dem Grid habe ich das Problem nicht, da die Spalten mir die Uhrzeit egal welche Größe sagen. **

Antwort

16

In Antwort auf Ihre eigentliche Frage:

Sie sollten einen DepencyPropertyChanged Handler hinzufügen, wie Sie bereits erwähnt. In diesem Handler, sollten Sie einen Event-Handler auf die Collection Eigenschaft auf der neuen Kollektion und entfernen Sie den Handler aus der alten Sammlung, wie folgt hinzufügen:

public ObservableCollection<WorkItem> WorkItems 
    { 
     get { return (ObservableCollection<WorkItem>)GetValue(WorkItemsProperty); } 
     set { SetValue(WorkItemsProperty, value); } 
    } 

    // Using a DependencyProperty as the backing store for WorkItems. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty WorkItemsProperty = 
     DependencyProperty.Register("WorkItems", typeof(ObservableCollection<WorkItem>), typeof(DragGrid), new FrameworkPropertyMetadata(null, OnWorkItemsChanged)); 

    private static void OnWorkItemsChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     DragGrid me = sender as DragGrid; 

     var old = e.OldValue as ObservableCollection<WorkItem>; 

     if (old != null) 
      old.CollectionChanged -= me.OnWorkCollectionChanged; 

     var n = e.NewValue as ObservableCollection<WorkItem>; 

     if (n != null) 
      n.CollectionChanged += me.OnWorkCollectionChanged; 
    } 

    private void OnWorkCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     if (e.Action == NotifyCollectionChangedAction.Reset) 
     { 
      // Clear and update entire collection 
     } 

     if (e.NewItems != null) 
     { 
      foreach (WorkItem item in e.NewItems) 
      { 
       // Subscribe for changes on item 
       item.PropertyChanged += OnWorkItemChanged; 

       // Add item to internal collection 
      } 
     } 

     if (e.OldItems != null) 
     { 
      foreach (WorkItem item in e.OldItems) 
      { 
       // Unsubscribe for changes on item 
       item.PropertyChanged -= OnWorkItemChanged; 

       // Remove item from internal collection 
      } 
     } 
    } 

    private void OnWorkItemChanged(object sender, PropertyChangedEventArgs e) 
    { 
     // Modify existing item in internal collection 
    } 

Wie gehho erklärt, es klingt wie Sie das Grid verwenden Klasse wie ursprünglich beabsichtigt, obwohl Sie möglicherweise zu weit in Entwicklung sind, um an dieser Stelle von vorne beginnen zu wollen. Von Panel abgeleitete Klassen sind eigentlich nur dazu gedacht, ihre Kinder visuell zu zeichnen/anzuordnen, anstatt sie zu manipulieren und zu verbessern. Weitere Informationen erhalten Sie unter ItemsControl und WPF Content Model.

+0

Josh, danke dafür. Im collectionchanged-Ereignis kann ich der internen Auflistung von Arbeitselementen nicht hinzufügen, da .NET dies nicht zulässt. Ich denke, was Sie hier gemeint haben, ist, die Steuerelemente der Children-Kollektion des Rasters hinzuzufügen. – John

+1

Nein, eigentlich meinte ich es zu deiner Sammlung hinzuzufügen + der Children-Sammlung des Rasters oder etc. Ich bin mir nicht sicher, wie genau deine Kontrolle funktioniert, aber es ist offensichtlich, dass du Arbeit machst, um eine Sammlung von Gegenständen zu verwalten. Basierend auf Ihrem Kommentar klingt es so, als würden Sie die Grid.Children-Sammlung verwalten, also fügen Sie sie dort hinzu. –

+0

Idealerweise würden Sie ein ItemsControl (oder möglicherweise eine benutzerdefinierte untergeordnete Klasse daraus nehmen, nicht sicher, ohne Ihr Problem besser zu verstehen), und Sie würden die WorkItems als untergeordnete Elemente des ItemsControl festlegen. Sie würden dann ItemsPanelTemplate verwenden, um die Elemente anzuzeigen ein Gitter. Sie würden eine DataTemplate verwenden, um die tatsächlichen Visuals für die Arbeitselemente zu generieren. DataBinding für das Elementvorlagensteuerelement, das Containersteuerelement (das von ItemsControl generiert wird) und das Raster ermöglichen das Zeichnen von Elementen an den richtigen Stellen. –

1

Sorry, ich habe keine Lösung für Ihr konkretes Grid Problem, aber ich habe nur einen Vorschlag wie Sie könnte es leichter machen (und ich nehme an, wie es von den WPF-Designern gemeint ist). Eigentlich ist ein Grid kein Steuerelement, Elemente zu arrangieren. Es ist ein Panel, der Controls arrangiert. Also, ich vermute, das ist einer der Gründe, warum Sie mit Ihrer Lösung in Schwierigkeiten geraten.

Was ich stattdessen verwenden würde, ist ein ItemsControl (z. B. ein ListBox) mit einem Canvas als ItemsPanel.

<ListBox ItemsSource="{Binding WorkItemsProperty}"> 
    <ListBox.ItemsPanel> 
     <ItemsPanelTemplate> 
      <Canvas IsItemsHost="True"/> 
     </ItemsPanelTemplate> 
    </ListBox.ItemsPanel> 
</ListBox> 

Nun definieren Sie die entsprechenden Eigenschaften in Ihrem WorkItem (oder WorkItemViewModel) Klasse wie XPos und YPos, die zu den Canvas.Left und Canvas.Top Eigenschaften wie diese datengebundene werden:

<Style x:Key="WorkItemStyle" TargetType="{x:Type ListBoxItem}"> 
    <Setter Property="Canvas.Left" Value="{Binding XPos, Mode=TwoWay}"/> 
    <Setter Property="Canvas.Top" Value="{Binding YPos, Mode=TwoWay}"/> 
</Style> 

können Sie dann diese verwenden Objektart durch Zuweisen der ItemContainerStyle Eigenschaft der ListBox:

ItemContainerStyle="{StaticResource WorkItemStyle}" 

Ich weiß nicht, wie man das Drag & Drop-Zeug implementiert, weil ich das nie getan habe, aber offensichtlich haben Sie es bereits für Ihre benutzerdefinierte Grid getan, so sollte es kein großes Problem sein, es in einem ListBox auch zu verwenden . Wenn Sie jedoch die Eigenschaften Ihres WorkItem aktualisieren, sollte das Element automatisch neu positioniert werden. Wenn Sie ein Objekt zu Ihrer Sammlung hinzufügen oder daraus entfernen (WorkItemsProperty), wird es automatisch hinzugefügt/entfernt, da die Datei ListBox an die Sammlung gebunden ist.

Sie müssen möglicherweise Ihre WorkItemStyle je nach Szenario ändern. Wenn beispielsweise die Größe der ListBox zur Laufzeit geändert wird, müssen Sie möglicherweise die Positionen relativ zur Größe des Containers (Canvas) festlegen. Daher benötigen Sie eine MultiBinding anstelle der einfachen Binding. Aber das ist eine andere Geschichte ...

Jetzt ist es Ihre Entscheidung, ob Sie noch zu diesem Ansatz bewegen können oder ob Ihre Grid fast fertig ist und Sie nicht bereit sind, zu ändern. Ich weiß, es ist schwer, aber in meinen Augen ist der obige Ansatz der sauberere (und einfacher!)!

Verwandte Themen