¿Cómo puedo guardar la configuración de la aplicación en una aplicación de Windows Forms?

582

Lo que quiero lograr es muy simple: tengo una aplicación de Windows Forms (.NET 3.5) que usa una ruta para leer información. El usuario puede modificar esta ruta mediante el formulario de opciones que proporciono.

Ahora, quiero guardar el valor de la ruta en un archivo para su uso posterior. Esta sería una de las muchas configuraciones guardadas en este archivo. Este archivo se ubicaría directamente en la carpeta de la aplicación.

Entiendo que hay tres opciones disponibles:

  • Archivo de configuración (appname.exe.config)
  • Registro
  • Archivo XML personalizado

Leí que el archivo de configuración .NET no está previsto para guardar valores de nuevo en él. En cuanto al registro, me gustaría alejarme lo más posible.

¿Significa esto que debo usar un archivo XML personalizado para guardar la configuración?

Si es así, me gustaría ver un ejemplo de código de eso (C #).

He visto otras discusiones sobre este tema, pero todavía no me queda claro.

Fueled
fuente
¿Es esta una aplicación .NET WinForms? Si es así, ¿en qué versión de .NET está desarrollando?
Portman el
1
Sí, es una aplicación WinForms .NET Framework versión 3.5.
Fueled
1
¿necesita guardar contraseñas o valores secretos ? Tal vez requiere algún cifrado
Kiquenet

Respuestas:

593

Si trabaja con Visual Studio, es bastante fácil obtener configuraciones persistentes. Haga clic derecho en el proyecto en el Explorador de soluciones y elija Propiedades. Seleccione la pestaña Configuración y haga clic en el hipervínculo si la configuración no existe.

Use la pestaña Configuración para crear configuraciones de aplicaciones. Visual Studio crea los archivos Settings.settingsy Settings.Designer.settingsque contienen la clase singleton Settingsheredada de ApplicationSettingsBase . Puede acceder a esta clase desde su código para leer / escribir la configuración de la aplicación:

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

Esta técnica es aplicable tanto para consola, Windows Forms y otros tipos de proyectos.

Tenga en cuenta que debe establecer la propiedad de alcance de su configuración. Si selecciona Ámbito de aplicación, entonces Configuración.Por defecto. <Su propiedad> será de solo lectura.

Referencia: Cómo: Escribir la configuración del usuario en tiempo de ejecución con C # - Microsoft Docs

aku
fuente
2
Si tengo una solución, ¿se aplicará a la solución completa o para cada proyecto?
franko_camron
8
@Four: Tengo un proyecto .NET 4.0 WinApp aquí, y mi SomeProperty no es de solo lectura. Settings.Default.SomeProperty = 'value'; Settings.Default.Save();Funciona de maravilla. ¿O es porque tengo la configuración de usuario?
Doekman
44
@Cuatro: Cuando cambié una configuración de Usuario a Ámbito de aplicación y guardé el archivo, vi en el código generado que desapareció el configurador. Esto también sucede con el perfil del Cliente 4.0 ...
doekman
3
@Four: excelente enlace, aunque su afirmación de que Settings.Default.Save()no hace nada es incorrecta. Como @aku dice en la respuesta, la configuración del alcance de la aplicación es de solo lectura: guardar es ineficaz para ellos. Use ese PortableSettingsProvider personalizado para guardar la configuración del alcance del usuario en la app.config ubicada donde está el exe en lugar del que está en la carpeta AppData del usuario. No, en general no es bueno, pero lo uso durante el desarrollo para usar la misma configuración de compilación en compilación (sin él, van nuevas carpetas de usuario únicas con cada compilación).
Minnow
77
A partir de ahora, con .NET 3.5 parece que solo puede usar Settings.Default.SomeProperty para asignar un valor y obtener un tipo de letra sólido. Además, para ahorrar tiempo a otros (me tomó un tiempo resolver esto), debe escribir Properties.Settings.Default o agregar usando YourProjectNameSpace.Settings en la parte superior de su archivo. "Configuración" solo no está definido / encontrado.
eselk
95

Si planea guardar en un archivo dentro del mismo directorio que su ejecutable, aquí hay una buena solución que utiliza el formato 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;
        }
    }
}
Trevor
fuente
Sí, cambie el DEFAULT_FILENAME a una ruta absoluta si desea guardar en otro directorio. Creo que es más común guardar el archivo en el mismo directorio que la aplicación, o en un subdirectorio, si no los está guardando en el registro.
Trevor
Oh, quizás la mejor opción sería almacenar el archivo de configuración en la carpeta de datos de aplicación del usuario.
Trevor
1
No es necesario cambiar DEFAULT_FILENAME, solo llame settings.Save(theFileToSaveTo); Al ser todo en mayúsculas, DEFAULT_FILENAMEse supone que es una constante . Si desea una propiedad de lectura y escritura, cree una y haga que el constructor la establezca DEFAULT_FILENAME. Luego haga que el valor de argumento predeterminado sea null, pruebe esto y use su propiedad como valor predeterminado. Es un poco más de tipeo, pero le brinda una interfaz más estándar.
Jesse Chisholm
10
Tendrá que hacer referencia System.Web.Extensions.dllsi aún no lo ha hecho.
TEK
99
He creado una biblioteca completa basada en esta respuesta con muchas mejoras y la he puesto a disposición en nuget: github.com/Nucs/JsonSettings
NucS
67

El registro es un no-go. No está seguro de si el usuario que usa su aplicación tiene suficientes derechos para escribir en el registro.

Puede usar el app.configarchivo para guardar configuraciones de nivel de aplicación (que son las mismas para cada usuario que usa su aplicación).

Almacenaría configuraciones específicas del usuario en un archivo XML, que se guardaría en Almacenamiento aislado o en el directorio SpecialFolder.ApplicationData .

Además de eso, a partir de .NET 2.0, es posible almacenar valores de nuevo en el app.configarchivo.

Frederik Gheysels
fuente
8
Sin embargo, use el registro si desea la configuración por usuario / inicio de sesión.
thenonhacker
19
El registro no es portable
Kb.
10
@thenonhacker: O use Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData)
Kenny Mann
44
Se puede escribir en el registro de usuarios (muchos programas escriben información allí, y los permisos de los usuarios nunca son un problema). La ventaja de usar el registro sobre el uso de la configuración es que si tiene varias aplicaciones que comparten la misma carpeta (por ejemplo, un programa de instalación y un programa de aplicación), no compartirán la misma configuración.
Kesty
3
La principal desventaja de Registry es la forma difícil de exportar / copiar configuraciones a otras PC. Pero no estoy de acuerdo con "No estás seguro de si el usuario que usa tu aplicación tiene suficientes derechos para escribir en el registro" - En HKEY_CURRENT_USER siempre tienes derechos para escribir. Se puede denegar, pero el sistema de archivos también puede ser inaccesible para el usuario actual (todas las carpetas TEMP posibles, etc.).
i486
20

La ApplicationSettingsclase no admite guardar configuraciones en el archivo app.config . Eso es mucho por diseño; Las aplicaciones que se ejecutan con una cuenta de usuario debidamente protegida (piense en Vista UAC) no tienen acceso de escritura a la carpeta de instalación del programa.

Puedes luchar contra el sistema con la ConfigurationManagerclase. Pero la solución trivial es ir al diseñador de Configuración y cambiar el alcance de la configuración a Usuario. Si eso causa dificultades (por ejemplo, la configuración es relevante para cada usuario), debe colocar su función de Opciones en un programa separado para que pueda solicitar la solicitud de elevación de privilegios. O renunciar a usar una configuración.

Hans Passant
fuente
¿Podría por favor ampliar su última oración? Solicite elevación para escribir app.config o para escribir una aplicación separada que pasaría por los folers de todos los usuarios, busque user.config y edite estos.
CannibalSmith
2
El programa separado requiere un manifiesto para pedir elevación. Google 'asinvoker requireadministrator' para encontrar la sintaxis adecuada. Editar user.config no es práctico ni necesario.
Hans Passant el
18

El argumento Registry / configurationSettings / XML todavía parece muy activo. Los he usado todos, ya que la tecnología ha progresado, pero mi favorito se basa en el sistema de Threed combinado con el almacenamiento aislado .

El siguiente ejemplo permite el almacenamiento de objetos denominados propiedades en un archivo en almacenamiento aislado. Como:

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

Las propiedades pueden recuperarse usando:

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

Es solo una muestra, que no sugiere las mejores prácticas.

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
fuente
1
O mejor aún; use DataContractJsonSerializer
Boczek
16

Quería compartir una biblioteca que he construido para esto. Es una pequeña biblioteca, pero una gran mejora (en mi humilde opinión) sobre los archivos .settings.

La biblioteca se llama Jot (GitHub) . Aquí hay un viejo artículo de The Code Project que escribí al respecto.

Así es como lo usaría para realizar un seguimiento del tamaño y la ubicación de una ventana:

public MainWindow()
{
    InitializeComponent();

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

El beneficio en comparación con los archivos .settings: hay considerablemente menos código, y es mucho menos propenso a errores ya que solo necesita mencionar cada propiedad una vez .

Con los archivos de configuración, debe mencionar cada propiedad cinco veces: una vez cuando crea explícitamente la propiedad y cuatro veces más en el código que copia los valores de un lado a otro.

El almacenamiento, la serialización, etc. son completamente configurables. Cuando los objetos de destino son creados por un IoC contenedor de , puede [conectarlo] [] para que aplique el seguimiento automáticamente a todos los objetos que resuelve, de modo que todo lo que necesita hacer para que una propiedad sea persistente es abofetear a [Rastreable] atributo en él.

Es altamente configurable y puede configurar: - cuando los datos se conservan y se aplican globalmente o para cada objeto rastreado - cómo se serializan - dónde se almacenan (por ejemplo, archivo, base de datos, en línea, almacenamiento aislado, registro) - reglas que pueden cancelar la aplicación / datos persistentes para una propiedad

Confía en mí, ¡la biblioteca es de primera categoría!

anakic
fuente
14

Una manera simple es usar un objeto de datos de configuración, guardarlo como un archivo XML con el nombre de la aplicación en la carpeta local y al iniciarlo, volver a leerlo.

Aquí hay un ejemplo para almacenar la posición y el tamaño de un formulario.

El objeto de datos de configuración está fuertemente tipado y es fácil de usar:

[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; }
    }
}

Una clase de administrador para guardar y cargar:

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();
        }
    }
}

Ahora puede crear una instancia y usarla en los eventos de carga y cierre de su formulario:

    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();
    }

Y el archivo XML producido también es legible:

<?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>
Dieter Meemken
fuente
1
Esto funciona muy bien en el desarrollo, pero cuando implemento la aplicación, el usuario promedio no tiene acceso a la c:\program files\my applicationcarpeta, por lo que guardar la configuración arroja un error. Estoy buscando guardar el archivo xml en AppData, pero me preguntaba si había una forma obvia de solucionar este problema, ya que este enfoque parece haber funcionado para usted.
Philip Stratford
@PhilipStratford Dado que es solo un archivo normal, puede guardarlo en cualquier lugar. Simplemente encuentre un lugar con acceso de escritura.
Dieter Meemken
@PhilipStratford Puede ser que la carpeta AppData sea una opción para usted, vea C # obteniendo la ruta de% AppData% , como lo menciona kite .
Dieter Meemken
Gracias, ya he implementado esto, guardando el archivo xml en la carpeta AppDate. Me preguntaba si había una manera fácil de guardarlo en la carpeta de la aplicación según su ejemplo, ya que supuse que lo había hecho funcionar. ¡No se preocupe, la carpeta AppData es probablemente una mejor ubicación de todos modos!
Philip Stratford
5

Otras opciones, en lugar de usar un archivo XML personalizado, podemos usar un formato de archivo más fácil de usar: archivo JSON o YAML.

  • Si usa .NET 4.0 dinámico, esta biblioteca es realmente fácil de usar (serializar, deserializar, admitir objetos anidados y ordenar la salida como desee + fusionar múltiples configuraciones en una) JsonConfig (el uso es equivalente a ApplicationSettingsBase)
  • Para la biblioteca de configuración .NET YAML ... No he encontrado una que sea tan fácil de usar como JsonConfig

Puede almacenar su archivo de configuración en múltiples carpetas especiales (para todos los usuarios y por usuario) como se detalla aquí. Entorno. Enumeración de carpeta especial y múltiples archivos (solo lectura predeterminada, por rol, por usuario, etc.)

Si elige usar múltiples configuraciones, puede fusionar esas configuraciones: por ejemplo, fusionando la configuración por defecto + BasicUser + AdminUser. Puede usar sus propias reglas: la última anula el valor, etc.

cometa
fuente
4

"¿Esto significa que debo usar un archivo XML personalizado para guardar la configuración?" No, no necesariamente Utilizamos SharpConfig para tales operaciones.

Por ejemplo, si un archivo de configuración es así

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

Podemos recuperar valores como este

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

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

Es compatible con .NET 2.0 y superior. Podemos crear archivos de configuración sobre la marcha y podemos guardarlos más tarde.

Fuente: http://sharpconfig.net/
GitHub: https://github.com/cemdervis/SharpConfig

Turker Tunali
fuente
3

Por lo que puedo decir, .NET admite configuraciones persistentes utilizando la función de configuración de aplicación integrada:

La función de Configuración de aplicaciones de Windows Forms facilita la creación, el almacenamiento y el mantenimiento de las aplicaciones personalizadas y las preferencias del usuario en la computadora cliente. Con la configuración de la aplicación Windows Forms, puede almacenar no solo datos de la aplicación, como cadenas de conexión de la base de datos, sino también datos específicos del usuario, como las preferencias de la aplicación del usuario. Con Visual Studio o el código administrado personalizado, puede crear nuevas configuraciones, leerlas y escribirlas en el disco, vincularlas a las propiedades de sus formularios y validar los datos de configuración antes de cargar y guardar. - http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx

Jacob
fuente
2
No es cierto ... ver la respuesta de aku arriba. es posible usando Configuración y ApplicationSettingsBase
Gishu
2

A veces, desea deshacerse de la configuración guardada en el archivo web.config o app.config tradicional. Desea un control más detallado sobre la implementación de sus entradas de configuración y el diseño de datos separados. O el requisito es habilitar la adición de nuevas entradas en tiempo de ejecución.

Me imagino dos buenas opciones:

  • La versión fuertemente tipada y
  • La versión orientada a objetos.

La ventaja de la versión fuertemente tipada son los nombres y valores de configuración fuertemente tipados. No hay riesgo de mezclar nombres o tipos de datos. La desventaja es que se deben codificar más configuraciones, no se pueden agregar en tiempo de ejecución.

Con la versión orientada a objetos, la ventaja es que se pueden agregar nuevas configuraciones en tiempo de ejecución. Pero no tiene nombres y valores fuertemente tipados. Debe tener cuidado con los identificadores de cadena. Debe conocer el tipo de datos guardado anteriormente al obtener un valor.

Puede encontrar el código de ambas implementaciones completamente funcionales AQUÍ .

usuario3130351
fuente
2

Sí, es posible guardar la configuración, pero depende en gran medida de la forma en que elija hacerlo. Permítame describir las diferencias técnicas para que pueda comprender las opciones que tiene:

Primero, debe distinguir si desea usar applicationSettings o AppSettings en su archivo *.exe.config(también conocido App.configen Visual Studio): existen diferencias fundamentales que se describen aquí .

Ambos proporcionan diferentes formas de guardar los cambios:

  • Los AppSettings le permiten leer y escribir directamente en el archivo de configuración (a través config.Save(ConfigurationSaveMode.Modified);, donde se define como config config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);).

  • La configuración de la aplicación permite leer, pero si escribe cambios (a través de Properties.Settings.Default.Save();) se escribirá por usuario, almacenados en un lugar especial (por ejemplo C:\Documents and Settings\USERID\Local Settings\Application Data\FIRMNAME\WindowsFormsTestApplicati_Url_tdq2oylz33rzq00sxhvxucu5edw2oghw\1.0.0.0). Como Hans Passant mencionó en su respuesta, esto se debe a que un usuario generalmente tiene derechos restringidos a los Archivos de programa y no puede escribir en él sin invocar la solicitud de UAC. Una desventaja es que si agrega claves de configuración en el futuro, debe sincronizarlas con cada perfil de usuario.

Nota: Como se menciona en la pregunta, hay una tercera opción: si trata el archivo de configuración como documento XML, puede cargarlo, modificarlo y guardarlo utilizando la System.Xml.Linq.XDocumentclase. No es necesario usar un archivo XML personalizado, puede leer el archivo de configuración existente; para consultar elementos, incluso puede usar consultas Linq. He dado un ejemplo aquí , mira la función GetApplicationSettingallí en la respuesta.

Si necesita cifrado para proteger sus valores, consulte esta respuesta.

Mate
fuente
Gracias por esta excelente respuesta.
No,
2
@NoChance - De nada, ¡me alegro de que pueda ayudarte!
Matt
¡Agradable! Finalmente lo resolví todo 😂😂😂
Momoro
1
@Momoro: ¡me alegra saberlo! ;-)
Matt
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
fuente