Dynamische Erstellung der Controls in einem SettingsDialog

Es gibt 17 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Dynamische Erstellung der Controls in einem SettingsDialog

    Ich bin momentan dabei den SettingsDialog in meinen Programmen zu modernisieren. Der SettingsDialog hat 2 Bereiche. Im linken Bereich befindet sich ein TreeView, indem alle Kategorien aufgelistet sind. Im rechten Bereich des Dialogs befindet sich dann der Content, dieser wird geladen, wenn man auf eine Kategorie klickt. Der SettingsDialog und jede ContentPage hat Zugriff auf die ApplicationSettings.

    Nun zur eigentlichen Idee, dazu möchte ich eure Meinung hören, ob man dies so machen kann und auch ob es sich umsetzen lässt. Wie man es umsetzen muss, darüber mach ich mir bereits gedanken.

    In der ApplicationSettings habe ich eine unbestimmte Anzahl von Eigenschaften. Nun hat fast jede Eigenschaft ein zugehöriges Control in einer ContentPage. Jetzt kam mir heute die Idee, da ich öfters die ApplicationSettings um weitere Eigenschaften erweitere muss ich die einzelnen ContentPages erweitern, das nun beim Aufruf der ContentPages die Controls dynamisch anhand der Eigenschaften erstellt werden. Die Eigenschaften kann ich mit Attribute versehen, mit denen ich die Control-Erstellung steuern kann.
    Hi
    Ich würde das mit Reflection lösen. Dazu kannst du GetType(SettingsType).GetProperties(Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public) verwenden. Anschließend wertest du noch die Attribute entweder per IsDefined(GetType(AttributeType), False) oder per GetCustomAttributes(GetType(AttributeType), False) aus. Zweiteres gibt dir natürlich die Attribute zurück, während IsDefined nur auf deren Existenz überprüft. Für die Controls würde ich eine abstrakte Klasse definieren, die die Controls einem Behälter hinzufügt (z.B. Public Sub AddControls(ByVal parent As Control) und eine, die sie wieder entfernt (oder einfach Controls.Clear() vom Parent aufrufen). Auf die abgeleiteten Klassen kannst du dann im Attribut verweisen und per Activator.CreateInstance die Instanz der Controls erzeugen (oder per Type.GetConstructor bzw. Type.GetConstructors(), wenn du verschiedene Arten der Instanzierung anbieten möchtest - das ist allerdings eher unschön und sollte über eine andere Klasse gelöst werden, die dann die Instanz erzeugt).
    Die Klasse, die du erzeugst sollte dann auch eine Property besitzen, die auf die Einstellungs-Klasse und die editierte Einstellung verweist, damit die Eigenschaft auch verändert werden kann (evtl. einfach die PropertyInfo übergeben). Verwalten kannst du die Knoten im TreeView dadurch, dass du einfach eine Klasse von TreeNode ableitest und sie so modifizierst, dass sie ihren Job tut.

    Gruß
    ~blaze~

    Myrax schrieb:

    Wie wär es mit ner XML Datei?

    Es gibt bereits eine XML-Datei, in dieser werden die Einstellungen gespeichert. Oder was meinst du genau?

    ~blaze~ schrieb:

    Hi
    Ich würde das mit Reflection lösen. Dazu kannst du GetType(SettingsType).GetProperties(Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public) verwenden. Anschließend wertest du noch die Attribute entweder per IsDefined(GetType(AttributeType), False) oder per GetCustomAttributes(GetType(AttributeType), False) aus. Zweiteres gibt dir natürlich die Attribute zurück, während IsDefined nur auf deren Existenz überprüft. Für die Controls würde ich eine abstrakte Klasse definieren, die die Controls einem Behälter hinzufügt (z.B. Public Sub AddControls(ByVal parent As Control) und eine, die sie wieder entfernt (oder einfach Controls.Clear() vom Parent aufrufen). Auf die abgeleiteten Klassen kannst du dann im Attribut verweisen und per Activator.CreateInstance die Instanz der Controls erzeugen (oder per Type.GetConstructor bzw. Type.GetConstructors(), wenn du verschiedene Arten der Instanzierung anbieten möchtest - das ist allerdings eher unschön und sollte über eine andere Klasse gelöst werden, die dann die Instanz erzeugt).
    Die Klasse, die du erzeugst sollte dann auch eine Property besitzen, die auf die Einstellungs-Klasse und die editierte Einstellung verweist, damit die Eigenschaft auch verändert werden kann (evtl. einfach die PropertyInfo übergeben). Verwalten kannst du die Knoten im TreeView dadurch, dass du einfach eine Klasse von TreeNode ableitest und sie so modifizierst, dass sie ihren Job tut.

    Gruß
    ~blaze~

    Danke für den hilfreichen Tipp. Der SettingsDialog ist soweit fertig. Es geht lediglich darum, die Controls anhand der einzelnen Eigenschaften in der ApplicationSettings dynamisch in den ContentPages zu erstellen
    Also ich habe schon mal so ein Programm geschrieben, das eine ähnliche Eigenschaftenverwaltung hatte (habe das mit minimierbaren Groupboxen gemacht, die jeweils Schlüssel-Wert-Paare enthalten haben und deren Wert dann per Control geändert werden kann). Die Eigenschaften kannst du ja per Reflection laden, wie beschrieben, und anschließend die die Controls erstellende Klasse instanzieren (die, die von der abstrakten Klasse abgeleitet ist). Naja, bin jetzt zu faul zum noch mal erklären. Hier im Expander wäre eine unvollständige, ungetestete und nur so zusammengeschriebene "Lösung" (Vorsicht Spoiler)
    Spoiler anzeigen
    Du solltest überall System.Reflection importieren.

    VB.NET-Quellcode

    1. Public MustInherit Class SettingControlProvider
    2. Private piProperty As PropertyInfo
    3. Private objInstance As Object
    4. Public MustOverride Function CreateControl() As Control
    5. Public Property Setting() As PropertyInfo
    6. Get
    7. Return piProperty
    8. End Get
    9. Set(ByVal value As PropertyInfo)
    10. piProperty = value
    11. End Set
    12. End Property
    13. Public Property Instance() As Object
    14. Get
    15. Return objInstance
    16. End Get
    17. Set(ByVal value As Object)
    18. objInstance = value
    19. End Set
    20. End Property
    21. End Class
    22. Public Class MySettingControlProvider
    23. Inherits SettingControlProvider
    24. Public Overrides Function CreateControl() As Control
    25. Return New Label() With {.Text = "Cookies!"}
    26. End Function
    27. End Class


    Die CreateControl-Funktion kannst du natürlich noch anpassen oder ganz anders formulieren.

    VB.NET-Quellcode

    1. <AttributeUsage(AttributeTargets.Property, AllowMultiple:=False, Inherited:=False)> _
    2. Public Class SettingsAttribute
    3. Inherits Attribute
    4. Private tpEditorType As Type
    5. Public ReadOnly Property EditorType() As Type
    6. Get
    7. Return tpEditorType
    8. End Get
    9. End Property
    10. Public Sub New(editorType As Type)
    11. tpEditorType = editorType
    12. End Sub
    13. End Class

    VB.NET-Quellcode

    1. <SettingsAttribute(MySettingControlProvider)> _
    2. Public Property CustomProperty() As Integer
    3. Get
    4. Return &H37333331
    5. End Get
    6. Set(ByVal value As Integer)
    7. End Set
    8. End Property

    VB.NET-Quellcode

    1. Public Class MyTreeNode
    2. Inherits TreeNode
    3. Private scpSettingsProvider As SettingControlProvider
    4. Public ReadOnly Property SettingsProvider() As SettingControlProvider
    5. Get
    6. Return scpSettingsProvider
    7. End Get
    8. End Property
    9. Public Sub New(settingsProvider As SettingControlProvider)
    10. scpSettingsProvider = settingsProvider
    11. Me.Text = settingsProvider.Setting.Name
    12. End Sub
    13. End Class

    VB.NET-Quellcode

    1. Dim properties() As PropertyInfo = GetType(ApplicationSettings).GetProperties(BindingFlags.Public Or BindingFlags.Instance)
    2. Dim att() As SettingsAttribute
    3. Dim provider As SettingControlProvider
    4. For Each pi As PropertyInfo In properties
    5. att = DirectCast(pi.GetCustomAttributes(GetType(SettingsAttribute), False), SettingsAttribute)
    6. If att.Length = 1 Then
    7. provider = DirectCast(Activator.CreateInstance(att(0).EditorType), SettingControlProvider)
    8. povider.Instance = applicationsettingsinstance
    9. provider.Setting = pi
    10. tvSettings.Nodes.Add(New SettingsNode(provider))
    11. End If
    12. Next

    Beim anwählen des TreeNodes musst du dann halt noch die Controls erzeugen und deine eigene Verwaltung dafür schreiben...

    Der Code ist auf eine Instanz ausgelegt, also nicht auf statische Eigenschaften von ApplicationSettings. Notfalls halt die BindingFlags.Instance auf BindingFlags.Static umändern und die Instance-Eigenschaft vom SettingControlProvider entfernen.


    Gruß
    ~blaze~

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ () aus folgendem Grund: [vb] zu [/vb] umgeschrieben

    Ich hatte wieder ein bisschen Zeit um an dem Aufbau zu arbeiten. Nun möchte ich zu der Idee eure Meinung hören.

    Jede Eigenschaft hat ein Attribute namens Setting. In diesem Attribute wird die Beschreibung, Kategorie und den Type des DesignerControls gespeichert. Das DesignerControl erbt von UserControl und hat eine feste Breite, außerdem hat es 2 Eigenschaften "Description" und "Value". z.b. TextBoxControl besteht aus einem Label und einer TextBox, den Text des Labels änder ich über die Eigenschaft "Description" und den Wert von TextBox kann ich über die Eigenschaft "Value" abrufen und auch festlegen. Die ContentPage "GeneralContentPage" hat die Kategorie "General", die ContentPage hat ein Control namens FlowLayoutPanel. Beim Laden der ContentPages gehe ich alle Eigenschaften von ApplicationSettings durch und prüfe welche Kategorie die Eigenschaft hat und erstelle dann eine Instanz vom DesignerControl und füge dies dem FlowLayoutPanel in der zugehörigen ContentPage zu.
    Ich würde das Control nicht von UserControl, sondern von Control erben lassen, da dadurch die Möglichkeit der Verwendung anderer Controls ebenfalls zugelassen wird. Ich würde mir außerdem ein eigens FlowLayoutPanel erstellen, weil du da die Anordnung selbst übernehmen und immer Paare aus Label und Control bilden kannst, wenn das notwendig ist. Ansonsten würde ich sagen, dass es ganz gut ist. Funktionieren würde es auf jeden Fall. Nur die ContentPages sollten dann auch die Übersichtlichkeit fördern. Eventuell kannst du auch noch einen optionalen ToolTip für die Labels anbieten.

    Gruß
    ~blaze~

    ~blaze~ schrieb:

    Ich würde mir außerdem ein eigens FlowLayoutPanel erstellen, weil du da die Anordnung selbst übernehmen und immer Paare aus Label und Control bilden kannst, wenn das notwendig ist.

    Wie meinst du das genau? Ich hab mir das so überlegt, das in jedem DesignerControl ein TableLayoutPanel ist und ich dann dort 2 Spalten habe, in der ersten befinden sich das Label mit der Beschreibung und in der zweiten Spalte die Controls.
    So wär's in Ordnung, aber das FlowLayoutPanel an sich ordnet die Controls nicht in 2 Spalten an. Wenn du keine eigenen Einstellungen treffen willst (deiner Beschreibung nach nicht), kannst du auch das TableLayoutPanel verwenden, du brauchst dann wohl aber für jedes Control eine Beschreibung, die nicht null oder leer ist.

    Gruß
    ~blaze~
    Also PropertyName wäre wahrscheinlich überflüssig, weil das ja bei der System.Refleciton.PropertyInfo bereits in der Name-Eigenschaft steht. Die Beschreibung steht ja schon in der Description. Wenn du damit dem generierten Control eine ID zuordnen möchtest, wäre es vielleicht vorteilhafter, die Eigenschaft als ControlName zu bezeichnen. Ansonsten würde das so passen, denke ich. Zwar ist der Begriff Designer bereits an die von der IDesigner-Schnittstelle bereitsgestellten Designer (für Komponenten etc.) vergeben, aber ich denke es ist klar, was gemeint ist.

    Gruß
    ~blaze~