none
How to save current font settings in a program to a file and retrieve them later

    Question

  • Say you have two users who want different values for My.Settings. You want to save each user's preferences to separate files (NOT with My.Settings.Save). I save the font like this:

        Sub SaveFont
          With My.Settings.OptionsFont
             AppendSetting(.FontFamily.ToString)
             AppendSetting(.Size.ToString)
             AppendSetting(.Style.ToString)
          End With
        End Sub
    
        Sub AppendSetting(Setting As Object)
            File.AppendAllText(ExpertFile, Setting & vbCrLf)
        End Sub
    

    That compiles and works.

    Then I try to retrieve the font like this (after loading the settings into an array from the file with File.ReadAllLines):

                .OptionsFont = New Font(fontfamily:=Settings(19), Size:=Settings(20), style:=Settings(21))
    That won't compile, error says bad arguments. How can I fix it?



    Robert Homes


    • Edited by Robert Homes Thursday, December 07, 2017 2:01 PM
    Thursday, December 07, 2017 1:58 PM

Answers

  • This is an addition to my original question. I changed the line for reading the font to this:

        .OptionsFont = New Font(family:=Settings(19), emSize:=Val(Settings(20)), style:=Settings(21))

    The line now compiles, but raises an error which says that you can't convert a string to a font family. So my question now is, how do you read the file's text for Font Family and set the Font Family accordingly?

    The line in the file for font family is:

    [FontFamily: Name=Tempus Sans ITC]


    Robert Homes

    I wouldn't use Application Settings for this. A binary file makes more sense and by serializing it to a binary file, any valid .Net type will be persisted.

    Another advantage to serialization (binary, XML, or SOAP) is that the formatter handles nulls.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    • Marked as answer by Robert Homes Thursday, December 07, 2017 3:22 PM
    Thursday, December 07, 2017 2:44 PM

All replies

  • Go back to your other thread.

    This can also be done by saving individual instances out to individual files.

    Read what I just posted in your other question and I think it'll be obvious.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 2:09 PM
  • This is an addition to my original question. I changed the line for reading the font to this:

        .OptionsFont = New Font(family:=Settings(19), emSize:=Val(Settings(20)), style:=Settings(21))
    

    The line now compiles, but raises an error which says that you can't convert a string to a font family. So my question now is, how do you read the file's text for Font Family and set the Font Family accordingly?

    The line in the file for font family is:

    [FontFamily: Name=Tempus Sans ITC]


    Robert Homes

    Thursday, December 07, 2017 2:40 PM
  • This is an addition to my original question. I changed the line for reading the font to this:

        .OptionsFont = New Font(family:=Settings(19), emSize:=Val(Settings(20)), style:=Settings(21))

    The line now compiles, but raises an error which says that you can't convert a string to a font family. So my question now is, how do you read the file's text for Font Family and set the Font Family accordingly?

    The line in the file for font family is:

    [FontFamily: Name=Tempus Sans ITC]


    Robert Homes

    I wouldn't use Application Settings for this. A binary file makes more sense and by serializing it to a binary file, any valid .Net type will be persisted.

    Another advantage to serialization (binary, XML, or SOAP) is that the formatter handles nulls.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    • Marked as answer by Robert Homes Thursday, December 07, 2017 3:22 PM
    Thursday, December 07, 2017 2:44 PM
  • Say you have two users who want different values...

    Is this what you meant a while back about having one area for common data? Otherwise it wouldn't make much sense because you'd be talking about two different computers.

    If so then we can put all of this (and your other question) together.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 3:07 PM
  • Frank,

    Actually, I just used the example of 2 different users to avoid having to give the following true explanation. In reality, there is only one (assumed) user, but the program has a feature I call "Expert Mode"; if you set that to true, you see all the options and features of the program, while setting it false, the options and features are severely limited. When the user switches from "Expert" to "Basic" mode, several options are changed and then hidden from the user (to simplify what he/she is seeing). Then when he switches back to Expert Mode, I want to be able to restore those options which were changed to what they were before. E.g., I have an option "Confirm Deletes" (my.settings.OptionConfirmDeletes). In Basic Mode, that should be turned on, and then the user shouldn't see it or be able to change it while in that mode. But back in Expert Mode, the option should be restored to whatever it was in Expert Mode. I have about 20 of these things where the setting is changed for Basic mode and needs to be restored back in Expert mode. I have many more features that aren't changed for Basic mode, but are simply hidden from the User. But those don't need to be remembered and saved and restored later; since they aren't visible in Basic Mode, and thus can't be changed by the user there. I need to save/restore only those options which the program itself changes when switching to Basic Mode.

    Obviously, all that wouldn't work with just the My.Settings.Save feature, so the "Expert" settings need to be in a file apart from My.Settings, and then restored to My.Settings when back in Expert Mode.

    I tend to add too many features to my programs! When my son tried to deal with all of them in a recent program I wrote for him, he suggested the "Expert/Basic" thing, and I like it so much, I'm going to put in all of my complicated programs from now on. The code you wrote for me using the Binary stuff will help me a lot in the future. Thanks so much.


    Robert Homes

    Thursday, December 07, 2017 3:36 PM

  •  The code you wrote for me using the Binary stuff will help me a lot in the future. Thanks so much.


    Robert Homes

    Give me 20 minutes please - there's something that you're misunderstanding (or I think there is) that might cause you consternation.

    There's also a way around it - a  better way overall - but bear with me to explain please.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 3:42 PM
  • Robert,

    In the other question, even though I showed them separately, this is what I had:

    Public Class Form1
        Private Sub _
            Form1_Load(sender As Object, _
                       e As EventArgs) _
                       Handles MyBase.Load
    
            Dim data As New MyData _
                With {.B1 = True, .B2 = False, .B3 = False, _
                      .I1 = 1, .I2 = 2, .I3 = 3, _
                      .F1 = New Font("Tahoma", 9, FontStyle.Bold), _
                      .F3 = New Font("Calibri", 12, FontStyle.Italic)}
    
            Dim desktop As String = _
                Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
    
            Dim filePath As String = IO.Path.Combine(desktop, "TestMe.bin")
    
            SharedMethods.SaveBinaryData(data, filePath)
    
            Dim newData As MyData = _
                DirectCast(SharedMethods.LoadBinaryData(filePath), MyData)
    
            Stop
    
        End Sub
    End Class
    
    
    
    
    
    <Serializable()> _
    Public Structure MyData
        Public Property B1 As Boolean
        Public Property B2 As Boolean
        Public Property B3 As Boolean
        Public Property I1 As Integer
        Public Property I2 As Integer
        Public Property I3 As Integer
        Public Property F1 As Font
        Public Property F2 As Font
        Public Property F3 As Font
    End Structure

    There's also the class which has the shared methods that's not shown there but here's the part that you might not be aware of:

    With binary serialization, the identity is maintained. With XML serialization (or no serialization) that's not so. With SOAP - which is XML of a different flavor - that's not so but with binary it is.

    Here's how it's doing that:

    The program that I opened and put that it is called "Forum Query 5" but look at the code above. I did NOT put that structure (I'd use a class, not a structure, but you get the point) inside Form1; it is, though, in the same assembly ... the assembly's name is "Forum Query 5".

    Here's my point: Let's say you set something up and you think you'll use it in various programs. Will that be a problem here?

    YES!

    It will serialize but you can see above that the serialized output will know where it came from and it won't deserialize because it's not from the same assembly (ergo, it's not the identity that created it).

    *****

    There's a solution here but I want to be sure that you're following what I mean so far?


    "A problem well stated is a problem half solved.” - Charles F. Kettering


    • Edited by Frank L. Smith Thursday, December 07, 2017 3:56 PM ...typo
    Thursday, December 07, 2017 3:55 PM
  • Edit to above: I highlighted too far.

    Stop after PublicToken=null.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 3:58 PM
  • Frank,

    I don't really understand. I mean, I don't see why I can take your code and put it in a module and copy the module to a vb project and use it there. I would be IN that project, not in the original project I used it in. What am I missing?


    Robert Homes

    Thursday, December 07, 2017 4:24 PM
  • Frank,

    I don't really understand. I mean, I don't see why I can take your code and put it in a module and copy the module to a vb project and use it there. I would be IN that project, not in the original project I used it in. What am I missing?


    Robert Homes

    The identity is based on the assembly:

    https://msdn.microsoft.com/en-us/library/ms973231.aspx

    That's an old MSDN document but it's still applicable.

    The best way - for this and other reasons - is to create our own dotNET assembly with a class library. When you do that, the identity is the assembly (the class library) that contains it. It will always serialize from that and deserialize back to it.

    You can then add that reference to any dotNET project and it will always work. It will always work because it's always the same assembly that's doing the serialization/deserialization.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 4:29 PM
  • Frank,

    Here's the structure:

    Module modExpert
    
        <Serializable()>
        Structure ExpertSettingsStructure
            Public OptionsAttachWord As Boolean
            Public OptionsLimitMoves As Boolean
            Public OptionsWrapFile As Boolean
            Public OptionsUseTitle As Boolean
            Public OptionsTracking As Integer
            Public OptionsLogging As Boolean
            Public OptionsMusic As Boolean
            Public OptionsIntroSelection As Boolean
            Public OptionsPreview As Boolean
            Public OptionsShedSkin As Boolean
            Public OptionsClipToolbar As Boolean
            Public OptionsTrimMenus As Boolean
            Public OptionsDragDrop As Boolean
            Public OptionsSplashFast As Boolean
            Public OptionsQuickAction As Boolean
            Public OptionsConfirmDeletes As Boolean
            Public OptionsStayOnTop As Boolean
            Public OptionsFont As Font
        End Structure
        Public ExpertSettings As ExpertSettingsStructure
    

    Here's my version of your code for saving & restoring the thing:

       Sub SaveBinaryData(ByVal data As Object, ByVal filePath As String)
            Try
                Using fs As New IO.FileStream(filePath, IO.FileMode.Create)
                    Dim formatter As New Runtime.Serialization.Formatters.Binary.BinaryFormatter
                    formatter.Serialize(fs, data)
                End Using
            Catch ex As Exception
                Throw
            End Try
        End Sub
    
        Function LoadBinaryData(ByVal filePath As String) As Object
            Dim retVal As Object = Nothing
    
            Try
                Using fs As New IO.FileStream(filePath, IO.FileMode.Open)
                    Dim formatter As New Runtime.Serialization.Formatters.Binary.BinaryFormatter()
                    retVal = formatter.Deserialize(fs)
                End Using
            Catch ex As Exception
                retVal = Nothing
                Throw
            End Try
    
            Return retVal
        End Function

    And here's where I call your load function:

         ExpertSettings = LoadBinaryData(ExpertFile$)
    


    Robert Homes

    Thursday, December 07, 2017 4:30 PM
  • create our own dotNET assembly with a class library. 
    How the heck to I do that? (Nothing is ever simple, is it?)


    Robert Homes

    Thursday, December 07, 2017 4:33 PM
  • create our own dotNET assembly with a class library. 
    How the heck to I do that? (Nothing is ever simple, is it?)


    Robert Homes

    Good question!

    I'm not being snarky here - it's easy though. I figure if I can do it, anyone can.

    I'll use your last setup as an example. Am I right that you'd be setting these up one-per-user or how do you have in mind?


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 4:37 PM
  • Frank, is this the part that brings in the msdn.microsoft stuff?

    New Runtime.Serialization.Formatters.Binary.BinaryFormatter
                    formatter.Serialize . . . etc.


    Robert Homes

    Thursday, December 07, 2017 4:38 PM
  • Frank, is this the part that brings in the msdn.microsoft stuff?

    New Runtime.Serialization.Formatters.Binary.BinaryFormatter
                    formatter.Serialize . . . etc.


    Robert Homes


    Yes - when I set up the class library, I'll be adding that and several other references with Imports statements.

    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 4:39 PM
  • Frank,

    Am I right that you'd be setting these up one-per-user ...

    Not sure what you mean. Are you asking whether I'm writing something for a multiuser network? I'm not. The program is for one user at a time.


    Robert Homes

    Thursday, December 07, 2017 4:49 PM
  • Frank,

    Am I right that you'd be setting these up one-per-user ...

    Not sure what you mean. Are you asking whether I'm writing something for a multiuser network? I'm not. The program is for one user at a time.


    Robert Homes

    Ok, don't worry with it. If it needs to be changed later then we'll change it then.

    I'll be a few hours - I'll both create it and I'll step you through how to do the same thing.

    Stay tuned. :)


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 4:53 PM
  • Frank,

    Don't you have other/better things to do than giving me so much help?

    You said XML wouldn't have the same problem. Why not translate your code to that? Wouldn't that be much simpler?


    Robert Homes

    Thursday, December 07, 2017 5:04 PM
  • Frank,

    Don't you have other/better things to do than giving me so much help?

    You said XML wouldn't have the same problem. Why not translate your code to that? Wouldn't that be much simpler?


    Robert Homes

    If I get a call or e-mail from work then of course that will take priority - I don't have much option about that.

    *****

    XML serialization will do this but it's not the panacea that I might have portrayed it to be. In this case, it would walk through it pretty easily. It's the same thing that Application Settings uses - and it's based on the .ToString method.

    My reasoning for staying away from XML/SOAP is this: It's true that there is no identity, and that's a plus here, but there are drawbacks. XML (and especially SOAP) are very verbose. The file size is is a lot larger than it needs to be, and much larger than binary.

    The other thing is this: In this case you're not dealing with a collection but if you were, you'd be in a bind: XML (and SOAP) do NOT deal with generics. At all!

    What you'd end up doing would be to take it to an intermediary type on the way in and do it again on the way out and then one-by-one hand off this to that, and that to this and .... it's just a pain.

    It can be done but it's not as seamless as binary.

    As for the class library itself, this is it's own assembly and I get some advantage there. I'll show you one key one in a few hours.

    Be patient please...


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 5:15 PM
  • Robert,

    I use a much older version of Visual Studio so in the following, keep that in mind. Yours will be a little different but it should be similar.

    *****

    I have the project folder zipped up and uploaded here:

    http://www.fls-online.net/VBNet_Forum/12-07-17/Test_UserSettings.zip

    If you'll please download that and extract the contents, then open it in your version of Visual Studio. It may prompt you to convert it and if so, just follow the prompts and it should work ok.

    You'll see in Solution Explorer that there are two projects in the solution and in the following, you'll see how it got there.

    *****

    To create a new class library, I start with a WinForms project the same as though I'm creating a new WinForms application. I do that so that I have something to test it with once I get to that point.

    For this one, I called it "Test_UserSettings". Once it opens up I save it to file first then I add a new project to it:

    Now it asks what to create:

    At this point you want to choose to create a new class project. The name that you use here (see the bottom of the screenshot above) will be the name of the assembly and the name of the .dll file that it will create when you build it.

    I called this one "UserSettings" as you can see there and when you have a look at the contents of the zip file, you'll see "UserSettings.dll" in that zipped up directory.

    At this point, have a look at Solution Explorer and you'll see that you have two projects showing:

    By default, a new class named "Class1" is added for you and the file name is given the same as you see above. That's useless obviously. ;-)

    I'm going to add a new namespace because this will have two classes in it: The one that you expect and one that the consumer of the class will never know even exists. I'm going to name the namespace "Data" so using Solution Explorer, right-click and change the name:

    That changed the name of the class to "Data" but I then changed that to the namespace "Data" with no classes yet in it.

    Before I continue, please do understand this: The assembly that you seen shown above is what the serialization routine will refer to for identity. In fact I'll show that in a bit here.

    Now I start adding the classes and I start with one that won't be seen by the consumer. In my case, I obfuscate it (not applicable here) and you'd be hard-pressed to know that it ever existed. Even without that though, the class is given the access modifier "Friend" and you'll see that in the code when you get the solution open.

    If you were to use "Friend" in your WinForms projects, it may as well be "Public" because you're in the same assembly! This a stand-alone assembly all of itself and that's why all of this works.

    Have a good look around in that class. There's a bunch of shared methods and a few shared properties, fields, etc. and I don't want the consumer knowing about any of it. They use it, sure, but they don't know how it got there!

    Notice also that I'm taking advantage of the ApplicationData directory which is created there if it doesn't exist.

    By default, when you save the data (and/or when you load it back) it's looking for the file path that's declared there:

            Public Shared ReadOnly Property BinaryFilePath As String
                Get
                    Return Combine(ProgramDataFolderPath, "Data.bin")
                End Get
            End Property

    Notice this also please: In that class named "InternalData", everything is given the access level of PUBLIC. That might seem confusing but don't let it: The methods and properties, etc. are "public" in the context of a class which is "friend". Nothing outside of that assembly can see or use it.

    Moving on, now I create the one public class that I set out to here named "ExpertSettings". Have  a look through that, including one thing that you may want to change:

            Public Property Tracking As Integer
                Get
                    Return _tracking
                End Get
    
                Set(ByVal value As Integer)
                    ' You may want to change this: I'm going
                    ' to assume that whatever "tracking" is
                    ' that it can't be negative:
    
                    If value < 0 Then
                        Throw New ArgumentOutOfRangeException("Tracking", "Must be a non-negative value." & vbCrLf)
                    Else
                        _tracking = value
                    End If
                End Set
            End Property

    I am guessing here that whatever "tracking" is, it can't be negative. You might want to change that though.

    Also, for the font - which is a class that implements the IDisposble interface, I have the following:

            Public Property OptionsFont As Font
                Get
                    Return _optionsFont
                End Get
    
                Set(ByVal value As Font)
                    Dispose()
                    _optionsFont = value
                End Set
            End Property

    When you set it - including nullifying it if that's what you're doing - it's calling a private method that calls the font's .Dispose method (if the font isn't null). It then sets it to whatever you used.

    Now once you've got all of that, it's time to test it. Before you can test it, you have to build it:

    That has now created "UserSettings.dll" in the ...\bin\Release directory (depending on how it's set up but that's where it defaults to).

    I go now to my tester: Form1 that I started from. In Form1, I have to add a reference to it the same as you will in your real programs:

    I've also added an Imports statement that you'll see just to make for less typing:

    Imports UserSettings.Data
    
    Public Class Form1
        Private _desktop As String = _
            Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
    
        Private Sub _
            Form1_Load(sender As System.Object, _
                       e As System.EventArgs) _
                       Handles MyBase.Load
    
            Dim mySettings As New ExpertSettings
    
            With mySettings
                .OptionsFont = New Font("Calibri", 14)
                .Tracking = 2
            End With
    
            Stop
    
            ' Now hover your mouse over "mySettings" to
            ' confirm that the options font and tracking
            ' changed.
    
            ' Now I'll save the settings to my desktop to
            ' test that functionality:
    
            Dim filePath As String = _
                IO.Path.Combine(_desktop, "TestMe.bin")
    
            mySettings.SaveToBinary(filePath)
    
            Stop
    
            ' Now just to make sure, I'll set "mySettings"
            ' to null:
    
            mySettings = Nothing
    
            Stop
    
            ' Hover your mouse over "mySettings" to confirm
            ' that it's "Nothing". After that, let's load
            ' it back:
    
            mySettings = New ExpertSettings
            mySettings.LoadFromBinary(filePath)
    
            Stop
    
            ' Hover your mouse over "mySettings" and you'll
            ' see that it's back.
    
        End Sub
    End Class

    In that test routine above, you see that I'm first saving to my desktop (for testing) and if you look at the file using a standard text editor, you'll see why this whole thing works:

    Give all of that some thought and try it out realtime. :)

    ***** EDIT *****

    Once again, I went to far in highlighting the last screenshot. Sorry about that.


    "A problem well stated is a problem half solved.” - Charles F. Kettering


    • Edited by Frank L. Smith Thursday, December 07, 2017 7:10 PM ...typo and note about the last screenshot
    Thursday, December 07, 2017 6:58 PM
  • Addendum to what I posted: I meant to include the class diagram:


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 7:15 PM
  • Frank,

    Don't you have other/better things to do than giving me so much help?

    You said XML wouldn't have the same problem. Why not translate your code to that? Wouldn't that be much simpler?


    Robert Homes

    As a clarification to what I showed above about this, I'm specifically talking about serialization to XML, not the XML format itself.

    If you write your own, bypassing the serializer, then none what I said above applies.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 7:22 PM
  • Frank,

    Thank you so much! Just briefly looking at the first parts I can see this is going to be very helpful. I don't have time right now to get into it, but I'll try to study it carefully and get back to you tomorrow or over the weekend.

    All the Best,

    Robert Homes


    Robert Homes

    Thursday, December 07, 2017 7:29 PM
  • Frank,

    Thank you so much! Just briefly looking at the first parts I can see this is going to be very helpful. I don't have time right now to get into it, but I'll try to study it carefully and get back to you tomorrow or over the weekend.

    All the Best,

    Robert Homes


    Robert Homes

    Come back to it if/when you want.

    It's a lot to consider but all in all, it's not really a lot to it.

    I hope you find it useful. :)


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, December 07, 2017 7:40 PM
  • Frank,

    Is it possible for me to get your email address so I can correspond with you directly?


    Robert Homes

    Tuesday, December 12, 2017 12:26 PM
  • Frank,

    Is it possible for me to get your email address so I can correspond with you directly?


    Robert Homes

    I've never posted it here and I don't want to change that.

    If you want though, post your e-mail address, give it about two minutes, then delete your post. I'll still get it in an e-mail.


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Tuesday, December 12, 2017 1:23 PM