Laden...

Propertygrid - Sortierung

Letzter Beitrag vor 17 Jahren 13 Posts 5.538 Views
Propertygrid - Sortierung

Hallo miteinander
Ich schlage mich hier in meinem Projekt mit dem Property-Grid Control aus dem .NET Framework rum...

Ich habe n Objekte in denen ich Konfigurationen abgelegt habe. Diese Objekte sind alle vom selben Typ!

Die Klasse von der diese Objekte erstellt wurden hat ca. 20 Eigenschaften denen ich über ein Attribut die Kategorie bzw. die Beschreibung zuweise...

Im Property-Grid selbst soll der Name je nach sprache verschieden angezeigt werden. (mehrsprachig eben 😛)

das ganze sieht dann etwa so aus (ja vb .NET, aber dies ist ja neben C# auch eine .NET-Community oder? 🙂)


    ''' <summary>
    ''' Getter / Setter für BackSave.yoffset
    ''' </summary>
    ''' <value>Aktuelle Wert von BackSave.yoffset</value>
    <GlobalizedProperty("yoffset", Category:="catM")> _
    Public Property yoffset() As Integer
        Get
            Return BackSave.getInstance().yOffset
        End Get
        Set(ByVal value As Integer)
            If (ParamValueChecker.CheckParam("yOffset", value)) Then
                BackSave.getInstance().yOffset = value
            End If
        End Set
    End Property

GlobalizedPropertyGrid ist ein eigenes Attribut das "ich" erstellt habe. Genauer gesagt ich hab es von diesem Artikel:

http://www.codeproject.com/cs/miscctrl/GlobalizedPropertyGrid.asp

Nun das ganze funktioniert alles ganz gut, solange man im Property-Grid immer nur ein einzelnes Objekt benutzt... sprich so:


meinPropertyGrid.SelectedObject = einObject

Ich möchte aber die Möglichkeit geben, sämtliche Konfigurations-objekte gleichzeitig editieren zu können.. dazu müsste man dem PropertyGrid alle Objekte als Array zuweisen:


meinPropertyGrid.SelectedObjects = objArray

wenn ich das mache, dann verliert das PropertyGrid sämtliche Kategoriezugehörigkeiten. Alle Eigenschaften werden in die Kategorie Misc gepflanzt.
Weiter werden die Eigenschaften nicht mehr nach dem DisplayNamen sortiert (wie das normal der Fall ist) sondern nach dem Namen, welcher die Eigenschaft wirklich hat. Dies führt zu einem heillosen durcheinander im grid!

Sieht hier jemand meinem Fehler? :S
Oder habt ihr vielliecht bessere Lösungen für dieses problem?

Hallo Opendix,

ja vb .NET, aber dies ist ja neben C# auch eine .NET-Community oder?

Eine Antwort findest du in Wie poste ich richtig? Punkt 6.

Aber zur eigentlichen Frage: Wenn du ein Array an das PropertyGrid bindest, dann sollten eigentliche ein Eigenschaften das Array-Objekts angezeigt werden, nicht der enthaltenen Objekte. An ein Grid, kann immer nur ein Objekt zur Zeit gebunden werden.

herbivore

Argh.. sry für das falsche Posten schäm

Aber zur eigentlichen Frage: Wenn du ein Array an das PropertyGrid bindest, dann sollten eigentliche ein Eigenschaften das Array-Objekts angezeigt werden, nicht der enthaltenen Objekte. An ein Grid, kann immer nur ein Objekt zur Zeit gebunden werden.

hmm... das ist so nicht korrekt!
das PropertyGrid hat ja die Eigenschaft "SelectedObjects" über die man dem Grid mehrere Objekte (zusammengefasst in einem Array) zuweisen kann...

Es werden dann alle Eigenschaften angezeigt, welche in allen zugewiesenen Objekten vorhanden sind. Und da bei mir alle Objekte vom selben Typ sind, werden auch entsprechend alle Eigenschaften von diesem Typ angezeigt!

Das ganze sieht so aus:


Dim arr() As ModelbaseNew = {New ModelbaseNew(), New ModelbaseNew()}
pgConfigs.SelectedObjects = arr

Ich frag mich nur wo die Unterteilung in die verschiedenen Kategorien liegengeblieben ist :S

Vielleicht kur allgemein das Prinzip:



Class ModelBaseNew
    inherits GlobalizedObject

    'Hier sind die ganzen Properties
End Class

Dann implementiert die Klasse GlobalizedObject das interface ICustomTypeDescriptor!
Folgende Funktion gibt die Properties der Klasse zurück


    Public Function GetProperties(ByVal attributes As Attribute()) As PropertyDescriptorCollection Implements ICustomTypeDescriptor.GetProperties
        If globalizedProps Is Nothing Then
            Dim baseProps As PropertyDescriptorCollection = TypeDescriptor.GetProperties(Me, attributes, True)
            globalizedProps = New PropertyDescriptorCollection(Nothing)
            For Each oProp As PropertyDescriptor In baseProps
                globalizedProps.Add(New GlobalizedPropertyDescriptor(oProp))
            Next
        End If
        Return globalizedProps
    End Function

Das funktioniert eindwandfrei, ich habe vor dem Return alle Properties kontrolliert. Jedes weist die korrekte Kategorie auf.

Was in aller Welt steht den nun noch zwischen dem PropertyGrid und dieser Methode. Bzw. Ich dachte bis jetzt, dass das propertyGrid diese Methode benutzt um an die Properties der Objekte zu gelangen. (Tut es auch, diese Methode wird aufgerufen sobald das PropertyGrid die Eigenschaften anzeigen soll..) Die Methode liefert dann die korrekten Werte, dieser werden aber falsch dargestellt :S

Ich probier/such/debug/was auch immer mal weiter 🙂

Hallo Opendix,

sorry, da ich SelectedObjects nicht mehr in Erinnerung hatte, hatte ich auch bei der Array-Zuweisung SelectedObject gelesen. Dann wäre es so, wie ich gesagt hätte. Das man nicht mehrere Objekte anzeigen kann, ist aber in der Tat eine falsche Aussage gewesen.

Eine Antwort auf deine eigentliche Frage habe ich nicht. Vielleicht weiß jemand anders was.

herbivore

Die Lösung ist eigentlich gar nicht so schwer, eigentlich sind es nur zwei Klassen die du brauchst:

  1. Einen TypeConverter
  2. (am besten generische) Eigene PropertyDescriptors
Public Class MyTypeConverter
   Inherits ExpandableObjectConverter

   Public Overrides GetProperties(...) as PropertyDescriptorCollection
    ...
   End Function

End Class

Und

Public Class MyPropertyDescriptor
   Inherits PropertyDescriptor

   (die ganzen zu überschreibenden Eigenschaften und Methoden)

   Public Overrides Property DisplayName as String (...)

   Public Overrides Property Category as String (...)

   Public Overrides Property Description as String (...)

End Class

Setz am besten gleich eine Import-Direktive auf System.ComponentModel...

Wenn du die hast kannst du dir in GetProperties irgendwelche Eigenschaften ausdenken und mit Werten füllen, unabhängig davon, was das Objekt eigentlich für Eigenschaften hatte. Ich hab noch nicht recherchiert, welche Eigenschaften man umstellen muss, um auch einen normalen TypeConverter nehmen zu können, aber die Performance von der Lösung halte ich für völlig ausreichend.

ich versteh grad nicht ganz, was du mir damit sagen willst :S

Das ganze mit der mehrsprachigkeit funktioniert ja soweit (auch wenn ich dem PropertyGrid mehrere Objekte zuweise...)

Das problem liegt nur dabei, dass wenn ich dem Grid mehrere Objekte zuweise, dieses dann nur noch die Kategorie "Sonstige" kennt. Die Kategorie-Zuweisungen die ich mittels Attribut gemacht habe, scheinen das Grid dann nicht zu interessieren :S

Ich kenn die Internals vom PropertyGrid nicht gut genug um dir das mit Sicherheit sagen zu können, aber ich denke, dass es auch nur ein Objekt anzeigt, nämlich eine Collections, deren TypeConverter offensichtlich die Attribute ignoriert. Die einzige Chance, die ich da sehe, wäre eine eigene Collection-Klasse zu implementieren und die mit einem TypeConverter auszustatten, der deine Bedürfnisse erfüllt (und da sind wirklich quasi keine Grenzen gesetzt, TypeConverter können, richtig eingesetzt, alles, was sich um Darstellung in einem PropertyGrid dreht).

Wenn ich mit meinem Zivi fertig bin und nach Irland gehe werd ich mich wahrscheinlich mal etwas mehr mit Mehrsprachigkeit befassen und gegebenfalls mal einen Artikel schreiben, wenn sich genügend Leute dafür interessieren.

Hallo onlinegurke 🙂

Das mit dem typeConverter muss ich mir mal noch genauer anschauen, hab da noch bisschen eine Bildungslücke g

Also eigentlich dachte ich, dass wenn ich ja meine Klassen mit den Properties von "GlobalizedObject" ableite, ich dafür keinen TypeConverter mehr brauche...
Dies weil GlobalizedObject ja das Interface von ICustomTypeDescriptor implementiert. Damit kann ich ja direkt in der Klasse GlobalizedObject die GetProperties-Methode entsprechend abändern, dass diese die gewünschten Properties mitsamt den entsprechenden Attributen zurückgibt...

Müsste doch eigenltich gehen... hmm ich überprüf da den Ablauf nochmal, vielleicht hab ich da irgendwo einen Bock drin 🙂

Ich meinte damit, dass du eine eigene Collectionklasse schreiben musst und für die dann einen eigenen TypeConverter. Du kannst die Collectionklasse auch ICustomTypeDescrptor implementieren lassen, aber das würde ich dir nicht empfehlen (Das ist Designkram, das hat mit der Klasse eigentlich nix zu tun, insofern find ich das auch auf dem codeproject-Artikel doof gelöst...). Wie willst du denn deine Objekte darstellen? Angenommen die Klasse hat eine Eigenschaft mit der Kategorie "Darstellung", soll dann angezeigt werden "Darstellung (Objekt1)", oder sollen die Darstellungseigenschaften von allen Objekten in die Kategorie "Darstellung", oder oder oder...

Hallo
war in den Lenrferien, darum erst jetzt meine Antwort 🙂

Also ich möchte das wie folgt darstellen, ich habe n Objekte vom Typ ModelBase!
Daraus folgt ja, dass alle dieselben Eigenschaften haben, bzw. alle Eigenschaftern den gleichen Kategorieren zugewiesen sind.

Nun soll die Darstellung gleich aussehen, wie wenn ich nur eines dieser Objekte selektiert habe.
Sprich, es sollen alle Kategorien mit den jeweiligen Eigenschaften angezeigt werden. Wenn ich nun eine Eigenschaft editiere, muss diese in allen Objekten abgeändert werden.

Hat beim anzeigen eine Eigenschaft nicht in allen Objekten denselben Wert, so soll dann der Wert leer angezeigt werden!

hmm... das mit der Collection hört sich interessant an, ich schau mal, ob ich das hinbekomme!

Soo... hab das mal gebaut und welch Wunder.. es funktioniert suuper 😁

also die Lösung sieht wie folgt aus (falls irgendwer, das hier mal nachbauen will)

Datei SelectedObjectsCollection:


Public Class SelectedObjectsCollection(Of T)
    Inherits SelectedObjectsTypeConverter
    Implements ICollection(Of T)

    ''' <summary>
    ''' Interne Collection
    ''' </summary>
    ''' <remarks></remarks>
    Private _internalList As New List(Of T)

    'Hier werden noch die ganzen Methoden von ICollection implementiert...
End Class

Datei SelectedObjectsTypeConverter


Public Class SelectedObjectsTypeConverter
    Implements ICustomTypeDescriptor

    Public Function GetClassName() As String Implements ICustomTypeDescriptor.GetClassName
        Return TypeDescriptor.GetClassName(Me, True)
    End Function

    Public Function GetAttributes() As AttributeCollection Implements ICustomTypeDescriptor.GetAttributes
        Return TypeDescriptor.GetAttributes(Me, True)
    End Function

    Public Function GetComponentName() As String Implements ICustomTypeDescriptor.GetComponentName
        Return TypeDescriptor.GetComponentName(Me, True)
    End Function

    Public Function GetConverter() As TypeConverter Implements ICustomTypeDescriptor.GetConverter
        Return TypeDescriptor.GetConverter(Me, True)
    End Function

    Public Function GetDefaultEvent() As EventDescriptor Implements ICustomTypeDescriptor.GetDefaultEvent
        Return TypeDescriptor.GetDefaultEvent(Me, True)
    End Function

    Public Function GetDefaultProperty() As PropertyDescriptor Implements ICustomTypeDescriptor.GetDefaultProperty
        Return TypeDescriptor.GetDefaultProperty(Me, True)
    End Function

    Public Function GetEditor(ByVal editorBaseType As Type) As Object Implements ICustomTypeDescriptor.GetEditor
        Return TypeDescriptor.GetEditor(Me, editorBaseType, True)
    End Function

    Public Function GetEvents(ByVal attributes As Attribute()) As EventDescriptorCollection Implements ICustomTypeDescriptor.GetEvents
        Return TypeDescriptor.GetEvents(Me, attributes, True)
    End Function

    Public Function GetEvents() As EventDescriptorCollection Implements ICustomTypeDescriptor.GetEvents
        Return TypeDescriptor.GetEvents(Me, True)
    End Function

    Public Function GetProperties() As System.ComponentModel.PropertyDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetProperties
        Dim globalizedProps As PropertyDescriptorCollection
        Dim baseProps As PropertyDescriptorCollection = TypeDescriptor.GetProperties(New ModelbaseNew(), True)
        globalizedProps = New PropertyDescriptorCollection(Nothing)
        For Each oProp As PropertyDescriptor In baseProps
            globalizedProps.Add(New GlobalizedPropertyDescriptor(oProp))
        Next
        Return globalizedProps
    End Function

    Public Function GetProperties(ByVal attributes() As System.Attribute) As System.ComponentModel.PropertyDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetProperties
        Dim globalizedProps As PropertyDescriptorCollection
        Dim baseProps As PropertyDescriptorCollection = TypeDescriptor.GetProperties(New ModelbaseNew(), attributes, True)
        globalizedProps = New PropertyDescriptorCollection(Nothing)
        For Each oProp As PropertyDescriptor In baseProps
            globalizedProps.Add(New GlobalizedPropertyDescriptor(oProp))
        Next
        Return globalizedProps
    End Function

    Public Overridable Function GetPropertyOwner(ByVal pd As PropertyDescriptor) As Object Implements ICustomTypeDescriptor.GetPropertyOwner
        Return Me
    End Function
End Class

Datei GlobalizedPropertyDescriptor.
Diese Klasse implementiert den PropertyDescriptor, welchen ich jedem Property in meiner ModelBase-Klasse zugewiesen habe. Hier muss man nun die GetValue und SetValue-methoden entsprechend den neuen Umständen anpassen:



Public Overloads Overrides Function GetValue(ByVal component As Object) As Object
        If (TypeOf component Is ICollection(Of ModelbaseNew)) Then
            Dim coll As SelectedObjectsCollection(Of ModelbaseNew) = CType(component, SelectedObjectsCollection(Of ModelbaseNew))
            Dim obj As Object = Nothing
            For Each item As ModelbaseNew In coll
                If (obj Is Nothing) Then
                    obj = Me.basePropertyDescriptor.GetValue(item)
                End If
                If (Not obj.Equals(Me.basePropertyDescriptor.GetValue(item))) Then
                    Return ""
                End If
            Next
            Return Me.basePropertyDescriptor.GetValue(coll(0))
        Else
            Return Me.basePropertyDescriptor.GetValue(component)
        End If
        Return ""
    End Function

    Public Overloads Overrides Sub SetValue(ByVal component As Object, ByVal value As Object)
        If (TypeOf component Is ICollection(Of ModelbaseNew)) Then
            Dim coll As SelectedObjectsCollection(Of ModelbaseNew) = CType(component, SelectedObjectsCollection(Of ModelbaseNew))
            For Each item As ModelbaseNew In coll
                Me.basePropertyDescriptor.SetValue(item, value)
            Next
        Else
            Me.basePropertyDescriptor.SetValue(component, value)
        End If

    End Sub

Verwendung der ganzen Sache:



Dim list As New SelectedObjectsCollection(Of ModelbaseNew)
list.Add(New ModelbaseNew())
list.Add(New ModelbaseNew())
'usw.
meinPropertyGrid.SelectedObject = list


Ob das nun die beste Lösung ist, weis ich nicht, ich weis nur, dass mit dieser Lösung alle meine probleme verschwunden sind 🙂
Aber vielleicht hat ja noch jemand Verbesserungsvorschläge? 🙂

une was ich vergessen habe: grossen Dank an onlinegurke für diesen Tipp 🙂

Hm, du arbeitest also wie in dem CodeProject-Artikel mit ICustomTypeDescriptor, was mir persönlich nicht so zusagt, weil du da eine Vererbungshierarchie aufbaust (alle Collections, die dieses Verhalten an den Tag legen müssen SelectedObjectsTypeConverter erben), die m.E. nicht sein muss. Mir persönlich gefällt der Ansatz wie ich das schon oben gesagt hab über TypeConverter besser, weil da die Vererbungshierarchie wegfällt und die ganze Geschichte damit unglaublich viel mächtiger wird (man könnte einen TypeConverter auch ohne Probleme auf Klassen anwenden, die in Fremdbibliotheken sind, auf die man keinen Zugriff hat).

ICustomTypeDescriptor ist (vermute ich mal, anhand des Designs) für andere Aufgaben gedacht, z.B., wenn eine Instanz erst zur Laufzeit entscheiden soll welcher Editor benutzt werden soll, vor allem auch welcher TypeConverter benutzt werden soll, dann ist das das Einsatzgebiet. Solange ein statischer TypeConverter aber zum Ziel führt finde ich, sollte man ihn benutzen...