简介:Janus.WinForms.Controls.Suite v2.0.1000是一款面向WinForms开发者的商业级界面控件包,破解版可无限制使用全部功能组件,适用于需要快速构建专业风格桌面应用的中高级.NET程序员。资源包共3个文件,包含exe安装引导、msi安装包以及htm使用说明文档,整体大小19.12MB,结构精简,便于按需选择安装与查阅。已有355人下载学习。通过该控件包,开发者可直接调用Janus系列网格、日程、导航等常用控件,减少自定义绘制工作量,同时借助附带的说明文档快速掌握属性配置与事件处理要点,有效提升窗体应用开发效率与界面统一性。整套资源便于本地部署,适合中小型项目团队或个人开发者用于日常WinForms开发。
1. 为什么老项目里离不开Janus.WinForms.Controls2.0:一个仍能打的WinForms控件包
接手一个维护了八年的进销存项目,打开解决方案,界面层密密麻麻全是Janus.WinForms.Controls2.0控件包拖出来的窗体。这个WinForms控件包从.NET 2.0时代就是企业桌面应用的常客,GridEX表格、UITab标签页、UICommandBars工具栏、Schedule日程,一套能把整个业务后台整合完。它解决的是老产品继续改、继续加功能的历史包袱问题——不是每个团队都有预算把所有界面重写成WPF或Web,多数时候只能在这套地基上把需求做扎实。适合正在改老系统、在VS2015里做C# WinForms维护、或者第一次接触Janus控件但马上要动手的读者。这份资源把控件包的安装、用法、外观与权限设置归拢到一起,照着做能少走几周的弯路。
2. 摸清控件家族与引用方式:四个主力控件和一个可跑通的Demo
2.1 先想明白为什么选Janus,而不是把界面重写一遍
接手老项目的人最容易犯的错,就是看到GridEX第一眼就想把它换成DataGridView。等到真正动手你会发现,老业务里的分组汇总、单元格编辑模式、打印导出、列权限这些逻辑,全都散在GridEX的事件和属性里。DataGridView本身不提供RowGroup和GroupTotals,你要自己实现分组聚合、合并表头、导出Excel,工作量和风险都成倍上升。
Janus控件包(英文资源名也叫Janus Control)的设计思路,是把企业后台常见的交互做成现成控件:GridEX管表格,UITab管多页签,UICommandBars管工具栏和菜单,Schedule管排班日程。和DevExpress相比,它的体积更小、升级兼容性更好,老项目从.NET 2.0一路升到.NET 4.x,dll替换掉一般还能编译通过。和纯手写控件相比,它省掉的是事件体系、绘制逻辑和打印导出这三大块重复劳动。所以只要项目不是从零开始,我的建议都是先评估改动范围,再决定要不要动这一层。
2.2 在VS2015里把控件包挂进项目:dll、工具箱和licenses.licx
安装包解压后,把需要的dll放到解决方案根目录的libs文件夹里,比如Janus.Windows.GridEX.dll、Janus.Windows.UI.dll。在VS2015里右键项目选择“添加引用”,浏览到libs目录选中这几个dll,然后在工具箱空白处右键“选择项”,浏览到同一个dll,工具箱里就会出现GridEX、UITab、UICommandBars、Schedule等控件。
这里有个新手必踩的隐藏步骤:直接把dll拖进项目往往缺了licenses.licx。Licenses.licx是WinForms授权文件,里面每一行声明一个需要授权的控件,编译时会由VS调用lc.exe生成程序集级的LicenseProvider。老项目用Git管理时licenses.licx经常被.gitignore过滤掉,导致换电脑后一拖控件就报评估版水印。手动补的办法是新建一个文本文件命名为licenses.licx,内容格式如下:
Janus.Windows.GridEX.GridEX, Janus.Windows.GridEX, Version=2.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxx Janus.Windows.UI.CommandBars.UICommandManager, Janus.Windows.UI, Version=2.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxx注意这里的PublicKeyToken不能手抄,要用安装包工具或VS自带SDK里的lc.exe重新生成,token不对,控件在运行时一样不认账。文件建好后,在csproj工程文件里把它标记成嵌入资源:
<EmbeddedResource Include="licenses.licx" />这样编译时就不会出现“控件未授权”的对话框。VS2015里默认的.licx文件位置在项目的Properties文件夹下,如果你看到Properties里已经有一个licenses.licx,直接编辑它即可。
2.3 GridEX、UITab、UICommandBars、Schedule:四个主力控件的定位
先列一张对照表,方便你把老代码里的控件映射到业务场景:
| Janus控件 | 主要类 | 对应原生控件 | 典型业务场景 |
|---|---|---|---|
| GridEX | Janus.Windows.GridEX.GridEX | DataGridView | 单据明细、报表、分组汇总 |
| UITab | Janus.Windows.UI.Tab.UITab | TabControl | 多页签编辑、详情页 |
| UICommandBars | Janus.Windows.UI.CommandBars.UICommandBars | ToolStrip / MenuStrip | 工具栏、右键菜单 |
| Schedule | Janus.Windows.Schedule.Calendar | 无直接对应 | 排班、生产计划、日程 |
GridEX是这套控件包的核心。和DataGridView最大的区别是它有一个始终存在的RootTable,列对象不再是DataGridViewColumn,而是TableColumn。你在代码里设置列宽、格式化、分组模式时,全部要走RootTable.Columns。一个最基础的分组表格配置如下:
// 给GridEX挂数据源,并配置列和分组 this.gridEX1.DataSource = dtOrders; this.gridEX1.RootTable.Columns["OrderNo"].Width = 110; this.gridEX1.RootTable.Columns["OrderDate"].FormatString = "yyyy-MM-dd"; this.gridEX1.RootTable.Columns["Status"].Caption = "订单状态"; this.gridEX1.RootTable.GroupMode = Janus.Windows.GridEX.GroupMode.Nested; this.gridEX1.GroupTotals = Janus.Windows.GridEX.GroupTotals.Always;这里的GroupMode.Nested表示分组行里再嵌套子分组,GroupTotals.Always则让每个分组行都显示小计。这两行是GridEX在报表场景里最常用的配置,也是它比DataGridView省事的关键。值得留意的是FormatString是加在列上的,不是靠单元格格式化事件,批量设置时比原生控件整洁。
2.4 拼一个能马上跑通的Demo:工具栏联动表格与状态栏
控件包单独理解容易,真正组合起来才是老项目的日常。我一般会先做一个最小可跑通的三件套:顶部UICommandBars工具栏、中间GridEX、底部UIStatusBar。这样从工具栏触发刷新、表格显示数据、状态栏提示进度的链路就通了。
// 创建命令管理器,把命令挂到工具栏上 UICommandManager cmdMgr = new UICommandManager(); UICommand refreshCmd = new UICommand("cmdRefresh"); refreshCmd.Text = "刷新数据"; refreshCmd.Image = Properties.Resources.icon_refresh; cmdMgr.Commands.Add(refreshCmd); this.uiCommandBars1.CommandManager = cmdMgr; this.uiCommandBars1.Commands.Add(refreshCmd); // 命令点击事件里重新加载数据并更新状态栏 cmdMgr.CommandClick += delegate(object sender, CommandClickEventArgs e) { if (e.Command.Key == "cmdRefresh") { LoadOrderData(); this.uiStatusBar1.Panels["info"].Text = "数据已刷新,共 " + dtOrders.Rows.Count + " 行"; } };这里的关键是UICommandManager和UICommandBars的绑定关系。CommandManager是命令的集中容器,CommandBars只是可视化的承载,不要让UICommandBars自己去Add控件。事件参数里的e.Command.Key用来判断是哪一个命令被点击,比sender判断控件来源更稳。状态栏里通过Panels["info"]去更新文字,而不是直接用Text属性,因为老项目里状态栏往往有多个面板,直接用Text会覆盖掉左侧的版本号之类的信息。
3. 把控件包用进真实业务:界面美化、状态栏进度条和视频播放
3.1 界面美化:统一主题、配色和外观定制
WinForms原生控件的默认外观放在今天看确实有点旧,Janus控件包的优势在于它有一套VisualStyle体系,可以用来统一整个应用的主题。核心入口是JanusColorScheme,这个对象就像整个界面的调色板,设置好之后GridEX、UICommandBars、UITab全部会跟着变。
// 统一设置一套Office 2010风格,并指定主题色 Janus.Windows.Common.JanusColorScheme scheme = new Janus.Windows.Common.JanusColorScheme(); scheme.VisualStyle = Janus.Windows.Common.VisualStyle.Office2010; scheme.CustomColors = true; scheme.HighlightColor = Color.FromArgb(0, 112, 192); this.gridEX1.ColorScheme = scheme; this.uiCommandBars1.ColorScheme = scheme; this.uiTab1.ColorScheme = scheme;VisualStyle枚举里还有Office2007、WindowsXP等,老机器上跑Office2007风格比Office2013更省资源。CustomColors设为true之后,HighlightColor控制选中行和高亮控件的颜色,相当于整站主题色。注意每个控件都要单独挂一次ColorScheme引用,同一个JanusColorScheme对象可以同时被多个控件引用,这是一个对象多处复用的关系,不是拷贝多份。
3.2 状态栏与进度条:异步导出时别把UI冻结
热词里被问得多的一个场景,是C# WinForms如何更新状态栏与进度条。Janus的UIStatusBar和UIProgressBar接管了这两个区域,但真正让新手翻车的是卡界面——循环导出时进度条不动,窗体拖不动,像死了一样。常见做法是用BackgroundWorker把耗时任务放到后台线程,进度通过ProgressChanged事件回传到UI线程:
private void ExportToExcel() { BackgroundWorker worker = new BackgroundWorker(); worker.WorkerReportsProgress = true; worker.DoWork += delegate(object sender, DoWorkEventArgs e) { // 模拟分批导出,每处理一批就报告一次进度 for (int i = 1; i <= 100; i++) { Thread.Sleep(20); // 实际替换为导出逻辑 worker.ReportProgress(i, "正在导出第 " + i + " / 100 行"); } }; worker.ProgressChanged += delegate(object sender, ProgressChangedEventArgs e) { this.uiProgressBar1.Value = e.ProgressPercentage; this.uiStatusBar1.Panels["info"].Text = e.UserState.ToString(); }; worker.RunWorkerAsync(); }ProgressChanged事件本身已经运行在UI线程,所以这里不需要Invoke。真正需要警惕的是不要在DoWork里直接去改任何Janus控件属性,只让BackgroundWorker后台干活,UI更新全部走ProgressChanged。另一点是UIStatusBar的Panels集合,如果你在设计期已经把面板建好,运行时通过Panels["info"]索引;如果你是在代码里动态添加面板,必须先把Panels.Add(new StatusBarPanel())执行完,再赋值Text,否则会拿到空引用。
3.3 用LayoutPanel承载视频播放器画面
有些管理系统要在界面上嵌入监控画面或播放培训视频,Janus控件包本身不做多媒体解码,但它的LayoutPanel可以作为一个停靠容器,把原生的Windows Media Player COM控件放进去。这种方式比直接在窗体上拖AxWindowsMediaPlayer更干净,因为布局和外观统一由Janus管理。
// 创建一个Windows Media Player控件,放入Janus的LayoutPanel中 AxWMPLib.AxWindowsMediaPlayer wmp = new AxWMPLib.AxWindowsMediaPlayer(); wmp.Dock = DockStyle.Fill; this.layoutPanel1.Controls.Add(wmp); // 设置播放源并开始播放 wmp.URL = @"D:\videos\product_demo.wmv"; wmp.settings.setMode("loop", true); wmp.Ctlcontrols.play();这里有个典型的坑:项目平台目标如果是Any CPU,运行在64位机器上时AxWMPLib会报COM类未注册,因为系统里的WMP控件是32位和64位分开注册的。我一般会把项目平台目标改成x86,或者至少把“首选32位”勾上。LayoutPanel也支持多个子区域切分,如果你需要做“左侧列表、右侧视频”的布局,可以直接在LayoutPanel里再建LayoutPanel,视频播放器只放在右侧那块。
4. 避坑指南:授权水印、DPI缩放、折叠箭头和版本冲突的五个现场
4.1 运行弹“评估版本”:licenses.licx与授权注册表
现象:编译没问题,一运行窗体就弹出评估版本提示,GridEX上有一层水印。原因:licenses.licx缺失,或者licx里的PublicKeyToken和当前dll对不上,LicenseProvider拿不到授权信息。解决:先确认dll版本一致,然后用VS自带的lc.exe根据安装目录里的授权文件重新生成licx。我遇到过一个项目,licx文件在,但引用的dll是从别处拷贝的另一个小版本,token自然不同,换成安装包原始dll后问题消失。命脉就一句话:licx必须和dll一一对应,混用必出问题。
4.2 高DPI下行高错乱、文字模糊
现象:在125%或150%缩放的屏幕上,GridEX行高被截断,列头文字变模糊,UITab的页签也偏小。原因:Janus控件包是GDI绘制的老架构,没有声明PerMonitorV2 DPI感知,系统缩放后它仍然按96dpi算尺寸。解决:在app.manifest文件里把dpiAware设置为true/per-monitor,并在Main入口调用SetProcessDpiAwareness;如果改了之后字体仍发虚,就把GridEX的Font和RowHeight固定成具体像素值,别用Auto。另一种更省事的做法是干脆让整个程序按系统缩放兼容运行,接受模糊但布局不错乱,适合纯内部工具。
4.3 分组折叠箭头点了不展开、不重绘
现象:GridEX分组模式下,点击分组行的折叠箭头,内容没有即时展开,鼠标移动到别处再移回来才刷新。原因:这个版本的GridEX在GroupRow点击后没有主动让箭头区域失效重绘,当AllowGroupBy开启并且多个分组列混用时尤其明显。解决:在GroupRowClick事件里调用gridEX1.UpdateLayout()强制布局刷新。注意不要在RowFormatting事件里频繁给groupRow.Image赋值,否则重绘频繁反而拖慢界面。
this.gridEX1.GroupRowClick += delegate(object sender, RowActionEventArgs e) { // 点击分组行后强制刷新布局,解决箭头不重绘问题 this.gridEX1.UpdateLayout(); };4.4 启动闪退与TypeLoadException:多版本dll打架
现象:部署到客户机器上,程序启动直接闪退,日志里看到TypeLoadException或者找不到Janus.Windows.GridEX的某个类型。原因:开发机上GAC或安装目录里有一个高版本的Janus dll,项目编译时加载了它,但部署时只拷贝了低版本dll,类型签名对不上。解决:所有引用Janus的地方统一使用本地dll,csproj里把SpecificVersion设为true,Copy Local设为true,部署时把dll和exe放同一目录。如果同一台机器上确实存在多版本,可以在app.config里加bindingRedirect强制所有版本跳转到指定版本。
4.5 打包成MSI后控件变回评估版
现象:本地开发正常,打成安装包放到干净机器上装完,窗体弹授权错误。原因:商业控件授权分开发版和部署版,安装包里只拷贝了dll,没有附带运行时授权文件,也没有执行官方激活动作。解决:在安装项目里把licenses.licx和授权证书一并打包,并在安装完成的自定义操作里调用厂商提供的授权安装程序;正式商用环境建议在安装流程里加入联网激活校验,比静默写注册表省心。WinForms打包成安装程序时,不少团队只关注.NET Framework依赖,忘了第三方商业控件的授权也是需要装的,这一条对Janus、DevExpress都适用。
5. 进阶:GridEX主从表绑定和折叠箭头的自定义绘制
GridEX真正值钱的地方是MasterMode。老系统里做“订单主表+订单明细”的联动,原生DataGridView要做两个表格,再监听主表行切换事件去重新查询子表;GridEX可以在一个控件里直接绑定DataSet的关系,主表和子表自动联动展示。
// 为GridEX绑定主从表关系 DataSet ds = new DataSet(); ds.Tables.Add(dtOrders); ds.Tables.Add(dtOrderDetails); ds.Relations.Add("OrderDetails", ds.Tables["Orders"].Columns["OrderID"], ds.Tables["OrderDetails"].Columns["OrderID"]); this.gridEX1.DataSource = ds; this.gridEX1.DataMember = "Orders"; this.gridEX1.RootTable.Relations.Add("OrderDetails", dtOrderDetails, "OrderID", "OrderID", false);最后一行Add的四个参数分别是:关系名、子表、父表关联列、子表关联列、是否级联关闭。这个用法的好处是主表展开时子表记录自动跟随,而且折叠箭头由GridEX内部绘制,不需要自己管展开状态。需要注意DataMember必须设置为主表名,否则GridEX不知道从哪个表开始展示。
再来看热词里反复出现的“winform菜单折叠的箭头是怎么绘制的”。在GridEX里,分组箭头其实是GroupRow的一部分,不是独立控件。如果你想让箭头换成自定义图标,可以在RowFormatting事件里给groupRow的Image赋值:
this.gridEX1.RowFormatting += delegate(object sender, Janus.Windows.GridEX.RowFormattingEventArgs e) { if (e.Row is Janus.Windows.GridEX.GridEXGroupRow) { // 根据展开状态切换箭头图标 e.Row.Image = e.Row.InternalState == Janus.Windows.GridEX.InternalState.Expanded ? Properties.Resources.arrow_down : Properties.Resources.arrow_right; } };这个技巧的边界在于:Janus控件的绘制不归你管,你只是换了图标的图片,点击逻辑仍然是它内部的。所以做完自定义图标,一定要回到第4.3节那个习惯,手动调用UpdateLayout,否则箭头图片和展开状态对不上,用户会觉得这个系统有bug。从那以后我每次做GridEX分组界面,都强制走一遍:先确认GroupMode,再确认GroupRowClick里有没有UpdateLayout,最后才去改图标。这三件事按顺序做,分组交互基本不会翻车。希望帮到你。
本文还有配套的精品资源,点击获取