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