Как сохранить настройки приложения в приложении Windows Forms?

583

То, чего я хочу достичь, очень просто: у меня есть приложение Windows Forms (.NET 3.5), которое использует путь для чтения информации. Этот путь может быть изменен пользователем с помощью формы параметров, которую я предоставляю.

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

Я понимаю, что доступны три варианта:

  • Файл настроек конфигурации (appname.exe.config)
  • реестр
  • Пользовательский файл XML

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

Означает ли это, что я должен использовать пользовательский файл XML для сохранения настроек конфигурации?

Если это так, я хотел бы увидеть пример кода (C #).

Я видел другие дискуссии на эту тему, но мне все еще не ясно.

Fueled
источник
Это приложение .NET WinForms? Если да, то на какой версии .NET вы разрабатываете?
Портман
1
Да, это приложение .NET Framework версии 3.5 WinForms.
заправлено
1
вам нужно сохранить пароли или секретные значения ? Может быть, требуется любое шифрование
Kiquenet

Ответы:

594

Если вы работаете с Visual Studio, то получить постоянные настройки довольно просто. Щелкните правой кнопкой мыши проект в обозревателе решений и выберите «Свойства». Выберите вкладку «Настройки» и нажмите на гиперссылку, если настройки не существуют.

Используйте вкладку «Настройки» для создания настроек приложения. Visual Studio создает файлы Settings.settingsи Settings.Designer.settingsкоторые содержат класс одноплодной , Settingsунаследованный от ApplicationSettingsBase . Вы можете получить доступ к этому классу из своего кода для чтения / записи настроек приложения:

Properties.Settings.Default["SomeProperty"] = "Some Value";
Properties.Settings.Default.Save(); // Saves settings in application configuration file

Этот метод применим как для консоли, Windows Forms, так и для других типов проектов.

Обратите внимание, что вам нужно установить область свойство ваших настроек. Если вы выберите Область приложения, тогда Settings.Default. <Ваше свойство> будет доступно только для чтения.

Ссылка: Как: записать пользовательские настройки во время выполнения с C # - Microsoft Docs

Ака
источник
2
если у меня есть решение, будет ли это применяться ко всему решению или к каждому проекту?
franko_camron
8
@Four: У меня есть проект WinNET для .NET 4.0, и мой SomeProperty не только для чтения. Settings.Default.SomeProperty = 'value'; Settings.Default.Save();работает как шарм. Или это потому, что у меня есть пользовательские настройки?
doekman
4
@Four: Когда я изменил настройку с User на Application-scope и сохранил файл, я увидел, что в сгенерированном коде установщик исчез. Это также происходит с профилем клиента 4.0 ...
doekman
3
@Four: отличная ссылка, хотя ваше утверждение, что Settings.Default.Save()ничего не делает, неверно. Как пишет @aku в ответе, настройки области приложения доступны только для чтения: сохранение для них неэффективно. Используйте этот пользовательский PortableSettingsProvider для сохранения настроек области пользователя в файле app.config, расположенном там, где находится exe, а не в папке пользователя AppData. Нет, не очень хорошо, но я использую его во время разработки, чтобы использовать одни и те же настройки от компиляции до компиляции (без нее они создают новые уникальные пользовательские папки с каждой компиляцией).
Минноу
7
На данный момент в .NET 3.5, похоже, вы можете просто использовать Settings.Default.SomeProperty, чтобы присвоить значение и получить сильное приведение типов. Кроме того, чтобы сэкономить время других (на это у меня ушло время), вам нужно либо набрать Properties.Settings.Default, либо добавить с помощью YourProjectNameSpace.Settings в начало вашего файла. Только «Настройки» не определены / не найдены.
Eselk
95

Если вы планируете сохранить файл в том же каталоге, что и ваш исполняемый файл, вот хорошее решение, которое использует формат JSON :

using System;
using System.IO;
using System.Web.Script.Serialization;

namespace MiscConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            MySettings settings = MySettings.Load();
            Console.WriteLine("Current value of 'myInteger': " + settings.myInteger);
            Console.WriteLine("Incrementing 'myInteger'...");
            settings.myInteger++;
            Console.WriteLine("Saving settings...");
            settings.Save();
            Console.WriteLine("Done.");
            Console.ReadKey();
        }

        class MySettings : AppSettings<MySettings>
        {
            public string myString = "Hello World";
            public int myInteger = 1;
        }
    }

    public class AppSettings<T> where T : new()
    {
        private const string DEFAULT_FILENAME = "settings.json";

        public void Save(string fileName = DEFAULT_FILENAME)
        {
            File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(this));
        }

        public static void Save(T pSettings, string fileName = DEFAULT_FILENAME)
        {
            File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(pSettings));
        }

        public static T Load(string fileName = DEFAULT_FILENAME)
        {
            T t = new T();
            if(File.Exists(fileName))
                t = (new JavaScriptSerializer()).Deserialize<T>(File.ReadAllText(fileName));
            return t;
        }
    }
}
Тревор
источник
Да, измените DEFAULT_FILENAME на абсолютный путь, если вы хотите сохранить в другой каталог. Я думаю, что наиболее распространенным является сохранение файла в том же каталоге, что и приложение, или подкаталог, если вы не сохраняете их в реестре.
Тревор
О, возможно, лучшим вариантом было бы сохранить файл настроек в папке appdata пользователя.
Тревор
1
Не нужно менять DEFAULT_FILENAME, просто позвоните settings.Save(theFileToSaveTo); Будучи всеми заглавными буквами, DEFAULT_FILENAMEдолжен быть постоянным . Если вам нужно свойство для чтения и записи, создайте его и присвойте конструктору значение DEFAULT_FILENAME. Затем nullукажите значение аргумента по умолчанию , проверьте это и используйте свое свойство в качестве значения по умолчанию. Это немного больше печатать, но дает вам более стандартный интерфейс.
Джесси Чисхолм
10
Вам нужно будет ссылаться, System.Web.Extensions.dllесли вы еще этого не сделали.
TEK
9
Я создал целую библиотеку на основе этого ответа с множеством улучшений и сделал ее доступной в nuget: github.com/Nucs/JsonSettings
NucS
67

Реестр запрещён. Вы не уверены, имеет ли пользователь, который использует ваше приложение, достаточные права для записи в реестр.

Вы можете использовать app.configфайл для сохранения настроек уровня приложения (которые одинаковы для каждого пользователя, использующего ваше приложение).

Я бы сохранял пользовательские настройки в файле XML, который сохранялся бы в изолированном хранилище или в каталоге SpecialFolder.ApplicationData .

Кроме того, начиная с .NET 2.0, можно сохранять значения обратно в app.configфайл.

Фредерик Гейсель
источник
8
Используйте реестр, однако, если вы хотите индивидуальные настройки входа / пользователя.
thenonhacker
19
Реестр не переносимый
Кб.
10
@thenonhacker: Или используйте Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData)
Кенни Манн,
4
В реестр пользователей можно записать данные (многие программы записывают туда информацию, и права пользователей никогда не являются проблемой). Преимущество использования реестра перед использованием настроек заключается в том, что если у вас есть несколько приложений, совместно использующих одну и ту же папку (скажем, программа установки и прикладная программа), они не будут использовать одни и те же настройки.
Кести
3
Основным недостатком реестра является сложный способ экспорта / копирования настроек на другой компьютер. Но я не согласен с «Вы не уверены, имеет ли пользователь, который использует ваше приложение, достаточные права для записи в реестр» - в HKEY_CURRENT_USER у вас всегда есть права на запись. Это может быть запрещено, но файловая система также может быть недоступна для текущего пользователя (все возможные папки TEMP и т. Д.).
i486
20

ApplicationSettingsКласс не поддерживает сохранение настроек в app.config файл. Это очень много задумано; приложения, работающие с должным образом защищенной учетной записью пользователя (например, Vista UAC), не имеют доступа на запись в папку установки программы.

Вы можете бороться с системой с ConfigurationManagerклассом. Но тривиальный обходной путь заключается в том, чтобы зайти в конструктор настроек и изменить область настройки на «Пользователь». Если это вызывает затруднения (скажем, настройка важна для каждого пользователя), вы должны поместить свою опцию «Параметры» в отдельную программу, чтобы вы могли запросить запрос на повышение привилегий. Или отказаться от использования настроек.

Ганс Пассант
источник
Не могли бы вы рассказать о вашем последнем предложении? Попросите повышение прав, чтобы написать app.config или написать отдельное приложение, которое будет проходить через домашние программы всех пользователей, искать user.config и редактировать их?
CannibalSmith
2
Отдельная программа требует манифеста для запроса повышения. Google 'asinvoker требует администратора', чтобы найти правильный синтаксис. Редактирование user.config не практично и не необходимо.
Ганс Пассант
18

Аргумент registry / configurationSettings / XML по-прежнему выглядит очень активным. Я использовал их все по мере развития технологии, но мой любимый основан на системе Threed в сочетании с изолированным хранилищем .

Следующий пример позволяет хранить объекты с именами свойств в файле в изолированном хранилище. Такие как:

AppSettings.Save(myobject, "Prop1,Prop2", "myFile.jsn");

Свойства могут быть восстановлены с использованием:

AppSettings.Load(myobject, "myFile.jsn");

Это просто образец, не наводящий на мысль о передовой практике.

internal static class AppSettings
{
    internal static void Save(object src, string targ, string fileName)
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        Type type = src.GetType();

        string[] paramList = targ.Split(new char[] { ',' });
        foreach (string paramName in paramList)
            items.Add(paramName, type.GetProperty(paramName.Trim()).GetValue(src, null));

        try
        {
            // GetUserStoreForApplication doesn't work - can't identify.
            // application unless published by ClickOnce or Silverlight
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
            using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Create, storage))
            using (StreamWriter writer = new StreamWriter(stream))
            {
                writer.Write((new JavaScriptSerializer()).Serialize(items));
            }

        }
        catch (Exception) { }   // If fails - just don't use preferences
    }

    internal static void Load(object tar, string fileName)
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        Type type = tar.GetType();

        try
        {
            // GetUserStoreForApplication doesn't work - can't identify
            // application unless published by ClickOnce or Silverlight
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
            using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Open, storage))
            using (StreamReader reader = new StreamReader(stream))
            {
                items = (new JavaScriptSerializer()).Deserialize<Dictionary<string, object>>(reader.ReadToEnd());
            }
        }
        catch (Exception) { return; }   // If fails - just don't use preferences.

        foreach (KeyValuePair<string, object> obj in items)
        {
            try
            {
                tar.GetType().GetProperty(obj.Key).SetValue(tar, obj.Value, null);
            }
            catch (Exception) { }
        }
    }
}
Boczek
источник
1
Или еще лучше; использовать DataContractJsonSerializer
Boczek
16

Я хотел поделиться библиотекой, которую я построил для этого. Это крошечная библиотека, но большое улучшение (IMHO) по сравнению с файлами .settings.

Библиотека называется Jot (GitHub) . Вот старая статья The Code Project, которую я написал об этом.

Вот как вы можете использовать его для отслеживания размера и местоположения окна:

public MainWindow()
{
    InitializeComponent();

    _stateTracker.Configure(this)
        .IdentifyAs("MyMainWindow")
        .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
        .RegisterPersistTrigger(nameof(Closed))
        .Apply();
}

Преимущество по сравнению с файлами .settings: кода значительно меньше, и он намного менее подвержен ошибкам, поскольку вам нужно упомянуть каждое свойство только один раз .

В файлах настроек вам нужно упоминать каждое свойство пять раз: один раз, когда вы явно создаете свойство, и еще четыре раза в коде, который копирует значения туда и обратно.

Хранение, сериализация и т. Д. Полностью настраиваются. Когда целевые объекты создаются IoC контейнером , вы можете [подключить его] [], чтобы он автоматически применял отслеживание ко всем объектам, которые он разрешает, так что все, что вам нужно сделать, чтобы сделать свойство постоянным, - это нажать [Отслеживаемый] атрибут на это.

Он легко настраивается, и вы можете настроить: - когда данные сохраняются и применяются глобально или для каждого отслеживаемого объекта - как они сериализуются - где они хранятся (например, файл, база данных, онлайн, изолированное хранилище, реестр) - правила, которые могут отменить применение / постоянные данные для свойства

Поверьте мне, библиотека на высшем уровне!

anakic
источник
14

Простой способ - использовать объект данных конфигурации, сохранить его в виде файла XML с именем приложения в локальной папке и при запуске прочитать его обратно.

Вот пример для хранения позиции и размера формы.

Объект данных конфигурации строго типизирован и прост в использовании:

[Serializable()]
public class CConfigDO
{
    private System.Drawing.Point m_oStartPos;
    private System.Drawing.Size m_oStartSize;

    public System.Drawing.Point StartPos
    {
        get { return m_oStartPos; }
        set { m_oStartPos = value; }
    }

    public System.Drawing.Size StartSize
    {
        get { return m_oStartSize; }
        set { m_oStartSize = value; }
    }
}

Класс менеджера для сохранения и загрузки:

public class CConfigMng
{
    private string m_sConfigFileName = System.IO.Path.GetFileNameWithoutExtension(System.Windows.Forms.Application.ExecutablePath) + ".xml";
    private CConfigDO m_oConfig = new CConfigDO();

    public CConfigDO Config
    {
        get { return m_oConfig; }
        set { m_oConfig = value; }
    }

    // Load configuration file
    public void LoadConfig()
    {
        if (System.IO.File.Exists(m_sConfigFileName))
        {
            System.IO.StreamReader srReader = System.IO.File.OpenText(m_sConfigFileName);
            Type tType = m_oConfig.GetType();
            System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
            object oData = xsSerializer.Deserialize(srReader);
            m_oConfig = (CConfigDO)oData;
            srReader.Close();
        }
    }

    // Save configuration file
    public void SaveConfig()
    {
        System.IO.StreamWriter swWriter = System.IO.File.CreateText(m_sConfigFileName);
        Type tType = m_oConfig.GetType();
        if (tType.IsSerializable)
        {
            System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
            xsSerializer.Serialize(swWriter, m_oConfig);
            swWriter.Close();
        }
    }
}

Теперь вы можете создать экземпляр и использовать в событиях загрузки и закрытия формы:

    private CConfigMng oConfigMng = new CConfigMng();

    private void Form1_Load(object sender, EventArgs e)
    {
        // Load configuration
        oConfigMng.LoadConfig();
        if (oConfigMng.Config.StartPos.X != 0 || oConfigMng.Config.StartPos.Y != 0)
        {
            Location = oConfigMng.Config.StartPos;
            Size = oConfigMng.Config.StartSize;
        }
    }

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        // Save configuration
        oConfigMng.Config.StartPos = Location;
        oConfigMng.Config.StartSize = Size;
        oConfigMng.SaveConfig();
    }

И созданный XML-файл также доступен для чтения:

<?xml version="1.0" encoding="utf-8"?>
<CConfigDO xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <StartPos>
    <X>70</X>
    <Y>278</Y>
  </StartPos>
  <StartSize>
    <Width>253</Width>
    <Height>229</Height>
  </StartSize>
</CConfigDO>
Дитер Мимкен
источник
1
У меня это отлично работает в разработке, но при развертывании приложения у обычного пользователя нет доступа к c:\program files\my applicationпапке, поэтому сохранение настроек выдает ошибку. Вместо этого я пытаюсь сохранить XML-файл в AppData, но мне просто интересно, существует ли очевидный способ обойти эту проблему, поскольку этот подход, похоже, сработал для вас.
Филипп Стратфорд
@PhilipStratford Так как это обычный файл, вы можете сохранить его где угодно. Просто найдите место с доступом для записи.
Дитер Мимкен
@PhilipStratford Может быть, папка AppData - это вариант для вас, смотрите C # получение пути% AppData% , как упомянуто kite .
Дитер Мимкен,
Спасибо, я уже реализовал это, сохраняя XML-файл в папке AppDate. Мне просто интересно, есть ли простой способ сохранить его в папке приложения в соответствии с вашим примером, так как я предполагал, что вы заставили его работать. Не волнуйтесь, папка AppData в любом случае, вероятно, лучше!
Филипп Стратфорд
5

Другие варианты, вместо использования пользовательского файла XML, мы можем использовать более удобный для пользователя формат файла: файл JSON или YAML.

  • Если вы используете динамический .NET 4.0, эта библиотека действительно проста в использовании (сериализация, десериализация, поддержка вложенных объектов и упорядочение вывода по вашему желанию + объединение нескольких настроек в один) JsonConfig (использование эквивалентно ApplicationSettingsBase)
  • Для библиотеки конфигурации .NET YAML ... Я не нашел такой простой в использовании, как JsonConfig

Вы можете сохранить файл настроек в нескольких специальных папках (для всех пользователей и для каждого пользователя), как указано здесь Environment.SpecialFolder Enumeration и несколько файлов (только для чтения по умолчанию, для каждой роли, для каждого пользователя и т. Д.)

Если вы решили использовать несколько настроек, вы можете объединить эти настройки: Например, объединение настроек по умолчанию + BasicUser + AdminUser. Вы можете использовать свои собственные правила: последнее переопределяет значение и т. Д.

воздушный змей
источник
4

«Значит ли это, что я должен использовать собственный XML-файл для сохранения настроек конфигурации?» Нет, не обязательно Мы используем SharpConfig для таких операций.

Например, если файл конфигурации такой

[General]
# a comment
SomeString = Hello World!
SomeInteger = 10 # an inline comment

Мы можем получить такие значения

var config = Configuration.LoadFromFile("sample.cfg");
var section = config["General"];

string someString = section["SomeString"].StringValue;
int someInteger = section["SomeInteger"].IntValue;

Он совместим с .NET 2.0 и выше. Мы можем создавать файлы конфигурации на лету и сохранить их позже.

Источник: http://sharpconfig.net/
GitHub: https://github.com/cemdervis/SharpConfig

Turker Tunali
источник
3

Насколько я могу судить, .NET поддерживает сохранение настроек с помощью встроенного средства настройки приложения:

Функция «Параметры приложения» в Windows Forms позволяет легко создавать, хранить и поддерживать пользовательские настройки приложений и пользователей на клиентском компьютере. В настройках приложения Windows Forms вы можете хранить не только данные приложения, такие как строки подключения к базе данных, но также данные, относящиеся к конкретному пользователю, такие как настройки приложения пользователя. Используя Visual Studio или пользовательский управляемый код, вы можете создавать новые настройки, считывать их и записывать на диск, связывать их со свойствами в формах и проверять данные настроек перед загрузкой и сохранением. - http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx

Иаков
источник
2
Не правда .. см. Ответ аку выше. это возможно с помощью настроек и ApplicationSettingsBase
Gishu
2

Иногда вы хотите избавиться от этих настроек, хранящихся в традиционном файле web.config или app.config. Вы хотите более детальный контроль над развертыванием записей настроек и отдельным дизайном данных. Или требуется включить добавление новых записей во время выполнения.

Я могу представить два хороших варианта:

  • Сильно типизированная версия и
  • Объектно-ориентированная версия.

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

Преимущество объектно-ориентированной версии заключается в том, что новые параметры могут быть добавлены во время выполнения. Но у вас нет строго типизированных имен и значений. Нужно быть осторожным со строковыми идентификаторами. Должен знать тип данных, сохраненный ранее при получении значения.

Вы можете найти код обеих полнофункциональных реализаций ЗДЕСЬ .

user3130351
источник
2

Да, можно сохранить конфигурацию, но это в значительной степени зависит от того, как вы решите это сделать. Позвольте мне описать технические различия, чтобы вы могли понять, какие у вас есть варианты:

Во- первых, нужно различать, хотите ли вы использовать applicationSettings или AppSettings в вашем *.exe.config(он же App.configв Visual Studio) файл - есть принципиальные различия, которые описаны здесь .

Оба предоставляют разные способы сохранения изменений:

  • В AppSettings позволяют читать и записывать непосредственно в конфигурационный файл ( с помощью config.Save(ConfigurationSaveMode.Modified);, где конфигурации определяется как config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);).

  • В applicationSettings позволяет читать, но если вы напишете изменения (через Properties.Settings.Default.Save();) будет написано на каждый пользователь, хранятся в специальном месте (например C:\Documents and Settings\USERID\Local Settings\Application Data\FIRMNAME\WindowsFormsTestApplicati_Url_tdq2oylz33rzq00sxhvxucu5edw2oghw\1.0.0.0). Как отметил в своем ответе Ханс Пассант , это связано с тем, что пользователь обычно имеет ограниченные права на программные файлы и не может писать в него, не вызывая приглашение UAC. Недостатком является то, что если вы добавляете ключи конфигурации в будущем, вам необходимо синхронизировать их с каждым профилем пользователя.

Примечание. Как уже упоминалось в вопросе, существует третий вариант: если вы рассматриваете файл конфигурации как XML-документ, вы можете загрузить, изменить и сохранить его с помощью System.Xml.Linq.XDocumentкласса. Не обязательно использовать пользовательский файл XML, вы можете прочитать существующий файл конфигурации; для запроса элементов вы можете даже использовать запросы Linq. Я привел пример здесь , проверьте функцию GetApplicationSettingтам в ответе.

Если вам требуется шифрование для защиты ваших значений, проверьте этот ответ.

Matt
источник
Спасибо за этот отличный ответ.
NoChance
2
@NoChance - Пожалуйста, рад, что я могу вам помочь!
Мэтт
Ницца! Наконец-то все понял 😂😂😂
Моморо
1
@ Моморо - приятно слышать! ;-)
Мэтт
1
public static class SettingsExtensions
{
    public static bool TryGetValue<T>(this Settings settings, string key, out T value)
    {
        if (settings.Properties[key] != null)
        {
            value = (T) settings[key];
            return true;
        }

        value = default(T);
        return false;
    }

    public static bool ContainsKey(this Settings settings, string key)
    {
        return settings.Properties[key] != null;
    }

    public static void SetValue<T>(this Settings settings, string key, T value)
    {
        if (settings.Properties[key] == null)
        {
            var p = new SettingsProperty(key)
            {
                PropertyType = typeof(T),
                Provider = settings.Providers["LocalFileSettingsProvider"],
                SerializeAs = SettingsSerializeAs.Xml
            };
            p.Attributes.Add(typeof(UserScopedSettingAttribute), new UserScopedSettingAttribute());
            var v = new SettingsPropertyValue(p);
            settings.Properties.Add(p);
            settings.Reload();
        }
        settings[key] = value;
        settings.Save();
    }
}
Paul - Soura Tech LLC
источник