WinForm教程(一)App.config等配置文件

最近又回到用WinForm开发项目,难免少不了跟数据库打交道。平时也会在App.config中写连接字串,但没有仔细深究过,当然除了写数据库字串,肯定还能写别的,我们无论在控制台程序还是窗体程序,都会遇见他,乘代码敲累之际,写篇博客平缓下。

应用程序配置文件是标准的 XML 文件,XML 标记和属性是区分大小写的。它是按需更改,开发人员可以使用配置文件来更改设置,而不必重编译应用程序。配置文件又分:(1)内置配置文件(2)用户自定义配置文件。内置配置文件主要包括App.config、Web.config、Settings.settings( 这个用的不多,操作也很简单,在此不详细叙述)等等。用户自定义配置文件一般是将配置信息放到XML文件或注册表中,配置信息一般包括程序设置,记录运行信息,保存控件的信息(比如位置,样式)。

(一)App.config

一般新建项目都会自动添加配置文件,如果没有可以选中项目,右击添加新建项,选择" 添加应用程序配置文件",即可完成添加,其根节点是固定的configuration。

常用的子节点有:connectionStrings、appSettings、configSections等,操作都略有不同。DotNet直接提供了操作各节点的后台方法,在用到ConfigurationManager时注意要添加System.Configuration.dll程序集的引用。通过下面程序例子,我们会有大致了解。

(1)connectionStrings

一般存放数据库连接字串,如开头所讲,注意后台获取方法。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>  
    <connectionStrings >
        <add name="MystrCon" connectionString="Server=58.210.75.190;Database=jkpt00003;User Id=root;Password=123456;Persist Security Info=True"/>
    </connectionStrings >
</configuration>

//不适用于配置节新增子元素
public static string conStr = System.Configuration.ConfigurationManager.ConnectionStrings["MystrCon"].ConnectionString;//后台获取方法

如果我们进行此connectionStrings节的增删改呢?首先不能通过上述后台ConfigurationManager方法直接赋值写入,会报错该配置是只读的。但肯定有别的方法进行完成诉求,通过Configuration配置对象来实现,请看下面程序。

Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);        
config.ConnectionStrings.ConnectionStrings["suny"].ConnectionString = "is a suny?";//修改 
config.ConnectionStrings.ConnectionStrings.Add(new ConnectionStringSettings("cloudy", "is cloud"));//增加
//原始或新加子元素都能获取
string name_three = config.ConnectionStrings.ConnectionStrings["cloudy"].ToString();
config.Save(ConfigurationSaveMode.Modified);//只有加保存功能,*.vshost.exe.Config才会作改变
ConfigurationManager.RefreshSection("ConnectionStrings");

(2)appSettings

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
      <add key="connectionstring" value="Data Source=127.0.0.1;initial catalog=Guest;User ID =sa;password=eric19897222008 "/>
    </appSettings>
</configuration>
//private static string SQL_CONN_STR = System.Configuration.ConfigurationSettings.AppSettings["connectionstring"];//已经被废弃
//不能获取配直节新增子元素
private static string SQL_CONN_STR = System.Configuration.ConfigurationManager.AppSettings["connectionstring"];

注意:现在.Net FrameWork 2.0以上框架中已经明确表示此ConfigurationSettings属性已经废弃,建议改为 ConfigurationManager。

当然,也能像connectionStrings一样,也能进行增删改查,请看下面程序。

Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["foggy"].Value = "is a fog?";//修改子节点
config.AppSettings.Settings.Remove("welcome");//删除子节点              
config.AppSettings.Settings.Add("url", "http://www.fx163.net");//增加子节点        
string get_ui = config.AppSettings.Settings["url"].Value;//获取子节点值,原配置或代码添加配置节子节点都适用   
config.Save(ConfigurationSaveMode.Modified);//只有加保存功能,*.vshost.exe.Config才会作改变
System.Configuration.ConfigurationManager.RefreshSection("AppSettings");

(3)configSections(自定义节点)

显然,用上面的两种默认的配置节,能满足基本使用。是否可以自定义配置节呢?当然可以了,我们可以自己定义各元素之间的关系,先看个简单的。

<configuration>
   <configSections>
      <section name="simple"  type="Appconfig.SimpleSection,Appconfig"/>
   </configSections>
   <simple maxValue="20" minValue="1"></simple>
   <startup> 
      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
   </startup>
</configuration>

Xml文件,注意三个点,一是configSections配置节,自定义的一定要写在其外面;二是name是新增节名字,Type是程序集命名空间+类名和空间;三是是必须作为第一个配置节,否则无法获取配置对象实例,前面搞定,我们敲开后台大门,先定义一个配置类。

一般需要做如下三件事:
1. 定义类型从System.Configuration.ConfigurationSection继承;
2. 定义配置类的属性,这些属性需要用ConfigurationProperty特性修饰,并制定属性在配置节中的名称和其他一些限制信息;
3. 通过基类的string索引器实现属性的get ,set;

//新加了个.cs类文件
namespace Appconfig
{
 
    public class SimpleSection : System.Configuration.ConfigurationSection
    {
        [ConfigurationProperty("maxValue", IsRequired = false, DefaultValue = Int32.MaxValue)]
        public int MaxValue
        {
            get
            {
                return (int)base["maxValue"];
            }
            set
            {
                base["maxValue"] = value;
            }
         }
         [ConfigurationProperty("minValue", IsRequired = false, DefaultValue = 1)]
         public int MinValue
            {
                get { return (int)base["minValue"]; }
                set { base["minValue"] = value; }
            }
         [ConfigurationProperty("enabled", IsRequired = false, DefaultValue = true)]
         public bool Enable
         {
             get
             {
                 return (bool)base["enabled"];
             }
             set
             {
                 base["enabled"] = value;
             }
          }
     }
}
//获取配置信息代码
Appconfig.SimpleSection simple = ConfigurationManager.GetSection("simple") as Appconfig.SimpleSection;
MessageBox.Show(simple.MinValue.ToString() + simple.MaxValue.ToString());

我们看个再复杂点的,配置节点自带属性值和子元素:

<configuration>
   <configSections>
      <section  name="complex"  type="Appconfig.ComplexSection,Appconfig"/>
   </configSections>
   <complex height="190">
      <child firstName="James" lastName="Bond"/>
   </complex>
   <startup> 
      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
   </startup>
</configuration>

这个配置的名字是complex,他有一个属性height,他的节点内还有一个child元素这个元素有两个属性firstName和lastName;对于这个内嵌的节点该如何实现呢?首先我们需要定义一个类,要从ConfigurationElement类继承,然后再用和SimpleSection类似的方法定义一些用ConfigurationProperty特性修饰的属性就可以了,当然属性值的get,set也要使用基类的索引器。如下实现:

namespace Appconfig
{
    class ComplexSection : ConfigurationSection 
    {
        [ConfigurationProperty("height", IsRequired = true)] 
        public int Height 
        { 
            get
            { 
                return (int)base["height"]; 
            } 
            set
            { 
                base["height"] = value; 
            } 
         } 
        [ConfigurationProperty("child", IsDefaultCollection = false)] 
        public ChildSection Child 
        { 
            get
            { 
                return (ChildSection)base["child"]; 
            } 
            set
            { 
                base["child"] = value; 
            } 
         } 
     }
    public class ChildSection : ConfigurationElement
    {
        [ConfigurationProperty("firstName", IsRequired = true, IsKey = true)]
        public string FirstName
        {
            get
            {
                return (string)base["firstName"];
            }
            set
            {
                base["firstName"] = value;
            }
        }

        [ConfigurationProperty("lastName", IsRequired = true)]
        public string LastName
        {
            get
            {
                return (string)base["lastName"];
            }
            set
            {
                base["lastName"] = value;
            }
        }
    }
}
 ComplexSection complex = ConfigurationManager.GetSection("complex") as ComplexSection;
 MessageBox.Show("Height" + complex.Height);
 MessageBox.Show("complex firstname+lastname:" + complex.Child.FirstName + complex.Child.LastName);

还有稍微再复杂一点的情况,我们可能要在配置中配置一组相同类型的节点,也就是一组节点的集合。如下面的配置:

<configuration>
  <configSections>
    <section name="morecomplex" type="Appconfig.MoreComplexSection,Appconfig"/>
  </configSections>
  <morecomplex height="190">
    <child firstName="James" lastName="Bond"/>
    <children>
      <add firstName="Zhao" lastName="yukai"/>
      <add firstName="Lee" lastName="yukai"/>
      <remove firstName="Zhao"/>
    </children>
  </morecomplex>
 <startup> 
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
 </startup>
</configuration>

请看children节点,它就是一个集合类,在它里面定义了一组add元素(children不是直接'/'结尾,包含两个子元素,见形式),也可以有remove节点把已经添进去的配置去掉。要使用自定义节点集合需要从ConfigurationElementCollection类继承一个自定义类,然后要实现此类GetElementKey(ConfigurationElement element)和ConfigurationElement CreateNewElement()两个方法;为了方便的访问子节点可以在这个类里面定义只读的索引器,请看下面的实现:

namespace Appconfig
{
    class MoreComplexSection : ConfigurationSection 
    {
        [ConfigurationProperty("height", IsRequired = true)]
        public int Height
        {
            get
            {
                return (int)base["height"];
            }
            set
            {
                base["height"] = value;
            }
        }
        [ConfigurationProperty("child", IsDefaultCollection = false)]
        public ChildSection Child
        {
            get
            {
                return (ChildSection)base["child"];
            }
            set
            {
                base["child"] = value;
            }
        }
        [ConfigurationProperty("children", IsDefaultCollection = false)]
        //[ConfigurationCollection(typeof(ChildSection), CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap, RemoveItemName = "remove")]//可加可不加
        public Children Children
        {
            get
            {
                return (Children)base["children"];
            }
            set
            {
                base["children"] = value;
            }
        }
    }
    public class Children : ConfigurationElementCollection
    {
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((ChildSection)element).FirstName;//新建节点定义一个Key值,Key同为firstname
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new ChildSection();
        }

        public ChildSection this[int i]
        {
            get
            {
                return (ChildSection)base.BaseGet(i);
            }
        }

        public ChildSection this[string key]
        {
            get
            {
                return (ChildSection)base.BaseGet(key);
            }
        }
        protected override string ElementName
        {
            get
            {
                return "children";
            }
        }
    }
    public class ChildSection : ConfigurationElement
    {
        [ConfigurationProperty("firstName", IsRequired = true, IsKey = true)]
        public string FirstName
        {
            get
            {
                return (string)base["firstName"];
            }
            set
            {
                base["firstName"] = value;
            }
        }

        [ConfigurationProperty("lastName", IsRequired = true)]
        public string LastName
        {
            get
            {
                return (string)base["lastName"];
            }
            set
            {
                base["lastName"] = value;
            }
        }
    }
}
MoreComplexSection morecomplex = ConfigurationManager.GetSection("morecomplex") as MoreComplexSection;
MessageBox.Show("Height" + morecomplex.Height);
MessageBox.Show("morecomplex child: firstname+lastname:" + morecomplex.Child.FirstName + morecomplex.Child.LastName);
//remove一个元素,所以只有1个
//组元素请参考 https://blog.csdn.net/sweety820/article/details/41695335
MessageBox.Show("morecomplex children: firstname+lastname:" + morecomplex.Children[0].FirstName + morecomplex.Children[0].LastName);

其实网上有很多微软提供的默认接口,如System.Configuration.DictionarySectionHandler、NameValueSectionHandler、SingleTagSectionHandler,去实现单或组元素获取,只需要写类去实现这些接口(Type用的是即是此类),很快就能获取到数据,可见https://blog.csdn.net/lulu_jiang/article/details/6688078https://www.cnblogs.com/caoyc/p/6002702.html两篇博客。当然现在我们用的是非继承接口自定义类,幸运的是,我们也实现了(单/组)元素的获取,前提代码量不小。如果使用系统标准的<add name=" ...", valeu="...">,就可以大大减少我们自定义的代码量,看下面程序:

<configuration>
  <configSections>
    <section name="easy" type="Appconfig.EasySection,Appconfig"/>
  </configSections>
  <easy height="190">
    <NVs>
      <add name="abc" value="123"/>
      <add name="abcd" value="12d3"/>
    </NVs>
  </esay>
 <startup> 
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
 </startup>
</configuration>
namespace Appconfig
{
    class EasySection : ConfigurationSection 
    {
        [ConfigurationProperty("height", IsRequired = true)]
        public int Height
        {
            get
            {
                return (int)base["height"];
            }
            set
            {
                base["height"] = value;
            }
        }
        [ConfigurationProperty("NVs", IsDefaultCollection = false)]
        public System.Configuration.NameValueConfigurationCollection NVs
        {
            get
            {
                return (NameValueConfigurationCollection)base["NVs"];
            }
            set
            {
                base["NVs"] = value;
            }
        }
    }
}
EasySection Easy= ConfigurationManager.GetSection("easy") as EasySection;
MessageBox.Show("Height" + Easy.Height);
MessageBox.Show(Easy.NVs["abc"].Value);

是不是代码量少了许多?到这儿已经基本上可以满足所有的配置需求了。不过还有一点更大但是不复杂的概念,就是sectionGroup。我们可以自定义SectionGroup,然后在sectionGroup中配置多个section;分组对于大的应用程序是很有意义的。
如下配置,配置了一个包含simple和一个morecomplex两个section的sectionGroup

<configuration>
  <configSections>
    <sectionGroup type="Appconfig.SampleSectionGroup,Appconfig" name="sampleGroup">
      <section type="Appconfig.SimpleSection,Appconfig" allowDefinition="Everywhere" name="simple" />
      <section type="Appconfig.MoreComplexSection,Appconfig" allowDefinition="Everywhere" name="morecomplex"/>
    </sectionGroup>
  </configSections>
  <sampleGroup>
    <simple maxValue="20" minValue="1">
    </simple>
    <morecomplex height="190">
      <child firstName="James" lastName="Bond"/>
      <children>
        <add firstName="Zhao" lastName="yukai"/>
        <add firstName="Lee" lastName="yukai"/>
        <remove firstName="Zhao"/>
      </children>   
    </morecomplex>
  </sampleGroup>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
</configuration>
namespace Appconfig
{
    class SampleSectionGroup: System.Configuration.ConfigurationSectionGroup 
    {
        public MoreComplexSection Complex
        {
            get
            {
                return (MoreComplexSection)base.Sections["morecomplex"];
            }
        }

        public SimpleSection Simple
        {
            get
            {
                return (SimpleSection)base.Sections["simple"];
            }
        } 
    }
}

SimpleSection和MoreComplexSection不变,SectionGroup只是做个集合,需要注意的是SectionGroup不能使用ConfigurationManager.GetSection(string)方法来获得,要获得sectionGroup必须通过Configuration类的SectionGroups[string]索引器获得,如下示例代码:

SampleSectionGroup sample = (SampleSectionGroup)ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["sampleGroup"];
MessageBox.Show(sample.Simple.MinValue.ToString());
MessageBox.Show(sample.Complex.Children[0].LastName);

(二)Web.config

Web.config跟App.config差不多,由于我不了解C#的Web运用,所以没法举例。日后如果学习了这部分做网页知识,肯定会来补上。但是,个人觉得做网页并非是微软系C#的强项,更不提主流开发语言了。

(三)Settings.settings

敬请期待,周一更新。

总结

我们可以发现,在项目debug调试下,会在其目录生成app.config,*.exe.config 和 *.vshost.exe.config。它们三者的功能及存在意义都不同。

app.config 作为开发时配置参数的文件,可以在vs开发时右键属性设置是否复制到可执行程序同目录下(默认始终不复制)。在不复制的情况下,凡进行编译操作在生成可执行程序时会copy该文件为*.exe.config文件和*.vshost.exe.config文件。除非编写程序修改它,一旦生成可执行程序后,没有存在意义。

*.exe.config exe.config文件为程序实际运行时(直接运行.exe程序)直接操作的配置文件,它极大地提高程序的灵活性。例如当程序发布了,我们又想改配置字段。我们不需要去源码中改app.config文件,然后重新编译一遍,这样笨拙繁琐,完全抛去了*.exe.config设计初衷。我们直接修改它,就可以了,执行文件运行只跟它打交道,跟app.config及*.vshost.exe.config都没关系。

*.vshost.exe.config 从名字我们可以看出它跟宿主程序有关系,跟调试有关系,事实上确实如此。该文件主要是在vs调试运行时起作用。在调试运行程序伊始,*vshost.exe.config先copy *.exe.config内容,覆盖*.vshost.exe.config,然后运行程序时操作的配置文件为*.vshost.exe.config,代码对配置文件增删改,都会在这里变动。在调试程序结束时 *.vshost.exe.config再次copy *.exe.config覆盖*.vshost.exe.config,因为exe.config仅为执行文件运行操作,调试不会起任何变动,所以*.vshost.exe.config的内容又变回跟*.exe.config一样。

实际上发布release版本的exe程序时,*.vshost.exe.config 和app.config(如果copy了的话)可以不要的,但是*.exe.config文件必须有,它是要被执行文件读取的,后两者在发布后没有存在的意义。

猜你喜欢

转载自blog.csdn.net/yangwenxue1989/article/details/81707619