2010年11月19日
#
在如Excel一样的表格中,某些单元格是靠计算产生值的,不用人工填写。本来使用的是Farpoint Spread 这个控件,生成表头,设定了公式,锁定列等等看来都很好使,界面也很好。
静态计算是没什么问题的,即页面第一次加载生成时计算那些单元格的值;但是动态计算上体验不太完美,利用它自己内置的功能反应不够灵敏,公式涉及的行列越多,计算层次越深其反应越不灵敏。鉴于我们遇到的公式基本只有二种,合计与百分比,遂自己写脚本来动态更新相关单元格的值。
设定事件,取得行列索引不细表,直接进入正题

计算单元格
function calcValue(sheet, currentRow, currentCol, level) {
var sumFormulaExp = /R(\d+)C(\d+)\+/g; //相加的表达式
var percentFormulaExp = /IF\(R(\d+)C(\d+)=0,\S{3},R(\d+)C(\d+)\*100\/R(\d+)C(\d+)\)/g; //计算百分比的表达式
var formulaPart = "R" + currentRow + "C" + currentCol; //exp:R4C3
$("td[fpformula*='" + formulaPart + "']").each(function() { //exp:找公式中包含有R4C3的单元格
//。。。计算相关单元格的值
});
}
四个参数: sheet--当前sheet,从页面上能取到的那个控件对象
currentRow--当前行号,从1开始计数
currentCol--当前列号,从1开始计数
level--层次,递归调用时控制层次
特别说明一下那个level参数,这是以防万一公式设定错误导致无休止调用发生错误而设的。为什么会发会递归调用,因为一个值发生改变后会涉及到多个多行多列单元格的值发生联动。比如R7C7的单元格(即7行7列)数字变动了,那它的行小计R7C4、行合计R7C1、列小计R4C7、列合计R1C7,以及最终的总计R1C1都要发生变动,上述单元格有参与百分比计算的也要重新计算相关的百分比。输入值的单元格=》行小计=》行合计=》总计的这个过程就是递归调用过程,递归的终点是在当前表格中找不到有任何一个单元格公式表达式中包含有当前单元格。这个传递过程只有那个累加的单元格才会发生传递,计算百分比的单元格是不会往下传递的,比如一个行合计相关的百分比单元格R9C1(=R8C1/R7C1),这个R9C1是不用往下传递到合计百分比单元格R3C1(=R2C1/R1C1)的。
当前单元格有没有被计算是字符串包含来匹配的。还是以R7C7来举列,跟它有关的公式会找到二个,
R7C4上的累加公式:R7C7+R7C8+R7C9+R7C10+
R9C7上的百分比公式:IF(R7C7=0,'-',R8C7/R7C7)
接下来就是计算R7C4,R9C7时里的新值。
计算之前先作一个预处理,R7C7的话还一般不会有干扰。但如果是找R7C1,你找到了一个公式R7C10 + R7C11,jquery选择里会把这个公式与列为有效公式,计算是不会出错,但效率会差很多。所以用一个正则把干扰公式排除掉

排除匹配不准确的单元格
var cellExp = new RegExp(formulaPart + "\\D{1}", "");
var formula = $(this).attr("fpformula");
if (!cellExp.test(formula))//jquery的选择器匹配出来的是弱的,用正则强匹配一下。
return;
接下来要用正则表达式去匹配公式了

公式匹配
var mat = sumFormulaExp.exec(formula);
var percentMat = percentFormulaExp.exec(formula);
var colIndex = parseInt($(this).parent().children().index($(this))) + 1;
var rowIndex = parseInt($(this).parent().attr("fpkey")) + 1;
if (mat != null) {
//...计算合计
} else if (percentMat != null) {
//。。。计算百分比
}

累加的处理
var result = 0;
while (mat != null) {
var row = parseInt(mat[1]) - 1;
var col = parseInt(mat[2]) - 1;
var v = parseFloat(sheet.GetValue(row, col));
result += v;
mat = sumFormulaExp.exec(formula);
}
$(this).children("nobr").text(result);
if (level < 4) {//5层已经够用了
var dl = parseInt(level) + 1;
calcValue(sheet, rowIndex, colIndex, dl);
}

百分比的处理
var mRow, mCol, cRow, cCol;
mRow = parseInt(percentMat[1]) - 1;
mCol = parseInt(percentMat[2]) - 1;
cRow = parseInt(percentMat[3]) - 1;
cCol = parseInt(percentMat[4]) - 1;
percentFormulaExp.lastIndex = 0;
var denominator = parseFloat(sheet.GetValue(mRow, mCol));
if (denominator == 0)
$(this).children("nobr").text("-");
else {
var numerator = parseFloat(sheet.GetValue(cRow, cCol));
var percent = numerator * 100 / denominator;
percent = percent.toFixed(1);
$(this).children("nobr").text(percent);
}
上面sheet.GetValue()是控件提供的方法,用来取单元格的值。fpformula是设定的公式,根据数据库里的定义,在第一次加载时在服务端就都设定好了,公式格式是控件支持的,所以静态计算也支持的,首次显示时相关单元格会自动计算。那个$(this).children("nobr").text里的nobr纯粹是查看控件生成的html代码得到的。
经过上述一些处理,现在页面上相应格子里输入一个值一回车,跟它有关的七、八个单元格的值立马就发生了变化,跟在本地编辑Excel文件一样的速度。不过万恶的IE愣是要慢半拍,如果表格超过10行或10列的话更明显,至少要多1秒。以后可以拿这个功能去说服那些人远离IE。
2010年11月9日
#
继续表格的有关操作,这篇说说表格行与列的删除。
所有这些操作恼人的地方其实都是在融合的单元格上
a.行的移除
首先计算整个表格的总列数。

总列数
var $row = $(sender).parent().parent();
var $nextRow = $row.next();
var $firstRow = $row.parent().children("tr:first");
var colCount = $firstRow.children("td").size();
$firstRow.children("td[colspan]").each(function() {
colCount = colCount + ($(this).attr("colspan") - 1);
});//表格总列数
然后是当前这一行被行融合所占去的列数,

融合数
var count = colCount - $row.children().size();
$row.children("td[colspan]").each(function() {
count = count - ($(this).attr("colspan") - 1);
});//被行融合被占用的单元格数目
如果当前行没有被行融合占掉的就好说了,如果有的话要把父节点的rowspan值减1,先贴代码吧。

父层节点的rowspan值减1
if (count > 0) {//当前行有位置被行合并单元格占用的,要往上溯把相应的rowspan值减1
var $preRow = $row.prev();
var spanClsName = "";
while ($preRow) {
var spanColumn = $preRow.children("td[rowspan]").get();
for (var i = 0; i < spanColumn.length; i++) {
var $this = $(spanColumn[i]);
if (spanClsName.length > 0 && spanClsName <= $this.attr("class"))
break;
var spanCount = $this.attr("rowspan");
spanCount = spanCount - 1;
if (spanCount == 1)
$this.removeAttr("rowspan");
else
$this.attr("rowspan", spanCount);
var colSpan = $this.attr("colspan");
if (colSpan)
count = count - colSpan;
else
count = count - 1;
if (count == 0)
break;
}
if (count == 0)
break;
if (spanColumn.length > 0)
spanClsName = $(spanColumn[0]).attr("class");
$preRow = $preRow.prev();
}
}
最终把count归零就表示把所有父节点的rowspan值都减1了。
里面那个循环,
var spanColumn = $preRow.children("td[rowspan]").get();
for (var i = 0; i < spanColumn.length; i++) {
}
这里为什么不用jquery的each,而用js原始形态的for,是为了避免把兄弟节点的rowspan值也减1。
接下来,考虑当前行有行融合单元格的,把rowspan值减1,加入到下一行中,这里也用了循环,而且是递减的,是为了把节点以正确的顺序加入到下行的行首

当前行rowspan的处理
var spanRows = $row.children("td[rowspan]").get();//当前行有行合并单元格的,要把rowspan值减1,并加入到下一行
for(var i = spanRows.length - 1;i >= 0;i--){
var $this = $(spanRows[i]);
var spanCount = $this.attr("rowspan");
spanCount = spanCount - 1;
if (spanCount == 1)
$this.removeAttr("rowspan");
else
$this.attr("rowspan", spanCount);
$this.insertBefore($nextRow.children("td:first"));
};
最后把当前行移除
$row.remove(); //当前行移除
b.列的移除
列的移除思路也跟行移除一样,先找父代的colspan的节点;当前列如果是融合的,colspan值减1,加入到下一列(注:这里加入到下一列只是把class改为下一列的class而已)。

取得相关元素,并得到操作符所在行的行索引
var $column = $(sender).parent();
var className = $column.attr("class");
var nextClass = $column.next().attr("class");
var $row = $column.parent();
var $table = $row.parent();
var opIndex = $table.children("tr").index($row);
var ri = 0;
var $chCol, $pchCol;
取操作符所在行的行索引,是为了寻当前列的单元格时有一个终止条件。

从第一行开始找当前列的单元格
while (ri < opIndex) {
var $cRow = $table.children("tr").eq(ri);
$chCol = $cRow.children("td." + className);
if ($chCol.size() == 0) {
//......当前行当前列,没有相应单元格,往左找它的融合单元格
} else {
//......当前行当前列,有单元格存在
}
}
当前列没有单元格时,用操作符列的class来回溯找融合单元格

当前列没单元格时的处理
var $preColumn = $column.prev();
var preClassName = $preColumn.attr("class");
$pchCol = $cRow.children("td." + preClassName);
while ($pchCol.size() == 0) {//这个位置上没有单元格位置,找它的融合单元格
$preColumn = $preColumn.prev();
preClassName = $preColumn.attr("class");
$pchCol = $cRow.children("td." + preClassName);
}
var spanCount = $pchCol.attr("colspan"); //相应的colspan值减1
spanCount--;
if (spanCount == 1)
$pchCol.removeAttr("colspan");
else
$pchCol.attr("colspan", spanCount);
var rSp = $pchCol.attr("rowspan"); //跳过行融合的单元格
if (rSp && rSp > 1)
ri += rSp;
else
ri++;

当前列存在单元格时的处理
var $this = $chCol.eq(0);
var spanCount = $this.attr("colspan"); //当前单元格是融合的
if (spanCount && spanCount > 1) {
spanCount--;
if (spanCount > 1)
$this.attr("colspan", spanCount);
else
$this.removeAttr("colspan");
$this.attr("class", nextClass); //colspan值减1后加到后面一列去
}
var rSp = $this.attr("rowspan");
if (rSp && rSp > 1)
ri += rSp;
else
ri++;
最后把这一列移除
$("td." + className).each(function() {
$(this).remove();
});
很久以来都在做一些没有营养的东西,即使工作之中有时会有一些比较有技巧性的东西也是基于一些不是非常用的第三方组件的,所以也就没有记下来。前一阵做了个编辑表格列头行头定义的功能,写了一大堆javascript,回头来看看还算有点用途,保不准以后会用到,这二天有空把它整理一下。
先说明一下,下面的代码都是基于Jquery 1.3.2的。
因为要动态生成表格的行头列头,所以把一些定义放到数据库中,这些定义包括标题、层级关系、行列融合、序号、以及其他一些跟计算有关的属性定义等。现在就是做一个能编辑这个定义的简单界面,在这篇文章里说说它们之间的顺序调整,即表格行列的移动。
如果是没有任何行列融合的话,移动是非常容易的,但一旦有了行列融合存在,移动就得考虑很多情况了,同一行的元素还好说,如何找同一列的元素是个问题,所以我最终是对行列头每个单元格都定义了相应的class来标识它们是否是属于同一列的,同一列的class是相同的。
还有这里假定不会出现奇异表格。即列头从上往下,列融合的数目只会越来越小,最后一个td只占一列;同理行头从左往右,行融合的数目也只会越来越小,最后一个td也只占一行。当移动的时候,如果是碰上有行列融合的,最终移动的时候是把整个融合在一起那些行列移动到新位置的。最后在行头定义的最后一列,列头定义的最后一行,我加入一列(或行)来放置操作符,这也是为了明确当前操作的是哪一行、哪一列,因为这些单元格是铁定只占一行一列的。
a.列的移动
主要是讲列的左移,因为右移是可以化为左移来处理的。

操作之前的一些处理
var $column = $(sender).parent();
var className = $column.attr("class");
var preClassName = $column.prev().attr("class");
if (!preClassName)
return;
var $row = $column.parent();
var $table = $row.parent();
var ri = 0;
var $sCol, $pCol;
那个return,是为了当前列是第一列的情况下直接返回用。
基于不会出现奇异表格的事实,从上往下找当前列的第一个单元格
$sCol = $table.find("td." + className).eq(0);
记下第一个单元格出行时的行索引
var mcRowIndex = $table.children("tr").index($sCol.parent());
记下这个索引以后要用。

移动位置与要移动的列数
$pCol = $sCol.prev();
preClassName = $pCol.attr("class");
var $preOpColumn = $row.children("td." + preClassName);
var preEndIndex = $row.children("td").index($preOpColumn); //移动列时,最终的位置,用操作符所在单元格的列索引来判定最终移动列的位置
var moveCount = 1; //要移动的列数
moveCount = $sCol.attr("colspan");
if (!moveCount)
moveCount = 1;
位置的取得主要就是利用同列的class相同,以及操作符单元格只占一列这二个特性
下面是多列移动的骨架,单列移动下面单独列出

多列的移动
var moveIndex = 0;
var clsName;
var $mvColumn = $column;
while (moveIndex < moveCount) {
clsName = $mvColumn.attr("class");
$mvColumn = $mvColumn.next();
var $preColumn, $iterOpCol, opIndex;
$("td." + clsName).each(function() {
//......单列的移动......
});
moveIndex++;
preEndIndex++;
}
单独一列的移动,即在那个$("td." + clsName).each()里面的遍历函数

单列的移动
$preColumn = $(this).prev();
$iterOpCol = $row.children("td." + $preColumn.attr("class")); //前一单元格所在列的操作符单元格
if ($iterOpCol) {
opIndex = $row.children("td").index($iterOpCol);
while (opIndex > preEndIndex) {//找移动目的地的单元格
$preColumn = $preColumn.prev();
$iterOpCol = $row.children("td." + $preColumn.attr("class")); //前一单元格所在列的操作符单元格
if (!$iterOpCol)
break;
opIndex = $row.children("td").index($iterOpCol);
}
if (opIndex >= preEndIndex)
$(this).insertBefore($preColumn);
else
$(this).insertAfter($preColumn);
}
用一个while (opIndex > preEndIndex)循环找目的地,那是因为表格融合的关系,这一行不一定会存在class名为preClassName的单元格。最终找到的是“它”之前或之后的单元格。
从表象上讲,移动的代码到这里就结束了,但后续操作的关系,还有一个东西要处理,即那些未参与移动的单元格的class的变动。比如:
<tr>
<td colspan="2" class="1" />
</tr>
<tr>
<td class="1" />
<td class="2" />
</tr>
当第2列左移时,第一行那个单元格并没有移动过,最张要把它的class从“1”改为“2”.
这时前面那个mcRowIndex就用得上了。
$table.children("tr:lt(" + mcRowIndex + ")").children("td." + preClassName).each(function() {
$(this).attr("class", className);
});
在mcRowIndex这一行之前的其实都是没有参与移动的。
b.行的移动
行的移动处理方式其实跟列的移动差不多,唯一要说明的是,操作符所在那一列是专门定义class的。移动行比移动列简单,一是不用处理class的变换问题,二是交换二行比交换二列要容易。
这里就不作一一解释了。

上移一行
var $row = $(sender).parent().parent();
var $preRow = $row.prev();
var $opCol = $row.children("td.op:first");
var $preopCol = $preRow.children("td.op:first");
if ($preopCol.size() == 0)
return;
var $rowFCell = $row.children("td:first");
var clsName = $rowFCell.attr("class");
var moveCount = $rowFCell.attr("rowspan");
if (!moveCount)
moveCount = 1;
while ($preRow.children("td." + clsName).size() == 0)
$preRow = $preRow.prev();
var stIndex = $preRow.children("td").index($preRow.children("td." + clsName));
var moveIndex = 0;
var $moveRow = $row;
var $nRow;
while (moveIndex < moveCount) {
$nRow = $moveRow.next();
$moveRow.insertBefore($preRow);
$moveRow = $nRow;
moveIndex++;
}
$preRow.children("td:lt(" + stIndex + ")").each(function() {
$(this).insertBefore($rowFCell);
});
下移一行也可以化为上移一行处理的,这里略。
2009年6月16日
#
前二天机缘巧合,在四五年后重新用起了fastreport,不得不感叹其强大之处,定制能力实在是太强了,今天把一些以前写的放在大富翁论坛的东西翻出来,如果现在还有用这个东西的,如果你也有一些另类的运用,可以有所帮助。
这里有一个FR报表简繁体转化的解决方案,只做一份报表,即可在简繁体下用。
报表运行时可编辑,这并不是什么用法,只是一个当时解决问题跟踪FR源码时的所得,以前在delphi下开发跟踪跟踪人家的源码受益匪浅。
数据集行列转换不是什么新鲜事,关于SQL的用法网上随便搜搜即有,这里有不用改变数据集,在FR里直接行列转换输出的例子,这样在就不用在表单层专门为报表重新生成一个数据集。
这里的很多技巧其实是因为不想为了报表显示重新用SQL生成全新的数据集,下面这个对帐单式的交错复合报表即是。
上面是以前做的,前二天做报表时,又遇到了新情况,报表的分组页码显示,这个其实简单得很,只是好久没用FR报表了所以觉得有搞,其实象那种在分组头显示合计之类的都是用类似技巧实现的。
这里的情况适用于FastReport2.53版本。
报表页码一般情况下就象在Word里一样不用费什么脑筋,FastReport里直接从系统变量可以得到。但特殊情况下就无法轻松得到了。
这里谈的是分组页码的问题,就是在报表分组的情况下。首先是每个分组强制分页,然后页码重新计数。比如5页分为二组,一组占了2页,另一组占3页。平常显示只要
1/5 2/5 3/5 4/5 5/5
这时要变成
1/2
2/2 1/3 2/3 3/3
第几页这是很容易算出来的,用个变量来表示页码,分组结束后重新算页码就行了,难点就在分组的页数,基本思路就是利用两遍报表,第一次得到各个分组的总页数,第二次再显示之。
首先定义三个变量,GPage用来表示页码初始值为1,PageList用来存放分组的页码数初始值为{0},GIndex用来计算分组的索引初始值为0,FGIndex用来第二次遍历时计算分组索引,其实这个与GIndex可以共用一个,但为了清晰起见分开用。
在PageFooter的BeforePrint里写
GPage := GPage + 1;
在GroupFooter的BeforePrint里写
if FinalPass then //二次遍历时增加分组索引
FGIndex := FGIndex + 1
else
begin
setLength(PageList,GIndex +
1);
PageList[GIndex] := Gpage;
//存放分组的总页数
GIndex := GIndex + 1;
end;
GPage := 0;
这样所有的东西都有了,在显示页码的Memo里填[GPage]。
在显示页数的Memo下面脚本段里写
if not FinalPass then exit;
MemoPageCount.Lines[0] :=
PageList[FGIndex];
(MemoPageCount
是这个Memo的名字)
这样就达到上述的分组页码的要求了。
2007年8月2日
#
该总结的东西都总结得差不多了,剩下的都是一些细枝末节的了,基本可以收尾了,这一篇讲一讲设计模式。
设计模式是个什么东西,它其实只是前人在当前系统实现基本手段基础上提取出来的一些方法论,目的是为了更好更快的解决我们在系统实现中碰到的各种问题。其实如果总是处于那种“你来我往”的状态之中,没有静下心来细细的思量,无法把握到项目的全貌,没有精益求精的态度,那些方法论不用也罢。可幸这一次上述的如果都没有,所以才能一直写这个系统的总结写了二十来篇,可谓收获颇丰。
一、创建型模式
创建型模式里用得比较多,也比较好掌握的就是Singleton模式了,这个模式对于实现者的设计能力要求不甚高,这也是我最早运用的模式之一。在此次系统实现中当然也不会少,不过其中由于环境特性,我有些实现的地方并没有用标准的方法,而是简化了。那些地方可以说徒有其名而无其实,但最终的效果还是一样的。
Factory与Abstract Factory模式是比较耳熟能详的二个模式,其二者很容易混淆起来,其实我在运用它们的时候也不是非常的得心应手,总的来说实现了一种设计后并不能严格意义上区分它是哪一个模式。对于很多的模式都有这种感觉,特别是当一个设计开始趋向有层次的复杂之后,这从另一方面印证了设计模式是一种特定条件下的方法论的观点。
在这一次的系统中并没有很多的、很复杂的类的创建,所以Prototype与Builder模式也无从谈起。系统中类的创建要化点脑筋的就数硬件访问与服务加载了,二者实现手段是一致的。都是先提取一个公共的接口,系统启动创建时,根据配置文件创建相关类,并设置相关属性,最后把创建出来的类放入到一个集合中供系统调用。这里有Abstract Factory,也有Bridge以及Iterator。
二、结构型模式
Adapter在这新的系统用处不大,但Bridge则大有用武之地。特定硬件与系统的解耦就是通过Bridge来实现的,还记得硬件访问时提取出来的那个接口吗?那个接口与我们封装过的特定硬件访问类之间就是桥的关系。
硬件技术总归是一种比较复杂的东西,我们的系统利用的只是它们功能的小部分,同时厂商提供的硬件访问API对于系统是很不友好的,它对于我们暴露了过多的细节,这时Facade模式就派上用场了。所以硬件访问这一块是在Facade基础上的Bridge。
其实数据通讯这一块跟硬件访问有异曲同工之妙,通讯组件的逻辑封装与技术封装之间可以认为有Bridge,即不同的通讯技术对于客户端的最终调用是不透明的;而技术封装本身就是一个Facade。
Composite模式最通常的应用就是菜单类了。而Decorator模式在我看来是无处不在,这个模式是“优先使用对象组合,而不用类继承”的典型体现。我说这个模式无处不在并不是说系统中大量充斥着一个个的Decorator,而是说明对象组合的普遍性。最能体现Decorator的价值的地方是数据对象的查询条件、排序设置,数据对象EntityBase与查询条件WhereCondition之间是Decorator关系,而WhereCondition内部则是Composite关系。
Flyweight与Proxy在系统中没有很明显的应用。
三、行为型模式
相比前二类模式,行为型模式似乎比较的深奥一点,以前有几次出去面试时,居然不止一次的发现有些人不了解其中的一些模式。抛开一些个体的因素不谈,个人认为行为模式显得“深奥”正是反应了我们真实世界的多变复杂性。我们的世界是如此的难以琢磨,而行为型模式对于这个世界相比前二类模式有更深的介入,以至于我们在运用这一类的模式时有时会很不好权衡利弊。
Command模式,每次提到时好象都拿菜单与工具栏说事,这个运用也是比较的普遍,这一次实现系统当然也就没有放过它。
Iterator模式在行为型模式中算是比较“大众化”的一个模式了,因为集合的运用对于每一个系统实现者来说都是非常必要的,而现在绝大多数的系统框架在集合类的实现上都体现了这个模式的精髓。而Observer是另一个系统框架涉及比较多的模式,事件-委托机制就可以认为是这个模式一种表现。集合与事件是在系统设计与实现中大量运用的,上述二种模式当然就逃不过。
State模式在这次的系统实现的运用中不是非常的到位,因为虽然窗体状态看起来也挺多,但对于界面变化来讲只分为浏览与非浏览二种,而各个状态的操作只有一个“确定”是各异的。State模式在这个场景显得有点鸡肋。
Strategy本来是没什么机会用的,不过在管控端有一个功能,就是显示座位,座位号的排列并不是每次都是从左到右来的,这里有个排座策略问题,这个模式恰好排上用场。
Template Method是行为模式中在我看来运用得很频繁的一个模式,这次系统实现中也大量的运用,比如在单数据窗体中工具栏按钮事件中的一串方法,还有在数据访问对象中的很多方法。其实象这种由父类放一个钩子出去,然后子类Override相关方法实现具体操作的用法,在很久以前还不知道设计模式这个东西的时候就在使用了。
剩下的其他行为型模式有些有太特殊的应用场景(如Interpret),而有些虽然可以应用到系统中,但鉴于系统需求没有那么深入,犯不着为了一个设计模式复杂化整个系统(如Memento)。
最后数了一下,在这次系统实现中共用了12个模式,占了Gof设计模式的一半强,可以说“麻雀虽小,五脏俱全”。半年来的经历比之过去几年的收获还大,当然没有之前的积累也一下子用不上这么多的设计模式,总之过去的那半年虽然没有投身到新技术的大潮中,但还是很值得。
2007年7月30日
#
这一篇其实没什么可讲的,只提一下跟客户端不太一样的一些地方。
服务端跟客户端最大的区别是它面对的不是单单一个连接,而是有一些个连接。对于接收与发送来讲它是要具体到accept进来的每一个连接的,所以这里有一个SocketStateObject参数会贯穿始终,这个参数主要就是放对应客户端的Socket连接及一些状态变量,在accept进来一个连接后即创建一个这个对象。
public void Listen(int port)
{
IPEndPoint ipe = new IPEndPoint(IPAddress.Any,port);
workSock = new Socket(ipe.AddressFamily,SocketType.Stream,ProtocolType.Tcp);
workSock.Bind(ipe);
workSock.Listen(100);
acpT = new Thread(new ThreadStart(acceptThread));
acpT.IsBackground = true;
acpT.Start();
}
private void acceptThread()
{
while(true)
{
Socket asock = workSock.Accept();
SocketStateObject state = new SocketStateObject(asock);
if (OnConnect != null)
OnConnect(state,System.EventArgs.Empty);
ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveThreadEntryPoint),state);
}
}
象上面OnConnect事件及ReceiveThreadEntryPoint方法里都会跟进这么一个参数。
管理连接是个很复杂、很讲究技术的活。可用连接的数据接收与发送(甚至有些接收与发送必须是相关的)、连接的可用性监测、失效连接的移除。所有这些都要作应有的记录,并且一些还要往UI界面触发事件,事件触发这一点至关重要,也最为棘手,这其中涉及到很多线程同步、线程阻塞的问题。
对于这种极为复杂的问题,最好的解决方法是参照现有的成熟方案进行设计、实现或者拿来主义。但之前也没涉足到这些方面的开发,一时还真找不到比较理想的东西,加之项目本身对于通讯的要求不甚高,10个连接顶天了(如果真有哪个客户要10个签到终端,那真是绝对的上帝了,至目前为止最多的也就5个终端,一般不会多于3个),所以在这次开发中我就用了一些简单的方法来管理客户端的连接。
由于这一块并没有经过认真的设计,也暂时无力作认真的设计,最后实现的东西自己也不满意,这里就不去讲述如何实现管理连接的了。
2007年7月26日
#
摘要: 数据窗体的状态大概可以概括为以下几种,即初始、浏览、新增、修改、删除、查询(单数据窗体的查询是一种简单地基于当前表的查询,各个条件之间只能“与”运算,关系运算只有“等于”,当然字符串字段会有“like”)。在各种状态下,窗体的UI呈现是不相同的,而且在不同状态下,一些按钮的动作是各异的。比如“确定”按钮,在新增状态下按“确定”和在查询状态下按“确定”明显是不同的二种动作。上述的这些情况可以用GoF...
阅读全文
2007年7月25日
#
摘要: 所谓的单数据窗体是指那种在窗体只显示一张单表的数据的窗体,主要就是用于显示基本表,因为这个系统要显示的差不多就是基本表为主,这一篇主要讲这个数据显示窗体的基类。既然是基类嘛,当然要提供一些虚方法给子类来改写,要有一些变量必须由子量来初始化。首先必须由子量来提供实例的变量主要是跟子类关联的数据表有关,因为窗体的始化及UI的呈现都是与数据有关的,所以上述变量的初始化必须是在构造函数的最开始的地方进行。...
阅读全文
2007年7月24日
#
摘要: 经过上二篇的铺垫,现在终于可以加载菜单与工具栏了。先来看一下IService的接口实现里LoadCommand方法是如何来加载菜单与工具栏的:IService接口的实现Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->publicclassSignInR...
阅读全文