代码生成工具之Winform查询列表界面生成

在上面一篇随笔《代码生成工具之界面快速生成》介绍了代码生成工具Database2Sharp的界面生成操作,其中介绍了Web界面(包括列表界面、内容显示、内容编辑界面的生成,另外还介绍了Winform界面的内容编辑界面的生成,本篇主要继续介绍Winform界面生成中的查询列表界面的操作展示等信息。

基于Winform的界面生成,配合我的Winform开发框架,提供了三种不同的界面生成,包括传统界面样式、DotNetBar界面样式和DevExpress界面样式的代码生成,这几种界面是目前Winform开发中非常常见的界面样式。

1、EnterpriseLibray架构代码及Web界面生成

基于EnterpriseLibray架构的代码生成,除了可以生成传统的业务层、数据访问层、数据访问接口层、实体层外,还同时生成了Web界面工程,Web界面工程已经添加了相关的引用程序集及必须的控件,生成后即可编译运行,一些表字段设计合理的话,甚至不用修改一行代码就能直接使用,如下所示。

生成的Web界面截图如下所示,包含列表查询界面、内容显示界面、内容编辑界面等。

 

内容编辑界面(新增和编辑)如下所示。

2、Winform界面生成

1)Winform数据编辑界面生成

在上面一篇随笔《代码生成工具之界面快速生成》同时也介绍了Winform界面的生成,上一篇主要介绍了Winform界面中的数据编辑界面。

Winform界面生成界面提供了很多参数进行控制,以期生成精细化的界面内容。

 

DevExpress界面样式的生成。

传统样式的界面生成。

2)Winform查询列表界面生成

很多情况下,查询列表界面很常见,如果能快速生成标准的界面,除了可以节省时间,提高开发效率外,也给我们统一界面风格及代码风格等方面,提供更好的支持。

复杂累赘的界面能够自动生成,绝对是开发过程的一大提升,让我们更加享受开发的乐趣。

 1)设置好相关的界面参数,如指定列表的查询字段、列表显示字段,选择界面样式,以及设定代码的主命名空间等参数。

2)生成界面代码到文件后,把文件直接复制到项目中,不用修改直接就可以看到列表界面效果,Yeah,正是我们需要的样式。

当然,上面的查询条件可以一行,也可以两行或者多行,界面生成的时候,会自动合理计算好布局,保证完美展现我们想要的列表界面效果。以上界面几乎不用任何修改就直接可以编译运行,里面的后台代码也同时生成了。

后台代码里面生成包括,分页控件展示及列表查询显示、Excel数据导入、数据导出等功能的后台界面代码。以下就是完整生成的界面后台代码,没有编辑过的列表界面后台代码如下所示。

public partial class FrmItemDetail : BaseDock
{
public FrmItemDetail2()
{
InitializeComponent();

InitDictItem();

this.winGridViewPager1.OnPageChanged += new EventHandler(winGridViewPager1_OnPageChanged);
this.winGridViewPager1.OnStartExport += new EventHandler(winGridViewPager1_OnStartExport);
this.winGridViewPager1.OnEditSelected += new EventHandler(winGridViewPager1_OnEditSelected);
this.winGridViewPager1.OnAddNew += new EventHandler(winGridViewPager1_OnAddNew);
this.winGridViewPager1.OnDeleteSelected += new EventHandler(winGridViewPager1_OnDeleteSelected);
this.winGridViewPager1.OnRefresh += new EventHandler(winGridViewPager1_OnRefresh);
this.winGridViewPager1.AppendedMenu = this.contextMenuStrip1;
this.winGridViewPager1.ShowLineNumber = true;

this.txtItemNo.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtItemName.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtManufacture.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtMapNo.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtSpecification.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtMaterial.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtItemBigType.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtItemType.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtUnit.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);
this.txtSource.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);

}

/// <summary>
/// 编写初始化窗体的实现,可以用于刷新
/// </summary>
public override void FormOnLoad()
{
BindData();
}

/// <summary>
/// 初始化字典列表内容
/// </summary>
private void InitDictItem()
{
//初始化代码
}

/// <summary>
/// 分页控件刷新操作
/// </summary>
private void winGridViewPager1_OnRefresh(object sender, EventArgs e)
{
BindData();
}

/// <summary>
/// 分页控件删除操作
/// </summary>
private void winGridViewPager1_OnDeleteSelected(object sender, EventArgs e)
{
if (MessageDxUtil.ShowYesNoAndTips(您确定删除选定的记录么?) == DialogResult.No)
{
return;
}

int[] rowSelected = this.winGridViewPager1.GridView1.GetSelectedRows();
foreach (int iRow in rowSelected)
{
string ID = this.winGridViewPager1.GridView1.GetRowCellDisplayText(iRow, ID);
BLLFactory
<ItemDetail>.Instance.Delete(ID);
}

BindData();
}

/// <summary>
/// 分页控件编辑项操作
/// </summary>
private void winGridViewPager1_OnEditSelected(object sender, EventArgs e)
{
string ID = this.winGridViewPager1.gridView1.GetFocusedRowCellDisplayText(ID);
List
<string> IDList = new List<string>();
for (int i = 0; i < this.winGridViewPager1.gridView1.RowCount; i++)
{
string strTemp = this.winGridViewPager1.GridView1.GetRowCellDisplayText(i, ID);
IDList.Add(strTemp);
}

if (!string.IsNullOrEmpty(ID))
{
FrmEditItemDetail dlg
= new FrmEditItemDetail();
dlg.ID
= ID;
dlg.IDList
= IDList;
if (DialogResult.OK == dlg.ShowDialog())
{
BindData();
}
}
}

/// <summary>
/// 分页控件新增操作
/// </summary>
private void winGridViewPager1_OnAddNew(object sender, EventArgs e)
{
btnAddNew_Click(
null, null);
}

/// <summary>
/// 分页控件全部导出操作前的操作
/// </summary>
private void winGridViewPager1_OnStartExport(object sender, EventArgs e)
{
string where = GetConditionSql();
this.winGridViewPager1.AllToExport = BLLFactory<ItemDetail>.Instance.FindToDataTable(where);
}

/// <summary>
/// 分页控件翻页的操作
/// </summary>
private void winGridViewPager1_OnPageChanged(object sender, EventArgs e)
{
BindData();
}

/// <summary>
/// 根据查询条件构造查询语句
/// </summary>
private string GetConditionSql()
{
SearchCondition condition
= new SearchCondition();
condition.AddCondition(
ItemNo, this.txtItemNo.Text, SqlOperator.Like);
condition.AddCondition(
ItemName, this.txtItemName.Text, SqlOperator.Like);
condition.AddCondition(
Manufacture, this.txtManufacture.Text, SqlOperator.Like);
condition.AddCondition(
MapNo, this.txtMapNo.Text, SqlOperator.Like);
condition.AddCondition(
Specification, this.txtSpecification.Text, SqlOperator.Like);
condition.AddCondition(
Material, this.txtMaterial.Text, SqlOperator.Like);
condition.AddCondition(
ItemBigType, this.txtItemBigType.Text, SqlOperator.Like);
condition.AddCondition(
ItemType, this.txtItemType.Text, SqlOperator.Like);
condition.AddCondition(
Unit, this.txtUnit.Text, SqlOperator.Like);
condition.AddCondition(
Source, this.txtSource.Text, SqlOperator.Like);

string where = condition.BuildConditionSql(DatabaseType.SqlServer).Replace(Where, “”);

return where;
}

/// <summary>
/// 绑定列表数据
/// </summary>
private void BindData()
{
this.winGridViewPager1.DisplayColumns = ItemNo,ItemName,Manufacture,MapNo,Specification,Material,ItemBigType,ItemType,Unit,Price,Source,StoragePos,UsagePos,Note,WareHouse,Dept;
#region 添加别名解析

this.winGridViewPager1.AddColumnAlias(ItemNo, 备件编号);
this.winGridViewPager1.AddColumnAlias(ItemName, 备件名称);
this.winGridViewPager1.AddColumnAlias(Manufacture, 供货商);
this.winGridViewPager1.AddColumnAlias(MapNo, 图号);
this.winGridViewPager1.AddColumnAlias(Specification, 规格型号);
this.winGridViewPager1.AddColumnAlias(Material, 材质);
this.winGridViewPager1.AddColumnAlias(ItemBigType, 备件属类);
this.winGridViewPager1.AddColumnAlias(ItemType, 备件类别);
this.winGridViewPager1.AddColumnAlias(Unit, 单位);
this.winGridViewPager1.AddColumnAlias(Price, 单价);
this.winGridViewPager1.AddColumnAlias(Source, 来源);
this.winGridViewPager1.AddColumnAlias(StoragePos, 库位);
this.winGridViewPager1.AddColumnAlias(UsagePos, 使用位置);
this.winGridViewPager1.AddColumnAlias(Note, 备注);
this.winGridViewPager1.AddColumnAlias(WareHouse, 所属库房);
this.winGridViewPager1.AddColumnAlias(Dept, 所属部门);

#endregion

string where = GetConditionSql();
List
<ItemDetailInfo> list = BLLFactory<ItemDetail>.Instance.FindWithPager(where, this.winGridViewPager1.PagerInfo);
this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<ItemDetailInfo>(list);
this.winGridViewPager1.PrintTitle = Portal.gc.gAppUnit + + 信息报表;
}

/// <summary>
/// 查询数据操作
/// </summary>
private void btnSearch_Click(object sender, EventArgs e)
{
BindData();
}

/// <summary>
/// 新增数据操作
/// </summary>
private void btnAddNew_Click(object sender, EventArgs e)
{
FrmEditItemDetail dlg
= new FrmEditItemDetail();
if (DialogResult.OK == dlg.ShowDialog())
{
BindData();
}
}

/// <summary>
/// 提供给控件回车执行查询的操作
/// </summary>
private void SearchControl_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
btnSearch_Click(
null, null);
}
}


private string moduleName = 备件信息;
/// <summary>
/// 导入Excel的操作
/// </summary>
private void btnImport_Click(object sender, EventArgs e)
{
string templateFile = string.Format({0}-模板.xls, moduleName);
FrmImportExcelData dlg
= new FrmImportExcelData();
dlg.SetTemplate(templateFile, System.IO.Path.Combine(Application.StartupPath, templateFile));
dlg.OnDataSave
+= new FrmImportExcelData.SaveDataHandler(ExcelData_OnDataSave);
dlg.OnRefreshData
+= new EventHandler(ExcelData_OnRefreshData);
dlg.ShowDialog();
}

void ExcelData_OnRefreshData(object sender, EventArgs e)
{
BindData();
}

bool ExcelData_OnDataSave(DataRow dr)
{
bool success = false;
ItemDetailInfo info
= new ItemDetailInfo();
info.ItemNo
= dr["备件编号"].ToString();
info.ItemName
= dr["备件名称"].ToString();
info.Manufacture
= dr["供货商"].ToString();
info.MapNo
= dr["图号"].ToString();
info.Specification
= dr["规格型号"].ToString();
info.Material
= dr["材质"].ToString();
info.ItemBigType
= dr["备件属类"].ToString();
info.ItemType
= dr["备件类别"].ToString();
info.Unit
= dr["单位"].ToString();
info.Price
= Convert.ToDecimal(dr["单价"].ToString());
info.Source
= dr["来源"].ToString();
info.StoragePos
= dr["库位"].ToString();
info.UsagePos
= dr["使用位置"].ToString();
info.Note
= dr["备注"].ToString();
info.WareHouse
= dr["所属库房"].ToString();
info.Dept
= dr["所属部门"].ToString();

success
= BLLFactory<ItemDetail>.Instance.Insert(info);
return success;
}

/// <summary>
/// 导出Excel的操作
/// </summary>
private void btnExport_Click(object sender, EventArgs e)
{
string file = FileDialogHelper.SaveExcel(string.Format({0}.xls, moduleName));
if (!string.IsNullOrEmpty(file))
{
List
<ItemDetailInfo> list = BLLFactory<ItemDetail>.Instance.GetAll();
DataTable dtNew
= DataTableHelper.CreateTable(序号|int);
DataRow dr;
int j = 1;
for (int i = 0; i < list.Count; i++)
{
dr
= dtNew.NewRow();
dr[
"序号"] = j++;
dr[
"备件编号"] = list[i].ItemNo;
dr[
"备件名称"] = list[i].ItemName;
dr[
"供货商"] = list[i].Manufacture;
dr[
"图号"] = list[i].MapNo;
dr[
"规格型号"] = list[i].Specification;
dr[
"材质"] = list[i].Material;
dr[
"备件属类"] = list[i].ItemBigType;
dr[
"备件类别"] = list[i].ItemType;
dr[
"单位"] = list[i].Unit;
dr[
"单价"] = list[i].Price;
dr[
"来源"] = list[i].Source;
dr[
"库位"] = list[i].StoragePos;
dr[
"使用位置"] = list[i].UsagePos;
dr[
"备注"] = list[i].Note;
dr[
"所属库房"] = list[i].WareHouse;
dr[
"所属部门"] = list[i].Dept;
dtNew.Rows.Add(dr);
}

try
{
string error = “”;
AsposeExcelTools.DataTableToExcel2(dtNew, file,
out error);
if (!string.IsNullOrEmpty(error))
{
MessageDxUtil.ShowError(
string.Format(导出Excel出现错误:{0}, error));
}
else
{
if (MessageDxUtil.ShowYesNoAndTips(导出成功,是否打开文件?) == System.Windows.Forms.DialogResult.Yes)
{
System.Diagnostics.Process.Start(file);
}
}
}
catch (Exception ex)
{
LogHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
}
}
}

传统界面效果生成操作,只需要选择传统界面样式,生成即可,得到的初始化界面如下所示。

如果是只是指定了几个查询条件(一行的情况),那么工具会自动计算好布局位置,得到界面效果如下所示。

代码生成工具Database2Sharp还提供了生成基于DotNetbar的样式的查询列表界面代码,和上图类似,在此不再赘述。

通过代码生成工具,不仅可以生成整体性的EnterpriseLibary框架结构代码,连我们繁琐的Web界面、Winform界面都能快速、高标准生成,开发界面其实是一件很轻松快速的事情,不要整天从数据库字段和界面属性设置中来回切换了。有了Database2Sharp代码生成工具,一切变得宁静很多,但更加快速。

本文链接

博客园_首页

扩展iQuery使其支持多种编程语言(三) – 兼编译器的语义分析简介

iQuery是一个开源的自动化测试框架项目,有兴趣的朋友可以在这里下载:https://github.com/vowei/iQuery/downloads

源码位置:https://github.com/vowei/iQuery

上一篇文章中,简单介绍了iQuery解释器的语义分析部分。

ANTLR使用的LL(*)的语法解析技术,它在从语法文件生成编译器时,会将每一个语法元素生成为一个函数,为了在语法元素之间传递数据,ANTLR支持函数调用和参数传递的概念,比如(摘自:https://github.com/vowei/iQuery/blob/master/java/iquery/iquery-core/src/main/java/cc/iqa/iquery/iQuery.g):

 

query [List<ITreeNode> candidates] returns [List<ITreeNode> survival]
: selectors[$ candidates] NEWLINE* EOF
;

selectors [List<ITreeNode> candidates] returns [List<ITreeNode> survival]

  

在上面的语法里,“query [List<ITreeNode> candidates]”就是在antlr里声明参数的方式,因为antlr默认是生成Java源码,所以参数声明的方式也是遵循Java语法的。“returns [List<ITreeNode> survival]”指明了生成的query函数的返回值,返回值的类型“List<ITreeNode>”和保存返回值的局部变量名“survival”。而第2行里,“selectors[$ candidates]”则是语法推演,在推演过程中,antlr使用类似函数调用的概念,允许上级语法元素传递参数给下级语法,而“selector”的函数形式可以参看第5行。

在语法文件里填充好函数声明和函数之间的调用关系后,可以使用下面这个命令生成解释器的源码:

java -cp antlr-3.3-complete.jar org.antlr.Tool iQuery.g

其中antlr-3.3-complete.jar可以从antlr的官网上下载。需要注意的是,当前写作时的最新版antlr-3.4,对生成JavaScript语言的解释器有Bug,因此建议使用3.3版本。

代码生成后,上例中的语法就会被翻译成类似下面的代码:

// 下面一行对应代码:
// query [List<ITreeNode> candidates]
public final List<ITreeNode> query(List<ITreeNode> candidates) throws RecognitionException {
… …
// 下面代码对应:selectors[$ candidates]
selectors1=selectors(candidates);
… …
// 对应代码:returns [List<ITreeNode> survival]
return survival;
}

public final iQueryParser.selectors_return selectors(List<ITreeNode> candidates) throws RecognitionException {
… …
}

                                

相应的,如果是要生成JavaScript版本的解释器 – 可用在iOS上,只需要在语法文件的顶部,加上一个选项指示(参考代码 – https://github.com/vowei/iQuery/blob/master/iOS/lib/iQuery.g):

options {
language=JavaScript;
}

对应的参数声明和参数传递使用JavaScript语法填充:

prog [candidates] returns [survival]
: p=selectors[$ candidates] NEWLINE* EOF
;

selectors [candidates] returns [survival]
 

对比Java版和JavaScript版的语法,可以看到语法之间传递数据的方式在antlr里是固定的 - 参看“selectors[$ candidates]”。

定义好参数列表和函数之间的调用关系之后,所需要做的就是填充自定义的过滤代码,根据不同的语法元素所代表的语义来过滤候选控件集合(candidates)。

例如,下面的代码中就是实现“:first-child”的语义,从候选控件集合中过滤出第一个子控件并返回:

| ‘:’ FIRST_CHILD
{
List<ITreeNode> nodes = new ArrayList<ITreeNode>();
for ( int i = 0; i < $ candidates.size(); ++i ) {
ITreeNode node = $ candidates.get(i);
if ( node.getChildren().size() > 0 ) {
nodes.add(node.getChildren().get(0));
}
}

$ survival = nodes;
}
 

 

上面的代码有几个地方需要留意,首先自定义的代码是用大括号“{”括起来的,参见第2-12行,antlr直接将里面的代码插入到生成的编译器代码的指定位置。另外,不需要在代码里显式使用“return”跳出函数,而是给预先定义的返回值变量赋值 - “ $ survival = nodes;”。例如上面的代码最终会生成:

switch ( input.LA(2) ) {
… …
case FIRST_CHILD:
{
alt9=7;
}
break;
… …
case 7 :
// cc/iqa/iquery/iQuery.g:674:7: ‘:’ FIRST_CHILD
{
match(input,37,FOLLOW_37_in_selector_expression884);

match(input,FIRST_CHILD,FOLLOW_FIRST_CHILD_in_selector_expression886);

List<ITreeNode> nodes = new ArrayList<ITreeNode>();
for ( int i = 0; i < candidates.size(); ++i ) {
ITreeNode node = candidates.get(i);
if ( node.getChildren().size() > 0 ) {
nodes.add(node.getChildren().get(0));
}
}

survival = nodes;


}
break;
case 8 :

  

  

由于antlr为每一个语法元素生成一个函数,因此可以使用一些小的编程技巧,比如为了实现“>>”和“>”这样的子孙节点操作符,这些操作符后面可能还会跟随有过滤条件,例如“>> :first”这个查询语句的意思就是,在当前候选控件集合里,取第一个控件的所有子孙节点,并返回子孙节点集合里的第一个元素。而iQuery.g这个语法是无状态的,需要在操作符“>>”和过滤条件“:first”之间传递子孙节点集合,因此在源码里是这样写的:

| DESCENDANT c=selector[descendants($ candidates, -1)]
{
$ survival = $ c.survival;
}

注意上例中“selector[descendants($ candidates, -1)]”,在调用“selector”这个语法元素之前,调用了函数“descendants”,目的就是获取当前“candidates”控件集合里的子孙控件之后再传递给“selector”语法。而变量“c”则是antlr的语法糖,用来保留“selector”语法元素过滤后返回的控件集合。

 

好了,本文对iQuery的语义分析的介绍就讲到这里,下一篇文章讲解iQuery的错误处理。

 

本文由知平软件 施懿民编写,请关注我们的微博

本文链接

博客园_首页

第二章(6)跨多表拆分一个实体

问题:

 你现在有两张以上的表,他们共享了同一个主键,然后你想一个实体映射到这两张表中。

解决过程:

  让我们把问题勾画成图2-6-1这张表:

图2-6-1

    接着,要建立一个代表这两张表的模型,按如下步骤:

  1. 在你的项目上右键,添加一个新的模型。选择“添加”->”添加新项″。选择ADO.NET 实体数据模型。
  2. 选择“从数据库中生成”,点击“下一步”。
  3. 使用向导选择一个已有数据库连接,或者新建一个。
  4. 在弹出的“选择数据库对象”框中,选择Product和ProductWebInfo表,保持最下面的两个复选框选中。

  生成的模型如图2-6-2所示:

图2-6-2

 

  既然我们已经把两张表导入了模型,现在我们要做的事就是把两个实体合成一个实体。步骤如下:

  1. 把ProductWebInfo实体中的ImageURL标量属性复制到Product实体中。你可以用复制/粘贴,但不用复制SKU标量属性。
  2. 在ProductWebInfo实体上右键,选择“删除”。如图2-6-3,它提示是否要删除数据库中表,选择“否”。这保证存储层的模型不会被删除。
  3. 点击Product实体,查看映射细节窗口。如果映射细节窗口没有显示的,在菜单栏中选择View-》other windows-》entity data Model Mapping Details.
  4. 在Product实体的映射细节窗口,点击“添加表或视图”,选择ProductWebInfo表。这就把ProductWebInfo表添加到Product实体的映射中了。
  5. 在映射细节窗口的ProductWebInfo表下,把ImageURL列和ImageURL属性映射起来。同时保证SKU属性是和ProductWebInfo表中的SKU列对应的。最后的映射如图2-6-4所示。

     图2-6-3

     图2-6-4

 

原因:

    在传统的系统中,每一行的额外信息要在另外一张表中找到的情况非常常见。通常发生在数据库演变和添加列一些关键的表但不愿意打破现有的代码的情况。解决的办法就是把额外的信息保存在一张新表中。

  通过合并两个或更多表到一个单一的实体,将一个单一的实体跨两个或更多表,我们可以把所有的部件作为一个逻辑实体。这个过程通常被称为垂直分割。

  垂直分割不利的一面是,我们每次要获得这个实体实例的时候都需要通过额外连接来保证实体类型完整。

SELECT
[Extent1].[SKU] AS [SKU],
[Extent2].[Description] AS [Description],
[Extent2].[Price] AS [Price],
[Extent1].[ImageURL] AS [ImageURL]
FROM [dbo].[ProductWebInfo] AS [Extent1]
INNER JOIN [dbo].[Product] AS [Extent2] ON [Extent1].[SKU]
= [Extent2].[SKU]

 

 

using (var context = new EFRecipesEntities())
{
var product = new Product { SKU = 147,
Description
= Expandable Hydration Pack,
Price
= 19.97M, ImageURL = /pack147.jpg };
context.Products.AddObject(product);
product
= new Product { SKU = 178,
Description
= Rugged Ranger Duffel Bag,
Price
= 39.97M, ImageURL = /pack178.jpg };
context.Products.AddObject(product);
product
= new Product { SKU = 186,
Description
= Range Field Pack,
Price
= 98.97M, ImageURL = /noimage.jp };
context.Products.AddObject(product);
product
= new Product { SKU = 202,
Description
= Small Deployment Back Pack,
Price
= 29.97M, ImageURL = /pack202.jpg };
context.Products.AddObject(product);
context.SaveChanges();
}

using (var context = new EFRecipesEntities())
{
foreach (var p in context.Products)
{
Console.WriteLine(
{0} {1} {2} {3}, p.SKU, p.Description,
p.Price.ToString(
C), p.ImageURL);
}
}
————————————-
147 Expandable Hydration Pack $ 19.97 /pack147.jpg
178 Rugged Ranger Duffel Bag $ 39.97 /pack178.jpg
186 Range Field Pack $ 98.97 /noimage.jpg
202 Small Deployment Back Pack $ 29.97 /pack202.jpg
————————————-

 

 

本文链接

博客园_首页

JavaScript基础

JavaScript基础

  JavaScript不难,但是对于初学者来讲比较的怪异。

  是一种客户端语言,在浏览器执行的。是一种脚本语言,直接就能解释执行的叫脚本代码。像dos命令还有SQL也是。

  为什么要引用JavaScript语言呢?HTML没有计算能力。为什么不用C#中的代码来完成呢?C#得通过网络,一通过网络就慢了。

  JS是非常灵活的动态语言,不像C#等静态语言那样严谨。数据类型不固定。建议不去使用VS2010里面关于JS的智能提示。

  JavaScript基本组成

基本语法ECMAScript

Dom(文档对象模型)

Bom(浏览器对象,有兼容性的问题)

 

语法概述

大小写敏感

弱类型语言(声明变量都用var)JS当中的字符串推荐使用单引号,当然用双引号也行。当C#和JS遇见的时候就不会尴尬了。每句话后面要跟分号。

C#和JS中的注释一样,但是推荐使用//单行注释。(因为在使用正则表达式的时候会有问题)。

 

 

一、新建HTML页

 

二、选择语言JS代码

 

三、alert 内置方法

 

四、声明变量,及同时声明多个变量

 

 

五、显示当前时间

  <script 两下tab键

整个一行<script type=”text/javascript”>就出来了。

 

六、标签<script>可以放的位置

 

七、不能再插入一个</script>

  下面我们看看如果我把JS文件写到另外一个页面上时,怎么加载。

 

八、Jscript

在Jscript中专门写JS的代码。然后进行添加。

 

九、外部引用

还可以连接入外部网站的JS,这样功能就非常的强大了。一般当连入大量的JS的时候都是把这段代码写在最后,让用户感觉不到慢。

 

下面我们看看JS当中的事件:

 

十、按钮的单击事件

 

十一、单击一个超链接显示当前时间

一个特殊的地方:单击一个超链接显示当前时间。

<a href=“javascript:js代码”>热点文字</a>

只有超链接的href中的JavaScript中才需要加“javascript:”,因为它不是事件,而是把”javascript:”看成像“http:”、“ftp:”、“thunder://”、“ed2k://”、“mailto:”一样的网络协议,交由js解析引擎处理。只有href中这是,这是一个特例。

 

十二、失去焦点事件

变量命名规则:以字母、下划线或$ 开头,中间可以包括字母、数字、下划线或$ 。(变量命名中多了一个$ )

 

jS中的数据类型

Boolean(布尔)、Number(数字)、String(字符串)、Undefined(未定义)、Null(空对象)、Object(对象类型),undefined与其他值计算得到的结果不是我们想要的,但与数据库中的null稍有区别,比如与数字计算或与字符串计算结果。

除了Object是引用类型以外,其他都是基本数据类型

Undefined类型、Null类型都是只有一个值的数据类型,分别为undefined与null.

 

十三、用typeof看数据类型

接下来我们看下JS中的null和undefined的区别。

 

十四、undefined

null表示的是一个已知的值,是一个空对象,与undefined不一样,undefined表示的是一个未定义的值。

 

十五、null

 

十六、==和===

  怎么样转成布尔类型呢?

alert(Boolean(x));

 

下面我们再来判断变量是否可用?

 

十七、判断变量是否可用

&& ||表示短路,& | 表示 每个条件都要判断一下,不存在短路问题。

 

也可以直接把判断的方法写在括号里面。

 

十八、判断变量是否可用也可以写成这样

  接下来我们看下JS中变量的作用域

 

十九、JS中变量的作用域

 

二十、声明变量的时候一般建议都要使用var关键字

  下面我们看看JS中的转译符问题

跟C#中一样的,

  JavaScript中字符串同样需要转义符   ’  \  ’,与C#一样。

想在页面输出:c:\windows\microsoft\vs.txt,这句话,需要这样写:alert(‘c:\windows\microsoft\vs.txt’);不能用@,@只在C#中有效。

常见转义符:\t、\n、\’、\”、\

在用正则表达式的时候也会遇到转义符问题。

 

注:switch判断时,是“全等于”,===

 

接下来我们看下类型转换:

第一种是字符串转到整数

 

二十一、字符串到整数

 

二十二、转换小数

把任意的类型转换成对应的类型。

 

二十三、任意类型转换成对应的类型

 

二十四、调eval

下面重点讲解通过浏览器来调试JS代码。

 

二十五、VS中调试第一步

 

二十六、VS中调试第二步

  用VS调试,需要保证默认的浏览器是IE6以上版本。

 

二十七、通过IE浏览器进行调试

目前火狐浏览器是JS调试最好的工具。

 

二十八、Firebug

如果没有的话,可以上网下载一个,具体步骤如下:

 

二十九、Firebug的安装

下面我们看看怎么在火狐中进行调试呢?

 

三十、在火狐中的调试过程

下面讲下函数声明:

 

三十一、js中方法的写法

Js中没有方法的重载,所以下面一段代码的结果是这样子的。

 

三十二、js中没有方法的重载

如果我想实现多个数求和的功能,又没有方法的重载怎么办呢?

arguments里面是我们传的参数,既然arguments里面都是我们

传的参数,所以我们写了个循环。

 

三十三、arguments

接下来我们看下匿名函数:

没有方法名就是匿名函数,没有方法名,我们怎么调用呢?

 

三十四、匿名函数的第一种写法

 

三十五、匿名函数的第二种写法

接下来我们看下闭包:

 

三十六、闭包

什么是“闭包”?

在一个函数内部又定义了一个函数,内部函数函数能访问外部函数作用域范围内的变量,这时这个内部函数就叫做闭包。无论这个内部函数在哪里被调用都能访问的到外部函数作用域中的那些变量。

“闭包”是怎么实现的?

通过作用域链

“闭包”能做什么?

Js中的面向对象都是用“闭包”来模拟的。

 

三十七、用函数来模拟

接下来我们看看JS面向对象:

现在JS中的面向对象实际上还是用函数来模拟,为了便于大家的理解,我们还是用类,和对象来阐述。

 

三十八、面向对象的object写法

 

三十九、自己写的面向对象

 

四十、通过构造函数写属性

接下来我们看看JS中的字符串:

 

四十一、字符串的长度和根据索引找到指定的字符

 

四十二、indexof

 

四十三、split

作者近期文章列表:

C#基础教程(完全免费,献给代码爱好者的最好礼物。注:本作者分享自己精心整理的C#基础教程,无任何商业目的。
希望与更多的代码爱好者交流心得,也请高手多多指点!!!)
SQL数据库 ADO.net 数据库的应用图解一
数据库的应用详解二
面向过程,面向对象中高级 面向过程,面向对象的深入理解一
面向过程,面向对象的深入理解二
面向对象的深入理解三
winform基础 Winform基础
winform中常用的控件
面向过程 三种循环的比较
C#中的方法(上)
我们常见的数组
面向对象 思想的转变
C#中超级好用的类
C#中析构函数和命名空间的妙用
C#中超级好用的字符串
C#中如何快速处理字符串
值类型和引用类型及其它
ArrayList和HashTable妙用一
ArrayList和HashTable妙用二
文件管理File类
多态
C#中其它一些问题的小节
GDI+ 这些年我收集的GDI+代码
这些年我收集的GDI+代码2
HTML概述 你不能忽视的HTML语言
你不能忽视的HTML语言2精编篇
你不能忽视的HTML语言3

本文链接

博客园_首页

Android之旅 -- Recovery相关原理分析

原创文章,欢迎转载,转载请注明出处http://www.cnblogs.com/becklc/archive/2012/09/24/2676600.html

本文依据android2.3源码只分析Recovery相关原理,不针对代码走读,现在Android版本已经4.x.x但是recovery的基本原理不变。

一、Recovery是如何构成的

  说recovery的构成并不贴切,应该说recovery.img的构成,它是由boot_img_hdr + zImage + recovery-ramdisk构成。boot_img_hd是个结构体它描述了很多重要的信息。

1 struct boot_img_hdr
2 {
3 unsigned char magic[BOOT_MAGIC_SIZE];
4 unsigned kernel_size; /* size in bytes */
5 unsigned kernel_addr; /* physical load addr */
6 unsigned ramdisk_size; /* size in bytes */
7 unsigned ramdisk_addr; /* physical load addr */
8 unsigned second_size; /* size in bytes */
9 unsigned second_addr; /* physical load addr */
10 unsigned tags_addr; /* physical addr for kernel tags */
11 unsigned page_size; /* flash page size we assume */
12 unsigned unused[2]; /* future expansion: should be 0 */
13 unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */
14 unsigned char cmdline[BOOT_ARGS_SIZE];
15 unsigned id[8]; /* timestamp / checksum / sha1 / etc */
16 };

其中kernel_size表示zImage的实际大小;kernel_addr表示zImage载入内存的物理地址,这个地址也是bootloader跳转到内核的地址;ramdisk_size表示ramdisk(此处就是指recovery-ramdisk)的实际大小;ramdisk_addr是ramdisk加载到内存的物理地址,之后kernel会解压并把它挂在成根文件系统,我们的中枢神经-init.rc就隐藏于内;second_size,second_addr做扩展用一般都不会使用(在我的一个项目中把它扩展成另外一种功能有机会介绍给大家);tags_addr是传参数用的物理内存地址,它作用是把bootloader中的参数传递给kernel;page_size是flash(eg. nandflash)的一个页大小,一般为2K,这通常情况取决于你使用Flash芯片的页大小;cmdline就是command line它可以由bootloader传递也可以在.config(kernel)中配置。

zImage是我们熟悉的内核镜像,由kernel编译生成。recovery-ramdisk是由mkbootfs、gzip打包生成的命令如下:

mkbootfs ramdiskdir | gzip > recovery-ramdisk.gz

 我们可以通过解压recovery.img来获取真正的recovery可执行文件,操作如下:

1 ./split_bootimg.pl recovery.img
2 mkdir out
3 cd out
4 gunzip -c ../recovery.img-ramdisk.gz | cpio -i

/sbin目录下的recovery就是可执行文件了,还原可参照如下操作:

1 find . | cpio -o -H newc | gzip > ../recovery.img-ramdisk.gz
2 mkbootimg –kernel recovery.img-kernel –ramdisk recovery.img-ramdisk.gz -o new-recovery.img

注: 上述使用到的工具下载(android-img-tools

        recovery.img与boot.img在结构上是一样的。

        现在很多平台的打包方式是不一样的,它们的做法是编译时把kernel镜像和根文件系统打包在一起合称为zImage。

        本文提到的bootloader指blob,下同。

二、Recovery是如何启动的

       请看如下流程图:

这个流程图只是大概描述bootloader中选择启动部分的流程。当设备上电或Reboot进入bootloader时会检测此时是否有特殊键被按下也就是流程图中的KeyPress,例如某手机开机时同时按下照相键+音量键就会进入recovery。有人会问如何检测按键被按下?很简单就是读键盘控制寄存器的值,如果你是GPIO按键就读GPIO的寄存器。如果没有键被按下,bootloader会读取misc分区中bootloader_message结构信息。

1 struct bootloader_message {
2 char command[32];
3 char status[32];
4 char recovery[1024];
5 };

(这里顺便提一下:如果你的存储芯片是NandFlash bootloader_message是存放在Misc分区Block 0的第二个页上,如果是MMC那么就是这个分区的开始位置。在Android兴起之初NandFlash很流行,但由于MMC总线接口简单统一、大容量、易于更换大有替换NandFlash之势)再看bootloader_message中的command如果其内容是”boot-recovery”那么bootloader会进入recovery;bootloader_message中的recovery是放解析的参数它是告诉Recovery系统需执行什么样的动作,如:”recovery\n–update_package=CACHE:update.zip”,这样Recovery系统就会明白它将要更新AndroidOS系统,升级包是/cache/update.zip。同样还有”recovery\n–wipe_date” 清除用户数据后重启,通常用它做恢复出厂设置功能;再有”recovery\n–wipe_cache”清除cachec分区内容后重启,cache分区一般放临时文件用;当然你也可以自定义实现你想要的功能。那么这个bootloader_message是谁写入的呢?当你需要OTA升级的时候,系统会到指定地址下载升级包放入Cache分区,然后把bootloader_message信息写入Misc分区然后重启,还有当你需要恢复出厂设置时它也会进行同样的操作。

bootloader加载recovery.img时先读取img头信息(上文提到的boot_img_hdr)把zImage和recovery-ramdisk加载至制定内存地址,设置参数等操作后,跳转至kernel在内存中的地址(就是kernel_addr),至此kernel被加载启动。挂载根文件后执行init,init会解析init.rc,大家注意init.rc中有如下一条:

service recovery /sbin/recovery

执行到这条时我们的recovery就算启动了。

题外话:我们可以发现recovery可执行文件是静态编译的,之所以这样是因为recovery模式中没有共享库还有缺动态链接库加载器(/system/bin/linker)。

三、Recovery是如何工作的

    上图主要描述recovery执行的主要功能的主体框架

    1、UpdateXXX.zip  

    我们知道recovery升级时需要一个特殊的UpdateXXX.zip包,解压出来可以发现META-INF是一定会有的,在看其里面的内容:

|– CERT.RSA
|– CERT.SF
|– com
| `– google
| `– android
| |– update-binary
| |– updater-script
| `– update-script
`
– MANIFEST.MF

    其中 CERT.RSA、CERT.SF、MANIFEST.MF是做包时产生的签名文件如下脚本可以生成:

java -jar signapk.jar xxx.pem xxx.pk8 xxx-unsigned.zip updatexxx-signed.zip

    其中密钥文件xxx.pem xxx.pk8 在 build/target/product/security/下就可以找到,signapk.jar是由build/tools/signapk/编译生成的。当然各个厂家为了不混淆自家的升级包文件可能会定制自己的密钥文件。接下来我们来看看update-binary、updater-script、update-script这三个文件。其中update-script是Amend脚本,在Android1.5之前使用的现在已经不再支持,它是由Recovery直接解析执行的。现在使用的updater-script是Edify脚本,是由update-binary解析执行的。后者的好处在于,它完全独立于Recovery系统,在recovery中fork出一个进程来执行update-binary的,而且用户可以扩展供执行的解析命令,这样灵活性很高,可以做的事情越多。

    2、升级时recovery是如何更新系统的?

    细心的朋友可能早就发现了,boot.img和system.img虽然都叫xxx.img其实他们有本质的不同。boot.img不带有文件系统的信息,通俗的话说就是”裸数据”,直接写入分区就好;而system.img是带有文件信息的,所带有的文件信息最终取决于你的存储芯片和你所使用的文件系统,比如使用的存储芯片是NandFlash,那么文件系统可能是Yaffs(2)、Jffs(2)等等,很大一部分是Yaffs2,那system.img是带有Yaffs2的文件信息(包括文件类型、文件chunk数、chunkID、Yaffs2的OOB信息等等),如果使用的存储芯片是MMC,那么文件系统可能是ext2、ext3、ext4、fat32等等,很多使用的是ext系列。那么update.zip需要升级boot.img和system.img怎么实现呢?我们先看如下脚本(以NandFlash-Yaffs2为例)

1 format(“MTD”, “system”);
2
mount(MTD, system, /system);
3 package_extract_dir(system, /system);

4 package_extract_file(boot.img, /tmp/boot.img),
5 write_raw_image(/tmp/boot.img, boot);

我们可以发现对更新boot.img、system.img是不同的脚本。更新system时先格式化system分区,大家看到MTD并不是格式化成MTD文件系统,MTD是Nandflash驱动的管理层,Yaffs2文件系统是构建MTD层之上,MTD起到呈上启下的作用,另像分区信息、设备节点等都有MTD层来管理。如果查看代码就会发现format类型是MTD时就会把这个分区格式化成Yaffs2的。我们可以通过cat /proc/mtd来查看分区信息。格式化完成之后我们会把system分区挂载到某一目录下,然后把update.zip中的system文件下所有文件写到刚挂载的目录下。而对于boot.img的更新是直接把update.zip中的boot.img写到设备中也就是nandflash芯片中。那么两者同样是写有什么不同呢?直接写设备节点是直接由mtd接口来完成的,nandflash只在涉及到的页的OOB区添加ECC校验信息。但是如果是带有文件系统的写首先需要通过Yaffs文件系统的文件接口加上OOB的一些信息,然后再通过MTD接口来完成,nandflash在涉及到的页的OOB区添加ECC和文件系统的一些信息如:chunkid、chunksize等信息。

    3、几条很好用的脚本命令

    getprop   获取系统属性,eg:getprop(“ro.flag”)==”ok”                                            一般和其他脚本联合使用

    assert      脚本中的断言,eg:assert(getprop(“ro.flag”) == “ok”);                             如果失败则终止脚本继续执行

    ifelse        脚本中的判断,eg:ifelse(getprop(“ro.flag”) == “ok”, ui_print(“ok”), ui_print(“no,ok”));

    run_program    执行第三方可执行文件,eg:run_program(“test_binary”);                    可执行文件必须是静态编译的

    脚本命令这里就不一一列举了,网上资料很多。

四、Recovery的适配及修改

    遇到一个新项目Recovery需要修改哪些呢?

    1、进入按键,在bootloader中需要检测哪些键被按下时进入Recovery

    2、分区信息,如果你是nandflash存储芯片可以查看cat /proc/mtd, 若是eMMC看recovery.fstab文件中的内容是否正确

    3、recovery中的按键,上下选择及执行按键是否映射正确,可以参看kernel中的头文件<linux/input.h>

    4、是否支持你的文件系统,笔者分析使用的代码只支持Yaffs2及ext3的

    5、签名验证,签名验证的密钥证书是否一致,可以写个小应用验证下

    6、显示,framebuffer设备节点是否一致读写是否正常,tty设备配置成图形类型是否正常,RGB是否适配

    7、misc分区,misc分区是recovery默认的传递参数的区域,可以换掉

     上述只例举笔者在工作中遇到的一些情况,不同的平台、环境、需求时需注意的地方可能不尽相同

本文主要分析了Recovery涉及到的相关原理,从这么一个小小的系统中可以发现它涵盖了大量的知识点,此篇只揭开了它一片小小的面纱。

本文链接

博客园_首页

node.js入门 – 6.I/O模式

  我们按照执行的方式,把I/O分为串行I/O和并行I/O。串行I/O是完成当前I/O之后,才会去执行下一个I/O。并行I/O要复杂些,但是更容易理解,就是几个I/O同时运行。有一点需要提醒的是,在串行I/O中一般是按照请求队列的顺序来执行请求的,并以这个顺序返回结果。并行I/O返回的结果没有任何的顺序可言。串行I/O和并行I/O可以组合起来工作,例如:有多组并行I/O,每组中包含不同数目的串行I/O。

  在Node中我们假设所有的I/O无限延迟的,他们可能执行0到无限的时间,我们不知道也不能假设他们具体的执行时长。与其等待他们执行完毕,我们不如使用基于事件模式,当I/O执行完毕时使用回调函数来响应结果。因此,使用并行I/O是更理想的解决方案,我们可以执行多个请求,按照他们执行完毕的顺序返回结果。

  所以我们现在有两种方式去处理I/O:顺序串行处理和非顺序的并行处理。顺序的并行处理也是一种有用的模式,当我们允许I/O并行运行,并使用原有顺序返回结果时可以使用这种方式。非顺序的串行没有益处,我们就不讨论了。

  note:

  感觉这部分内容似曾相识,以前学习操作系统的运行方式,还有学习多线程编程的时候,也有类似的概念或者实现方式。大家对那些知识比较熟悉的话,理解这块知识肯定会容易的多。

本文链接

博客园_首页

MS CRM 2011中创建服务

在CRM中,我们可以创建服务,比如我们需要去客户那里维护我们卖的机器。提供这个服务就需要我们使用一些资源,比如技术人员和技术人员要开的车。在这篇文章中我会演示如何创建一个服务,并定义Selection Rule。

要创建一个新的服务,需要打开Settings –> Business Managerment –> Services,点击New。Initial Status Reason 将决定你创建Service Activities时这些Service Activity的状态。Default Duration通常就是这项服务所需要的时间。Start Activities Every可以用来定义提供该服务的准备时间,我们选择了15分钟,也就是说在每个服务活动之间,至少需要间隔15分钟来做一些准备。

image

接下来我们需要定义我们提供该服务所使用的资源了。

image

这里的提示已经很清楚,要注意先创建Selection Rule,然后再选择资源或者资源组。假如我们该服务需要我们派去一个技术人员,技术人员需要驾驶一辆公司的专用车辆。我们最后对Required Resources的定义应该如下:

image

到这你的服务就定义好了。以后的文章中我会介绍如何使用这个服务以及服务中定义的Selection Rule来Schedule服务活动(Service Activities)。

本文链接

博客园_首页