根据注册表读取应用程序配置文件,根据XML文本动态生成树状列表,自定义SAP GUI登录页面

 技术要点

1.根据SAPGUI应用程序名动态读取登录配置文件的绝对路径,如果应用程序未安装,则提示相应消息。
2. 根据获取的路径得到配置文件的XML文本字符串,其中包含所有的登录信息,调用上一篇日志解析XML的类方法,得到登录界面结构的文档实例
3.通过文档实例利用控件类动态生成控件的实例TreeView和ListView,作为窗体面板控件的子节点
4.TreeView和ListView联动控制,点击TreeView叶子节点动态加载ListView,点ListView中某一行项目,则根据该项的信息关联配置文件获的的数据信息,动态生成一个*.sap图标文件,并将相关信息构造成结构化字符串写入到*.sap登录文件中,

5.调用SapGui.exe可执行程序打开刚创建好的 *.sap文件,从而实现GUI动态登录;当然这个功能强大的地方不在于此,而是找到了一种不用修改底层数据文件和代码,直接通过前端UI操作来修改底层数据,实现想要的功能的方法
 



读写注册表的类   

 

class Registry

    {

       public enum SoftName : short

        {

           SAPgui

        }

       public enum ConfigKeyName : short

        {

           ConnectionConfigFile,       //D:\ProgramFiles\Java\Common\saplogon.ini

           MessageServerConfigFile,    //C:\WINDOWS\SAPMSG.INI

           RouterConfigFile,           //C:\WINDOWS\SAPROUTE.INI

           ShortcutConfigFile,         //D:\ProgramFiles\Java\Common\sapshortcut.ini

           TreeConfigFile              //D:\ProgramFiles\Java\Common\SapLogonTree.xml

        }

       private static int index;

       public static string findApplicaiton(SoftName softName)

        {

           string strKeyName = string.Empty;

           string softPath;

           try

            {

                Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.LocalMachine;

                switch(softName)

               {

                    case SoftName.SAPgui:

                        softPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\AppPaths\";

                        Microsoft.Win32.RegistryKey regSubKey = regKey.OpenSubKey(softPath +softName + ".exe", false);

                        object objResult = regSubKey.GetValue(strKeyName);

                        Microsoft.Win32.RegistryValueKind regValueKind =regSubKey.GetValueKind(strKeyName);

                        if (regValueKind == Microsoft.Win32.RegistryValueKind.String)

                       {

                            return objResult.ToString();

                       }

                        break;

                    default:

                        break;

               }

            }

           catch

            {

                return "";

            }

           return "";

        }



       public static string findApplicationFile(SoftName softName, ConfigKeyName fileName)

        {

           index = 0;

           if (findApplicaiton(softName) == "")

            {

                return "";

            }

           else

            {

                switch(softName)

               {

                    case SoftName.SAPgui:

                        return getApplicationKeyInfo(softName, fileName);

                        //break;

                    default:

                        break;

               }

            }

           return "";

        }

       private static string getApplicationKeyInfo(SoftName softName, ConfigKeyName fileName)

        {

           Microsoft.Win32.RegistryKey KeyNode;

           string path;

           string[] keyName;

           switch (softName)

            {

                case SoftName.SAPgui:

                    KeyNode = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\SAP\SAPLogon");

                   keyName = KeyNode.GetSubKeyNames();

                    for (int i = index; i < keyName.Length; i++)

                   {

                       index = i;

                        if (keyName[i].Contains("ConfigFiles"))

                       {

                            path = (string)KeyNode.OpenSubKey(keyName[i]).GetValue(fileName.ToString());

                            if (path == null || path == "")

                            {

                                continue;

                            }

                            else

                            {

                                return path;

                            }

                       }

                   }

                    break;

                default:

                    break;

            }

           return "";

        }

    }


读取配置文件的类,配置文件是以格式化文本的形式保存,根据其规则动态加载到键值表实例   

class AnalyseKeyValueString

    {

       static Dictionary[]source;

       static int index = 0;

       static Regex regex = newRegex(@"[a-zA-Z0-9-\s]+?=(""(\s|\S)*?""|[^""]*?$)");

       static MatchCollection matchCollection;

       public static Dictionary[]getStruct(string str)

        {

           string[] keyvalue = new string[2];

           string lable = "";

           int num = getNum(str);

           source = new Dictionary[num];

           for (int i = 0;i < num; i++)

            {

                source[i] = new Dictionary();

            }

           string[] lines = str.Split(new char[] { '\r', '\n' },StringSplitOptions.RemoveEmptyEntries);

           for (int i = 0;i < lines.Length; i++)

            {

                if(lines[i][0]=='[')

               {

                   index = 0;

                   lable = lines[i].Substring(1, lines[i].Length - 2);

                    continue;

               }

               matchCollection = regex.Matches(lines[i]);

                if(matchCollection.Count == 1)

               {

                    keyvalue =matchCollection[0].Value.Split(newchar[] { '=' }, 2);

                   source[index].Add(lable, keyvalue[1]);

               }

                else if(matchCollection.Count > 1)

               {

                    for (int j = 0; j < matchCollection.Count; j++)

                   {

                        keyvalue =matchCollection[j].Value.Split(newchar[] { '=' }, 2);

                       source[index].Add(keyvalue[0].Trim(), keyvalue[1].Replace("\"",""));

                   }

               }

               index++;

            }

           return source;

        }



       private static int getNum(string str)

        {

           return new Regex(@"([a-zA-Z0-9-\s]+?=(""(\s|\S)*?""|[^""^\[]*?\r\n)){2,}").Match(str).Value.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;

        }

    }

解析XML的类AnalyseXML内容有点多,在上一篇日志已经提过,有几个主要的方法

1通过递归的方式将偏平的链表结构转为树状的深度链表结构         

private List getNest(List ltag)

        {

           Listntag = new List();

           tagNest subtag;

           for (int i = 0;i < ltag.Count; i++)

            {

                subtag = new tagNest();

               subtag.tagName = ltag[i].tagName;

               subtag.Attribute = ltag[i].Attribute;

               subtag.subtag = getNest(Ltag.FindAll(s =>s.parentKey.Equals(ltag[i].Key)));

               ntag.Add(subtag);

            }

           return ntag;

        }

public void plain2nest()

        {

           Ntag = getNest(new List() { Ltag[0] });

        }

2.根据获取的深度链表动态创建TreeView节点
         

 /// 

        /// 初始化时用

       /// 

       /// 

       public void addToAplicationViewTree()

        {

           getStruct();

           plain2nest();

           TreeNode node;

           TreeNode subNode;

           ListNodes = Ntag[0].subtag[1].subtag[1].subtag;



           for (int i = 0;i < Nodes.Count; i++)

            {

               node = aplicationView.treeView1.Nodes.Add(Nodes[i].tagName);

               node.SelectedImageIndex = node.ImageIndex = 2; //设置选中后的图片和选中前的一致

                for (int j = 0; j < Nodes[i].subtag.Count; j++)

               {

                    Nodes[i].subtag[j].tagName= Nodes[i].subtag[j].Attribute["name"].ToString().Replace("\"","");

                   subNode = node.Nodes.Add(Nodes[i].subtag[j].tagName);

                   subNode.SelectedImageIndex = subNode.ImageIndex = 3;



               }

            }

        }


3.根据点击TreeView不同层级的节点响应不同的方法,叶子动态加载ListView         

/// 

        /// 点击Tree Node时调用

       /// 

        /// 要展示的地方,提前已经创建好了Header

        /// Node节点名称

       internal void addToAplicationViewList(TreeNode treeNode)

        {

           Dictionary detail;

           if (treeNode.Level == 0)

            {

                if(treeNode.IsExpanded)

               {

                   treeNode.Collapse();

                   treeNode.ImageIndex = 2;

                   treeNode.SelectedImageIndex = 2;

               }

                else

               {

                   treeNode.Expand();

                   treeNode.ImageIndex = 1;

                   treeNode.SelectedImageIndex = 1;

               }

           }else if (treeNode.Level == 1)

            {

               aplicationView.listView1.Items.Clear();

                tagNestnodes = Ntag[0].subtag[1].subtag[1].subtag.Find(s =>s.tagName.Equals(treeNode.Parent.Text)).subtag.Find(j =>j.tagName.Equals(treeNode.Text));

               aplicationView.listView1.BeginUpdate();  //数据更新,UI暂时挂起,直到EndUpdate绘制控件,可以有效避免闪烁并大大提高加载速度  

                switch(treeNode.Parent.Text)

               {

                    case "Favorites":

                       aplicationView.nodeStyle = AplicationView.NodeStyle.Favorites;

                        break;

                    case "Shortcuts":

                       aplicationView.nodeStyle = AplicationView.NodeStyle.Shortcuts;

                        //tagNest nodes =Ntag[0].subtag[1].subtag[1].subtag.Find(s=>s.Attribute.First(q=>q.Value== ""))

                       aplicationView.listView1.Columns.Clear();

                       aplicationView.listView1.Columns.Add("名称", 120, HorizontalAlignment.Left);

                       aplicationView.listView1.Columns.Add("描述", 150, HorizontalAlignment.Left);

                       aplicationView.listView1.Columns.Add("系统", 60, HorizontalAlignment.Left);

                       aplicationView.listView1.Columns.Add("集团", 60, HorizontalAlignment.Left);

                       aplicationView.listView1.Columns.Add("账号", 100, HorizontalAlignment.Left);

                       aplicationView.listView1.Columns.Add("事务代码", 60, HorizontalAlignment.Left);



                        for (int i = 0; i < nodes.subtag.Count; i++)

                       {

                           nodes.subtag[i].tagName = nodes.subtag[i].Attribute["name"].ToString().Replace("\"", "");

                            ListViewItem lvi = new ListViewItem();

                            lvi.ImageIndex = 4;     //通过与imageList绑定,显示imageList中第i项图标    

                            lvi.Text =nodes.subtag[i].tagName;

                            detail =aplicationView.listViewDetailShortcuts.First((s) => s["Label"] == lvi.Text);

                           lvi.SubItems.Add(detail["-desc"]);

                           lvi.SubItems.Add(detail["-sid"]);

                           lvi.SubItems.Add(detail["-clt"]);

                           lvi.SubItems.Add(detail["-u"]);

                           lvi.SubItems.Add(detail["-cmd"]);



                           aplicationView.listView1.Items.Add(lvi);

                       }

                        break;

                    case "Connections":

                       aplicationView.nodeStyle = AplicationView.NodeStyle.Connections;

                       aplicationView.listView1.Columns.Clear();

                       aplicationView.listView1.Columns.Add("名称", 180, HorizontalAlignment.Left);

                       aplicationView.listView1.Columns.Add("服务器地址", 180, HorizontalAlignment.Left);

                       aplicationView.listView1.Columns.Add("系统编号", 60, HorizontalAlignment.Left);

                       aplicationView.listView1.Columns.Add("系统标识", 60, HorizontalAlignment.Left);



                        for (int i = 0; i < nodes.subtag.Count; i++)

                       {

                           nodes.subtag[i].tagName = nodes.subtag[i].Attribute["name"].ToString().Replace("\"", "");

                            ListViewItem lvi = new ListViewItem();

                            lvi.ImageIndex = 4;     //通过与imageList绑定,显示imageList中第i项图标    

                            lvi.Text =nodes.subtag[i].tagName;

                            detail =aplicationView.listViewDetailConnections.First((s) => s["Description"] == lvi.Text);

                           lvi.SubItems.Add(detail["Server"]);

                           lvi.SubItems.Add(detail["Database"]);

                           lvi.SubItems.Add(detail["MSSysName"]);



                           aplicationView.listView1.Items.Add(lvi);

                       }

                        break;

                    default:

                        break;

               }

               aplicationView.listView1.EndUpdate();  //结束数据处理,UI界面一次性绘制。

            }



        }

登录窗口的类

     public partial class AplicationView : Form

    {

       AnalyseXML analyseXML;

       Form1 form1;

       string configfilename="";

       int edition = 0;

       Dictionary items;

       public Dictionary[]listViewDetailShortcuts;

       public Dictionary[]listViewDetailConnections;

       public enum NodeStyle

        {

           None,

           Favorites,

           Shortcuts,

           Connections,

        }

       public NodeStyle nodeStyle;

       public AplicationView(Form1 form1)

        {

           this.form1 = form1;

           InitializeComponent();

        }

       private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)

        {

           analyseXML.addToAplicationViewList(e.Node);

        }

       private void AplicationView_Load(object sender, EventArgs e)

        {

           this.listView1.View = View.Details;

           this.listView1.MultiSelect = false;

           this.listView1.HeaderStyle = ColumnHeaderStyle.Clickable;

           this.listView1.LabelEdit = false;

           this.listView1.FullRowSelect = true;

           



           string txtpath = Registry.findApplicationFile(Registry.SoftName.SAPgui, Registry.ConfigKeyName.TreeConfigFile);

           //MessageBox.Show(FileProcess.readContent(a));

           analyseXML = new AnalyseXML(FileProcess.readContent(txtpath,Encoding.UTF8),this);

           analyseXML.addToAplicationViewTree();



           listViewDetailShortcuts = AnalyseKeyValueString.getStruct(FileProcess.readContent(Registry.findApplicationFile(Registry.SoftName.SAPgui, Registry.ConfigKeyName.ShortcutConfigFile),Encoding.GetEncoding("GB2312")));

           listViewDetailConnections = AnalyseKeyValueString.getStruct(FileProcess.readContent(Registry.findApplicationFile(Registry.SoftName.SAPgui, Registry.ConfigKeyName.ConnectionConfigFile),Encoding.GetEncoding("GB2312")));

        }

       //ConnectSAPconnectSAP;

       string fileContent;

       private void listView1_Click(object sender, EventArgs e)

        {

           string[] paths =null;

            int selectCount = this.listView1.SelectedItems.Count;

            if (selectCount > 0)

            {

                switch(nodeStyle)

               {

                    case NodeStyle.None:

                        break;

                    case NodeStyle.Favorites:

                        break;

                    case NodeStyle.Shortcuts:

                        items =listViewDetailShortcuts.First(s => s["-desc"].Equals(this.listView1.SelectedItems[0].SubItems[1].Text));

                        FileProcess.SubFolderCheckOrCreate(Application.StartupPath, "LoginConfig");

                        try

                       {

                            configfilename = Regex.Replace(items["-desc"], "[/:*?\"<>|]", match => { return "_";}) + this.listView1.SelectedItems[0].SubItems[3].Text;

                            paths = Directory.GetFiles(Application.StartupPath + "\\LoginConfig", configfilename + "*.sap", SearchOption.TopDirectoryOnly);

                       }

                        catch (Exception)

                       {

                       }

                        if (null != paths && paths.Length > 0)

                       {

                            edition = Convert.ToInt32(Path.GetFileNameWithoutExtension(paths[0]).Substring(configfilename.Length+ 1)) + 1;

                            File.Delete(paths[0]);

                       }

                        else

                       {

                            edition = 1;

                       }

                        fileContent = "[System]\r\nName=" + this.listView1.SelectedItems[0].SubItems[2].Text+ "\r\nDescription=" + this.listView1.SelectedItems[0].SubItems[1].Text+ "\r\nClient=" + this.listView1.SelectedItems[0].SubItems[3].Text+ "\r\n[User]\r\nName=" + this.listView1.SelectedItems[0].SubItems[4].Text+ "\r\nLanguage=" + items["-l"] + "\r\nPassword=" + items["-pwenc"] + "\r\n[Function]\r\nTitle=" + configfilename + "_" + edition + "\r\nCommand=" + items["-cmd"] + "\r\n[Configuration]\r\nWorkDir=" + items["-wd"] + "\r\n[Options]\r\nReuse=1\r\n";

                        FileProcess.CreateFormatplainFile("LoginConfig\\" + configfilename + "_" + edition + ".sap", fileContent);

                        break;

                    case NodeStyle.Connections:

                        break;

                    default:

                        break;

               }

                Process.Start(Application.StartupPath + "\\LoginConfig\\" + configfilename + "_"+ edition + ".sap");

                //connectSAP = new ConnectSAP(this.form1);

                //connectSAP.ShiLianSystem();

               

            }

        }


连接RFC的类

using SAP.Middleware.Connector;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;



namespace WritePlane

{

    class ConnectSAP

    {

       RfcDestination rfcDest;

       RfcConfigParameters Parms;

       RfcRepository rfcrep;

       IRfcFunction func;

       string FFMName;

       string InputName;

       string OutputName;

       public class ConnectStruct

        {

           public ConnectStruct(bool p_isconnect, bool p_isconnectsuccessful, string p_connectinfo)

            {

               isconnect = p_isconnect;

               isconnectsuccessful = p_isconnectsuccessful;

               connectinfo = p_connectinfo;

            }

            public bool isconnect = false;//连接状态:true正在连接,false连接结束

            public bool isconnectsuccessful = false;//是否成功连接,ture成功,false失败

            public string connectinfo = "WithoutConnection";//连接信息

        };



       ConnectStruct connstru;

        delegate void SetTextCallback(ConnectStruct info);//具有一个传入参数的委托

       Funcfunc_sys;

       Funcfunc_rfm;

       IAsyncResult cookie;



       Form1 form1;

       public ConnectSAP(Form1form1)

        {

           this.form1 = form1;

        }

       public void ShiLianSystem()

        {

           form1.toolStripStatusLabel1.Text = "Wait...";

           connstru = new ConnectStruct(false, false, "WithoutConnection");



           RfcConfigParameters parms = new RfcConfigParameters();



           parms.Add(RfcConfigParameters.Name, "ED1");

           parms.Add(RfcConfigParameters.AppServerHost, "XX.XX.XX.XXX");

           parms.Add(RfcConfigParameters.SystemNumber, "XX");

           parms.Add(RfcConfigParameters.Client, "XXX");

           parms.Add(RfcConfigParameters.User, "XXXXXXX");

           parms.Add(RfcConfigParameters.Password, "XXXXXXXXXX");

           parms.Add(RfcConfigParameters.Language, "ZH");

           parms.Add(RfcConfigParameters.PoolSize, "1");

           //parms.Add(RfcConfigParameters.,"10");

           parms.Add(RfcConfigParameters.IdleTimeout, "1");

           parms.Add(RfcConfigParameters.MaxPoolWaitTime, "1");

           parms.Add(RfcConfigParameters.SAPRouter, "XXXXXXXXXXXXX");

           FFMName = "ZZTEST_WEBSERVICE_02";

           InputName = "ZZIMSEG";

           OutputName = "ZZEMSEG";

           this.SetConnectandRFM(parms);

           //this.SetRFM("ZZTEST_WEBSERVICE_02");





           //RfcDestinationrfcDest;

           

           //rfcDest =RfcDestinationManager.GetDestination(parms);

           //RfcRepository rfcrep= rfcDest.Repository;

            //IRfcFunction func =rfcrep.CreateFunction("ZPPRFM005");//RFM名称

            //func.SetValue("I_USAGE","1");//SAP里面的传入参数

           //func.SetValue("I_STATUS","4");

           //func.SetValue("I_UNIT","EA");

           //func.SetValue("I_ACTION","1");

           //func.Invoke(rfcDest);

            //IRfcTable IrfTable =func.GetTable("IT_ROUTING");   //提前实例化一个空的表结构出来



            //IRfcStructure IrfStru =func.GetStructure("E_MESSAGE");   //提前实例化一个空的表结构出来



           //string a=IrfStru.GetString("ID");



           //DataTable dt = newDataTable();

           //dt.Columns.Add("ID");

           //dt.Columns.Add("ZBX");

           //dt.Columns.Add("MESSAGE");

            循环把IRfcTable里面的数据放入Table里面,因为类型不同,不可直接使用。

           //for (int i = 0; i< IrfStru.Count; i++)

           //{

           //    IrfTable.CurrentIndex = i;

           //    DataRow dr = dt.NewRow();

           //    dr["ID"] =IrfStru.GetString("ID");

           //    dr["ZBX"] =IrfStru.GetString("ZBX");

           //    dr["MESSAGE"] =IrfStru.GetString("MESSAGE");

           //    dt.Rows.Add(dr);

           //}

            将重新生成的Table赋值给数据控件DataGridView。             

           //dataGridView1.DataSource= dt; 

        }



       public string ExecuteRFM( string i_param)

        {

            //先检查服务器是否连接成功(需要检查服务器是否真正连接),不成功不允许调用参数

           if (connstru.isconnect == false)

            {

               return "正在连接RFM中,请勿重复点击,当然我也不会处理!";//正在连接中,直接返回,不让调用RFC函数

            }

           else

            {

                if(connstru.isconnectsuccessful == false)

               {

                   return "连接RFM失败,请勿重复点击,当然我也不会处理!";//连接失败,直接返回,不让调用RFC函数

               }

            }

           func.SetValue(InputName, i_param);//SAP里面的传入参数

           func.Invoke(rfcDest);

           //returnfunc.GetValue(1).ToString();

           return func.GetString(OutputName);

        }

       //public DataTableGetSAPData(DataTable dt)

       //{

       //    DataTable dtSAPData = new DataTable();

       //    String str1, str2, str3;

       //    try

       //    {

        //        //向数据库中添加字段

       //       dtSAPData.Columns.Add("DATA1", typeof(string));

       //       dtSAPData.Columns.Add("DATA2", typeof(string));

       //       dtSAPData.Columns.Add("DATA3", typeof(string));





       //        string strOrder = null;





       //        IRfcTable tSAP =func.GetTable("IT_ROUTING");



       //        for (int i = 0; i < dt.Rows.Count;i++)

       //        {

       //            str1 = dt.Rows[i][0].ToString();

       //            str2 = dt.Rows[i][1].ToString();

       //            str3 = dt.Rows[i][2].ToString();

       //            IRfcStructure struSAP =tSAP.Metadata.LineType.CreateStructure();

       //            struSAP.SetValue("str1",str1);

       //            struSAP.SetValue("str2",str2);

       //            struSAP.SetValue("str3",str3);

       //            tSAP.Append(struSAP);

       //        }

        //        func.SetValue("INPUT_TABLE",tSAP); //table 参数

        //        func.SetValue("WERKS","A");        //单个参数   

        //        func.SetValue("STATUS","B");    //单个参数

       //        func.Invoke(_rfcDest);

       //        IRfcTable SAPDataTable =func.GetTable("RETURN_TABLE");



       //        for (int i = 0; i s.Name.Equals(name)) != null)//如果数据仓库中找到函数名为name的对象,则不设置函数

            {

                return;

            }

           FFMName = name;

           InputName = inputname;

           OutputName = outputname;

           func_rfm = SetRFM;//泛型func设置了一个返回参数,不能用Acion

           cookie = func_rfm.BeginInvoke((IAsyncResult res) => { this.SetLableText(func_rfm.EndInvoke(cookie)); }, null);//回调函数,当异步调用完成之后触发,第二个参数是传入参数

        }

       private ConnectStruct SetRFM()

        {

           try

            {

               func = rfcrep.CreateFunction(FFMName);//RFM名称

            }

           catch (Exceptionex)

            {

                return new ConnectStruct(true, false, ex.ToString());

            }

           return new ConnectStruct(true, true, "ConnectRFM successful!");

        }

       public void SetConnectandRFM(RfcConfigParameters param)

        {

           Parms = param;

           func_sys = SetConnectandRFM;//泛型func设置了一个返回参数,不能用Acion

           cookie = func_sys.BeginInvoke((IAsyncResult res) => { this.SetLableText(func_sys.EndInvoke(cookie)); }, null);//回调函数,当异步调用完成之后触发,第二个参数是传入参数

        }

       private ConnectStruct SetConnectandRFM()

        {

           try

            {

                rfcDest = RfcDestinationManager.GetDestination(Parms);

               rfcrep = rfcDest.Repository;

               func = rfcrep.CreateFunction(FFMName);//RFM名称

            }

           catch (Exceptionex)

            {

                return new ConnectStruct(true, false, ex.ToString());

            }

           return new ConnectStruct(true, true, "Connectsuccessful!");

        }

       private void SetLableText(ConnectStruct info)

        {

           // InvokeRequiredrequired compares the thread ID of the

           // calling thread tothe thread ID of the creating thread.

           // If these threadsare different, it returns true.

           if (this.form1.InvokeRequired)

            {

                SetTextCallback d = new SetTextCallback(SetLableText);

                this.form1.Invoke(d,new object[] { info });

            }

           else

            {

                this.form1.toolStripStatusLabel1.Text+= "\r\n" + info.connectinfo;

               connstru = info;

            }

        }

    }

}

 

你可能感兴趣的:(C#,SAP,SAP登录界面,读写注册表,HTML/XML解析,动态控件)