wenmo8 发布的文章

在我们基于微信开发框架各种微信项目的时候,往往一个场景一个菜单集合,多个微信项目就可能存在多个不同的菜单组合。由于微信菜单的数量限制,我们想测试一下其他不同项目的菜单功能的时候,如果没有一个很好的管理方案,就会显得很麻烦,本篇随笔介绍如何在管理微信菜单的基础上增加一个菜单组的概念,以便随时激活某个项目的菜单集合,提交到服务器上进行项目切换或者功能测试。

1、项目菜单的处理

一般来说,我们受限于微信菜单的数量限制,微信菜单不能超过三个大菜单,每个大菜单下最多可以延伸五个子菜单,那么一般我们可以在微信应用中管理这些菜单,方便提交到微信服务器上,如下是对菜单树列表的管理。

以及我们对每个菜单进行详细列表信息的管理,包括新增,修改、删除、查询等等的维护。

维护单个菜单的时候,编辑界面如下所示。

如果我们没有引入菜单分组,那么我们只有对部分菜单进行禁用或者删除的处理,一旦在里面整合了多个项目的菜单,数量就不少,就会更加难以管理了。

我们希望保留多个项目的菜单以供参考或者功能测试,那么我们就需要引入一个菜单分组的概念,以便更好的管理这些微信应用菜单。

2、引入菜单分组的菜单管理界面

首先我们在管理的入口先维护菜单的分组,菜单分组其实也是一个菜单记录,只是作为一个顶端的菜单记录存在,方便维护而已。

菜单分组可以禁用、启用,新增、编辑,以及展示该分组下的树形列表和提交到微信服务器上的功能,如下所示是我们对菜单分组的界面设计。

这个是作为一级菜单组的管理界面,以便给我们一个直观的菜单集合的树列表,如果我们需要详细维护菜单的数据,我们可以引导用户进入一个菜单维护的界面,如下所示。

这样我们就有一个对菜单群进行统一维护的菜单分组,也方便我们随时启用某个菜单组群,以便对某个应用进行简单的测试或者体验,特别在我们微信框架整合了多个项目,以便客户切换不同项目进行使用,就显得更加方便。某个时候,随时提交一下就可以及时进行测试,不需要的时候,再更换一个不同的应用菜单群组。

多个项目之间随时提交切换,非常方便。

以上就是关于菜单分组的管理心得,希望对大家有所启发。

之前我在随笔《
使用FastReport报表工具生成报表PDF文档
》介绍过使用FastReport.Net来根据报表模板进行生成PDF,以及随笔《
使用FastReport报表工具生成标签打印文档
》介绍过生成标签文档的处理,基本上都是基于模板进行生成PDF的做法,由于PDF在手机端显示总是觉得不那么方便,不管使用pdfjs或者直接打开PDF,都不太令客户满意,客户希望直接展示图片的方式显示。本篇随笔介绍如何使用FastReport.Net来直接生成报表格式的图片文件。

1、报表模板及数据绑定处理

FastReport.Net是一款适用于Windows Forms, ASP.NET和MVC框架的功能齐全的报表分析解决方案。之前使用直接生成PDF的方式构建报表文档,如下效果所示。

在FastReport设计报表模式里面,我们可以对报表进行设置,以便于在实际运行环境的情况下生成上面的报表文档。在FastReport设计器里面的效果如下所示。

这个报表包含了主表信息,和明细表的信息,我们主表动态信息,可以通过参数的绑定方式绑定,明细表则通过绑定DataTable的方式动态处理即可。

采用参数绑定,我们需要在报表设计器里面定义好我们需要的参数,如下所示。

我们一般预先定义好相关的参数,然后绑定在模板里面,并设置好内容的对其格式即可。

如报表页面里面,我们放置了一个表格,定义好表格的行列和宽度后,双击表格单元格,就可以设置表格单元格的文本内容为对应的参数了,如下界面所示。

对于动态展示的明细列表部分,我们需要定义一个数据源的方式,从而可以让报表模板绑定对应的字段名称。

我根据数据表的信息,生成一个用于绑定明细列表的数据源,如下所示。

这样我们在代码绑定的时候,只需要指定Detail的名称和对应的字段名称即可,有了这些定义,我们可以在报表设计中使用字段绑定了。

对于运行时刻报表数据的绑定,主要使用对应的对象的注册数据和设置参数处理函数即可实现。

//刷新数据源
report.RegisterData(dt, "Detail");foreach (string key indict.Keys)
{
report.SetParameterValue(key, dict[key]);
}
//运行报表 report.Prepare();//导出PDF报表 PDFExport export = newPDFExport();
report.Export(export, realPath);
report.Dispose();

2、实现报表生成图片文档

图片生成的处理,和PDF格式的处理大同小异,主要就是先处理数据的绑定和准备,后续在根据对应的文件后缀名进行相应的图片生成,PDF生成使用PDFExport,图片生成采用的是ImageExport类处理。

如果是基于Web开发的,我们在控制器上处理对应的报表输出文件名称和路径,如下所示。

//导出PDF的文件路径
string exportPdfPath = string.Format("/GenerateFiles/Pres/Report_{0}.jpg", id);//转换为物理路径
string realPath = Server.MapPath(exportPdfPath);

加载报表模板并初始化,这个不管PDF或者换图片格式,处理一样。

//以报表模板,初始化报表对象
Report report = newReport();
report.Load(reportPath);

BS下生成并输出图片文件如下所示

//刷新数据源
report.RegisterData(dt, "Detail");foreach (string key indict.Keys)
{
report.SetParameterValue(key, dict[key]);
}
//运行报表 report.Prepare();//导出PDF报表//PDFExport export = new PDFExport();//导出JPG报表 ImageExport export = newImageExport();//export.JpegQuality = 392;//export.ResolutionY = 226; report.Export(export, realPath);
report.Dispose();

result
= Content(exportPdfPath);//返回Web相对路径

我们看到,图片生成的操作和PDF的处理差不多。

最后生成图片的界面效果如下所示。

如果报表的页数超过一页,我们可以通过参数来生成不同的图片,如下所示。

new ImageExport() { PageRange = PageRange.Current, CurPage = count }

我们来看看另外一个处理代码,如下所示。

//多个图片导出
int count = 1;string firstFileName = exportImgPath.Replace(".png", "");foreach (PageBase item inreport.Pages)
{
string fileName = string.Format("{0}_{1}.png", firstFileName, count);
exportImgPath
=fileName;
report.Export(
new ImageExport() { PageRange = PageRange.Current, CurPage =count }, fileName);
count
++;
}

和之前的不同,这个图片格式指定为PNG,另外可以支持多个页面的图片生成。

针对FastReport报表的特性,我在综合案例里面编写了一个专门用来处理FastREport的案例代码,如下所示。

在较早期的报表套打的时候,我倾向于使用LODOP的ActiveX进行报表的打印或者套打,BS效果还是很不错的。之前利用它在Winform程序里面实现信封套打功能,详细参考《
基于信封套打以及批量打印的实现过程
》,虽然功能能够完美实现,不过由于还需要附带一个不是百分百整合一起的插件,还是有点另类的,虽然只是第一次使用的时候,需要安装一次插件即可。本篇随笔介绍一下如何旧瓶装新酒,使用FastReport报表工具来实现信封的套打及批量打印的功能。

1、信封套打及批量打印功能的回顾

首先我们要有一些特制的信封或者普通信封,这样才能基于这个基础上进行套打,把邮政编码、地址和联系人等信息打印上去。

然后你需要有一个打印设备,我这里采用了一个佳能的喷墨打印机(当然其他的也没问题)。

其次我们开发一个工具来快速实现数据的导入和批量打印,如下界面所示。

最后当然能够满足要求的打印大量的信封出来,减少我们人工干预的麻烦了。

2、使用FastReport报表工具来实现信封的套打及批量打印

首先我们模仿上面的工具界面来做一个新的Winform程序,这次使用DevExpress界面来做,得到界面如下所示。

功能和前面软件的基本一样,只是界面有所变化差异而已。

现在我们来聊聊如何FastReport报表工具来实现套打的处理,这样我们就可以使用它来进行信封的打印了。

首先,和前面随笔《
使用FastReport报表工具生成报表PDF文档
》/《
使用FastReport报表工具生成标签打印文档
》等文章介绍的一样,我们套打一样需要准备好一个报表模板,然后基于这个基础上进行套打处理。

套打的原理,就是预设一个图片和报表的大小,以及放置一些需要打印的内容,预设的图片一般不需要打印出来,这样其他内容打印在特定的纸张上就实现了证书、信封、发票、快递单等的打印了。

而使用FastReport报表工具,我们可以对报表模板里面的元素的位置、大小、样式等进行一定的调整,以适应我们具体的报表需要,基于这个模式我们先设计一个FastReport报表模板,如下所示。

以上模板的设置,主要就是注意定义好几个参数,并将参数和具体的展示控件进行绑定,并加入一个图片作为不打印的元素即可。

报表在运行时刻可以进行模板的调整,如下是报表的【打印设计】界面。

我们可以利用FastReport提供的报表设计工具进行元素位置、大小、样式等方面的调整。这样就可以给客户很大的灵活性进行处理。

报表打印的操作如下代码所示。

        /// <summary>
        ///报表打印测试/// </summary>
        private void btnPrintTest_Click(objectsender, EventArgs e)
{
if(this.txtAddress.Text.Length == 0)
{
MessageDxUtil.ShowTips(
"请输入地址");this.txtAddress.Focus();return;
}
else if (this.txtReceiver.Text.Length == 0)
{
MessageDxUtil.ShowTips(
"请输入收件人");this.txtReceiver.Focus();return;
}

FrmReportPreview dlg
= newFrmReportPreview();var report =dlg.Report;//加载报表 var reportFile = Path.Combine(baseDir, "Report/信封报表.frx");
report.Load(reportFile);
//绑定数据源//定义参数和数据格式 var dict = new Dictionary<string, object>();var zipCode = txtPostCode.Text.Trim().PadRight(6, ' ').ToCharArray();
dict.Add(
"C1", zipCode[0]);
dict.Add(
"C2", zipCode[1]);
dict.Add(
"C3", zipCode[2]);
dict.Add(
"C4", zipCode[3]);
dict.Add(
"C5", zipCode[4]);
dict.Add(
"C6", zipCode[5]);
dict.Add(
"Address", this.txtAddress.Text.Trim());var Recipient = this.txtReceiver.Text.Trim();if(!Recipient.EndsWith(""))
{
Recipient
+= "";
}
dict.Add(
"Recipient", Recipient);//刷新数据源 foreach (string key indict.Keys)
{
report.SetParameterValue(key, dict[key]);
}

dlg.ShowDialog();
}

以上打印处理的时候,会调用打印预览界面展示数据,如下界面所示。

报表打印设计处理,和打印测试差不多,也需要绑定数据,方便预览,代码如下所示。

        /// <summary>
        ///报表打印设计/// </summary>
        private void btnPrintDesign_Click(objectsender, EventArgs e)
{
FrmReportDesign dlg
= newFrmReportDesign();var report =dlg.Report;//加载报表文件 var reportFile = Path.Combine(baseDir, "Report/信封报表.frx");
report.Load(reportFile);
//绑定数据源//定义参数和数据格式 var dict = new Dictionary<string, object>();var zipCode = txtPostCode.Text.Trim().PadRight(6, ' ').ToCharArray();
dict.Add(
"C1", zipCode[0]);
dict.Add(
"C2", zipCode[1]);
dict.Add(
"C3", zipCode[2]);
dict.Add(
"C4", zipCode[3]);
dict.Add(
"C5", zipCode[4]);
dict.Add(
"C6", zipCode[5]);
dict.Add(
"Address", this.txtAddress.Text.Trim());var Recipient = this.txtReceiver.Text.Trim();if (!Recipient.EndsWith(""))
{
Recipient
+= "";
}
dict.Add(
"Recipient", Recipient);//刷新数据源 foreach (string key indict.Keys)
{
report.SetParameterValue(key, dict[key]);
}

dlg.ShowDialog();
}

信封打印,我们不需要一个个确认打印对话框,指定那个PrintDialog为False即可。信封的批量打印代码如下所示。

        /// <summary>
        ///信封批量打印操作/// </summary>
        private void btnBatchPrint_Click(objectsender, EventArgs e)
{
if(dtImport == null || dtImport.Rows.Count == 0)
{
MessageDxUtil.ShowTips(
"没有打印数据");return;
}

FastReport.Report report
= newFastReport.Report();
FastReport.Utils.Res.LocaleFolder
= Path.Combine(baseDir, "L18N");var file = FastReport.Utils.Res.LocaleFolder + @"Chinese (Simplified).frl";
FastReport.Utils.Res.LoadLocale(file);
//加载报表 var reportFile = Path.Combine(baseDir, "Report/信封报表.frx");
report.Load(reportFile);

SplashScreenHelper.Show(
typeof(FrmWaitForm));
SplashScreenHelper.SetCaption(
"正在打印...");int total =dtImport.Rows.Count;int i = 0;foreach(DataRow dr indtImport.Rows)
{
SplashScreenHelper.SetDescription(
string.Format("正在打印...任务 {0} / {1}", i++, total));//绑定数据源//定义参数和数据格式 var dict = new Dictionary<string, object>();var zipCode = dr["邮编"].ToString().Trim().PadRight(6, ' ').ToCharArray();
dict.Add(
"C1", zipCode[0]);
dict.Add(
"C2", zipCode[1]);
dict.Add(
"C3", zipCode[2]);
dict.Add(
"C4", zipCode[3]);
dict.Add(
"C5", zipCode[4]);
dict.Add(
"C6", zipCode[5]);
dict.Add(
"Address", dr["地址"].ToString().Trim());var Recipient = dr["收件人"].ToString().Trim();if (!Recipient.EndsWith(""))
{
Recipient
+= "";
}
dict.Add(
"Recipient", Recipient);//刷新数据源 foreach (string key indict.Keys)
{
report.SetParameterValue(key, dict[key]);
}

report.PrintSettings.ShowDialog
= false;
report.Print();

Thread.Sleep(
100);
}
SplashScreenHelper.SetCaption(
"打印完成!");
SplashScreenHelper.SetDescription(
string.Format("完成全部打印,共打印【{0}】份!", total));
Thread.Sleep(
500);
SplashScreenHelper.Close();
}

我们使用该批量模式测试打印几个信封,效果如下所示。

如果导入数据很多,那么一样和前面的软件打印效果一样,中间不需要人工接入,喝茶等着完成即可。

这个就是整合了FastReport报表工具实现信封套打功能的软件,这样整合后,软件体验性就更加完美了。

我在较早之前的随笔《
基于MVC4+EasyUI的Web开发框架形成之旅--附件上传组件uploadify的使用
》Web框架介绍中介绍了基于Uploadify的文件上传操作,免费版本用的是Jquery+Flash实现文件的上传处理,HTML5收费版本的没有试过。随着Flash逐渐退出整个环境,很多浏览器都可能不再支持Flash的了,因此在更新原有EasyUI整个框架的时候,我们对框架全面进行了完善,包括替换了这个Uploadify的文件上传模块,使用EasyUI内置的FileBox控件,结合Jquery的Ajax操作,我们就可以实现了单文件或者多文件的上传操作了。本篇随笔就主要基于这个主题,介绍基于FileBox控件实现文件上传处理,包括前端页面代码和后端的C#处理代码的操作。

1、FileBox控件的使用

FileBox控件原理上是一个TextBox控件和隐藏的一个File控件,因此对于TextBox控件的变化时间onchange依旧可以使用,而由于其隐藏了File控件,那么需要根据DOM规则获取其下面的File控件,从而可以获得文件列表进行Ajax的上传操作,同时由于File文件是一个特殊的内容,我们在上传的时候,使用了JS的FormData对象容器来进行承载文件信息和其他信息,从而能够顺利把文件及其他内容信息一并通过AJax调用,上传到后台去处理。

按照官方的示例,其实就是定义一个HTML元素,并通过脚本方式初始化对应的样式和事件即可,如下所示。

<inputclass="easyui-filebox"style="width:300px">
//初始化脚本
$('#fb').filebox({
buttonText:
'选择文件',
buttonAlign:
'left'})

这样就可以了。

我们来看看我的实际案例,一般需要上传附件的地方,包括导入Excel文件的记录,或者上传文件作为记录的附件,前者一般是单文件的方式,一次导入一个Excel文件,并加载Excel进行展示,如下所示。

文件控件的部分代码如下所示

        <divid="tb"style="padding:5px;height:auto">
        <!-------------------------------搜索框----------------------------------->
            <fieldset>
                <legend>Excel导入操作</legend>
                <formid="ffSearch"method="post"enctype="multipart/form-data">
                    <divtitle="Excel导入操作"style="padding: 5px"data-options="iconCls:'icon-key'">
                        <inputclass="easyui-validatebox"type="hidden"id="AttachGUID"name="AttachGUID" />
                        <br/>
                        <inputclass="easyui-filebox"id="file_upload"style="width:300px" />
                    </div>
                </form>
                <divid="div_files"></div>
            </fieldset>
                
        <!-------------------------------详细信息展示表格----------------------------------->
        <tableid="grid"title="用户操作"data-options="iconCls:'icon-view'">            
        </table>
    </div>

和前面Demo的定义类似,这里只是声明了一个FileBox控件,主要还是通过JS代码来进行初始化,和相关的控制。

    <scripttype="text/javascript">$(function() {//添加对话框,上传控件初始化
$('#file_upload').filebox({
buttonText:
'选择文件',//按钮文本 buttonAlign:'right',//按钮对齐 //multiple: true, //是否多文件方式 //accept: "image/gif,image/bmp,image/jpeg,image/jpg,image/png", //指定文件类型 onChange:function(e) {
UploadFile(
this,"file_upload","AttachGUID","div_files");//上传处理 }
});
});
</script>

通过JS的控制,就可以较好的呈现文件上传的控件了,比不用EasyUI的样式好看很多。

通过onchange的控制,可以对文件操作及时进行响应,如我这里选择文件后,马上进行上传,并进行数据的读取和显示在Datagrid里面。

文件上传的JS逻辑代码如下所示,全部贴出来供参考。

        //上传文件操作
        functionUploadFile(_obj, file_ctrlname, guid_ctrlname, div_files) {var value = $("#" + file_ctrlname).filebox('getValue');var files = $("#" + file_ctrlname).next().find('input[type=file]')[0].files;//console.log(files);

            //传入this参数,也可以用这个获取文件
            //var files = $(_obj).context.ownerDocument.activeElement.files;
            //console.log(files);
            
            var guid = $("#" +guid_ctrlname).val();if (value && files[0]) {
                //构建一个FormData存储复杂对象
                var formData = newFormData();
formData.append(
"folder", '数据导入文件');
formData.append(
"guid", guid);
formData.append(
'Filedata', files[0]);//默认的文件数据名为“Filedata” $.ajax({
url:
'/FileUpload/Upload', //单文件上传 type: 'POST',
processData:
false,
contentType:
false,
data: formData,
success:
function(json) {//转义JSON为对象 var data =$.parseJSON(json);//提示用户Excel格式是否正常,如果正常加载数据 ShowUpFiles(guid, div_files);

$.ajax({
url:
'/BillDetail_Cust/CheckExcelColumns?guid=' +guid,
type:
'get',
dataType:
'json',
success:
function(data) {if(data.Success) {
InitGrid();
//重新刷新表格数据 showTips("文件已上传,数据加载完毕!");
}
else{
showTips(
"上传的Excel文件检查不通过。请根据页面右上角的Excel模板格式进行数据录入。");
}
}
});
},
error:
function(xhr, status, error) {
$.messager.alert(
"提示", "操作失败"); //xhr.responseText }
});
}
}

我们这里使用了FormData来存储文件复杂对象。

var formData = new FormData();

通过查询控件的子DOM对象,我们获得File控件对象,并获取控件里面的Files属性。

var files = $("#" + file_ctrlname).next().find('input[type=file]')[0].files;

文件上传后,我们在文件框下面显示已经上传的文件列表,如下代码所示。

ShowUpFiles(guid, div_files);

文件上传到服务器后,我们通过接口获取对应的Excel文件,并转换为JSON列表返回,供页面绑定数据展示。

InitGrid(); //重新刷新表格数据

上传文件展示列表信息,整体界面效果如下所示。

单击删除,弹出确认删除对话框后移除文件即可。

上面通过限定,使用了一个文件,以及可以指定上传文件的格式等,如果我们使用多文件上传,客户端只需要设置Multiple为True即可,后端也不需要调整接口 ,使用同一个接口进行处理即可。

多文件的上传场景,一般是某个记录需要上传文件的情况下,在录入或者编辑界面提供文件上传处理,如下所示。

我们这里可以选择多个文件进行一并上传,初始化fileBox控件的时候,只需要指定multiple为true即可。

            //添加对话框,上传控件初始化
            $('#file_upload').filebox({
buttonText:
'选择文件', //按钮文本 buttonAlign: 'right', //按钮对齐 multiple: true,//accept: "image/gif,image/bmp,image/jpeg,image/jpg,image/png", //指定文件类型 onChange: function(e) {
UploadFile(
this, "file_upload", "Attachment_GUID", "div_files");//上传处理 }
});

文件的上传和前面操作类似,只不过,我们这次添加了多个文件。

    //构建一个FormData存储复杂对象
    var formData = newFormData();
formData.append(
"folder", '政策法规');
formData.append(
"guid", guid);for (var i = 0; i < files.length; i++) {
formData.append(
'Filedata', files[i]);//注意:默认的文件数据名为“Filedata” }

最后也是统一通过ajax进行条用处理

$.ajax({
url:
'/FileUpload/Upload',
type:
'POST',
processData:
false,
contentType:
false,
data: formData,
success:
function(json) {ShowUpFiles(guid, div_files);//完成后更新已上传的文件列表 showTips("上传完毕!"); //提示完成 },
error:
function(xhr, status, error) {
$.messager.alert(
"提示", "操作失败"); //xhr.responseText }
});

以上就是使用EasyUI的FileBox控件进行处理的前端界面部分。

2、文件上传的后端代码处理

前面小节介绍了控件前端的使用,包括HTML代码的定义和JS的初始化,以及使用AJax异步方式上传文件并展示出来等操作。

文件的上传成功,其实也是需要后端的接口处理,需要对文件进行存储,和数据库信息的更新等操作。

前面我们通过AJax操作,在前端通过JS的方式传输FormData的内容。

$.ajax({
url:
'/FileUpload/Upload',
type:
'POST',
processData:
false,
contentType:
false,
data: formData,
success:
function(json) {//转义JSON为对象 //var data = $.parseJSON(json); ShowUpFiles(guid, div_files);//完成后更新已上传的文件列表 showTips("上传完毕!"); //提示完成 },
error:
function(xhr, status, error) {
$.messager.alert(
"提示", "操作失败"); //xhr.responseText }
});

其中/FileUpload/Upload就是处理的路径地址,我们来看看MVC里面的FileUploadController对应方法Upload即可。

先来看看定义和主要处理文件的部分内容。

我们通过HttpContext.Request.Files 获得多个文件的集合,并进行处理即可。

文件的处理,我们先读取文件内容到对象里面,然后使用了业务类进行统一的存储文件和数据库信息即可。

文件上传成功后,我们返回对应的地址给前端使用(如果需要的话),完整的上传处理文件的后端代码如下所示。

        /// <summary>
        ///多文件上传处理/// </summary>
        /// <param name="guid">附件组GUID</param>
        /// <param name="folder">指定的上传目录</param>
        /// <returns></returns>
[AcceptVerbs(HttpVerbs.Post)]public ActionResult Upload(string guid, stringfolder)
{
//如果需要修改字段显示,则参考下面代码处理 dynamic obj = newExpandoObject();
List
<string> urls = new List<string>();var result = newCommonResult();
HttpFileCollectionBase files
=HttpContext.Request.Files;if (files != null)
{
int i = 0;foreach (string key infiles.Keys)
{
try{#region MyRegionHttpPostedFileBase fileData= files[i++];if (fileData != null)
{
HttpContext.Request.ContentEncoding
= Encoding.GetEncoding("UTF-8");
HttpContext.Response.ContentEncoding
= Encoding.GetEncoding("UTF-8");
HttpContext.Response.Charset
= "UTF-8";string fileName = Path.GetFileName(fileData.FileName); //原始文件名称 string fileExtension = Path.GetExtension(fileName); //文件扩展名 FileUploadInfo info= newFileUploadInfo();
info.FileData
=ReadFileBytes(fileData);if (info.FileData != null)
{
info.FileSize
=info.FileData.Length;
}
info.Category
=folder;
info.FileName
=fileName;
info.FileExtend
=fileExtension;
info.AttachmentGUID
=guid;

info.AddTime
=DateTime.Now;
info.Editor
= CurrentUser.Name;//登录人//info.Owner_ID = OwerId;//所属主表记录ID result= BLLFactory<FileUpload>.Instance.Upload(info);if (!result.Success)
{
LogTextHelper.Error(
"上传文件失败:" +result.ErrorMessage);
}
else{//返回具体路径地址 string serverRealPath =info.BasePath.UriCombine(info.SavePath);if (!Path.IsPathRooted(info.BasePath) && !info.BasePath.StartsWith("http://") && !info.BasePath.StartsWith("https://"))
{
//如果是相对目录,加上当前程序的目录才能定位文件地址 var url =HttpContext.Request.Url;var baseurl = url.AbsoluteUri.Replace(url.PathAndQuery, "");
serverRealPath
= baseurl.UriCombine(serverRealPath).Replace('\\', '/');
}
urls.Add(serverRealPath);
}
}
#endregion}catch(Exception ex)
{
result.ErrorMessage
=ex.Message;
LogTextHelper.Error(ex);
}
}
obj.urls
=urls;
}
else{
result.ErrorMessage
= "fileData对象为空";
}
var newResult = new { Success = result.Success, ErrorMessage = result.ErrorMessage, urls =urls };returnToJsonContent(newResult);
}

以上就是使用EasyUI的控件FileBox的前端处理和后端C#代码,从而实现了文件的AJax处理操作,实现较好的体验和功能实现。

完整代码都已全部贴出,方便供参考学习。

在我较早的一篇随笔《
在DevExpress程序中使用TeeList控件以及节点查询的处理
》中,介绍了在树形列表TreeList控件上面,利用SearchControl实现节点的模糊查询过滤操作,效果还是非常不错的,TreeList功能比较强大,界面也相对比微软内置的Winform的TreeView控件美观不少。后来在一次偶然过程中,发现TreeList控件本身就可以打开头部过滤输入,实现节点的快速过滤,不过过滤是完全匹配方式,和我们常规的模糊匹配思路不一样,本篇随笔介绍对TreeList控件常规过滤方式的改进,支持内置输入过滤框中实现节点的模糊查询过滤操作。

1、利用SearchControl实现节点的模糊查询过滤回顾

我在随笔《
在DevExpress程序中使用TeeList控件以及节点查询的处理
》中介绍了整个开发的步骤和思路,最终的实现效果如下所示。

上面那个界面是之前案例的效果,新版本目前已经把不匹配的过滤掉了,最终效果如下所示。

如果我们忽略树列表的初始化操作和绑定数据源的部分,那么实现这个功能,主要的步骤就是在TreeList控件上面增加一个SearchControl的控件,并通代码初始化事件处理即可,如下代码所示。

/// <summary>
///实现树节点的过滤查询/// </summary>
private voidInitSearchControl()
{
this.searchControl1.Client = this.tree;this.tree.FilterNode += (object sender, DevExpress.XtraTreeList.FilterNodeEventArgs e) =>{if (tree.DataSource == null)return;string nodeText = e.Node.GetDisplayText("Name");//参数填写FieldName if (string.IsNullOrWhiteSpace(nodeText))return;bool isExist = nodeText.IndexOf(searchControl1.Text, StringComparison.OrdinalIgnoreCase) >= 0;if(isExist)
{
var node =e.Node.ParentNode;while (node != null)
{
if (!node.Visible)
{
node.Visible
= true;
node
=node.ParentNode;
}
else break;
}
}
e.Node.Visible
=isExist;
e.Handled
= true;
};
}

虽然实现的效果非常不错,不过麻烦的地方就是需要自己添加一个控件,并处理好布局和控件的初始化代码,稍显麻烦一些。

下面的处理方式就是打开内置的过滤输入框,并通过代码的方式实现模糊查询的方式过滤操作。

2、利用TreeList控件内置过滤方式实现模糊查询过滤

上面介绍了一种扩展方式实现节点的模糊查询,我们也可以利用TreeList控件本身具有的节点过滤框控件来实现TreeList控件节点的过滤。

在TreeList控件设计模式下,打开并设置 OptionsView.ShowAutoFilterRow、OptionsBehavior.EnableFiltering为True就可以看到树形控件顶部增加一个内置的控件显示了,如下界面所示。

也可以通过代码方式进行打开。

   this.treeList1.OptionsView.ShowAutoFilterRow = true;//显示过滤行
   this.treeList1.OptionsBehavior.EnableFiltering = true;//开启过滤功能

可以看到顶部多了一个输入框,默认的事件就支持对节点的过滤操作,不过是节点名称完全匹配,类似Equal的处理方式,这个和我们常规需要的操作方式不同,那么就需要进行一定的改进了。

TreeList控件提供ColumnFilterChanged事件进行自定义的过滤操作,我们在其中实现我们想要的部分匹配,也就是模糊查询方式的处理即可。

            this.treeList1.ColumnFilterChanged += (s, e) => //自定义过滤事件
{var tree =(TreeList)s;if (tree != null && tree.ActiveEditor != null)
{
string newKey =tree.ActiveEditor.EditValue.ToString();
tree.FilterNodes();
var operation = new FilterNodeOperation(newKey ?? "");
tree.NodesIterator.DoOperation(operation);
}
};

其中FilterNodeOperation 是我们自定义的一个过滤节点操作对象,具体的定义代码如下所示。

    /// <summary>
    ///过滤节点操作类/// </summary>
    classFilterNodeOperation : TreeListOperation
{
stringpattern;public FilterNodeOperation(string _pattern) { pattern =_pattern; }public override voidExecute(TreeListNode node)
{
if(NodeContainsPattern(node, pattern))
{
node.Visible
= true;if (node.ParentNode != null)
{
node.ParentNode.Visible
= true;
}
}
else{
node.Visible
= false;
}
}
/// <summary> ///模糊包含模式/// </summary> bool NodeContainsPattern(TreeListNode node, stringpattern)
{
foreach (TreeListColumn col innode.TreeList.Columns)
{
if(node.GetValue(col).ToString().ToUpper().Contains(pattern.ToUpper()))
{
return true;
}
}
return false;
}
}

根据上面的操作,做了一个过滤节点的Demo界面。

常规没有过滤的界面效果如下所示。

TreeList节点过滤后,界面效果如下所示。

对比之前的SearchControl实现的效果,这里面没有文本的高亮显示,不过显示也还是比较直观。

整个TreeList的控件处理代码如下所示,贴出供大家参考学习。

        /// <summary>
        ///初始化树/// </summary>
        private voidInitTree()
{
this.treeList1.OptionsBehavior.Editable = false;this.treeList1.OptionsView.EnableAppearanceOddRow = true;this.treeList1.OptionsView.EnableAppearanceEvenRow = true;this.treeList1.OptionsView.RowImagesShowMode = RowImagesShowMode.InCell;//紧凑型图标 this.treeList1.ExpandAll();//列过滤处理 this.treeList1.OptionsView.ShowAutoFilterRow = true;//显示过滤行 this.treeList1.OptionsBehavior.EnableFiltering = true;//开启过滤功能 this.treeList1.ColumnFilterChanged += (s, e) => //自定义过滤事件 {var tree =(TreeList)s;if (tree != null && tree.ActiveEditor != null)
{
string newKey =tree.ActiveEditor.EditValue.ToString();
tree.FilterNodes();
var operation = new FilterNodeOperation(newKey ?? "");
tree.NodesIterator.DoOperation(operation);
}
};
//初始化树节点选择事件 this.treeList1.FocusedNodeChanged += (s, e) =>{this.FocusedNodeChanged(s, e);
};
}
private void FocusedNodeChanged(objects, FocusedNodeChangedEventArgs e)
{
var tree =(TreeList)s;if (tree != null && tree.FocusedNode != null)
{
//选中节点文本 var text = tree.FocusedNode.GetDisplayText(0);switch(text)
{
case "采血":
ChildWinManagement.LoadMdiForm(
this, typeof(FrmOrganization));break;default:break;
}
}
}

这些就是Winform中一些界面处理的技巧,DevExpress界面控件的功能还是非常不错的,我们基类一些界面的处理技巧,可以极大提高用户的体验效果,同时提高我们软件的内在价值。