c#winform中的界面开发大全


WinForm 界面开发之模块化分合 本文继续 WInform 界面开发系列的介绍,主要针对性介绍 Winform 开发过程中,经 常用到的模块:数据字典模块、参数配置模块、权限管理模块的模块化应用,以及各模 块的分合之道。 1 、数据字典模块 我们知道,一般程序基本上都涉及到了一个是数据参考用途的数据字典模块,不管是 Web 还是 Winform 的,这个模块基本上是必不可少的, Winform 的界面效果大致如 下所示: 本模块的数据字典支持无限级树形分层应用,可以添加大类,字典项目等数据, 系统 集成只需要拷贝相关的字典表即可实现集成。2 、参数配置模块 在程序中, 一般应用就是通过代码把相关的内容进行转义保存或者解析,以便呈现给用 户更好的数据展示效果,这个模块比较通用,可分可合。 另外一个也比较常用的就是参数的配置管理模块, 一般如果参数比较少, 直接使用 .NET 的配置保存功能即可,如下图所示。 但这种方式只能保存比较少的内容,对于比较复杂的配置,一般很少采用这种模式存放 程序的参数信息,这种方式存储的参数,如果不是放到一个独立的文件中,还存在一个 不同步的现象。 其实我们还是可以把参数的配置功能作为一个独立的模块进行处理, 我的程序就是经常 这么干的,而且由于是相对比较独立,并在设计时候就支持参数的编辑及展示功能,因 此效率大大提高, 对使用用户来说, 由于修改界面比较统一, 而且参数的说明等很丰富, 因此用户修改系统的配置参数友好性大大增强,界面效果如下所示: 使用用户看到的参数配置界面效果:开发者在设计时刻的参数配置界面如下所示:这些参数最终目的是为了程序的读取和调用, 调用代码非常简单,而且由于是强类型的 属性以及良好的提示功能,应该是比较方便的,示例代码如下所示: 代码 double hours = span.Hours; hours = (hours < SystemConfig.Default.KFPartAt LeaseHoure) ? SystemConfig.Default.KFPartAtLeaseHour e : hours; // 最小的钟点房计费 if (span.Minutes < 60 && span.Minutes >= Syste mConfig.Default.KFPartHourMinutes) { hours += 1; } else if (span.Minutes > SystemConfig.Default.K FPartHalfHourMinte && span.Minutes < SystemConfig.Default.KFPart HourMinutes) { hours += 0.5 ; } 其中的 SystemConfig.Default.KFPartAtLeaseHoure 就是参数的名称了, 根据 这个名称就可以读取和设置该参数的值。 该参数配置模块是一个 Visual Studio 的 Addin 插件,因此可以在设计时刻提供参数 的添加、修改、删除等支持,非常方便,而且也能在发布后给用户界面提供修改,是一 个独立的模块。 3 、权限管理模块 最后说说第三个模块,权限管理模块,我们知道,一般对于进销存或者稍微复杂一点的 系统,都需要最基本的权限控制模块,以便控制不同用户的访问功能,这个可以做成独 立的应用程序进行管理,如下图所示:程序是一个独立的程序, 但是权限系统需要和业务应用系统结合一起才有价值,因此它 们之间的结合,一个除了数据库的整合(把权限系统需要的表整合一起),还需要在系 统的代码中进行整合(实现功能模块的登陆及权限控制等)。登陆验证如下所示: try { string loginName = this .cmbzhanhao.Text.Trim(); User userBLL = new User(); string identity = userBLL.VerifyUser(loginNam e, this .tbPass.Text, Guid.NewGuid().ToString()); if (! string .IsNullOrEmpty(identity)) { UserInfo info = userBLL.GetUserByName(loginName); #region 获取用户的功能列表 Function functionBLL = new Function(); List list = functionBLL.GetF unctionsByUser(info.ID, "HOTELMS" ); if (list != null && list.Count > 0) { foreach (FunctionInfo functionInfo i n list) { if (!Portal.gc.FunctionDict.Contai nsKey(functionInfo.ControlID)) { Portal.gc.FunctionDict.Add(fun ctionInfo.ControlID, functionInfo); } } } #endregion bLogin = true ; Portal.gc.LoginInfo = info; this .DialogResult = DialogResult.OK; } else { MessageUtil.ShowTips( " 用户帐号密码不正确 " ); this .tbPass.Text = "" ; // 设置密码为空 } } catch (Exception err) { MessageUtil.ShowError(err.Message); } 用户功能权限认证如下所示: #region KTV 包间管理 if (Portal.gc.HasFunction( "KTV" )) { OutlookBarBand myBasicBand = new OutlookBarBan d( "KTV 包间管理 " ); myBasicBand.SmallImageList = this .imageList; myBasicBand.LargeImageList = this .imageList; if (Portal.gc.HasFunction( "KTV/View" )) { myBasicBand.Items.Add( new OutlookBarItem( " KTV 包间状态视图 " , 0)); } if (Portal.gc.HasFunction( "KTV/Setting" )) { myBasicBand.Items.Add( new OutlookBarItem( " KTV 包间设置 " , 1)); } if (Portal.gc.HasFunction( "KTV/Goods" )) { myBasicBand.Items.Add( new OutlookBarItem( " KTV 商品设置 " , 3)); } if (Portal.gc.HasFunction( "KTV/Waiter" )) { myBasicBand.Items.Add( new OutlookBarItem( " KTV 服务生管理 " , 4)); } if (Portal.gc.HasFunction( "KTV/BookIn" )) { myBasicBand.Items.Add( new OutlookBarItem( " KTV 预订管理 " , 5)); } if (Portal.gc.HasFunction( "KTV/OtherIncome" )) { myBasicBand.Items.Add( new OutlookBarItem( " KTV 其他款项登记 " , 6)); } myBasicBand.Background = SystemColors.AppWorks pace; myBasicBand.TextColor = Color.White; outlookBar1.Bands.Add(myBasicBand); } #endregion 至此,三个常用而且比较独立模块介绍完毕,这几种场景你用了几个呢,如果觉得有价 值,欢迎一起讨论切磋。开发之道,顺手拈来;分合之需, 顺其自然;优码不语,润物无声; winform 复选框控件赋值的小技巧 Posted on 2010-02-07 16:36 伍华聪 阅读 (1909) 评论 (5) 编辑 收藏 前几天,有一位园友写了一篇不错的文章《 WinForm 清空界面控件值的小技巧》,文 章里面介绍了怎么清空界面各个控件值的一个好技巧,这个方法确实是不错的,在繁杂 的界面控件值清理中,可谓省时省力。 本人在开发 Winform 程序中,也有一个类似的小技巧,不是清空控件值,而是赋值, 给复选框赋值和获取值的小技巧,分享讨论一下。 应用场景是这样的, 如果你有一些需要使用复选框来呈现内容的时候, 如下面两图所示: 以上的切除部分的内容,是采用在 GroupBox 中放置多个 CheckBox 的方式;其实这 个部分也可以使用 Winform 控件种的 CheckedListBox 控件来呈现内容。如下所示。 不管采用那种控件,我们都会涉及到为它赋值的麻烦,我这里封装了一个函数,可以很 简单的给控件 赋值,大致代码如下。 CheckBoxListUtil.SetCheck( this .groupRemove, info. 切除程度 ); 那么取控件的内容代码是如何的呢,代码如下: info. 切除程度 = CheckBoxListUtil.GetCheckedItems( this .groupRemov e); 赋值和取值通过封装函数调用, 都非常简单, 也可以重复利用, 封装方法函数如下所示。代码 public class CheckBoxListUtil { /// /// 如果值列表中有的 , 根据内容勾选 GroupBox 里面的成员 . /// /// 包含 CheckBox 控件组的 GroupBox 控件 /// 逗号分隔的值列表 public static void SetCheck(GroupBox group, string val ueList) { string [] strtemp = valueList.Split( ',' ); foreach ( string str in strtemp) { foreach (Control control in group.Controls) { CheckBox chk = control as CheckBox; if (chk != null && chk.Text == str) { chk.Checked = true ; } } } } /// /// 获取 GroupBox 控件成员勾选的值 /// /// 包含 CheckBox 控件组的 GroupBox 控件 /// 返回逗号分隔的值列表 public static string GetCheckedItems(GroupBox group) { string resultList = "" ; foreach (Control control in group.Controls) { CheckBox chk = control as CheckBox; if (chk != null && chk.Checked) { resultList += string .Format( "{0}," , chk.Te xt); } } return resultList.Trim( ',' ); } /// /// 如果值列表中有的 , 根据内容勾选 CheckedListBox 的成员 . /// /// CheckedListBox 控件 /// 逗号分隔的值列表 public static void SetCheck(CheckedListBox cblItems, s tring valueList) { string [] strtemp = valueList.Split( ',' ); foreach ( string str in strtemp) { for ( int i = 0; i < cblItems.Items.Count; i++) { if (cblItems.GetItemText(cblItems.Items [i]) == str) { cblItems.SetItemChecked(i, true ); } } } } /// /// 获取 CheckedListBox 控件成员勾选的值 /// /// CheckedListBox 控件 /// 返回逗号分隔的值列表 public static string GetCheckedItems(CheckedListBox cb lItems) { string resultList = "" ; for ( int i = 0; i < cblItems.CheckedItems.Count; i ++) { if (cblItems.GetItemChecked(i)) { resultList += string .Format( "{0}," , cblIte ms.GetItemText(cblItems.Items[i])); } } return resultList.Trim( ',' ); } } 以上代码分为两部分, 其一是对 GroupBox 的控件组进行操作,第二是对 CheckedListBox 控件进行操作。 这样在做复选框的时候,就比较方便一点,如我采用第一种 GroupBox 控件组方式, 根据内容勾选的界面如下所示。 应用上面的辅助类函数,如果你是采用 GroupBox 方案,你就可以随便拖几个 CheckBox 控件进去就可以了, 也犯不着给他取个有意义的名字, 因为不管它是张三还 是李四,只要它的父亲是 GroupBox 就没有问题了。 如何使用 dotnetbar 控件来构造多文档界面 Posted on 2010-12-17 22:20 伍华聪 阅读 (2183) 评论 (10) 编辑 收藏 在前段时间一篇随笔《 利用优秀的 .NET 界面控件,打造新潮的界面效果 》中介绍过 Dotnetbar 的界面效果,虽然引发不少关于该控件效果的争议,不过话说回来,使用 该控件也不失为一个界面的解决方案, 本文继续探寻该控件的使用, 在 QQ 搜通天企业 版软件中使用该控件做了一次完整的改造,碰到并解决了一些问题,本文主要总结介绍 如何利用 Dotnetbar 控件来实现多文档界面的效果。首先我们先来看看软件的主体界 面效果,如下图所示。本界面主要利用 Bar 控件来实现多文档的界面效果, 每个子窗体皆为一个用户控件, 当 然也包括容纳各种窗体的容器 CtrlMdiBar 类,也是一个用户控件,在主界面 Ribbon 控件的 RibbonClientPanel 区域放置容器,然后每次打开窗体,就动态创建或者激活 一个窗体页,这样就实现了多文档界面的效果了。 bar = new CtrlMdiBar(); bar.DockTabClosed = new DockTabClosedDelegate(OnDo ckItemClosed); SetDetailPanel(bar); private CtrlMdiBar bar; private Dictionary< string , DockContainerItem> MdiDic t = new Dictionary< string , DockContainerItem>(); public void SetDetailPanel(UserControl uc) { if (uc == null ) { throw new ArgumentNullException( "uc" , @"用户控件 uc 不能为空 " ); } uc.Dock = DockStyle.Fill; ribbonDetailPanel.Controls.Clear(); ribbonDetailPanel.Controls.Add(uc); } public void SetMdiForm(UserControl uc, string itemText) { DockContainerItem item = null ; string type = itemText; //uc.GetType().Name; if (MdiDict.ContainsKey(type)) { item = MdiDict[type]; } else { PanelDockContainer panel = new PanelDockContai ner(); item = new DockContainerItem(); item.Control = panel; item.Text = itemText; uc.Dock = DockStyle.Fill; panel.Controls.Add(uc); MdiDict.Add(type, item); bar.bar1.Items.Add(item); } bar.bar1.SelectedDockContainerItem = item; this .Refresh(); } private void OnDockItemClosed(DockContainerItem item) { string type = item.Text; if (MdiDict.ContainsKey(type)) { MdiDict.Remove(type); } } 然后每次按钮打开一个窗体页的时候,只需要简单的调用函数即可,如下面几个窗体 页的打开操作一样 private void btnMyQunUser_Click( object sender, EventAr gs e) { SetMdiForm( new CtrlGroupUser(), MDIForm. 查询个人群成 员.ToString()); } private void btnMyQQUser_Click( object sender, EventArg s e) { SetMdiForm( new CtrlQQContact(), MDIForm. 查询个人 QQ好 友.ToString()); } private void btnXiaoyou_Click( object sender, EventArg s e) { SetMdiForm( new CtrlXiaoyou(), MDIForm. 查询 QQ校友 .To String()); } 其中 CtrlMDIBar 容器,主要是一个用户控件放置一个 Dotnetbar 的 Bar 控件,然后 在该控件里面调用委托处理控件关闭的事件,主要代码如下所示。 public delegate void DockTabClosedDelegate(DockContainerIte m item); public partial class CtrlMdiBar : UserControl { public DockTabClosedDelegate DockTabClosed; public CtrlMdiBar() { InitializeComponent(); } private void bar1_DockTabClosed( object sender, DevComp onents.DotNetBar.DockTabClosingEventArgs e) { //MessageExUtil.ShowTips("DockTabClosed"); if (DockTabClosed != null ) { DockTabClosed(e.DockContainerItem); } } ............... 这样,我们后面如果要增加一个窗体页放到容器里面,只需要再定义一个用户控件,并 设计好界面等处理方式即可,动态增加一个界面窗体页将非常简单。 从 Socket 数据处理线程想到的普通 Winform 数据显示的应用 Posted on 2010-02-05 23:15 伍华聪 阅读 (2213) 评论 (8) 编辑 收藏 在前面介绍过 Socket 编程的文章中,有一篇是《 Socket 开发探秘 -- 基类及公共类的 定义》,其中介绍了一个独立线程处理类,专门在一个独立的线程中处理 Socket 的数 据包的。摘录前面的内容介绍一下:5、ThreadHandler ,数据独立线程处理类 对每个不同类型的数据(不同的协议类型),可以用独立的线程进行处理,这里封装了 一个基类,用于进行数据独立线程的处理 上面的工作原理是这样的,每次收到数据后,系统把数据扔给独立线程处理类,处理类 放到一个队列 Queue 的列表中,每次从中弹出一个来处理,根据不同的协议头,分派 到不同的线程来处理,这样可以提高响应速度,防止线程之间的阻塞,能够充分利用系 统的资源。 其实我们还可以把这个思想应用到日常的 Winform 开发中,有时候我们可能在处理一 些比较费时的操作,可能是需要做一部分显示一部分,类似日常生活中的项目周报、月 周报的场景, 因为不可能等一个几年的项目完成后, 你才告诉老板你的工作情况吧。 借鉴 Socket 的数据处理方式, 我在 Winform 程序中运用了这种数据处理方式, 如我 在采集赶集网的数据的时候,可以把采集到的部分数据扔给系统中的数据独立处理线 程,让他们爱怎么显示就怎么显示,程序不中断,继续乐此不彼的去采集内容去,然后 继续这样做(每采集一部分仍出去一部分),直到采集完毕。 代码 public class ThreadHandler { /// /// 处理数据线程 /// Thread _Handlehread = null ; private string _ThreadName = "" ; private Fifo _DataFifo = new Fifo(); /// /// 线程名字 /// public string ThreadName { get { return _ThreadName; } set { _ThreadName = value; } } /// /// 接收处理数据 /// /// public virtual void AppendData(T data) { if (data != null ) _DataFifo.Append(data); } /// /// 数据处理 /// protected virtual void DataThreadHandle() { try { while ( true ) { T data = _DataFifo.Pop(); DataHandle(data); } } catch (Exception ex) { LogHelper.Error(ex); } } /// /// 数据处理 /// /// public virtual void DataHandle(T data) { } /// /// 开始数据处理线程 /// public virtual void StartHandleThread() { if (_Handlehread == null ) { _Handlehread = new Thread( new ThreadStart(Data ThreadHandle)); _Handlehread.IsBackground = true ; _Handlehread.Start(); } LogHelper.Info( string .Format( "[ThreadHandler] 线程->{0} 启动。。。。。。 " , _ThreadName)); } 上面的是独立线程处理的基类, 下面我们用一个子类继承他, 方便代码逻辑的剥离封装: 在下面的代码中, 我根据不同的 Table 表内容类型, 放到不同的函数中进行处理, 以便 实现不同的显示方式。 代码 public class TestDataHandleThread : ThreadHandler { public TestDataHandleThread() { base .ThreadName = " 测试数据操作处理线程 " ; } public override void DataHandle(PreData data) { try { if (data.Key == KeyType.PostAticle) { if (! string .IsNullOrEmpty(data.Content.Tab leName)) { ThreadPool.QueueUserWorkItem( new WaitC allback(Portal.gc.MainDialog.DisplayForm), data.Content); } } else if (data.Key == KeyType.ContactInfo) { if (! string .IsNullOrEmpty(data.Content.Tab leName)) { ThreadPool.QueueUserWorkItem( new WaitC allback(Portal.gc.MainDialog.DisplayContactForm), data.Conten t); } } } catch (Exception ex) { LogHelper.Error( "[TestDataHandleThread] 测试数据 操作处理线程异常: {0}" + ex.ToString()); } } } 下面代码是表的不同类型的枚举类和预处理数据格式定义。 代码 public enum KeyType{PostAticle, ContactInfo}; /// /// 预处理的数据 /// public class PreData { private KeyType key; private DataTable content; public KeyType Key { get { return key; } set { key = value; } } public DataTable Content { get { return content; } set { content = value; } } public PreData(KeyType key, DataTable data) { this .key = key; this .content = data; } } 在实际的赶集网采集程序中,我需要每采集一个链接的内容后,就处理并显示,因此 示例代码如下所示: 代码 /// /// 获取网站发布内容,并添加到线程进行处理 /// /// /// private void GetContent(Dictionary< string , string > ite mDict) { foreach ( string key in itemDict.Keys) { DataTable dt = new DataTable(key); // 标题解析,省略 N 行代码 // 内容解析,省略 N+N 行代码 // 添加到线程进行处理 Portal.gc.MainDialog.AddData( new PreData(KeyTy pe.PostAticle, dt)); } } 代码 /// /// 添加消息数据,根据不同的消息类型分派到不同的线程处理 /// /// 消息数据 public void AddData(PreData data) { _testDataThread.AppendData(data); } /// /// 采用多线程方式显示内容数据 /// /// public void DisplayForm( object table) { DataTable data = table as DataTable; FrmContent content = FindDocument(data.TableNam e) as FrmContent; if (content == null ) { content = new FrmContent(); content.TabText = data.TableName; content.Text = data.TableName; } this .Invoke( new MethodInvoker( delegate () { content.BindData(data, data.TableName); content.Show( this .dockPanel); })); } 好了,思路是思路,程序是程序,两者结合就是实践的证明,采集大量的网站连接的时 候,在也不会出现主界面停顿或者假死的情况了。下面是我闲暇时间的练笔之作, 贴 图以证方案之可行。 在采集的时候, 整个程序再也不会出现假死的情况, 你还可以去处理其他工作的。 另外, 由于涉及了线程的处理工作,你还需要定时检测处理线程,如果线程有问题,还需要重 启线程就可以了,这部分是属于线程检查优化的部分,不再介绍。 再谈布局控件 "WeifenLuo.WinFormsUI.Docking" 的使用 -- 如何控制自动停靠 窗口的大小 Posted on 2009-10-20 20:40 伍华聪 阅读 (3667) 评论 (5) 编辑 收藏 在上篇文章《 WinForm 界面开发之布局控件 "WeifenLuo.WinFormsUI.Docking" 的 使用》,我介绍了如何在程序界面中使用 WeifenLuo.WinFormsUI.Docking 这个优 秀的布局控件,这款软件目前我还没有找出比他更好的免费控件了,不知 WeifenLuo 其人如何,不过东西确实不错,赞一个。 在使用这个控件的时候,估计大家都会碰到 这样一个问题,就是当窗口是自动隐藏的 时候,好像出来的大小一般比实际的大,感觉不太美观,有没有什么方法可以控制它的 呢,答案是当然有了,其实实现起来也很简单。首先我们来看看其内部机制是如何实现的,因为该控件有一些属性,专门用来控制窗口 的比例的。我们在该控件的源码上看到 DocingPanel 类中有这么一个属性,是用来控 制自动隐藏窗口的缩放比例的。 [LocalizedCategory( "Category_Docking" )] [LocalizedDescription( "DockContent_AutoHidePortion_Des cription" )] [DefaultValue( 0.25 )] public double AutoHidePortion { get { return DockHandler.AutoHidePortio n; } set { DockHandler.AutoHidePortion = valu e; } } 默认的大小是 0.25 ,这个参数是可以修改的。因为控件提供了一个保存布局状态的方 法,它默认是没有保存的, 如果需要记住调整的窗口大小 (当然这是一种最好的方法) , 那么只需要加上几段代码即可。 首先我们看保存布局状态的代码。 private void MainForm_Closing( object sender, System.C omponentModel.CancelEventArgs e) { string configFile = Path.Combine(Path.GetDirectory Name(Application.ExecutablePath), "DockPanel.config" ); if (m_bSaveLayout) dockPanel.SaveAsXml(configFile); else if (File.Exists(configFile)) File.Delete(configFile); } 这样的方法,因为是直接调用控件本身的保存,所以应该比较易懂。我们再看看程序 启动的时候,加载还原原有布局信息的时候,是如何的。 private void MainForm_Load( object sender, EventArgs e) { // 加载布局 m_deserializeDockContent = new DeserializeDockCont ent(GetContentFromPersistString); string configFile = Path.Combine(Path.GetDirectory Name(Application.ExecutablePath), "DockPanel.config" ); if (File.Exists(configFile)) { dockPanel.LoadFromXml(configFile, m_deserializ eDockContent); } } private IDockContent GetContentFromPersistString( strin g persistString) { if (persistString == typeof (MainToolWindow).ToStri ng()) return mainToolWin; else if (persistString == typeof (FrmStatus).ToStri ng()) return mainStatus; else if (persistString == typeof (FrmRoomView).ToSt ring()) return frmRoomView; else return null ; } 这样,我们就可以实现布局由用户调整,而不需要怕每次都有那么一点大,不雅观了。 我们看程序的根目录下面生成了一个文件,叫做 DockPanel.config , 我们看看就知 道里面的布局状态参数了 , 其中的 AutoHidePortion ,我们通过自动调整界面,它 也就会跟着变化的了,如下面的那个 AutoHidePortion = "0.179554494828958" 就是我调整后的大小。 当然如果我们需要 完美的布局,只需要在发布前,调整好这些参数给用户就可以了, 是不是很方便呢。 WinForm 界面开发之 “ OutLookBar ”工具条 Posted on 2009-07-10 19:16 伍华聪 阅读 (5916) 评论 (32) 编辑 收藏 在很多软件界面中,一个好的界面、方便的导航除了为软件增色不少外,也提高了用户 体验,促进软件的良性发展,因为我们的软件一般需要有菜单、工具条、状态条等这些 基本的东西,但是工具条本身应该是一些常用的快捷键,内容不能放置太多,否则会容 易给客户凌乱的感觉。菜单条则可以分类,但是好像每次去点击,一步步深入,则显得 比较麻烦。本篇我介绍一下一个很好的导航条 OutlookBar 控件。 在我的 2 个版本的送水软件中,都用到了 OutLookBar 的工具条,使用的界面效果如 下所示。左边的工具条它们都是同一个控件来的, 控件提供了一种类似 Outlook 方式的工具条, 用来切换各种业务窗口,用上这个控件,肯定为您的程序增色不少。这个控件结合上面 介绍的布局控件 "WeifenLuo.WinFormsUI.Docking" (具体见文章 WinForm 界面开 发之布局控件 "WeifenLuo.WinFormsUI.Docking" 的使用 ),那么效果会更好。下面 介绍下如何在代码中使用这个 Outlookbar 工具控件。 1 、首先创建一个窗体,用来放置该控件,由于该控件不是一个可视化的控件,因此需 要做一些特别的处理,如添加一个 ImageList 控件,并把 OutlookBar 控件中用到的 图标加载进来,记得选择一些好看的图片哦。 2、在 MainToolWindow 窗体的构造函数或者 Load 事件中添加 OutlookBar 的初始 化代码和设置代码,如下所示。 private OutlookBar outlookBar1 = null ; public MainToolWindow() { InitializeComponent(); InitializeOutlookbar(); } private void InitializeOutlookbar() { outlookBar1 = new OutlookBar(); #region 销售管理 OutlookBarBand outlookShortcutsBand = new Outloo kBarBand( " 销售管理 " ); outlookShortcutsBand.SmallImageList = this .imageLi st; outlookShortcutsBand.LargeImageList = this .imageLi st; outlookShortcutsBand.Items.Add( new OutlookBarItem ( " 订单管理 " , 0)); outlookShortcutsBand.Items.Add( new OutlookBarItem ( " 客户管理 " , 1)); outlookShortcutsBand.Items.Add( new OutlookBarItem ( " 水票管理 " , 2)); outlookShortcutsBand.Items.Add( new OutlookBarItem ( " 套餐管理 " , 3)); outlookShortcutsBand.Items.Add( new OutlookBarItem ( " 今日盘点 " , 5)); outlookShortcutsBand.Items.Add( new OutlookBarItem ( " 来电记录 " , 6)); outlookShortcutsBand.Items.Add( new OutlookBarItem ( " 送货记录 " , 7)); outlookShortcutsBand.Background = SystemColors.App Workspace; outlookShortcutsBand.TextColor = Color.White; outlookBar1.Bands.Add(outlookShortcutsBand); #endregion #region 产品库存管理 OutlookBarBand mystorageBand = new OutlookBarBan d( " 产品库存管理 " ); mystorageBand.SmallImageList = this .imageList; mystorageBand.LargeImageList = this .imageList; mystorageBand.Items.Add( new OutlookBarItem( " 产品管理 " , 2)); mystorageBand.Items.Add( new OutlookBarItem( " 库存管理 " , 3)); mystorageBand.Background = SystemColors.AppWorkspa ce; mystorageBand.TextColor = Color.White; outlookBar1.Bands.Add(mystorageBand); #endregion . outlookBar1.Dock = DockStyle.Fill; outlookBar1.SetCurrentBand( 0); outlookBar1.ItemClicked += new OutlookBarItemClick edHandler(OnOutlookBarItemClicked); outlookBar1.ItemDropped += new OutlookBarItemDropp edHandler(OnOutlookBarItemDropped); //outlookBar1.FlatArrowButtons = true; this .panel1.Controls.AddRange( new Control[] { outl ookBar1 }); } private void OnOutlookBarItemClicked(OutlookBarBand ba nd, OutlookBarItem item) { switch (item.Text) { #region 销售管理 case " 订单管理 " : Portal.gc.MainDialog.ShowContent( " 订单管理 " , typeof (FrmOrder)); break ; case " 客户管理 " : Portal.gc.MainDialog.ShowContent( " 客户管理 " , typeof (FrmCustomer)); break ; case " 水票管理 " : Portal.gc.MainDialog.ShowContent( " 水票管理 " , typeof (FrmTicketHistory)); break ; case " 套餐管理 " : FrmYouhui dlg = new FrmYouhui(); dlg.ShowDialog(); break ; case " 来电记录 " : Portal.gc.MainDialog.ShowContent( " 来电记录" , typeof (FrmComingCall)); break ; case " 送货记录 " : Portal.gc.MainDialog.ShowContent( " 送货记录 " , typeof (FrmDeliving)); break ; #endregion #region 产品库存管理 case " 产品管理 " : Portal.gc.MainDialog.ShowContent( " 产品管理 " , typeof (FrmProduct)); break ; case " 库存管理 " : Portal.gc.MainDialog.ShowContent( " 库存管理 " , typeof (FrmStock)); break ; #endregion .. default : break ; } } private void OnOutlookBarItemDropped(OutlookBarBand ba nd, OutlookBarItem item) { // string message = "Item : " + item.Te xt + " was dropped "; // MessageBox.Show(messag e); } 在代码中注意绑定相关项目的图标序号,否则如果序号不正确,可能会出错的,其实整 个控件就是提供展示一些图标,并用同一的事件对鼠标的事件进行处理,用户根据 OutlookBarItem 的文本内容来判断处理, 虽然模式有点落后, 不过个人感觉控件还是 非常好用,方便。 WinForm 界面开发之布局控件 "WeifenLuo.WinFormsUI.Docking" 的使用 Posted on 2009-07-09 20:25 伍华聪 阅读 (10024) 评论 (28) 编辑 收藏本篇介绍 Winform 程序开发中的布局界面的设计,介绍如何在我的共享软件中使用布 局控件 "WeifenLuo.WinFormsUI.Docking" 。 布局控件 "WeifenLuo.WinFormsUI.Docking" 是一个非常棒的开源控件, 用过的人都 深有体会,该控件之强大、美观、不亚于商业控件。而且控件使用也是比较简单的。先 看看控件使用的程序界面展示效果。 配电网络可视化管理系统的界面截图: 深田之星送水管理系统网络版的界面截图:我在几个共享软件都使用了该布局控件, 我们先以 “深田之星送水管理系统网络版 ”这款 软件为例,介绍如何完成该界面的设计及显示的。 1、首先,我们添加一个主界面窗体,命名为 MainForm ,该窗体 IsMdiContainer 设 置为 True ,也就是设置为多文档窗体格式。拖拉布局控件 "WeifenLuo.WinFormsUI.Docking.DockPanel" 到主窗体 MainForm 中,并设置下 面几个属性: Dock 为 Fill 、DocumentStyle 为 DockingMdi 、RightToLeftLayout 为 True 。 这几个属性的意思应该不难, Dock 就是 覆盖整个 MDI 窗体的区域, DocumentStyle 为多文档类型、 RightToLeftLayout 是指新打开的窗口都停靠在右边区域。 我们看看设计界面视图如下所示。2、主界面其实基本上就可以了,另外我们看到 “送水管理系统网络版 ”的界面中有一个 左边的工具栏, 它其实也是在一个停靠的窗体中的,我们增加一个窗体用来承载相关的 工具快捷键按钮展示。命名为 MainToolWindow 的窗体,继承自 WeifenLuo.WinFormsUI.Docking.DockContent. 其中的 “HideOnClose”属性很重要,该属性一般设置为 True ,就是指你关闭窗口时, 窗体只是隐藏而不是真的关闭。 左边的窗口 MainToolWindow 实现停靠的代码是在 MainForm 的构造函数或者 Load 函数中加载即可。 mainToolWin.Show(this.dockPanel, DockState.DockLeft); 3 、对于工具窗口我们已经完成了, 但是主业务窗口还没有做, 也就是下面的部分内容。 为了方便,我们定义一个基类窗体,命名为 BaseForm ,继承自 DockContent ,如下 所示 public class BaseForm : DockContent 然后每个业务窗口继承 BaseForm 即可。 4、剩下的内容就是如何在主窗体 MainForm 中展示相关的业务窗口了, 展示的代码如 下所示 public partial class MainForm : Form { #region 属性字段 private MainToolWindow mainToolWin = new MainToolWind ow(); private FrmProduct frmProduct = new FrmProduct(); private FrmCustomer frmCustomer = new FrmCustomer(); private FrmOrder frmOrder = new FrmOrder(); private FrmStock frmStock = new FrmStock(); private FrmComingCall frmComingCall = new FrmComingCal l(); private FrmDeliving frmDeliving = new FrmDeliving(); private FrmTicketHistory frmHistory = new FrmTicketHis tory(); #endregion public MainForm() { InitializeComponent(); SplashScreen.Splasher.Status = " 正在展示相关的内容 " ; System.Threading.Thread.Sleep( 100 ); mainToolWin.Show( this .dockPanel, DockState.DockLef t); frmComingCall.Show( this .dockPanel); frmDeliving.Show( this .dockPanel); frmHistory.Show( this .dockPanel); frmStock.Show( this .dockPanel); frmProduct.Show( this .dockPanel); frmCustomer.Show( this .dockPanel); frmOrder.Show( this .dockPanel); SplashScreen.Splasher.Status = " 初始化完毕 " ; System.Threading.Thread.Sleep( 50 ); SplashScreen.Splasher.Close(); } 5. 下面贴出基本窗口的基本操作事件函数 private void menu_Window_CloseAll_Click( object sende r, EventArgs e) { CloseAllDocuments(); } private void menu_Window_CloseOther_Click( object sende r, EventArgs e) { if (dockPanel.DocumentStyle == DocumentStyle.Syste mMdi) { Form activeMdi = ActiveMdiChild; foreach (Form form in MdiChildren) { if (form != activeMdi) { form.Close(); } } } else { foreach (IDockContent document in dockPanel.Do cumentsToArray()) { if (!document.DockHandler.IsActivated) { document.DockHandler.Close(); } } } } private DockContent FindDocument( string text) { if (dockPanel.DocumentStyle == DocumentStyle.Syste mMdi) { foreach (Form form in MdiChildren) { if (form.Text == text) { return form as DockContent; } } return null ; } else { foreach (DockContent content in dockPanel.Docu ments) { if (content.DockHandler.TabText == text) { return content; } } return null ; } } public DockContent ShowContent( string caption, Type fo rmType) { DockContent frm = FindDocument(caption); if (frm == null ) { frm = ChildWinManagement.LoadMdiForm(Portal.gc. MainDialog, formType) as DockContent; } frm.Show( this .dockPanel); frm.BringToFront(); return frm; } public void CloseAllDocuments() { if (dockPanel.DocumentStyle == DocumentStyle.Syste mMdi) { foreach (Form form in MdiChildren) { form.Close(); } } else { IDockContent[] documents = dockPanel.Documents ToArray(); foreach (IDockContent content in documents) { content.DockHandler.Close(); } } } 最后呈上该控件文件,大家可以下来玩玩。 WinForm 界面开发之 “ HTML 内容编辑控件 ” Posted on 2009-07-07 13:31 伍华聪 阅读 (3902) 评论 (15) 编辑 收藏 做过了很多 Winform 的共享软件,对界面的设计有了一定的经验和积累,准备开一个 “ WinForm 界面开发 ”系列文章, 介绍下相关的 Winform 界面设计和相关控件的使用, 促进相互交流,以获得更好的发展和了解。 在很多 Winform 程序中 ,我们可能需要 HTML 内容的所见即所得的编辑控件,如 Asp.net 中的 FreeTextBox 等控件样式的。 搜索了一下, 发现很少这方面的控件, 找了一个, 好像是收费的, 而且感觉一般, 跳过, 再找,然后发现 CodeProject 上有一个不错的东西,拿过来进行修改一下,作为控件 进行使用,效果还不错,我在 QQ 号码采集机邮件发送系统中使用,如下图所示控件支持基本的 HTML 编辑操作,如文字、图片、排列(左对齐,右对齐,居中等)、 项目符号、缩进调整等操作,如下图所示另外控件支持右键菜单操作,常用的编辑功能以及查找、修改 HTML 、查看内容、插入 HTML 元素等 内容查找界面如下所示 修改HTML窗体如下所示, 可以增加修改里面的内容, 确定后控件的内容将重新变化。插入图片的对话框,提示用户输入相关的信息。 控件我们一般使用两个属性即可,如下代码所示 string bodyHtml = this .txtContent.BodyHtml; string bodyText = this .txtContent.BodyText; 最后,呈上大家最关心的控件文件,大家可以按照使用一般的控件使用即可(控件我没 有进行加密,大家需要修改源码的,反编译一下即可,呵呵) : WinForm 界面开发之 “分页控件 ” Posted on 2009-07-07 21:06 伍华聪 阅读 (5660) 评论 (50) 编辑 收藏 在程序中,分页总是永远的话题,因为数据总是很多很多,分页展示在程序性能和数据 查看感官方面得到很好的平衡,是一种良好的编程习惯和 UI 设计。Winform 中的分页控件可能没有 Asp.net 世界中的分页控件那么丰富多彩,不过也有 不少的分页控件可以采用,各个人的可能都有一些不同的东西,一些好的东西。就我而 言,我希望控件能够尽可能的多一些功能,耦合性低一些,例如我不想是基于存储过程 的,因为我很多程序需要使用 Access 作为数据库,一般来说,我还希望有导出 Excel 数据的功能,还有打印预览功能,由于我的数据源表头,如实体类集合、表格内容绑定 的时候,表头是英文的,我需要变为中文的,其他的功能有则更好。本篇介绍我在我的 共享软件中大量使用的分页控件,如送水管理系统软件、病人资料管理软件等等,希望 大家有兴趣的话,可以一起讨论使用该分页控件。以便整理吸收更多好的特性,共同学 习。 先给大家一个总体感觉, 这是我在送水软件中的界面展示,红色圈圈部分是分页控件的 展示界面。大家可以看到,除了基本的分页外,还有其他功能,如导出当前页、导出全部页、打印 列表、以及相关功能操作的菜单(只要实现了相关的接口,则呈现相同的菜单,另外还 有一些小地方,也是很常用关键的地方,就是间隔行的颜色变化,表头的中文化,行提 示内容等等,我认为这些分页控件应该做的,特别是表头中文化部分很重要,因为这个 分页控件的数据源是基于一般的对象集合(如 List()) 或者 DataTable 的,因此数据源的表头可能是英文的(一般都是 ^_^ )。 导出 Excel 是基本的功能, 本控件支持当前页导出, 全部页导出两种模式, 导出的 Excel 数据也还是比较好看的,不是一般的格式哦。控件另外一项功能,也是集实用功能之所成,打印当前列表内容,如下图所示,该内容 会保存用户在每个列表数据中的信息,打印不同的表头内容,如下图所示。那么控件应该如何使用呢,下面介绍一下使用的相关代码。 1、首先在 Form_Load 事件中绑定相关的委托处理事件, 默认有 “新建 ”、编辑选定项、 删除、刷新、打印几个按钮,您可以在此基础上增加更多的菜单。 private void FrmProduct_Load( object sender, EventArg s e) { BindData(); this .winGridViewPager1.ProgressBar = this .toolStri pProgressBar1.ProgressBar; this .winGridViewPager1.OnPageChanged += new EventH andler(winGridViewPager1_OnPageChanged); this .winGridViewPager1.OnStartExport += new EventH andler(winGridViewPager1_OnStartExport); this .winGridViewPager1.OnEditSelected += new Event Handler(winGridViewPager1_OnEditSelected); this .winGridViewPager1.OnAddNew += new EventHandle r(winGridViewPager1_OnAddNew); this .winGridViewPager1.OnDeleteSelected += new Eve ntHandler(winGridViewPager1_OnDeleteSelected); this .winGridViewPager1.OnRefresh += new EventHandl er(winGridViewPager1_OnRefresh); this .winGridViewPager1.AppendedMenu = this .context MenuStrip1; } 2。实现表头解析和上面的委托时间的例子代码如下 . private void winGridViewPager1_OnRefresh( object sende r, EventArgs e) { BindData(); } private void winGridViewPager1_OnDeleteSelected( objec t sender, EventArgs e) { if (MessageUtil.ShowYesNoAndTips( " 您确定删除选定的记录 么? " ) == DialogResult.No) { return ; } DataGridView grid = sender as DataGridView; if (grid != null ) { foreach (DataGridViewRow row in grid.SelectedR ows) { BLLFactory.Instance.Delete(row.Ce lls[ "ID" ].Value.ToString()); } BindData(); } } private void winGridViewPager1_OnEditSelected( object s ender, EventArgs e) { DataGridView grid = sender as DataGridView; if (grid != null ) { foreach (DataGridViewRow row in grid.SelectedR ows) { FrmEditProduct dlg = new FrmEditProduct(); dlg.ID = row.Cells[ "ID" ].Value.ToString(); if (DialogResult.OK == dlg.ShowDialog()) { BindData(); } break ; } } } private void winGridViewPager1_OnAddNew( object sende r, EventArgs e) { btnAddNew_Click( null , null ); } private void winGridViewPager1_OnStartExport( object se nder, EventArgs e) { PagerInfo pagerInfo = new PagerInfo(); pagerInfo.CurrenetPageIndex = 1; pagerInfo.PageSize = int .MaxValue; this .winGridViewPager1.AllToExport = BLLFactory.Instance.GetAllToDataSet(pagerInfo).Tables[ 0]; //product. GetAllToDataSet(pagerInfo).Tables[0]; } private void winGridViewPager1_OnPageChanged( object se nder, EventArgs e) { BindData(); } private void BindData() { #region 添加别名解析 this .winGridViewPager1.AddColumnAlias( "ID" , " 编号 " ); this .winGridViewPager1.AddColumnAlias( "ProductType " , " 产品类型 " ); this .winGridViewPager1.AddColumnAlias( "ProductName " , " 产品名称 " ); this .winGridViewPager1.AddColumnAlias( "Specification" , " 产品规格 " ); this .winGridViewPager1.AddColumnAlias( "Model" , " 产 品型号 " ); this .winGridViewPager1.AddColumnAlias( "OfferPrice " , " 进货价 " ); this .winGridViewPager1.AddColumnAlias( "AdvisePrive " , " 建议价 " ); this .winGridViewPager1.AddColumnAlias( "SalePrice " , " 零售价 " ); this .winGridViewPager1.AddColumnAlias( "Manufacture " , " 生产厂商 " ); this .winGridViewPager1.AddColumnAlias( "Manufacture _ID" , " 厂商 ID" ); this .winGridViewPager1.AddColumnAlias( "Note" , " 备注 " ); this .winGridViewPager1.AddColumnAlias( "LastUpdated " , " 更新日期 " ); #endregion SearchCondition condition = new SearchCondition(); condition.AddCondition( "ProductName" , this .txtName. Text, SqlOperator.Like) .AddCondition( "ProductType" , this .cmbProductTy pe.Text, SqlOperator.Like) .AddCondition( "Specification" , this .cmbSpecNum ber.Text, SqlOperator.Like) .AddCondition( "Note" , this .txtNote.Text, SqlOp erator.Like) .AddCondition( "Manufacture" , this .cmbManufactu re.Text, SqlOperator.Like); string where = condition.BuildConditionSql().Repla ce( "Where" , "" ); List list = BLLFactory.Insta nce.Find( where , this .winGridViewPager1.PagerInfo); this .winGridViewPager1.DataSource = new WHC.Pager. WinControl.SortableBindingList(list); } 这样就实现了分页控件的内容展示以及相关功能的菜单挂接 ,实现后的菜单展示可能是 这样子的 , 如下图所示 ,是否还可以呢 , 呵呵 . 最后呈上控件文件一个 , 大家有兴趣可以下载下载玩玩 ,使用有问题请在此留言 . (已下载) WinForm 界面开发之 "SplashScreen 控件 " Posted on 2009-07-08 17:17 伍华聪 阅读 (4663) 评论 (17) 编辑 收藏我们在开发桌面应用程序的时候,由于程序启动比较慢,往往为了提高用户的体验,增 加一个闪屏,也就是 SplashScreen ,好处有: 1、让用户看到加载的过程,提高程序 的交互响应; 2. 可以简短展示或者介绍程序的功能或者展示 Logo ,给客户较深的印象。 本人在开发的共享软件中,对于启动比较慢的程序,也倾向于引入这个控件来展示下, 先看看软件启动的时候的效果 中间的那些文字 “正在展示相关的内容 ”可以根据加载的进度显示不同的内容, 当然最好 简单扼要了,其他的内容你也可以视需要做相应变化,因为这个是一个 Form ,你想改 变什么就改变什么的。 看看闪屏代码如何使用先,首先我们在入口的 Main 函数中开始,看看代码就知道 public class Portal { public static GlobalControl gc = new GlobalControl(); /// /// 应用程序的主入口点。 /// [STAThread] private static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault( fals e); // 登陆界面 Logon dlg = new Logon(); dlg.StartPosition = FormStartPosition.CenterScreen; if (DialogResult.OK == dlg.ShowDialog()) { if (dlg.bLogin) { SplashScreen.Splasher.Show( typeof (SplashSc reen.frmSplash)); gc.MainDialog = new MainForm(); gc.MainDialog.StartPosition = FormStartPos ition.CenterScreen; Application.ThreadException += new System. Threading.ThreadExceptionEventHandler(Application_ThreadExcept ion); Application.Run(gc.MainDialog); } } dlg.Dispose(); } private static void Application_ThreadException( objec t sender, System.Threading.ThreadExceptionEventArgs ex) { LogHelper.Error(ex.Exception); string message = string .Format( "{0}\r\n 操作发生错误, 您需要退出系统么? " , ex.Exception.Message); if (DialogResult.Yes == MessageUtil.ShowYesNoAndEr ror(message)) { Application.Exit(); } } } 上面中最关键的代码是: SplashScreen.Splasher.Show(typeof(SplashScreen.frmSplash)); 之所以贴出全部的代码, 也是供大家参考如何启动登陆窗口并运行主窗体程序的, 上面 的 GlobalControl 类是一个公共类,用来放置一些全局变量或者通用操作的函数。 Application_ThreadException 是用来在程序运行出错的时候,友好提示一下用户, 是否退出,否则有一些莫名其妙的错误,程序没有提示就马上退出的问题,扯远了,言 归正传,还是说说如何使用闪屏的功能吧。 上面开启了闪屏的功能后, 那么我们可能就要在程序中, 根据不同的加载进度显示不同 的内容了,看看是如何做到的 记得在 MainForm 窗体的构造函数中添加相应的闪屏操作代码,如下所示。 public MainForm() { InitializeComponent(); SplashScreen.Splasher.Status = " 正在展示相关的内容 " ; System.Threading.Thread.Sleep( 100 ); .. // 此处省略部分加载耗时的代码 SplashScreen.Splasher.Status = " 初始化完毕 " ; System.Threading.Thread.Sleep( 50 ); SplashScreen.Splasher.Close(); } 控件的使用代码就这么多了,其他的就是封装好的控件部分内容了,下面打包放上来, 给大家参考使用,源码级的哦,下载了记得顶一下。 提示:控件的背景图片可能不能正常显示,您自己弄一个图片附上去就可以了。 (已下载) 吉日嘎啦通用权限管理系统解读及重构升华 -- 高度封装的编辑窗体 Posted on 2010-05-31 23:06 伍华聪 阅读 (5335) 评论 (78) 编辑 收藏 吉日嘎拉,一个专注于通用权限管理系统的开发狂热者, 在博客园是一个有争议的人物, 不过从其文章数量及内容介绍,专注肯定不是浪得虚名,一个人把东西做的专注,也就 意味着更多的投入及考虑,可以作为后来者更多的借鉴。 本人从其购买一套源码,用作深入分析及提炼使用,初一接触,整个体系架构还是比较 庞大,如下所示。本文思路是源自对 Winform 窗体的分析研究,发现其中不少优点,很多闪亮的地方, 结合本人在 Winform 方面的积累及研究,对即日嘎拉的代码进行进一步的提炼,于是 撰写了本文,本文主要介绍继承窗体的封装使用。 我们先来分析下吉日嘎拉其对窗体的设计代码,一步步进行深入的研究。 首先其也是设计一个窗体基类 BaseForm , 其他窗体继承自 BaseForm 基类,这也是 一种常用的设计。 代码 /// /// 加载窗体 /// private void FormOnLoad() { // 多语言国际化加载 this .Localization( this ); // 获得权限 this .GetPermission(); // 设置焦点 this .ActiveControl = this .txtCode; this .txtCode.Focus(); } #endregion private void FrmItemDetailsAdd_Load( object sender, Eve ntArgs e) { if (! this .DesignMode) { // 设置鼠标繁忙状态 this .Cursor = Cursors.WaitCursor; try { this .FormOnLoad(); } catch (Exception ex) { this .ProcessException(ex); } finally { // 设置鼠标默认状态 this .Cursor = Cursors.Default; } } } #region private void SaveEntity(bool close) /// /// 保存 /// /// 关闭窗体 private void SaveEntity( bool close) { // 检查输入的有效性 if ( this .CheckInput()) { // 设置鼠标繁忙状态 this .Cursor = Cursors.WaitCursor; try { if ( this .SaveEntity()) { if (close) { // 关闭窗口 this .Close(); } else { this .ClearScreen(); } } } catch (Exception ex) { this .ProcessException(ex); } finally { // 设置鼠标默认状态 this .Cursor = Cursors.Default; } } } #endregion private void btnAdd_Click( object sender, EventArgs e) { this .SaveEntity( false ); } private void btnSave_Click( object sender, EventArgs e) { this .SaveEntity( true ); } private void btnCancel_Click( object sender, EventArg s e) { this .Close(); } 从其中的封装调用我们看到, 吉日嘎拉已经把很多变化的代码抽象出来,也不失为一个 较好的设计,另外对窗体的输入回车转 Tab 也做了一个通用的转换,如下所示: 代码private void FrmItemDetailsAdd_KeyDown( object sende r, KeyEventArgs e) { string keyCode = e.KeyCode.ToString(); switch (keyCode) { case "Enter" : case "Return" : if ( this .ActiveControl is TextBox) { if (!((TextBox) this .ActiveControl).Mul tiline) { SendKeys.Send( "{TAB}" ); } } break ; } switch (e.KeyValue) { case 116 : // 点击了 F5 按钮 this .FormOnLoad(); break ; } } 闪亮之处还有很多,本文暂时在此打住,介绍一下其中可以重构更好的地方。 由于吉日嘎拉的窗体设计,对于一般常用到的编辑数据、新增数据窗体,分开了两个不 同的窗体进行处理,而且由于 BaseForm 窗体没有对通用的函数进行进一步的抽象, 因此,编辑及新增窗体多了很多重复累赘的代码, 其实可以把新增、 编辑合并一个窗体, 然后根据新增、编辑两种不同的条件进行处理即可。 由于 BaseForm 一般需要在大多数的窗体中, 而新增编辑数据窗体一般较为特殊一点, 可以再增加一个基类 BaseEditForm ,用来做新增编辑窗体的基类,该基类继承自 BaseForm 类,工程代码如下所示。其中运行例子的效果如下所示:编辑以及新增我们整合在一个窗体中, 先看看该基类的设计视图, 我们在其中添加了 3 个按钮(常用的添加、保存、关闭按钮)。窗体的代码我大类采用了可重载的虚函数,留给子类窗体进行实现不同的处理操作,如 窗体加载显示操作、显示数据到控件的操作、保存、新增等函数都是需要实现的,而调 用逻辑以及一些通用的处理,则在基类 BaseEditForm 中实现,子类不用重复这些代 码,按钮控件(添加、保存、关闭)的事件处理也已经进行了逻辑封装,如下所示。 代码 public override void FormOnLoad() { base .FormOnLoad(); if (! this .DesignMode) { if (! string .IsNullOrEmpty(ID)) { this .Text = " 编辑 " + this .Text; this .btnAdd.Visible = false ; // 如果是编辑, 则屏 蔽添加按钮 } else { this .Text = " 新建 " + this .Text; } DisplayData(); } } /// /// 显示数据到控件上 /// public virtual void DisplayData() { } /// /// 检查输入的有效性 /// /// 有效 public virtual bool CheckInput() { return true ; } /// /// 清除屏幕 /// public virtual void ClearScreen() { this .ID = "" ; /// / 需要设置为空,表示新增 ClearControlValue( this ); this .FormOnLoad(); } /// /// 保存数据(新增和编辑的保存) /// public virtual bool SaveEntity() { bool result = false ; if (! string .IsNullOrEmpty(ID)) { // 编辑的保存 result = SaveUpdated(); } else { // 新增的保存 result = SaveAddNew(); } return result; } /// /// 更新已有的数据 /// /// public virtual bool SaveUpdated() { return true ; } /// /// 保存新增的数据 /// /// public virtual bool SaveAddNew() { return true ; } /// /// 保存 /// /// 关闭窗体 private void SaveEntity( bool close) { // 检查输入的有效性 if ( this .CheckInput()) { // 设置鼠标繁忙状态 this .Cursor = Cursors.WaitCursor; try { if ( this .SaveEntity()) { MessageUtil.ShowTips( " 保存成功 " ); if (close) { this .DialogResult = DialogResult.OK; this .Close(); } else { this .ClearScreen(); } } } catch (Exception ex) { this .ProcessException(ex); } finally { // 设置鼠标默认状态 this .Cursor = Cursors.Default; } } } private void btnAdd_Click( object sender, EventArgs e) { this .SaveEntity( false ); } private void btnOK_Click( object sender, EventArgs e) { this .SaveEntity( true ); } private void btnCancel_Click( object sender, EventArg s e) { this .DialogResult = DialogResult.Cancel; this .Close(); } 其中值得介绍的是, 窗体的控件数据清空在基类窗体中通过遍历实现了通用的数据清空 操作,该部分代码引用了 “潇湘隐者的博客 (http://www.cnblogs.com/kerrycode/archive/2010/02/05/1664267.html ),对此感谢。另外,基类窗体还实现了 Tab 键的转换,这个思路来源于即日嘎拉的代码,但由于是 基类实现,有些不同,他的放在每个具体的子类中,因此通用性有些限制。 我们重载了 ProcessCmdKey(ref Message msg, Keys keyData) 函数后,就可以 实现统一的回车键转换了。 代码 protected override bool ProcessCmdKey( ref Message ms g, Keys keyData) { if (keyData == Keys.F5) { this .FormOnLoad(); } if ((!(ActiveControl is Button)) && (keyData == Keys.Up || keyData == Keys.Down | | keyData == Keys.Enter)) { if (keyData == Keys.Enter) { System.Windows.Forms.SendKeys.Send( "{TAB} " ); return true ; } if (keyData == Keys.Down) { System.Windows.Forms.SendKeys.Send( "{TAB} " ); } else { SendKeys.Send( "+{Tab}" ); } return true ; } else { return base .ProcessCmdKey( ref msg, keyData); } } 最后,我们看看具体的子类窗体,看新增编辑界面需要实现的代码,如下所示,其中 大部分是原子级别的操作,逻辑操作已经在基类中实现了哦: 代码 public partial class FrmEditCustomer : BaseUI.BaseEditForm { public FrmEditCustomer() { InitializeComponent(); } /// /// 实现控件输入检查的函数 /// /// public override bool CheckInput() { bool result = true ; #region 输入验证 if ( this .txtName.Text.Length == 0) { MessageUtil.ShowTips( " 宾客名称不能为空 " ); this .txtName.Focus(); result = false ; } else if ( this .txtIDNumber.Text.Length == 0) { MessageUtil.ShowTips( " 证件号码不能为空 " ); this .txtIDNumber.Focus(); result = false ; } #endregion return result; } /// /// 编辑或者保存状态下取值函数 /// /// private void SetInfo(CustomerInfo info) { info.Address = txtAddress.Text; info.CompanyName = txtCompany.Text; info.IDCarType = cmbIDCarType.Text; info.Name = txtName.Text; info.Note = txtNote.Text; info.IDNumber = txtIDNumber.Text; info.Telephone = txtTelephone.Text; info.Source = cmbSource.Text; info.CustomerType = cmbType.Text; info.Sex = cmbSex.Text; } /// /// 数据字典加载 /// private void InitDictItem() { this .cmbSource.Items.Clear(); this .cmbSource.Items.AddRange(DictItemUtil.GetCust omerSource()); this .cmbType.Items.Clear(); this .cmbType.Items.AddRange(DictItemUtil.GetCustom erType()); this .cmbIDCarType.Items.Clear(); this .cmbIDCarType.Items.AddRange(DictItemUtil.GetI DCarType()); } /// /// 数据显示的函数 /// public override void DisplayData() { // 数据字典加载(公用) InitDictItem(); if (! string .IsNullOrEmpty(ID)) { // 编辑状态下的数据显示 CustomerInfo info = BLLFactory.Insta nce.FindByID(ID); if (info != null ) { #region 显示客户信息 txtAddress.Text = info.Address; txtCompany.Text = info.CompanyName; txtName.Text = info.Name; txtNote.Text = info.Note; txtIDNumber.Text = info.IDNumber; txtTelephone.Text = info.Telephone; cmbSource.Text = info.Source; cmbType.Text = info.CustomerType; cmbSex.Text = info.Sex; cmbIDCarType.Text = info.IDCarType; lblCreateDate.Text = info.RegistrationDate. ToString(); #endregion } } else { // 新增状态的数据显示 lblCreateDate.Text = DateTime.Now.ToString(); } } /// /// 新增状态下的数据保存 /// /// public override bool SaveAddNew() { CustomerInfo info = new CustomerInfo(); SetInfo(info); info.RegistrationDate = DateTime.Now; bool succeed = BLLFactory.Instance.Inser t(info); return succeed; } /// /// 编辑状态下的数据保存 /// /// public override bool SaveUpdated() { CustomerInfo info = BLLFactory.Instance. FindByID(ID); if (info != null ) { SetInfo(info); bool succeed = BLLFactory.Instance.U pdate(info, info.ID.ToString()); return succeed; } return false ; } } WinForm 界面开发之酒店管理系统 -- 开篇 Posted on 2009-11-29 15:18 伍华聪 阅读 (5673) 评论 (35) 编辑 收藏 星移斗转,时光似箭,不知不觉中,酒店管理系统的开发从开始到现在的结束,已经 2 个月了, 2 个月的业余时间, 2 个月的生活情趣,都寄托在这个软件当中,经历了各种 艰苦和困惑,终于得以修成正果 --- 深田之星酒店管理系统的顺利发布。 技术的历程是一个开拓进取、攻克难题的历程,其中有困惑也有兴奋,有苦涩也有甜 蜜,在这个过程中, 再一次检阅了我的 Database2Sharp 代码自动生成的开发工具的, 再一次从 “深田之星送水管理系统 ”进行升华,技术从来没有尽头,只有不断完善,以及 不断的超越和创新。在这个过程中,总会产生一系列的 Q&A ,碰到了一个难题,如何 寻找相应的解决方法,就是非常有趣的问题了。 写这个随笔的初衷主要不是宣传我做的软件,而是有感而发,感随物现,介绍在其中历 程的一些思考和解决方法, 介绍做这个酒店管理系统的一些界面和非界面, 代码和非代 码的东西, 和大家做一个交流, 希望大家能我从言之无物、 略表空洞的文章中捡趣拾遗, 略受启发。言毕,晒上所做东西,在继续 ....... 整个系统的界面布局还是沿用我的 “送水管理系统网络版 ”的界面样式,采用了 OutlookBar + Wenfenluo 停靠控件,客房状态视图、 KTV 状态视图、茶室状态视图 等都是动态展示相关的房间信息的,因此需要做成控件,整个控件结合了菜单操作,以 及公布一些接口给界面调用显示的,封装这块总的还是花费了不少功夫,因为很多时间 花费在寻找合适的控件上,寻找是否有人家造好的轮子,以免重复制造轮子。不过再好 的轮子,要想用的好,都是需要修改和调整的。由于没有找到很合适的,基本上这个界 面都是自己封装控件来实现的。 下面几篇文章我会详细介绍一些这方面的知识, 为读者,也为自己在技术方面做一个到此一游的标记, N 月之后,回头看看,希望仍觉得有用, 呵呵。 下面介绍一下另外一个部分,就是下图左边部分的显示,它是一个很好的开源控件,给 我进行了适当的封装,里面的显示内容,可以随意定制,因此在客服、 KTV 、茶室中公 用一个状态显示窗口,但是显示的内容不同,界面效果还是不错的。左边的状态那块用 的是一个 ExploreBar 的控件,另外一个比普通按钮好看的是一个不错的按钮类,功能 比较强大方便,可以设置 很多种效果,包括各种图片的设置还是很方便的,我这里只 是用了它的最原始效果。下面这个是报表模块中的一部分了,整个系统很多报表,报表都脱不了打印啊、导出啊 的功能了,开始想利用 ActiveReport 做为报表打印的,可是发现为每个不同的报表设 计一个报表窗口,实在是消受不起,而且这些内容又是重复再重复的了,因此利用我原 先封装好的分页 GridView 控件就可以了,由于很多报表不需要分页功能,因此再封装 一个不用分页,但是有导出、打印功能的 GridView 控件就可以了。封装后的控件,既 能解析类似 List 的格式数据源, 也可以解析 DataTable 的数据格式, 还 可以对字段的显示名称随意设置,感觉省了很多麻烦。另外一个就是小票打印了, 很多基本上采用了 GP5860 这种 POS 打印机进行小票打印 了,这种如果是串口的打印,那么很方便,我原来的送水系统中就实现了,而且网上也 有 POS 打印的 C# 代码,可是如果我偏偏碰到了 USB 口的小票打印机,那么采用那个 就不行了,而且那个没有预览功能,另外 USB 口的小票打印机和普通的打印机很容易 弄错乱,不知道是否他们的打印原理差不多 ?因此必须解决小票打印机和普通打印机的 打印问题,即多个并存,互不影响。这个问题可能是做进销存问题,如果碰到打印机冲 突,需要解决的问题之一吧。主要碰到的问题,基本上就是上面这些,其他的很多事苦力活,界面的设计需要耐心细 致,功能的开发调试,更需要一份清晰的开发思路。 在开发这个系统的过程中, 越来越感觉积累是很重要的东西 (前面开发的软件经验和代 码积累) ,开发的辅助工具 (如我的 Database2Sharp 代码生成工具) 也是必不可少, 每次能够在已有资源上有所创新,有所超越,是一个非常有趣的心理体验。 WinForm 界面开发之酒店管理系统 -- 控件篇 Posted on 2009-11-29 22:52 伍华聪 阅读 (4800) 评论 (9) 编辑 收藏 在上篇《 WinForm 界面开发之酒店管理系统 -- 开篇 》中介绍了一些界面的东西,本篇 开始抽丝剥茧,细致分析里面的控件组成,并公布相关的控件资源,以飨读者。 1 、按钮控件 首先介绍一个按钮控件,这个是一个 Vista 样式的控件,其代码是在 Codeproject 上 有的: http://www.codeproject.com/KB/buttons/VistaButton.aspx通过改变其颜色,就可以实现不同的效果,而且鼠标靠近或者离开都有特殊的效果,比 较酷。例如我加上颜色图片后,得到的效果如下所示: 2 、Tab 控件 在使用 Tab 控件做那个房间状态视图的时候,由于内置的 Tab 控件样式感觉不是很满 意,我参考过很多不一样的控件,我觉得比较好的一个是 Codeproject 上的一个中国 人在日本发表的一篇控件文章 : http://www.codeproject.com/KB/tabs/CustomizedTabcontrol.aspx ,控件的 界面大致如下。虽然我因为样式冲突的问题,最终没有使用上她的控件,不过我觉得是很不错的,由于 她的控件在 Fill 状态下有点问题,特意请教了她,并得到了她的最新控件代码,我上传 到博客上,给大家下载参考吧: http://files.cnblogs.com/wuhuacong/CustomTabcontrol.rar 。 我做的控件大致的思路是先设计一个窗口框架, 里面的 Tabpage 可是通过代码增加的, 由于客房的房间类型是动态变化,而不是固定的,如下图所示。我们每次 New 出一个 TabPage 的时候,把有图标的用户控件加载(下一个图)进去就可以了。下面这个是 Winform 的用户控件,它的职责就是获取数据库的房间信息,根据不同的 状态显示不同的图标, 然后动态创建, 每种房间类型有多少个房间, 就动态创建多少个。 如下图所示。另外我们还需要它绑定相关的业务菜单,根据不同的状态,禁用或者显示特定的菜单, 如下图所示。 这样我们在最终的界面上就少管很多事情,这样层层下去,各管各的事情,互不干扰。 这个控件会公布一些事件,让外部进行相关的操作,如下代码所示:代码 public delegate void ShowStatusHandler(RoomInfo roomIn fo); public ShowStatusHandler OnShowStatus = null ; 另外,它也会公布一些接口,给 Ower 对他进行相关的管理,主要是改变视图类型(大 图标、小图标、列表显示),改变房间状态(空闲、占用、预定等),以及强制刷新操 作。如下代码所示。 代码 /// /// 修改 ListView 的视图 /// /// public void ChangeViewType(View viewType) { this .listView1.View = viewType; BindData(); this .Refresh(); } /// /// 修改房间的状态显示 /// /// public void ChangeRoomStatus( string roomStatus) { this .RoomStatus = roomStatus; BindData(); this .Refresh(); } public void UpdateStatus() { BindData(); this .Refresh(); } 搞定了小的,现在开始搞大的了,就是该用户控件的 Owner 窗体,它负责很多个这样 的用户控件的创建、更新等操作。下面看看代码先。 代码 /// /// 更新所有房间的状态显示 /// public void UpdateStatus() { foreach (TabPage page in this .tabControl1.TabPages) { foreach (Control control in page.Controls) { RoomViewControl lvwControl = control as Ro omViewControl; if (lvwControl != null ) { lvwControl.UpdateStatus(); } } page.Refresh(); } } public void ChangeViewType(View viewType) { foreach (TabPage page in this .tabControl1.TabPages) { foreach (Control control in page.Controls) { RoomViewControl lvwControl = control as Ro omViewControl; if (lvwControl != null ) { lvwControl.ChangeViewType(viewType); } } page.Refresh(); } } public void ChangeRoomStatus( string roomStatus) { foreach (TabPage page in this .tabControl1.TabPages) { foreach (Control control in page.Controls) { RoomViewControl lvwControl = control as Ro omViewControl; if (lvwControl != null ) { lvwControl.ChangeRoomStatus(roomStatu s); } } page.Refresh(); } } 上面的代码, 其实就是遍历其 TabPage 中的控件,并判断是否特定的控件,然后进行 相关的操作,就是调用每一个控件公布的接口。 由于控件的变化,需要通知状态视图,进行相应的显示,如下图所示。 要实现动态的状态变化, 那么就需要注册状态变化的事件了,我们在构建该用户控件的 时候,注册它的变化事件相应即可。如下代码所示代码 private void Form1_Load( object sender, EventArgs e) { #region 根据不同的房间类型创建不同的 Tab 和房间视图 this .tabControl1.TabPages.Clear(); List roomTypeList = BLLFactory.Instance.GetAll(); foreach (RoomTypeInfo info in roomTypeList) { TabPage page = new TabPage(); page.Text = info.Name; page.Tag = info; RoomViewControl viewControl = new RoomViewCont rol(); viewControl.RoomType = info.Name; viewControl.Dock = DockStyle.Fill; viewControl.OnShowStatus = new RoomViewControl. ShowStatusHandler(OnShowStatus); page.Controls.Clear(); page.Controls.Add(viewControl); this .tabControl1.TabPages.Add(page); } #endregion } 下面的代码是就是事件响应代码,它的功能就是完成状态的更新显示, 以及房价费用的 显示。如下图所示。 代码 private void OnShowStatus(RoomInfo roomInfo) { decimal allMoney = 0.0M ; #region 更新消费记录 if (roomInfo != null ) { List consumerList = BLLFacto ry.Instance.GetAllConsumption(roomInfo.RoomNo); this .listView1.Items.Clear(); int i = 1; foreach (ConsumerListInfo info in consumerList) { ListViewItem item = new ListViewItem(i.ToS tring()); item.SubItems.Add(info.RoomNo); item.SubItems.Add(info.ItemName); item.SubItems.Add(info.Price.ToString( "C2 " )); item.SubItems.Add(info.Discount.ToString ()); item.SubItems.Add(info.DiscountPrice.ToStr ing( "C2" )); item.SubItems.Add(info.Quantity.ToString ()); item.SubItems.Add(info.Amount.ToString( "C2 " )); item.SubItems.Add(info.BeginTime.ToString ()); item.SubItems.Add(info.Waiter); item.SubItems.Add(info.Creator); if (info.Quantity < 0) { item.ForeColor = Color.Red; } this .listView1.Items.Add(item); allMoney += info.Amount; i++; } } #endregion #region 更新房间信息 FrmStatus dlg = Portal.gc.MainDialog.mainStatus; if (dlg != null ) { if (roomInfo != null ) { InitDisplayItems(dlg.DisplayItems, roomInf o, allMoney); dlg.UpdateContent(); } else { dlg.InitDisplayItems(); dlg.UpdateContent(); } } //Portal.gc.MainDialog.ShowMainStatusWin(); #endregion this .lblAmount.Text = string .Format( " 消费总金额: {0:C 2}" , allMoney); } 好了,描述与代码齐上,虽不齐整,但希望抛砖引玉能,给各位读者的思绪及灵感有一 个引桥般的铺垫,完毕收工。 WinForm 界面开发之酒店管理系统 -- 报表篇 Posted on 2009-12-14 20:55 伍华聪 阅读 (4059) 评论 (14) 编辑 收藏 报表模块几乎是各种大小管理系统都是必不可少的一个模块, 而往往报表都需要有数据 查看、打印、导出、数据汇总等方面,原本我在准备做酒店管理系统的时候,曾经考虑 过试用 ActiveReport 报表控件的, 因为我以前的送水管理系统就是采用这个来做报表 的,因此曾经写过一篇文章《 ActiveReport 报表开发 --- 谈谈 ActiveReport 的中文化 问题 》,提前为做中文报表做准备。 做 ActiveReport 的报表,大致需要下面几个步骤,设计好报表表现内容的文件,设计 一个通用的窗体用来实现 Preview 功能的报表查看窗体,再设计一个通用的报表函数 进行统一调用,如下所示: 代码 /// /// 提供通用的打印函数 /// /// /// public void PrintPreview(ActiveReport3 rpt1, ref DataS et ds) { try { FrmPrintPreview frm = new FrmPrintPreview(); frm.StartPosition = FormStartPosition.CenterSc reen; // frm.Dock = DockStyle.Fill; // frm.WindowState = FormWindowState.Maximized; frm.Show(); rpt1.DataSource = ds.Tables[ 0]; // rpt1.DataMember = cmbQuery.Text; rpt1.Run(); frm.arvMain.Document = rpt1.Document; } catch (Exception ex) { MessageUtil.ShowError(ex.Message); } } 这样通过报表文件和界面的数据源,就可以实现报表的显示了。 不过由于这种方式,必须为每个报表都设计一个报表文件,如下所示。最后得到的报表界面大致如下所示。 使用这个 ActiveReport 来实现我的报表功能的话,这样如果我的报表非常多,那么这 个工作量就比较吓人了, 最后还是放弃了这种方式,采用了改造我的分页控件的方式来 实现,既可以方便数据的展示,有可以继承了报表预览打印、导出等功能,而且这样做 的好处就是, 我省却了不用设计那么多报表格式文件的时间, 并且总体效果也非常不错。 在我前面的文章有介绍过 Winform 分页控件的内容《 WinForm 界面开发之 “分页控 件”》,该控件集成了分页、导出 Excel 、打印、右键菜单等操作,我唯一需要改造的 主要就是不用分页功能, 然后在增加一些细微的修改就可以了。 先看看我的酒店管理系 统中的一些报表界面吧。总体上就是我们一般报表所需要的功能。其中报表打印预览可以设置报表标题,打印的 列也可以设定, 有一些字段的汇总功能,而且这样的报表基本上不需要额外的代码就能 实现(相对分页控件来说)。 下面我们来看看我这个控件大致的代码实现。 首先在 Form 窗体初始化的时候,指定该控件所需要的一些载体,如空的菜单控件、空 的 Progressbar 进度条控件。 代码 private void KFTopTradeReport_Load( object sender, Even tArgs e) { InitDictItem(); this .winGridView1.ProgressBar = this .toolStripProg ressBar1.ProgressBar; this .winGridView1.AppendedMenu = this .contextMenuS trip1; this .dtStart.Value = Convert.ToDateTime(DateTime.N ow.ToString( "yyyy-MM-dd 00:00:00" )); this .dtEnd.Value = Convert.ToDateTime(DateTime.Now.ToString( "yyyy-MM-dd 23:59:59" )); } 然后在所需要的地方,如按钮、更新操作,调用下面的函数就可以了。 代码 private void BindItemCateData() { #region 添加别名解析 this .winGridView1.AddColumnAlias( "Rank" , " 名次 " ); this .winGridView1.AddColumnAlias( "ItemName" , " 项目 名称 " ); this .winGridView1.AddColumnAlias( "Price" , " 商品单价 " ); this .winGridView1.AddColumnAlias( "Quantity" , " 销售 数量 " ); this .winGridView1.AddColumnAlias( "OriginalAmount " , " 销售总额 " ); this .winGridView1.AddColumnAlias( "OfferMoney" , " 优 惠金额 " ); this .winGridView1.AddColumnAlias( "Amount" , " 折后金额 " ); #endregion #region 条件检索 SearchCondition condition = new SearchCondition(); condition.AddCondition( "ItemType" , this .txtStatist icItem.Text, SqlOperator.Like) .AddCondition( "BeginTime" , this .dtStart.Value.ToSt ring(), SqlOperator.MoreThanOrEqual) .AddCondition( "BeginTime" , this .dtEnd.Value.ToStri ng(), SqlOperator.LessThanOrEqual); string filter = condition.BuildConditionSql(); int topCount = Convert.ToInt32( this .txtTopCount.Te xt); #endregion DataTable dt = BLLFactory.Instance.G etTopTradeReport(topCount, filter); int i = 1; foreach (DataRow row in dt.Rows) { row[ "Rank" ] = i++; // 修改名次信息 } #region 增加汇总信息 if (dt.Rows.Count > 0) { DataRow dr = dt.NewRow(); dr[ "ItemName" ] = string .Format( " 项目数 :{0}" , dt. Rows.Count); decimal Price = 0M; decimal Quantity = 0M; decimal OriginalAmount = 0M; decimal OfferMoney = 0M; decimal Amount = 0M; foreach (DataRow row in dt.Rows) { Price += Convert.ToDecimal(row[ "Price" ]); Quantity += Convert.ToDecimal(row[ "Quantit y" ]); OriginalAmount += Convert.ToDecimal(row[ "O riginalAmount" ]); OfferMoney += Convert.ToDecimal(row[ "Offer Money" ]); Amount += Convert.ToDecimal(row[ "Amount" ]); } dr[ "Price" ] = Price; dr[ "Quantity" ] = Quantity; dr[ "OriginalAmount" ] = OriginalAmount; dr[ "OfferMoney" ] = OfferMoney; dr[ "Amount" ] = Amount; dt.Rows.Add(dt.NewRow()); dt.Rows.Add(dt.NewRow()); dt.Rows.Add(dr); } #endregion this .winGridView1.DataSource = dt.DefaultView; this .winGridView1.PrintTitle = Portal.gc.gAppUni t + " -- " + " 客房按项目类别统计报表 " ; } 以上代码,和分页控件一样,就是对显示的字段进行中文的转义显示,构造查询条件并 检索数据,汇总报表内容,然后绑定到自定义控件上,就可以了。 其中检索的代码大致如下所示就可以了,返回的 DataTable ,当然,如果你的数据源 是实体类集合,如 List() 的格式数据源,一样正常显示的。 代码 /// /// 获取贸易排行报表 /// /// /// public DataTable GetTopTradeReport( int topCount, strin g condition) { string sql = string .Format( @"select top {0} '0' a s Rank, ItemName, Price, sum(Quantity) Quantity,sum(Price*Quantity) OriginalAmount,sum ((Price-DiscountPrice)*Quantity) OfferMoney,sum(Amount) Amount from KF_ConsumerList {1} And ItemName <> ' 部分结账 ' group by ItemName,Price order by Sum(Quantity) DESC" , topCoun t, condition); return base .SqlTable(sql); } 我们说看到的就是,基本上所有的报表展示,都不需要关注报表预览的问题,只是关注 如何在 GridView 控件中显示就可以了,因为打印和导出,都是集中管理的。 最后,呈上我所封装的控件,大家可以直接过来使用就可以了,没有任何限制。 分页控件、报表显示控件(已下载) Database2Sharp2009 代码生成工具 Posted on 2006-08-26 23:59 伍华聪 阅读 (1230) 评论 (2) 编辑 收藏 NHibernate 、Castle-ActiveRecord 和 PetShop 架构的 C# 代码生成工具 Database2Sharp 文件大小 : 5655KB 更新时间 : 2009-5-7 下载地址 : http://www.iqidi.com/Download/Database2SharpSetup.rar 在线帮助: 2009-5-7 修改:增加企业级别的界面自动生成,集成在 Enterprise Library 架构中, 并更新 EditControl 、SearchControl 和分页控件的 Web 界面层控件,并修正一些小 问题。版本升级为 5.0 2009-2-10 修改:更新 EditControl 和 SearchControl 这两个 Web 界面层控件,并修正一些小问题。 2009-1-5 修改:增加 Java 框架( Ibatis+Spring+struts+Extjs) 代码的生成;添加 Java 实体类快速生成。 2008-12-28 修正:修正对 SQL Server 2005 部分数据库显示有问题的错误。完善 Enterprise Library 代码生成。 产品说明如下: ★软件功能 深田之星 Database2Sharp ,是一个 NHibernate 、Castle-ActiveRecord 、 Enterprise Library 和 PetShop 架构的 C# 代码生成工具,提供了对 MS Sql2000 、 MS Sql2005 、 Oracle 、Mysql 、Access 的支持;可以生成各种架构代码,导出数据 库文档、浏览数据库架构、查询数据、生成 Sql 脚本等。 主要的功能如下: 1、 Castle 的 ActiveRecord 代码生成功能,准确生成各种关系。 2、 NHibernate 代码生成,生成相应的实体类和 HBM 文件。 3、PetShop 架构代码的生成(多种形式),直接生成所需的解决方案,为你封装 了功能强大的基础类库和数据访问基类。 4、Web 界面自动生成功能,生成列表、增加、修改界面代码和后台代码,包括烦 琐的绑定数据和赋值语句代码,为你封装具有漂亮界面和布局的编辑、查看、添加和分 页控件,随手可得,率性而为。 5、Enterprise Library 代码生成,生成整个项目工程框架,包含实体类、数据 访问类、业务类、 Asp.net 页面类,利用泛型及缓存机制,良好的架构极大简化代码, 强大完善的基类使你甚至不用编写一行代码。 6、可以查看数据库的信息和实现查询分析器的功能,可以很快地看看数据库的信 息。 7、实体类快速生成,提供各种常用的实体类代码生成,直接在窗体中显示,并用 语法高亮显示,方便拷贝使用。 8、数据库文档的生成,在模块设计中非常有用,谁想去写描述和字段名称,类型 的对应关系呢? 9、测试数据脚本的生成,在外键非常复杂的表中,我们想添加一条记录都是很容 易哦,这个可以更加表的外键关系调整插入测试数据的顺序,很周到吧。 10 、增加 Select 、Update 、Insert 和 Delete 基本语句代码的生成,方便您直接 在代码中使用。 11 、增加 Java 框架( Ibatis+Spring+struts+Extjs) 代码的生成;添加 Java 实体类快速生成。 12 、增加企业级别的界面自动生成,集成了标准、美观、统一的界面元素。 软件提供非常方便的数据库配置管理功能,用户根据不同的数据库类型输入所需 的信息即可配置好不同的数据库; 所有的代码生成及其他功能可以在不同的数据库中进 行切换;软件支持表名称别名修改,字段别名修改等(用于代码的引用),对于是中文 的数据库表名称和字段名称,非常方便。 ★系统需求 深田之星 Database2Sharp 使用 C# 2.0 开发 适运行在 Microsoft WindowsNT/2000/XP/2003 等平台 ,但必须安装有 .Net 2.0 平台. 如果您的计算机不能运行本程序,强烈建议您下载 Microsoft .NET Framework 2.0 运行库。 MicroSoft .NET Framework 2.0 官方下载地址:http://www.microsoft.com/downloads/info.aspx?na=90&p=&SrcDisplayLa ng=zh-cn&SrcCategoryId=&SrcFamilyId=0856eacb-4362-4b0d-8edd-aab 15c5e04f5&u=http://download.microsoft.com/download/5/6/7/567758a3- 759e-473e-bf8f-52154438565a/dotnetfx.exe 代码生成操作 NHibernate 、Castle-ActiveRecord 、Enterprise Library 和 PetShop 架构的 C# 代码 生成操作提供了统一的操作方式, 步骤基本上一样。 首先在数据库或者表节点上使用右键菜单, 在弹出的菜单中选择 “代码生成 ”-》“**** 代码生成 ”(也可以选择其他方式的代码生成),如 下图所示。在出现的向导视图中, 选择需要生成代码的数据库, 如下图所示, 然后继续下一步操作。在向导的左边选择需要生成代码的表(可以双击鼠标选择 ) ,如下图所示,然后单击下一 步。在出现的向导视图中输入生成代码所需的一些基本信息, 如下图所示, 然后单击下一步。在接下来的视图中, 系统确认用户的一些信息, 如果确定并想生成所需的代码, 单击 “完 成”即可。硬件接口开发之 Modem 来电显示 Posted on 2009-09-06 23:07 伍华聪 阅读 (3236) 评论 (3) 编辑 收藏 本文介绍下如何实现 Modem 的来电显示的功能。 Modem 的来电显示是在我最早 的送水管理软件中实现的,大概是 05 年完成的,由于 Modem 的成本比较低(普通的 在 100 元之内),而来电显示功能在送水管理软件中比较需要,因此是一项性价比比 较好的功能。 随着时间的推移, Modem 的生产越来越少,而支持来电显示模块的 Modem 就更 少一些了,取而代之是功能比较专一的来电显示盒横行天下,一般 USB 来电显示盒都 可以支持 2 路、 4 路、 8 路等等,但是 USB 来电显示盒较贵, Modem 由于性价比比 较好, 市场上依然还继续出现。 本篇主要介绍如何实现 Modem 的来电显示, 下篇将继 续介绍基于 USB 的来电显示盒的接口开发。目前在我的送水软件中,为了兼顾两者的 好处,一般是集成了两个硬件的功能,随便选一个硬件都可以使用来电显示功能。 看看 Modem 来电显示的应用场景,在送水软件中,链接一台电话( Modem 只能 连接一台电话),当有客户的电话接入的时候,软件会提示用户的信息,并进入下订单 的界面,如下所示。这样非常方便业务员的信息输入,同时还可以调出更多客户的相关信息,如客户的 账号信息,购买记录作为默认这次订单的操作等,达到优质服务的目的。如下这个改进 版的送水软件的来电显示功能就提供了客户财务信息,默认上次的购买记录等信息。Modem 的来电显示功能需要下面所需的前提条件。 一、 实现来电显示的系统最小需求。 1 . 你的固定电话已经向电信局申请开通了来电显示功能,若没有申请,即使 MODEM 支持也是不行的。 2 . 安装了 MODEM 的专用驱动软件(注意不要使用 Windows 如 XP 自带的驱 动程序)。 3 . MODEM 支持来电显示。不管是内置 MODEM ,外置 MODEM ,只要它支持 来电显示即可。二、怎么判断 MODEM 是不是支持来电显示? 1. 打开超级终端,随便输入一个连接名称,比如 TEST 。 2. 按确定后,下个窗口中,看到你的 MODEM ,不要输入电话号码。再下一步。 3. 在这个窗口中按取消。 4. 这样一个可以输入的空白窗口就有了。 输入: AT 回车 如果出现 OK,说明 MODEM 支持 AT 指令,不然,其他也不用试了。 然后输入下面的命令 (每条前面加上 AT),只要一条反应有 OK,就说明 MODEM 本身芯片支持来电显示。 #CID=1 %CCID=1 +VCID=1 #CC1 *ID1 三、 Modem 驱动安装的问题 如果您的操作系统是 Windows 2K/XP ,当安装 MODEM 驱动程序的时候, 会自 动安装操作系统自带的 MODEM 驱动,而此驱动程序只能支持数据传送(即只能用来 拨号上网,而没有传真和语音功能)。所以您必须升级 MODEM 的驱动程序,并且在 升级的时候手工指定到跟随 MODEM 的光盘中的驱动程序(而不让系统自己搜索)。 并且在升级完之后重新启动系统。 下面介绍如何在 C# 中实现来电显示模块的功能。 Modem 的来电显示需要一个和串口打交道的控件 Apax Control ,它是一个 ActiveX 的控件,原则上可以应用在任何语言中,本文只介绍在 C# 中的应用。 首先需要注册 ActiveX 控件,然后在开发 IDE 中引用控件,把控件拖动到界面上,如 下所示。然后生成目录会多出几个文件,如下所示。 在代码中,我们可以再 Form_Load 方法中初始化控件的一些属性。如下代码所示。 // 设置来电显示控件的属性 this .axApax1.EnableVoice = true ; this .axApax1.Visible = false ; this .axApax1.TapiStatusDisplay = false ; this .axApax1.TapiAnswer(); this .axApax1.OnRing += new EventHandler(axApax 1_OnRing); this .axApax1.OnTapiCallerID += new IApaxEvents _OnTapiCallerIDEventHandler(axApax1_OnTapiCallerID); 然后我们就只需要关注 axApax1_OnRing 和 axApax1_OnTapiCallerID 处理函数 了,如下代码所示 /// /// 来电响铃后的处理事件 /// private void axApax1_OnRing( object sender, EventArgs e) { // 由于此函数会触发 3 次,故使用 axApax1_OnTapiCallerID 事件 进行处理。 } /// /// 来电响铃后的处理事件 /// private void axApax1_OnTapiCallerID( object sender, IAp axEvents_OnTapiCallerIDEvent e) { ComingCallUtil.PlaySound(); string oldCaption = this .Text; this .Text = " 最近一个客户来电号码: " + this .axApax1.Cal lerID; string phoneNumber = this .axApax1.CallerID; this .axApax1.Close(); this .axApax1.TapiAnswer(); Portal.gc.CustomerComingCall(phoneNumber); this .Text = oldCaption; } 最后关闭程序的时候要释放资源,否则会出现 COM 错误的,代码如下所示。 /// /// 清理所有正在使用的资源。 /// protected override void Dispose( bool disposing) { if ( this .axApax1 != null ) { this .axApax1.Dispose(); // 必须执行,否则出错 } Portal.gc.Dispose(); // 伍华聪 if (disposing) { if (components != null ) { components.Dispose(); } } base .Dispose(disposing); } 硬件接口开发之 USB 电话录音盒来电显示 Posted on 2009-09-08 22:04 伍华聪 阅读 (2348) 评论 (5) 编辑 收藏 除了上篇《 硬件接口开发之 Modem 来电显 》介绍的 Modem 来电显示,还可以采用 USB 电话录音盒来进行来电显示和电话录音功能,本文介绍如何在 C# 中实现该功能。 首先我们看看我是如何在我的软件中集成 USB 电话录音盒的功能, 先对 USB 电话录音 盒的功能和应用场景有一个总体的认识先。 我的送水软件可以支持两路及以上多路的 来电显示 和电话录音 功能,当用户有新的来 电,系统会检测数据库是否存在该电话的用户,如果有那么调出该用户的资料(包括上 次订购产品信息),否则提示操作员建立新客户的资料,并记录该次的电话号码。这些 来电显示功能非常方便操作人员进行业务操作,点击下鼠标就可以完成客户的订单。图 1 来电自动记录并在状态栏显示相关来电信息图 2 来电显示功能调出已有客户资料,并默认调出上次订购信息 实现来电显示的系统最小需求 1 . 你的固定电话已经向电信局申请开通了来电显示功能,若没有申请,即使有 软件支持也是不行的。 2 . 安装了指定厂商的录音与来电弹屏设备(包括硬件和软件)。 3 . 正确安装深田之星送水管理系统网络版软件。但前提是需要一些前提条件,下面是实现来电显示的最小要求和一些相关问题。 图 3 安装所需的来电显示设备驱动图 4 电话录音文件,用户可以选择指定的文件进行回播 使用来电显示和电话录音设备的好处 : 一、总体优势 1 、先进的语音设备 录音设备采用 USB 或者 PCI 接口,最多可支持达 256 路电话同时录音、同 时弹出客户资料; 2 、强大的电话录音功能 拥有自主知识产权的专用压缩算法, 录音数据量小、 通话清晰, 每线通话数据 量只有 5.5 兆 / 小时, 即一个 120G 的硬盘可以单线连续录音 21000 小时。采用专用的压缩算法, 可以有效的防止他人恶意窃听、篡改录音数据,确保资料安全。可随时将重要的录音资 料进行备份。3 、录音回听 用户可根据自己的权限回听授权的录音资料, 以便对重要通话进行分析、 做笔 记等。可在本地回听,也可通过局域网或者 Internet 等广域网进行远程回听,极大地 方便了经常出差在外或者公司部门分布较广的公司管理层人员使用。 4 、远程同步监听 特定用户可以在本地或者通过局域网或者 Internet 等广域网进行远程实时电 话通话情况查看、 实时通话监听等。 及时掌握员工对外联系状况, 及时跟进重要客户等。 5 、来电弹出 客户来电, 系统自动根据来电号码调出客户资料, 在摘机接听电话前就可以及 时掌握来电客户的基本信息,沟通历史等,给员工一种亲切感,同时客户可以得到最及 时、最准确、最周到的服务。 6 、记录未接来电不放过一个来电信息,保障与客户的即时沟通。 7 、电话集中录音,有效防止录音资料丢失; 以上的电话录音盒功能介绍是厂商提供的内容, 不过我们一般就是使用来做来电弹出业 务窗口、电话录音这两个功能了,其他的都可以不管。那么我们如何在我们开发的程序 中集成,也就是使用代码如何完成和硬件的接口开发呢。 由于这个来电显示的录音设备开发包也是一个 ActiveX 的控件来的,因此和操作其他 ActiveX 控件一样,我们在工具栏中加入控件,然后拖动到界面上就可以了,注意为了 避免给客户产生不好的影响,把控件设置为隐藏即可。 1)首先在 Form_OnLoad 函数中初始化硬件信息和事件,如下所示 private void InitCallControl() { this .axTMNC_OCXX1.OnConnect += new AxTMNC_OCX.ITM NC_OCXXEvents_OnConnectEventHandler(axTMNC_OCXX1_OnConnect); this .axTMNC_OCXX1.OnDisConnect += new AxTMNC_OCX. ITMNC_OCXXEvents_OnDisConnectEventHandler(axTMNC_OCXX1_OnDisCo nnect); this .axTMNC_OCXX1.OnCallIn += new AxTMNC_OCX.ITMN C_OCXXEvents_OnCallInEventHandler(axTMNC_OCXX1_OnCallIn); this .axTMNC_OCXX1.OnCallOut += new AxTMNC_OCX.ITM NC_OCXXEvents_OnCallOutEventHandler(axTMNC_OCXX1_OnCallOut); this .axTMNC_OCXX1.OnNoAnswer += new AxTMNC_OCX.IT MNC_OCXXEvents_OnNoAnswerEventHandler(axTMNC_OCXX1_OnNoAnswer); } private void MainToolWindow_Load( object sender, EventArgs e) { #region 来电显示设置内容 try { host = config.AppConfigGet( "CallHost" ); channel = config.AppConfigGet( "CallChannel" ); UseCallDevice = Convert.ToBoolean(config.AppC onfigGet( "UseCallDevice" )); } catch (Exception ex) { LogHelper.Error(ex); MessageUtil.ShowError( " 您没有配置来电显示需要的 Ca llHost 和 CallChannel 参数! " ); return ; } try { if (UseCallDevice) { InitCallControl(); this .axTMNC_OCXX1.EC_Connect(host); //this.axTMNC_OCXX1.EC_StartMonitor(prot); string content = " 来电显示设备已就绪 " ; Portal.gc.MainDialog.RefreshComingCallSta tus(content); } else { string content = " 来电设备未启用 " ; Portal.gc.MainDialog.RefreshComingCallSta tus(content); } } catch (Exception ex) { LogHelper.Error(ex); string content = " 设备未链接 ( 错误 )" ; Portal.gc.MainDialog.RefreshComingCallStatus (content); } #endregion } 2)然后就是实现上面的几个委托事件了,代码如下所示。 private void axTMNC_OCXX1_OnNoAnswer( object sender, Ax TMNC_OCX.ITMNC_OCXXEvents_OnNoAnswerEvent e) { /**//* 说明:当有电话打入,且无人接听的情况,即未接来电信息。 LocalNum :本地号码。 CallID :未接来电号码。 注:此来电号码在 OnCallIn 事件中也会发送过来,但是不同的是, 只有当来电无人接听时 才会产生 OnNoAnswer 事件。 */ string content = string .Format( " 未接来电 :{0} 本地号码 : {1}" , e.callID, e.localNum); Portal.gc.MainDialog.RefreshComingCallStatus(conte nt); } private void axTMNC_OCXX1_OnCallOut( object sender, AxT MNC_OCX.ITMNC_OCXXEvents_OnCallOutEvent e) { string content = string .Format( " 拨出号码 :{0} 本地号码 : {1}" , e.callOutNum, e.localNum); Portal.gc.MainDialog.RefreshComingCallStatus(conte nt); } private void axTMNC_OCXX1_OnCallIn( object sender, AxTM NC_OCX.ITMNC_OCXXEvents_OnCallInEvent e) { string content = string .Format( " 来电号码 :{0} 本地号码 : {1}" , e.callInNum, e.localNum); Portal.gc.MainDialog.RefreshComingCallStatus(conte nt); Portal.gc.DealWithComingCall(e.callInNum); } private void axTMNC_OCXX1_OnDisConnect( object sende r, AxTMNC_OCX.ITMNC_OCXXEvents_OnDisConnectEvent e) { string content = " 设备未连接 " ; Portal.gc.MainDialog.RefreshComingCallStatus(conte nt); } private void axTMNC_OCXX1_OnConnect( object sender, AxT MNC_OCX.ITMNC_OCXXEvents_OnConnectEvent e) { string content = " 设备已就绪 " ; Portal.gc.MainDialog.RefreshComingCallStatus(conte nt); } Winform 下动态执行 JavaScript 脚本获取运行结果,谈谈网站的自动登录及资料 获取操作 Posted on 2010-11-08 17:10 伍华聪 阅读 (2015) 评论 (6) 编辑 收藏 为了有效阻止恶意用户的攻击,一般登录都会采用验证码方式方式处理登录,类似 QQ 的很多产品的验证码处理,但在一些 OA 系统中,系统通过非对称加密方式来处理登录 的密码信息, 登录页面每次提供对密码进行加密的公钥是不同的, 因此如果要模拟登录, 就需要先获取公钥, 然后根据公钥把输入的密码加密, 然后通过 POST 提交给服务器进 行验证登录。由于公钥是页面刷新变化的,而加密是通过 Javascript 脚本进行加密, 如下面的登录页面源码所示。 下一篇继续介绍下证件套打的打印功能。 Web 打印的解决方案之证件套打 Posted on 2010-01-20 12:14 伍华聪 阅读 (3021) 评论 (3) 编辑 收藏 由于以前未接触过套打, 一直觉得套打是一个比较神秘和麻烦的事情, 因为打印机的位 置总是需要调整的,你总不能硬编码吧?但是如果位置可调,有需要直观一些来处理, 那就比较麻烦了。 在前面介绍过《 Web 打印的解决方案之普通报表打印 》的一片文章中提到过那个打印 控件 Lodop ,做起套打来感觉还是挺方便的,至少位置调整界面不需要自己弄,位置 嘛,也提供了自动保存的功能,不需要理会。 一般的套打,包含了几部分操作:打印预览、打印维护、打印设计。 打印预览和打印维护是面向终端用户的,打印维护是指内容不能修改删除、 但位置可以 调整,给不同的打印机不同的尺寸打印提供调整位置的可能性。 打印设计是面向开发人员的, 开始需要通过这个功能来设计好套打的界面, 就是根据套 打证件的背景图片,大致摆放好各个内容的位置。 大致的实现代码如下所示: ", message).Replace("\r\n", ""); control.Page.RegisterStartupScript("", script); } /**//// /// 提示信息并关闭窗口 /// /// 当前的页面 /// 提示信息 public static void AlertAndClose(Control control, string message) { string script = string.Format("", message).Replace("\r\n", ""); control.Page.RegisterStartupScript("", script); } /**//// /// 将错误信息记录到事件日志中 /// /// 文本信息 public static void LogError(string errorMessage) { logger.Error(errorMessage); } /**//// /// 将错误信息记录到事件日志中 /// /// 错误对象 public static void LogError(Exception ex) { try { logger.Error(ex.Message + "\n" + ex.Source + "\n" + ex.StackTrace); } catch { } } /**//// /// 弹出提示信息 /// /// key /// 提示信息 /// 当前请求的 page public static void Alert(string key, string message, Page page) { string msg = string.Format("", message); page.RegisterStartupScript(key, msg); } /**//// /// 弹出提示信息 /// /// /// public static void Alert(string message, Page page) { Alert("message", message, page); } /**//// /// 定位到指定的页面 /// /// 目标页面 public static void GoTo(string GoPage) { HttpContext.Current.Response.Redirect(GoPage); } /**//// /// 定位到指定的页面 /// /// 当前请求的 page /// 目标页面 public static void Location(Control control, string page) { string js = ""; control.Page.RegisterStartupScript("", js); } /**//// /// 提示信息并定位到指定的页面 /// /// 当前请求的 page /// 目标页面 /// 提示信息 public static void AlertAndLocation(Control control, string page, string message) { string js = ""; control.Page.RegisterStartupScript("", js); } /**//// /// 关闭页面,并返回指定的值 /// /// /// public static void CloseWin(Control control, string returnValue) { string js = ""; control.Page.RegisterStartupScript("", js); } /**//// /// 获取 Html 的锚点 /// /// /// /// public static HtmlAnchor GetHtmlAnchor(string innerText, string href) { HtmlAnchor htmlAnchor = new HtmlAnchor(); htmlAnchor.InnerText = innerText; htmlAnchor.HRef = href; return htmlAnchor; } /**//// /// 判断输入的字符是否为数字 /// /// /// public static bool IsNumerical(string strValue) { return Regex.IsMatch(strValue, @"^[0-9]*$"); } /**//// /// 判断字符串是否是 NULL 或者 string.Empty /// /// /// public static bool IsNullorEmpty(string text) { return text == null || text.Trim() == string.Empty; } /**//// /// 获 取 DataGrid 控 件 中 选 择 的 项 目 的 ID 字 符 串 ( 要 求 DataGrid 设 置 datakeyfield="ID") /// /// 如果没有选择 , 那么返回为空字符串 , 否则返回逗号分隔的 ID 字符串 (如 1,2,3) public static string GetDatagridItems(DataGrid dg) { string idstring = string.Empty; foreach (DataGridItem item in dg.Items) { string key = dg.DataKeys[item.ItemIndex].ToString(); bool isSelected = ((CheckBox) item.FindControl("cbxDelete")).Checked; if (isSelected) { idstring += "'" + key + "'" + ","; // 前后追加单引号,可以其他非数值的 ID } } idstring = idstring.Trim(','); return idstring; } /**//// /// 设置下列列表控件的 SelectedValue /// /// DropDownList 控件 /// SelectedValue 的值 public static void SetDropDownListItem(DropDownList control, string strValue) { if (!IsNullorEmpty(strValue)) { control.ClearSelection(); ListItem item = control.Items.FindByValue(strValue); if (item != null) { control.SelectedValue = item.Value; } } } } Web 界面层操作的辅助类测试代码 private void btnShowError_Click(object sender, EventArgs e) { try { throw new Exception(" 测试错误 "); } catch (Exception ex) { UIHelper.ShowError(this, ex, false); return; } } private void btnAlert_Click(object sender, EventArgs e) { UIHelper.Alert(" 这是一个提示信息 ", this); } .NET 开发不可不知、不可不用的辅助类(三)(报表导出 --- 终结版) Posted on 2007-12-03 20:47 伍华聪 阅读 (2533) 评论 (2) 编辑 收藏 .NET 导出报表一般是采用导出 Excel 报表的方式输出内容。而这又分为两种方式:使 用 Excel 模板方式和使用网页输出 Excel 格式两种。 首先介绍简单的一种,网页输出 Excel 内容,这种不需要引用 Excel 的程序集。 /// /// 报表导出辅助类 /// public class ExportToExcel { 字段信息 public ExportToExcel() { } /// /// 带参数的构造函数 /// /// 导出的 Excel 文件名 /// 源数据 DataTable /// 报表的抬头 public ExportToExcel( string fileName, DataTable sourceTable, strin g title) { this .fileName = fileName; this .sourceTable = sourceTable; this .title = title; } public void ExportReport() { if (SourceTable == null || SourceTable.Rows.Count == 0) { return ; } DataGrid dataGrid = new DataGrid(); dataGrid.DataSource = sourceTable; dataGrid.DataBind(); HttpResponse Response = HttpContext.Current.Response; Response.Clear(); Response.Buffer = true ; Response.AddHeader(C_HTTP_HEADER_CONTENT, C_HTTP_ATT ACHMENT + HttpUtility.UrlEncode(fileName + ".xls")); Response.ContentType = C_HTTP_CONTENT_TYPE_EXCEL; Response.ContentEncoding = Encoding.GetEncoding("gb2312"); Response.Charset = charSet; StringWriter oStringWriter = new StringWriter(); HtmlTextWriter oHtmlTextWriter = new HtmlTextWriter(oString Writer); dataGrid.RenderControl(oHtmlTextWriter); string str = oStringWriter.ToString(); int trPosition = str.IndexOf("", 0); string str1 = str.Substring(0, trPosition - 1); string str2 = str.Substring(trPosition, str.Length - trPosition); string str3 = "\r\n\t"; str3 += "\r\n\t\t" + title + ""; str3 += "\r\n\t"; Response.Write(str1 + str3 + str2); Response.End(); } } 使用时候代码如下: private void btnExport2_Click( object sender, EventArgs e) { DataTable table = SelectAll().Tables[0]; ExportToExcel export = new ExportToExcel("TestExport", tabl e, "TestExport"); export.ExportReport(); } public static DataSet SelectAll() { string sqlCommand = " Select ID, Name, Age, Man, CONVERT(C HAR(10), Birthday ,120) as Birthday from Test"; DataSet ds = new DataSet(); string connectionString = "Server=localhost;Database=Test;uid =sa;pwd=123456"; SqlDataAdapter adapter = new SqlDataAdapter(sqlCommand, co nnectionString); adapter.Fill(ds); return ds; } 另外一种就是先定义好 Excel 模板,然后输出指定格式的内容, 这些内容通过开始单元 格名称定位,然后写入内容,但是这种功能比较强大,输出的 Excel 内容也比较整齐。 1. 首先在 Web.Config 中配置下 2. 创建一个 Excel 模板文件,如下图所示,当然这个是简单的 Excel 模板,你可以定 义很复杂 3. 在网站的根目录中创建一个 Temp 目录,给 EveryOne 读写权限, 当然你也可以给 AuthenticatedUsers 4. 辅助类代码 /**//// /// 报表导出基类 /// public abstract class BaseReport { 变量及属性 #region 变量及属性 protected const string C_HTTP_HEADER_CONTENT = "Content-Disposition"; protected const string C_HTTP_ATTACHMENT = "attachment;filename="; protected const string C_HTTP_INLINE = "inline;filename="; protected const string C_HTTP_CONTENT_TYPE_EXCEL = "application/ms-excel"; protected const string C_HTTP_CONTENT_LENGTH = "Content-Length"; protected const string C_ERROR_NO_RESULT = "Data not found."; protected string CharSet = "utf-8"; protected string fileName; protected string sheetName; //表名称 private ExcelHelper excelHelper; #endregion public BaseReport() { excelHelper = new ExcelHelper(false); } /**//// /// 打开 Excel 文件和关闭 Excel /// /// 返回 OK 表示成功 protected virtual bool OpenFile() { return excelHelper.OpenFile(fileName); } /**//// /// 关闭工作薄和 excel 文件 /// protected virtual void CloseFile() { excelHelper.stopExcel(); } /**//// /// 导出 EXCEL 文件 /// protected virtual void ExportFile() { string tempFileName = HttpContext.Current.Request.PhysicalApplicationPath + @"Temp\" + sheetName.Replace(".xls", ""); string SaveFileName = tempFileName + DateTime.Now.ToLongDateString() + DateTime.Now.ToLongTimeString().Replace(":", "-") + ".xls"; excelHelper.SaveAsFile(SaveFileName); CloseFile(); HttpResponse Response = HttpContext.Current.Response; Response.Clear(); Response.Buffer = true; Response.AddHeader(C_HTTP_HEADER_CONTENT, C_HTTP_ATTACHMENT + HttpUtility.UrlEncode(DateTime.Now.ToLongDateString() + sheetName)); Response.ContentType = C_HTTP_CONTENT_TYPE_EXCEL; Response.ContentEncoding = Encoding.GetEncoding("gb2312"); Response.Charset = CharSet; Response.WriteFile(SaveFileName); Response.Flush(); Response.Clear(); File.Delete(SaveFileName); } /**//// /// 填充表单数据到 excel 中 /// /// 定义的首个 Cell 名称 /// 数据表 Datatable protected virtual void FillCell(string GotoCell, DataTable dt) { int BeginRow = 2; int RowCount = dt.Rows.Count; Range rgFill = excelHelper.GotoCell(GotoCell); if (RowCount > BeginRow) { excelHelper.InsertRows(rgFill.Row + 1, RowCount - BeginRow); // 从定位处 的下一行的上面插入新行 } //Fill if (RowCount > 0) { excelHelper.DataTableToExcelofObj(dt, excelHelper.IntToLetter(rgFill.Column) + rgFill.Row.ToString(), false); } } private void AppendTitle(string titleAppendix) { if (titleAppendix != null && titleAppendix != string.Empty) { try { excelHelper.AppendToExcel(titleAppendix, "Title"); } catch (Exception ex) { throw new Exception(" 您没有指定一个 Title 的单元格 ", ex); } } } /**//// /// 写入内容 /// public virtual void ExportExcelFile() { ExportExcelFile(string.Empty); } /**//// /// 写入内容并追加标题内容 /// /// 追加在 Title 后面的内容 (一般如年月份) public virtual void ExportExcelFile(string titleAppendix) { try { OpenFile(); AppendTitle(titleAppendix); FillFile(); ExportFile(); } catch //(Exception ex) { CloseFile(); throw; } } protected virtual void FillFile() { } } /**//// ///通用的报表导出类 /// /// /// /// DataTable dt = InitTableData(); //InitTableData 为自定义获取数据表的函数 /// CommonExport report = new CommonExport(dt, " 架空线 .xls", "Start"); //Start 是 Excel 一个单元格名称 /// report.ExportExcelFile(); /// /// public class CommonExport : BaseReport { private DataTable sourceTable; private string startCellName; /**//// /// 构造函数 /// /// 要导出的 DataTable 对象 /// 相 对 于 根 目 录 的 文 件 路 径 , 如 Model/Test.xls /// 开始的单元格名称 public CommonExport(DataTable sourceTable, string excelFileName, string startCellName) { fileName = Path.Combine(HttpContext.Current.Request.PhysicalApplicationPath, excelFileName); sheetName = Path.GetFileName(fileName); this.sourceTable = sourceTable; this.startCellName = startCellName; } /**//// /// 填写文件 /// protected override void FillFile() { FillCell(startCellName, sourceTable); } /**//// /// Excel 帮助类 /// internal class ExcelHelper : IDisposable { 一般的属性变量 #region 一般的属性变量private Application excelApp = null; private Windows excelWindows = null; private Window excelActiveWindow = null; private Workbooks excelWorkbooks = null; private Workbook excelWorkbook = null; private Sheets excelSheets = null; private Worksheet excelWorksheet = null; private static object m_missing = Missing.Value; private static object m_visible = true; private static object m_false = false; private static object m_true = true; private bool m_app_visible = false; private object m_filename; #endregion 打开工作薄变量 #region 打开工作薄变量 private object _update_links = 0; private object _read_only = m_false; private object _format = 1; private object _password = m_missing; private object _write_res_password = m_missing; private object _ignore_read_only_recommend = m_true; private object _origin = m_missing; private object _delimiter = m_missing; private object _editable = m_false; private object _notify = m_false; private object _converter = m_missing; private object _add_to_mru = m_false; private object _local = m_false; private object _corrupt_load = m_false; #endregion 关闭工作薄变量 #region 关闭工作薄变量 private object _save_changes = m_false; private object _route_workbook = m_false; #endregion /**//// /// 当前工作薄 /// public Workbook CurrentExcelWorkBook { get { return excelWorkbook; } set { excelWorkbook = value; } } /**//// /// 释放对象内存,推出进程 /// /// private void NAR(object obj) { try { Marshal.ReleaseComObject(obj); } catch { } finally { obj = null; } } public ExcelHelper() { StartExcel(); } /**//// /// 确定 Excel 打开是否可见 /// /// true 为可见 public ExcelHelper(bool visible) { m_app_visible = visible; StartExcel(); } /**//// /// 开始 Excel 应用程序 /// private void StartExcel() { if (excelApp == null) { excelApp = new ApplicationClass(); } // Excel 是否可见 excelApp.Visible = m_app_visible; } public void Dispose() { stopExcel(); GC.SuppressFinalize(this); } 打开、保存、关闭 Excel 文件 #region 打开、保存、关闭 Excel 文件 /**//// /// 打开 Excel 文件和关闭 Excel /// /// 文件名 /// 返回 OK 表示成功 public bool OpenFile(string fileName) { return OpenFile(fileName, string.Empty); } /**//// /// 打开 Excel 文件 /// /// 文件名 /// 密码 /// 返回 OK 表示成功 public bool OpenFile(string fileName, string password) { m_filename = fileName; if (password.Length > 0) { _password = password; } try { // 打开工作薄 excelWorkbook = excelApp.Workbooks.Open( fileName, _update_links, _read_only, _format, _password, _write_res_password, _ignore_read_only_recommend, _origin, _delimiter, _editable, _notify, _converter, _add_to_mru, _local, _corrupt_load); excelSheets = excelWorkbook.Worksheets; excelWorksheet = (Worksheet) excelSheets.get_Item(1); } catch { CloseFile(); return false; } return true; } /**//// /// 关闭工作薄 /// public void CloseFile() { foreach (Workbook workbook in excelWorkbooks) { workbook.Close(_save_changes, m_filename, _route_workbook); NAR(workbook); } } public void SaveFile(string workbook) { FindExcelWorkbook(workbook); excelWorkbook.Save(); } /**//// /// 保存文件 /// /// 输出的文件名 public void SaveAsFile(string outputFile) { SaveAsFile(string.Empty, outputFile); } /**//// /// 保存指定工作薄的文件 /// /// 工作薄 /// 输出的文件名 public void SaveAsFile(string workbook, string outputFile) { if (File.Exists(outputFile)) { try { File.Delete(outputFile); } catch { return; } } if (workbook != string.Empty) { FindExcelWorkbook(workbook); } excelWorkbook.SaveAs(outputFile, Type.Missing, _password, _write_res_password, Type.Missing, Type.Missing, XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); } /**//// /// 杀掉 Excel 进程 .退出 Excel 应用程序 . /// public void stopExcel() { excelApp.Quit(); NAR(excelSheets); NAR(excelWorksheet); NAR(excelWorkbooks); NAR(excelWorkbook); NAR(excelWindows); NAR(excelActiveWindow); NAR(excelApp); GC.Collect(); if (excelApp != null) { Process[] pProcess; pProcess = Process.GetProcessesByName("EXCEL"); pProcess[0].Kill(); } } #endregion windows 窗口, workbook 工作薄, worksheet 工作区操作 #region windows 窗口, workbook 工作薄, worksheet 工作区操作 /**//// /// 得到工作薄的工作区集合 /// public void GetExcelSheets() { if (excelWorkbook != null) { excelSheets = excelWorkbook.Worksheets; } } /**//// /// 找到活动的 excel window /// /// 窗口名称 /// public bool FindExcelWindow(string workWindowName) { bool WINDOW_FOUND = false; excelWindows = excelApp.Windows; if (excelWindows != null) { for (int i = 1; i < excelWindows.Count; i++) { excelActiveWindow = excelWindows.get_Item(i); if (excelActiveWindow.Caption.ToString().Equals(workWindowName)) { excelActiveWindow.Activate(); WINDOW_FOUND = true; break; } } } return WINDOW_FOUND; } /**//// /// 查找工作薄 /// /// 工作薄名 /// true 为发现 public bool FindExcelWorkbook(string workbookName) { bool WORKBOOK_FOUND = false; excelWorkbooks = excelApp.Workbooks; if (excelWorkbooks != null) { for (int i = 1; i < excelWorkbooks.Count; i++) { excelWorkbook = excelWorkbooks.get_Item(i); if (excelWorkbook.Name.Equals(workbookName)) { excelWorkbook.Activate(); WORKBOOK_FOUND = true; break; } } } return WORKBOOK_FOUND; } /**//// /// 查找工作区 /// /// /// true 为发现 public bool FindExcelWorksheet(string worksheetName) { bool SHEET_FOUND = false; excelSheets = excelWorkbook.Worksheets; if (excelSheets != null) { for (int i = 1; i <= excelSheets.Count; i++) { excelWorksheet = (Worksheet) excelSheets.get_Item((object) i); if (excelWorksheet.Name.Equals(worksheetName)) { excelWorksheet.Activate(); SHEET_FOUND = true; break; } } } return SHEET_FOUND; } #endregion 行列操作 #region 行列操作 /**//// /// 得到工作区的选择范围的数组 /// public string[] GetRange(string startCell, string endCell) { Range workingRangeCells = excelWorksheet.get_Range(startCell, endCell); workingRangeCells.Select(); Array array = (Array) workingRangeCells.Cells.Value2; string[] arrayS = ConvertToStringArray(array); return arrayS; } /**//// /// 将二维数组数据写入 Excel 文件(不分页) /// public void ArrayToExcel(string[,] arr, string getCell) { int rowCount = arr.GetLength(0); // 二维数组行数(一维长度) int colCount = arr.GetLength(1); // 二维数据列数(二维长度) Range range = excelWorksheet.get_Range(getCell, Type.Missing); range = range.get_Resize(rowCount, colCount); range.HorizontalAlignment = XlHAlign.xlHAlignCenter; range.VerticalAlignment = XlV Align.xlV AlignCenter; range.set_Value(Missing.Value, arr); } public void ArrayToExcel(object[,] arr, string getCell) { int rowCount = arr.GetLength(0); // 二维数组行数(一维长度) int colCount = arr.GetLength(1); // 二维数据列数(二维长度) Range range = excelWorksheet.get_Range(getCell, Type.Missing); range = range.get_Resize(rowCount, colCount); range.HorizontalAlignment = XlHAlign.xlHAlignCenter; range.VerticalAlignment = XlV Align.xlV AlignCenter; range.Value2 = arr; //range.set_Value(System.Reflection.Missing.Value,arr); } /**//// /// 合并单元格 /// /// 开始 Cell /// 结束 Cell /// 填写文字 public void MergeCell(string startCell, string endCell, string text) { MergeCell(string.Empty, startCell, endCell, text); } /**//// /// 合并单元格/// /// /// /// /// public void MergeCell(string workbookName, string startCell, string endCell, string text) { if (workbookName != string.Empty) FindExcelWorkbook(workbookName); Range range = excelWorksheet.get_Range(startCell, endCell); range.ClearContents(); range.MergeCells = true; range.Value2 = text; range.HorizontalAlignment = XlHAlign.xlHAlignCenter; range.VerticalAlignment = XlV Align.xlV AlignCenter; } /**//// /// 添加样式 /// /// 样式名 /// 字体名 /// 字体大小 /// 字体 Color(0-255) /// Range 的填充 Color(0-255) public void AddStyle(string styleName, string fontName, int fontSize, int fontColor, int interiorColor) { try { Style existStyle = excelWorkbook.Styles[styleName]; return; } catch { } Style style = excelWorkbook.Styles.Add(styleName, Type.Missing); style.Font.Name = fontName; style.Font.Size = fontSize; if (fontColor >= 0 && fontColor <= 255) { style.Font.Color = fontColor; } if (fontColor >= 0 && fontColor <= 255) { style.Interior.Color = fontColor; } style.Interior.Pattern = XlPattern.xlPatternSolid; } /**//// /// 应用样式 /// /// Range 的开始 /// Range 的结束 /// 样式名 public void ApplyStyle(string startCell, string endCell, string styleName) { Style style; try { style = excelWorkbook.Styles[styleName]; } catch { return; } Range workingRangeCells = excelWorksheet.get_Range(startCell, endCell); workingRangeCells.Style = style; } /**//// /// 插行(在指定行上面插入指定数量行) /// /// 行开始 Index public void InsertRows(int rowIndex) { try { Range range = (Range) excelWorksheet.Rows[rowIndex, Type.Missing]; range.Insert(XlDirection.xlDown, Type.Missing); } catch { return; } } /**//// /// 插行(在指定行上面插入指定数量行) /// /// 行开始 Index /// 插入的行数 public void InsertRows(int rowIndex, int count) { try { for (int i = 0; i < count; i++) { Range range = (Range) excelWorksheet.Rows[rowIndex, Type.Missing]; range.Insert(XlDirection.xlDown, Type.Missing); } } catch { return; } } /**//// /// 插列(在指定列右边插入指定数量列) /// /// 列开始 Index public void InsertColumns(int columnIndex) { try { Range range = (Range) excelWorksheet.Columns[IntToLetter(columnIndex), Type.Missing]; range.Insert(XlDirection.xlToLeft, Type.Missing); } catch { return; } } /**//// /// 指定 Cell 格填充 /// /// 填充内容 /// Cell 位置 public void InsertToExcel(string text, string getCell) { Range range = excelWorksheet.get_Range(getCell, Type.Missing); range.Value2 = text; } public void InsertToExcel(object text, string getCell) { Range range = excelWorksheet.get_Range(getCell, Type.Missing); range.Value2 = text; } /**//// /// 往指定 Cell 格后面追加填充 /// /// 追加填充的内容 /// Cell 位置 public void AppendToExcel(string text, string getCell) { Range range = excelWorksheet.get_Range(getCell, Type.Missing); range.Value2 = range.Value2 + text; } /**//// /// 删除行 /// /// 行 Index /// 行数 public void DeleteRows(int rowIndex, int count) { try { Range range = (Range) excelWorksheet.Rows[rowIndex + ":" + (rowIndex + count - 1), Type.Missing]; range.Delete(XlDirection.xlUp); } catch { return; } } /**//// /// 删除列 /// /// 列 Index /// 列数 public void DeleteColumns(int columnIndex, int count) { try { string cells = IntToLetter(columnIndex) + ":" + IntToLetter(columnIndex + count - 1); Range range = (Range) excelWorksheet.Columns[cells, Type.Missing]; range.Delete(XlDirection.xlDown); } catch { return; } } /**//// /// 将 Excel 列的整数索引值转换为字符索引值 /// /// /// public string IntToLetter(int n) { if (n > 256) { throw new Exception(" 索引超出范围, Excel 的列索引不能超过 256!"); } int i = Convert.ToInt32(n / 26); int j = n % 26; char c1 = Convert.ToChar(i + 64); char c2 = Convert.ToChar(j + 64); if (n > 26) { return c1.ToString() + c2.ToString(); } else if (n == 26) { return "Z"; } else { return c2.ToString(); } } /**//// /// 将 Excel 列的字母索引值转换成整数索引值 /// /// /// public int LetterToInt(string letter) { if (letter.Trim().Length == 0) { throw new Exception(" 不接受空字符串! "); } int n = 0; if (letter.Length >= 2) { char c1 = letter.ToCharArray(0, 2)[0]; char c2 = letter.ToCharArray(0, 2)[1]; if (!char.IsLetter(c1) || !char.IsLetter(c2)) { throw new Exception(" 格式不正确,必须是字母! "); } c1 = char.ToUpper(c1); c2 = char.ToUpper(c2); int i = Convert.ToInt32(c1) - 64; int j = Convert.ToInt32(c2) - 64; n = i*26 + j; } if (letter.Length == 1) { char c1 = letter.ToCharArray()[0]; if (!char.IsLetter(c1)) { throw new Exception(" 格式不正确,必须是字母! "); } c1 = char.ToUpper(c1); n = Convert.ToInt32(c1) - 64; } if (n > 256) { throw new Exception(" 索引超出范围, Excel 的列索引不能超过 256!"); } return n; } /**//// /// DataTable 填充 Excel /// /// DataTable 表 /// Cell 位置 /// 是否显示表头 public void DataTableToExcel(DataTable dt, string getCell, bool showHeader) { int rowCount = dt.Rows.Count; //DataTable 行数 int colCount = dt.Columns.Count; //DataTable 列数 string[,] array; if (showHeader) { array = new string[rowCount + 1,colCount]; } else { array = new string[rowCount,colCount]; } if (showHeader) // 添加行字段 { for (int i = 0; i < colCount; i ++) { array[0, i] = dt.Columns[i].ColumnName; } } for (int j = 0; j < rowCount; j++) { for (int k = 0; k < colCount; k++) { array[j + (showHeader ? 1 : 0), k] = dt.Rows[j][k].ToString(); } } ArrayToExcel(array, getCell); } /**//// /// DataTable 填充 Excel 以 object 方式填充 /// /// DataTable 表 /// Cell 位置 /// 是否显示表头 public void DataTableToExcelofObj(DataTable dt, string getCell, bool showHeader) { int rowCount = dt.Rows.Count; //DataTable 行数 int colCount = dt.Columns.Count; //DataTable 列数 object[,] array; if (showHeader) { array = new object[rowCount + 1, colCount]; } else { array = new object[rowCount, colCount]; } if (showHeader) // 添加行字段 { for (int i = 0; i < colCount; i ++) { array[0, i] = dt.Columns[i].ColumnName; } } for (int j = 0; j < rowCount; j++) { for (int k = 0; k < colCount; k++) { array[j + (showHeader ? 1 : 0), k] = dt.Rows[j][k]; } } ArrayToExcel(array, getCell); } /**//// /// DataRow 填充 Excel 以 object 方式填充 /// /// DataRow /// Cell 位置 /// 是否显示表头 public void DataRowToExcel(DataRow[] dr, string getCell, bool showHeader) { int rowCount = dr.GetLength(0); //DataRow 行数 int colCount = dr[0].Table.Columns.Count; //DataRow 列数 object[,] array; if (showHeader) { array = new object[rowCount + 1,colCount]; } else { array = new object[rowCount,colCount]; } if (showHeader) // 添加行字段 { for (int i = 0; i < colCount; i ++) { array[0, i] = dr[0].Table.Columns[i].ColumnName; } } for (int j = 0; j < rowCount; j++) { for (int k = 0; k < colCount; k++) { array[j + (showHeader ? 1 : 0), k] = dr[j][k]; } } ArrayToExcel(array, getCell); } private Range SelectRange(string range) { return excelWorksheet.get_Range(range, Type.Missing); } public void RangeCopy(string startCell, string endCell, string targetCell) { RangeCopy(string.Empty, string.Empty, startCell, endCell, string.Empty, string.Empty, targetCell); } public void RangeCopy(string worksheetName, string startCell, string endCell, string targetCell) { RangeCopy(string.Empty, worksheetName, startCell, endCell, string.Empty, string.Empty, targetCell); } public void RangeCopy(string worksheetName, string startCell, string endCell, string targetWorksheetName, string targetCell) { RangeCopy(string.Empty, worksheetName, startCell, endCell, string.Empty, targetWorksheetName, targetCell); } public void RangeCopy(string workbookName, string worksheetName, string startCell, string endCell, string targetWorksheetName, string targetCell) { RangeCopy(workbookName, worksheetName, startCell, endCell, string.Empty, targetWorksheetName, targetCell); } /**//// /// 区域复制粘贴/// /// 工作薄名 /// 工作区名 /// 开始 Cell /// 结束 Cell /// 目标工作薄名 /// 目标工作区名 /// 目标 Cell public void RangeCopy(string workbookName, string worksheetName, string startCell, string endCell, string targetWorkbookName, string targetWorksheetName, string targetCell) { if (workbookName != string.Empty && !FindExcelWorkbook(workbookName)) return; if (worksheetName != string.Empty && !FindExcelWorksheet(worksheetName)) return; Range workingRangeCells = excelWorksheet.get_Range(startCell, endCell); if (workingRangeCells == null) return; if (targetWorkbookName != string.Empty && !FindExcelWorkbook(targetWorkbookName)) return; if (targetWorksheetName != string.Empty && !FindExcelWorksheet(targetWorksheetName)) return; Range targetRange = excelWorksheet.get_Range(targetCell, Type.Missing); workingRangeCells.Copy(targetRange); } /**//// /// 转换 Array 为字符串数组 /// /// Array /// String[] private string[] ConvertToStringArray(Array values) { string[] newArray = new string[values.Length]; int index = 0; for (int i = values.GetLowerBound(0); i <= values.GetUpperBound(0); i++) { for (int j = values.GetLowerBound(1); j <= values.GetUpperBound(1); j++) { if (values.GetV alue(i, j) == null) { newArray[index] = ""; } else { newArray[index] = values.GetValue(i, j).ToString(); } index++; } } return newArray; } public Range GotoCell(string Key) { excelApp.Goto(Key, 0); return excelApp.ActiveCell; } #endregion } 列表查询组件代码 , 简化拼接条件 SQL 语句的麻烦 Posted on 2007-11-19 13:39 伍华聪 阅读 (2279) 评论 (20) 编辑 收藏 控件代码及测试例子: http://files.cnblogs.com/wuhuacong/CommonSearch.rar 使用场景 : 在列表页面中 , 一般有好几个条件 , 用户进行查询时候 , 需要根据这几个条件 进行过滤查询 .但在组装这些过滤条件的时候,代码比较烦琐臃肿,本组件代码为解决 该问题而设计。 使用目的 : 1. 减少对参数非空的条件判断 2. 可以构造出参数化的 DbCommand 对 象, 简化操作 . 3. 适当修改后可以用于其他数据访问的参数化参数生成 .4. 构造 Sql 语句 或者参数化条件更加易读 1. 生成 SQL 条件语句 如有几个字段,需要根据不同的字段进行过滤,想生成的 SQL 语句如下: Where (1=1) AND AA2 Like '%AA2Value%' AND AA6 >= 'Value6' AND AA7 <= 'value7' AND AA3 = 'Value3' AND AA4 < 'Value4' AND AA5 > 'Value5' AND AA <> '1' 那么代码如下: SearchCondition search = new SearchCondition(); search.AddCondition("AA", 1, SqlOperator.NotEqual) .AddCondition("AA2", "AA2Value", SqlOperator.Like) .AddCondition("AA3", "Value3", SqlOperator.Equal) .AddCondition("AA4", "Value4", SqlOperator.LessThan) .AddCondition("AA5", "Value5", SqlOperator.MoreThan) .AddCondition("AA6", "Value6", SqlOperator.MoreThanOrEqua l) .AddCondition("AA7", "value7", SqlOperator.LessThanOrEqua l); string conditionSql = search.BuildConditionSql(); 2. 生成基于 Enterprise Library 的 DbCommand 对象 Database db = DatabaseFactory.CreateDatabase(); SearchCondition search = new SearchCondition(); search.AddCondition("Name", " 测试 " , SqlOperator.Like) .AddCondition("ID", 1, SqlOperator.MoreThanOrEqual); DbCommand dbComand = search.BuildDbCommand(db, "selec t Comments from Test", " Order by Name"); using (IDataReader dr = db.ExecuteReader(dbComand)) { while (dr.Read()) { this .txtSql.Text += "\r\n" + dr["Comments"].ToString(); } } 下面是该控件的类对象图解下面我们比较一下使用该控件和不使用在列表查询页面中的代码, 可以看出使用了控件 后的代码大大较少了,并且可读性也增强了 1. 使用该控件 , 列表查询页面中的代码 private string GetCondition() { SearchCondition search = new SearchCondition(); search.AddCondition("GroupID", this .ddlUserGroup.SelectedValu e, SqlOperator.Equal, true ) // 班组 ID .AddCondition("DealGroupName", this .ddlDealGroup.Selected Value, SqlOperator.Equal, true ) /* 消缺单位 */ .AddCondition("VisioStationID", this .ddlStation.SelectedValu e, SqlOperator.Like, true ) // 变电站 .AddCondition("VisioImageID", this .ddlLine.SelectedValue, Sq lOperator.Like, true ) /* 馈线 */ .AddCondition("BugNo", this .txtBugNo.Text.Trim(), SqlOperat or.Like, true ) /* 编号 */ .AddCondition("Finder", this .ddlFindUser.SelectedValue, SqlO perator.Like, true ) /* 发现人 */ .AddCondition("CheckUser", this .ddlCheckUser.SelectedValu e, SqlOperator.Like, true ) // 验收人 .AddCondition("DeviceBug.BugType", this .ddlBugType.Selected Value, SqlOperator.Equal, true ) // 缺陷类别 .AddCondition("CurrentState", this .ddlCurrentState.SelectedVal ue, SqlOperator.Equal, true ) // 处理状态 .AddCondition("FindDate", this .txtFindBeginDate.Text.Trim(), S qlOperator.MoreThanOrEqual, true ) // 发现日期 .AddCondition("FindDate", this .txtFindEndDate.Text.Trim(), Sql Operator.LessThanOrEqual, true ) // 发现日期 .AddCondition("EndDate", this .txtEndBeginDate.Text.Trim(), S qlOperator.MoreThanOrEqual, true ) // 消缺日期 .AddCondition("EndDate", this .txtEndEndDate.Text.Trim(), Sql Operator.LessThanOrEqual, true ); // 消缺日期 return search.BuildConditionSql(); } 2. 普通做法,不使用控件,列表查询页面中的代码 private string GetCondition() { string condition = ""; if ( this .ddlUserGroup.SelectedValue != "0") { condition += string .Format( " GroupID = {0}" , this .ddlUserGrou p.SelectedValue.ToString() ); } // 消缺单位 if ( this .ddlDealGroup.SelectedValue != "0") { if (condition == "") { condition += string .Format( " DealGroupName = '{0}'" , this . ddlDealGroup.SelectedItem.Text ); } else { condition += string .Format( " And DealGroupName = '{0}'" , t his .ddlDealGroup.SelectedItem.Text ); } } if ( this .txtStation.Text.Trim() != "") { if (condition == "") { condition += string .Format(" Station like '%{0}%'", this .txtSta tion.Text.Trim() ); } else { condition += string .Format(" And Station like '%{0}%' ", this .txtStation.Text.Trim() ); } } if ( this .txtLineName.Text.Trim() != "") { if (condition == "") { condition += string .Format(" LineName like '%{0}%'", this .txt LineName.Text.Trim() ); } else { condition += string .Format(" And LineName like '%{0}%' ", thi s.txtLineName.Text.Trim() ); } } // 编号 if ( this .txtBugNo.Text.Trim() != "") { if (condition == "") { condition += string .Format("BugNo like '%{0}%'", this .txtBug No.Text.Trim() ); } else { condition += string .Format(" And BugNo like '%{0}%'", this .tx tBugNo.Text.Trim() ); } } // 发现人 if ( ddlFindUser .SelectedIndex >= 1 ) { if (condition == "") { condition += string .Format("Finder like '%{0}%'",ddlFindUser . SelectedValue ); } else { condition += string .Format(" And Finder like '%{0}%'",ddlFindUser .SelectedValue ); } } // 验收人 if ( this .ddlCheckUser .SelectedIndex >= 1 ) { if (condition == "") { condition += string .Format("CheckUser like '%{0}%'", this .ddl CheckUser .SelectedValue ); } else { condition += string .Format(" And CheckUser like '%{0}%'", thi s.ddlCheckUser .SelectedValue ); } } // 缺陷类别 if ( this .ddlBugType.SelectedValue.Trim() != "#") { if (condition == "") { condition += string .Format("DeviceBug.BugType={0}", this .dd lBugType.SelectedValue.Trim() ); } else { condition += string .Format(" And DeviceBug.BugType={0}", t his .ddlBugType.SelectedValue.Trim() ); } } // 处理状态 if ( this .ddlCurrentState.SelectedValue.Trim() != "#") { if (condition == "") { condition += string .Format("CurrentState='{0}'", this .ddlCurre ntState.SelectedValue.Trim() ); } else { condition += string .Format(" And CurrentState='{0}'", this .ddl CurrentState.SelectedValue.Trim() ); } } // 发现日期 if ( this .txtFindBeginDate.Text.Trim() != "") { if (condition == "") { condition += string .Format("FindDate>='{0}'", this .txtFindBeg inDate.Text.Trim() ); } else { condition += string .Format(" And FindDate>='{0}'", this .txtFin dBeginDate.Text.Trim() ); } } if ( this .txtFindEndDate.Text.Trim() != "") { if (condition == "") { condition += string .Format("FindDate<='{0}'", this .txtFindEnd Date.Text.Trim() ); } else { condition += string .Format(" And FindDate<='{0}'", this .txtFin dEndDate.Text.Trim() ); } } // 消缺日期 if ( this .txtEndBeginDate.Text.Trim() != "") { if (condition == "") { condition += string .Format("EndDate>='{0}'", this .txtEndBegi nDate.Text.Trim() ); } else { condition += string .Format(" And EndDate>='{0}'", this .txtEndBeginDate.Text.Trim() ); } } if ( this .txtEndEndDate.Text.Trim() != "") { if (condition == "") { condition += string .Format("EndDate<='{0}'", this .txtEndEnd Date.Text.Trim() ); } else { condition += string .Format(" And EndDate<='{0}'", this .txtEn dEndDate.Text.Trim() ); } } return condition; } 如何获取类或属性的自定义特性 (Attribute) Posted on 2007-11-23 09:18 伍华聪 阅读 (2319) 评论 (5) 编辑 收藏 问题说明 : 在 ActiveRecord 或者其他的 ORM 等代码中 , 我们经常可以看到自定义 特性 (Attribute) 的存在 ( 如下面的代码所示 ) [PrimaryKey(PrimaryKeyType.Native, "PostId")] public int Id { ...... } 看似非常神秘的东西 , 我们在什么场合需要使用它 , 我们该如何使用 它? 自定义特性 (Attribute) 在一些需要声明特定信息的时候非常有用 , 如标 识实体类属于那个表 , 那个字段是否为主键 ,主键的数据库字段名是什么 这些信息的时候就会派上用场 , 使用的时候也是非常的简单 . 解决方法 : 1. 定义一个以 Attribute 结尾的特性类 , 特性类继承自 System.Attribute, 如下所示 . [AttributeUsage(AttributeTargets.Property, AllowMultiple=false)] public class PrimaryKeyAttribute : System.Attribute { ............. 其中 AttributeTargets 是一个枚举的值 , 可以是 : Assembly | Module | Class | Struct | Enum | Constructor | Method | Property | Field | Event | Interface | Parameter | Delegate 2. 在需要使用的地方使用 PrimaryKey 自定义特性标签 , 如下所示 . [PrimaryKey(Column = "CustomerID", IsIdentity=false)] public int ID { ............. 3. 为了获取自定义特性的信息 , 需要反射的方式获取其数 据, 首先我们定义一个类来存储这些信息 , 如下所示 public class PrimaryKeyModel { private readonly PropertyInfo propertyInfo;// 外键的 属性字段信息 private readonly PrimaryKeyAttribute primaryKeyAtt;// 外键的特性信息 ................ public static PrimaryKeyModel GetPrimaryKey(Type type) { PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo p in properties) { object[] keys = p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true); if (keys.Length == 1) { return new PrimaryKeyModel(p, keys[0] as PrimaryKeyAttribute); } } return null; } 4. 在 Customer 类中获取其特性的信息代码如下 string strReturn = string.Empty; //Get PrimaryKey Name PrimaryKeyModel attribute= PrimaryKeyModel.GetPrimaryKey(this.GetType()); if(attribute != null) { strReturn += string.Format("PrimaryKey Name:{0} IsIdentity:{1} Column:{2}\r\n", attribute.Property.Name, attribute.PrimaryKeyAtt.IsIdentity, attribute.PrimaryKeyAtt.Column); } 详细解释 : 附件的实例中演示了如何获取类和属性的自定义特性 , 类的 特性包括获取实体类的表名称 , 属性的特性包括获取外键的 信息 代码下载完。 记录转化为有层次结构的树状列表的通用算法 Posted on 2007-11-23 09:15 伍华聪 阅读 (1757) 评论 (6) 编辑 收藏 问题说明 : 在获取数据库记录数据的时候 , 通常返回的 ArrayList 集合 , 没有了层次 关系 . 如果每次根据 PID 重新到数据库获取记录 , 可以做到 , 但有以下 几个缺点 : 1. 访问数据库记录次数随着记录的增多而增多 2. 由于需要多次访问数据库 , 因此访问速度受影响 3. 需要数据库访问层的支持 , 并对记录进行转化 , 耦合性太强 4. 通用性不好 , 每次需要一个新的类型列表 , 就需要重新编写 解决方法 : 我根据原有的树状结构算法代码 , 编写一个通用的算法 , 利用 反射原理 , 递归的对数据进行筛选 . 这样只需要访问数据库一次 , 然后就在内存中遍历 , 而且适合 于所有具有 (PID, ID, Name) 属性的实体类集合的排序 . 如我需要生成设备类型实体类集合的树状结构时候 , 代码如 下: ArrayList equipTypelist = equipmentType.GetA ll(); equipTypelist = CollectionHelper.GetTreeIte ms(equipTypelist); this .ddlEquipmentTypes.DataSource = equi pTypelist; this .ddlEquipmentTypes.DataTextField = "N ame"; this .ddlEquipmentTypes.DataValueField = " ID"; this .ddlEquipmentTypes.DataBind(); this .ddlEquipmentType.Items.Insert(0, ne w ListItem("( 全部 )", "0")); public class CollectionHelper { private static ArrayList Fill( int pID, int level, ArrayList list) { ArrayList returnList = new ArrayList(); foreach ( object obj in list) { int typePID = ( int )ReflectionUtil.GetProperty(obj, "PID"); int typeID = ( int )ReflectionUtil.GetProperty(obj, "ID"); string typeName = ReflectionUtil.GetProperty(obj, "Name") a s string ; if (pID == typePID) { string newName = new string ('-', level * 4) + typeName; ReflectionUtil.SetProperty(obj, "Name", newName); returnList.Add(obj); returnList.AddRange(Fill(typeID, level+1, list)); } } return returnList; } /// /// 生成有层次结构的列表 /// /// 具有 Name,ID,PID 成员的任何集合 /// public static ArrayList GetTreeItems(ArrayList list) { return Fill(-1, 0, list); } } public sealed class ReflectionUtil { private ReflectionUtil() { } public static BindingFlags bf = BindingFlags.DeclaredOnly | Binding Flags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.St atic; public static void SetProperty( object obj, string name, object value) { PropertyInfo fi = obj.GetType().GetProperty(name, bf); fi.SetValue(obj, value, null ); } public static object GetProperty( object obj, string name) { PropertyInfo fi = obj.GetType().GetProperty(name, bf); return fi.GetValue(obj, null ); } } 效果图如下 如何利用系统函数操作文件夹及文件 Posted on 2008-06-04 20:31 伍华聪 阅读 (3136) 评论 (6) 编辑 收藏 平时使用 Path 类不多,常用 Combine 来串联两个路径, 其他的很少用, 偶然发现 Path 提供了很多方便实用的函数。 名称 说明 ChangeExtension 更改路径字符串的扩展名。 Combine 合并两个路径字符串。 GetDirectoryName 返回指定路径字符串的目录信息。 GetExtension 返回指定的路径字符串的扩展名。GetFileName 返回指定路径字符串的文件名和扩展 名。 GetFileNameWithoutExtension 返回不具有扩展名的指定路径字符串 的文件名。 GetFullPath 返回指定路径字符串的绝对路径。 GetInvalidFileNameChars 获取包含不允许在文件名中使用的字 符的数组。 GetInvalidPathChars 获取包含不允许在路径名中使用的字 符的数组。 GetPathRoot 获取指定路径的根目录信息。 GetRandomFileName 返回随机文件夹名或文件名。 GetTempFileName 创建磁盘上唯一命名的零字节的临时 文件并返回该文件的完整路径。 GetTempPath 返回当前系统的临时文件夹的路径。 HasExtension 确定路径是否包括文件扩展名。 IsPathRooted 获取一个值,该值指示指定的路径字 符串是包含绝对路径信息还是包含相 对路径信息。 我比较喜欢那个 GetTempPath 函数,一句代码就可以返回临时目录的路径了,很方 便,大家可能也注意到, Environment.GetEnvironmentVariable(string variable) 也提供了如 何获取特别路径的函数,另外 Environment.GetFolderPath(Environment.SpecialFolder folder) 的函数也提供了很多实用功能 ,返回相应的目录 ,枚举包含有 : EnvironmentSpecialFolder 枚举 成员名称 说明ApplicationData 目录,它用作当前漫游用户的应用程序特定数据的 公共储存库。 CommonApplicationData 目录,它用作所有用户使用的应用程序特定数据的 公共储存库。 LocalApplicationData 目录,它用作当前非漫游用户使用的应用程序特定 数据的公共储存库。 Cookies 用作 Internet Cookie 的公共储存库的目录。 Desktop 逻辑桌面,而不是物理文件系统位置。 Favorites 用作用户收藏夹项的公共储存库的目录。 History 用作 Internet 历史记录项的公共储存库的目录。 InternetCache 用作 Internet 临时文件的公共储存库的目录。 Programs 包含用户程序组的目录。 MyComputer “我的电脑 ”文件夹。 MyMusic “ My Music ”文件夹。 MyPictures “ My Pictures ”文件夹。 Recent 包含用户最近使用过的文档的目录。 SendTo 包含 “发送 ”菜单项的目录。 StartMenu 包含 “开始 ”菜单项的目录。 Startup 对应于用户的 “启动 ”程序组的目录。 System “ System ”目录。Templates 用作文档模板的公共储存库的目录。 DesktopDirectory 用于物理上存储桌面上的文件对象的目录。 Personal 用作文档的公共储存库的目录。 MyDocuments “我的电脑 ”文件夹。 ProgramFiles “ Program files ”目录。 CommonProgramFiles 用于应用程序间共享的组件的目录 最后我顺便提供一个递归删除文件夹和文件的操作函数 ,方便大家 /// /// 删除文件夹及其下面的子文件和文件夹 /// /// public static void DeleteSubFileAndFolder( string filePath) { if (Directory.Exists(filePath)) { foreach ( string path in Directory.GetFileSystemEntries(filePat h)) { if (File.Exists(path)) { File.Delete(path); } else { DeleteSubFileAndFolder(path); } } Directory.Delete(filePath); } } Winform 系统设计开发中的一些经验总结 Posted on 2008-07-12 08:23 伍华聪 阅读 (1603) 评论 (4) 编辑 收藏 界面应统一风格(菜单、工具条、状态条) 控件的图片、图片透明颜色 控件的命名统一菜单项目 menu_ 工具条按钮 tsb_ 文本 txt_ 、下拉列表 cmb_ 等 控件布局: 基类 BaseForm ,实现统一出现位置 窗体的大小尽量一样 Tab 顺序、控件长度高度、控件停靠、自动伸缩 菜单、按钮快捷键 注重你代码及知识的储备 每做一个项目,储备几个辅助类 看到好的控件或代码,收集 利用一切可以使用的轮子 到 codeproject 网站中找相关内容 比较并找出较好、较合适的进行修改 组装更好的轮子 时态数据库的应用介绍( 1) Posted on 2008-07-18 21:28 伍华聪 阅读 (2155) 评论 (1) 编辑 收藏 随着信息技术的发展, 传统的关系数据库 ( 如关系型数据库 ) 功能已不能满足当今信息系 统中对时态信息处理能力的需求。 而时态数据库理论的完善和发展为解决时态信息处理 问题提供了一个很好的解决方案。 什么是时态数据库? 区别于传统的关系型数据库 (RDBMS) ,时态数据库 (Temporal Database) 主要用于 记录那些随着时间而变化的值的历史, 而这些历史值对应用领域而言又是重要的, 这类 应用有:金融、保险、预订系统、决策支持系统等。 目前时态数据库还没有像如 Oracle 、SQL Server 等大型关系数据库那样的产品。在 当前时态数据库技术尚未完全成熟的现状下, DBMS 提供商不会轻易把时态处理功能引 入现有的 DBMS 中,因此,利用成熟的 RDBMS 数据库,建立时态数据库的中间件, 在现阶段是一个较好的选择,因此就应运而生 TimeDB 和 TempDB 了。 国外的 TimeDB ( http://www.timeconsult.com/Software/Software.html )是一种结合关系型数据 库来实现时间数据库应用的技术,它支持时态数据库脚本: ATSQL2 [SBJS96a , SBJS96b ,SBJS98] 。它可以认为是关系数据库的一个前端,把时态数据库语句转换 为关系型数据库的脚本进行执行,它的运用场景如下所示。国内的 TempDB 也是一种类似的技术应用,支持 ATSQL2 ,和 TimeDB 类似,都是 基于目前非常成熟的关系型数据库基础上的应用。 时态数据库中间件不改变现有商业 DBMS 的结构和功能,而是作为一个上层构件添加 到商业 DBMS 之上,应用程序之下,形成一个中间层结构。这个中间层结构能处理时 态结构化查询语言( ATSQL2 ),把它转换成下层 DBMS 能理解的标准 SQL 语言(如 P-SQL )并在商业 DBMS 中执行。 时态数据库理论的基本概念 时态数据库理论提出了三种基本时间:用户自定义时间、有效时间和事务时间。同时把 数据库分为四种类型:快照数据库、回滚数据库、历史数据库和双时态数据库。 时态数据库理论提出了三种基本时间:用户自定义时间、有效时间和事务时间。同时把 数据库分为四种类型:快照数据库、回滚数据库、历史数据库和双时态数据库。 用户自定义时间 :指用户根据自己的需要或理解定义的时间。时态数据库系统不处理 用户自己定义的时间类型。因此,用户自定义时间是和应用相关的,不在时态数据库处 理的范围之内。 有效时间( Valid-Time ):指一个对象在现实世界中发生并保持的时间,即该对象 在现实世界中语义为真的时间,包含 Valid-From 和 Valid-To 两个值。它可以指示过 去、现在和未来。例如,考虑事实 “小明从 2003 年到 2007 年是大学生 ”,那么时间区 间[2003, 2007] 是事实 “小明是大学生 ”的有效时间区间, 该事实在该时间区间内为真。 有效时间可以是时间点、时间点的集合、时间区间或者时间区间的集合,或者是整个时 间域。有效时间由时态数据库系统解释并处理,在查询的过程中对用户透明。用户也可 以显式地查询和更新有效时间。 事务时间( Transaction-Time ): 指一个数据库对象发生操作的时间,是一个事 实存储在数据库、或者在数据库中发生改变的时间,包含 Transaction-From 和 Transaction-To 两个值。当用户对数据库状态进行更改时,会产生各种操作历史,事 务时间真实地记录了数据库状态变更的历史。有时也称事务时间为系统时间。 快照数据库: 快照数据库是反映现实世界某一瞬间情况的数据模型。它记录了特定时 刻的数据库状态。快照数据库采用这样的假设:一个存储在数据库中的元组,一定是真 实世界中的有效事实。 历史数据库:数据库中被管理对象的生命周期是对象的有效时间,每一个元组记录了数据的一个 “历史 ”状态。历史数据库中没有约束时间的表示方法, 可以是时间点的集合、 时间区间或者区间集合等形式表示。 回滚数据库: 数据库中被管理对象的生命周期是事务时间的数据库。它保存了数据库 中事务提交、状态演变的历史状态。 双时态数据库: 数据库中元组包含一个系统支持的有效时间和一个系统支持的事务时 间的数据库,称为双时态数据库。双时态数据库具备了快照数据库、历史数据库和回滚 数据库的特点,存储了现实世界和数据库系统的变更历史。 Now :Now 的中文意思是当前时间, 是一个时间变元, 随着当前时间的变化而变化, 记录了随时间变化的信息,它的有效值依赖于当前时间。 时态数据库脚本 ATSQL 的使用 时态数据处理构件根据 ATSQL2 的语法引入了 Now 、Beginning 和 Forever 三个变 元。 Beginning 和 Forever 分别表示时态数据处理构件所能表示的时间起点和终点。 Now 表示当前时间。 每次执行操作时必须使 Now 绑定到一个固定的值 (操作执行的当 前时间),这样后继操作才能正常进行。在下面的 ATSQL2 中表示的这几个单词是具 有具体的含义的。 ATSQL2 的数据定义语句包括以下几项功能:创建表、创建视图、删除表、删除视图。 ATSQL2 语言和普通 SQL 语句一样,使用 “ create table ”关键字创建数据库表,关键 字“as validtime ”用于指示系统创建具有有效时间支持的数据库表。 1. 创建员工表 Employee, 是在标准的创建表 SQL 结尾,加入 AS VALIDTIME 关键 字即可。 CREATE TABLE Employee( ID VARCHAR( 30 ) NOT NULL, NAME VARCHAR( 50) NOT NULL) AS VALIDTIME; 2. 插入一条记录的 ATSQL, 需要通过 VALIDTIME PERIOD 来指定一个时间范围的, 其他部分和标准 SQL 一样 VALIDTIME PERIOD [1981-1985) Insert into Employee values ('11 2', 'Jack'); 3. 记录查询 : 查询所有记录,在标准 SQL前加 VALIDTIME关键字即可 , 查询指定 时间段的记录,可以通过 PERIOD关键字指定时间区间 : VALIDTIME SELECT * FROM Employee; VALIDTIME PERIOD [1983-1986) SELECT * FROM Employee; VALIDTIME PERIOD [1983-forever) SELECT * FROM Employee; 4. 向员工表里添加人员信息插入记录前后分别如表 3-1 和表 3-2 所示 VALIDTIME period [ date "2008-1-1" – now) Insert into Staff Values ( 132202, 'Zhang Jinning', ' 信息部 ');5. 为 employee 表创建一个视图, 该视图包含所有 1999-9-9 前离职的工人信息, 其 中 VALIDTIME(R) 表示取元组 R 的有效时间区间: Create view employee_v as Validtime select * from employee Where VALIDTIME(employee) before date "1999-9-9"; 6. 删除员工表的 ATSQL 和一般的 SQL 语句一样,如下 DROP TABLE Employee; 几个查询相关的概念 快照查询:查询当前时刻的数据库状态。对于非时态表(快照数据库),快照查询是一 种向上兼容的查询方式,可以查询非时态数据库的数据。对于时态表(历史数据库或回 滚数据库),快照查询截取当前数据库状态,返回当前状态下的查询结果。 顺序查询:顺序查询是一种时态查询,通过关键字 VALIDTIME 或者 TRANSACTIONTIME 指示系统执行哪个时间维 (有效时间维、 事务时间维, 或者两个 维度)的时态查询。在这种查询中系统运用时态代数操作自动处理元组的时间戳(有效 时间、事务时间,或两者的结合)。 非顺序查询:执行非顺序查询时,系统不再根据时态代数操作解释时间戳,只是把有效 时间和事务时间当作普通的用户定义属性对待。非顺序查询适合特殊的查询需求, 例如 查询条件涉及状态的变化的查询。 时态归并:时态归并是作用于时态数据库的一种重构操作 [23] 。归并把具有相同属性 值并且时间戳相邻或重叠的元组合并成一个元组, 归并的过程保持时态数据库的状态不 被改变。 时态数据的变元 时态数据处理构件根据 ATSQL2 的语法引入了 Now 、Beginning 和 Forever 三个变 元。 Beginning 和 Forever 分别表示时态数据处理构件所能表示的时间起点和终点。 Now 表示当前时间。 每次执行操作时必须使 Now 绑定到一个固定的值 (操作执行的当 前时间),这样后继操作才能正常进行。时态数据处理构件使用重写底层比较过程的方法绑定 Now 的语义。它把 Now 、 Beginning 和 Forever 分别用以下三个具体的时间值表示: Now = ,9999 -01- 01 00:00:00? ; Beginning = ,1000 -01- 01 00:00:00? ; Forever = ,9999 -12- 31 23:59:59?. 因为使用了特殊值表示这三个变元, 一方面使得时态数据处理构件能处理的时间范围比 原商业数据库能处理的时间范围小, 另一方面使得原商业数据库底层对时间类型数据的 比较算子有了新的意义。例如,如果当前时间是 2007 年 12 月 1 日,考虑 “ 2009-1- 1>Now ” 的意义时,应当先把 Now 替换为 2008-07-18 ,比较结果为真。 在 TimeDB 中 Now 只是当前时间的别名。在时态插入语句的执行过程中, Now 作为 一个标记被替换为语句执行的当前时间。 注:本文部分内容引用了黄永钊的《时态数据处理构件的性能优化研究与实现》 时态数据库的应用介绍( 2) -- 时态数据库之 TimeDB Posted on 2008-07-18 21:29 伍华聪 阅读 (1509) 评论 (3) 编辑 收藏 前面介绍了 TimeDB ( http://www.timeconsult.com/Software/Software.html ) 是一种结合关系型数据库来实现时间数据库应用的技术,它支持时态数据库脚本: ATSQL2 [SBJS96a ,SBJS96b ,SBJS98] 。它可以认为是关系数据库的一个前端, 把时态数据库语句转换为关系型数据库的脚本进行执行,它的运用场景如下所示。 TimeDB 是传统的数据库管理系统的前端软件,应用程序中使用的时态 ATSQL 语句, 通过 TimeDB 转换后形成标准的 SQL 语言和操作,这些标准 SQL 语句和操作传入到 后台数据库中操作实际的数据。 TimeDB 支持 ATSQL2 语言和时态模型,实现了时态 查询、时态更新、时态视图和部分的时态完整性约束等基本的时态功能。 TimeDB 2.0 版本使用 Java 语言开发,具有平台无关的有点; 基于 JDBC 访问数据库, 目前仅支持 Oracle 、 Sybase 和 IBM 的 Cloudscape?s JBMS 三种数据库,下一版本 可能支持 Microsoft's Access 、 SQL Server 7.0 、Informix 等数据库;具有较友好 的用户界面;优化了辅助表的创建过程; 具有可以供 Java 应用程序调用的接口 TDBCI , 可供 Java 应用程序调用以执行 ATSQL2 语句。 TimeDB2.0 的程序运行界面如下:TDBCI 提供的接口函数如下所示 // 设置访问参数 public boolean setPrefs(String Path, int DBMS, String JDBCDri ver, String URL); // 初始化 / 清除 执行 ATSQL 语句所需的表 public boolean createDB(); public boolean clearDB(); // 打开或者关闭数据库 public boolean openDB(String Login, String Password); public void closeDB(); // 执行 ATSQL 语句 public ResultSet execute(String stmt); 其中 setPrefs 是用来设置 TimeDB 的相关访问参数的,如下所示 if (t.setPrefs("C:\\TimeDB 2.2\\", // Path to TimeDB2.0 directory 1, // Using Oracle DBMS "oracle.jdbc.driver.OracleDriver", // Oracle's JDBC driver "jdbc:oracle:thin:1521:ORCL")) // URL createDB 是用来创建一些基础表, 以便支持执行 ATSQL 的,而 clearDB 则是清除这 些表,对数据库而言,只需要开始的时候调用 createDB 一次即可。 openDB 和 closeDB 有点类似于我们操作数据库的时候, 打开 Connection 连接的操 作,每次查询事务,都需要先执行 openDB 的操作,事务提交后,调用 closeDB 来关 闭数据库。 执行 ATSQL 语句只有一个函数 execute ,它返回的是 TimeDB 定义的 ResultSet 对 象,而 ResultSet 对象可以获取每一个 ResultRow 对象, ResultRow 对象通过 row.getColumnValue(i) 和 row.getColumnType(i) 函数调用只能拿到列的值和对 应值的类型,结果类型是字符类型。 返回的类型名称有: ? number ? smallint ?float? numeric ?integer ?double ? longint ?real? interval ? date?period ?char?varchar 如何应用 TimeDB 的时间数据库技术 由于我们的产品技术主要是基于 .NET 基础上开发的,而 TimeDB 是基于 Java 的应用 技术,因此可以考虑通过包装 TimeDB 成为 WebService 的应用,然后通过 .NET 客 户端进行访问,实现时间数据库技术的应用。 初始化数据库基础信息 TimeDB 是基于关系型数据库之上的一个模块, 在做时间数据库相关的脚本( ATSQL ) 解析前,它需要一些基础表和记录来存储相关的信息, 因此需要初始化所建立的数据库。 初始化数据库可以通过执行安装目录下的 SQL 脚本而进行(位置为 X:\TimeDB 2.2\scripts\initDB ),如 Oracle 调用 initOracle 脚本, Sybase 数据库调用 initSybase 脚本。 另外,也可以通过调用 TDBCI 的接口 createDB 创建这些基础表。 使用 Java 创建基于 TimeDB 查询操作的 WebService TDBCI 公开了一些供 Java 应用程序调用的 API 函数,为了使得 .NET 的应用程序也能 在 TimeDB 中执行 ATSQL 脚本,我们把对 TDBCI 的调用封装成了 WebService 。 开发环境: MyEclipse6.01 、Tomcat5.5 、Java SDK1.6 、Oracle 10g 、TimeDB2.0 。 实现步骤: 1、 使用 MyEclipse 创建 WebService 项目,配置好 Tomcat 服务器 2、 引用 Oralce 的 JDBC 类和 TimeDB 开发所需的类 3、 创建具体的 WebService 服务类,提供返回结果查询和无结果的查询接口,并实 现接口函数 4、 部署 WebService 到 Tomcat 中 使用 .NET 客户端对 WebService 进行访问,执行 ATSQL 实现步骤: 1、 引用包装好的 WebService 2、 调用相应的接口,使用 ATSQL 语句创建业务表 3、 调用相应的接口实现 ATSQL 数据操作(查询、插入、删除等) 创建业务表: 和在关系数据库(如 Oracle )中创建表不同,创建时间数据库表需要调用 TimeDB 的 接口,执行 ATSQL 而实现。 ATSQL2 是一个 SQL 标准,具体使用请参考相关文档。 如何创建一个标准的 Windows 服务 Posted on 2009-02-11 13:08 伍华聪 阅读 (3658) 评论 (16) 编辑 收藏 在很多时候,我们需要一个定时器,当间隔某段时间或者在某一个时刻的时候,触发某 个业务的处理,这个时候,我们就可能需要引入 Windows 服务来做这个事情,如某些 数据的同步操作、某些工作任务的创建或者侦听某些端口的工作等等。 做过 Windows Forms 开发的人,对开发 Windows 服务可能会熟悉一些,其实它本 身应该算是一个 Windows Forms 程序。基本上整个 Windows 服务的程序分为几个 部分:安装操作实现、程序启动、服务操作等。 本例子创建一个 Windows 服务,服务可以在整点运行,也可以在某段间隔时间运行, 通过配置指定相关的参数。 完整的服务代码请下载文件进行学习: http://files.cnblogs.com/wuhuacong/AutoSyncService.rar 1)安装操作类的实现 首先需要继承 System.Configuration.Install.Installer 类,并且需要增加 ServiceProcessInstaller 、ServiceInstaller 两个对象来处理,另外您需要重载 BeforeUninstall 和 AfterInstall 来实现服务在安装前后的启动和停止操作。 [RunInstaller(true)] public class ListenInstaller : Installer { private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller; private System.ServiceProcess.ServiceInstaller serviceInstaller; /// /// 必需的设计器变量。 /// private System.ComponentModel.IContainer components = null; public ListenInstaller() { InitializeComponent(); //重新覆盖设计时赋予的服务名称 this.serviceInstaller.DisplayName = Constants.ServiceName; this.serviceInstaller.ServiceName = Constants.ServiceName; } public override void Install(System.Collections.IDictionary stateSaver) { base.Install(stateSaver); } private void serviceInstaller_AfterInstall(object sender, InstallEventArgs e) { ServiceController service = new ServiceController(Constants.ServiceName); if (service.Status != ServiceControllerStatus.Running) { try { service.Start(); } catch (Exception ex) { EventLog loger; loger = new EventLog(); loger.Log = "Application"; loger.Source = Constants.ServiceName; loger.WriteEntry(ex.Message + "\n" + ex.StackTrace, EventLogEntryType.Error); } } } private void serviceInstaller_BeforeUninstall(object sender, InstallEventArgs e) { ServiceController service = new ServiceController(Constants.ServiceName); if (service.Status != ServiceControllerStatus.Stopped) { try { service.Stop(); } catch (Exception ex) { EventLog loger; loger = new EventLog(); loger.Log = "Application"; loger.Source = Constants.ServiceName; loger.WriteEntry(ex.Message + "\n" + ex.StackTrace, EventLogEntryType.Error); } } } ............... } 2)程序启动 程序的启动很简单,基本上是自动创建服务程序的时候就生成了,这里列出来解析是 为了说明服务调试的操作。 程序的启动是在 Main 函数里面,添加下面的代码即可 Code ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new SocketService() }; ServiceBase.Run(ServicesToRun); 上面是标准的启动代码,但很多时候,我们需要调试服务,因此会加入一个跳转的开 关 Code #region 调试程序时使用的代码 //使用方法: 在该 Project 的属性页, 设置输入参数 "-T", 即可进入下面这段 代码,发布时请去掉参数;if (args.Length >= 1 && args[0].ToUpper() == "-T") { try { SocketService service = new SocketService(); service.Execute(); } catch (Exception ex) { throw ex; } return; } #endregion 上面的操作就是为了可以使用普通的调试功能调试 Windows 服务,其中的 "-T" 是在开 发工具 VS 的 IDE 上设置的一个参数 , 如下图所示。 3)服务操作 首先需要创建一个集成自 System.ServiceProcess.ServiceBase的服务类, 如 SocketService 服务类,在 SocketService 类的构造函数中,您可能需要初始化一些信息,如创建一个定时 器,修改服务器类的名称,读取配置参数等信息,以便初始化服务类的参数。接着您需要重载服务基类的一些函数: OnStart、 OnStop、 OnContinue 、 OnPause、 OnShutdown 和定时器的触发函数 timerReAlarm_Elapsed 。完整的类如下 Code public class SocketService : ServiceBase { private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private AppConfig appConfig = new AppConfig(); private System.Timers.Timer timerReAlarm; private int ServiceCycle = 1;// 服务运行间隔,和整点运行相斥(单位分钟) private int CycleCount = 0;// 间隔的服务运行计数(单位分钟) private int ServiceRunAt = 0;// 整点运行服务时间,负数为禁用(单位小时) private bool RunAtOnce = false;// 整点运行服务是否已经运行 private bool GBLService = false;// 是否启动 GBL 同步服务 private bool DomainService = false;// 是否启动域用户同步 /// /// 必需的设计器变量。 /// private System.ComponentModel.IContainer components = null; private System.Diagnostics.EventLog eventLog; public SocketService() { InitializeComponent(); eventLog = new EventLog(); eventLog.Log = "Application"; eventLog.Source = Constants.ServiceName; this.ServiceName = Constants.ServiceName; try { //系统心跳 int interval = int.Parse(appConfig.AppConfigGet("TimerInterval")); interval = (interval < 1) ? 1 : interval;// 不能太小 timerReAlarm = new System.Timers.Timer(interval * 60000);// 分钟 timerReAlarm.Elapsed += new ElapsedEventHandler(timerReAlarm_Elapsed); //服务运行间隔 ServiceCycle = int.Parse(appConfig.AppConfigGet("ServiceCycle")); ServiceCycle = (ServiceCycle < interval) ? interval : ServiceCycle;// 不能 小于心跳 //服务整点运行 ServiceRunAt = int.Parse(appConfig.AppConfigGet("ServiceRunAt")); GBLService = Convert.ToBoolean(appConfig.AppConfigGet("GBLService")); DomainService = Convert.ToBoolean(appConfig.AppConfigGet("DomainService")); logger.Info(Constants.ServiceName + " 已初始化完成 "); } catch (Exception ex) { logger.Error(Constants.ServiceName + " 初始化错误 ", ex); } } /// /// 设置具体的操作,以便服务可以执行它的工作。 /// protected override void OnStart(string[] args) { logger.Info(Constants.ServiceName + " 开始启动。 "); timerReAlarm.Start(); eventLog.WriteEntry(Constants.ServiceName + " 已 成 功 启 动 。 ", EventLogEntryType.Information); CreateTask(); } /// /// 停止此服务。 /// protected override void OnStop() { timerReAlarm.Stop(); } /// /// 暂停后继续运行 /// protected override void OnContinue() { timerReAlarm.Start(); base.OnContinue(); } /// /// 暂停 /// protected override void OnPause() { timerReAlarm.Stop(); base.OnPause(); } /// /// 关闭计算机 /// protected override void OnShutdown() { base.OnShutdown(); } private void timerReAlarm_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { CreateTask(); } /// /// 使用线程池方式运行程序 /// 优点 :快速启动 Windows 服务 , 在后台继续程序操作 . /// private void CreateTask() { ThreadPool.QueueUserWorkItem(new WaitCallback(ExecuteTask), null); } /// /// 开始执行同步任务 /// private void ExecuteTask(object status) { try { //采用整点运行方式 if(ServiceRunAt > 0) { if(DateTime.Now.Hour == ServiceRunAt) { if(!RunAtOnce) { Execute(); RunAtOnce = true;// 标识整点已经运行过了 } } else { RunAtOnce = false; } } else//采用间隔运行方式 { //不管服务间隔是否心跳的倍数,只要服务间隔时间大于等于间 隔时间,就执行一次 if(CycleCount >= ServiceCycle) { Execute(); CycleCount = 0; } else { CycleCount++; } } } catch (Exception ex) { logger.Error("ExecuteTask() 函数发生错误 ", ex); eventLog.WriteEntry(Constants.ServiceName + " 运行时出现异常! \r\n" + ex.Message + "\r\n" + ex.Source + "\r\n" + ex.StackTrace); } } public void Execute() { //初始化数据库连接 string DatabasePassword = Sys.decode(ConfigurationSettings.AppSettings.Get("DatabasePassword")); DAO.init(ConfigurationSettings.AppSettings.Get("DatabaseConnect").Replace("{$password}", DatabasePassword), DAO.DATABASE_SQLSERVER); // 初始化数据库 (SQL Server)访问对象 Lib.adPasswd = Sys.decode(ConfigurationSettings.AppSettings.Get("password")); if(GBLService) { AutomatismXml xml = new AutomatismXml(); xml.AutomatismXmlData(0); logger.Info(Constants.ServiceName + DateTime.Now.ToShortTimeString() + " 已成功调用了 GBLService 一次。 "); eventLog.WriteEntry(DateTime.Now.ToShortTimeString() + "已成功调 用了 GBLService 一次。 ", EventLogEntryType.Information); } if(DomainService) { string msg = string.Empty; string path = ConfigurationSettings.AppSettings.Get("path"); string username = ConfigurationSettings.AppSettings.Get("username"); string domain = ConfigurationSettings.AppSettings.Get("domain"); AD.init(path, username, Lib.adPasswd, domain); DomainHelper.accountSync(true, false, true, ref msg, 1); Log.saveADLog(null, " 系统同步域用户 :" + msg); logger.Info(Constants.ServiceName + DateTime.Now.ToShortTimeString() + " 已成功调用了 DomainService 一次。 "); eventLog.WriteEntry(DateTime.Now.ToShortTimeString() + "已成功调 用了 DomainService 一次。 ", EventLogEntryType.Information); } } ................... } 4. 使用 InstallUtil 来安装和卸载服务 安装和卸载 Windows 服务,需要使用 InstallUtil 工具类进行操作,该工具是 Dotnet 框 架附带的一个工具,在 %SystemRoot%\Microsoft.NET\Framework\*** 对应的目录中。 其中 App.config 中的内容如下 Code 安装 Windows 服务的命令如下: Code @ECHO OFF REM The following directory is for .NET1.1 set DOTNETFX=%SystemRoot%\Microsoft.NET\Framework\v1.1.4322 set PATH=%PATH%;%DOTNETFX% cd\ 用户信息同步服务 " echo 正在安装 用户信息同步服务 echo --------------------------------------------------- InstallUtil /i AutoSyncService.exe echo --------------------------------------------------- echo Done. Exit 卸载 Windows 服务的命令如下: Code @ECHO OFF REM The following directory is for .NET1.1 set DOTNETFX=%SystemRoot%\Microsoft.NET\Framework\v1.1.4322 set PATH=%PATH%;%DOTNETFX% cd\ 用户信息同步服务 " echo 正在卸载 用户信息同步服务 echo --------------------------------------------------- InstallUtil /U AutoSyncService.exe echo --------------------------------------------------- echo Done. Exit
还剩248页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

gdc123

贡献于2017-06-12

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf