UserSettingsProvider (Persistieren von UserSettings)

    • VB.NET

    Es gibt 28 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

      UserSettingsProvider (Persistieren von UserSettings)

      Hallo Community!

      Diese kleine Projekt zeigt, wie man seine Settings an seine eigene Bedürfnisse angepasst persistiert.
      (Entstehungsgeschichte)

      Anforderung:
      Man soll die Settings genauso handhaben können, wie es der Standard im VS vorsieht:
      • automatisches Speichern bei Beenden der App.
      • mit dem Settingsdesigner Eigenschaften anlegen und Typen auswählen können
      • ApplicationSettings binden können
      • den Designer beim Binden verwenden können
      • Der Provider kann zeitgleich von mehreren Anwendungen verwendet werden - jede Anwendung hat dann seine eigene Settingsdatei!
      • ...
      Das Kernstück liegt im eigens geschriebener UserSettingsProvider, der als .dll-Projekt vorliegt.

      UserSettingsProvider

      VB.NET-Quellcode

      1. Imports System.Configuration
      2. Imports System.ComponentModel
      3. Imports System.Reflection
      4. Imports System.IO
      5. Imports System.Collections.Specialized
      6. Imports System.Runtime.Serialization.Formatters.Binary
      7. Namespace MyProvider
      8. ''' <summary>
      9. ''' Dieser Provider speichert die betroffenen Settings-Einstellungen in eine eigene .exe.config.xml-Datei.
      10. ''' Diese Datei liegt im Arbeitsverzeichnis der Anwendung.
      11. ''' HINWEISE:
      12. ''' Die ApplicationName-Property erstellt die ProduktName-Eigenschaft von der My.Application.Info.ProduktName
      13. ''' Hier sollte in den Projekteigenschaften/Anwendung/Assemblyinformationen die Eigenschaft ProduktName = AssemblyName gestellt sein!
      14. ''' </summary>
      15. ''' <remarks></remarks>
      16. Public Class UserSettingsProvider
      17. Inherits SettingsProvider
      18. Implements IApplicationSettingsProvider
      19. Private Datas As Dictionary(Of String, Object)
      20. Private Property DataFile As FileInfo
      21. Private Property AppPath As String
      22. Sub New()
      23. Dim fi = New FileInfo(Assembly.GetExecutingAssembly.Location)
      24. ApplicationName = fi.Name.Replace(fi.Extension, String.Empty)
      25. AppPath = fi.FullName.Replace(fi.Name, String.Empty)
      26. 'DataFile = New FileInfo(Path.Combine(AppPath, String.Concat(ApplicationName, ".exe.config.dat")))'weglassen
      27. Datas = New Dictionary(Of String, Object)()
      28. End Sub
      29. Public Overrides Sub Initialize(ByVal name As String, ByVal config As NameValueCollection)
      30. MyBase.Initialize(ApplicationName, config)
      31. End Sub
      32. Public Overrides Property ApplicationName() As String ' hier wird der Produktname von der Assembly angegeben
      33. Public Overrides ReadOnly Property Name As String
      34. Get
      35. Return "UserSettingsProvider"
      36. End Get
      37. End Property
      38. ''' <summary>
      39. ''' SetPropertyValues wird erst abgearbeitet, wenn ApplicationSettingsBase.Save durchgeführt wird.
      40. ''' ApplicationSettingsBase stellt sicher, dass für jeden einzelnen Provider nur seine markierten Werte herangenommen werden.
      41. ''' Egal, ob der Provider auf einer Einzel-Einstellung angesetzt ist oder ob klassenweit die Einstellung angegeben wurde.
      42. ''' Wenn Einstellungen nicht verändert wurden, müssen sie nicht gespeichert werden!
      43. ''' Anwendungsspezifische Einstellungen können nicht geändert werden und werden daher auch nicht gespeichert!
      44. ''' </summary>
      45. ''' <param name="context"></param>
      46. ''' <param name="collection"></param>
      47. Public Overrides Sub SetPropertyValues(ByVal context As SettingsContext, ByVal collection As SettingsPropertyValueCollection)
      48. For Each prpValue As SettingsPropertyValue In collection
      49. If Not prpValue.IsDirty OrElse (prpValue.SerializedValue Is Nothing) Then Continue For
      50. If IsApplicationScoped(prpValue.Property) Then Continue For
      51. Datas(prpValue.Name) = prpValue.SerializedValue
      52. Next
      53. SaveBinary(DataFile, Datas)
      54. End Sub
      55. Public Overrides Function GetPropertyValues(ByVal context As SettingsContext, ByVal collection As SettingsPropertyCollection) As SettingsPropertyValueCollection
      56. SetDataFile()
      57. If DataFile.Exists Then Datas = LoadBinary(DataFile)
      58. Dim col As New SettingsPropertyValueCollection()
      59. For Each prp As SettingsProperty In collection
      60. Dim Value As New SettingsPropertyValue(prp)
      61. If Datas.ContainsKey(prp.Name) Then Value = GetPropertyValue(prp) ' Wert von Datas einlesen
      62. col.Add(Value)
      63. Next
      64. Return col
      65. End Function
      66. ''' <summary>
      67. ''' Nur benutzerspezifische Eigenschaften sind zulässig.
      68. ''' Wenn eine Eigenschaft keinen Wert hat, wird der Defaultwert geladen.
      69. ''' </summary>
      70. ''' <param name="prp"></param>
      71. Private Function GetPropertyValue(ByVal prp As SettingsProperty) As SettingsPropertyValue
      72. Dim value As New SettingsPropertyValue(prp)
      73. If IsUserScoped(prp) Then value.SerializedValue = Datas(prp.Name)
      74. value.IsDirty = False
      75. Return value
      76. End Function
      77. ''' <summary>
      78. ''' Test auf anwendungsspezifische Eigenschaft
      79. ''' </summary>
      80. ''' <param name="prop"></param>
      81. Private Function IsApplicationScoped(ByVal prop As SettingsProperty) As Boolean
      82. Return HasSettingScope(prop, GetType(ApplicationScopedSettingAttribute))
      83. End Function
      84. ''' <summary>
      85. ''' Test auf benutzerdefinierte Eigenschaft
      86. ''' </summary>
      87. ''' <param name="prop"></param>
      88. Private Function IsUserScoped(prop As SettingsProperty) As Boolean
      89. Return HasSettingScope(prop, GetType(UserScopedSettingAttribute))
      90. End Function
      91. ''' <summary>
      92. ''' prüft auf erlaubte Einstellung, so wie es der LocalFileSettingsProvider auch macht ...
      93. ''' </summary>
      94. ''' <param name="prop"></param>
      95. ''' <param name="attributeType"></param>
      96. Private Function HasSettingScope(ByVal prop As SettingsProperty, ByVal attributeType As Type) As Boolean
      97. Dim isAppScoped As Boolean = prop.Attributes(GetType(ApplicationScopedSettingAttribute)) IsNot Nothing
      98. Dim isUserScoped As Boolean = prop.Attributes(GetType(UserScopedSettingAttribute)) IsNot Nothing
      99. If isUserScoped AndAlso isAppScoped Then Throw New ConfigurationErrorsException("BothScopeAttributes: " & prop.Name)
      100. If Not isUserScoped AndAlso Not isAppScoped Then Throw New ConfigurationErrorsException("NoScopeAttributes: " & prop.Name)
      101. Select Case True
      102. Case attributeType Is GetType(ApplicationScopedSettingAttribute) : Return isAppScoped
      103. Case attributeType Is GetType(UserScopedSettingAttribute) : Return isUserScoped
      104. Case Else : Return False
      105. End Select
      106. End Function
      107. ''' <summary>
      108. ''' Datenfile der gerade aktiven Anwendung bestimmen
      109. ''' </summary>
      110. ''' <remarks></remarks>
      111. Private Sub SetDataFile()
      112. Dim fi = New FileInfo(Assembly.GetExecutingAssembly.Location)
      113. AppPath = fi.FullName.Replace(fi.Name, String.Empty)
      114. DataFile = New FileInfo(Path.Combine(AppPath, String.Concat(ApplicationName, ".exe.config.dat")))
      115. End Sub
      116. ''' <summary>
      117. ''' Daten binär speichern
      118. ''' </summary>
      119. ''' <param name="DataFile"></param>
      120. ''' <param name="Datas"></param>
      121. Private Sub SaveBinary(DataFile As FileInfo, Datas As Dictionary(Of String, Object))
      122. SetDataFile()
      123. ' deleteLocalPathUserSettings()
      124. Using fs As Stream = File.Create(DataFile.FullName)
      125. Dim binfmt As New BinaryFormatter()
      126. binfmt.Serialize(fs, Datas)
      127. End Using
      128. End Sub
      129. ' ''' <summary>
      130. ' ''' falls der LocalUserAppDataPath vorhanden ist,
      131. ' ''' wird der Ordner mit allen untergeordneten Verzeichnissen gelöscht
      132. ' ''' </summary>
      133. ' ''' <remarks></remarks>
      134. ' Private Sub deleteLocalPathUserSettings()
      135. ' Dim di As New DirectoryInfo(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath)
      136. ' di = di.Parent.Parent.Parent
      137. ' If di.Exists Then di.Delete(True)
      138. ' End Sub
      139. ''' <summary>
      140. ''' binäre Daten laden
      141. ''' </summary>
      142. ''' <param name="DataFile"></param>
      143. Private Function LoadBinary(DataFile As FileInfo) As Dictionary(Of String, Object)
      144. SetDataFile()
      145. Using fs As Stream = File.Open(DataFile.FullName, FileMode.Open)
      146. Dim binfmt As New BinaryFormatter()
      147. Return CType(binfmt.Deserialize(fs), Dictionary(Of String, Object))
      148. End Using
      149. End Function
      150. #Region "IApplicationSettingsProvider Members"
      151. ''' <summary>
      152. ''' Abrufen der letzten Version der Settingseigenschaft
      153. ''' </summary>
      154. ''' <param name="context"></param>
      155. ''' <param name="property"></param>
      156. Public Function GetPreviousVersion(ByVal context As SettingsContext, ByVal [property] As SettingsProperty) As SettingsPropertyValue Implements IApplicationSettingsProvider.GetPreviousVersion
      157. Dim prpValue As New SettingsPropertyValue([property])
      158. If Datas.ContainsKey([property].Name) Then prpValue.PropertyValue = Datas([property].Name)
      159. Return prpValue
      160. End Function
      161. ''' <summary>
      162. ''' wieder auf die Defaultwerte der Settings zurücksetzen!
      163. ''' </summary>
      164. ''' <param name="context"></param>
      165. Public Sub Reset(ByVal context As SettingsContext) Implements IApplicationSettingsProvider.Reset
      166. If DataFile.Exists Then Datas.Clear() : SaveBinary(DataFile, Datas)
      167. End Sub
      168. ''' <summary>
      169. ''' ErfinderDesRades
      170. ''' fehlende Settings werden sofort der Sammlung entfernt
      171. ''' neue hinzugekommene Settings werden beim nächsten Speichervorgang gesichert
      172. ''' </summary>
      173. ''' <param name="context"></param>
      174. ''' <param name="properties"></param>
      175. Public Sub Upgrade(ByVal context As SettingsContext, ByVal properties As SettingsPropertyCollection) Implements IApplicationSettingsProvider.Upgrade
      176. If Datas.Count = 0 AndAlso DataFile.Exists Then Datas = LoadBinary(DataFile)
      177. Dim toDelete = Datas.Keys.Except(properties.Cast(Of SettingsProperty).Select(Function(p) p.Name)).ToList
      178. toDelete.ForEach(AddressOf Datas.Remove)
      179. End Sub
      180. #End Region
      181. End Class
      182. End Namespace

      Die Schnittstelle IApplicationSettingsProvider (Versionierung) könnte man weglassen...
      Dann bleiben die Schnittstellen-Member (Upgrade, Reset und GetPreviousVersion) bei den Settings ohne Funktion.
      Dieser Provider wird einfach einem Projekt mitgegeben und ein Verweis angelegt.
      Bei der Version DirectorySettingsProvider kann man sogar den Persistierpfad außerhalb des Providers benutzerdefiniert angeben...

      Wichtig:
      Der Provider wird entweder, wie unten gezeigt, in der Settings.vb den Settings vorgelegt.
      Man geht auf die Seite Menü/Projekt/Projekt-Eigenschaften/Einstellungen. Beim Anwählen des Menüpunkts Code anzeigen
      wird im My-Namespace eine Partial Class des Designercodes erzeugt, in der man dann das Attribut für den UserSettingsProvider angeben kann (der Provider ist dann für alle Settings zuständig)...

      VB.NET-Quellcode

      1. Namespace My
      2. <Global.System.Configuration.SettingsProvider(GetType(UserSettingsProvider.MyProvider.UserSettingsProvider))> _
      3. Partial Friend NotInheritable Class MySettings
      4. End Class
      5. End Namespace

      Oder man gibt den einzelnen Settings bei der Eigenschaft Provider den vollständig qualifizierten Providernamen an!
      Dann werden nur die extra gekennzeichneten Settings benutzerdefiniert gespeichert - die anderen werden wie üblich persistiert...
      Man könnte auch verschiedene Provider, die dem Projekt verwiesen sind, der einzelnen Settings zuordnen.

      DemoSolution:
      Da werden die Eigenschaften Form.Location und die Form.ClientSize als Settings gebunden.
      Zum Weiteren sind noch einige Controls zur Veranschaulichung dargestellt.
      Der Pfad zur application.config.dat ist in diesem Projekt im Anwendungspfad festgelegt. Dieser kann individuell angepasst werden...
      Diesen Provider kann man sich als Vorlage nehmen und an seine Bedürfnisse anpassen...

      Danke an:
      @ErfinderDesRades : für seine Vorschläge und Einwände
      @RodFromGermany : Hinweis bei Benutzung mehrerer Anwendungen, die den gleichen Provider verwenden

      Anbei jetzt die letzte Version der korrigierten DemoSolution.
      Dateien

      Dieser Beitrag wurde bereits 27 mal editiert, zuletzt von „VB1963“ ()

      Danke für deinen Hinweis - ich habe die DemoSolution erneuert!
      Mir ist da leider eine falsche Solution hineingerutscht... :|
      Interessant war hier, wenn man die Benamung der Settings im SettingsEditor vornimmt,
      diese Änderungen im FormsDesigner richtig und im DesignerCode aber nicht übernommen werden! 8|
      Es wird dann erst in der Laufzeit gemeldet, dass hier die geänderte Eigenschaft nicht zu finden ist...
      Also man soll sich da niemals darauf verlassen...
      jetzt gucke ich grad die Upgrade-Sub an, und scheint mir suspekt:

      VB.NET-Quellcode

      1. ''' <summary> Dictionary Datas() wird mit PropertyCollection synchronisiert und aufgeräumt </summary>
      2. Public Sub Upgrade(ByVal context As SettingsContext, ByVal properties As SettingsPropertyCollection) Implements IApplicationSettingsProvider.Upgrade
      3. If Datas.Count = 0 AndAlso DataFile.Exists Then Datas = LoadBinary(DataFile)
      4. For i = Datas.Count - 1 To 0 Step -1
      5. Dim Key = Datas.Keys(i)
      6. Dim f = False
      7. For Each c As SettingsProperty In properties
      8. If c.Name <> Key Then Continue For
      9. f = True : Exit For
      10. Next
      11. If f Then Continue For
      12. Datas.Remove(Key)
      13. Next
      14. End Sub
      Also wenn in den properties Keys fehlen, werden die auch aus dem Dictionary gelöscht, aber wenn neue Properties hinzukamen - muss da nix geadded werden?
      Die alten gespeicherten Settings können keine neuen Eigenschaften enthalten.
      Das kann nur in der neuen Version passieren und die muss erst gespeichert werden.
      Ich gebe zu, dass diese Schleife patschert ausgeführt ist. Die muss ich unbedingt umschreiben...

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VB1963“ ()

      Unten die neue Upgrade-Methode...

      VB.NET-Quellcode

      1. Public Sub Upgrade(ByVal context As SettingsContext, ByVal properties As SettingsPropertyCollection) Implements IApplicationSettingsProvider.Upgrade
      2. If Datas.Count = 0 AndAlso DataFile.Exists Then Datas = LoadBinary(DataFile)
      3. Dim tmp = (From e In properties Select DirectCast(e, SettingsProperty).Name).ToList
      4. For i = Datas.Count - 1 To 0 Step -1
      5. Dim Key = Datas.Keys(i)
      6. If Not tmp.Contains(Key) Then Datas.Remove(Key)
      7. Next
      8. End Sub
      Das geht noch besser

      VB.NET-Quellcode

      1. ''' <summary> entfernt in den Properties nicht mehr vorhandene Settings auch aus dem Datas-Dictionary </summary>
      2. Public Sub Upgrade(ByVal context As SettingsContext, ByVal properties As SettingsPropertyCollection) Implements IApplicationSettingsProvider.Upgrade
      3. If Datas.Count = 0 AndAlso DataFile.Exists Then Datas = LoadBinary(DataFile)
      4. Dim toDelete = Datas.Keys.Except(properties.Cast(Of SettingsProperty).Select(Function(p) p.Name)).ToList
      5. toDelete.ForEach(AddressOf Datas.Remove)
      6. End Sub
      Der Kommentar geht vlt. noch besser - aber immerhin sagt der, was da passiert.

      Und das hier ist mir auch suspekt:

      VB.NET-Quellcode

      1. ''' <summary> DataFile-Datei löschen und reload, d.h. es gelten wieder die Defaultwerte! </summary>
      2. Public Sub Reset(ByVal context As SettingsContext) Implements IApplicationSettingsProvider.Reset
      3. If DataFile.Exists Then DataFile.Delete() : Datas = LoadBinary(DataFile)
      4. End Sub
      Erst wird DataFile deleted, und dann soll davon geladen werden??

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

      Au, da hast du recht - die Reset-Methode failt und ist so nicht zu gebrauchen, das habe ich leider nicht mehr getestet gehabt :S .
      Im Grunde genommen geht es hierbei nur darum, dass die gespeicherten Properties aus dem Datafile geleert sind, d. h.,
      dass das Datenfile leer sein soll oder gar gelöscht ist.
      Nach dem Reset werden die Settings neu initialisiert und versucht neu zu Laden.
      Aber weil keine Daten vorhanden sind, werden die Defaultwerte herangenommen.

      VB.NET-Quellcode

      1. ' Dictionary Datas() löschen und speichern, d.h. es gelten wieder die Defaultwerte bei den Properties!
      2. Public Sub Reset(ByVal context As SettingsContext) Implements IApplicationSettingsProvider.Reset
      3. If DataFile.Exists Then Datas.Clear() : SaveBinary(DataFile, Datas)
      4. End Sub

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

      @VB1963 Ich hab da ein Problem.

      VB.NET-Quellcode

      1. Dim fi = New FileInfo(Assembly.GetExecutingAssembly.Location)
      liefert den Pfad zur DLL, nicht aber den Pfad zur dazugehörigen Exe.
      Wenn 2 Exen in einem Verzeichnis darauf zugreifen, überschreiben sie sich gegenseitig die Settings.
      Nun wollt ich mir mit coredll.dll.GetModuleFileName() den gesuchten Namen holen, aber das knallt hier bei meinem sch..... W8:
      An exception of type 'System.DllNotFoundException' occurred

      ------
      Die Prozedur gibt es noch mal in der "Kernel32.dll".

      VB.NET-Quellcode

      1. <DllImport("kernel32.dll", SetLastError:=True)> <PreserveSig()> _
      2. Public Shared Function GetModuleFileName(<[In]()> ByVal hModule As IntPtr, <Out()> ByVal lpFilename As StringBuilder, <[In]()> <MarshalAs(UnmanagedType.U4)> ByVal nSize As Integer) As UInteger
      3. End Function

      VB.NET-Quellcode

      1. Sub New()
      2. Dim sb As New StringBuilder(1024)
      3. GetModuleFileName(IntPtr.Zero, sb, 1024)
      4. Dim exePath = sb.ToString
      5. Dim fi = New FileInfo(exePath)
      6. ApplicationName = IO.Path.GetFileNameWithoutExtension(exePath)
      7. ' in der Debug-Umgebung auf dieselben Settings zugreifen
      8. If ApplicationName.ToLower.EndsWith(".vshost") Then
      9. ApplicationName = IO.Path.GetFileNameWithoutExtension(ApplicationName)
      10. End If
      11. AppPath = fi.DirectoryName
      12. DataFile = New FileInfo(IO.Path.Combine(AppPath, String.Concat(ApplicationName, ".exe.config.dat")))
      13. Datas = New Dictionary(Of String, Object)()
      14. End Sub

      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

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

      Bei Projekt/Eigenschaften/Anwendung/Assemblyinformationen...
      kannst du einfach bei der Produktangabe einen Produktnamen für die Anwendung angeben, der zu anderen Anwendungen, die den Provider verwenden,
      verschieden sein muss.
      Dann kann er die Namen der Anwendungen unterscheiden (der wird im Provider mit der Property ApplicationName() abgerufen...)
      Ich hoffe das hilft dir weiter...

      VB1963 schrieb:

      Dann kann er die Namen der Anwendungen unterscheiden
      Wie gesagt:
      Ein Pfad, mehrere Exen, eine UserSettingsProvider.dll
      Der Setter von ApplicationName() wird im Konstruktor falsch befüllt:

      VB1963 schrieb:

      VB.NET-Quellcode

      1. Dim fi = New FileInfo(Assembly.GetExecutingAssembly.Location)
      2. ApplicationName = fi.Name.Replace(fi.Extension, String.Empty)
      Sieh Dir den Output von GetExecutingAssembly an:

      VB.NET-Quellcode

      1. Console.WriteLine(Assembly.GetExecutingAssembly.Location)
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Ja, das stimmt schon, aber die Property überschreibt sich irgendwann nach der Initialisierung und dann
      müsste etwas später der Produktname deiner Anwendung abgerufen werden können (je nach dem welche Anwendung den Provider gerade benutzt)...
      Da meine ich die Zeile #39 oben im 1.Post im Spoiler...

      VB1963 schrieb:

      abgerufen werden können
      Da ist aber DataFile bereits initialisiert.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Sorry, jetzt weis ich warum's so ist... :/
      Dieses Demo ist hier nur für eine .exe gedacht, und da habe ich mich bereits für 'UserSettingsProvider.exe.config.dat', das im Konstruktor zusammengebaut wurde, entschieden. Das sich der Provider für mehrere Anwendungen zugleich eignet - habe ich hier gar nicht behandelt X/
      Bei mehreren Anwendungen genügt es erst beim Persistieren die Property ApplicationName() ab zu rufen - dann sollte die momentan verantwortliche Anwendung ausgegeben werden...

      VB1963 schrieb:

      ab zu rufen - dann
      kommt der Name der DLL raus.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Ich habe den Provider jetzt ein wenig umgebaut:
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Imports System.Configuration
      2. Imports System.ComponentModel
      3. Imports System.Reflection
      4. Imports System.IO
      5. Imports System.Collections.Specialized
      6. Imports System.Runtime.Serialization.Formatters.Binary
      7. Namespace MyProvider
      8. ''' <summary>
      9. ''' Dieser Provider speichert die betroffenen Settings-Einstellungen in eine eigene .exe.config.xml-Datei.
      10. ''' Diese Datei liegt im Arbeitsverzeichnis der Anwendung.
      11. ''' HINWEISE:
      12. ''' Die ApplicationName-Property erstellt die ProduktName-Eigenschaft von der My.Application.Info.ProduktName
      13. ''' Hier sollte in den Projekteigenschaften/Anwendung/Assemblyinformationen die Eigenschaft ProduktName = AssemblyName gestellt sein!
      14. ''' </summary>
      15. ''' <remarks></remarks>
      16. Public Class UserSettingsProvider
      17. Inherits SettingsProvider
      18. Implements IApplicationSettingsProvider
      19. Private Datas As Dictionary(Of String, Object)
      20. Private Property DataFile As FileInfo
      21. Private Property AppPath As String
      22. Sub New()
      23. Dim fi = New FileInfo(Assembly.GetExecutingAssembly.Location)
      24. ApplicationName = fi.Name.Replace(fi.Extension, String.Empty)
      25. AppPath = fi.FullName.Replace(fi.Name, String.Empty)
      26. 'DataFile = New FileInfo(Path.Combine(AppPath, String.Concat(ApplicationName, ".exe.config.dat")))'weglassen
      27. Datas = New Dictionary(Of String, Object)()
      28. End Sub
      29. Public Overrides Sub Initialize(ByVal name As String, ByVal config As NameValueCollection)
      30. MyBase.Initialize(ApplicationName, config)
      31. End Sub
      32. Public Overrides Property ApplicationName() As String ' hier wird der Produktname von der Assembly angegeben
      33. Public Overrides ReadOnly Property Name As String
      34. Get
      35. Return "UserSettingsProvider"
      36. End Get
      37. End Property
      38. ''' <summary>
      39. ''' SetPropertyValues wird erst abgearbeitet, wenn ApplicationSettingsBase.Save durchgeführt wird.
      40. ''' ApplicationSettingsBase stellt sicher, dass für jeden einzelnen Provider nur seine markierten Werte herangenommen werden.
      41. ''' Egal, ob der Provider auf einer Einzel-Einstellung angesetzt ist oder ob klassenweit die Einstellung angegeben wurde.
      42. ''' Wenn Einstellungen nicht verändert wurden, müssen sie nicht gespeichert werden!
      43. ''' Anwendungsspezifische Einstellungen können nicht geändert werden und werden daher auch nicht gespeichert!
      44. ''' </summary>
      45. ''' <param name="context"></param>
      46. ''' <param name="collection"></param>
      47. Public Overrides Sub SetPropertyValues(ByVal context As SettingsContext, ByVal collection As SettingsPropertyValueCollection)
      48. For Each prpValue As SettingsPropertyValue In collection
      49. If Not prpValue.IsDirty OrElse (prpValue.SerializedValue Is Nothing) Then Continue For
      50. If IsApplicationScoped(prpValue.Property) Then Continue For
      51. Datas(prpValue.Name) = prpValue.SerializedValue
      52. Next
      53. SaveBinary(DataFile, Datas)
      54. End Sub
      55. Public Overrides Function GetPropertyValues(ByVal context As SettingsContext, ByVal collection As SettingsPropertyCollection) As SettingsPropertyValueCollection
      56. SetDataFile()
      57. If DataFile.Exists Then Datas = LoadBinary(DataFile)
      58. Dim col As New SettingsPropertyValueCollection()
      59. For Each prp As SettingsProperty In collection
      60. Dim Value As New SettingsPropertyValue(prp)
      61. If Datas.ContainsKey(prp.Name) Then Value = GetPropertyValue(prp) ' Wert von Datas einlesen
      62. col.Add(Value)
      63. Next
      64. Return col
      65. End Function
      66. ''' <summary>
      67. ''' Nur benutzerspezifische Eigenschaften sind zulässig.
      68. ''' Wenn eine Eigenschaft keinen Wert hat, wird der Defaultwert geladen.
      69. ''' </summary>
      70. ''' <param name="prp"></param>
      71. Private Function GetPropertyValue(ByVal prp As SettingsProperty) As SettingsPropertyValue
      72. Dim value As New SettingsPropertyValue(prp)
      73. If IsUserScoped(prp) Then value.SerializedValue = Datas(prp.Name)
      74. value.IsDirty = False
      75. Return value
      76. End Function
      77. ''' <summary>
      78. ''' Test auf anwendungsspezifische Eigenschaft
      79. ''' </summary>
      80. ''' <param name="prop"></param>
      81. Private Function IsApplicationScoped(ByVal prop As SettingsProperty) As Boolean
      82. Return HasSettingScope(prop, GetType(ApplicationScopedSettingAttribute))
      83. End Function
      84. ''' <summary>
      85. ''' Test auf benutzerdefinierte Eigenschaft
      86. ''' </summary>
      87. ''' <param name="prop"></param>
      88. Private Function IsUserScoped(prop As SettingsProperty) As Boolean
      89. Return HasSettingScope(prop, GetType(UserScopedSettingAttribute))
      90. End Function
      91. ''' <summary>
      92. ''' prüft auf erlaubte Einstellung, so wie es der LocalFileSettingsProvider auch macht ...
      93. ''' </summary>
      94. ''' <param name="prop"></param>
      95. ''' <param name="attributeType"></param>
      96. Private Function HasSettingScope(ByVal prop As SettingsProperty, ByVal attributeType As Type) As Boolean
      97. Dim isAppScoped As Boolean = prop.Attributes(GetType(ApplicationScopedSettingAttribute)) IsNot Nothing
      98. Dim isUserScoped As Boolean = prop.Attributes(GetType(UserScopedSettingAttribute)) IsNot Nothing
      99. If isUserScoped AndAlso isAppScoped Then Throw New ConfigurationErrorsException("BothScopeAttributes: " & prop.Name)
      100. If Not isUserScoped AndAlso Not isAppScoped Then Throw New ConfigurationErrorsException("NoScopeAttributes: " & prop.Name)
      101. Select Case True
      102. Case attributeType Is GetType(ApplicationScopedSettingAttribute) : Return isAppScoped
      103. Case attributeType Is GetType(UserScopedSettingAttribute) : Return isUserScoped
      104. Case Else : Return False
      105. End Select
      106. End Function
      107. ''' <summary>
      108. ''' Datenfile der gerade aktiven Anwendung bestimmen
      109. ''' </summary>
      110. ''' <remarks></remarks>
      111. Private Sub SetDataFile()
      112. Dim fi = New FileInfo(Assembly.GetExecutingAssembly.Location)
      113. AppPath = fi.FullName.Replace(fi.Name, String.Empty)
      114. DataFile = New FileInfo(Path.Combine(AppPath, String.Concat(ApplicationName, ".exe.config.dat")))
      115. End Sub
      116. ''' <summary>
      117. ''' Daten binär speichern
      118. ''' </summary>
      119. ''' <param name="DataFile"></param>
      120. ''' <param name="Datas"></param>
      121. Private Sub SaveBinary(DataFile As FileInfo, Datas As Dictionary(Of String, Object))
      122. SetDataFile()
      123. deleteLocalPathUserSettings()
      124. Using fs As Stream = File.Create(DataFile.FullName)
      125. Dim binfmt As New BinaryFormatter()
      126. binfmt.Serialize(fs, Datas)
      127. End Using
      128. End Sub
      129. ''' <summary>
      130. ''' falls der LocalUserAppDataPath vorhanden ist,
      131. ''' wird der Ordner mit allen untergeordneten Verzeichnissen gelöscht
      132. ''' </summary>
      133. ''' <remarks></remarks>
      134. Private Sub deleteLocalPathUserSettings()
      135. Dim di As New DirectoryInfo(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath)
      136. di = di.Parent.Parent.Parent
      137. If di.Exists Then di.Delete(True)
      138. End Sub
      139. ''' <summary>
      140. ''' binäre Daten laden
      141. ''' </summary>
      142. ''' <param name="DataFile"></param>
      143. Private Function LoadBinary(DataFile As FileInfo) As Dictionary(Of String, Object)
      144. SetDataFile()
      145. Using fs As Stream = File.Open(DataFile.FullName, FileMode.Open)
      146. Dim binfmt As New BinaryFormatter()
      147. Return CType(binfmt.Deserialize(fs), Dictionary(Of String, Object))
      148. End Using
      149. End Function
      150. #Region "IApplicationSettingsProvider Members"
      151. ''' <summary>
      152. ''' Abrufen der letzten Version der Settingseigenschaft
      153. ''' </summary>
      154. ''' <param name="context"></param>
      155. ''' <param name="property"></param>
      156. Public Function GetPreviousVersion(ByVal context As SettingsContext, ByVal [property] As SettingsProperty) As SettingsPropertyValue Implements IApplicationSettingsProvider.GetPreviousVersion
      157. Dim prpValue As New SettingsPropertyValue([property])
      158. If Datas.ContainsKey([property].Name) Then prpValue.PropertyValue = Datas([property].Name)
      159. Return prpValue
      160. End Function
      161. ''' <summary>
      162. ''' wieder auf die Defaultwerte der Settings zurücksetzen!
      163. ''' </summary>
      164. ''' <param name="context"></param>
      165. Public Sub Reset(ByVal context As SettingsContext) Implements IApplicationSettingsProvider.Reset
      166. If DataFile.Exists Then Datas.Clear() : SaveBinary(DataFile, Datas)
      167. End Sub
      168. ''' <summary>
      169. ''' ErfinderDesRades
      170. ''' fehlende Settings werden sofort der Sammlung entfernt
      171. ''' neue hinzugekommene Settings werden beim nächsten Speichervorgang gesichert
      172. ''' </summary>
      173. ''' <param name="context"></param>
      174. ''' <param name="properties"></param>
      175. Public Sub Upgrade(ByVal context As SettingsContext, ByVal properties As SettingsPropertyCollection) Implements IApplicationSettingsProvider.Upgrade
      176. If Datas.Count = 0 AndAlso DataFile.Exists Then Datas = LoadBinary(DataFile)
      177. Dim toDelete = Datas.Keys.Except(properties.Cast(Of SettingsProperty).Select(Function(p) p.Name)).ToList
      178. toDelete.ForEach(AddressOf Datas.Remove)
      179. End Sub
      180. #End Region
      181. End Class
      182. End Namespace
      und um die SetDataFile-Methode erweitert (siehe Zeile#124)
      Das Festlegen der DataFile erfolgt immer erst vor einer Persistierung...
      Ich habe es mit 2 verschiedenen Anwendung getestet und es erzeugt 2 verschiedene DataFiles.
      Hier ist es wichtig, dass bei den Anwendungen der Produktname in den Assemblyinformationen verschieden angegeben ist!
      @VB1963 Wär doch gelacht, wenn wir das nicht hinkriegen.
      Was muss ich tun, um die Daten wie gewohnt als XML zu speichern / zu laden?
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      VB1963 schrieb:

      SetDataFile
      In GetPropertyValues() muss auch ein SetDataFile() rein.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „RodFromGermany“ ()