一个用DEVEXPRESS+SQLITE+EF+CODEFIRST做的WINFORM多窗体框架小程序

技术内容

  1. 数据库:SQLITE。以SQLITE作为数据库,具有下支持XP,不用安装SQL等好处
  2. CODE FIRST:配置自由,不受建立数据库之累
  3. EF:使用方便,代码简单 DEVEXPRESS:界面美观,功能强大
  4. 多窗体:以XtraTabControl作为容器,支持多窗体加载
  5. gridControl:列表功能强大
  6. NSIS打包:最常用的打包工具

一、新建DEVEXPRESS的RIBBON based项目

新项目为了兼容XP,合用.net4.0,安装其它组件时注意依赖情况

1、安装SQLITE FOR EF

安装如下 Package:
System.Data.SQLite
SQLite.CodeFirstSy

2、添加EF

项目中添加ADO.NET实体数据,选空CODE FIRST模型,创建数据上下文 MYDB.cs。
OnModelCreating方法必须被重写,以建立数据表格,这一步在用LOCALDB时是不用的
另外,因为EF解释contains或indexOf成的CHARINDEX函数在sqlite里面并不支持,所以要加一个拦截器实现函数

public class MYDB : DbContext
{
   public MYDB() : base("name = MYDB")
   {
       System.Data.Entity.Infrastructure.Interception.DbInterception.Add(new SqliteInterceptor());
   }       

   protected override void OnModelCreating(DbModelBuilder modelBuilder)
   {
       var initializer = new SqliteDropCreateDatabaseWhenModelChanges<MYDB>(modelBuilder); //模型变更时重建数据库
       Database.SetInitializer(initializer);
   }
   public DbSet<Person> People { get; set; }
}

拦截器的类SqliteInterceptor

 public class SqliteInterceptor : IDbCommandInterceptor
{
private static Regex replaceRegex = new Regex(@"\(CHARINDEX\((.*?),\s?(.*?)\)\)\s*?>\s*?0");

public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}

public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}

public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
}

public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
    ReplaceCharIndexFunc(command);
}

public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
}

public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
    ReplaceCharIndexFunc(command);
}

private void ReplaceCharIndexFunc(DbCommand command)
{
    bool isMatch = false;
    var text = replaceRegex.Replace(command.CommandText, (match) =>
    {
        if (match.Success)
        {
            string paramsKey = match.Groups[1].Value;
            string paramsColumnName = match.Groups[2].Value;
            //replaceParams
            foreach (DbParameter param in command.Parameters)
            {
                if (param.ParameterName == paramsKey.Substring(1))
                {
                    param.Value = string.Format("%{0}%", param.Value);
                    break;
                }
            }
            isMatch = true;
            return string.Format("{0} LIKE {1}", paramsColumnName, paramsKey);
        }
        else
            return match.Value;
    });
    if (isMatch)
        command.CommandText = text;
}
}

修改程序配置 App.config

<entityFramework>
  ...
    <providers>    
    <!--原invariantName="System.Data.SQLite.EF6"改为invariantName="System.Data.SQLite"-->
      <provider invariantName="System.Data.SQLite" (type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />    
      ....  
      <!--connectionString要改动一下,只有data source和 providerName-->
  <connectionStrings>
    <add name="MYDB" connectionString="data source=.\sampledb.db" providerName="System.Data.SQLite" />
  </connectionStrings> 
</configuration>

二、添加spire.doc

nuget包中添加freespire.doc支持,可以实现doc输出功能

三、完善界面

1、ribbon工具栏增按钮

新增三个LARGE按钮,设好图标,功能btnList显示列表,btnNew新建记录,btnPrint打印列表

2、添加devexpress的tabcontrol控件作为窗体容器

拖一个imagecollection控件,设上一组图片,用于在tab左侧显示小图
设为dock,填充满窗体
ClosePageButtonShowMode属性设为InAllTabPageHeaders,在所有tab页上显示关闭按钮
ShowTabHeader设为TRUE显示tab头

3、添加两个方法,功能是在tab中打开或显示子窗口

窗口如果已加载,就切换过去,如果没有加载,就新建立一个子窗口

/// <summary>
/// 在指定标签页内打开某个窗体 
/// </summary>
/// <param name="tabText">标签名</param>
/// <param name="classType">标签内的窗体类型</param>
public void Add_TabPage(string tabText, Type classType)
{
    if (!this.openTabCheck(this.tabMdiContener, tabText))
    {
        var myForm=(DevExpress.XtraEditors.XtraForm)classType.Assembly.CreateInstance(classType.FullName);
        var tabpage = this.tabMdiContener.TabPages.Add(tabText);
        //tabpage.Appearance.Header.Font = new Font("Tahoma", 9.5F);
        tabpage.Appearance.Header.Options.UseFont = true;
        tabpage.ImageOptions.Image = (Image)imageCollection1.Images[0]; //为标签头设置图标

        this.tabMdiContener.SelectedTabPage = tabpage;
        myForm.FormBorderStyle = FormBorderStyle.None;
        myForm.TopLevel = false;
        myForm.Width = tabpage.Width;
        myForm.Height = tabpage.Height - 25;
        myForm.Show();
        myForm.Parent = tabpage;
        myForm.Anchor = (AnchorStyles)((AnchorStyles.Top | AnchorStyles.Bottom) | AnchorStyles.Left) | AnchorStyles.Right; //随主窗体产生自动变化
    } 
}

/// <summary>
/// 检查是否已打开同名标签
/// </summary>
/// <param name="tabControl"></param>
/// <param name="tabName"></param>
/// <returns></returns>
private bool openTabCheck(XtraTabControl tabControl, string tabName) //看tabpage中是否已有窗体
{
   for (int i = 0; i < tabControl.TabPages.Count; i++)
   {
       if (tabControl.TabPages[i].Text == tabName)
       {
           tabControl.SelectedTabPageIndex = i;
           return true;
       }
   }
   return false;
}

4、为tabcontrol的CloseButtonClick添加代码:

private void tabMdiContener_CloseButtonClick(object sender, EventArgs e)
{
 var EArg = (DevExpress.XtraTab.ViewInfo.ClosePageButtonEventArgs)e; 
 string name = EArg.Page.Text;//得到关闭的选项卡的text
 foreach (XtraTabPage apage in this.tabMdiContener.TabPages)//遍历得到和关闭的选项卡一样的Text
 {
     if (apage.Text == name)
     {
         ((Form)apage.Controls[0]).Close(); //关闭子窗体     
         tabMdiContener.TabPages.Remove(apage);
         apage.Dispose();
         return;
     }
 }  
}

5、其它按钮事件绑定

btnList.click:加载frmlist窗体作为子窗口,显示列表

  private void barbtnList_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
{
    this.Add_TabPage("核算记录",typeof(FrmList));    
}

btnNew.click与btnPrint.click不再赘述

四、添加其它窗体

1、添加新建记录窗口 FrmNew

本窗口实现对象数据的添加

写好POCO类

选写一个POCO类:

  public class DBTestClass
  {
      public int ID { get; set; }
      public string name { get; set; }
      public int age { get; set; }
  } 

在MYDB.CS中注册

public virtual DbSet<DBTestClass> DBTestClasses { get; set; }

添加窗体控件并绑定

  1. FrmNew窗体中添加控件两个TEXTBOX ,一个errorprovider错误提示控件,一个databindingsource控件,一个保存按钮
  2. 明一个DBTestClass 变量dbTestClass并实例化
  3. FORMLOAD中将bindingsource的datasource设成这个dbTestClass对象
  4. 把TEXTBOX的属性面板中,设备高级数据绑定,text绑到bindingsource的name与age上,age格式设为数字
  5. 保存按钮编写事件:
private void btnSave_Click(object sender, EventArgs e)
  {

       if (this.texbox1.Text == string.Empty)
       {
           errorProvider1.SetError(this.txbCompanyName, "不得为空!");
           return;
       }
      
       this.bindingSource1.EndEdit();
       db.DBTestClass.Add(this.dbTestClass);
       db.SaveChanges();     
       MessageBox.Show("保存成功!");
        
   }

2、添加一个显示列表窗体FrmList

该窗体实现列表显示数据库内容,可以删除、弹窗修改记录、重新加载数据

放置gridcontrol,为gridview设置属性:

   this.gridView1.OptionsBehavior.Editable = false; //修改不保存
   this.gridView1.OptionsBehavior.ReadOnly = true; //禁止单元格进入编辑状态
   this.gridView1.OptionsCustomization.AllowFilter = false; //禁止用户过滤数据
   this.gridView1.OptionsFind.AlwaysVisible = true;   //显示查找面板
   this.gridView1.OptionsFind.FindNullPrompt = "输入搜索...";  //查找框提示
   this.gridView1.OptionsFind.ShowClearButton = false; //不显示clear按钮
   this.gridView1.OptionsFind.ShowFindButton = false;  //不显示查找按钮
   this.gridView1.OptionsView.ShowGroupPanel = false;  //不显示行列拖曳提示          
   this.gridView1.OptionsMenu.EnableColumnMenu=false;//禁止点击列头弹出的菜单
   this.gridView1.OptionsCustomization.AllowFilter=false;//禁止列头过滤器
   this.gridView1.OptionsSelection.EnableAppearanceFocusedCell = false; //禁止选中的单元格高高
   this.gridView1.OptionsSelection.EnableAppearanceFocusedRow = false; //禁止选中行高亮(仅显示外围框线)

如果希望通过gridview的editform来进行编辑:

editform是devexpress中gridcontrol的一个修改行数据的行为模式,避免再开一个窗体修改数据,尤其是数据格式比较简单的时候。
开启editform模式:

  this.gridView1.OptionsBehavior.AllowAddRows = DevExpress.Utils.DefaultBoolean.True;
  this.gridView1.OptionsBehavior.EditingMode = DevExpress.XtraGrid.Views.Grid.GridEditingMode.EditFormInplace;
  this.gridView1.OptionsBehavior.EditorShowMode = DevExpress.Utils.EditorShowMode.Click;

另外,如果在加载表格数据时,需要对数据进行过滤,需要将过滤出来的数据重新构造bindinglist,然后绑室databindingsource。

this.objBindingSource.DataSource = new BindingList<DBTestClass>(db.DBTestClass.Where(p => p.CompanyID == this.companyID).ToList());

但这样一来,新增的行数据无法保存,需要另外进行处理,比如在editform的rowupdated事件中添加处理方法:

private void gridView1_RowUpdated(object sender, DevExpress.XtraGrid.Views.Base.RowObjectEventArgs e)
{
 this.objBindingSource.EndEdit();
 var p = (DBTestClass)e.Row;
 if (p.ID == 0) //如果是新增元素,则ID自增量KEY肯定是零,所以可以通过它进行add操作。
 {
     db.DBTestClass.Add(p);       
 }
 db.SaveChanges();
}

显示主从表数据

列表中有时会有一些字段是集合型,比如“部门员工”employee字段,如果想显示部分员工名字,就需要写一下view的CustomColumnDisplayText事件

private void gridView1_CustomColumnDisplayText(object sender, DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventArgs e)
 {
     if (e.Column.FieldName == "employee")
     {
         var b = e.Value;
         if (b == null) return;
         var d = (HashSet<employee>)b;
         e.DisplayText = "";
         foreach (var f in d)
         {
             e.DisplayText = f.name + ",";
         }
     }
         
 }

或是在数据源初始化时就把员工姓名提取出来放在字段中。

数据绑定

选中GRID,右上角,data source wizard,按向导设置好数据绑定,我一般选择通过bindingsource进行绑定。系统自动在窗体初始化代码中加入数据绑定代码。
在designer中调整列的表现形式。

表格右键菜单

  1. 添加barManager1,这是用于管理devexpress的工具栏、菜单栏、右键菜单、状态栏的
  2. 添加popupMenu1,进入designer,添加项(删除、修改、刷新),添加事件处理程序
  3. 在gridview的RowClick中添加右键弹出菜单功能:
private void gridView1_RowClick(object sender, DevExpress.XtraGrid.Views.Grid.RowClickEventArgs e)
{     
  if (e.Button == MouseButtons.Right && ModifierKeys == Keys.None)
  {
      Point p = new Point(Cursor.Position.X, Cursor.Position.Y);
      popupMenu1.ShowPopup(p);
  }    
}

本窗体可以作为添加记录窗体,也可以做为修改记录窗体。

  1. 声明几个变量:
	public int id; //作为要修改记录的ID
	public DBTestClass dbTestClass;
	public bool isNew = true; //是添加还是修改
//然后在保存记录事件处理方法中,判断该记录是添加还是修改,执行不同的操作。
  1. 有时需要在本窗体中添加一个退出功能,实现子窗体的自我关闭:
//btnclose.click:
 var parentTabCtl = (DevExpress.XtraTab.XtraTabControl)this.Parent.Parent;
 var page = parentTabCtl.SelectedTabPage;
 parentTabCtl.TabPages.Remove(page);
 this.Close();

如果在建立或修改记录时需要用到一对多,比如为部门depart选择员工employee,可以用checkedcomboboxedit控件。
在修改时需要初始化赋值(假设部门员工列表存在(list)depart.employee中:

this.checkboxdepart.Properties.DataSource= db.employee.ToList();           
this.checkboxdepart.Properties.DisplayMember = "name";
this.checkboxdepart.Properties.ValueMember = "ID";
//SetEditValue方法可以接受一个逗号分割的字符串,根据这个字串去解析valuemember字段,根据情况进行选择状态初始化
this.checkboxdepart.SetEditValue(string.Join(",", this.depart.employee.Select(p => p.ID).ToArray()));

读取选择结果checkboxDepart_EditValueChanged事件:

if (this.checkboxdepart.EditValue == this.checkboxdepart.OldEditValue) return;
   ///更新开发人员
   var selectedMen=this.checkboxdepart.EditValue.ToString().Split(',');            
   this.depart.employee = new List<employee>();
   foreach(var menid in selectedMen)
   {
       this.depart.employee.Add(db.employee.Find(int.Parse(menid)));
   }
}

3、添加输出窗口FrmPrint

 public class PrintRst
    {
        public OtherCost otherCost;

        public void Print()
        {
            if (this.otherCost == null) return;
            startPrint();
        }
        public void Print(int id)
        {
            using (var db = new MYDB())
            {
                var cost = db.OtherCosts.Find(id);
                if (cost == null) return;

                this.otherCost = cost;
            }

            startPrint();
        }

        private void startPrint()
        {
            Document document = new Document();
            Section section = document.AddSection();
            section.AddParagraph(); //增加段落
            var paraTitle = section.AddParagraph();
            paraTitle.Format.HorizontalAlignment = Spire.Doc.Documents.HorizontalAlignment.Center;
            TextRange txtRange = paraTitle.AppendText("核算报表");
            txtRange.CharacterFormat.FontName = "宋体";
            txtRange.CharacterFormat.Bold = true;
            txtRange.CharacterFormat.FontSize = 24;
            section.AddParagraph();
            addText(section, "名称", otherCost.CompanyName);
            addText(section, "联系人", otherCost.CompanyName);
           //............

            //文件输出
            var docFilename = @"WordTable" + GetTimeStamp() + ".docx";
            document.SaveToFile(docFilename);

            System.Diagnostics.Process.Start(docFilename);
        }
        private void addText(Section section, string desc, string value)
        {
            var paragraph = section.AddParagraph();
            paragraph.Format.BeforeSpacing = 20;
            var txtRange = paragraph.AppendText(desc + ":" + value);
            txtRange.CharacterFormat.FontName = "宋体";
            txtRange.CharacterFormat.FontSize = 16;
        }

        private string GetTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }
    }

五、打包

1、图标设置

  1. 找一个图标PNG文件,256X256以上尺寸,在http://ico.duduxuexi.com/网站生成ICO文件,复制到项目中。
  2. 主窗体ICO设为此ICO
  3. 项目属性中在“应用程序”面板,图标和清单,将ICO文件设好,这个图标即是程序运行时的图标

2、特定文件与生成

在项目中添加部分文件:lincens.txt文件写好,这是安装时的用户协议文件,readme或help文件写好,dotNetFx40_Client_x86_x64.exe文件也复制到项目中。三个文件的属性面板中,生成操作设为“无”,复制到输出目录设为“如果较新就复制”
执行“生成项目”在debug目录生成程序文件

3、NSIS安装脚本

用HM NIS EDIT生成安装脚本,完善内容,并添加.net检测,保存脚本到debug目录,脚本选项选中“转为相对路径”
编译、安装看一下。没问题的话,脚本文件复制到项目目录,同样生成操作设为“无”,复制到输出目录设为“如果较新就复制”,这样在debug目录清空的情况下,每次生成还会复制过去。以后再修改程序,最多改一下文件列表

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值