[转载]Asp.net Mvc+MongoDB+Autofac等打造轻量级blog系统(一)

mikel阅读(1466)

[转载]Asp.net Mvc+MongoDB+Autofac等打造轻量级blog系统(一) – 爱因斯坦的小脑 – 博客园.

这两天坐地铁上在想着是否可以做一个很轻量级的.net博客发布系统。。。所有东西都用轻量级的,我想要系统是基于ASP.NET Mvc框架的,所以选定了如下几个大的组件来完成这个设想。

1. 整个应用程序架构:ASP.NET mvc 3 (Razor)

2.数据存储 : MongoDB,是个面向文档的数据库,它是多系统支持,轻量级,高性能的。

3.ORM : 现在的应用开发如果你不用ORM,那就好像有点老土了,但是ORM永远都无法和ado.net媲美,无乱是EF,NHibernate还是linq等 等。。。。而我这里还是想使用一个ORM工具,于是选择了Simple.Data这个非常轻量级的ORM工具,它使用了C# 中的Dynamic特性。

4.IoC工具,绝对是autofac这个最轻量级了。。。

对于ASP.NET mvc你可以到这里看到很多学习资料:http://www.cnblogs.com/n-pei/tag/Asp.net%20MVC/

包括ASP.NET MVC 3的系列文章。。。。微笑

环境的要求:

1.首先你需要的是.net framework 4的安装。你机器不需要安装ASP.NET MVC,只需要把对应的几个dll添加到bin目录下就行。

2.MongoDB的安装 如果你以前接触过MongoDB,请跳过这一段,直接看第三步。

image

http://www.mongodb.org/ 它的数据是以json格式存储的。

下载到对应的压缩包,然后解压到某个盘下。

image

默认的mongo是不会自己创建文件夹,而它却需要找到指定的文件夹Data\db,所以我们需要在bin目录所在的根文件夹下创建如下文件夹:

image

接下来就是运行db server了。

image

现在数据库服务器就开始运行了,因为它是在dos下运行的,所以不能关闭这个窗口,以后说明下如何把它制定为windows service,这样就需要开着窗口了。

3.ORM: Simple.Data这个是使用C# Dynamic属性的轻量级ORM工具,它不是很好用,但是速度是挺快的,而且不需要配置文件,支持各种数据库。。。

你可以到这里下载:http://github.com/markrendle/Simple.Data

image

4. IoC工具,这个Autofac我之前有好多文章都介绍了。你可以到这里下载和查看:http://code.google.com/p/autofac/

我博客中相关的文章: http://www.cnblogs.com/n-pei/tag/Autofac/

可能你已经不耐烦了,,我啰嗦这么多,,好吧,接下来开始使用MogonDB,这篇文章主要介绍如何在ASP.NET MVC中使用它。。。。其它模块在以后的文章中介绍。

首先是创建实体,这里只创建好Post和comment两个实体。

image

接下来是创建Repository模块:

Post的Repository接口:

image

对应的Save方法:

image

这里的操作都是比较繁琐的,以后会结合autofac优化这一部分。

GetAll方法和通过Id得到某个post实体的方法如下:

image

还有一部分是update某个post.这部分代码就不贴了。

接下来是Controller部分的代码:

Create post部分的代码:

image

添加对应的View以后,运行:

image

点击Craete按钮后:

image

保存成功,然后会自动跳转到List页面:

image

稍候等整个项目写的差不多了,我会把代码放到codeplex上,支持下微软,呵呵。

[转载]对资源加读写锁的容器

mikel阅读(1309)

[转载]对资源加读写锁的容器 – 怕怕的韦恩卑鄙 – 博客园.

之前写了一篇《对不能用using的成对操作,快速扩展IDisposable的方法》提到了如何快速的把销毁操作用闭包的形式封装为IDisposable,并且实现了一个ReaderWriteerLockSlimHelper。

对于没有using的RWLock似乎已经很好用了, 但是我仍嫌弃其不够简单。

资源读写锁的应用,绝大多数是针对某批特定的资源。如果为这样的资源做一个封装,资源的引用需要被隐藏在封装内,封装外不通过读锁不可以访问资源。显然ReaderWriteerLockSlimHelper却无法做到这一点。

我们在上文中已经建立了一个 Disposable对象,取得该对象意味着我们可以加锁,将其销毁我们就可以解锁,其生存期与整个加锁周期吻合。对于读写锁来说具有这种行为的对象是一种作为读写钥匙/令牌的存在。解锁后的资源引用放在这个令牌上作为属性暴露出来再合适不过了。

以此为考量,我们先设计一个实现IDisposable,读写锁的令牌

///
/// 读写资源的令牌
///
public abstract class LockToken : IDisposable
{
protected LockableObjectContainer _container;

///
/// 被锁住的资源引用
///
public T Value { get; set; }
///
/// 读写锁的原钥匙,对其销毁即为解锁
///
protected IDisposable _innerTicket;

///
/// 创建读写资源的令牌
///
///
<span> </span>解锁的资源引用 ///
<span> </span>锁的真实引用 ///
<span> </span>在解锁前需要做的操作 如把资源的新值覆盖回锁住的资源 ///
<span> </span>在解锁后需要做的操作 如写日志 internal LockToken(LockableObjectContainer container, IDisposable innerTicket, Action&gt; beforeDispose, Action&gt; afterDispose)
{
_container=container;
_innerTicket = innerTicket;
Value = container._value;
this._disposeAction =
() =&gt;
{
try
{

if (beforeDispose != null) beforeDispose(this);
this.Value = default(T);
_innerTicket.Dispose();
if (afterDispose != null) afterDispose(this);
}
catch
{

}
};
}

#region IDisposable Members

///
/// 令牌销毁时要做的操作
///
Action _disposeAction;

///
/// 销毁
///
public void Dispose()
{
_disposeAction();
}

#endregion
}

读写时,所做的操作略有不同。写令牌在销毁时要把令牌上的新引用/值 覆盖到容器内

依此我们可以做两个不同的令牌子类

 class ReadLockToken<T> : LockToken<T>
    {
        public ReadLockToken(LockableObjectContainer<T> container, ReaderWriterLockSlim _lock) :
            base(container, _lock.CreateLockScope(LockType.Read), null, null)
        {
        }

    }



    class WriteLockToken<T> : LockToken<T>
    {
        public WriteLockToken(LockableObjectContainer<T> container, ReaderWriterLockSlim _lock)
            : base
            (
                container,
                _lock.CreateLockScope(LockType.Write),
                lt =>
                {
                    //在解锁前 把新值保存给原对象 
                      lt._container._value =lt.Value;
           },
          null
            )
        {
        }

    }

这些做好后,编写容器就很容易了

public class LockableObjectContainer<T>
    {
        internal protected T _value;


        System.Threading.ReaderWriterLockSlim _lock = new System.Threading.ReaderWriterLockSlim();
        public LockableObjectContainer(T value)
        {
            _value = value;

        }

        public LockToken<T> GetReadToken()
        {
            return new ReadLockToken<T>(this, _lock);
        }
        public LockToken<T> GetWriteToken()
        {
            return new WriteLockToken<T>(this, _lock);

        }

    }

上面的内容很枯燥

写读写锁的时候很方便

static LockableObjectContainer <Dictionary <Type ,IFactoryContainer > > planConnectionGetters
= new LockableObjectContainer<Dictionary<Type, IFactoryContainer>>
(new Dictionary<Type, IFactoryContainer>());


。。。。。。。。。。。。。。。。。。。。。。。。。



using (var token =planConnectionGetters.GetReadToken())
{
var dic = token.Value;

if (dic.TryGetValue(contractType , out channelFactory ))
{
return channelFactory.GetChannel (uri);

}

}


using (var token = planConnectionGetters.GetWriteToken ())
{
var dic = token.Value;

if (!dic.TryGetValue(contractType, out channelFactory))
{
Type t = typeof(FactoryContainer<>);
channelFactory = Activator.CreateInstance(t.MakeGenericType(contractType)) as IFactoryContainer;
dic.Add(contractType, channelFactory);

}



}

 如果容器经常保存字典等常用集合对象 我们也可以这样做一些扩展方法

public static TValue GetOrCreateValue<TKey, TValue>
(
this LockableObjectContainer<IDictionary<TKey, TValue>> container,
TKey key,
Predicate <TValue > valueChecker,
Func<TValue> valueFactory
)
{
TValue val=default (TValue);
using (var token = container.GetReadToken())
{

var dic = token.Value;
if (dic.TryGetValue(key, out val))
{
if (valueChecker (val))
return val;

}
}

using (var token = container.GetWriteToken())
{
var dic = token.Value;
if (dic.TryGetValue(key, out val))
{
if (valueChecker(val))
return val;

}
val=valueFactory();
dic.Add(key,val) ;

}
return val;

}




------------------------------------------------------------

TQueue q;
q = instanceQueues.GetOrCreateValue(instance, _ => true, () => new TQueue());
return q;


[转载]编程基本算法(二)

mikel阅读(884)

[转载]编程基本算法(二) – 清风飘过 – 博客园.

在写此文章之前,笔者想说说关于程序员的基本知识,好多园友在博客园上谈论自己的工作经 历,或者给毕业生的建议,笔者很赞同期中园友建议在同学在学校里将计算机基础打好,没有良好基础怎么能建大厦呢?有了一些基础基本知识,在去学习深的理论 就是事半功倍了,如果是先遇到深理论在去学习相关的基础,那就是事倍功半了。也许许多同学会说,现在的很多企业都招能直接上手的,笔者首先想说那种企业肯 定是小企业,鼠目寸光,招也找不到很优秀的人才,就算去了,这种人才也不会呆很长时间,因为这种企业没有发展的远见,有技术的人才可能因没发展前途而跳 槽。其次笔者想说如果你有良好的计算机基础,笔者相信你能成功在三个月之内学习适应达到企业技术要求。

其实,笔者想表达任何时候不要忽略基础。闲话不多说了,直接转基本排序算法。

编程基本算法(一)

冒泡排序

使用条件:集合的元素可对比大小

算法思想:连续地扫描待排序的记录,每扫描一次,都会找出最小记录,使之更接近顶部。由于每次扫描都会把一条记录置于它的最终最正确的位置,因此下次扫描不需要重新检查这条记录

举例编程:int b[10]={77,1,65,13,81,93,10,5,23,17}将其冒泡排序(这里笔者将概念弄混淆了,感谢zdd的指出

//冒泡排序 void Bubble(int b[10]) { int temp; int i; for(i=9;i>0;i--) { for(int j=0;j<i;j++) { if(b[j]>b[j+1]) { temp=b[i]; b[i]=b[j]; b[j]=temp; } } } cout<<"the sort is:"; for(int i=0;i<10;i++) { cout<<b[i]<<" "; } cout<<endl; }

性能分析:时间复杂度O(n^2)

希尔排序

使用条件:集合的元素可对比大小

算法思想:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序“时,在对全体记录进行一次直接插入排序。 子序列构成的不是简单“逐段分割”,而是相隔某个“增量”的记录组成一个子序列。因此比较排序时候关键字较小的记录就不是一步一步往前挪动,而是相隔一定 增量移动,该“增量”呈现一个递减趋势,最后这个“增量”总是为1,那么此时序列已基本有序,只要作少量的比较和移动几个完成排序。希尔排序不好把握增量 的设定。一般8个数我们认为设定“增量”为:4,2,1。(这是一般希尔排序的设定)。那么笔者这里要拟定一个求“增量”的公式 h(n+1)=3*h(n)+1,(h>N/9停止)这个公式可能选择增量不是最合适,但是却适用一般“增量”的设定。如果是8个数的话,那么这里 增量就是1。

举例编程:int b[10]={77,1,65,13,81,93,10,5,23,17}将其希尔排序

//希尔排序自增量需要自己合适选择 void ShellSort(int b[10]) { int h,i; int n=10; //通过这个循环算出增量为1和4 for(h=1;h<=n/9;h=3*h+1); //增量循环 for(;h>0;h/=3) { for(i=h;i<n;i++) { int j,temp; temp=b[i]; //插入排序 for(j=i-h;j>=0;j=j-h) { if(b[j]>temp) { b[j+h]=b[j]; } else { break; } } b[j+h]=temp; } } cout<<"the sort is:"; for(int i=0;i<10;i++) { cout<<b[i]<<" "; } cout<<endl; }

性能分析:时间复杂度对于希尔排序就有点复杂,它根据具体的“增量”不同而不同,这里笔者采用严蔚敏《数据结构》的O(n^3/2)

快速排序

使用条件:可对比大小的集合。

算法思想:通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分关键字小,则可分别对这两部分记录继续这种排序,最后 达到有序序列。这里有一个关键点,就是选取分割的“基准”。肯定是大于这个“基准”分成一个部分,小于这个“基准”分成一个部分。这里笔者默认取该部分第 一个记录为“基准”。

举例编程:int b[10]={77,1,65,13,81,93,10,5,23,17}

//快速排序 void QuickSort(int *b,int low,int high) { //交换函数 void Sawp(int *a,int *b); int Old_low=low; int Old_high=high; while(low<high) { while(*(b+high)>*(b+low)&&low<high)high--; Sawp(b+low,b+high); while(*(b+low)<*(b+high)&&low<high)low++; Sawp(b+low,b+high); } if(Old_low<low-1) { QuickSort(b,Old_low,low-1); } if(high+1<Old_high) { QuickSort(b,high+1,Old_high); } } //交换函数 void Sawp(int *a,int *b) { int temp; temp=*a; *a=*b; *b=temp; }

性能分析:时间复杂度O(nlogn)

[转载]编程基本算法(一)

mikel阅读(866)

[转载]编程基本算法(一) – 清风飘过 – 博客园.

笔者好长时间没有更新博客了,一个原因是开发的项目所用到的技术都是老技术点,所接触到的知识都是行业逻辑流程,所以只是自己做了总结并没有拿上来分享。另外一个原因是目前笔者在重新学习C++语言以及计算机的一些基本知识(算法等)。

下面的代码为C++代码,好了直接进入正题

折半查找

又称二分查找。

使用条件:有序集合。

算法思想:先确定待查记录所在的范围(区间),然后逐步缩小范围直到找到或者不找到为止。

关键点在于比较中间位置所记录的关键字和给定值的比较,如果比给定值大(这里假设集合从小到大排列)那么可以缩小区间范围(集合开始–>中间位置的上一位),在比较该区间的中间位置所记录的关键字与给定值,依次循环到找到或者找不到位置。

举例编程:这里有一个整数数据 int a[10]={1,5,10,13,17,23,65,77,81,93};

(1)这是递归(感谢园友zdd指出这里判断条件的错误,应该改为if(min>max)

//折半查找 //数组必须按照一定的顺序 //参数:最大,最小,目标(参数类型为整数) int BinarySearch(int min,int max,int num) { if(min==max)return -1; int mid=(min+max)/2; if(a[mid]==num)return mid; else if(a[mid]<num) { return BinarySearch(mid+1,max,num); } else { return BinarySearch(min,mid-1,num); } }

(2)非递归

//非递归算法 int BinarySearch_F(int num) { int min=0; int max=9; int mid; while(min<=max) { mid=(min+max)/2; if(a[mid]==num)return mid; else if(a[mid]>num)max=mid-1; else min=mid+1; } return -1; }

性能分析:时间复杂度O(logn)

插入排序

使用条件:可对比大小的集合。

算法思想:将一个记录插入到已排好序的有序列中,从而得到一个新的,记录数增1的有序序列。待插记录依次比较已经排好序列,如果序列数大于该待插记录,那么该序列往后挪一位,直到找到序列小于待插记录,那么此时插入到该序列的后一个位置,依次上面操作,直至插完位置。

举例编程:int b[10]={77,1,65,13,81,93,10,5,23,17}将其排序

//插入排序 //这里temp是哨兵位 //从小到大 void InsertSort() { int temp; int j; for(int i=1;i<10;i++) { temp=b[i]; for(j=i-1;j>=0;j--) { if(b[j]>temp) { b[j+1]=b[j]; } else { break; } } b[j+1]=temp; } cout<<"the sort is:"; for(int i=0;i<10;i++) { cout<<b[i]<<" "; } cout<<endl; }

性能分析:时间复杂度O(n^2)

折半插入排序

使用条件:可对比大小的集合。

算法思想:基本思想与简单插入排序思想相似,唯一的不同点在于找出插入的位置,简单插入排序用的是依次比较,这里折半插入排序改进了,将依次查找改进成折半查找

举例编程:int b[10]={77,1,65,13,81,93,10,5,23,17}将其排序

void BinaryInsertSort() { int temp,min,max,mid; int j; for(int i=1;i<10;i++) { min=0;max=i-1; temp=b[i]; while(min<=max) { mid=(min+max)/2; if(b[mid]>temp) { max=mid-1; } else { min=mid+1; } } for(j=i-1;j>=max+1;j--) { b[j+1]=b[j]; } b[max+1]=temp; } cout<<"the sort is:"; for(int i=0;i<10;i++) { cout<<b[i]<<" "; } cout<<endl; }

性能分析:时间复杂度O(n^2)

虽然这里时间复杂度与简单插入排序一样,但是通过查找找到插入的位置用的比较次数是明显减少的。

[转载]在C#中选择正确的集合进行编码

mikel阅读(722)

[转载]在C#中选择正确的集合进行编码 – 不如来编码-luminji’s web – 博客园.

要选择正确的集合,我们首先要了解一些数据结构的知识。所谓数据结构,就是相互之间存在一种或多种特定关系的数据元素的集合。结合下图,我们看一下对集合的分类。

image

集合分类

在上图中,可以看到,集合总体上分为线性集合和非线性集合。线性集合指元素具有唯一的前驱和后驱的数据结构类型。非线性集合是指具有多个前驱或后驱的数据结构类型,如:树、图。在FCL中,非线性集合实现的比较少,所以我们将会更多的讨论线性集合。

clip_image004 注意:由于类型安全、转型效率等方面的原因,本建议将只讨论泛型集合。

线性集合按存储方式,又分为直接存储和顺序存储。所谓直接存储是指:该类型的集合数据元素可以直接通过下标(也即index)来访问,在C#中有三 种形式:Array(包括数组和List<T>),string,struct。直接存储结构的优点是:向数据结构中添加元素是很高效的,只 要直接放在数据末尾的第一个空位上就可以了。它的缺点是:向集合插入元素将会变得低效,它需要给插入的元素腾出位置并顺序移动后面的元素。

string和structs虽然是直接存储结构,但它们与一般的集合定义有很大的不同,所以也不在本建议讨论之中。在直接存储的数据结构中,需要 区分的是数组和List<T>的选择。再次强调一下:如果集合的数目固定并且不涉及到转型,使用数组效率高,否则就使用 List<T>。

顺序存储结构,也即线性表。线性表的大小可动态的扩大和缩小,它在一片连续的区域中存储数据元素。线性表不能按照索引进行查找,它通过对地址的引用 来搜索元素,为了找到某个元素,它必须遍历所有元素,直到找到对应的元素为止。所以线性表的优点是插入和删除数据效率高,而缺点是查找的效率相对来说低一 些。

线性表又可以分为队列、栈以及索引群集,在C#中,分别表现为:Queue<T>,Stack<T>,索引群集又进一步泛 化为字典类型Dictionary< TKey, TValue >和双向链表LinkedList<T>。

队列Queue<T>遵循的是先入先出模式,它在集合末尾添加元素,在集合起始删除元素,如图:

image

队列操作

根据队列的特点,可以用来处理并发命令等场景:将所有客户端的命令先入队,由专门的工作线程来执行队列的命令。在分布式中的消息队列就是一个典型的队列应用实例。

栈Stack<T>遵循的是后入先出模式,它在集合末尾添加元素,同时也在集合末尾删除元素,如图2-3:

image

栈操作

字典Dictionary<TKey, TValue>存储的是键值对,值在基于键的散列码的基础上进行存储。字典类对象由包含集合元素的存储桶组成,每一存储桶与基于该元素的键的哈希值 关联。如果需要根据键进行值的查找,使用Dictionary<TKey, TValue>将会使搜索和检索更会快捷。

双向链表LinkedList<T>是一个类型为LinkedListNode的元素对象的集合。当我们在集合中觉得插入和删除数据很 慢的时候,我们可以考虑使用链表。如果我们使用LinkedList<T>,我们会发现此类型并没有其它集合普遍具有的Add方法,取而代之 的是AddAfter、AddBefore、AddFirst、AddLast等方法。双向链表中的每个节点都向前指向Previous节点,向后指向 Next节点。

以上讨论了线性集合,在FCL中,非线性集合实现的不多。非线性集合分为层次集合和组集合。层次集合,如树,在FCL中就没有实现。组集合,又分为 集和图。集在FCL中实现为HashSet<T>,而图在FCL中也没有对应实现。集的概念在本意上是指存放在集合中的元素是无序的且不能重 复的。下图演示了集的用途:

image

集操作

除了上面我们提到的集合类型,还有其他几个要掌握的集合类型,它们是在实际应用中发展出来的对以上基础类型的扩 展:SortedList<T>,SortedDictionary<TKey, TValue>,SortedSet<T>。它们所扩展的对应类为 List<T>,Dictionary<TKey,TValue>,HashSet<T>,作用是将原本无序排列的 元素,变为有序排列。

除了排序上的需求增加了上面3个集合类,在命名空间System.Collections.Concurrent下,还涉及几个多线程集合类。它们 主要是:ConcurrentBag<T>对应List<T>,ConcurrentDictionary<TKey, TValue>对应Dictionary<TKey, TValue>,ConcurrentQueue<T>对应 Queue<T>,ConcurrentStack<T>对应Stack<T>。如果我们的集合被用于多线程应用 中,可以使用这几个集合类型。关于集合的线程安全性,可以进一步查看MSDN。

本建议到此为止已经介绍了FCL中的大部分泛型集合类,为了对它们有更好的了解,最后我们给出一个主要集合类的类图。实际工作中,应该根据需要选择合适的集合类。

clip_image002[12]

FCL集合类图

[转载]分享27款非常棒的 jQuery 表单插件

mikel阅读(1053)

[转载]分享27款非常棒的 jQuery 表单插件 – 梦想天空 – 博客园.

JQuery的易扩展性吸引了来自全球的开发者来共同编写JQuery插件。jQuery插件不仅能够增强网站的可用性,有效地改善用户体验,还可以大大减少开发时间。本文收集了非常棒的jQuery表单插件与大家分享,欢迎大家推荐更多更好的插件。

1- jQuery inline form validation

jQuery Form Plugins

2- Uniform

jQuery Form Plugins

3- Autotab

jQuery Form Plugins

4- jquery Niceforms

jQuery Form Plugins

5- jquery Form Validator

jQuery Form Plugins

6- Toggle FormText plug-in

jQuery Form Plugins

7- jQuery Field Plug-in

jQuery Form Plugins

8- In-Field Labels jQuery Plugin

jQuery Form Plugins

9- jQuery Comment Preview

jQuery Form Plugins

10- Input Fields with Images

jQuery Form Plugins

11- Pretty Comments

jQuery Form Plugins

12- jQuery Highlight Plugin

jQuery Form Plugins

13- Select Multiple Form Fields

jQuery Form Plugins

14- Password Masking jquery plugin

jQuery Form Plugins

15- jTip

jQuery Form Plugins

16- Password Strength Indicator and Generator

jQuery Form Plugins

17- Live validation

jQuery Form Plugins

18- jQuery Autosave

jQuery Form Plugins

19- jQuery Dropdown Checkbox

jQuery Form Plugins

20- jQuery Ajax Form Builder

jQuery Form Plugins

21- Perfect signin dropdown box likes Twitter with jQuery

jQuery Form Plugins

22- Checking username availability with ajax using jQuery

jQuery Form Plugins

23- jqTransform

jQuery Form Plugins

24- Magicpreview plug-in

jQuery Form Plugins

25- A Fancy AJAX Contact Form

jQuery Form Plugins

26- AJAX Upload

jQuery Form Plugins

27- Current Field Highlighting in Forms

jQuery Form Plugins

[转载]c#事件,委托机制 这个例子非常好哦.提供源码

mikel阅读(904)

[转载]c#事件,委托机制 这个例子非常好哦.提供源码,.一下子就了解了,还推荐去看 皮特的故事 要学委托 事件的话, – shenqiboy – 博客园.

事件(event)是一个非常重要的概念,我们的程序时刻都 在触发和接收着各种事件:鼠标点击事件,键盘事件,以及处理操作系统的各种事件。所谓事件就是由某个对象发出的消息。比如用户按下了某个按钮,某个文件发 生了改变,socket上有数据到达。触发事件的对象称作发送者(sender),捕获事件并且做出响应的对象称作接收者(receiver),一个事件 可以存在多个接受者。

在异步机制中,事件是线程之间进行通信的一个非常常用的方式。比如:用户在界面上按下一个按钮,执行某项耗时的任务。程 序此时启动一个线程来处理这个任务,用户界面上显示一个进度条指示用户任务执行的状态。这个功能就可以使用事件来进行处理。可以将处理任务的类作为消息的 发送者,任务开始时,发出“TaskStart”事件,任务进行中的不同时刻发出“TaskDoing”事件,并且携带参数说明任务进行的比例,任务结束 的时候发出“TaskDone”事件,在画面中接收并且处理这些事件。这样实现了功能,并且界面和后台执行任务的模块耦合程度也是最低的。

具体说C#语言,事件的实现依赖于“代理”(delegate)的概念,先了解一下代理。

代理(delegate)

delegate是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。与其它的类不同,delegate 类能够拥有一个签名(signature),并且它只能持有与它的签名相匹配的方法的引用。它所实现的功能与C/C++中的函数指针十分相似。它允许你传 递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m。但与函数指针相比,delegate有许多函数指针不具备的优点。首先,函数 指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。在引用非静态成员函数时,delegate不但保存了对此函数 入口指针的引用,而且还保存了调用此函数的类实例的引用。其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对 象。也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。

实现一个delegate是很简单的,通过以下3个步骤即可实现一个delegate:

1.  声明一个delegate对象,它应当与你想要传递的方法具有相同的参数和返回值类型。

2.  创建delegate对象,并将你想要传递的函数作为参数传入。

3.  在要实现异步调用的地方,通过上一步创建的对象来调用方法。

下面是一个简单的例子:

public class MyDelegateTest

{

// 步骤1,声明delegate对象

public delegate void MyDelegate(string name);

// 这是我们欲传递的方法,它与MyDelegate具有相同的参数和返回值类型

public static void MyDelegateFunc(string name)

{

Console.WriteLine(“Hello, {0}”, name);

}

public static void Main ()

{

// 步骤2,创建delegate对象

MyDelegate md = new MyDelegate(MyDelegateTest.MyDelegateFunc);

// 步骤3,调用delegate

md(“sam1111”);

}

}

输出结果是:Hello, sam1111

下面我们来看看事件是如何处理的:

事件(event)

C#中的事件处理实际上是一种具有特殊签名的delegate,象下面这个样子:

public delegate void MyEventHandler(object sender, MyEventArgs e);

其中的两个参数,sender代表事件发送者,e是事件参数类。MyEventArgs类用来包含与事件相关的数据,所 有的事件参数类都必须从System.EventArgs类派生。当然,如果你的事件不含特别的参数,那么可以直接用System.EventArgs类 作为参数。

结合delegate的实现,我们可以将自定义事件的实现归结为以下几步:

1:定义delegate对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。

2:定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略。

3:定义事件处理方法,它应当与delegate对象具有相同的参数和返回值类型。

4:用event关键字定义事件对象,它同时也是一个delegate对象。

5:用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。

6:在需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。名字是可以是OnEventName。

7:在适当的地方调用事件触发方法触发事件。

下面是一个例子,例子模仿容器和控件的模式,由控件触发一个事件,在容器中捕捉并且进行处理。

事件的触发者:

/// <summary>

/// 事件的触发者

/// </summary>

public class Control

{

public delegate void SomeHandler(object sender, System.EventArgs e);

/**

* 可以采用系统提供的System.EventHandler, 这里为了说明情况使用了自己定义的delegate

* 如果需要在事件的参数中使用自己定义的类型,也要自己定义delegate

*/

//public event System.EventHandler SomeEvent;

public event SomeHandler SomeEvent;

public Control()

{

//这里使用的delegate必须与事件中声名的一致

//this.SomeEvent += new System.EventHandler(this.Control_SomeEvent);

this.SomeEvent += new SomeHandler(this.ProcessSomeEvent);

}

public void RaiseSomeEvent()

{

EventArgs e = new EventArgs();

Console.Write(“Please input ‘a’:”);

string s = Console.ReadLine();

//在用户输入一个小a的情况下触发事件,否则不触发

if (s == “a”)

{

SomeEvent(this, e);

}

}

//事件的触发者自己对事件进行处理,这个方法的参数必须和代理中声名的一致

private void ProcessSomeEvent(object sender, EventArgs e)

{

Console.WriteLine(“hello”);

}

}

事件的接收者:

/// <summary>

/// 事件的接收和处理者

/// </summary>

class Container

{

private Control ctrl = new Control();

public Container()

{

//这里使用的delegate必须与事件中声名的一致

//ctrl.SomeEvent += new EventHandler(this.OnSomeEvent);

ctrl.SomeEvent += new Control.SomeHandler(this.ResponseSomeEvent);

ctrl.RaiseSomeEvent();

}

public static void Main()

{

Container pane = new Container();

//这个readline是暂停程序用的,否则画面会一闪而过什么也看不见

Console.ReadLine();

}

//这是事件的接受者对事件的响应

private void ResponseSomeEvent(object sender, EventArgs e)

{

Console.WriteLine(“Some event occur!”);

}

}

程序运行的结果如下:

please input ‘a’:a

hello

Some event occur!

事件的应用

例如有下面的需求需要实现:程序主画面中弹出一个子窗口。此时主画面仍然可以接收用户的操作(子窗口是非模态的)。子窗口上进行某些操作,根据操作的结果要在主画面上显示不同的数据。我发现一些程序员这样实现这个功能:

主画面弹出子窗口后,将自己的指针交给子画面,然后在子画面中使用这个指针,调用主画面提供的方法,改变主画面上的数据 显示。这样虽然可以达到目的,但是各个模块之间产生了很强的耦合。一般说来模块之间的调用应该是单方向的:模块A调用了模块B,模块B就不应该反向调用 A,否则就破坏了程序的层次,加强了耦合程度,也使得功能的改变和追加变得很困难。

这时正确的做法应该是在子窗口的操作过程中发出各种事件,而由主窗口捕捉这些事件进行处理,各个模块专心的做自己的事情,不需要过问其他模块的事情。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
class Program
{

public Program()
{
//这里使用的delegate必须与事件中声名的一致
//ctrl.SomeEvent += new EventHandler(this.OnSomeEvent);
ctrl.SomeEvent += new Control.SomeHandler(this.ResponseSomeEvent);
ctrl.RaiseSomeEvent();

}
private Control ctrl = new Control();
static void Main(string[] args)
{
Program pr = new Program();
Console.ReadLine();
}
//这是事件的接受者对事件的响应
private void ResponseSomeEvent(object sender, EventArgs e)
{
Console.WriteLine(“Some event occur!”);
}

}

/// <summary>
///事件触发者
/// </summary>
public class Control
{
public delegate void SomeHandler(object sender, System.EventArgs e);

/**
* 可以采用系统提供的System.EventHandler, 这里为了说明情况使用了自己定义的delegate
* 如果需要在事件的参数中使用自己定义的类型,也要自己定义delegate
*/
//public event System.EventHandler SomeEvent;
public event SomeHandler SomeEvent;
public Control()
{
//这里使用的delegate必须与事件中声名的一致
//this.SomeEvent += new System.EventHandler(this.Control_SomeEvent);
this.SomeEvent += new SomeHandler(this.ProcessSomeEvent);
}

public void RaiseSomeEvent()
{
EventArgs e = new EventArgs();
Console.Write(“Please input ‘a’:”);
string s = Console.ReadLine();

//在用户输入一个小a的情况下触发事件,否则不触发
if (s == “a”)
{
SomeEvent(this, e);
}
}

//事件的触发者自己对事件进行处理,这个方法的参数必须和代理中声名的一致
private void ProcessSomeEvent(object sender, EventArgs e)
{
Console.WriteLine(“hello”);
}
}

}

[转载]c#事件,委托机制 这个例子非常好哦.提供源码

mikel阅读(856)

[转载]c#事件,委托机制 这个例子非常好哦.提供源码,.一下子就了解了,还推荐去看 皮特的故事 要学委托 事件的话, – shenqiboy – 博客园.

事件(event)是一个非常重要的概念,我们的程序时刻都 在触发和接收着各种事件:鼠标点击事件,键盘事件,以及处理操作系统的各种事件。所谓事件就是由某个对象发出的消息。比如用户按下了某个按钮,某个文件发 生了改变,socket上有数据到达。触发事件的对象称作发送者(sender),捕获事件并且做出响应的对象称作接收者(receiver),一个事件 可以存在多个接受者。

在异步机制中,事件是线程之间进行通信的一个非常常用的方式。比如:用户在界面上按下一个按钮,执行某项耗时的任务。程 序此时启动一个线程来处理这个任务,用户界面上显示一个进度条指示用户任务执行的状态。这个功能就可以使用事件来进行处理。可以将处理任务的类作为消息的 发送者,任务开始时,发出“TaskStart”事件,任务进行中的不同时刻发出“TaskDoing”事件,并且携带参数说明任务进行的比例,任务结束 的时候发出“TaskDone”事件,在画面中接收并且处理这些事件。这样实现了功能,并且界面和后台执行任务的模块耦合程度也是最低的。

具体说C#语言,事件的实现依赖于“代理”(delegate)的概念,先了解一下代理。

代理(delegate)

delegate是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。与其它的类不同,delegate 类能够拥有一个签名(signature),并且它只能持有与它的签名相匹配的方法的引用。它所实现的功能与C/C++中的函数指针十分相似。它允许你传 递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m。但与函数指针相比,delegate有许多函数指针不具备的优点。首先,函数 指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。在引用非静态成员函数时,delegate不但保存了对此函数 入口指针的引用,而且还保存了调用此函数的类实例的引用。其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对 象。也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。

实现一个delegate是很简单的,通过以下3个步骤即可实现一个delegate:

1.  声明一个delegate对象,它应当与你想要传递的方法具有相同的参数和返回值类型。

2.  创建delegate对象,并将你想要传递的函数作为参数传入。

3.  在要实现异步调用的地方,通过上一步创建的对象来调用方法。

下面是一个简单的例子:

public class MyDelegateTest

{

// 步骤1,声明delegate对象

public delegate void MyDelegate(string name);

// 这是我们欲传递的方法,它与MyDelegate具有相同的参数和返回值类型

public static void MyDelegateFunc(string name)

{

Console.WriteLine(“Hello, {0}”, name);

}

public static void Main ()

{

// 步骤2,创建delegate对象

MyDelegate md = new MyDelegate(MyDelegateTest.MyDelegateFunc);

// 步骤3,调用delegate

md(“sam1111”);

}

}

输出结果是:Hello, sam1111

下面我们来看看事件是如何处理的:

事件(event)

C#中的事件处理实际上是一种具有特殊签名的delegate,象下面这个样子:

public delegate void MyEventHandler(object sender, MyEventArgs e);

其中的两个参数,sender代表事件发送者,e是事件参数类。MyEventArgs类用来包含与事件相关的数据,所 有的事件参数类都必须从System.EventArgs类派生。当然,如果你的事件不含特别的参数,那么可以直接用System.EventArgs类 作为参数。

结合delegate的实现,我们可以将自定义事件的实现归结为以下几步:

1:定义delegate对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。

2:定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略。

3:定义事件处理方法,它应当与delegate对象具有相同的参数和返回值类型。

4:用event关键字定义事件对象,它同时也是一个delegate对象。

5:用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。

6:在需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。名字是可以是OnEventName。

7:在适当的地方调用事件触发方法触发事件。

下面是一个例子,例子模仿容器和控件的模式,由控件触发一个事件,在容器中捕捉并且进行处理。

事件的触发者:

/// <summary>

/// 事件的触发者

/// </summary>

public class Control

{

public delegate void SomeHandler(object sender, System.EventArgs e);

/**

* 可以采用系统提供的System.EventHandler, 这里为了说明情况使用了自己定义的delegate

* 如果需要在事件的参数中使用自己定义的类型,也要自己定义delegate

*/

//public event System.EventHandler SomeEvent;

public event SomeHandler SomeEvent;

public Control()

{

//这里使用的delegate必须与事件中声名的一致

//this.SomeEvent += new System.EventHandler(this.Control_SomeEvent);

this.SomeEvent += new SomeHandler(this.ProcessSomeEvent);

}

public void RaiseSomeEvent()

{

EventArgs e = new EventArgs();

Console.Write(“Please input ‘a’:”);

string s = Console.ReadLine();

//在用户输入一个小a的情况下触发事件,否则不触发

if (s == “a”)

{

SomeEvent(this, e);

}

}

//事件的触发者自己对事件进行处理,这个方法的参数必须和代理中声名的一致

private void ProcessSomeEvent(object sender, EventArgs e)

{

Console.WriteLine(“hello”);

}

}

事件的接收者:

/// <summary>

/// 事件的接收和处理者

/// </summary>

class Container

{

private Control ctrl = new Control();

public Container()

{

//这里使用的delegate必须与事件中声名的一致

//ctrl.SomeEvent += new EventHandler(this.OnSomeEvent);

ctrl.SomeEvent += new Control.SomeHandler(this.ResponseSomeEvent);

ctrl.RaiseSomeEvent();

}

public static void Main()

{

Container pane = new Container();

//这个readline是暂停程序用的,否则画面会一闪而过什么也看不见

Console.ReadLine();

}

//这是事件的接受者对事件的响应

private void ResponseSomeEvent(object sender, EventArgs e)

{

Console.WriteLine(“Some event occur!”);

}

}

程序运行的结果如下:

please input ‘a’:a

hello

Some event occur!

事件的应用

例如有下面的需求需要实现:程序主画面中弹出一个子窗口。此时主画面仍然可以接收用户的操作(子窗口是非模态的)。子窗口上进行某些操作,根据操作的结果要在主画面上显示不同的数据。我发现一些程序员这样实现这个功能:

主画面弹出子窗口后,将自己的指针交给子画面,然后在子画面中使用这个指针,调用主画面提供的方法,改变主画面上的数据 显示。这样虽然可以达到目的,但是各个模块之间产生了很强的耦合。一般说来模块之间的调用应该是单方向的:模块A调用了模块B,模块B就不应该反向调用 A,否则就破坏了程序的层次,加强了耦合程度,也使得功能的改变和追加变得很困难。

这时正确的做法应该是在子窗口的操作过程中发出各种事件,而由主窗口捕捉这些事件进行处理,各个模块专心的做自己的事情,不需要过问其他模块的事情。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
class Program
{

public Program()
{
//这里使用的delegate必须与事件中声名的一致
//ctrl.SomeEvent += new EventHandler(this.OnSomeEvent);
ctrl.SomeEvent += new Control.SomeHandler(this.ResponseSomeEvent);
ctrl.RaiseSomeEvent();

}
private Control ctrl = new Control();
static void Main(string[] args)
{
Program pr = new Program();
Console.ReadLine();
}
//这是事件的接受者对事件的响应
private void ResponseSomeEvent(object sender, EventArgs e)
{
Console.WriteLine(“Some event occur!”);
}

}

/// <summary>
///事件触发者
/// </summary>
public class Control
{
public delegate void SomeHandler(object sender, System.EventArgs e);

/**
* 可以采用系统提供的System.EventHandler, 这里为了说明情况使用了自己定义的delegate
* 如果需要在事件的参数中使用自己定义的类型,也要自己定义delegate
*/
//public event System.EventHandler SomeEvent;
public event SomeHandler SomeEvent;
public Control()
{
//这里使用的delegate必须与事件中声名的一致
//this.SomeEvent += new System.EventHandler(this.Control_SomeEvent);
this.SomeEvent += new SomeHandler(this.ProcessSomeEvent);
}

public void RaiseSomeEvent()
{
EventArgs e = new EventArgs();
Console.Write(“Please input ‘a’:”);
string s = Console.ReadLine();

//在用户输入一个小a的情况下触发事件,否则不触发
if (s == “a”)
{
SomeEvent(this, e);
}
}

//事件的触发者自己对事件进行处理,这个方法的参数必须和代理中声名的一致
private void ProcessSomeEvent(object sender, EventArgs e)
{
Console.WriteLine(“hello”);
}
}

}

[转载]Benchmark Test On Android Devices

mikel阅读(864)

[转载]Benchmark Test On Android Devices – 进则净土,退则凡尘 – 博客园.

一、Android设备上的Benckmark测试概述

同PC相比,在Android设备上的性能测试还没有一个公认的标准。也没有PC上那么多的测试程序集。但我们可以通过一些工具所得到的信息更好的 了解设备的特性,毫无疑问,这也是非常有价值的参考信息。Benchmark Test的关键就在于如何找到这一个标准以及能够提供测试各种性能的功能强大的程序集。
二、目前Android平台上的测试工具收集(以Android手机为例)
1、  Quadrant(Aurora Softworks推出)

针对CPU、内存、I/O输入输出、2D及3D图像的性能,提供了一键式的完整测试,或是根据需要选择其中某些测试项目单独测试。它还提供了内容格式清晰的系统信息查看功能,让你方便的了解非常详尽的手机处理器、内存及系统版本等特性。

该公司网站为:http://www.aurorasoftworks.com
版本一:Quadrant Standard

免费版,2010年8月18日更新最新版本v1.1.2,在Google应用商店有超过3000个评论,综合评分高达4.5星,免费的标准版可以提供所有的测试功能,唯一不便的地方是在测试后需要向极光软件(Aurora Softworks)的服务器提交数据进行分析后,才能显示结果。
版本二:Quadrant Advanced

高级版,2010年8月8日更新最新版本v1.1.1,售价2.99美元,这个版本在测试功能上与免费的标准版完全一样,但在测试结束后会直接显示一个测试结果直方图:

版本三:Quadrant Professional

专业版,2010年8月8日更新最新版本v1.1.1,售价高达24.99美元,主要的差别其实就是“授权”费,允许使用此版本进行测试获得的结果可以用于商业用途,包括将测试结果出版在商业媒体上。
2、  Benchmark 1.03

一个测试系统性能的软件,可以测试Android系统的2D图形、cpu、内存和文件系统等方面的性能.测试项目非常多,且测试速度快,可以给出一个参考 分数供用户参考使用。
3、  Linpack for android

用于测试系统整体性能,偏向单任务下的表现。

官方网址:http://www.greenecomputing.com/apps/linpack/
4、  Sunspider benchmark

测试webkit内核浏览器的JavaScript表现。

测试地址:

http://www.webkit.org/perf/sunspider-0.9.1/sunspider-0.9.1/driver.html

测试方式:直接在浏览器上打开该网页即可,测试结果会直接显示在网页上。
5、  Google V8 benchmark

Google V8 Benchmark测试是Google放出的JavaScript测试集。V8 Benchmark Suite会载入JavaScript代码,对系统的内核、加密、解密、渲染等进行速度测试。

测试地址:http://v8.googlecode.com/svn/data/benchmarks/v6/run.html

测试方式:直接在浏览器上打开该网页即可,测试结果会直接显示在网页上。

说明:测试得分越高说明浏览器的网页显示速度越快。
6、  Dromaeo benchmark

由Mozilla基金会发布,它提供了浏览器表现的第三方视角。

测试网址:http://dromaeo.com/

测试方式:在浏览器上打开该网页,可以选择自己想做的测试。

[转载]所见即所得的Excel报表生成—获取Html table结构

mikel阅读(1225)

[转载]所见即所得的Excel报表生成(一)——获取Html table结构 – iSun – 博客园.

最近做一个小的报表系统,功能本身没什么。最后客户要求一个打印功能,所谓打印,就是按照页面上报表的样子,一模一样的为其生成Excel文件。

再也不想为了构造结构一样的Excel表格而再次考虑繁琐数据逻辑了!于是乎冒出了这样的一个想法:我要是能获得页面上的报表table,那么只要分析其结构,不就可以构造出相应的Excel表格来吗?

思来想去,觉得这应该是一条可以走的通的路,于是便着手寻找实现的办法。终于,发现WebRequest和WebResponse,其分别代表一个Web请求和服务器基于请求的回应,而这个回应包含服务器返回的数据流,从数据流便可获取想要的报表table。

整个Excel生成功能的思路如下图所示:

涉及WebRequest、WebResponse的核心代码如下:

WebRequest wr = WebRequest.Create(psUrl);
HttpWebRequest oRequest = wr as HttpWebRequest;
if (null == oRequest) return null;

HttpWebResponse oResponse  ;= (HttpWebResponse) oRequest.GetResponse();
string sResponseContent = string.Empty;
if (oResponse.StatusCode == HttpStatusCode.OK)
{
using (StreamReader sr = new StreamReader(oResponse.GetResponseStream(),Encoding.UTF8))
{
sResponseContent = sr.ReadToEnd();      //sResponseContent,保存了服务器返回的数据流字符串内容
sr.Close();
}
}
oResponse.Close();

特别需要说明的是,在从数据流向字符串转化的过程中,其内会残留许多\r\t之类的转义字符,我们需要将其剔除。

1 sResponseContent = Regex.Replace (sResponseContent, [\n\r\t] , “”);

本篇至此,下篇将着重介绍table到Excel cell的转化过程。

附示例代码:ExcelGenerator.rar

Html table的结构我们大家都很熟悉,那么在另一端如何构造一个结构,让Excel可以很好的接受和处理呢?

直观的看,一个完整Excel的内容是由位于各个单元格(Cell)中的内容组合而成的。而每个单元格(Cell)都有相应X、Y坐标来标示其 位置。也就是说,一个Excel文件实质上就是许多Cell构成的集合,每个Cell用坐标属性确定位置,用内容属性存储内容。

基于此,我设计了最基本的Cell结构:
◆ X坐标
◆ Y坐标
◆ 合并列情况
◆ 合并行情况
◆ 内容

构成Excel的最基本的结构已经确定,下一步摆在我们面前的就是将html table转化为Excel Cell集合。

Html table中的每个td节点对应一个Excel单元格,其内容不必说,行、列的合并情况也自可由td的rowspan、colspan属性得出,转化的关键点就在于由table的tr td结构定位Excel单元格位置,即X、Y坐标。

Y坐标容易确定,即td所在tr的行数。至于一td的X坐标,其要受到两方面因素的影响:与该td同处一tr,但位于其之前(反映在表格视觉上即其左侧td)td的占位情况和该td所在tr的之前tr中某些td的跨行情况。

基于此种考虑,定位td的X坐标需经过两个过程的推导:用于处理左侧td占位影响的横向推导(Horizontal Deduction)和处理之前行跨行td影响的纵向推导(Vertical Deduction)。

以下图所示table为例,展示两次推导过程。

横向推导(Horizontal Deduction)
一次横向推导(Horizontal Deduction)限定在一tr范围内。整个过程基于递归的原理,递归模型如下:

核心代码为:

1 private int HorizontalDeduction(HtmlNode phnTd)
2 {
3 HtmlNode hnPreviousSibling = phnTd.PreviousSibling;
4 while (hnPreviousSibling != null && hnPreviousSibling.Name != phnTd.Name)
5 {
6 hnPreviousSibling = hnPreviousSibling.PreviousSibling;
7 }
8
9 if (hnPreviousSibling != null)
10 {
11 int nColSpan = hnPreviousSibling.GetAttributeValue(colspan, 1);
12 return HorizontalDeduction(hnPreviousSibling) + nColSpan;
13 }
14
15 return 0;
16 }

经过横向推导,各td的X、Y坐标如下图所示:

纵向推导(Vertical Deduction)
一次纵向推导的过程可以描述为(当前推导td用A表示):

找到A之前的行tr中与A具有相同X坐标的td节点B

if (B.rowspan>(A.Y-B.Y))
{
X+=B.colspan,即A的X坐标向后推B.colspan的位置

同时,与A同处一tr但在其后边的td节点均应向后推B.colspan个位移
}

对td节点A反复执行这样的一个过程,直到确定A无需再次移动。

纵向推导核心代码为:

1 bool bActedPush = false;
2
3 do
4 {
5 int nComparedItemIndex = 1;
6 for (int j = i 1; j >= 0; j)
7 {
8 if (plstCells[j]._nStartX == oCurrentCell._nStartX)
9 {
10 nComparedItemIndex = j;
11 break;
12 }
13 }
14
15 if (nComparedItemIndex >= 0)
16 {
17 if (plstCells[nComparedItemIndex]._nRowSpan > (oCurrentCell._nStartY plstCells[nComparedItemIndex]._nStartY))
18 {
19 oCurrentCell._nStartX += plstCells[nComparedItemIndex]._nColSpan;
20
21 bActedPush = true;
22
23 for (int k = i + 1; k < plstCells.Count; k++)
24 {
25 if (plstCells[k]._nStartY == oCurrentCell._nStartY)
26 {
27 plstCells[k]._nStartX += plstCells[nComparedItemIndex]._nColSpan;
28 }
29 }
30 }
31 else
32 {
33 bActedPush = false;
34 }
35 }
36 else
37 {
38 bActedPush = false;
39 }
40 }
41 while (bActedPush);

以示例table中的four td为例,其经过纵向推导过程后的坐标位置情况如下图:

关于示例代码的几点说明:
1、 在示例代码中,我通过一个Config文件对生成的Excel的文件名、其内报表的内容和位置做了一些控制。基本内容如下:

<ExcelDocument>
<BaseInfo>
<FileName>Sample Excel File</FileName> <!–生成Excel的文件名–>
<SheetCount>1</SheetCount> <!–Excel中sheet的数量–>
</BaseInfo>
<Tables>
<ExcelTable>
<TableName>示例表一</TableName> <!–报表名称–>
<WhichSheet>1</WhichSheet> <!–所在sheet的序号–>
<StartX>2</StartX> <!–左上角X坐标–>
<StartY>2</StartY> <!–左上角Y坐标–>
<Source>Sample_Page_1.aspx</Source> <!–table所在页面–>
</ExcelTable>
</Tables>
</ExcelDocument>

2、 在解析html的过程中,我使用了HtmlAgilityPack解析工具。感兴趣的朋友可以研究一下。地址是这里:http://htmlagilitypack.codeplex.com/
3、 HtmlAgilityPack解析html的过程中,其将html标签之间的空隙也会看成是一个节点,为内容为空字符串的文本节点,这点大家应注意。
4、 该示例代码基本上是一个完整的功能,并且和系统中其他模块耦合度很小。有类似需求的朋友可以拿来直接用。

附示例代码:ExcelGenerator.rar