ApplicationSettings v1.2

    • Release

    Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von Niko Ortner.

      ApplicationSettings v1.2

      Name des Programms:
      ApplicationSettings.dll v1.2

      Beschreibung:
      Diese Dll nimmt sehr viel Arbeit beim Verwalten von Anwendungseinstellungen ab. Bei der ersten Verwendung wird ein XML-Schema verwendet, um eine Dll zu generieren, die die gewünste Struktur beinhaltet. Das XML-Schema kann in Visual Basic IDEs praktischerweise über den integrierten XML-Parser angegeben werden (Codebeispiele siehe weiter unten).
      Die Einstellungen werden im XML-Format abgespeichert.
      Dabei ist es möglich, durch das ableiten eigener TypeConverter, selbst das Format von gespeicherten Daten zu bestimmen (es sollte sogar möglich sein, komplette XML-Elemente als Daten abzuspeichern, aber dafür übernehme ich keine Garantie ;) ).
      Optional können die Einstellungs-Container das INotifyPropertyChanged-Interface implementieren, wodurch DataBinding möglich ist.
      Es kann auch angegeben werden, das für jede Enstellung ein entsprechendes (separates) Event erstellt wird, das ausgelöst wird, wenn sich die Einstellung ändert.

      Klassendiagramme:
      Ich habe mir erlaubt, die Liste etwas zu kürzen, denn viele Klassen werden nur von den generierten Assemblies verwendet.
      Friend und Private Member sind ebenfalls nicht aufgelistet.
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Namespace ApplicationSettings
      2. Class DirectoryFromEnvironmentDirectory : Inherits DirectoryIdentifierBase
      3. ReadOnly Property SpecialDirectory As Environment.SpecialFolder
      4. Sub New(NewSpecialDirectory As Environment.SpecialFolder)
      5. Class DirectoryFromPath : Inherits DirectoryIdentifierBase
      6. Sub New(NewPath As String)
      7. MustInherit Class DirectoryIdentifierBase : Inherits IdentifierBase
      8. Function GetSubDirectory(DirectoryName As String) As DirectoryIdentifierBase
      9. Function GetFileInDirectory(FileName As String) As FileIdentifierBase
      10. Class FileFromPath : Inherits FileIdentifierBase
      11. Sub New(NewPath As String)
      12. MustInherit Class FileIdentifierBase : Inherits IdentifierBase
      13. MustOverride ReadOnly Property ContainingDirectory As DirectoryIdentifierBase
      14. MustOverride ReadOnly Property FileName As String
      15. Class FileInDirectory : Inherits FileIdentifierBase
      16. Sub New(NewContainingDirectory As DirectoryIdentifierBase, NewFileName As String)
      17. Class Generator
      18. Shared Sub CreateSettingsAssembly(Path As String, AssemblyDescription As System.Xml.Linq.XElement)
      19. Class GeneratorException : Inherits Exception
      20. Sub New(Message As String)
      21. MustInherit Class IdentifierBase
      22. MustOverride ReadOnly Property Path As String
      23. Class NopeException : Inherits Exception
      24. Sub New()
      25. Sub New(NewMessage As String)
      26. Class XMLParseException : Inherits Exception
      27. Sub New(NewElement As XElement, NewMessage As String)

      Verwendete Programmiersprache und IDE:
      Visual Basic .Net (IDE: Microsoft VisualStudio 2010 Express)

      Systemanforderungen:
      .Net Framework 4.0

      Tutorial für Verwendung:
      Spoiler anzeigen

      XML-Schema:

      Anhand eines Beispiels erklärt:

      XML-Quellcode

      1. <Testanwendung>
      2. <MainWindow>
      3. <WindowTitle TypeCode="String"/>
      4. <BackGroundColor TypeName="System.Drawing.Color"/>
      5. <CodeWindow>
      6. <SyntaxHighlighting TypeCode="String"/>
      7. </CodeWindow>
      8. </MainWindow>
      9. <DebugWindow>
      10. <InUse TypeCode="Boolean"/>
      11. </DebugWindow>
      12. <IO>
      13. <Directory Parent="ApplicationDirectory" Name="SettingsDirectory" Path="Settings">
      14. <File Name="SettingsXmlFile" Path="ApplicationSettings.xml"/>
      15. </Directory>
      16. <Directory Name="Desktop">C:\Users\Niko\Desktop</Directory>
      17. <File Parent="Desktop" Name="TestTextFile">Test.txt</File>
      18. <SpecialDirectory Name="AppData" Path="LocalApplicationData">
      19. <Directory Name="AppDataSettingsDirectory" Path="Testanwendung">
      20. <File Name="AppDataSettingsXmlFile">ApplicationSettings.xml</File>
      21. </Directory>
      22. </SpecialDirectory>
      23. </IO>
      24. </Testanwendung>

      Das äußerste Element gibt den Haupt-Namespace der Assembly an. In diesem Fall "Testanwendung".
      In diesem NameSpace wird eine Klasse namens "Settings" generiert. Diese Klasse hat statische Properties mit den Namen der Unterelemente, also in diesem Fall "MainWindow", "DebugWindow" und "IO". Die Typen dieser Properties (Klassen) werden im Namespace "Testanwendung.Generated" angelegt. Die IO-Property ist eine Ausnahme. Sie befindet sich im NameSpace "Testanwendung.Reserved". Warum das so ist, erkläre ich weiter unten.
      Diese Klassen haben dann ihre weiteren Properties. Diese sind dann nicht mehr statisch.
      Diese Properties können eine dieser beiden Möglichkeiten sein, nicht aber beides gleichzeitig:
      • Elemente mit nur Unterelementen (<CodeWindow>...</CodeWindow>):
        Diese Properties enthalten ihrerseits wieder Properties. Die Namen der XML-Elemente stellen die Namen der Properties dar. Die Klassen werden ebenfalls im Generated-NameSpace angelegt.
      • Elemente mit nur Attributen (<SyntaxHighlighting TypeCode="String"/>):
        Diese Properties werden zum Speichern und Auslesen von Werten verwendet. Die Namen der XML-Elemente stellen die Namen der Properties dar. Der Typ wird anhand einer dieser beiden Attribute festgestellt (beide gleichzeitig zu verwenden ist nicht möglich):
        • TypeCode:
          Ein Wert aus dem Enum System.TypeCode. Nicht erlaubt sind Empty und DBNull. Die Typen werden entsprechend dem TypeCode ausgewählt. "String" ergibt den Typ System.String. Es gibt folgende Aliase: "Int", "Integer" -> "Int32" und "Bool" -> "Boolean"
        • TypeName:
          Ein voll qualifizierter Name eines Typs. Wichtig ist hierbei, dass es zur Laufzeit einen TypeConverter für diesen Typ gibt, der von und zu String konvertieren kann.
          Beispielsweise System.Drawing.Color verwendet System.Drawing.ColorConverter. Ein TypeConverter wird üblicherweise so mit dem Typ verknüpft:

          VB.NET-Quellcode

          1. <System.ComponentModel.TypeConverterAttribute(GetType(TestConverter))> _
          2. Class Test
          3. End Class
          4. Class TestConverter
          5. Inherits System.ComponentModel.TypeConverter
          6. End Class
          Das ist für bereits existierende Klassen nicht möglich. Stattdessen wird der TypeConverter durch einen Aufruf an TypeDescriptor.AddAttribute verknüpft:

          VB.NET-Quellcode

          1. System.ComponentModel.TypeDescriptor.AddAttributes(GetType(Test), New System.ComponentModel.TypeConverterAttribute(GetType(TestConverter)))
          Weiters ist wichtig, dass der Typ genau einmal gefunden werden kann.
          Wird der Typ garnicht gefunden kann es daran liegen, dass der Name nicht richtig angegeben wurde. Typen, die sich nicht in der mscorlib.dll befinden, müssen mit Namespace qualifiziert sein. Oder die benötigte Assembly ist einfach nicht geladen.
          Wird der Typ mehrmals gefunden, kann es daran liegen, dass er in mehreren Assemblies gleich definiert ist. Abgesehen davon, dass das nicht vorkommen sollte, könnte man versuchen, den AssemblyQualifiedName anzugeben. Diesen erhält man von einem beliebigen Typen, indem man GetType(TypName).AssemblyQualifiedName ausliest. Das könnte helfen, den benötigten Namen zu finden. Weiter sollte man alle Assemblies, die die Namenskollisionen auslösen, entfernen, sofern das möglich ist.
        • DefaultValue:
          Die String-Darstellung des Standardwertes dieser Einstellung. Die Einstellung hat standardmäßig diesen Wert, bis er durch eine Zuweisung oder durch das Laden aus einer XML-Datei verändert wird.
          Auch hier ist wichtig, dass ein passender TypeConverter vorhanden ist.
      Die IO-Property hat eine Sonderrolle. Sie verwaltet Pfade, die nicht direkt zu den Einstellungen gehören. Beispielsweise den Speicherort der XML-Datei.
      Das IO-Element kann drei veschiedene Unterelemente beinhalten: File, Directory und SpecialDirectory. Das sind die Eigenschaften dieser Elemente:
      • Name:
        Dieses Attribut gibt den Namen der Property an, die in der IO-Klasse generiert wird. Sollte so aussagekräftig wie möglich sein. "ApplicationDirectory" und "ApplicationExecutableFile" sind als Namen ungültig, da diese Properties bereits in der Basisklasse vordefiniert sind. Weiters dürfen Namen nicht doppelt vorkommen, da es in der IO-Klasse keine Gruppierung gibt.
      • Parent:
        Ein übergeordnetes Element kann durch die Verschachtelung im XML-Schema oder durch ein Attribut namens "Parent" festgelegt sein. Im Beispiel oben ist das File-Element "SettingsXmlFile" dem Directory-Element "SettingsDirectory" untergeordnet. Ebenfalls ist das File-Element "TestTextFile" dem Directory-Element "AppDataSettingsDirectory" untergeordnet. Ebenso können vordefinierte Elemente (wie einen Punkt vorher erwähnt) als Parent-Elemente festgelegt werden (Wie beim Directory-Element "SettingsDirectory"). Abhängig davon, ob es ein Parent-Element gibt oder nicht, gibt der Pfad nur einen einzelnen Ordner-/Dateinamen oder einen vollständigen Pfad an. Das heißt, bei Elementen mit Parent-Elementen setzt sich der Pfad aus System.IO.Path.Combine(ÜbergeordneterPfad, DieserPfad) zusammen. Das Parent-Element muss immer vor dem untergeordneten Element angegeben werden. Befindet sich ein Parent-Element unterhalb des untergeordneten Elementes, führt das zur Laufzeit zu einer NullReferenceException, weil die Properties in der Reihenfolge instanziert werden, in der sie im XML-Schema stehen.
      • Path:
        Der Pfad eines Elementes kann unterschiedlich angegeben sein. Wenn das Element Unterelemente besitzt (bezieht sich auf das XML-Schema selbst und nicht auf Elemente, auf die mit einem Attribut verwiesen wird), muss der Pfad durch ein Attribut namens "Path" angegeben werden. Ansonsten kann der Pfad als Hauptinhalt angegeben werden, wie zum Beispiel beim File-Element "TestTextFile".
        Bei SpecialDirectory-Elementen wird anstelle eines tatsächlichen Pfades ein Wert aus dem Enum System.Environment.SpecialFolder verwendet. Der Pfad ergibt sich dann aus System.Environment.GetFolderPath(Ordnerbezeichnung).
        Korrekterweise müsste man bei Elementen, die ein Parent-Element besitzen, "DirectoryName" bzw. "FileName" anstelle von "Path" verwenden, aber dadurch würde man sich mehr Schlüsselwörter merken müssen.
      Hinweis: File-Elemente können logischerweise keine Unterelemente besitzen.

      Anwendung:

      Im entsprechenden Projekt muss ein Verweis auf die ApplicationSettings.dll hinzugefügt werden. Dann wird in einer Methode, die Funktion ApplicationSettings.Generator.CreateSettingsAssembly() aufgerufen. Der erste Parameter gibt den Pfad an, an dem die generierte Dll abgelegt werden soll. Der Dateiname muss die Erweiterung ".dll" haben. Der zweite Parameter ist ein System.Xml.Linq.XElement. Solche Elemente können in Visual Basic bequem im Code hingeschrieben werden. Es ist aber auch möglich, den Inhalt einer XML-Datei zu laden und diesen zu verwenden.
      Der Code muss einmal ausgeführt werden, dann kann er auskommentiert werden.
      Die generierte Dll kann jetzt optional in den gewünschten Ordner verschoben werden, wenn dieser nicht dem angegebenen Pfad entspricht, aber ich empfehle, den Pfad von vornherein richtig zu wählen, weil dann nicht bei jeder Änderung die Dll neu kopiert werden muss.
      Anschließend wird ein Verweis auf diese generierte Dll hinzugefügt. Jetzt kann die Dll verwendet werden.
      Jetzt ist es vom Vorteil, wenn das äußerste Element den selben Namen hat, wie der Standard-NameSpace des Projektes. Denn dadurch ist die Settings-Klasse direkt aufrufbar und man spart sich die Angabe bzw. den Import des NameSpaces.

      Jetzt müssen beim Starten der Anwendung (zum Beispiel im Konstruktor der Startform) die Einstellungen geladen werden. Das geschieht in zwei Schritten:
      Zuerst muss die IO-Klasse initialisiert werden, damit sie die Pfade korrekt zusammenstellen kann. Dazu muss ihr der Dateipfad zur ausgeführten Datei übergeben werden. Das ist üblicherweise System.Windows.Forms.Application.ExecutablePath.
      Im zweiten Schritt werden die Einstellungen aus der XML-Datei gelesen. Dazu wird der Pfad zur XML-Datei benötigt. Es bietet sich an, in der IO-Klasse eine Property für diesen Pfad anzulegen (beispielsweise den Programmordner oder unter AppData\Local einen Unterordner). Natürlich funktionieren auch alle anderen Pfade. Möglicherweise funktionieren beim Laden auch URLs.

      VB.NET-Quellcode

      1. Settings.IO.Initialize(Application.ExecutablePath)
      2. Settings.Load(Settings.IO.SettingsXmlFile.Path)

      Man beachte, dass die Properties in der IO-Klasse keine Strings sondern eigene Klassen sind, die das Arbeiten damit erleichtern. Deshalb muss vom Objekt, das von der SettingsXmlFile-Property zurückgegeben wird, die Path-Property abgerufen werden.

      Beim Beenden der Anwendung (z.B. OnClosing) müssen die Einstellungen wieder gespeichert werden.
      Dazu muss ebenfalls der Pfad zur XML-Datei angegeben werden:

      VB.NET-Quellcode

      1. Settings.Save(Settings.IO.SettingsXmlFile.Path)


      Nun können die Einstellungen verwendet werden. Beispiele:

      VB.NET-Quellcode

      1. Public Sub New()
      2. InitializeComponent()
      3. Settings.IO.Initialize(Application.ExecutablePath)
      4. Settings.Load(Settings.IO.SettingsXmlFile.Path)
      5. End Sub
      6. Protected Overrides Sub OnShown(e As System.EventArgs)
      7. Me.Text = Settings.MainWindow.WindowTitle
      8. Me.BackColor = Settings.MainWindow.BackGroundColor
      9. TextBox_SyntaxHighlighting.Text = Settings.MainWindow.CodeWindow.SyntaxHighlighting
      10. CheckBox_UseDebugWindow.Checked = Settings.DebugWindow.InUse
      11. MyBase.OnShown(e)
      12. End Sub
      13. Protected Overrides Sub OnClosing(e As System.ComponentModel.CancelEventArgs)
      14. Settings.MainWindow.WindowTitle = Me.Text
      15. Settings.MainWindow.BackGroundColor = Me.BackColor
      16. Settings.MainWindow.CodeWindow.SyntaxHighlighting = TextBox_SyntaxHighlighting.Text
      17. Settings.DebugWindow.InUse = CheckBox_UseDebugWindow.Checked
      18. Settings.Save(Settings.IO.SettingsXmlFile.Path)
      19. MyBase.OnClosing(e)
      20. End Sub

      Download:
      ApplicationSettings v1.2.zip (23KB gepackt, 56.5KB entpackt)
      ApplicationSettings.xml (12.3KB) (XML-Doku für Wiederverwendung)

      Lizenz/Weitergabe:
      Darf beliebig weitergegeben werden, solange dafür kein Geld verlangt wird.
      Einkompilieren erlaubt (Erwähnung wäre nett).
      Dekompilieren erlaubt, ich beantworte Fragen zum Code aber auch gerne selbst.
      "Luckily luh... luckily it wasn't poi-"
      -- Brady in Wonderland, 23. Februar 2015, 1:56
      Desktop Pinner | ApplicationSettings | OnUtils

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Niko Ortner“ ()

      @ErfinderDesRades: Bei den (My?).Settings ist der Speicherort der XML-Datei festgelegt auf den Ordner AppData\Local\Anwendungsname\Version (oder war's AppData\Roaming\...?).
      Die Angaben im IO-Element werden direkt in der SettingsAssembly abgelegt, und nicht in der XML-Datei. Dehalb kann z.B. Settings.Load(Settings.IO.SettingsXmlFile.Path) verwendet werden. Wenn ein Pfad ein Teil der Anwendungseinstellungen ist, dann müsste der natürlich woanders untergeordnet sein.
      Und die Handhabung von Ordnerstrukturen ist ein Stück bequemer, als mit Strings alleine.
      Die Settings von Visual Studio gehen, soweit ich das mitbekommen habe, beim Wechsel der Version verloren, weil sich der Pfad der XML-Datei ändert. Das passiert hier nicht. Hier ist es sogar möglich, die alte XML-Datei mit einer neuen SettingsAssembly zu laden, sofern man nicht sowas macht:

      XML-Quellcode

      1. <!-- vorige Version -->
      2. <Testanwendung>
      3. <MainWindow>
      4. <Settings1 TypeCode="String"/>
      5. </MainWindow>
      6. </Testanwendung>
      7. <!-- neue Version -->
      8. <Testanwendung>
      9. <MainWindow>
      10. <Settings1>
      11. <Settings2 TypeCode="String"/>
      12. </Settings1>
      13. </MainWindow>
      14. </Testanwendung>
      Also ein Eintrag darf nicht vorher eine Einstellung und nachher ein übergeordnetes Element (oder umgekehrt) sein. Man kann die XML-Datei aber ganz einfach abändern. Wenn Einträge beim Laden nicht gefunden werden, werden die Standardwerte angenommen. Und wenn überschüssige Einträge vorhanden sind, dann werden sie ignoriert:

      XML-Quellcode

      1. <!-- wenn das in der XML-Datei vorhanden ist: -->
      2. <Testanwendung>
      3. <MainWindow>
      4. <Settings1 TypeCode="String"/>
      5. <Settings2 TypeCode="String"/>
      6. </MainWindow>
      7. </Testanwendung>
      8. <!-- aber das kompiliert wurde: -->
      9. <Testanwendung>
      10. <MainWindow>
      11. <Settings1 TypeCode="String"/>
      12. </MainWindow>
      13. </Testanwendung>
      14. <!-- dann wird der Settings2-Eintrag ignoriert -->


      Das sind die Dinge, die mir jetzt als Unterschiede zu den Settings von Visual Studio einfallen (sofern Du die "My.Settings" meinst).
      "Luckily luh... luckily it wasn't poi-"
      -- Brady in Wonderland, 23. Februar 2015, 1:56
      Desktop Pinner | ApplicationSettings | OnUtils
      Optionale Implementierung von INotifyPropertyChanged hinzugefügt.
      Optionale PropertyChanged-Events hinzugefügt.
      "Luckily luh... luckily it wasn't poi-"
      -- Brady in Wonderland, 23. Februar 2015, 1:56
      Desktop Pinner | ApplicationSettings | OnUtils
      Tut mir leid, aber sowas beantwortet ich nicht konkret.
      Deine Software zum Code heißt Visual Studio, dann googlen:
      vs DLL zu Projekt hinzufügen
      »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais
      Na toll. Und ich dachte, ich hätte den Thread hier abonniert.

      @Sky Super
      Was das Video in 5 Minuten erklärt, ginge auch in einer:
      1. In der Menüleiste auf "Projekt" klicken.
      2. Ganz unten auf "(Projektname)-Eigenschaften" klicken.
      3. Oder als Alternative zu Schritt 1 und 2: Im Projektmappenexplorer einen Doppelklick auf "My Project" machen. (Gilt nur für VB.)
      4. Zur Registerkarte "Verweise" wechseln.
      5. Auf den Button "Hinzufügen" klicken.
      6. Zur Registerkarte "Durchsuchen" wechseln.
      7. ApplicationSettings.dll suchen und auswählen.
      8. "OK" klicken.
      9. Optional: Bei der Liste "Importierte Namespaces" ganz nach unten scrollen und bei den gewünschten Namespaces den Haken hinzufügen

      Das Spielchen wiederholt sich beim Auswählen der generierten Dll.
      Und nur ums nochmal zu erwähnen:

      Tutorial im Startpost schrieb:


      Jetzt ist es vom Vorteil, wenn das äußerste Element den selben Namen hat, wie der Standard-NameSpace des Projektes. Denn dadurch ist die Settings-Klasse direkt aufrufbar und man spart sich die Angabe bzw. den Import des NameSpaces.
      "Luckily luh... luckily it wasn't poi-"
      -- Brady in Wonderland, 23. Februar 2015, 1:56
      Desktop Pinner | ApplicationSettings | OnUtils