|
Introducción
Esta clase que te muestro en este artículo es la que me fabriqué para guardar y leer los datos de configuración de mis aplicaciones.
La versión original de esta clase la hice para usarla con Visual Basic 2005, (abajo tienes el link), pero ni me acordaba de que ya la tenía convertida a C#, pero esta mañana buscando otra cosa, pues me topé con ella, y como ando más despistado que una cabra en un garaje, pues... ¡ni me acordaba de que no lo había publicado!.
Así que, ahora lo hago, ya sabes eso de que... más vale tarde que nunca... je, je.
Como te decía en el artículo de la versión para Visual Basic 2005, el código es fácil de adaptar a C# 1.0 (anteriores a Visual Studio 2005), prácticamente lo que debes hacer es quitar todo el código que utiliza las colecciones generic, pero... eso ya es algo que debes hacer tú... ¡así practicas! je, je, je.Para que puedas probar esta clase, adjunto una aplicación de prueba, que está hecha con el Visual C# 2005 usando el Visual Studio 2005 Team System, y aunque no la he probado en el Visual C# 2005 Express Edition, estoy seguro de que funciona, ya que no utiliza nada especial que no se instale con la versión gratuita de Visual C# 2005.
Pulsa aquí, si quieres ver un ejemplo anterior para Visual Studio 2003 (VB y C#)
Con fecha del 17/Ago/06 he publicado la versión para Visual Studio .NET (2003)Pulsa aquí para ver esta misma clase y el ejemplo de cómo usarla con Visual Basic 2005
Funcionalidad de la clase ConfigXml
Como te puedes imaginar por el título de esta sección, la clase se llama ConfigXml, Config porque sirve para guardar datos de configuración y Xml porque ese es el formato que utiliza, de hecho utilizo instrucciones Xml para leer los nodos del fichero de configuración lo mismo que para guardarlos.
Pero eso ya lo descubrirás en el código fuente, ahora veamos que métodos y propiedades tiene la clase:
Propiedad o método Descripción GuardarAlAsignar True para guardar automáticamente los valores al asignarlos FileName El nombre del fichero de configuración Leer los valores GetValue(ByVal seccion As String, ByVal clave As String) As String Leer un dato de la sección y clave indicadas. GetValue(ByVal seccion As String, ByVal clave As String, ByVal predeterminado As String) As String GetValue(ByVal seccion As String, ByVal clave As String, ByVal predeterminado As Integer) As Integer Valor devuelto como entero GetValue(ByVal seccion As String, ByVal clave As String, ByVal predeterminado As Boolean) As Boolean Valor devuelto como Boolean Guardar los valores como elementos SetValue(ByVal seccion As String, ByVal clave As String, ByVal valor As String) Guardar un valor en la sección y clave indicadas. SetValue(ByVal seccion As String, ByVal clave As String, ByVal valor As Integer) Guardar un valor entero SetValue(ByVal seccion As String, ByVal clave As String, ByVal valor As Boolean) Guardar un valor Boolean, internamente se guarda como cadena, siendo "0" = False, y "1" = True Guardar los valores como un par key/value SetKeyValue(ByVal seccion As String, ByVal clave As String, ByVal valor As String) SetKeyValue(ByVal seccion As String, ByVal clave As String, ByVal valor As Integer) SetKeyValue(ByVal seccion As String, ByVal clave As String, ByVal valor As Boolean) RemoveSection(ByVal seccion As String) Eliminar la sección indicada Save() Guardar físicamente los datos Read() Leer los datos Métodos exclusivos para .NET 2.0 Secciones() As List(Of String) Devuelve todas las secciones Claves(ByVal seccion As String) As Dictionary(Of String, String) Devuelve los valores y claves de la sección indicada. Constructores New(ByVal fic As String) Adivina para que son los parámetros de los dos constructores New(ByVal fic As String, ByVal guardarAlAsignar As Boolean) Como te indico, hay dos métodos que solo funcionarán en .NET 2.0, ya que devuelven dos tipos generic, el primero de tipo List(Of String) que lo puedes sustituir por un ArrayList. El otro devuelve una colección de tipo Dictionary(Of String, String) que puedes sustituir por una colección de tipo Hashtable.
Aquí tienes el código completo de la clase para Visual C# 2005
La aplicación de ejemplo
En el formulario de ejemplo te muestro cómo usar los métodos SetValue y RemoveSection, además de las dos colecciones que leen las secciones y el contenido de una sección, ya que en ese formulario de ejemplo hay un ListView en el que se muestra el contenido del fichero de configuración.
El aspecto del formulario en tiempo de diseño es este:
Figura 1. El formulario de ejemplo en tiempo de diseño
Aquí tienes el código del formulario para Visual C# 2005
Espero que te sea de utilidad, que de eso es de lo que se trata.
Nos vemos.
Guillermo
Nerja, 02 de Mayo de 2006Código de la clase:
//----------------------------------------------------------------------------- // Clase para manejar ficheros de configuración (15/Nov/05) // // Las secciones siempre estarán dentro de <configuration> // al menos así lo guardará esta clase, aunque permite leer pares key / value. // Para que se sepa que se lee de configuration, // en el código se indica explícitamente. // // Basada en mi código publicado el 22/Feb/05 en: // http://www.elguille.info/NET/dotnet/appSettings2.htm // Pero para usarla de forma independiente de ConfigurationSettings // // Revisado para poder guardar automáticamente (21/Feb/06) // Poder leer todas las secciones y las claves de una sección (21/Feb/06) // Revisada para que tenga la misma funcionalidad de ConfigXml.vb (02/May/06) // // ©Guillermo 'guille' Som, 2005-2006 //----------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.Configuration; using System.Xml; using System.IO; namespace elGuille.info.Util{ public class ConfigXml{ //---------------------------------------------------------------------- // Los campos y métodos privados //---------------------------------------------------------------------- private bool mGuardarAlAsignar = true; const string configuration = "configuration/"; private string ficConfig = ""; private XmlDocument configXml = new XmlDocument(); // /// <summary> /// Indica si se se guardarán los datos cuando se añadan nuevos. /// </summary> /// <value>Indica si se se guardarán los datos cuando se añadan nuevos.</value> /// <returns>Un valor verdadero o falso según el valor de la propiedad</returns> /// <remarks></remarks> public bool GuardarAlAsignar { get{ return mGuardarAlAsignar; } set{ mGuardarAlAsignar = value; } } // /// <summary> /// Obtiene un valor de tipo cadena de la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <returns>Un valor de tipo cadena con el valor de la sección y clave indicadas</returns> /// <remarks> /// Existe otra sobrecarga para indicar un valor predeterminado. /// Tanbién hay otras dos sobrecargas para valores enteros y boolean. /// </remarks> public string GetValue(string seccion, string clave) { return GetValue(seccion, clave, ""); } /// <summary> /// Obtiene un valor de tipo cadena de la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="predeterminado">El valor predeterminado para cuando no exista.</param> /// <returns>Un valor de tipo cadena con el valor de la sección y clave indicadas</returns> /// <remarks></remarks> public string GetValue(string seccion, string clave, string predeterminado) { return cfgGetValue(seccion, clave, predeterminado); } /// <summary> /// Obtiene un valor de tipo entero de la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="predeterminado">El valor predeterminado para cuando no exista.</param> /// <returns>Un valor de tipo entero con el valor de la sección y clave indicadas</returns> /// <remarks></remarks> public int GetValue(string seccion, string clave, int predeterminado) { return Convert.ToInt32(cfgGetValue(seccion, clave, predeterminado.ToString())); } /// <summary> /// Obtiene un valor de tipo boolean de la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="predeterminado">El valor predeterminado para cuando no exista.</param> /// <returns>Un valor de tipo boolean con el valor de la sección y clave indicadas</returns> /// <remarks>Internamente el valor se guarda con un cero para False y uno para True</remarks> public bool GetValue(string seccion, string clave, bool predeterminado) { string def = "0"; if( predeterminado ) def = "1"; def = cfgGetValue(seccion, clave, def); if( def == "1" ){ return true; }else{ return false; } } /// <summary> /// Asignar un valor de tipo cadena en la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="valor">El valor a asignar</param> /// <remarks> /// El valor se guardar como un elemento de la sección indicada. /// <seealso cref="SetKeyValue" /> /// </remarks> public void SetValue(string seccion, string clave, string valor) { cfgSetValue(seccion, clave, valor); } /// <summary> /// Asignar un valor de tipo entero en la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="valor">El valor a asignar</param> /// <remarks> /// El valor se guardar como un elemento de la sección indicada. /// El valor siempre se guarda como un valor de cadena. /// <seealso cref="SetKeyValue" /> /// </remarks> public void SetValue(string seccion, string clave, int valor) { cfgSetValue(seccion, clave, valor.ToString()); } /// <summary> /// Asignar un valor de tipo boolean en la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="valor">El valor a asignar</param> /// <remarks> /// El valor se guardar como un elemento de la sección indicada. /// El valor siempre se guarda como un valor de cadena, siendo un 1 para True y 0 para False. /// <seealso cref="SetKeyValue" /> /// </remarks> public void SetValue(string seccion, string clave, bool valor) { if( valor ){ cfgSetValue(seccion, clave, "1"); }else{ cfgSetValue(seccion, clave, "0"); } } /// <summary> /// Asigna un valor de tipo cadena en la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="valor">El valor a asignar</param> /// <remarks> /// El valor se guarda como un atributo de la sección indicada. /// La clave se guarda con el atributo key y el valor con el atributo value. /// <seealso cref="SetValue" /> /// </remarks> public void SetKeyValue(string seccion, string clave, string valor) { cfgSetKeyValue(seccion, clave, valor); } /// <summary> /// Asigna un valor de tipo entero en la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="valor">El valor a asignar</param> /// <remarks> /// El valor se guarda como un atributo de la sección indicada. /// La clave se guarda con el atributo key y el valor con el atributo value. /// El valor siempre se guarda como un valor de cadena. /// <seealso cref="SetValue" /> /// </remarks> public void SetKeyValue(string seccion, string clave, int valor) { cfgSetKeyValue(seccion, clave, valor.ToString()); } /// <summary> /// Asigna un valor de tipo boolean en la sección y clave indicadas. /// </summary> /// <param name="seccion">La sección de la que queremos obtener el valor</param> /// <param name="clave">La clave de la que queremos recuperar el valor</param> /// <param name="valor">El valor a asignar</param> /// <remarks> /// El valor se guarda como un atributo de la sección indicada. /// La clave se guarda con el atributo key y el valor con el atributo value. /// El valor siempre se guarda como un valor de cadena, siendo un 1 para True y 0 para False. /// <seealso cref="SetValue" /> /// </remarks> public void SetKeyValue(string seccion, string clave, bool valor) { if( valor ){ cfgSetKeyValue(seccion, clave, "1"); }else{ cfgSetKeyValue(seccion, clave, "0"); } } /// <summary> /// Elimina la sección indicada, aunque en realidad la deja vacía. /// </summary> /// <param name="seccion">La sección a eliminar.</param> /// <remarks></remarks> public void RemoveSection(string seccion) { XmlNode n; n = configXml.SelectSingleNode(configuration + seccion); if( n != null ){ n.RemoveAll(); if( mGuardarAlAsignar ){ this.Save(); } } } // Guardar el fichero de configuración // /// <summary> /// Guardar el fichero de configuración. /// </summary> /// <remarks> /// Si no se llama a este método, no se guardará de forma permanente. /// Para guardar automáticamente al asignar, /// asignar un valor verdadero a la propiedad <see cref="GuardarAlAsignar">GuardarAlAsignar</see> /// </remarks> public void Save() { configXml.Save(ficConfig); } /// <summary> /// Lee el fichero de configuración. /// </summary> /// <remarks> /// Si no existe, se crea uno nuevo con los valores predeterminados. /// </remarks> public void Read() { string fic = ficConfig; const string revDate = "Tue, 02 May 2006 17:23:00 GMT"; if( File.Exists(fic) ){ configXml.Load(fic); // Actualizar los datos de la información de esta clase bool b = mGuardarAlAsignar; mGuardarAlAsignar = false; this.SetValue("configXml_Info", "info", "Generado con Config para Visual Basic 2005"); this.SetValue("configXml_Info", "revision", revDate); this.SetValue("configXml_Info", "formatoUTF8", "El formato de este fichero debe ser UTF-8"); mGuardarAlAsignar = b; this.Save(); }else{ // Crear el XML de configuración con la sección General System.Text.StringBuilder sb = new System.Text.StringBuilder(); sb.Append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>"); sb.Append("<configuration>"); // Por si es un fichero appSetting sb.Append("<configSections>"); sb.Append("<section name=\"General\" type=\"System.Configuration.DictionarySectionHandler\" />"); sb.Append("</configSections>"); sb.Append("<General>"); sb.Append("<!-- Los valores irán dentro del elemento indicado por la clave -->"); sb.Append("<!-- Aunque también se podrán indicar como pares key / value -->"); sb.AppendFormat("<add key=\"Revisión\" value=\"{0}\" />", revDate); sb.Append("<!-- La clase siempre los añade como un elemento -->"); sb.Append("<Copyright>©Guillermo 'guille' Som, 2005-2006</Copyright>"); sb.Append("</General>"); // sb.AppendFormat("<configXml_Info>{0}", "\r\n"); sb.AppendFormat("<info>Generado con Config para Visual Basic 2005</info>{0}", "\r\n"); sb.AppendFormat("<copyright>©Guillermo 'guille' Som, 2005-2006</copyright>{0}", "\r\n"); sb.AppendFormat("<revision>{0}</revision>{1}", revDate, "\r\n"); sb.AppendFormat("<formatoUTF8>El formato de este fichero debe ser UTF-8</formatoUTF8>{0}", "\r\n"); sb.AppendFormat("</configXml_Info>{0}", "\r\n"); // sb.Append("</configuration>"); // Asignamos la cadena al objeto configXml.LoadXml(sb.ToString()); // // Guardamos el contenido de configXml y creamos el fichero configXml.Save(ficConfig); } } /// <summary> /// El nombre del fichero de configuración. /// </summary> /// <value>El path completo con el nombre del fichero de configuración.</value> /// <returns>Una cadena con el fichero de configuración.</returns> /// <remarks>El nombre del fichero se debe indicar en el constructor.</remarks> public string FileName { get{ return ficConfig; } set{ // Al asignarlo, NO leemos el contenido del fichero ficConfig = value; //LeerFile() } } /// <summary> /// Constructor en el que indicamos el nombre del fichero de configuración. /// </summary> /// <param name="fic">El fichero a usar para guardar los datos de configuración.</param> /// <remarks> /// Si no existe, se creará. /// Al usar este constructor, por defecto se guardarán los valores al asignarlos. /// </remarks> public ConfigXml(string fic) { ficConfig = fic; // Por defecto se guarda al asignar los valores mGuardarAlAsignar = true; Read(); } // Con este constructor podemos decidir si guardamos o no automáticamente /// <summary> /// Constructor en el que indicamos el nombre del fichero de configuración. /// </summary> /// <param name="fic">El fichero a usar para guardar los datos de configuración.</param> /// <param name="guardarAlAsignar"> /// Un valor verdadero o falso para indicar /// si se guardan los datos automáticamente al asignarlos.</param> /// <remarks></remarks> public ConfigXml(string fic, bool guardarAlAsignar) { ficConfig = fic; mGuardarAlAsignar = guardarAlAsignar; Read(); } // /// <summary> /// Devuelve una colección de tipo List con las secciones del fichero de configuración. /// </summary> /// <returns>Una colección de tipo List(Of String) con las secciones del fichero de configuración.</returns> /// <remarks>Este método solo se puede usar en la versión 2.0 o superior.</remarks> public List<string> Secciones() { List<string> d = new List<string>() ; XmlNode root; string s = "configuration"; root = configXml.SelectSingleNode(s); if( root != null ){ foreach(XmlNode n in root.ChildNodes){ d.Add(n.Name); } } return d; } /// <summary> /// Devuelve una colección de tipo Dictionary con las claves y valores de la sección indicada. /// </summary> /// <param name="seccion">La sección de la que queremos obtener las claves y valores.</param> /// <returns>Una colección de tipo Dictionary(Of String, String) con las claves y valores.</returns> /// <remarks></remarks> public Dictionary<string, string> Claves(string seccion) { Dictionary<string, string> d = new Dictionary<string, string>() ; XmlNode root; seccion = seccion.Replace(" ", "_"); root = configXml.SelectSingleNode(configuration + seccion); if( root != null ){ foreach(XmlNode n in root.ChildNodes){ if( d.ContainsKey(n.Name) == false ){ d.Add(n.Name, n.InnerText); } } } return d; } // //---------------------------------------------------------------------- // Los métodos privados //---------------------------------------------------------------------- // // El método interno para guardar los valores // Este método siempre guardará en el formato <seccion><clave>valor</clave></seccion> private void cfgSetValue(string seccion, string clave, string valor) { // XmlNode n; // // Filtrar los caracteres no válidos // en principio solo comprobamos el espacio seccion = seccion.Replace(" ", "_"); clave = clave.Replace(" ", "_"); // Se comprueba si es un elemento de la sección: // <seccion><clave>valor</clave></seccion> n = configXml.SelectSingleNode(configuration + seccion + "/" + clave); if( n != null ){ n.InnerText = valor; }else{ XmlNode root; XmlElement elem; root = configXml.SelectSingleNode(configuration + seccion); if( root == null ){ // Si no existe el elemento principal, // lo añadimos a <configuration> elem = configXml.CreateElement(seccion); configXml.DocumentElement.AppendChild(elem); root = configXml.SelectSingleNode(configuration + seccion); } if( root != null ){ // Crear el elemento elem = configXml.CreateElement(clave); elem.InnerText = valor; // Añadirlo al nodo indicado root.AppendChild(elem); } } // if( mGuardarAlAsignar ){ this.Save(); } } // Asigna un atributo a una sección // Por ejemplo: <Seccion clave=valor>...</Seccion> // También se usará para el formato de appSettings: <add key=clave value=valor /> // Aunque en este caso, debe existir el elemento a asignar. private void cfgSetKeyValue(string seccion, string clave, string valor) { // XmlNode n; // // Filtrar los caracteres no válidos // en principio solo comprobamos el espacio seccion = seccion.Replace(" ", "_"); clave = clave.Replace(" ", "_"); n = configXml.SelectSingleNode(configuration + seccion + "/add[@key=\"" + clave + "\"]"); if( n != null ){ n.Attributes["value"].InnerText = valor; }else{ XmlNode root; XmlElement elem; root = configXml.SelectSingleNode(configuration + seccion); if( root == null ){ // Si no existe el elemento principal, // lo añadimos a <configuration> elem = configXml.CreateElement(seccion); configXml.DocumentElement.AppendChild(elem); root = configXml.SelectSingleNode(configuration + seccion); } if( root != null ){ XmlAttribute a = ((XmlAttribute)configXml.CreateNode(XmlNodeType.Attribute, clave, null)); a.InnerText = valor; root.Attributes.Append(a); } } if( mGuardarAlAsignar ){ this.Save(); } } // Devolver el valor de la clave indicada private string cfgGetValue(string seccion, string clave, string valor) { // XmlNode n; // // Filtrar los caracteres no válidos // en principio solo comprobamos el espacio seccion = seccion.Replace(" ", "_"); clave = clave.Replace(" ", "_"); // Primero comprobar si están el formato de appSettings: <add key = clave value = valor /> n = configXml.SelectSingleNode(configuration + seccion + "/add[@key=\"" + clave + "\"]"); if( n != null ){ return n.Attributes["value"].InnerText; } // // Después se comprueba si está en el formato <Seccion clave = valor> n = configXml.SelectSingleNode(configuration + seccion); if( n != null ){ XmlAttribute a = n.Attributes[clave]; if( a != null ){ return a.InnerText; } } // // Por último se comprueba si es un elemento de seccion: // <seccion><clave>valor</clave></seccion> n = configXml.SelectSingleNode(configuration + seccion + "/" + clave); if( n != null ){ return n.InnerText; } // // Si no existe, se devuelve el valor predeterminado return valor; } } }
Código del formulario:
//------------------------------------------------------------------------------ // Prueba de la clase ConfigXml (21/Feb/06) // para guardar datos de configuración en formato Xml // // ©Guillermo 'guille' Som, 2006 //------------------------------------------------------------------------------ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace pruebaConfigXml_CS { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private elGuille.info.Util.ConfigXml mCfg; private void btnLeer_Click(System.Object sender, System.EventArgs e) { // Leer el fichero OpenFileDialog openFD = new OpenFileDialog(); openFD.Title = "Selecciona el fichero de configuración"; openFD.Filter = "Configuración|*.cfg;*.config;*.configuration|Todos los ficheros|*.*"; openFD.FileName = txtFic.Text; openFD.Multiselect = false; openFD.CheckFileExists = false; if (openFD.ShowDialog() == System.Windows.Forms.DialogResult.OK) { mCfg = new elGuille.info.Util.ConfigXml(openFD.FileName, true); actualizarListView(); bool b = true; this.btnAdd.Enabled = b; this.btnEliminarSec.Enabled = b; this.btnGuardar.Enabled = b; } } private void actualizarListView() { ListView1.Items.Clear(); foreach (string s in mCfg.Secciones()) { foreach (KeyValuePair<string, string> s1 in mCfg.Claves(s)) { ListViewItem lvi = ListView1.Items.Add(s); lvi.SubItems.Add(s1.Key); lvi.SubItems.Add(s1.Value); } } } private void btnEliminarSec_Click(System.Object sender, System.EventArgs e) { if (MessageBox.Show("¿Quieres eliminar la sección " + txtSec.Text + "?", "Eliminar sección", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes) { mCfg.RemoveSection(txtSec.Text); actualizarListView(); } } private void btnAdd_Click(System.Object sender, System.EventArgs e) { // Añadirlo / actualizarlo y rellenar el ListView if (mCfg == null) { MessageBox.Show("Debes indicar el ficehero en el que guardar los datos", "Añadir", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } mCfg.SetValue(txtSec.Text, txtClave.Text, txtValor.Text); actualizarListView(); } private void ListView1_SelectedIndexChanged(System.Object sender, System.EventArgs e) { ListViewItem lvi; if (ListView1.SelectedItems.Count > 0) { lvi = ListView1.SelectedItems[0]; this.txtSec.Text = lvi.SubItems[0].Text; this.txtClave.Text = lvi.SubItems[1].Text; this.txtValor.Text = lvi.SubItems[2].Text; } } private void Form1_Load(System.Object sender, System.EventArgs e) { // Deshabilitar los botones y controles // hasta que se indique el fichero bool b = false; this.btnAdd.Enabled = b; this.btnEliminarSec.Enabled = b; this.btnGuardar.Enabled = b; txtFic.Text = System.IO.Directory.GetCurrentDirectory() + @"\prueba.cfg"; } private void btnGuardar_Click(System.Object sender, System.EventArgs e) { // No hace falta si se indica "guardar automáticamente" en el constructor mCfg.Save(); } } }