C#框架编程动态加载模块

本文系原创,转载请注明出处:

https://blog.youkuaiyun.com/chengbao315/article/details/82874729

在之前分享的博客中,我已经实现了一个静态加载的小框架,这个框架的模块已经在代码中确定,一旦生成程序,模块将无法改变。但在实际应用的大型项目中,我们更倾向于使用动态加载模块的框架,这样对于项目的移植更加灵活和方便,因此今天我就来实现这个效果,和大家一起分享。先看结果展示:

大家看这个动图是不是有种眼花缭乱的赶脚,没办法,优快云只让上传5M的图片。这次的案例会用到数据库设计、数据库编程、动图加载控件、反射、系统ListView控件等等知识,实现比较复杂,所以我打算分两部分来分享这个小框架的实现过程。

一、数据库设计

因为项目比较简单,并且为了便于使用,本案例中我使用了SQLite数据库。在写代码之前大家需要自行下载 System.Data.SQLite.dll 动态库和 sqlitestudio 数据库管理软件,这里就不做介绍了,如果找不到资源,可以给我留言。

数据库的设计也比较简单,我只添加了几个必要字段,主键、模块名称、动态库路径、类名称、使能、排序、时间戳、图标,如图所示:

以上设计就可以满足我要实现的功能了,如有需要,大家可以自行扩展。接下来需要准备DBHelper.cs,网上版本也是一大堆,我这里贴出一版供参考,看代码:

  public class SQLiteHelper
    {
        private static string dbFile = "myFrame.db";
      
        //生成连接字符串
        private static string CreateConnectionString()
        {
            SQLiteConnectionStringBuilder connectionString = new SQLiteConnectionStringBuilder();
            connectionString.DataSource = @"Data/" + dbFile;
 
            string conStr = connectionString.ToString();
            return conStr;
        }
 
        /// <summary>
        /// 对插入到数据库中的空值进行处理
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static object ToDbValue(object value)
        {
            if (value == null)
            {
                return DBNull.Value;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        /// 对从数据库中读取的空值进行处理
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static object FromDbValue(object value)
        {
            if (value == DBNull.Value)
            {
                return null;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        /// 执行非查询的数据库操作
        /// </summary>
        /// <param name="sqlString">要执行的sql语句</param>
        /// <param name="parameters">参数列表</param>
        /// <returns>返回受影响的条数</returns>
        public static int ExecuteNonQuery(string sqlString, params SQLiteParameter[] parameters)
        {
            string connectionString = CreateConnectionString();
            using (SQLiteConnection conn = new SQLiteConnection(connectionString))
            {
                conn.Open();
                using (SQLiteCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = sqlString;
                    foreach (SQLiteParameter parameter in parameters)
                    {
                        cmd.Parameters.Add(parameter);
                    }
                    return cmd.ExecuteNonQuery();
                }
            }
        }
 
        /// <summary>
        /// 执行查询并返回查询结果第一行第一列
        /// </summary>
        /// <param name="sqlString">SQL语句</param>
        /// <param name="sqlparams">参数列表</param>
        /// <returns></returns>
        public static object ExecuteScalar(string sqlString, params SQLiteParameter[] parameters)
        {
            string connectionString = CreateConnectionString();
            using (SQLiteConnection conn = new SQLiteConnection(connectionString))
            {
                conn.Open();
                using (SQLiteCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = sqlString;
                    foreach (SQLiteParameter parameter in parameters)
                    {
                        cmd.Parameters.Add(parameter);
                    }
                    return cmd.ExecuteScalar();
                }
            }
        }
 
        /// <summary>
        /// 查询多条数据
        /// </summary>
        /// <param name="sqlString">SQL语句</param>
        /// <param name="parameters">参数列表</param>
        /// <returns>返回查询的数据表</returns>
        public static DataTable GetDataTable(string sqlString, params SQLiteParameter[] parameters)
        {
            string connectionString = CreateConnectionString();
            using (SQLiteConnection conn = new SQLiteConnection(connectionString))
            {
                conn.Open();
                using (SQLiteCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = sqlString;
                    foreach (SQLiteParameter parameter in parameters)
                    {
                        cmd.Parameters.Add(parameter);
                    }
                    DataSet ds = new DataSet();
                    SQLiteDataAdapter adapter = new SQLiteDataAdapter(cmd);
                    adapter.Fill(ds);
                    return ds.Tables[0];
                }
            }
        }
    }

好了,以上我们的数据库准备就基本OK了,下面看模块动态库的准备。

二、准备加载模块

看过前两期文章的朋友应该知道,我之前的模块是与主程序放在一个项目中的,但实际应用中通常模块是分开的,这里我需要新建一个动态库项目,将之前的代码文件添加进去即可。没有做过之前项目的朋友也一样,在新建的动态库项目中新建几个模块界面即可。

如图所示,ModuleLib 就是我的模块动态库项目,里面添加了3个模块,大家根据自己需要设计。

三、设计配置页

先来看设计效果吧:

界面设计也非常简单一个显示的Table,三个增、删、改按钮和两个确认、取消按钮就OK了,关于这个Table,有很多第三方的控件可以支持,我用过的有FarPoint、Dev GridControl等等,非常方便,大家也可以自行设计。这里为了简单实现,尽量不要其他工具,我使用了系统自带的 ListView 控件。

使用 ListView 控件需要配置以下几个属性:

1. Columns(表头)

2. View (设置成Details样式)

3. GridLines(表格线)

设置完这些,我们的界面设计就基本OK了,其他按钮添加背景图片和文字就可以了。在这个配置页点击新增和编辑按钮都会弹出一个编辑页,下面需要设计一下这个界面。

四、设计编辑页

先看效果图:

这个界面就更简单了,只需要几个文本框、下拉框、按钮即可,这里就不做讲解了。强调一下就是下拉框的 DropDownStyle 属性要设置成 DropDownList,不允许编辑的状态。

 

 

 

以上我完成了界面的设计部分,下面我接着来讲具体的代码实现。先来看模块配置页面的实现,看代码:

        private void LoadItem()
        {
            string sql = "select * from Frame order by SortNum";
            DataTable dt = SQLiteHelper.GetDataTable(sql);
            for (int i = 0; i < dt.Rows.Count;i++ )
            {
                ModuleClass Module = new ModuleClass();
                Module.ModuleId = dt.Rows[i]["ModuleId"].ToString();
                Module.ModuleName = dt.Rows[i]["ModuleName"].ToString();
                Module.LibPath = dt.Rows[i]["LibraryPath"].ToString();
                Module.Moduleclass = dt.Rows[i]["ModuleClass"].ToString();
                Module.SortNum = int.Parse(dt.Rows[i]["SortNum"].ToString());
                Module.Enable = dt.Rows[i]["Enable"].ToString() == "True";
                Module.Image = dt.Rows[i]["img"].ToString();
 
                this.AddItem(Module);
            }
        }
首先页面加载进来时需要读取数据库,读取现在已经存在的模块数据,这里我用一个方法实现,直接编写sql ,调用dbhelper的查询方法即可。 同时我习惯采用的方式是实体编程,将查询到的结果赋值给一个实体,然后对实体进行操作。当然,你也可以直接控件赋值或者声明一个table结构赋值,看个人喜好。界面的操作代码如下:
        private void AddItem(ModuleClass module)
        {
            System.Windows.Forms.ListViewItem listViewItem = new System.Windows.Forms.ListViewItem(new string[] {
            module.ModuleName,
            module.Enable.ToString(),
            module.LibPath,
            module.Moduleclass,
            module.SortNum.ToString()}, int.Parse(module.Image));
            if (module.ModuleId != null)
            {
                listViewItem.Tag = module.ModuleId;
            }
            else
            {
                listViewItem.Tag = module;
            }
            this.listView1.Items.Add(listViewItem);
        }
 

ListView控件添加一个Item,Item里是需要显示的数据,这里注意一下,我将实体赋给了Item的Tag 属性,这里用于后面编辑时获取选择项目所用,就不需要重新从ListView界面取值了。之后给按钮添加点击事件,添加和编辑按钮事件处理函数中弹出编辑页,而删除按钮事件处理函数中删除数据即可,这里不做解释,下面来看编辑页代码:
        public FrmEditModule()
        {
            InitializeComponent();
            for (int i = 0; i < Resources.ImageList.Images.Count; i++)
            {
                this.cbxImg.Items.Add(i.ToString());
            }
        }
 
        public FrmEditModule(ModuleClass m)
        {
            InitializeComponent();
            for (int i = 0; i < Resources.ImageList.Images.Count; i++)
            {
                this.cbxImg.Items.Add(i.ToString());
            }
 
            this.tbxName.Text = m.ModuleName;
            this.tbxLib.Text = m.LibPath;
            //加载程序集(dll文件地址),使用Assembly类   
            Assembly assembly = Assembly.LoadFile(this.tbxLib.Text);
            //获取类型,参数(名称空间+类)   
            Type[] types = assembly.GetTypes();
            foreach (Type type in types)
            {
                if (type.BaseType.Name == "UserControl")
                {
                    this.cbxClass.Items.Add(type.Name);
                }
            }
            this.cbxClass.Text = m.Moduleclass;
            this.tbxNum.Text = m.SortNum.ToString();
            this.cbxEnable.Text = "true";
            this.cbxImg.SelectedIndex = int.Parse(m.Image);
        }

编辑页中我设计了两个构造函数,不带参数的用于添加控件时使用,如果不需要显示图标,构造函数中可以什么都不做。第二个带有实体参数的构造函数用于编辑控件时使用,对界面进行显示赋值。可以看到第二个构造函数中运用了反射的方式获取模块类。

剩下的控件事件就比较简单了,直接看代码不解释:

        // 确认按钮事件
        private void btnOk_Click(object sender, EventArgs e)
        {
            this.Module = new ModuleClass();
            if (this.tbxName.Tag != null)
            {
                Module.ModuleId = this.tbxName.Tag.ToString();
            }
            Module.ModuleName = this.tbxName.Text;
            Module.LibPath = this.tbxLib.Text;
            Module.Moduleclass = this.cbxClass.Text;
            Module.SortNum = int.Parse(this.tbxNum.Text);
            Module.Enable = bool.Parse(this.cbxEnable.Text);
            Module.Image = this.cbxImg.SelectedIndex.ToString();
            this.DialogResult = System.Windows.Forms.DialogResult.OK;
        }
 
        // 取消按钮事件
        private void btnCancel_Click(object sender, EventArgs e)
        {
            this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
        }
 
        // 选择动态库按钮点击事件
        private void btnselect_Click(object sender, EventArgs e)
        {
            OpenFileDialog diag = new OpenFileDialog();
            diag.Filter = "dll文件|*.dll";
            if (diag.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                this.tbxLib.Text = diag.FileName;
                //加载程序集(dll文件地址),使用Assembly类   
                Assembly assembly = Assembly.LoadFile(this.tbxLib.Text);
                //获取类型,参数(名称空间+类)   
                Type[] types = assembly.GetTypes();
                foreach (Type type in types)
                {
                    if (type.BaseType.Name == "UserControl")
                    {
                        this.cbxClass.Items.Add(type.Name);
                    }
                }
            }
        }
这里我想着重介绍一下图标选择框的处理方法,正常的comboBox 控件中是没有图片只显示文本的,但是这里做成了图片和文字同时下拉显示的效果,comboBox 控件需要注册一个DrawItem 事件,在事件处理函数中进行重绘操作,代码如下:

        private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
        {
            Graphics g = e.Graphics;
            Rectangle r = e.Bounds;
            Size imageSize = Resources.ImageList.ImageSize;
            Font fn = null;
            if (e.Index >= 0)
            {
                fn = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
                string s = (string)cbxImg.Items[e.Index];
                StringFormat sf = new StringFormat();
                sf.Alignment = StringAlignment.Near;
                if (e.State == (DrawItemState.NoAccelerator | DrawItemState.NoFocusRect))
                {
                    //画条目背景 
                    e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(0, 64, 64)), r);
                    //绘制图像 
                    Resources.ImageList.Draw(e.Graphics, r.Left, r.Top, e.Index);
                    //显示字符串 
                    e.Graphics.DrawString(s, fn, new SolidBrush(Color.Black), r.Left + imageSize.Width, r.Top);
                    //显示取得焦点时的虚线框 
                    e.DrawFocusRectangle();
                }
                else
                {
                    e.Graphics.FillRectangle(new SolidBrush(Color.LightBlue), r);
                    Resources.ImageList.Draw(e.Graphics, r.Left, r.Top, e.Index);
                    e.Graphics.DrawString(s, fn, new SolidBrush(Color.Black), r.Left + imageSize.Width, r.Top);
                    e.DrawFocusRectangle();
                }
            }
        }

最后回到主页面,主界面需要添加一个动态加载按钮和module的方法。我的思路是直接从数据库读取module 信息,根据module 数量决定添加按钮的数目,并通过反射获取module 对应的实体控件在主界面显示出来,代码如下

        private void LoadModule()
        {
            this.panel3.Controls.Clear();
            string sql = "select * from Frame where Enable = 1 order by SortNum";
            DataTable dt = SQLiteHelper.GetDataTable(sql);
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                ModuleClass Module = new ModuleClass();
                Module.ModuleId = dt.Rows[i]["ModuleId"].ToString();
                Module.ModuleName = dt.Rows[i]["ModuleName"].ToString();
                Module.LibPath = dt.Rows[i]["LibraryPath"].ToString();
                Module.Moduleclass = dt.Rows[i]["ModuleClass"].ToString();
                Assembly assembly = Assembly.LoadFile(Module.LibPath);
                //获取类型,参数(名称空间+类)   
                Type[] types = assembly.GetTypes();
                foreach (Type type in types)
                {
                    if (type.Name == Module.Moduleclass)
                    {
                        Module.Moduleclass = type.FullName;
                    }
                }
                myFrameModule.AppleKey btn = new myFrameModule.AppleKey();
                btn.BackColor = System.Drawing.Color.Transparent;
                btn.BorderColor = System.Drawing.Color.FromArgb(0, 254, 252);
                btn.FlatAppearance.BorderSize = 0;
                btn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
                btn.ForeColor = System.Drawing.Color.Cyan;
                btn.Location = new System.Drawing.Point(45 + i *100, 23);
                btn.Name = dt.Rows[i]["ModuleId"].ToString();
                btn.Size = new System.Drawing.Size(80, 33);
                btn.Text = dt.Rows[i]["ModuleName"].ToString();
                btn.Type = myFrameModule.AppleKeyType.Button;
                btn.Click += new EventHandler(btn_Click);
                btn.Tag = Module;
                this.panel3.Controls.Add(btn);
            }
        }
讲到这里我们的动态加载框架就基本实现了,这是我最近研究出来的一点小成果,欢迎各路朋友批评指教,如有疑问,欢迎留言,再次感谢大家支持!

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值