原文地址:http://tech.it168.com/a2010/0318/862/000000862631.shtml

作者:IT168 陈良乔

  【IT168 专稿】书接上回。在前一篇“Visual Studio 2010对并行计算的支持”文章中,我们介绍了如何利用Parallel.For和Parallel.ForEach函数来并行化for循环和foreach循环。实际上,Parallel.For和Parallel.ForEach函数主要是针对“并行数据”的并行化操作,所谓并行数据,就是整个数据集中数据单元是相互独立的,可以同时进行处理。在实际开发中,我们遇到的可以并行处理的不仅包括“并行数据”,还包括可以同时进行的“并行逻辑”。所谓“并行逻辑”,就是相互独立,可以同时执行的多个任务。比如,程序员陈良乔每天早上要做两件事情:烧水洗脸和锻炼身体。这两件事情就是相互独立可以并行的,也就是说他在烧水的时候可以同时锻炼身体。在以前的单核时代,CPU在同一时间只能完成一件事情,那么陈良乔只能先烧水后锻炼,或者是先锻炼后烧水,这导致他上班总是迟到。进入多核时代,CPU可以在同一时间完成多件事情了,借助.Net Framework 4.0中的Parallel类,我们可以方便地处理“并行逻辑”。现在,程序员陈良乔可以一边锻炼一边烧水,再也没有迟到过了。他逢人便说:“Parallel真是个好东西!自从用了它,我腰也不酸了,背也不疼了,编程更有劲儿了。。。”

使用Parallel.Invoke处理并行逻辑

  跟Parallel.For函数相似,Parallel.Invoke也是Parallel类的一个静态函数,它可以接受一个Action[]类型的对象作为参数,这个对象,就是我们要执行的任务。系统会根据代码运行的硬件环境,主要是CPU运算核心的个数,自动地进行线程的创建和分配。这有些类似于我们所熟悉的多线程开发,通过为每个线程指定一个线程函数而让多个任务同时进行,只是Parallel.Invoke函数简化了线程的创建和分配等繁琐的动作,我们只需要提供核心的线程函数就可以了。下面我们来看一个实际的例子。在上文中,我们介绍了程序员陈良乔起床的例子,在以前的单核时代,他起床大约是这个样子的:

      // 串行式起床
private static void GetUp()
{
Start("GetUp");
// 先烧水
boil();
// 后锻炼
exercise();
End("GetUp");
}


// 锻炼
private static void exercise()
{
Console.WriteLine("Exercise");
Thread.Sleep(2000);
Console.WriteLine("Finish Exercise");
}

// 烧水
private static void boil()
{
Console.WriteLine("Boil");
Thread.Sleep(3000);
Console.WriteLine("Finish Boil");
}

在单核时代,CPU在同一时间只能做一件事情,所以他只能先烧水,后锻炼,这样显然会耽误时间。一天,他又因为这事而迟到了,老板骂道,“你是猪啊,你不会用Parallel.Invoke一边烧水一边锻炼啊?”于是,有了下面的并行式起床:
// 并行式起床
private static void ParallelGetUp()
{
Start("ParallelGetUp");
// 在烧水的同时,锻炼身体
var steps = new Action[] { () => boil(), () => exercise() };
Parallel.Invoke(steps);
End("ParallelGetUp");
}

    通过Parallel.Invoke函数,我们将一些相互独立的任务同时执行,实现了“并行逻辑”,也大大地提高了应用程序的性能和效率。从下面的截图中,我们可以明显地看出两种方式的差别。串行方式所耗费的时间,是两个步骤的时间总和,而并行方式所耗费的时间,大约是单个任务的耗时最长的哪一个。

  图1 串行和并行的执行情况

 

 对Parallel.Invoke进行控制

  Parallel.Invoke提供了一个重载版本,它可以接受一个ParallelOptions对象作为参数,对Parallel.Invoke的执行进行控制。通过这个对象,我们可以控制并行的最大线程数,各个任务是否取消执行等等。例如,在一个智能化的家中,系统会判断主人是否离开房间,如果主人离开了房间,则自动关闭屋子里的各种电器。利用Parallel.Invoke我们可以实现如下:

public static void PInvokeCancel()
{
// 创建取消对象
CancellationTokenSource cts = new CancellationTokenSource();
// 利用取消对象,创建ParallelOptions
ParallelOptions pOption = new ParallelOptions() { CancellationToken = cts.Token };
// 设置最大线程数
pOption.MaxDegreeOfParallelism = 2;

// 创建一个守护监视进程
Task.Factory.StartNew(() =>
{
Console.WriteLine("Cancellation in 5 sec.");
Thread.Sleep(5000);
// 取消,结束任务的执行
cts.Cancel();
Console.WriteLine("Canceled requested");
});

try
{
// 以ParallelOptions作为参数,
// 调用Parallel.Invoke
Parallel.Invoke(pOption, () => ShutdownLights(pOption.CancellationToken),
() => ShutdownComputer(pOption.CancellationToken));

//输出执行结果
Console.WriteLine("Lights and computer are tuned off.");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

private static void ShutdownLights(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
Console.WriteLine("Light is on. " );
Thread.Sleep(1000);
}

}
private static void ShutdownComputer(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
Console.WriteLine("Computer is on." );
Thread.Sleep(1000);
}
}


除了这种方式之外,ParallelOptions更多地应用在取消任务队列中还未来得及执行的任务。当我们限制了最大并发线程数的时候,如果需要通过Parallel.Invoke执行的任务较多,则有可能部分任务在队列中排队而得不到及时的执行,如果到了一定的条件这些任务还没有执行,我们可能取消这些任务。一个恰当的现实生活中的例子就是火车站买票。火车站买票的人很多,但是售票的窗口有限,当到了下班时间后,窗口就不再售票了,也就是剩下的售票任务需要取消掉。我们可以用下面的代码来模拟这样一个场景:

public static void PInvokeCancel()

  {

  // 创建取消对象

  CancellationTokenSource cts = new CancellationTokenSource();

  // 利用取消对象,创建ParallelOptions

  ParallelOptions pOption = new ParallelOptions() { CancellationToken = cts.Token };

  // 设置最大线程数,也就相当于20个售票窗口

  pOption.MaxDegreeOfParallelism = 20;

  // 创建一个守护监视进程

  
// 当到下班时间后就取消剩下的售票活动

  Task.Factory.StartNew(() =>

  {

  Console.WriteLine("Cancellation in 5 sec.");

  Thread.Sleep(5000);

  // 取消,结束任务的执行

  cts.Cancel();

  Console.WriteLine("Canceled requested");

  });

  try

  {

  // 创建售票活动

  Action[] CustomerServices = CreateCustomerService(1000);

  // 以ParallelOptions作为参数,

  
// 调用Parallel.Invoke

  Parallel.Invoke(pOption, CustomerServices);

  }

  catch (Exception e)

  {

  // 当任务取消后,抛出一个异常

  Console.WriteLine(e.Message);

  }

  }

  // 创建售票的活动

  static Action[] CreateCustomerService(int n)

  {

  Action[] result = new Action[n];

  for (int i = 0; i < n; i++)

  {

  result[i] = () =>

  {

  Console.WriteLine("Customer Service {0}", Task.CurrentId);

  // 模拟售票需要的时间

  Thread.Sleep(2000);

  };

  }

  return result;

  }

 

并行任务之间的同步

  有时候我们在处理并行任务的时候,各个任务之间需要同步,也就是同时执行的并行任务,需要在共同到达某一个状态的后再一共继续执行。我们可以举一个现实生活中的例子。陈良乔,贾玮和单春晖是好朋友,他们相约到电影院看《建国大业》。他们三个住在不同的地方,为了能一起买票进电影院,他们约好先在电影院门口的KFC会合,然后再一起进电影院。这其中就涉及到一个同步的问题:他们需要先在KFC会合。他们是从家里分别到KFC的,但是需要在KFC进行同步,等到三个人都到齐后在完成后后继的动作,进电影院看电影。

  为了完成并行任务之间的同步,.NET Framework中提供了一个类Barrier。顾名思义,Barrier就像一个关卡或者是剪票口一样,通过Barrier类,我们可以管理并行任务的执行,完成他们之间的同步。Barrier类的使用非常简单,我们只需要在主线程中声明一个Barrier对象,同时指明需要同步的任务数。然后,在需要进行同步的地方调用Barrier类的SignalAndWait函数就可以了。 当一个并行任务到达SignalAndWait后,它会暂停执行,等待所有并行任务都到达同步点之后再继续往下执行。下面我们以一个实际的例子,来看看如何利用Barrier类完成看电影的同步问题。

using System;

  using System.Collections.Generic;

  using System.Linq;

  using System.Text;

  using System.Threading;

  using System.Threading.Tasks;

  namespace ParallelBarrier

  {

  class Program

  {

  // 用于同步的Barrier对象

  static Barrier sync;

  static void Main(string[] args)

  {

  // 创建Barrier对象,这里我们需要同步

  
// 任务有三个

  sync = new Barrier(3);

  // 开始执行并行任务

  var steps = new Action[] { () => gotothecinema("陈良乔", TimeSpan.FromSeconds(5) ),

  () => gotothecinema("贾玮", TimeSpan.FromSeconds(2) ),

  () => gotothecinema("单春晖", TimeSpan.FromSeconds(4) )};

  Parallel.Invoke(steps);

  Console.ReadKey();

  }

  // 任务

  static void gotothecinema(string strName, TimeSpan timeToKFC )

  {

  Console.WriteLine("[{0}] 从家里出发。", strName);

  // 从家里到KFC

  Thread.Sleep(timeToKFC);

  Console.WriteLine("[{0}] 到达KFC。", strName);

  // 等待其他人到达

  sync.SignalAndWait();

  // 同步后,进行后继动作

  Console.WriteLine("[{0}] 买票进电影院。", strName);

  }

  }

  }

 

  在这段代码中,我们首先创建了Barrier对象,因为在这里需要同步的任务有三个,所以创建Barrier对象时是的参数是3。然后就是使用Parallel.Invoke执行并行任务。我们在并行任务gotothecinema中设置了一个同步点,在这里我们调用Barrier对象的SignalAndWait函数,它表示当前任务已经到达同步点并同时等待其他任务到达同步点。当所有任务都到达同步点之后,再继续往下执行。运行上面的程序,我们可以获得这样的输出:

  图2 使用Barrier进行同步

 

更复杂的任务之间的同步

  我们在使用Barrier进行并行任务之间的同步时,有这样一个缺陷,我们需要预先知道所有需要同步的并行任务的数目,如果这个数目是随机的,就无法使用Barrier进行任务之间的同步了。并行任务数目不定这种情况很常见。我们还是来看上文中看电影的例子,每场进电影院看电影的观众数目是不固定的,那么退场的观众也是不固定的,甚至还有中途退场的。当所有观众都退场后,我们需要打扫电影院的卫生。这里需要的同步的就是所有观众都退场。针对这种数目不定的多个并行任务,.NET Framework提供了CountdownEvent这个类来进行任务之间的同步。

  就像它的名字一样,CountdownEvent基于这样一个简单的规则:当有新的需要同步的任务产生时,就调用AddCount增加它的计数,当有任务到达同步点是,就调用Signal函数减小它的计数,当CountdownEvent的计数为零时,就表示所有需要同步的任务已经完成,可以开始下一步任务了。下面我们利用CountdownEvent来模拟一下观众进场立场的情景。

 using System;

  using System.Collections.Generic;

  using System.Linq;

  using System.Text;

  using System.Threading;

  using System.Threading.Tasks;

  namespace CountdownEventDemo

  {

  // 观众类,用来表示一位观众

  class Customer

  {

  public Customer(int nID)

  {

  m_nID = nID;

  }

  // 观众的ID

  public int m_nID;

  }

  class Program

  {

  static void Main(string[] args)

  {

  // 创建CountdownEvent同步对象

  using (var countdown = new CountdownEvent(1))

  {

  // 产生一个随机数,表示观众的数目

  Random countRandom = new Random(DateTime.Now.Millisecond);

  int nCount = countRandom.Next(10);

  // 构造每一位观众看电影的任务

  Action[] seeafilm = new Action[ nCount ];

  for (int i = 0; i < nCount; i++)

  {

  // 构造Customer对象,表示观众

  Customer currentCustomer = new Customer( i+1 );

  seeafilm[i] = () =>

  {

  // 观众进场

  countdown.AddCount();

  Console.WriteLine("观众 {0} 进场。", currentCustomer.m_nID);

  // 模拟看电影的时间

  Thread.Sleep(countRandom.Next(3000,6000));

  // 观众退场

  countdown.Signal();

  Console.WriteLine("观众 {0} 退场。", currentCustomer.m_nID);

  };

  }

  //并行执行任务

  Parallel.Invoke( seeafilm );

  // 在此同步,最后CountdownEvent的计数变为零

  countdown.Signal();

  countdown.Wait();

  }

  Console.WriteLine("所有观众退场,开始打扫卫生。");

  Console.ReadKey();

  }

    在这段代码中,我们使用CountdownEvent进行随机个数任务之间的同步。最后,我们可以得到这样的输出。


 

  图3 使用CountdownEvent进行同步

  通过Parallel.Invoke函数,我们可以轻松地将相互独立的任务并行执行,同时通过Barrier和CountdownEvent类进行任务之间的同步。这种并行计算的开发方式,比以前那种基于线程的并行计算开发方式简便很多,解放了程序员的脑袋,让他们可以把更多的脑力放到业务逻辑问题的解决之上。

  使用Parallel类,多快好省地开发并行计算应用程序。









posted @ 2012-03-22 13:47 大洋 阅读(5) 评论(0) 编辑

NPOI

语言: C#

官方网站: http://npoi.codeplex.com/

描述: NPOI是POI的.NET移植版本,目前稳定版本中仅支持对xls文件(Excel 97-2003)文件格式的读写。目前的NPOI版本主要是基于POI 3.2的,目前正在将部分POI 3.5的bug修整放入新版本中。

相关文件格式: xls, OLE2格式

工具:POIFS Browser

代码协议: Apache 2.0

Office Binary Translator to Open XML

语言: c#

官方网站: http://b2xtranslator.sourceforge.net/

描述: 这个项目的主要目的是把Office 97-2003的文件格式转换成Open XML格式,为其他软件提供参考。

相关工具: BiffView++ (下载zip文件)

相关文件格式:doc/docx, ppt/pptx, xls/xlsx

代码协议:BSD

Open XML Format SDK

官方网站:http://www.microsoft.com/downloads/details.aspx?FamilyID=c6e744e5-36e9-45f5-8d8c-331df206e0d0&DisplayLang=en

描述:微软官方提供的Open XML读写库,目前最新版是2.0 CTP版本。

相关文件格式:Open XML Formats (docx, xlsx, pptx等)

ZeraldotNet

语言: c#

官方网站: http://www.codeplex.com/ZeraldotNet

描述: Zeral .Net类库实现了BitTorrent协议、eMule协议、Overnet协议和Gnutella协议。

相关文件格式: .torrent

代码协议:Ms-CL v1.1

 

 

NAudio

语言: C#

官方网站: http://www.codeplex.com/naudio

描述: NAudio是一套实用的音频类库,目的是加快.NET上音频相关工具的开发。它诞生于2001年,现在已经有了很多功能。

相关文件格式: MP3, OGG, SFZ, MIDI, SoundFont, WAV

代码协议:Ms-PL

 

 

 

DotNetZip

语言: C#

官方网站: http://www.codeplex.com/DotNetZip

描述: DotNetZip是一个小巧、使用简便的zip类库。用VB.NET及C#写的应用程序都能简单方便的创建、阅读和更新zip文件。

相关文件格式: zip

代码协议:Ms-PL

 

 

 

7zSharp

语言: C#

官方网站: http://www.codeplex.com/7zsharp

描述: 7zSharp是基于.NET 2.0的7z LZMA二次开发包,提供了一套便捷的API用于解密和加密7z文件。

相关文件格式: 7z (.7z), ZIP (.zip), GZIP (.gz), BZIP2 (.bz2) and TAR (.tar), RAR (.rar), CAB (.cab), ISO (.iso), ARJ (.arj), LZH (.lzh), CHM (.chm), Z (.Z), CPIO (.cpio), RPM (.rpm), DEB (.deb), NSIS (.nsis)

代码协议:LGPL 2.1

 

iTextSharp

语言: C#

官方网站: http://sourceforge.net/projects/itextsharp/

描述: iText# (iTextSharp)是iText Java开源库的C#移植版本,它可以让你从头开始生成PDF。

相关文件格式: PDF, rtf, bmp, gif, png,tiff, jpeg

相关技术: OpenPGP, OpenSSL, TSP(时间戳协议), x509, ocsp, BigInterger, BCPG

 

 

GifLib

语言: C#

官方网站: http://www.codeplex.com/GifLibhttp://www.cndotnet.org/GifLib

描述: GifLib能够无损读写Gif格式

相关文件格式: gif

相关技术: LZW压缩

代码协议:BSD

.NET DiscUtil

语言:C#

官方网站:http://discutils.codeplex.com/

相关文件格式:ISO, FAT, NTFS, VHD, XVA, VMDK和VDI

描述:可读写ISO文件和虚拟机磁盘文件格式(VHD, VDI, XVA, VMDK等)的库,完全用C#实现,没有P-Invoke。

代码协议:MIT

UOF and OOXML translator

官方网站:http://uof-translator.sourceforge.net/

相关文件格式:UOF, Open XML Formats

描述:这是微软公司、北京大学、LiSoft、清华大学、北京科技大学共同开发的开源项目,目的是实现UOF和OOXML的双向转换。目前最新版本是2.0,貌似正在开发2.1版本。

JPEG Data

环境:.NET 3.5 sp1

官方网站:http://jpegdata.codeplex.com/

相关文件格式:jpeg

描述:从jpeg中获得需要的数据

代码协议:Ms-PL

Mp4 Explorer

官方网站:http://mp4explorer.codeplex.com/

相关文件格式:mp4格式

描述:可用于从MPEG-4 iso文件((ISO/IEC 14496-12, ISO/IEC 14496-14, ISO/IEC 14496-15))中读取各种信息,如mp4头,元数据和其他数据

代码协议:Ms-PL

NetCDF Library for .NET

语言: J#, C# wrapper

官方网站: http://netcdf.codeplex.com/

描述: 这个项目从Java版本的NETCDF Library移植过来的,Java版的库是ESSE项目(环境场景搜索引擎)的一部分,这个项目由地球物理学中心俄罗斯研究中心、国家地球物理学数据中心NOAA、微软研究院以及剑桥参与完成

相关文件格式: netcdf

代码协议: GPL 2.1

WBFS Manager

语言:C#

官方网站: http://wbfsmanager.codeplex.com/

描述: WII游戏机必备的硬盘管理工具。

相关研究领域: WBFS (WII文件系统格式,类似FAT)

代码协议: GPLv2

Duplicate images finder

语言:C#

官方网站: http://duplifinder.codeplex.com/

描述: 这是一个能够帮你找到相似照片的应用,注意是相似,不是相同。

相关研究领域: jpeg格式、图像处理、相似度比较

代码协议: Ms-PL

#SNMP

语言:C#

官方网站: http://sharpsnmplib.codeplex.com/

描述: SNMP协议的.NET实现

相关文件格式: snmp

代码协议: GPL 2.1

zxing (Zebra Crossing)

语言:java (同时提供C#, C++, Ruby, actionscript的封装)

官方网站: http://code.google.com/p/zxing/

描述: 非常专业的1D/2D条形码生成类库,同时支持PC和手机应用,能够在iphone、andriod、rim等系统上使用

相关研究领域: 图像处理,条形码,UPC, Code 39,93,128,QR code, ITF

.NET DiscUtils

语言:C#

官方网站:http://discutils.codeplex.com/

描述: 纯.NET实现的磁盘文件读写库,支持各种虚拟机硬盘文件和ISO文件格式,支持刻录

相关研究领域: 虚拟机磁盘文件(VHD, VDI, XVA, VMDK), ISO, UDF, FAT, NTFS,

代码协议: MIT

GomuIso9660

语言:C#

官方网站: http://gomuiso9660.codeplex.com/

描述: 可用于读取各种光盘文件格式,支持刻录。

相关研究领域: iso, bin, mdf, nrg, img, cdi,光盘刻录

代码协议: Ms-PL

Website Screenshots & Thumbnails Extractor

语言:C#

官方网站: http://screenshotsextractor.codeplex.com/

描述: 网站截图工具,能够实现整页滚动截取,类似Fireshot

相关研究领域: 网页截图、屏幕截图

代码协议: Ms-PL

HtmlAgility

语言:C#

官方网站:http://htmlagilitypack.codeplex.com/

描述:可解析HTML的DOM结构,不依赖任何第三方组件,纯.NET实现

相关研究领域:HTML, XHTML

代码协议:Ms-PL

 

 

转自:http://tonyqus.sinaapp.com/archives/40

 

posted @ 2011-12-21 17:41 大洋 阅读(38) 评论(0) 编辑

.NET 4 和Silverlight 中可以使用以下方法:

public static void Validate(this Entity entity)
{
    // prepare the result
    var validationResults = new List<ValidationResult>();
    // create a validation context
    var validationContext = new ValidationContext(entity, null, null);
    // validate
    Validator.TryValidateObject(entity, validationContext, validationResults);
    // reset the validation errors of the entity
    entity.ValidationErrors.Clear();
    foreach (var error in validationResults)
        entity.ValidationErrors.Add(error);
}

.NET 3.5中的方法:

	public static class EntityValidator
	{
		public static IEnumerable<ErrorField> GetErrors(object instance)
		{
			var t = instance.GetType();
			var typeDescriptor = new AssociatedMetadataTypeTypeDescriptionProvider(t).GetTypeDescriptor(t);

			return from prop in typeDescriptor.GetProperties().Cast<PropertyDescriptor>()
					 from attribute in prop.Attributes.OfType<ValidationAttribute>()
					 where !attribute.IsValid(prop.GetValue(instance))
					 select new ErrorField(prop.Name, attribute.FormatErrorMessage(string.Empty));
		}	
	}

	[Serializable]
	public class ErrorField
	{
		public string ID { get; set; }
		
		public string Msg { get; set; }

		public ErrorField(string id, string msg)
		{
			this.ID = id;
			this.Msg = msg;
		}


	}

posted @ 2011-06-17 17:02 大洋 阅读(106) 评论(0) 编辑

对于值类型的List直接用以下方法就可以复制:

List<T> oldList = new List<T>();
oldList.Add(..);
List<T> newList = new List<T>(oldList);

对于引用类型的List无法用以上方法进行复制,只会复制List中对象的引用,可以用以下扩展方法复制:

static class Extensions
{
        public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
        {
                return listToClone.Select(item => (T)item.Clone()).ToList();
        }
}

当然前题是List中的对象要实现ICloneable接口

 

另一个更保险的方法是:

public static T Clone<T>(T RealObject)
{
    using (Stream objectStream = new MemoryStream())
    {
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(objectStream, RealObject);
            objectStream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(objectStream);
    }
}
posted @ 2011-02-01 09:43 大洋 阅读(203) 评论(0) 编辑

for /d /r . %d in (.svn) do @if exist "%d" rd /s/q "%d"

posted @ 2010-12-13 11:33 大洋 阅读(19) 评论(0) 编辑
摘要: 在Asp.net中,如果给CKeditor加了RequiredField Validator 验证控件,在提交表单时,可能要点两次,点第一次时会出现验证失败,点第二次就可以提交。原因是,点第一次时CKeditor中的内容还未负给Textarea控件。解决方法:阅读全文
posted @ 2010-09-07 10:30 大洋 阅读(238) 评论(0) 编辑
摘要: sp_MSforeachtable @command1='Delete from ?'sp_MSforeachtable使用方法1)说明系统存储过程sp_MSforeachtable和sp_MSforeachdb,是微软提供的两个不公开的存储过程,从ms sql 6.5开始。存放在SQL Server的MASTER数据库中。2)参数说明:@command1 nvarchar(2000), --第一...阅读全文
posted @ 2010-08-26 11:32 大洋 阅读(73) 评论(0) 编辑
摘要: 前面两行是FF,Chrome用的,后面是IE专用[代码]阅读全文
posted @ 2010-07-11 11:46 大洋 阅读(193) 评论(0) 编辑
摘要: 什么是css框架事实上让我们开始于什么是框架?框架就是一个你可以用于你的网站项目的基本的概念上的结构体。css框架通常只是一些css文件的集合,这些文件包括基本布局、表单样式、网格或简单结构、以及样式重置。比如:typography.css基本排版规则grid.css基于网格的布局layout.css通常的布局form.cssfor 表单样式general.css更多通用规则不同的css框架Ele...阅读全文
posted @ 2009-07-15 11:47 大洋 阅读(122) 评论(0) 编辑
摘要: 不同的浏览器对CSS的解释都有一点出入,特别是padding, line-height这些要细微控制的地方,下面的hack基本可以解决这个问题:在属性前加下划线(_),那么此属性只会被IE6解释在属性前加星号(*),此属性只会被IE7解释在属性值后面加"\9",表示此属性只会被IE8解释例如:[代码]阅读全文
posted @ 2009-06-06 17:42 大洋 阅读(203) 评论(0) 编辑