2023年2月

在我们开发基于WCF访问方式的Winform程序的时候,一般情况下需要对界面显示的字段进行中文显示的解析。如果是硬编码进行中文显示,那么除了不方便调整及代码臃肿外,性能上没有什么问题,但是不建议这样处理;一般情况下,我们把中文对照信息放到业务类里面去统一解析,但是这样会导致每次WCF访问方式请求解析中文化的操作耗费一定的响应时间。如果使用缓存存储中文字段的对照表,那么就不用每次请求WCF的数据访问,减少一些响应时间的消耗,提高用户体验效果。

1、使用硬编码方式的中文化解析操作

硬编码的方式,中文化字段的操作,是在本地进行的,一般响应会比较快,如下代码所示。

        public voidBindData()
{
#region 添加别名解析 this.winGridViewPager1.DisplayColumns = "ID,User_ID,LoginName,FullName,Note,IPAddress,MacAddress,SystemType_ID,LastUpdated";this.winGridViewPager1.AddColumnAlias("ID", "编号");this.winGridViewPager1.AddColumnAlias("User_ID", "登录用户ID");this.winGridViewPager1.AddColumnAlias("LoginName", "登录名");this.winGridViewPager1.AddColumnAlias("FullName", "真实名称");this.winGridViewPager1.AddColumnAlias("Note", "日志描述");this.winGridViewPager1.AddColumnAlias("IPAddress", "IP地址");this.winGridViewPager1.AddColumnAlias("MacAddress", "Mac地址");this.winGridViewPager1.AddColumnAlias("LastUpdated", "记录日期");this.winGridViewPager1.AddColumnAlias("SystemType_ID", "系统类型");#endregion string where =GetConditionSql();
PagerInfo pagerInfo
= this.winGridViewPager1.PagerInfo;
List
<LoginLogInfo> list = CallerFactory<ILoginLogService>.Instance.FindWithPager(where, refpagerInfo);this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<LoginLogInfo>(list);
}

只是这种方式弹性化不太好,如果字段比较多,在界面里面就有很多这样的代码,而且如果多处有这样的解析,就不好控制解析字段名称的一致性。

2、中文化统一解析操作

为了克服第一种方案的弊端,我们可以把中文化参考的操作移到底层DAL去实现,高一层的接口,只需要调用它进行解析(方法GetColumnNameAlias)就可以了。

        /// <summary>
        ///绑定列表数据/// </summary>
        private voidBindData()
{
this.winGridViewPager1.DisplayColumns = "HandNo,CardNo,CardStatus,CardGrade,Name,Sex,Telephone,Mobile,OpenDate,ValidateDate,Discount,Balance,MaxCount,Creator,CreateTime";this.winGridViewPager1.ColumnNameAlias = CallerFactory<IMemberService>.Instance.GetColumnNameAlias();//字段列显示名称转义 string where =GetConditionSql();
List
<MemberInfo> list = CallerFactory<IMemberService>.Instance.FindWithPager(where, this.winGridViewPager1.PagerInfo);this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<MemberInfo>(list);this.winGridViewPager1.PrintTitle = "会员信息报表";
}

这样处理后,解析的统一性提高了,代码也简化了很多,基本上达到了我们期望的效果。但是唯一的问题就是如果是WCF的数据访问方式,那么每次访问都会耗费一定的处理时间。

如果我们使用缓存,第二次直接从本地获取,那么速度会提高很多,特别是表的字段参照对象比较多的时候,性能提高更加明显。

3、使用缓存的操作处理

由于.NET提供了MemoryCache对象给我们进行缓存的处理,我们利用它就可以很好实现了,为了方便,我们可以对它进行一定的封装后在使用。

首先,我们希望封装后提供一个通用的对字段中文化的处理函数,传入相应的参数就可以了。因此先封装好一个辅助类。

    /// <summary>
    ///提供一些常见操作的缓存处理/// </summary>
    public class CacheDataUtil<T> whereT : BaseEntity
{
/// <summary> ///获取指定对象的别名/// </summary> /// <typeparam name="T">实体类信息</typeparam> /// <param name="service">接口服务对象</param> /// <returns></returns> public static Dictionary<string, string> GetColumnNameAlias(IBaseService<T>service)
{
System.Reflection.MethodBase method
=System.Reflection.MethodBase.GetCurrentMethod();string key = string.Format("{0}-{1}-{2}", method.DeclaringType.FullName, method.Name, typeof(T).Name);return MemoryCacheHelper.GetCacheItem<Dictionary<string, string>>(
key,
delegate() { returnservice.GetColumnNameAlias(); },new TimeSpan(24, 0, 0));//24小时,即1天后过期 }

}

然后在主体界面里面,我们绑定分页控件的处理代码如下所示即可。

        /// <summary>
        ///绑定列表数据/// </summary>
        private voidBindData()
{
//entity this.winGridViewPager1.DisplayColumns =displayColumns;//this.winGridViewPager1.ColumnNameAlias = CallerFactory<ICustomerService>.Instance.GetColumnNameAlias();//字段列显示名称转义//使用缓存存储表的别名,可以有效提高二次显示速度 this.winGridViewPager1.ColumnNameAlias = CacheDataUtil<CustomerInfo>.GetColumnNameAlias(CallerFactory<ICustomerService>.Instance);//字段列显示名称转义 string where =GetConditionSql();
PagerInfo pagerInfo
= this.winGridViewPager1.PagerInfo;
List
<CustomerInfo> list = CallerFactory<ICustomerService>.Instance.FindWithPager(where, refpagerInfo);this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<CustomerInfo>(list);this.winGridViewPager1.PrintTitle = "客户信息列表";
}

我们在开发Winform程序的时候,需要经常性的对界面的一些控件进行初始化,或者经常简单的封装,以方便我们在界面设计过程中反复使用。本文主要介绍在我的一些项目中经常性的界面处理操作和代码,以便为大家开发的时候提供必要的参考。

1、选择用户的控件封装操作

在一些系统模块里面,我们需要选择系统人员作为经办人员的操作,如下面几个界面场景所示。

我们注意到,一般在我们选择的时候,界面会弹出一个新的层给我们选择,里面通过列表详细展示相关的信息,还可以支持搜索,非常方便。

当我们完成选择的时候,我们看到界面会只有一个人员名称的显示,不占用额外的地方显示。

这种界面效果是如何实现的呢?下面进行详细的介绍。

1)首先我们定义一个自定义控件,让其继承自XtraUserControl 即可。

    /// <summary>
    ///经办人员、操作人员的选择控件封装/// </summary>
    public partial classOperatorSelectControl : XtraUserControl
{

2)然后在DevExpress的界面工具箱上拖动一个SearchLookUpEdit 控件到我们新的用户控件OperatorSelectControl 上。

调整好用户界面控件的排版相关属性,就会得到下面的界面所示。

3)在设计视图里面,我们为这个SearchLookUpEdit控件的GridView设置它的显示字段,如下所示,每个字段主要绑定FieldName(属性或者字段)和Caption(显示名称)。

这些必备的处理操作完成后,我们可以通过代码或者设计器把这个控件的显示内容和存储内容进行设定,并绑定它的数据源即可(根据需要调用自己的函数),如下所示。

        private void OperatorSelectControl_Load(objectsender, EventArgs e)
{
if (!this.DesignMode)
{
txtOperator.Properties.ValueMember
= "ID";
txtOperator.Properties.DisplayMember
= "FullName";
txtOperator.Properties.DataSource
=SecurityHelper.GetSimpleUsers();
}
}

4)我们为了方便,还可以进一步处理控件的显示内容和返回的值内容,我们希望绑定值或者获取值的时候,使用Text属性就可以了,那么我们重载一下这个自定义控件的Text属性即可。

        public override stringText
{
get{string result = "";if (this.txtOperator.EditValue != null)
{
result
= this.txtOperator.EditValue.ToString();
}
returnresult;
}
set{this.txtOperator.EditValue =value;
}
}

5)这样控件的操作就完成了,编译代码后,我们在工具箱上就可以看到最新的控件图标了。

接着我们把相关的控件拖动到需要的地方(如果是界面和控件在相同的工程里面,注意需要移除重复的工程引用),重新编译系统代码,那么本文开始的界面效果就可以出现了。

2、会员卡级别选择操作

刚才说了,人员选择可以使用这种弹出列表选择的方式,其实很多地方可以用这个方式来进行选择,如一些相对比较少记录的信息就很适合这种显示方式,比如在会员管理里面的,会员卡级别的选择,也可以采用这种方式。

这个模块的做法也和第一种很类似,有点不同的是,我需要选择后把会员卡的折扣也关联显示出来,那我们应该如何处理呢?

1)定义控件和事件处理

为了方便在控件选择后进行事件的触发处理,我在这里定义了一个事件处理器SelectedValueChanged

    /// <summary>
    ///卡级选择控件/// </summary>
    public partial classCardGradeSelectControl : XtraUserControl
{
/// <summary> ///选择项发生变化的事件处理/// </summary> public event EventHandler SelectedValueChanged;

然后在内部控件的EditValue改变的时候,在其中的事件里面触发我们自定义的事件即可, 如下所示。

        private void txtCardGrade_EditValueChanged(objectsender, EventArgs e)
{
if (SelectedValueChanged != null)
{
SelectedValueChanged(sender, e);
}
}

2)在窗体界面中自定义控件的事件调用

在会员编辑界面里面,我们对这个卡级别的选择控件的自定义事件进行处理即可。

处理事件里面,我们获取对应卡级别的优惠折扣(GetDiscountByGradeNo),然后绑定到界面的控件显示即可,这样就实现了联动效果了。

    public partial classFrmEditMember : BaseEditForm
{
/// <summary> ///创建一个临时对象,方便在附件管理中获取存在的GUID/// </summary> private MemberInfo tempInfo = newMemberInfo();publicFrmEditMember()
{
InitializeComponent();
this.txtCardGrade.SelectedValueChanged += newEventHandler(txtCardGrade_SelectedValueChanged);
}
void txtCardGrade_SelectedValueChanged(objectsender, EventArgs e)
{
string gradeNo =txtCardGrade.Text;if (!string.IsNullOrEmpty(gradeNo))
{
this.txtDiscount.Value = BLLFactory<CardGrade>.Instance.GetDiscountByGradeNo(gradeNo);
}
}

3、创建人员和创建时间的显示处理

在我们窗体数据的编辑界面里面,为了友好显示,我们可能需要显示数据的创建人和创建时间。这种效果是如何实现的,我们来看看。

1)数据显示时候处理

数据显示的时候,可能是新增界面的显示,也可以是现有记录的显示,我们需要区分对待。

如果是已有数据,我们需要解析创建人员的ID,显示已有时间即可;如果是新增界面,那么我们把当前登陆用户的名称显示出来,及显示当前时间即可。

在设计模式里面,把控件设置为ReadOnly=true,不用编辑修改控件的值即可。

        /// <summary>
        ///数据显示的函数/// </summary>
        public override voidDisplayData()
{
InitDictItem();
//数据字典加载(公用) if (!string.IsNullOrEmpty(ID))
{
#region 显示信息MemberInfo info= BLLFactory<WHC.Member.BLL.Member>.Instance.FindByID(ID);if (info != null)
{
tempInfo
= info;//重新给临时对象赋值,使之指向存在的记录对象 txtHandNo.Text=info.HandNo;
txtCardNo.Text
=info.CardNo;
txtCardCode.Text
=info.CardCode;
txtCardStatus.SetComboBoxItem(info.CardStatus.ToString());
txtOpenUser.Text=info.OpenUser;
..............

txtCreator.Text
=SecurityHelper.GetFullNameByID(info.Creator);
txtCreateTime.SetDateTime(info.CreateTime);

}
#endregion}else{this.txtOpenDate.DateTime =DateTime.Now;this.txtHandNo.Text =IDUtils.NewId();this.txtCreateTime.DateTime = DateTime.Now;
this.txtCreator.Text = LoginUserInfo.FullName;//默认为当前登录用户
}this.portraitControl1.MemberID =tempInfo.ID;this.portraitControl1.BindPicture(tempInfo.ID);//tempInfo在对象存在则为指定对象,新建则是全新的对象,但有一些初始化的GUID用于附件上传 SetAttachInfo(tempInfo);
}

但用户保存操作的时候,如果是新增数据,我们需要把当前时间和当前用户的信息记录到数据库里面,因此需要增加一些代码进行数据的关联处理。

        /// <summary>
        ///新增状态下的数据保存/// </summary>
        /// <returns></returns>
        public override boolSaveAddNew()
{
MemberInfo info
= tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用 SetInfo(info);

info.Creator
= LoginUserInfo.ID.ToString();
info.CreateTime = DateTime.Now;
info.Dept_ID = LoginUserInfo.DeptId;
info.Company_ID = LoginUserInfo.CompanyId;
try{#region 新增数据 //检查是否还有其他相同关键字的记录 bool exist = BLLFactory<WHC.Member.BLL.Member>.Instance.IsExistKey("CardNo", info.CardNo);if(exist)
{
MessageDxUtil.ShowTips(
"指定的【会员卡号】已经存在,不能重复添加,请修改");return false;
}
bool succeed = BLLFactory<WHC.Member.BLL.Member>.Instance.Insert(info);if(succeed)
{
//可添加其他关联操作 return true;
}
#endregion}catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
return false;
}

我们为了方便,一般是在数据库存储人员的ID,但是列表显示的时候,我们也就需要把对应的人员ID转换为人员名称了。

            this.winGridViewPager1.gridView1.CustomColumnDisplayText += new DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventHandler(gridView1_CustomColumnDisplayText);
        void gridView1_CustomColumnDisplayText(objectsender, DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventArgs e)
{
if (e.Column.FieldName == "Operator" || e.Column.FieldName == "Editor" || e.Column.FieldName == "Creator")
{
if (e.Value != null)
{
e.DisplayText
=SecurityHelper.GetFullNameByID(e.Value.ToString());
}
}
}

在上篇《
会员管理系统的设计和开发(1)
》介绍了关于会员系统的一些总体设计思路和要点,经过一段时间开发,软件终于完成并发布。在这期间,碰到了不少技术难点,并积累了不少开发心得和经验,本篇继续介绍这个开发过程中相关的技术要点,主要介绍其中RDLC报表的相关操作,如报表的设计和通用的动态加载模块的处理等内容。

什么是RDLC呢,它的全称是Report Definition Language Client-Side,原来是微软基于SQL Server 报表服务中经提供了一种被称为报表定义语言(Report Definition Language, RDL)的改进版本,增加了客户端处理报表功能,在WinForm和WebForm中均可以使用这种报表。

1、RDLC报表的设计

我们知道,RDLC报表展现是基于一个定义的报表文件的,一般是以rdlc后缀名的报表文件,在VS里面新建一个项,选定报表文件即可,如下所示。

RDLC的报表设计界面,提供了很多相关的控件进行绘制。

通过这些操作我们可以创建一些报表的元素在里面,如可以增加一些数据字段和列表字段在里面,最简单的报表例子里面,可以增加一些图片,标题,列表等内容,如下所示。

但往往这个只是测试阶段的学习,一般情况下,我们的报表比上面的规范很多,要考虑样式还有布局设计等方面,下面给出一些我会员系统里面的报表例子供参考对比。

下面是一个会员消费清单的报表设计,包括总的消费情况和明细报表设计。

以及一个会员身份的打印报表设计。

设计报表完成,只是完成了一部分工作,我们还需要建立数据集,把字段绑定到报表上面去,如上面的[Name]这样标识的就是字段已经绑定的了,一开始这些可能是没有的,需要我们先创建数据集对象。

创建数据集对象,可以选择是DataSet类型的或者是实体类型的,网上介绍很多是基于DataSet方式的,由于我的Winform框架及整个系列的产品是基于实体类的,因此我的报表创建的数据集对象也是基于实体对象的。

从上面可以看出,创建的数据集对象,是引用我的实体类对象,里面包含了很多对象属性,非常容易理解。

创建了相应的数据集对象后,我们进一步就是把这些信息绑定到设计的报表上面了。

先在报表视图里面,把列表对象选中,绑定它的数据集对象,操作如下面两个图所示。

这样我们就可以随意指定列表里面的列的字段绑定操作了。

2、RDLC报表的动态加载

通过第一节介绍的报表设计和数据集对象的创建,然后进行绑定操作后,基本上设计部分就已经完成了,注意的就是调整好对应的格式,加载测试后进行微调整即可了。

本小节继续介绍,如何把数据进行动态加载进行绑定。

为了实现报表的预览,我们需要在UI项目里面添加下面两个程序集对象。

然后在一个预览报表的窗体里面,放置一个报表查看控件ReportViewer,设计报表预览界面窗体如下所示。

以后我们设计的报表,就准备通过这个界面进行展现的了。

通过ReportViewer我们可以获得LocalReport对象的引用,然后对它进行处理即可。

LocalReport report = this.rpViewer.LocalReport;

报表的数据绑定,主要就是增加报表对象里面的对应的ReportDataSource对象即可。

通过报表对象LocalReport,我们可以使用代码增加对应的数据源给它的报表对象,这样我们需要展现的报表数据源就可以和报表进行绑定了,具体的处理代码如下所示。

那么我们如何在界面窗体里面进行报表的绑定操作呢?

封装好通用的报表展现界面后,我们只需要调用他们的接口进行绑定一个新的报表和数据源了。具体代码如下所示。

        private void menuPrintReport_Click(objectsender, EventArgs e)
{
string headerID = this.winGridViewPager1.gridView1.GetFocusedRowCellDisplayText("ID");if (!string.IsNullOrEmpty(headerID))
{
MemberConsumptionInfo consumptionInfo
= BLLFactory<MemberConsumption>.Instance.FindByID(headerID);if (consumptionInfo != null)
{
//修改一些属性值//会员姓名 consumptionInfo.Data1 = BLLFactory<Member.BLL.Member>.Instance.GetNameByID(consumptionInfo.Member_ID);
consumptionInfo.Data2
=SecurityHelper.GetFullNameByID(consumptionInfo.Creator);

List
<MemberConsumptionInfo> list = new List<MemberConsumptionInfo>();
list.Add(consumptionInfo);

List
<ConsumptionDetailInfo> detailList = BLLFactory<ConsumptionDetail>.Instance.FindByBillNo(consumptionInfo.BillNo);foreach (ConsumptionDetailInfo info indetailList)
{
//修改ProductID为商品编码 info.Product_ID = BLLFactory<MemberProduct>.Instance.GetHandNoByID(info.Product_ID);
}

ReportViewerDialog dlg
= newReportViewerDialog();
dlg.DataSourceDict.Add(
"MemberConsumptionInfo", list);
dlg.DataSourceDict.Add(
"ConsumptionDetailInfo", detailList);
dlg.ReportName
= "WHC.Member.ConsumptionReport";

AppConfig config
= newAppConfig();string companyName = config.AppConfigGet("CertificatedCompany");
dlg.Parameters.Add(
"CompanyName", companyName);
dlg.ShowDialog();
}
}
}

3、RDLC报表的展现效果

在上面两个小节里面,我们已经介绍过RDLC报表如何设计、如何绑定到ReportViewer报表预览界面上了,还有也说明了如何在主窗体里面使用数据源进行动态的加载操作,但到底具体的报表展现效果如何呢,下面我给出几个案例作为参考。

1)消费清单报表

2)收费记录报表

3)会员卡片打印报表

会员管理系统经过一段时间的紧锣密鼓开发,软件终于完成并发布。在这期间,碰到了不少技术难点,并积累了不少开发心得和经验,本篇继续介绍这个开发过程中相关的技术要点,本章主要介绍会员管理里面,列表主界面的一些设计思路分享。

有时候,遵循一些固定的套路做事情,总是很容易,如果每个地方搞一些创新和改进,那么往往需要花费很多
时间
,但是创新是有积极意义的,虽然可能会遇到困难,但是很值得去做。在Winform的界面设计上,虽然我可以使用代码生成工具生成比较标准的界面了,但是我总是喜欢参考学习,并改进一些界面方面的元素,因此我经常会花费一些时间在研究一些界面细节上,不过做完后却可以感觉非常愉悦,同时也可以给后面的项目参考。

1、完成后的会员管理界面

这个里面的会员信息管理界面,分为了左右两部分的格局,左边显示会员的一些属性和状态分类,右边显示的是查询条件和查询列表内容。我们再来看看其会员信息列表界面的设计状态下的视图,基本上和运行时的界面保持一致,只是列表部分内容是在运行时刻进行动态加载的而已。

2、界面分析和设计

我们以这个界面效果反推如何实现这个效果,这样对我们可能更有帮助,也容易理解一些。

1)左右分拆及折叠界面的设计

列表界面的布局方面是左右两部分,里面还有一个箭头,双击箭头附件的部分可以把左边进行折叠,因此我们先来看看这个地方是如何实现的。

首先需要在VS的工具箱里面找到SplitContainerControl控件,然后放到整个窗体里面,并把它Dock属性设置为Fill,让它全部占满整个窗体。

接着设置整个控件的树形如下所示。

最后就可以看到左右的格局并可以折叠第一个面板的效果了。

然后在左边的面板里面增加一个TreeView控件,并设置它的Dock属性,并在设计时刻增加一些树的节点,方便效果显示处理。

通过后台代码的数据绑定,我们可以动态构造树状列表了,并且列表界面的数据也可以在右边进行展示,这样运行时候我们就可以得到前面介绍的主界面效果了。

有时候,我们可能需要利用面板分割控件,把主从表两个表的记录进行关联展示。

如下界面所示是一个消费信息的表,包括了消费总信息和消费明细信息两部分,但选择左边主表信息的时候,右边刷新显示明细表的记录信息。

2)下拉列表按钮效果实现

有时候,在列表界面里面放太多的按钮,会显得比较凌乱,有时候界面缩小的话,可能就被遮挡或者重叠了,这样的界面布局就很糟糕,DevExpress控件组提供了一个下拉列表按钮组的控件,非常方便用于节省屏幕控件,并对按钮进行分组。如我为了方便,把一些会员的相关操作,放到了一起,集合在一个下拉列表按钮里面,效果如下所示。

那要实现上面按钮的效果,应该如何操作才做到的呢?

DevExpress组件的工具栏里面有一个对应的按钮控件,如下所示。

不过拖动上去,并没有对应的属性用来设置里面的按钮集合的,而是需要配合这三个控件对象进行使用的。

ImageCollection是用来设置按钮图标的,其他两个需要配对使用在这个例子里面,增加后BarManger会增加工具栏、菜单栏、状态栏,把这些默认的都删除,不需要,然后制定PopupMenu的Manager属性为barManager1即可。

处理完这些步骤后,进一步设置弹出的按钮集合,并添加对应的事件处理即可,如下设计界面所示。

3)列表展示界面

列表展示内容,主要就是使用代码生成工具Database2sharp自动生成的界面和后台代码,里面使用了分页控件进行展示而已,再次不在赘述了,看看界面效果如下所示。

前面介绍了很多篇关于使用C#开发微信门户及应用的文章,基本上把当时微信能做的接口都封装差不多了,微信框架也积累了不少模块和用户,最近发现微信公众平台增加了不少内容,特别是在自定义菜单里面增加了扫一扫、发图片、发地理位置功能,这几个功能模块很重要,想想以前想在微信公众号里面增加一个扫描二维码的功能,都做不了,现在可以了,还可以拍照上传等功能,本文主要介绍基于我前面的框架系列文章,进一步介绍如何集成和使用这些新增功能。

1、微信几个功能的官方介绍

1). 扫码推送事件

用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。

2). 扫码推送事件,且弹出“消息接收中”提示框

用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。

3). 弹出系统拍照发图

用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。

4). 弹出拍照或者相册发图

用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。

5). 弹出微信相册发图器

用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。

6). 弹出地理位置选择器

用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
但请注意,以上新增能力,均仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。

2、微信新菜单功能的测试公众号

微信不仅增加了这些功能模块的支持,还考虑到我们开发人员的方便,增加了一个叫做“menutest"的公众号,方便我们测试。我们在公众号搜索“menutest",然后关注它即可进行测试几个新增功能了。

“menutest"的公众号名称是”自定义菜单拓展测试“,我关注它并进行了测试,二维码、图片、地理位置都很OK,本身能够响应这些事件,并且图片、地理位置自身还能出现一个对应的事件,如下所示。

图片发送可以分为拍照、拍照和相册、微信相册三类,感觉后面两个有点类似,但有这些功能都很不错的。



3、改进菜单对象和提交菜单

前面说了,微信提供这些功能,可以在菜单里面进行集成,也就是菜单的类型由原来CLICK/VIEW两种,变为现在8种类型,增加2个扫码操作、3种图片操作、1种地理位置操作。

因此把菜单的枚举类型扩展一下,如下所示。

    /// <summary>
    ///菜单按钮类型/// </summary>
    public enumButtonType
{
/// <summary> ///点击/// </summary> click,/// <summary> ///Url/// </summary> view,/// <summary> ///扫码推事件的事件推送/// </summary> scancode_push,/// <summary> ///扫码推事件且弹出“消息接收中”提示框的事件推送/// </summary> scancode_waitmsg,/// <summary> ///弹出系统拍照发图的事件推送/// </summary> pic_sysphoto,/// <summary> ///弹出拍照或者相册发图的事件推送/// </summary> pic_photo_or_album,/// <summary> ///弹出微信相册发图器的事件推送/// </summary> pic_weixin,/// <summary> ///弹出地理位置选择器的事件推送/// </summary> location_select
}

然后在Winform里面调用创建菜单操作代码如下所示:

        private void btnCreateMenu_Click(objectsender, EventArgs e)
{
MenuJson productInfo
= new MenuJson("新功能测试", newMenuJson[] { new MenuJson("扫码推事件", ButtonType.scancode_push, "scancode_push")
,
new MenuJson("系统拍照发图", ButtonType.pic_sysphoto, "pic_sysphoto")
,
new MenuJson("拍照相册发图", ButtonType.pic_photo_or_album, "pic_photo_or_album")
,
new MenuJson("微信相册发图", ButtonType.pic_weixin, "pic_weixin")
,
new MenuJson("地理位置选择", ButtonType.location_select, "location_select")
});

MenuJson frameworkInfo
= new MenuJson("框架产品", newMenuJson[] {new MenuJson("Win开发框架", ButtonType.click, "win"),new MenuJson("WCF开发框架", ButtonType.click, "wcf"),new MenuJson("混合式框架", ButtonType.click, "mix"),new MenuJson("Web开发框架", ButtonType.click, "web")
,
new MenuJson("代码生成工具", ButtonType.click, "database2sharp")
});

MenuJson relatedInfo
= new MenuJson("相关链接", newMenuJson[] {new MenuJson("公司介绍", ButtonType.click, "event_company"),new MenuJson("官方网站", ButtonType.view, "http://www.iqidi.com"),new MenuJson("联系我们", ButtonType.click, "event_contact"),new MenuJson("应答系统", ButtonType.click, "set-1"),new MenuJson("人工客服", ButtonType.click, "event_customservice") });

MenuListJson menuJson
= newMenuListJson();
menuJson.button.AddRange(
newMenuJson[] { productInfo, frameworkInfo, relatedInfo }); if (MessageUtil.ShowYesNoAndWarning("您确认要创建菜单吗") ==System.Windows.Forms.DialogResult.Yes)
{
IMenuApi menuBLL
= newMenuApi();
CommonResult result
=menuBLL.CreateMenu(token, menuJson);
Console.WriteLine(
"创建菜单:" + (result.Success ? "成功" : "失败:" +result.ErrorMessage));
}
}

当然,一般情况下我们都是在Web后台系统进行的,维护菜单都是在自己微信平台上进行菜单管理,然后一次性提交到微信服务器即可。

而在Web后台,只需要把数据库的数据变化为Json数据提交即可,操作和上面的类似。

        /// <summary>
        ///更新微信菜单/// </summary>
        /// <returns></returns>
        publicActionResult UpdateWeixinMenu()
{
string token = base.GetAccessToken();
MenuListJson menuJson
=GetWeixinMenu();

IMenuApi menuApi
= newMenuApi();
CommonResult result
=menuApi.CreateMenu(token, menuJson);returnToJsonContent(result);
}

4、微信扫一扫功能集成

前面讲了,有了最新的功能,我们就可以实现扫一扫功能,从而可以扫描条形码,二维码的功能。有了条形码、二维码的快速和识别,我们就能开发一些如条码查询、商品处理等功能了。

这里我们介绍如何在我的微信开发框架里面整合这个扫一扫的功能处理操作。

前面已经增加了一些新功能的测试菜单,我们要做的就是响应这些事件处理,然后对他们进行应答处理就可以了。

下面是根据事件进行的一些API跳转处理,我们同时定义了几个相关的实体类用来处理他们的信息,如RequestEventScancodePush、RequestEventScancodeWaitmsg、RequestEventPicSysphoto等等。

RequestEventScancodeWaitmsg实体类的代码如下所示,其他的类似处理。

    /// <summary>
    ///扫码推事件且弹出“消息接收中”提示框的事件推送/// </summary>
    [System.Xml.Serialization.XmlRoot(ElementName = "xml")]public classRequestEventScancodeWaitmsg : BaseEvent
{
publicRequestEventScancodeWaitmsg()
{
this.MsgType =RequestMsgType.Event.ToString().ToLower();this.Event =RequestEvent.scancode_waitmsg.ToString();this.ScanCodeInfo = newScanCodeInfo();
}
/// <summary> ///事件KEY值,由开发者在创建菜单时设定/// </summary> public string EventKey { get; set; }/// <summary> ///扫描信息/// </summary> public ScanCodeInfo ScanCodeInfo { get; set; }

}

而根据实体类强类型的处理接口流转操作如下所示。

caseRequestEvent.scancode_push:
{
//扫码推事件的事件推送 RequestEventScancodePush info = XmlConvertor.XmlToObject(postStr, typeof(RequestEventScancodePush)) asRequestEventScancodePush;if (info != null)
{
responseContent
=actionBLL.HandleEventScancodePush(info);
}
}break;caseRequestEvent.scancode_waitmsg:
{
//扫码推事件且弹出“消息接收中”提示框的事件推送 RequestEventScancodeWaitmsg info = XmlConvertor.XmlToObject(postStr, typeof(RequestEventScancodeWaitmsg)) asRequestEventScancodeWaitmsg;if (info != null)
{
responseContent
=actionBLL.HandleEventScancodeWaitmsg(info);
}
}break;caseRequestEvent.pic_sysphoto:
{
//弹出系统拍照发图的事件推送 RequestEventPicSysphoto info = XmlConvertor.XmlToObject(postStr, typeof(RequestEventPicSysphoto)) asRequestEventPicSysphoto;if (info != null)
{
responseContent
=actionBLL.HandleEventPicSysphoto(info);
}
}break;
..................

处理扫描结果并返回的最终代码如下所示。

        /// <summary>
        ///扫码推事件且弹出“消息接收中”提示框的事件推送的处理/// </summary>
        /// <param name="info">扫描信息</param>
        /// <returns></returns>
        public stringHandleEventScancodeWaitmsg(RequestEventScancodeWaitmsg info)
{
ResponseText response
= newResponseText(info);
response.Content
= string.Format("您的信息为:{0},可以结合后台进行数据查询。", info.ScanCodeInfo.ScanResult);returnresponse.ToXml();
}

最后我们测试扫描一个条形码,可以看到返回的结果界面操作如下所示。

5、新菜单功能测试发现的问题

前面介绍了一些新菜单功能模块的集成,我个人对这种扫一扫菜单功能非常赞赏,这也是微信逐步整合更多硬件资源和接口处理的趋向,不过在集成使用的时候,发现公众号偶尔出现闪退的情况,还有就是这些新功能虽然后台能够实现数据的处理和接收,但是有一些不能返回应答消息,很郁闷。也许随着版本研发的加快,这些功能很快得到完善和解决。

另外微信开放平台也投入使用了,好些认证也是300元一年,不过暂时没有其应用的场景,我只是用到了它来获取微信账号的unionid的功能,其他功能慢慢了解吧。

还有就是微信的企业号也已经出来了,而且我也已经申请认证通过,它的开发用户的API也有不少,有空继续研究并整合到微信开发框架里面吧。

如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章,系列随笔如下所示:

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口