技术内容
- 数据库:SQLITE。以SQLITE作为数据库,具有下支持XP,不用安装SQL等好处
- CODE FIRST:配置自由,不受建立数据库之累
- EF:使用方便,代码简单 DEVEXPRESS:界面美观,功能强大
- 多窗体:以XtraTabControl作为容器,支持多窗体加载
- gridControl:列表功能强大
- 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; }
添加窗体控件并绑定
- FrmNew窗体中添加控件两个TEXTBOX ,一个errorprovider错误提示控件,一个databindingsource控件,一个保存按钮
- 明一个DBTestClass 变量dbTestClass并实例化
- FORMLOAD中将bindingsource的datasource设成这个dbTestClass对象
- 把TEXTBOX的属性面板中,设备高级数据绑定,text绑到bindingsource的name与age上,age格式设为数字
- 保存按钮编写事件:
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中调整列的表现形式。
表格右键菜单
- 添加barManager1,这是用于管理devexpress的工具栏、菜单栏、右键菜单、状态栏的
- 添加popupMenu1,进入designer,添加项(删除、修改、刷新),添加事件处理程序
- 在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);
}
}
本窗体可以作为添加记录窗体,也可以做为修改记录窗体。
- 声明几个变量:
public int id; //作为要修改记录的ID
public DBTestClass dbTestClass;
public bool isNew = true; //是添加还是修改
//然后在保存记录事件处理方法中,判断该记录是添加还是修改,执行不同的操作。
- 有时需要在本窗体中添加一个退出功能,实现子窗体的自我关闭:
//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、图标设置
- 找一个图标PNG文件,256X256以上尺寸,在http://ico.duduxuexi.com/网站生成ICO文件,复制到项目中。
- 主窗体ICO设为此ICO
- 项目属性中在“应用程序”面板,图标和清单,将ICO文件设好,这个图标即是程序运行时的图标
2、特定文件与生成
在项目中添加部分文件:lincens.txt文件写好,这是安装时的用户协议文件,readme或help文件写好,dotNetFx40_Client_x86_x64.exe文件也复制到项目中。三个文件的属性面板中,生成操作设为“无”,复制到输出目录设为“如果较新就复制”
执行“生成项目”在debug目录生成程序文件
3、NSIS安装脚本
用HM NIS EDIT生成安装脚本,完善内容,并添加.net检测,保存脚本到debug目录,脚本选项选中“转为相对路径”
编译、安装看一下。没问题的话,脚本文件复制到项目目录,同样生成操作设为“无”,复制到输出目录设为“如果较新就复制”,这样在debug目录清空的情况下,每次生成还会复制过去。以后再修改程序,最多改一下文件列表