Введение


В интернете приведено очень много способов хранения настроек программы, но все они как-то разбросаны, поэтому я решил их собрать вместе и расписать, как этим пользоваться.

C# и app.config


На хабре уже была посвящена этому тема, поэтому… перейти

C# и Properties.Settings


Информация о Properties.Settings

Организация Properties.Settings — это обычный xml файл, который можно найти в папке пользователя:

С:\ Users \ [user name] \ AppData \ Local \ [ (Project Name) or (AssemblyCompany) ] \ [name project_cashBuild] \ [AssemblyVersion] \ user.config

Для начала нам нужно создать такие переменные для Properties.Settings. Перейдем в Properties -> Settings.settings:

Properties -> Settings.settings in VS 2013


Я создал 3-и переменные и выбрал область их использования: 2- область пользователь и 1- приложение.

Различие между областями просты. Область приложения можно только читать, а пользователь — изменять и читать.

Вернемся к переменным:

  • Version — версия нашей программы. Определил ее строкой и областью приложение. Т.к. версия может содержать буквы (например, b — от beta). А область выбрал, чтоб не менялась наша версия приложения (т.к. AssemblyVersion редко кто использует).
  • Save_text — это переменная, куда мы будем сохранять наш текст.
  • open_sum — сколько раз мы открыли программу.

Теперь перейдем к коду

Код Form1.cs
namespace Habrahabr
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.Text += " " + Properties.Settings.Default.Version; //Добавляем в название программы, версию.
            Properties.Settings.Default.open_sum++; //Добавляем +1 к кол-ву запусков программы.
            label2.Text = Properties.Settings.Default.open_sum.ToString(); //выводим в Label2 кол-во запусков программы.
            richTextBox1.Text = Properties.Settings.Default.Save_text; // Загружаем ранее сохраненный текст
            Properties.Settings.Default.Save();  // Сохраняем переменные.
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Properties.Settings.Default.Save_text = richTextBox1.Text; // Записываем содержимое richTextBox1 в Save_text
            Properties.Settings.Default.Save(); // Сохраняем переменные.
            MessageBox.Show("Текст сохранен", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); // Говорим пользователю, что сохранили текст.
        }
    }
}


Результаты работы программы

Первый запуск, мы видим, что кол-во запусков равно 1. И теста в richTextBox1 нет.



Теперь напишем и сохраним текст.



При втором запуске мы видим, что текст сохранен, и кол-во запусков уже 2-ва.



Вывод

Очень удобно использовать этот объект, если надо работать в разных областях видимости в одном проекте. Метод хорош, когда вам не надо, чтоб рядовой пользователь рылся в файлах настройки программы.

C# и ini-файлы


С ini-файлами все на оборот, они лежат в папке рядом с программой, что позволяет пользователю изменить настройки вне-программы. Данный способ хорош, если настройки программы заносятся вручную. Например, эмулятор для запуска игры без лицензии (тотже revLoader).

Теперь перейдем к нашей теме. Для работы с таким типом файлов, нам нужно создать класс по работе с ним. Создаем класс, например «IniFile», подключаем пространство имен, которых нет:

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

А теперь разбираем по-порядку:

Код IniFiles.cs
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

namespace IniFiles
{
    class IniFile
    {
        string Path; //Имя файла.

        [DllImport("kernel32")] // Подключаем kernel32.dll и описываем его функцию WritePrivateProfilesString
        static extern long WritePrivateProfileString(string Section, string Key, string Value, string FilePath);

        [DllImport("kernel32")] // Еще раз подключаем kernel32.dll, а теперь описываем функцию GetPrivateProfileString
        static extern int GetPrivateProfileString(string Section, string Key, string Default, StringBuilder RetVal, int Size, string FilePath);

        // С помощью конструктора записываем пусть до файла и его имя.
        public IniFile(string IniPath)
        {
            Path = new FileInfo(IniPath).FullName.ToString();
        }

        //Читаем ini-файл и возвращаем значение указного ключа из заданной секции.
        public string ReadINI(string Section, string Key)
        {
            var RetVal = new StringBuilder(255);
            GetPrivateProfileString(Section, Key, "", RetVal, 255, Path);
            return RetVal.ToString();
        }
        //Записываем в ini-файл. Запись происходит в выбранную секцию в выбранный ключ.
        public void Write(string Section, string Key, string Value)
        {
            WritePrivateProfileString(Section, Key, Value, Path);
        }

        //Удаляем ключ из выбранной секции.
        public void DeleteKey(string Key, string Section = null)
        {
            Write(Section, Key, null);
        }
        //Удаляем выбранную секцию
        public void DeleteSection(string Section = null)
        {
            Write(Section, null, null);
        }
        //Проверяем, есть ли такой ключ, в этой секции
        public bool KeyExists(string Key, string Section = null)
        {
            return ReadINI(Section, Key).Length > 0;
        }
    }
}


Теперь переходим в основную программу.

Код Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace IniFiles
{
    public partial class Form1 : Form
    {
        IniFile INI = new IniFile("config.ini");
        public Form1()
        {
            InitializeComponent();
            auto_read();
               
        }

        private void auto_read()
        {
            if (INI.KeyExistsINI("SettingForm1", "Width"))
                numericUpDown2.Value = int.Parse(INI.ReadINI("SettingForm1", "Height"));
            else
                numericUpDown1.Value = this.MinimumSize.Height;

            if (INI.KeyExistsINI("SettingForm1", "Height"))
                numericUpDown1.Value = int.Parse(INI.ReadINI("SettingForm1", "Width"));
            else
                numericUpDown2.Value = this.MinimumSize.Width;

            if (INI.KeyExistsINI("SettingForm1", "Width"))
                textBox1.Text = INI.ReadINI("Other", "Text");

            this.Height = int.Parse(numericUpDown1.Value.ToString());
            this.Width = int.Parse(numericUpDown2.Value.ToString());
            
        }
        
        private void button1_Click(object sender, EventArgs e)
        {
            INI.WriteINI("SettingForm1", "Height", numericUpDown2.Value.ToString());
            INI.WriteINI("SettingForm1", "Width", numericUpDown1.Value.ToString());
            INI.WriteINI("Other", "Text", textBox1.Text);
            MessageBox.Show("Настройки SettingForm1 и Other сохранены", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); // Говорим пользователю, что сохранили текст.
         
        }

        private void button2_Click(object sender, EventArgs e)
        {
            auto_read(); // Чтоб не повторяться.
        }

        private void button3_Click(object sender, EventArgs e)
        {
            INI.WriteINI("SettingForm1", "Height", numericUpDown2.Value.ToString());
            INI.WriteINI("SettingForm1", "Width", numericUpDown1.Value.ToString());
            this.Height = int.Parse(numericUpDown1.Value.ToString());
            this.Width = int.Parse(numericUpDown2.Value.ToString());
            MessageBox.Show("Настройки SettingForm1 сохранены и применены", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); // Говорим пользователю, что сохранили текст.
         
           }


        
    }
}


Результаты работы программы

При первом запуска, у нас нет файла config.ini. Поэтому при проверке возвращаются fasle и мы приравниваем окно к минимальным параметрам.



Меняем параметры окна и жмем «Применить»



Редактируем файл config.ini руками и жмем загрузить.



На этом все, в следующий раз опишу работу с xml файлами и с бинарными файлами.

Комментарии (21)


  1. lair
    23.11.2015 14:41
    +2

    Организация объекта Properties.Settings — это обычный xml файл, который можно найти в папке пользователя:

    С:\ Users \ [user name] \ AppData \ Local \ [ (Project Name) or (AssemblyCompany) ] \ [name project_cashBuild] \ [AssemblyVersion] \ user.config

    Вы про app.config/web.config ничего не слышали?

    С ini-файлами все на оборот, они лежат в папке рядом с программой, что позволяет пользователю изменить настройки вне-программы. Данный способ хорош, если настройки программы заносятся вручную. Например, эмулятор для запуска игры без лицензии (тотже revLoader).

    А зачем это надо, когда есть app.config?

    Теперь перейдем к нашей теме. Для работы с таким типом файлов, нам нужно создать класс по работе с ним. Создаем класс, например «IniFile», подключаем пространство имен (библиотеки), которых нет:

    Пространства имен — это не библиотеки.


    1. HatsuneAkeno
      23.11.2015 18:23
      -4

      Вы про app.config/web.config ничего не слышали?

      Нет, не слышал.
      А зачем это надо, когда есть app.config?

      Хм… Интересно, упустил его. Спасибо за информацию.
      Пространства имен — это не библиотеки.

      Косяк, согласен. Исправил.


      1. lair
        23.11.2015 18:27
        +3

        Нет, не слышал.

        Ну так может стоит сначала разобраться в теме, о которой вы пришли писать статью на хабр? app/web.config — это первичное место для настроек программы на протяжении нескольких версий .net, а вы его «упустили».


        1. HatsuneAkeno
          23.11.2015 18:47
          -3

          Ну, начнем с того, что об этому уже писали на хабре, именно об app.config (статья). Так же я тут не затронул xml, в котором, тоже многие хранят настройки и не только.
          В ini-файле удобней описывать конфигурацию, чем в xml. Особенно для программ, которые не имеют пользовательского интерфейса.
          Так же можно разбить настройки в две категории:

          1. Программные — задаются разработчиком и находятся в app/web.config
          2. Пользовательские — задаются пользователем и живут в ini


          А я, тем временем, буду продолжать учиться и набивать шишки на своих ошибках.


          1. lair
            23.11.2015 18:51
            +1

            Ну, начнем с того, что об этому уже писали на хабре, именно об app.config

            И что? Если посыл вашей статьи в том, чтобы собрать вместе все способы хранения настроек, то пропускать app.config нельзя. Особенно когда вы первым пунктом пишете про .Settings-файл, настройки из которого тоже хранятся там же.

            В ini-файле удобней описывать конфигурацию, чем в xml.

            Кому удобнее?

            Так же можно разбить настройки в две категории:
            Программные — задаются разработчиком и находятся в app/web.config
            Пользовательские — задаются пользователем и живут в ini

            Если речь о десктопе, то зачем делать два механизма хранения настроек, когда есть один? Если речь о вебе, то как вы себе представляете хранение настроек в ini?


            1. HatsuneAkeno
              23.11.2015 19:11

              собрать вместе все способы хранения настроек

              Да это есть цель. Буду пополнять, постепенно. Или же вторую сделаю. Посмотрю еще, как лучше.

              Кому удобнее?

              Пользователю. Юзверю проще разобраться с секцией и ключем, чем в xml файле.
              (не знаю, как у других, но до изучения xml, было проще работаться с ini или похожими настройками)

              Если речь о вебе, то как вы себе представляете хранение настроек в ini?
              Но! Я не вел речи о вебе.


              1. lair
                23.11.2015 19:13

                Пользователю. Юзверю проще разобраться с секцией и ключем, чем в xml файле.

                А зачем неквалифицированному пользователю лезть в конфигурацию? А для квалифицированного xml не представляет проблем (особенно учитывая его поддержку современными редакторами).

                Но! Я не вел речи о вебе.

                В вебе настройки хранить не надо? Или для веба на C# не пишут?


                1. HatsuneAkeno
                  23.11.2015 19:30

                  В вебе настройки хранить не надо? Или для веба на C# не пишут?

                  Я разве сказал, что ему не нужна конфигурация? Я разве сказал, что не пишут? Что-то не припомню.
                  Веб тоже в них нуждается, но я не вел о нем речь. Затронута была тема десктопных приложений.

                  Да косяков много, но я буду их исправлять и набираться опыта в написании статей. Благодарю за критику.


                  1. lair
                    23.11.2015 20:53

                    Затронута была тема десктопных приложений.

                    Где это в статье написано?

                    C# и app.config [...] C# и объект Properties.Settings

                    А ничего, что второе реализовано через первое?

                    (впрочем, «объекта» Properties.Settings вообще не существует, чего уж)


  1. Reeze
    23.11.2015 14:59
    +1

    В новых UWP (статья про C#):

    msdn.microsoft.com/en-US/library/windows/apps/windows.storage.applicationdata.localsettings

    Данный способ очень прост и похож на localStorage в HTML5.

    Еще есть вариант хранить все в json: www.nuget.org/packages/newtonsoft.json


  1. kekekeks
    23.11.2015 15:02
    +5

    Эцсамое. Берём JSON.NET, делаем иерархию классов настроек любой вложенности. Из сохранение сводится к

    File.WriteAllText("settings.json", JObject.FromObject(settings).ToString())
    
    , а загрузка к J
    Object.Parse(File.ReadAllText("settings.json")).ToObject<Settings>()
    

    Получаем строго типизированный конфиг с производной древовидной структурой и простотой задания умолчаний (они задаются прямо в коде как дефолтные значения свойств) в две строчки кода. Совместимо с coreclr/corefx, мобилками, да вообще со всем кроме разве что .NET Micro. Не вижу смысла пользоваться чем-то ещё.


    1. Dima_Sharihin
      23.11.2015 16:18

      Чем JObject.Parse лучше, чем JsonConvert.DeserializeObject<T>?
      Или это два варианта одной операции?


      1. kekekeks
        23.11.2015 16:35

        Это то же самое, да.


    1. KvanTTT
      23.11.2015 20:49

      Если настроек очень много, то можно еще использовать и Automapper.


  1. vlivyur
    23.11.2015 17:28

    но все они как-то разбросаны, поэтому я решил их собрать вместе и расписать
    збросать теперь по хабру.


  1. dordzhiev
    23.11.2015 18:53
    +1

    Как-будто в 2010 вернулся


  1. leremin
    23.11.2015 20:27

    В реальных проектах кто-то app.config использует? Лично я в своих проектах использую синглтон в виде Dictionary (на самом деле там переписанный на C# xml-движок для QSettings из Qt).


    1. HatsuneAkeno
      23.11.2015 20:49

      Спасибо за информацию, прочитаю, добавлю в статью.


    1. lair
      23.11.2015 20:54
      +2

      Я использую (сильно больше одного проекта в разных компаниях).


    1. Razaz
      24.11.2015 12:59

      Вообще много где и очень активно. И web.config то же. Особенно кастомные секции, внешние секции. QSettings там рядом не стоял.


  1. dymanoid
    23.11.2015 22:15
    +5

    Properties.Settings можно лихо переделать под себя, написав свой SettingsProvider с блэкджеком и ш... шахматами и поэтессами. Если интересно, могу как-нибудь статью сбацать.