none
Configuration Files. What a pain. RRS feed

  • Question

  • I have an app that I started updating and I wanted to add an option to it so anyone can add what ever game they wanted too add. The app is base on mission editing. The idea is to let them add what ever game the wanted to and be able to keep track of them. The idea is to use a 10 char name as a file name for the game configuration. The game name. The games path. and so on. The issue is how to save it or better yet. what would be an idea to save it to what kind of format. what would be the easiest way to save this and then load it in. anyone have some ideas and some code would help.

    My idea is to have a list of games categories in a database that the use can add to and view. Current games are all so in a list that the user can pick from. The idea is to load the games configuration file when the user picks another game to manage.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Tuesday, March 5, 2019 10:33 PM

Answers

  • Good. Now I think we can make progress. I think you misunderstand how settings work. Here's the documentation for it but we'll walk through this step by step so you can more easily understand it hopefully.

    Open the `.settings` file in Visual Studio. This is the designer file that backs all the runtime settings. It is only used by the designer. Each setting you add here exposes a runtime setting that your app can use. In the designer you specify the setting name, type, whether it is scoped to the application or local to the user and its default value.

    Now go to Solution Explorer and expand the `.settings` file under Properties in your project. Then open the `.settings.designer.cs` file. This file is auto-generated as you make changes in the designer. Each setting you define in the designer gets a corresponding property declared here. It is this type that you're using in your code. Notice the attributes applied to the property. One of them is the default value that you put in the designer. This is baked into the source code at compilation time.

    Now go open the `app.config` file. The designer also added a section to your config with entries for each setting. You'll notice that each setting has a value that corresponds to the default value you put in the designer.

    Now we get to the runtime side of the fence. When you request a setting at runtime the framework uses a SettingsProvider type (the default being LocalFileSettingsProvider) to load the settings. If the provider does not find a value then it uses the attribute applied to the property instead. If the provider returns a value then it uses that instead.

    LocalFileSettingsProvider works with 2 different files - app.config and user.config. Application scoped settings are retrieved from `app.config`. Application scoped settings are considered to be read only because most apps do not have write access to the directory. These types of settings are best used for things that all users share although most people just use AppSettings or an equivalent instead.

    For user scoped settings each user gets their own stuff so the provider has to access a file that a user can read/write. This is where the `user.config` file comes in. By default the provider will store it under Documents, your app, your version. If your app ever sets a value to a user-scoped setting via the Settings class and then calls Save it goes into this file. This is what I've been referencing all along. It is here that your saved user settings are going. Yes this is an XML file but you don't ever need to muck with it directly. You should leave it alone and only use the Settings class to interact with it. Now, when the provider is requested to read a user-scoped variable it first tries to get it from the `user.config` file. If it doesn't exist then it falls back to looking in the `app.config`. As you saw earlier this is where the designer put the default value. So on the very first run of your version of the app it gets retrieved from `app.config` using the default value that was set when the designer file was saved last. However at any point after that if you call Save then all the user settings will be stored in `user.config` and future calls (for the same app/version) will use those values instead.

    Having covered all that, changing a setting's value in the designer only changes the default value that is stored in the code and config file. And even then you have to recompile your code for it to take effect. The default value will only be used if the `user.config` doesn't have it. If the `user.config` file has a value then that will always override the default. To reset it you'd need to either delete the file or, if programmatically, call Reset. Then the settings would revert to their default values (as defined in the config file).

    To change a user-scoped setting at runtime you'd set the value via the property and then call Save. This generates/updates the `user.config` file. The next time your app tries to read the value it'll comes from here. If you believe the file to be corrupt then delete it and run your app again to reset. 

    Going back to your original problem, if the `user.config` file does not exist then you should see `currgame` have whatever value you have currently defined in the settings designer (after recompilation). Once your code sets the value and calls Save then that will be the value you get the next time until you change and save again later.


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by Joesoft11a Wednesday, March 13, 2019 5:57 PM
    Wednesday, March 13, 2019 5:46 PM
    Moderator

All replies

  • Hi Joesoft11a,

    Thank your for posting here.

    For your question, settings in the config file is a good choice.

    You could refer to the simple example below. You could save the value in config file and read it in C# code.

    Here is a simple example in code project for your reference.

    https://www.codeproject.com/Articles/8168/Creating-Custom-Configurations

    Best Regards,

    Wendy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, March 6, 2019 2:43 AM
    Moderator
  • Wendy, Thanks for the info. This sample you sent me it for web pages. Do you know of a sample that can be used in a c# app. Windows Form.

    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 6, 2019 3:02 AM
  • Hi Joesoft11a,

    Is it same in C# between console app and Winform.

    Here are the samples for app.config.

    https://www.codeproject.com/Articles/9964/Custom-app-config

    https://www.codeproject.com/Articles/32490/Custom-Configuration-Sections-for-Lazy-Coders

    Best Regards,

    Wendy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, March 6, 2019 3:08 AM
    Moderator
  • It looks like that's what I was looking for. It's just it's not telling me how to create the config file in a name of my own choice. So that I'm confused with. I'll keep readying.

    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 6, 2019 3:31 AM
  • Hi Joesoft11a,

    Every project would have a config file. You do not need to create a config file. You just need create custom settings in the config file and read it as what you want.

    Best Regards,

    Wendy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, March 6, 2019 4:17 AM
    Moderator
  • OK, do you have a sample code for that or I don't under stand how the code works on the sample links you send me. The idea is to have one config file for each game. That could mean 100's of those files.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 6, 2019 8:45 AM
  • I don't recommend that you try to use the configuration subsystem to store your game-specific data. There is no value add there given the complexity. You mentioned using a database but then you mention a config per file. This doesn't really make sense to me. Why would you need/want to store part of the data in a database and the rest in a configuration file. You should pick one and use it for everything. There are plenty of articles on when to use a database vs other storage mechanisms but things to consider include:

    - How many people will be using the data?

    - Does the data need to be accessible across a network setting?

    - Do you want to have the data be portable so you could back it up and move it elsewhere easily?

    - Is the data per user or shared across all users?

    - How much data are you dealing with?

    If you decide to go the database route then you can have your games stored in a table with the basic information you need. If you need extended data about a game then create satellite tables that link back to the main table.

    If you decide to go the file route then create a single file to contain the games and any game-specific information you want. JSON has become the most popular format and would allow you to load/save data directly into business objects in your app. Unless you're talking about thousands of games then performance won't be an issue. Depending upon how your app works you could use a single data file for all users or each user could have their own data file in their Documents folder.

    If you really, really need to separate the core game data from "configuration" then the "config" files could be additional files in the same directory named such that your app can easily find them. But in my opinion this complicating things with little gain. What is the benefit in separating the "configuration" from the game data? You increase the likelihood of something getting disconnected, it takes longer to read the extra files and it'll take up a little more space. It also makes backing up and restoring data harder.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 6, 2019 3:00 PM
    Moderator
  • 1) Not known 100's could be using my app

    2) No, No network access. just a plain program on someone's PC.

    3) no it doesn't have to be portable.

    4) The games name, path. Mission editor ext name. and a few more.

    that's what I was thinking, It's just that I think that doesn't sql server express have limits on to what you can add to a database in tables and are the tables limited all so. The issue is that games can be removed and so on at any time. That's why I didn't want to use a database. It's would be easy just to setup one (1) in it's own config file that can be edited or deleted with it screwing up something else. See the app.config file is something I never played with. So that mite be too hard for me.

    a sample: Joint Ops. is a console game.

    jointops.dat is or could be the config file. Using the games short name 10 chars long it can load it and use that info from in that config file. It's just I wanted something better then a text file.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 6, 2019 4:06 PM
  • "It's just that I think that doesn't sql server express have limits on to what you can add to a database in tables and are the tables limited all so."

    No SQL has no limits beyond what any other relational database would have. It is literally SQL Server without the enterprise features. The main issue with SQL Express is that it has to be installed.

    "t's would be easy just to setup one (1) in it's own config file that can be edited or deleted with it screwing up something else"

    Deleting from a database requires 1 line of code so I don't agree with this assessment. In fact it is easier than if you're using a file that may potentially be opened by the program because you cannot delete a file that is open.

    I would lean toward either an in memory database like SQLite (which uses the same logic, albeit different types, as SQL) to make it easy to move around. But if you wanted a data file you can go that way as well. I don't think a separate data file for each game makes sense but even if you went that way then the underlying code would be the same. I'd use JSON. It takes one line of code to read the data in. I would not, under any circumstances, try to use a config file for this. That isn't what config files are designed for and it would require a lot more code anyway.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 6, 2019 4:23 PM
    Moderator
  • The issue I have is opening and closing the database all the time. I have the sql server database limited for a reason, anyway Each users using my app can create bases. Like "Single Player" Multiplayer", "coop" and so on. That's get's saved. Then a mission that he/she made can be added too the bases. The mission that the user add to the base gets zipped up and moved to a saved folder. where the user can unzip it and edit the mission in the games mission editor.

    if Json is the way to go. The searches I did on google didn't have anything that I fount that I could under stand. does anyone have some json files for creating, editing these files. I'll keep googling and see what I can come up with.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 6, 2019 4:33 PM
  • If you use an in memory DB then you won't notice open/close calls at all. Even with SQL Server itself you can do that a bunch of times and, because of connection pooling, you won't notice it. This is how web apps work and they can have 1000s of requests at any one time with each one using its own connection. 

    But perhaps JSON files are better for your approach. In that case you should look at JSON.NET. It is currently the standard JSON library people use for .NET. .NET Core 3 will have its own version that will be the standard most likely but it is not ready yet. The basics are covered here. But most people simply use JsonConvert which is one line for reads and writes.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 6, 2019 6:20 PM
    Moderator
  • Michael, Thanks. I'll have to study this. I never done this before. So I mite have to do just a basic text file. I was hoping for something better. The link you sent me I under stand the 1st part. Just don't under stand the 2nd part.

    This I under stand.

    StringBuilder sb = new StringBuilder();
    StringWriter sw = new StringWriter(sb);
    
    using (JsonWriter writer = new JsonTextWriter(sw))
    {
        writer.Formatting = Formatting.Indented;
    
        writer.WriteStartObject();
        writer.WritePropertyName("CPU");
        writer.WriteValue("Intel");
        writer.WritePropertyName("PSU");
        writer.WriteValue("500W");
        writer.WritePropertyName("Drives");
        writer.WriteStartArray();
        writer.WriteValue("DVD read/writer");
        writer.WriteComment("(broken)");
        writer.WriteValue("500 gigabyte hard drive");
        writer.WriteValue("200 gigabyte hard drive");
        writer.WriteEnd();
        writer.WriteEndObject();
    }
    
    // {
    //   "CPU": "Intel",
    //   "PSU": "500W",
    //   "Drives": [
    //     "DVD read/writer"
    //     /*(broken)*/,
    //     "500 gigabyte hard drive",
    //     "200 gigabyte hard drive"
    //   ]
    // }

    I see that I can write the json file. That's very plain for me to under stand. How ever I don't know how to read it in, So this part makes no sense.

    string json = @"{
       'CPU': 'Intel',
       'PSU': '500W',
       'Drives': [
         'DVD read/writer'
         /*(broken)*/,
         '500 gigabyte hard drive',
         '200 gigabyte hard drive'
       ]
    }";
    
    JsonTextReader reader = new JsonTextReader(new StringReader(json));
    while (reader.Read())
    {
        if (reader.Value != null)
        {
            Console.WriteLine("Token: {0}, Value: {1}", reader.TokenType, reader.Value);
        }
        else
        {
            Console.WriteLine("Token: {0}", reader.TokenType);
        }
    }
    
    // Token: StartObject
    // Token: PropertyName, Value: CPU
    // Token: String, Value: Intel
    // Token: PropertyName, Value: PSU
    // Token: String, Value: 500W
    // Token: PropertyName, Value: Drives
    // Token: StartArray
    // Token: String, Value: DVD read/writer
    // Token: Comment, Value: (broken)
    // Token: String, Value: 500 gigabyte hard drive
    // Token: String, Value: 200 gigabyte hard drive
    // Token: EndArray
    // Token: EndObject

    My idea was to load it in and then assign it to a class that I made. Then I can use that class any where in the form I need to until a new game is loaded in.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 6, 2019 6:54 PM
  • Like I said JsonConvert is generally the easier route provided you don't care too much about the actual JSON file format.

    //Data to write to JSON
    public class Game
    {
       public string Name { get; set; }
    
       public List<Player> Players { get; } = new List<Player>();
    }
    
    public class Player
    {
       public string Name { get; set; }
    
       public int Age { get; set; }
    }
    
    //Assume you have a list of Game objects (games) you want to write to JSON 
    var jsonData = JsonConvert.SerializeObject(games);
    File.WriteAllText("SomeFile.json", jsonData);
    
    //Read the file back in later
    var jsonData = File.ReadAllText("SomeFile.json");
    var games = JsonConvert.DeserializeObject<List<Game>>(jsonData);
    There are overloads that allow you some control over the data. The reader/writer code that I linked to initially is for more advanced scenarios if you need it. Most of the time you don't.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 6, 2019 8:01 PM
    Moderator
  • I see this in your code

    var jsonData = JsonConvert.SerializeObject(games);

    I don't under stand where did the games come from. I'm still learning about classes and how to use them sense I never used them before so I wanted to learn how by adding one into my project.

    Not sure let me see if I under stand this. The (games) is an object and you loaded in the info from the json file in this (games) object. Is that right. I just not sure if I under stand it.

    if this is right. I don't see any thing in the classes above that points to that class. You have to give each element a name when you load it in so that you can use it. Is that right. or am I wrong.

    See my class I'm making

    class categoryInfo
        {
    
            public string currPathstring { get; set; }
            public string currGame { get; set; }
            public string catDirsave { get; set; }
            public string category { get; set; }        
            public string missionext { get; set; }
    
            //public string 
    
        }

    there's only like one or 2 things I need to add to it. This is the class I will be using. So I just not sure if I under stand what you posted in your last link. Thanks again


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 6, 2019 9:34 PM
  • `games` in the write code was some variable that you had in your code somewhere that is storing the list of games. It is assumed to be of type List<Game>. It depends upon how you manage the data as to where this variable resides so my assumption was that you'd have it sitting around in a field somewhere that the read/write code had access to.

    In my code replace all references to `Game` with your type `categoryInfo` and you should be fine. It might help if you create, for now, a helper class to read, write, manage the list of games in your app.

    public class CategoryManager
    {
       public List<categoryInfo> Categories { get; } = new List<categoryInfo>();
    
       public void Load ( string filename )
       {
          var jsonData = File.ReadAllText(filename);
          Categories = JsonConvert.DeserializeObject<List<categoryInfo>>(jsonData);
       }
    
       public void Save ( string filename )
       {
          var jsonData = JsonConvert.SerializeObject(Categories);
          File.WriteAllText(filename, jsonData);
       }
    }
    

    If you have a single main form then the form can have a field of this type in it otherwise you'll need to store the list of games somewhere in your code that different classes can access.

    public class MainForm : Form
    {
       ...
    
       protected override OnLoad ( EventArgs e )
       {
          //Load the categories, if available
          var filename = "categories.json";
    
          if (File.Exists(filename))
             _categories.Load(filename);
    
          //Update the UI to show the category stuff
       }
    
       //For demo purposes, this method would trigger a call
       //to save any changes made to the categories
       void OnSave ()
       {
          _categories.Save("categories.json");
       }
    
       //The main form is responsible for tracking the categories
       //so create a field here to store it
       CategoryManager _categories = new CategoryManager();
    }


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 6, 2019 9:44 PM
    Moderator
  • or maybe something like this

    private void loadDefaults()
            {
    
                if (MLHSetting.Default.currGame != null)
                {
                    GameManager _games = new GameManager();
    
                    var GameData = FullPathWrite + @"games\" + MLHSetting.Default.currGame + ".json";
    
                    if (File.Exists(GameData))
                        _games.Load(GameData);
    
                    //inf.gamesave = FullPathWrite + @"save\" + MLHSetting.Default.currGame + @"\"; //where to save mission files
    
                }
                else
                {
                    //add a new game
                    gameDefaultForm def = new gameDefaultForm();
                    def.Show();
                    this.Close();
                }
    
                LoadCurrent(); loadBases();
            }
            

    and the class

     class categoryInfo
        {
    
            public string currPathstring { get; set; } //Path to mission editor samples
            public string gamename { get; set; } // Full name of game
            public string currGame { get; set; } // more then likely won't need this
            public string gamesave { get; set; } // path for saving zipped up mission
            public string category { get; set; } // Category of game 10 chars long
            public string missionext { get; set; } // Mission editors ext name used for missions
    
            //public string 
    
        }
    
        public class GameManager
        {
            public List<categoryInfo> Games { get; } = new List<categoryInfo>();
    
            public void Load(string filename)
            {
                var jsonData = File.ReadAllText(filename);
                Games = JsonConvert.DeserializeObject<List<categoryInfo>>(jsonData);
            }
    
            public void Save(string filename)
            {
                var jsonData = JsonConvert.SerializeObject(Games);
                File.WriteAllText(filename, jsonData);
            }
        }

    The only issue I see is that for some reason It's erroring out. It says that the Games is read only. So I'm confused about that.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 6, 2019 11:31 PM
  • I don't understand. What says Games is read only? Compiler, runtime, which line. 

    Michael Taylor http://www.michaeltaylorp3.net

    Thursday, March 7, 2019 2:51 PM
    Moderator

  • http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Thursday, March 7, 2019 3:03 PM
  • Change the `Games` property from a get to a get; private set.

    public List<CategoryInfo> Games { get; private set; } = new List<CategoryInfo>();


    Michael Taylor http://www.michaeltaylorp3.net

    Thursday, March 7, 2019 3:39 PM
    Moderator
  • That fixed one issue. just one left.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Thursday, March 7, 2019 5:19 PM
  • Mike, any idea at all. I can't even compile my app.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Friday, March 8, 2019 5:13 PM
  • Sorry, I thought you were looking at your last issue. Images don't come across in the emails for me. 

    The Games property is marked public and so is GameManager. Therefore Game (the type) would need to be marked as public. Right now it probably doesn't specify an accessibility so it defaults to internal.

    public class Game


    Michael Taylor http://www.michaeltaylorp3.net

    Friday, March 8, 2019 5:51 PM
    Moderator
  • Mike, Thanks. Now I have a way to load it in, How about saving it.

    something like this I guess.

    configurationinfo cfg = new configurationinfo();
    
    cfg.currpathstring = "c:\path to missions";
    //and so on. 
    
    string FullPath "Path to Jason file":
    
    configmanager Mgr = new configmanager();
    
    Mgr.Save(FullPath);
    
    //would this be the right way to set the class and then //save it. 


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Friday, March 8, 2019 9:15 PM
  • Based upon how the code was originally written the configmanager class is managing the list of configurationinfo items so you'd need to make sure that the new `cfg` object was added to it and then save.

    Note that the configmanager class (as I posted it) was designed to save a single JSON file with all the configurationinfo objects into a single file. If you're actually putting a configurationinfo object into each file then configmanager may do better to forego the list of items and simply have a Load method that returns the one configurationinfo defined in the file and Save to accept the object and filename and save it out. At this point configmanager may not be overly useful at all and you could move that logic into the configurationinfo type instead. Then you'd just create the object, set the properties and call Save on it. 


    Michael Taylor http://www.michaeltaylorp3.net

    Friday, March 8, 2019 9:21 PM
    Moderator
  • OK, Thanks. I'll code it in and give it a try.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Friday, March 8, 2019 9:49 PM
  • I started the area that creates a new game that the user types into a form, and then clicks save. The issue is that it saves the file OK. It's just the file only has [] in the file. Nothing from the form was saved. Let me post the code and the class and is anyone can help.

    private void btnSave_Click(object sender, EventArgs e)
            {
                try
                {
                    string filename = FullPathWrite + @"games\" + txtGameShort.Text + ".json";
    
                    gameInfo cfg = new gameInfo();
    
                    cfg.currGame = txtGameShort.Text;
                    MLHSetting.Default.currGame = txtGameShort.Text;
                    MLHSetting.Default.Save();
    
                    cfg.currPathstring = txtGamepath.Text;
                    cfg.gamename = txtGamename.Text;
                    cfg.missionext = txtEditor.Text;
                    cfg.category = txtCategory.Text;
                    
    
                    GameManager mgr = new GameManager();
                    mgr.Save(filename);
                                    
                    
                    this.Close();
                }
                catch (Exception w)
                {
                    MessageBox.Show(w.Message, Application.ProductName);
                }
                finally
                {
                    this.Close();
                }
            }

    Here's the class

    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    
    namespace MLHelper
    {
        public class gameInfo
        {
    
            public string currPathstring { get; set; } //Path to mission editor samples
            public string gamename { get; set; } // Full name of game
            public string currGame { get; set; } // more then likely won't need this
            public string gamesave { get; set; } // path for saving zipped up mission
            public string category { get; set; } // Category of game 10 chars long
            public string missionext { get; set; } // Mission editors ext name used for missions
    
            //public string 
    
        }
    
        public class GameManager
        {
            public List<gameInfo> Games { get; private set; } = new List<gameInfo>();
    
            public void Load(string filename)
            {
                var jsonData = File.ReadAllText(filename);
                Games = JsonConvert.DeserializeObject<List<gameInfo>>(jsonData);
            }
    
            public void Save(string filename)
            {
                var jsonData = JsonConvert.SerializeObject(Games);
                File.WriteAllText(filename, jsonData);
            }
        }
    }

    any ideas.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Tuesday, March 12, 2019 8:13 PM
  • Like I mentioned earlier, the original code I posted (GameManager) made the assumption that GameManager would be managing the list of gameInfo objects. Therefore when it loads a file it puts them into that Games property. When it saves it saves from the Games property.

    You aren't using the class this way. You're using GameManager as nothing more than a load/save class so it isn't managing anything. Since that appears to be the way you want to go you'll need to modify GameManager to not store the data anymore. At this point I'd question the logic of having this type so let's assume that you don't want a central type to manage all the gameInfo objects. Let's just move this logic directly into gameInfo instead.

    public class gameInfo
    {
       public string currPathString { get; set; }
       public string gamename { get; set; }
       …
    
       public void Save ( string filename )
       {
          var jsonData = JsonConvert.SerializeObject(this);
          File.WriteAllText(filename, jsonData);
       }
    
       public static gameInfo LoadFromFile ( string filename )  
       {
          var jsonData = File.ReadAllText(filename);
          return JsonConvert.DeserializeObject<gameInfo>(jsonData);
       }
    }

    Now each `gameInfo` instance is responsible for saving/loading its own data. The file itself only supports a single object now. This seems to line up with how you are using it. Your UI updates accordingly.

    private void btnSave_Click ( object sender, EventArgs e )
    {
       var filename = FullPathWrite…;
    
       var cfg = new gameInfo();
       cfg.currGame = txtGameShort.Text;
       ...
    
    
       cfg.Save(filename);
    }
    
    private void btnLoad_Click ( object sender, EventArgs e )
    {
       var filename = ...;
       var cfg = gameInfo.LoadFromFile(filename);
    }


    Michael Taylor http://www.michaeltaylorp3.net

    Tuesday, March 12, 2019 8:38 PM
    Moderator
  • I just copied what you gave me and setup everything and It saves it. Just nothing for the gameInfo.

    here's my code again. I think what I need to do is maybe find some video tutorials on c# classes and get some extra help. I don't know enough of classes to work with them.

     public class gameInfo
        {
    
            public string currPathstring { get; set; } //Path to mission editor samples
            public string gamename { get; set; } // Full name of game
            public string currGame { get; set; } // more then likely won't need this
            public string gamesave { get; set; } // path for saving zipped up mission
            public string category { get; set; } // Category of game 10 chars long
            public string missionext { get; set; } // Mission editors ext name used for missions
    
            //public string 
    
            public List<gameInfo> Games { get; private set; } = new List<gameInfo>();
    
            public void Save(string filename)
            {
                var jsonData = JsonConvert.SerializeObject(Games);
                File.WriteAllText(filename, jsonData);
            }
    
            public static gameInfo LoadFromFile(string filename)
            {
                var jsonData = File.ReadAllText(filename);
                return JsonConvert.DeserializeObject<gameInfo>(jsonData);
            }
    
            
    
        }
    private void btnSave_Click(object sender, EventArgs e)
            {
                try
                {
                    string filename = FullPathWrite + @"games\" + txtGameShort.Text + ".json";
    
                    //gameInfo cfg = new gameInfo();
                    var cfg = new gameInfo();
    
                    cfg.currGame = txtGameShort.Text;
                    MLHSetting.Default.currGame = cfg.currGame; //Gameinfo that loads on start up
                    MLHSetting.Default.Save();
    
                    cfg.currPathstring = txtGamepath.Text;
                    cfg.gamename = txtGamename.Text;
                    cfg.missionext = txtEditor.Text;
                    cfg.category = txtCategory.Text;
                    
                    cfg.Save(filename);                                
                    
                    
                }
                catch (Exception w)
                {
                    MessageBox.Show(w.Message, Application.ProductName);
                }
                finally
                {
                    DBMaps db = new DBMaps();
                    db.Show();
                    this.Close();
                }
            }


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Tuesday, March 12, 2019 9:15 PM
  • Right, use the code I posted last. You weren't "using" the GameManager class to manage the set of game configurations like I originally envisioned so I modified the code you posted to do away with it altogether. It was just adding to your confusion. The new code simply has you calling Load or Save on your gameInfo objects directly. GameManager is no longer needed. That should line up with your usage scenario.

    Michael Taylor http://www.michaeltaylorp3.net

    Tuesday, March 12, 2019 9:20 PM
    Moderator
  • well what I'm trying to get is the save too work. ? any idea would be great.

    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Tuesday, March 12, 2019 9:46 PM
  • I see what you did, you tried to add the GameManager logic to your gameInfo class and you're still using the Games property. Go back to using this version of `gameInfo`.

    public class gameInfo
    {
       public string currPathString { get; set; }
       public string gamename { get; set; }
       public string currGame { get; set; }
       public string gamesave { get; set; }
       public string category { get; set; }
       public string missionnext { get; set; }
    
       public void Save ( string filename )
       {
          var jsonData = JsonConvert.SerializeObject(this);
          File.WriteAllText(filename, jsonData);
       }
    
       public static gameInfo LoadFromFile ( string filename )  
       {
          var jsonData = File.ReadAllText(filename);
          return JsonConvert.DeserializeObject<gameInfo>(jsonData);
       }
    }
    Drop this implementation directly over the top of your existing `gameInfo` type and try again. This version doesn't have the `Games property at all. Consequently the call to Save doesn't use the `Game` property, it uses `this`. 


    Michael Taylor http://www.michaeltaylorp3.net

    Tuesday, March 12, 2019 9:53 PM
    Moderator
  • Mike, Thanks. I don't know why. It worked. Thank you. That's 2 that I owe you. any way I use a settings.settings file for app settings and so on. One of the settings is for the default game. This game gets loaded in when the user opens the database area form.

    MLHSetting.Default.currGame = cfg.currGame; //Gameinfo that loads on start up
    MLHSetting.Default.Save();
    

    Here, Look at this code. This code load in after the database and tables are created. If this doesn't exists. It let's the user add in a new game.

    private void loadDefaults()
            {
    
                if (MLHSetting.Default.currGame == null)
                {
                    var _games = new gameInfo();
    
                    var GameData = FullPathWrite + @"games\" + MLHSetting.Default.currGame + ".json";
    
                    if (File.Exists(GameData))
                    {                     
                        var cfg = gameInfo.LoadFromFile(GameData);
    
                    }
    
    
                    //inf.gamesave = FullPathWrite + @"save\" + MLHSetting.Default.currGame + @"\"; //where to save mission files
    
                }
                else
                {
                    //add a new game
                    gameDefaultForm def = new gameDefaultForm();
                    def.Show();
                    this.Close();
                }
    
                //loadBases();
            }
    

    For some unknown reason. I can't get the settings to save it. I don't under stand. It's all ways worked before. Could something to do with the class be stopping it from saving. I'm not getting any errors.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Tuesday, March 12, 2019 11:12 PM
  • That logic isn't going to be correct. Since you're new(er) to this let's walk through this code. Set a breakpoint on your if statement inside the loadDefaults method. Then run your code and step through line by line (F10). When the if statement executes it checks to see if you have  default game. If `currGame` is null then you don't and you enter the if statement. Next you create a new gameInfo object (BTW we only use _ for field names).  Not really sure why you're doing this but it is harmless. Now you are going to build a file path and store it into `GameData` (BTW we use camel casing for local variables). To get this path built you take `FullPathWrite` and then add to it `games` followed by `currGame`. But remember to get into this statement `currGame` was null so you're concatenating a null to the path and then appending a `.json`. This is most certainly not what you wanted to do.

    After you've created this path (which would be a .json file in the games folder) then you check to see if the file exists. If it does then you load it into a temporary variable `cfg`. Since the variable is scoped to the if statement it goes away when the if statement completes so that entire if statement doesn't really do anything.

    I don't fully understand your settings logic but I'm going to assume that `currGame` is the name of the last game you had loaded (as a string). So you should be checking for a non-empty string, not a null string. To check for a non-empty string (of which we generally include null in this) then use String.IsNullOrEmpty. Assuming that returns false then you have a `currGame` and your if statement should execute. 

    //Assuming currGame is the name of a game previously that you want to reload
    if (!String.IsNullOrEmpty(MLHSetting.Default.currGame))
    {
       //Use Path.Combine to build paths
       var path = Path.Combine(FullPathWrite, Games, MLHSetting.Default.currGame + ".json");
    
       if (File.Exists(path))
       {
          //TODO: Load this into some variable that represents your current game to the app
          gameInfo.LoadFromFile(path);
       } else ...
    };

    Notice the TODO comment. If you call LoadFromFile it returns the "current game". Somewhere in your app you have already implemented the logic to load a game such that the rest of your app has access to it. Where that TODO comment is you need to assign the results of the call to LoadFromFile to that variable so you can then have access to it in the rest of your UI. Based upon some of your earlier posts it looks like you aren't actually keeping track of the game variable directly but rather just using it to update the controls on your form. That is fine but that "load" logic that you're using to populate your UI needs to be replicated here. Personally I would recommend that you define a gameInfo field in your main form's class to hold the "current" game. Your load method should set that field while your save method should save the UI changes to that field. That field is what you'd be loading/saving each time rather than creating a new temporary variable (e.g. cfg) each time.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 2:16 AM
    Moderator
  • Sorry Mike your way off the issue. Look at this code.

    private void btnSave_Click(object sender, EventArgs e)
            {
                try
                {
                    string filename = FullPathWrite + @"games\" + txtGameShort.Text + ".json";
    
                    //gameInfo cfg = new gameInfo();
                    var cfg = new gameInfo();
    
                    cfg.currGame = txtGameShort.Text;                
                    cfg.currPathString = txtGamepath.Text;
                    cfg.gamename = txtGamename.Text;
                    cfg.missionnext = txtEditor.Text;
                    cfg.category = txtCategory.Text;
                    
                    cfg.Save(filename);
    
                    MLHSetting.Default.currGame = cfg.currGame; //Gameinfo that loads on start up
                    MLHSetting.Default.Save();
    
    
                }
                catch (Exception w)
                {
                    MessageBox.Show(w.Message, Application.ProductName);
                }
                finally
                {
                    DBMaps db = new DBMaps();
                    db.Show();
                    this.Close();
                }
            }

    You see the extra 2 lines that I added where it says it at.

                    MLHSetting.Default.currGame = cfg.currGame; //Gameinfo that loads on start up
                    MLHSetting.Default.Save();

    This is where the issue is. OK. I thought maybe that the class was keeping it from saving. I haven't been able to figure out why this doesn't save to the settings file.

    Let me try and explain what this does. when some one exits the program and restart it later. and starts the database that keeps track of all on his missions. The app will not know what to load in.The user would have to pick one from a list every time. So look at the screen shot below. Your looking at the 2nd option from the top. currgame. it will load this one game in so that the user doesn't have to do any thing.

    The 2nd option gets changed every time a new game is added or the user selects another game. So I hope this helps. Thanks again.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 13, 2019 2:34 AM
  • I see so you're using the Settings file that is autogenerated when you use the Settings designer in the project. In that case then it should save just fine. The Save method simply writes to the user.config file and therefore should just work. Having said that though it does save the user's configuration file to their documents directory under a folder structure as defined here. Take a look in your file system for the users.config file to ensure it is being saved. Note that the file is tied to the product version of the app. Newer versions of your app won't automatically read older versions (but there is a method to upgrade). Normally the product version won't change between builds but it depends upon what you have your AssemblyInfo.cs file set to.

    If this still doesn't work then do you happen to have this code in GitHub or something so we can see it ourselves?


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 2:52 AM
    Moderator
  • Also, put a breakpoint on the call to Save. Verify it is being hit and that the value you're setting that setting to is correct.

    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 2:55 AM
    Moderator
  • I tried that all so and it's is calling it right. and it's does see it. It's just not saving for some unknown reason. it's something I'm going to have to try and figure out. anyway no I don't have github. It you have Skype and TeamViewer. We could do it that way. By the way. what version of VS are you using by chance. I have VSC 2017. I just update it to 15.9.9. all so I didn't see an option to register on your web site.


    • Edited by Joesoft11a Wednesday, March 13, 2019 3:52 AM
    Wednesday, March 13, 2019 3:48 AM
  • Save will throw an exception if it fails. Are you getting an exception or silently handling it? If so then trap the exception with a try-catch to see why the save would fail.

    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 1:40 PM
    Moderator
  • This is what I did and what I got back on adding in a break. I don't see an issue. It doesn't save it.

    private void btnSave_Click(object sender, EventArgs e)
            {
    
                try
                {
                    //The code below fails to save
                    MLHSetting.Default.currGame = txtGameShort.Text; //Gameinfo that loads on start up
                    MLHSetting.Default.Save();
                }
                catch(Exception err)
                {
                    MessageBox.Show(err.Message,Application.ProductName);
                    return;
                }
    
    
                try
                {
                    string filename = FullPathWrite + @"games\" + txtGameShort.Text + ".json";
    
                    //this code works. 
                    var cfg = new gameInfo();
    
                    cfg.currGame = txtGameShort.Text;                
                    cfg.currPathString = txtGamepath.Text;
                    cfg.gamename = txtGamename.Text;
                    cfg.missionnext = txtEditor.Text;
                    cfg.category = txtCategory.Text;
                    
                    cfg.Save(filename);                
    
                }
                catch (Exception w)
                {
                    MessageBox.Show(w.Message, Application.ProductName);
                }
                finally
                {
                    DBMaps db = new DBMaps();
                    db.Show();
                    this.Close();
                }
            }

    look at the screen shot when I added a break in where the Save() is.

    It seems to be passing all the info it should be passing. So I'm confused. This has all ways worked before and never had an issue with it.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 13, 2019 3:28 PM
  • Go to the .config file where it is saving the changes. What value is there for 'currgame'? Go to the code where you're reading the settings for the first time. What value does 'currgame` have when the app loads it the very first time?

    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 4:07 PM
    Moderator
  • The config file will have an empty value. This is because no games have been added to the app. When a users creates a new game and adds it. It creates a short game name that the users picks and saves it to the settings file. Let me show you the settings file

    Its the 2nd one from the top. The top one settings will be removed like a lot of settings will be removed. They are not needed anymore. The users can update and add to this settings file. sense most of it is game related. It will get smaller then what it is now.

    when a user exists the app. This loads in again when the user tried and access the databases and tables. That way the user doesn't have to add a new game or pick from a list to manage his missions.


    • Edited by Joesoft11a Wednesday, March 13, 2019 4:32 PM
    Wednesday, March 13, 2019 4:30 PM
  • Right, that is the default settings if the app doesn't find the user.config already. You mentioned this was working before which means you already had a user.config file that it would automatically load. Since it found the config it would have loaded the value from there. So on subsequent runs it wouldn't be empty anymore. What I'd like to know is if that file is actually there. If it was working before then the file should still be there. When you load the app it should read the values. If the file doesn't exist then either you deleted that file or it never created it to begin with which leads us back to an issue with Save. However if the file is there and has a non-default value but when you load it doesn't get read then the issue is with the loading of the file.

    You can determine the path of the file being loaded/saved by looking at the settings provider in the debugger IIRC. This will give you the path to the file (generally under Documents, your app, your version).


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 4:36 PM
    Moderator
  • You really have me all confused. I don't have a user.config file. I don't know what that is for. How ever I do have a settings.settings file that I use for app settings. where users and the app can change them or update them. Some of these settings will all so get removed.

    As you cam see this file keeps track of settings for the app and for the user as well. I see I have used the Save() option before in other parts of my app and it's all ways worked before. So I'm still confused at why it doesn't want to save it.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 13, 2019 4:49 PM
  • I think I understand what is going on but just to confirm MLHSetting is a class in your code and it derives from ApplicationSettingsBase right?


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 4:58 PM
    Moderator
  • Right. I have a settings.settings file called MLHelper.settings. This is what I use to handle database and so on. I all so fount that user.config file. It doesn't seem to relate to my app (program) or what ever you want to call it. It's a XML file.

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <userSettings>
            <MLHelper.MLHSetting>
                <setting name="currGame" serializeAs="String">
                    <value>jointops</value>
                </setting>
            </MLHelper.MLHSetting>
            <MLHelper.Properties.Settings>
                <setting name="SoftwareKey" serializeAs="String">
                    <value>6b24e830-8f04-4518-a23d-2aef314642ff</value>
                </setting>
            </MLHelper.Properties.Settings>
        </userSettings>
    </configuration>


    It looks like it does have that one setting called currGame with a value. So I don't know what that is for.

    I haven't gone threw all my code yet. So I don't think I'm using XML files in my app.


    All so if you use discord. You can join my server. It would be better then posting messages back and forth. https://discord.gg/a72Qc5X ping, me iceman11a


    • Edited by Joesoft11a Wednesday, March 13, 2019 5:13 PM updated it
    Wednesday, March 13, 2019 5:11 PM
  • Good. Now I think we can make progress. I think you misunderstand how settings work. Here's the documentation for it but we'll walk through this step by step so you can more easily understand it hopefully.

    Open the `.settings` file in Visual Studio. This is the designer file that backs all the runtime settings. It is only used by the designer. Each setting you add here exposes a runtime setting that your app can use. In the designer you specify the setting name, type, whether it is scoped to the application or local to the user and its default value.

    Now go to Solution Explorer and expand the `.settings` file under Properties in your project. Then open the `.settings.designer.cs` file. This file is auto-generated as you make changes in the designer. Each setting you define in the designer gets a corresponding property declared here. It is this type that you're using in your code. Notice the attributes applied to the property. One of them is the default value that you put in the designer. This is baked into the source code at compilation time.

    Now go open the `app.config` file. The designer also added a section to your config with entries for each setting. You'll notice that each setting has a value that corresponds to the default value you put in the designer.

    Now we get to the runtime side of the fence. When you request a setting at runtime the framework uses a SettingsProvider type (the default being LocalFileSettingsProvider) to load the settings. If the provider does not find a value then it uses the attribute applied to the property instead. If the provider returns a value then it uses that instead.

    LocalFileSettingsProvider works with 2 different files - app.config and user.config. Application scoped settings are retrieved from `app.config`. Application scoped settings are considered to be read only because most apps do not have write access to the directory. These types of settings are best used for things that all users share although most people just use AppSettings or an equivalent instead.

    For user scoped settings each user gets their own stuff so the provider has to access a file that a user can read/write. This is where the `user.config` file comes in. By default the provider will store it under Documents, your app, your version. If your app ever sets a value to a user-scoped setting via the Settings class and then calls Save it goes into this file. This is what I've been referencing all along. It is here that your saved user settings are going. Yes this is an XML file but you don't ever need to muck with it directly. You should leave it alone and only use the Settings class to interact with it. Now, when the provider is requested to read a user-scoped variable it first tries to get it from the `user.config` file. If it doesn't exist then it falls back to looking in the `app.config`. As you saw earlier this is where the designer put the default value. So on the very first run of your version of the app it gets retrieved from `app.config` using the default value that was set when the designer file was saved last. However at any point after that if you call Save then all the user settings will be stored in `user.config` and future calls (for the same app/version) will use those values instead.

    Having covered all that, changing a setting's value in the designer only changes the default value that is stored in the code and config file. And even then you have to recompile your code for it to take effect. The default value will only be used if the `user.config` doesn't have it. If the `user.config` file has a value then that will always override the default. To reset it you'd need to either delete the file or, if programmatically, call Reset. Then the settings would revert to their default values (as defined in the config file).

    To change a user-scoped setting at runtime you'd set the value via the property and then call Save. This generates/updates the `user.config` file. The next time your app tries to read the value it'll comes from here. If you believe the file to be corrupt then delete it and run your app again to reset. 

    Going back to your original problem, if the `user.config` file does not exist then you should see `currgame` have whatever value you have currently defined in the settings designer (after recompilation). Once your code sets the value and calls Save then that will be the value you get the next time until you change and save again later.


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by Joesoft11a Wednesday, March 13, 2019 5:57 PM
    Wednesday, March 13, 2019 5:46 PM
    Moderator
  • Sorry I should have tried to post sooner. I fount the issue.  it's the if statement.

     if (MLHSetting.Default.currGame != "")

    I had to change this. What I did was is I added a label to the form for adding a new game. and it does see it and it does get saved. For some unknown reason  if (MLHSetting.Default.currGame == null) doesn't work. That's why I had to change it. How it all works. I'm sorry you took all that time out to help and you been create. I don't know what I would have done with out you.

    it is working. Thank you very much.

    Just one small question I hope. When I load in the settings for the game. How would I call them. Or use them. That's what I just started working on when I got your post.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software


    • Edited by Joesoft11a Wednesday, March 13, 2019 5:56 PM
    Wednesday, March 13, 2019 5:55 PM
  • To check for an empty string you should prefer String.IsNullOrEmpty(). It handles both null and empty string which we generally consider to be the same.

    " How would I call them. Or use them."

    Settings as in MLHSettings or settings as in your gameInfo? In the case of the former you just reference the MLHSettings properties as you need them. The provider will load them as needed. For your `gameinfo` after you call the Load method you'll need to basically do the inverse of your save logic.

    var cfg = gameInfo.LoadFromFile(…);
    
    txtGameShort.Text = cfg.currGame;
    txtGamepath.Text = cfg.currPathString;
    txtGamename.Text = cfg.gamename;
    txtEditor.Text = cfg.missionnext;
    txtCategory.Text = cfg.category;


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 6:09 PM
    Moderator
  • So what your saying is that I would have to load in the settings for every game when I need to use them.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 13, 2019 6:13 PM
  • I don't under stand why I have these dump issues. This works in other methods. why doesn't it work in this method.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 13, 2019 6:41 PM
  • LoadFromFile is a static method. To call a static method in C# you specify the type name followed by the static member.

    String.IsNullOrEmpty("Bob")
    GameInfo.LoadFromFile("file.json")

    `new` is used to create an instance of a type. It require a type name and then creates an instance of it.

    new Form()
    new gameInfo()
    In your case you're calling `new` followed by a static method call which isn't allowed. Get rid of the `new` because you're not creating a new instance, you're calling a method that will return the instance you want to use.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, March 13, 2019 6:53 PM
    Moderator
  • Mike, Thank you very much. I guess that's it until I start having more issues when crud. we'll see.

    That's 3 I owe you. need help with any thing let me know.


    http://www.df-barracks.com Delta Force Barracks
    http://www.starfiresoft.com Starfire Software

    Wednesday, March 13, 2019 8:44 PM