Chinaunix首页 | 论坛 | 博客
  • 博客访问: 356851
  • 博文数量: 132
  • 博客积分: 3066
  • 博客等级: 中校
  • 技术积分: 781
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-14 16:19
文章分类

全部博文(132)

文章存档

2012年(1)

2010年(50)

2009年(81)

我的朋友

分类: C/C++

2009-06-26 16:57:38

http多线程下载断点续传

依据HttpWebRequest 的 AddRange 来分成多个线程.
如果断了记录断点位置.

posted @ 2007-12-15 14:23 井泉 阅读(115) | 评论 (0)编辑

2007年11月20日

抓屏(转)

using   System;  
  using   System.Windows.Forms;  
  using   System.Drawing;  
  using   System.Drawing.Imaging;  
  using   System.Runtime.InteropServices;  
   
   
  namespace   PrintScreen  
  {  
          //一部分代码来自网络  
          [StructLayout(LayoutKind.Sequential)]  
          public   struct   RECT    
          {  
                  public   int   left;  
                  public   int   top;  
                  public   int   right;  
                  public   int   bottom;  
          }    
          //封装一部分api的类  
          public   class   apitmp    
          {  
                  [DllImport("user32.dll")]  
                  public   static   extern   bool   GetCursorPos(ref   Point   lpPoint);  
                  [DllImport("user32.dll")]  
                  public   static   extern   int   WindowFromPoint(   Point   lpPoint);  
                  [DllImport("user32.dll")]  
                  public   static   extern   IntPtr   GetDesktopWindow();  
                  [DllImport("user32.dll")]  
                  public   static   extern   int   GetForegroundWindow();  
                  [DllImport("user32.dll")]  
                  public   static   extern   int   GetWindowRect(int   hwnd,   ref   RECT   rc);    
                  [DllImport("user32.dll")]  
                  public   static   extern   int   GetWindowDC(   int   hwnd);  
                  [DllImport("Gdi32.dll")]  
                  public   static   extern   bool   BitBlt   (  
                          IntPtr   hdcDest   ,   //   目标设备的句柄  
                          int   nXDest   ,   //   目标对象的左上角的X坐标  
                          int   nYDest   ,   //   目标对象的左上角的X坐标  
                          int   nWidth   ,   //   目标对象的矩形的宽度  
                          int   nHeight   ,   //   目标对象的矩形的长度  
                          IntPtr   hdcSrc   ,   //   源设备的句柄  
                          int   nXSrc   ,   //   源对象的左上角的X坐标  
                          int   nYSrc   ,   //   源对象的左上角的X坐标  
                          System.Int32   dwRop   //   光栅的操作值  
                          )   ;  
                  [DllImportAttribute   (   "gdi32.dll"   )   ]  
                  public   static   extern   IntPtr   CreateDC   (  
                          string   lpszDriver   ,   //   驱动名称  
                          string   lpszDevice   ,   //   设备名称  
                          string   lpszOutput   ,   //   无用,可以设定位"NULL"  
                          IntPtr   lpInitData   //   任意的打印机数据  
                          )   ;  
   
          }  
   
          //封装各种抓屏操作的类  
          public   class   UCapture    
          {  
                  //获得屏幕指定区域    
                  public   Bitmap   getscreen(int   left,int   top   ,int   width,int   height)  
                  {  
                          IntPtr   dc1   =   apitmp.CreateDC   (   "DISPLAY"   ,   null   ,   null   ,   (   IntPtr   )   null   )   ;  
                          Graphics   newGraphics   =   Graphics.FromHdc(dc1);  
                          Bitmap   img   =   new   Bitmap(width   ,   height   ,newGraphics);  
                          Graphics   thisGraphics   =   Graphics.FromImage(img);  
                          IntPtr   dc2   =thisGraphics.GetHdc();  
                          IntPtr   dc3   =   newGraphics.GetHdc();  
                          apitmp.BitBlt(dc2,0,0,width   ,   height   ,dc3,left,top,13369376);  
                          thisGraphics.ReleaseHdc(dc2);  
                          newGraphics.ReleaseHdc(dc3);  
                          return   img;  
                  }  
                  //获得整个屏幕  
                  public   Bitmap   getfullscreen()  
                  {  
                          return   getscreen(0,0,Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height);  
                  }  
                  //抓取句柄所指窗口  
                  public   Bitmap   getscreenfromhandle(int   hwnd)  
                  {  
                          RECT   rc   =   new   RECT();  
                          apitmp.GetWindowRect(hwnd,   ref   rc);  
                          return   getscreen(rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top);  
                  }  
                  //抓取

活动窗口  
                  public   Bitmap   getscreenfromactivewindow()  
                  {  
                          int   handle   =   apitmp.GetForegroundWindow();  
                          return   getscreenfromhandle(handle);  
                  }  
                  //保存成各种格式  
                  public   string   savepic(Bitmap   bmp)  
                  {  
                          SaveFileDialog   saveDialog   =   new   SaveFileDialog();  
                          saveDialog.Filter   =   "位图文件   (*.bmp)|*.bmp|jpg文件   (*.jpg)|*.jpg|gif文件   (*.gif)|*.gif|tiff文件   (*.tiff)|*.tiff|"+  
                                  "emf文件   (*.emf)|*.emf|图标文件   (*.ico)|*.ico|wmf文件   (*.wmf)|*.wmf|png文件   (*.png)|*.png";  
                          saveDialog.DefaultExt="*.bmp";  
                          if(saveDialog.ShowDialog()   ==   DialogResult.OK)  
                          {  
                                  string   ext   =   saveDialog.FileName.Substring(saveDialog.FileName.Length-4,4);  
                                  switch(   ext)  
                                  {    
                                          case   ".bmp":  
                                                  bmp.Save(saveDialog.FileName,ImageFormat.Bmp);  
                                                  break;    
                                          case   ".gif":    
                                                  bmp.Save(saveDialog.FileName,ImageFormat.Gif);  
                                                  break;  
                                          case   ".jpg":    
                                                  bmp.Save(saveDialog.FileName,ImageFormat.Jpeg);  
                                                  break;  
                                          case   ".emf":  
                                                  bmp.Save(saveDialog.FileName,ImageFormat.Emf);  
                                                  break;    
                                          case   ".ico":    
                                                  bmp.Save(saveDialog.FileName,ImageFormat.Icon);  
                                                  break;  
                                          case   ".wmf":    
                                                  bmp.Save(saveDialog.FileName,ImageFormat.Wmf);  
                                                  break;  
                                          case   ".png":    
                                                  bmp.Save(saveDialog.FileName,ImageFormat.Png);  
                                                  break;  
                                          case   ".tiff":    
                                                  bmp.Save(saveDialog.FileName,ImageFormat.Tiff);  
                                                  break;  
                                          default   :  
                                                  return   "";  
                                  }    
                                  return   saveDialog.FileName;  
                          }  
                          return   "";  
                  }  
          }  
  }  
 

posted @ 2007-11-20 10:39 井泉 阅读(82) | 评论 (0)编辑

投票程序2 图像识别 Using The Office 2007 OCR Component in C#

http://www.devsource.com/article2/0,1759,2143124,00.asp

posted @ 2007-11-20 10:33 井泉 阅读(382) | 评论 (0)编辑

投票程序

tool ieHTTPHeaders  代理超人

Net WebClient通过代理服务器进行HTTP交互。//创建 代理服务器设置对象 的实例System.Net.WebProxy wp= new System.Net.WebProxy("172.24.2.98:8080");//代理服务器需要验证wp.BypassProxyOnLocal=false;//用户名密码wp.Credentials = new NetworkCredential("chengm", "00x0");//将代理服务器设置对象赋予全局设定System.Net.GlobalProxySelection.Select =wp;

(转)
我经常去网站抓些东西,有时也做一些自动填写表单的事情。最近接手了一个投票的任务,也即需要编写一个程序自动投票,从而也引发了如何编写自动投票程序和如何编写投票程序(投票程序怎么样防止自动投票)的话题。正所谓道高一尺、魔高一丈。自动投票和投票程序本身就是一种对弈状态。

如何编写投票程序,大致分为这么几步:

1:手动投票,分析中间出现的每一个页面的代码,找出投票规律。

2:捕获提交页面的时候所post的信息。

3:编程模拟这个手动过程。

我以我刚写的这个自动投票程序为例。在此我不会给出网址链接(保密),也不会给出真实数据和截图。

首先手动投票,是一个多选投票,点击投票按钮后,会弹出一个网页,这个网页会问你是确认还是放弃,点击确认后,返回投票成功的提示。当在此点击投票按钮的时候提示已经投过一票,不能重复投票。关闭所有浏览器窗口,打开一个新窗口,投票,仍然可以投票成功。根据如下行为,可以得出如下结论:

1:投票需要有两次和服务器的交互,第一次向服务器提交投票结果,第二次交互向服务器提交是否确认此结果。

2:此两页面之间的session是有联系的,也即同一个session下不能投两次票。当关闭浏览器,重新开启刘拉尼后,因为相当于新开了一个session,所以仍然会投票成功。

知道了这个后,就开始抓去两次交互的HTTP头信息(Header信息)

第一次交互的信息如下(点击投票按钮):

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

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Referer:
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2; .NET CLR 1.1.4322)
Host: xxx.xxx.xxx.xxx
Content-Length: 167
Connection: Keep-Alive
Cache-Control: no-cache

checkvalue=32&bbb=%CD%B6%C6%B1&ilc=0&kkk=22

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

从网页源代码中可以分析出来,数据是post上去的,post的数据为"checkvalue=32&bbb=%CD%B6%C6%B1& ilc=0&kkk=22",其中checkvalue=32即为投票选中的复选框的值,bbb为投票按钮的名称,"%CD%B6%C6%B1" 为"投票"两个汉字的转码。ilc=0和kkk=22是两个隐藏域中信息,作用还不明确。

从网页源代码中可以分析出来,数据是 post上去的,post的数据为"checkvalue=32&bbb=%CD%B6%C6%B1&ilc=0&kkk= 22",其中checkvalue=32即为投票选中的复选框的值,bbb为投票按钮的名称,"%CD%B6%C6%B1"为"投票"两个汉字的转码。 ilc=0和kkk=22是两个隐藏域中信息,作用还不明确。

然后接收到的Header如下:

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

Date: Wed, 05 Jan 2005 12:45:10 GMT
Server: Apache/1.3.27 (Win32)
X-Powered-By: PHP/4.1.2
Set-Cookie: ilc=22
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html

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

可以看到里面有一个关键的地方Set-Cookie : ilc = 22,也即设置了一个cookie,这个cookie值为22,也即post上去的kkk的值,猜测这是此次投票的编码。

可以看到里面有一个关键的地方Set-Cookie : ilc = 22,也即设置了一个cookie,这个cookie值为22,也即post上去的kkk的值,猜测这是此次投票的编码。

从返回的源代码中可以分析出来checkvalue=32这个值并没有写在第二个页面的隐藏域中,那么它就只能记录在session中。从第二次交互的信息中也可以证明。

第二次交互的信息:

发送:

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

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Referer:
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2; .NET CLR 1.1.4322)
Host: xxx.xxx.xxx.xxx
Content-Length: 22
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: ilc=22

inbtn=%C8%B7%C8%CF

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

可以看到第二次post上去的信息仅仅是一个按钮的信息。inbtn为"确认"按钮的名字。而"%C8%B7%C8%CF"就是"确认"两个字的转码。 checkvalue=32这个投票信息并不包含在第二次交互中,这只能说明第一次交互中就把这个信息写入到session中保存了。

可以看到第二次post上去的信息仅仅是一个按钮的信息。inbtn为"确认"按钮的名字。而"%C8%B7%C8%CF"就是"确认"两个字的转码。 checkvalue=32这个投票信息并不包含在第二次交互中,这只能说明第一次交互中就把这个信息写入到session中保存了。

不能重复投票可能是session中有记录,也可能是cookie中有记录。cookie的可能性大一些。
那么怎么模拟这个过程呢,我用了WebClient类,这个类非常好。其中的Headers属性可以设置头信息。而UploadData函数则可以post 数据上去。并且同一个WebClient的两次连接是在同一个Session中的。这样完成两次交互也就是一次投票之后,就可以重新new一个 WebClient,这样就相当于重新开了一个Session。

代码如下:

static void post()  

string uriString = ""
WebClient myWebClient 
= new WebClient(); 
string postData = null
byte[] byteArray; 
byte[] responseArray; 
WebHeaderCollection myWebHeaderCollection; 
 
postData 
= "checkvalue=32&bbb=%CD%B6%C6%B1&ilc=0&kkk=22"
myWebClient.Headers.Add(
"Content-Type","application/x-www-form-urlencoded"); 
myWebClient.Headers.Add(
"Referer",""); 
myWebClient.Headers.Add(
"Accept-Language","zh-cn"); 
myWebHeaderCollection 
= myWebClient.Headers; 
 
//第一次交互 
Console.WriteLine("发送的HTTP头信息"); 
for (int i=0; i < myWebHeaderCollection.Count; i++)  

Console.WriteLine (myWebHeaderCollection.GetKey(i) 
+ " : " + myWebHeaderCollection.Get(i)); 
}
 
 
byteArray 
= Encoding.Default.GetBytes(postData); 
responseArray 
= myWebClient.UploadData(uriString,"POST",byteArray); 
 
Console.WriteLine(
"接收的HTTP头信息"); 
myWebHeaderCollection 
= myWebClient.ResponseHeaders; 
for (int i=0; i < myWebHeaderCollection.Count; i++)  

Console.WriteLine (myWebHeaderCollection.GetKey(i) 
+ " : " + myWebHeaderCollection.Get(i)); 
}
 
Console.WriteLine(
"接收的正文信息"); 
Console.WriteLine(Encoding.Default.GetString(responseArray)); 
//Console.ReadLine(); 
 
//第二次交互(用同一个WebClient实例) 
postData = "inputinfo=%C8%B7%C8%CF"
myWebClient.Headers.Add(
"Content-Type","application/x-www-form-urlencoded"); 
myWebClient.Headers.Add(
"Referer",""); 
myWebClient.Headers.Add(
"Accept-Language","zh-cn"); 
myWebClient.Headers.Add(
"Cookie","ilc=126"); 
myWebHeaderCollection 
= myWebClient.Headers; 
 
Console.WriteLine(
"发送的HTTP头信息"); 
for (int i=0; i < myWebHeaderCollection.Count; i++)  

Console.WriteLine (myWebHeaderCollection.GetKey(i) 
+ " : " + myWebHeaderCollection.Get(i)); 
}
 
 
byteArray 
= Encoding.Default.GetBytes(postData); 
responseArray 
= myWebClient.UploadData(uriString,"POST",byteArray); 
 
Console.WriteLine(
"接收的HTTP头信息"); 
myWebHeaderCollection 
= myWebClient.ResponseHeaders; 
for (int i=0; i < myWebHeaderCollection.Count; i++)  

Console.WriteLine (myWebHeaderCollection.GetKey(i) 
+ " : " + myWebHeaderCollection.Get(i)); 
}
 
 
Console.WriteLine(
"接收的正文信息"); 
Console.WriteLine(Encoding.Default.GetString(responseArray)); 
//Console.ReadLine(); 
}
 

 

运行后输出的信息和手动投票时截获的信息基本一致。

然后就可以运行一个无限循环

 

int i = 0
while (true

try  

post(); 
++
Console.WriteLine(
"这是您投的第" + i + "张票"); 
}
 
catch (Exception e)  

Console.WriteLine(
"有错误发生:" + e.Message); 
}
 
Console.WriteLine(
"---------------------------------"); 
}
 


我做的命令行程序,要捕捉错误避免程序停止。停止程序的时候直接X掉窗口即可。另外用命令行程序的一个好处是不用做多线程,直接多运行几个exe的实例就可以达到多线程的目的(实际是多进程了)。

那么我们在做投票(包括其它表单)如何防备别人自动投票或者自动填写表但呢?session限制的方法显然不是一个有效的方法。而ip限制不实际,因为很多用户都没有ip地址,都是网络运营商作的NAT映射,封掉一个ip地址相当于封掉一批机器。所以一般也不采用。那么如何最有效呢。可以采用两种方法:

1:验证码,验证码直接导致了无法用程序来填写表单,因为验证码都为图片,文字的验证码是没有任何意义的。图片的验证码就决定了,如果要自动,那么必须识别出来验证码的数字和字母。至少一般人是没法做的,这是模式识别的问题。当然我也有朋友专门做模式识别的,可以从复杂背景下辨别出来潦草的手写笔迹,碰上这样的人谁也没办法了。但是像微软有些表单的验证码的图片作的非常复杂。除了背景有很多底纹之外,数字和图片还是花体的,并且角度也不一样,有竖的,有斜的,这样就很难识别。采用了验证码基本可以保证不会被自动投票。

2:ip限时间或票数:限制ip在一段时间内的投票数量或者投两票之间的间隔时间。比如同一ip地址一天内投票数不能超过100张。或者两次投票间隔至少5分钟等。这样即使有自动投票程序,其速度也就大大降低。起不到快速投票的作用。

如果同时结合这两种手段,那么基本很难再做自动投票的事情了。

另外就是上面我所提供的头信息是怎么抓取的,这个在IE上有个插件叫做ieHTTPHeader,您可以从此处

可以方便的看到提交网页时所提交的头信息和post数据。




posted @ 2007-11-20 10:28 井泉 阅读(200) | 评论 (0)编辑

httptunnel

// * @author Kamlesh Singh, Copyright (C) 2006, All Rights Reserved
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace HTTP_TUNNEL
{
 ///


 /// Summary description for httptunnel.
 ///

 public class httptunnel
 {
  public httptunnel()
  {
   //
   // TODO: Add constructor logic here
   //
  }

  private IPHostEntry proxyentry ;
  private IPAddress  proxyip ;
  private IPEndPoint proxyendpoint;
  public static Socket mysocket;
  
  public socket createHttpTunnel(string proxyipadd,string port,string struid,string strpwd,string destip,string destport)
  {
   proxyentry = Dns.Resolve(proxyipadd);
   proxyip = proxyentry.AddressList[0];
   int iPORT = Int32.Parse(port);
   proxyendpoint = new IPEndPoint(proxyip,iPORT);
    mysocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
   mysocket.Connect(proxyendpoint);
   string EncodedData = encodeUIDPWD(struid.ToString(),strpwd.ToString());

   string proxymsgmsg = "CONNECT " + destip +":" + destport +" HTTP/1.0\nProxy-Authorization:Basic " + EncodedData +  "\n\n" ;
   byte[] buffer = Encoding.ASCII.GetBytes(proxymsgmsg);
   byte[] buffer12 = new byte[500];
   mysocket.Send(buffer,buffer.Length,0);
   int msg = mysocket.Receive(buffer12,500,0);
   string data;
   data = Encoding.ASCII.GetString(buffer12);
   int index = data.IndexOf("200");
   
   if(index == -1)
   {
   // return 0;
   }
   else
   {
    //return 1;
    return mysocket;
   }
  }
  public string encodeUIDPWD(string uid, string pwd)
  {
   string uidpwd = uid + ":" + pwd;
   byte[] data = System.Text.UnicodeEncoding.UTF8.GetBytes(uidpwd);
   Base64Encoder myEncoder = new Base64Encoder(data);
   StringBuilder sb = new StringBuilder();
   sb.Append(myEncoder.GetEncoded());
   return (sb.ToString());

  }
 }
}



using System;

namespace HTTP_TUNNEL
{
 ///


 /// Summary description for Base64Encoder.
 ///

 public class Base64Encoder
 {
  byte[] source;
  int length,length2;
  int blockCount;
  int paddingCount;
  public Base64Encoder(byte[] input)
  {
   source=input;
   length=input.Length;
   if((length % 3)==0)
   {
    paddingCount=0;
    blockCount=length/3;
   }
   else
   {
    paddingCount=3-(length % 3);//need to add padding
    blockCount=(length+paddingCount) / 3;
   }
   length2=length+paddingCount;//or blockCount *3
  }

  public char[] GetEncoded()
  {
   byte[] source2;
   source2=new byte[length2];
   //copy data over insert padding
   for (int x=0; x   {
    if (x    {
     source2[x]=source[x];
    }
    else
    {
     source2[x]=0;
    }
   }
   
   byte b1, b2, b3;
   byte temp, temp1, temp2, temp3, temp4;
   byte[] buffer=new byte[blockCount*4];
   char[] result=new char[blockCount*4];
   for (int x=0;x   {
    b1=source2[x*3];
    b2=source2[x*3+1];
    b3=source2[x*3+2];

    temp1=(byte)((b1 & 252)>>2);//first

    temp=(byte)((b1 & 3)<<4);
    temp2=(byte)((b2 & 240)>>4);
    temp2+=temp; //second

    temp=(byte)((b2 & 15)<<2);
    temp3=(byte)((b3 & 192)>>6);
    temp3+=temp; //third

    temp4=(byte)(b3 & 63); //fourth

    buffer[x*4]=temp1;
    buffer[x*4+1]=temp2;
    buffer[x*4+2]=temp3;
    buffer[x*4+3]=temp4;

   }

   for (int x=0; x   {
    result[x]=sixbit2char(buffer[x]);
   }

   //covert last "A"s to "=", based on paddingCount
   switch (paddingCount)
   {
    case 0:break;
    case 1:result[blockCount*4-1]='=';break;
    case 2:result[blockCount*4-1]='=';
     result[blockCount*4-2]='=';
     break;
    default:break;
   }
   return result;
  }

  private char sixbit2char(byte b)
  {
   char[] lookupTable=new char[64]
     {
      'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
      'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
      '0','1','2','3','4','5','6','7','8','9','+','/'};

   if((b>=0) &&(b<=63))
   {
    return lookupTable[(int)b];
   }
   else
   {
    //should not happen;
    return ' ';
   }
  }
 }
}


posted @ 2007-11-20 10:05 井泉 阅读(75) | 评论 (0)编辑

2007年11月19日

(转)使用VS.Net IDE调试JavaScript

请按照如下步骤进行:

(1)     打开IE --> Internet Options -- > Advanced ; 去掉”Disable Script Debugging” 上的选项


(2)      打开需要调试的页面



(3)      启动VS.Net IDE, 选择 TOOLS-Debug Process (Ctrl + Alt + P). 选择需要调试的IE进程。


(4)      点击Attach按钮,选择Script选项; 再OK关掉对话框



(5)      选择IDE菜单 DEBUGàWindowsàRunning Documents (Ctrl + Alt + N)


(6)      选择需要调试的Javascript并设置断点

(7)      回到IE页面,运行与JavaScript有关的功能,就会中断到断点上。这时可以观察相关的参数,就和其他VS.Net程序一样。

 

posted @ 2007-11-19 13:12 井泉 阅读(44) | 评论 (0)编辑

2006年12月26日

WebBrowser页面与WinForm交互技巧

话说有了WebBrowser类,终于不用自己手动封装SHDocVw的AxWebBrowser这个ActiveX控件了。这个类如果仅仅作为一个和IE 一模一样浏览器,那就太没意思了(还不如直接用IE呢)。那么,无论我们是想做一个“定制版IE”,还是希望利用HTML来做用户界面(指WinApp而非WebApp。许多单机软件,包括Windows的帮助支持中心,都是HTML做的),都少不了Windows Form和包含在WebBrowser中的Web页面的交互。本文将通过几个实际的例子,初步介绍一下WinForm和WebBrowser所包含的 Web页面之间的交互。

下面的代码假设你已经建立了一个Windows Form,上面有一个WebBrowser名为“webBrowser”。

Study Case 1:用WinForm的Event Handler响应Web页面的事件

现在有这样一个Windows Application,它的界面上只有一个WebBrowser,显示一个本地的HTML文件作为界面。现在的问题是,所有逻辑都可以放在HTML文件里,唯独“关闭”按钮遇到了困难——通常,Web页面是没有办法直接控制浏览器的,更不用说结束这个WinForm程序了。

但是,在.Net 2.0当中,“由Windows Form响应Web页面的事件”已经成为了现实。

在.Net 2.0中,整个HTML文档以及其包含的各个HTML元素,都和一个个HtmlDocument、HtmlElement之类的.Net对象对应。因此只要找到这个“关闭”按钮对应的HtmlElement对象,为其click事件添加Event Handler即可。

假设HTML源代码如下:

<html>
<body>
<input type="button" id="btnClose" value="关闭" />
body>
html>

那么找出该按钮并为之添加Event Handler的代码如下:

 

HtmlDocument htmlDoc = webBrowser.Document;
HtmlElement btnElement 
= htmlDoc.All["btnClose"];
if (btnElement != null)
{
    btnElement.click 
+= new HtmlElementEventHandler(HtmlBtnClose_Click);
}

其中HtmlBtnClose_Click是按下Web按钮时的Event Handler。

很简单吧?那么稍稍高级一点的——我们都知道一个HTML元素可能有很多各种各样的事件,而HtmlElement这个类只给出最常用、共通的几个。那么,如何响应其他事件呢?这也很简单,只需要调用HtmlElement的AttachEventHandler就可以了:

btnElement.AttachEventHandler("onclick"new EventHandler(HtmlBtnClose_Click)); 
//这一句等价于上面的btnElement.click += new HtmlElementEventHandler(HtmlBtnClose_Click); 

对于其他事件,把"onclick"换成该事件的名字就可以了。例如:

formElement.AttachEventHandler("onsubmit"new EventHandler(HtmlForm_Submit)); 


Study Case 2:表单(form)的自动填写和提交

要使我们的WebBrowser具有自动填表、甚至自动提交的功能,并不困难。

假设有一个最简单的登录页面,输入用户名密码,点“登录”按钮即可登录。已知用户名输入框的id(或Name,下同)是username,密码输入框的id是password,“登录”按钮的id是submitbutton,那么我们只需要在webBrowser的 DocumentCompleted事件中使用下面的代码即可:

HtmlElement btnSubmit = webBrowser.Document.All["submitbutton"];
HtmlElement tbUserid 
= webBrowser.Document.All["username"];
HtmlElement tbPasswd 
= webBrowser.Document.All["password"];

if (tbUserid == null || tbPasswd == null || btnSubmit == null)
    
return;

tbUserid.SetAttribute(
"value""smalldust");
tbPasswd.SetAttribute(
"value""12345678");

btnSubmit.InvokeMember(
"click");

这里我们用SetAttribute来设置文本框的“value”属性,用InvokeMember来调用了按钮的“click”方法。因为不同的 Html元素,其拥有的属性和方法也不尽相同,所以.Net 2.0提供了统一的HtmlElement来概括各种Html元素的同时,提供了这两个方法以调用元素特有的功能。关于各种Html元素的属性和方法一览,可以查阅MSDN的。

※关于表单的提交,的确还有另一种方法就是获取form元素而不是button,并用form元素的submit方法:

HtmlElement formLogin = webBrowser.Document.Forms["loginForm"]; 
//…… 
formLogin.InvokeMember("submit"); 

本文之所以没有推荐这种方法,是因为现在的网页,很多都在submit按钮上添加onclick事件,以对提交的内容做最基本的验证。如果直接使用form的submit方法,这些验证代码就得不到执行,有可能会引起错误。


Study Case 3:查找并选择文本

这次我们希望实现一个和IE一模一样的查找功能,以对Web页面内的文字进行查找。

文本查找要借助于TextRange对象的findText方法。但是,.Net里并没有这个对象。这是因为,.Net 2.0提供的HtmlDocument,HtmlWindow,HtmlElement等类,只不过是对原有mshtml这个COM组件的不完整封装,只提供了mshtml的部分功能。所以许多时候,我们仍旧要借助mshtml来实现我们需要的功能。好在这些.Net类都提供了DomDocument这个属性,使得我们很容易把.Net对象转换为COM对象使用。下面的代码演示了如何查找Web页面的文本。
(需要添加mshtml的引用,并加上using mshtml;)

 

    public partial class SearchDemo : Form
    {
        
// 建立一个查找用的TextRange(IHTMLTxtRange接口) 
        private IHTMLTxtRange searchRange = null;
        
public SearchDemo()
        {
            InitializeComponent();
        }

        
private void btnSearch_Click(object sender, EventArgs e)
        {
            
// Document的DomDocument属性,就是该对象内部的COM对象。 
            IHTMLDocument2 document = (IHTMLDocument2)webBrowser.Document.DomDocument;
            
string keyword = txtKeyword.Text.Trim();
            
if (keyword == "")
                
return;

            
// IE的查找逻辑就是,如果有选区,就从当前选区开头+1字符处开始查找;没有的话就从页面最初开始查找。 
            
// 这个逻辑其实是有点不大恰当的,我们这里不用管,和IE一致即可。 
            if (document.selection.type.ToLower() != "none")
            {
                searchRange 
= (IHTMLTxtRange)document.selection.createRange();
                searchRange.collapse(
true);
                searchRange.moveStart(
"character"1);
            }
            
else
            {
                IHTMLBodyElement body 
= (IHTMLBodyElement)document.body;
                searchRange 
= (IHTMLTxtRange)body.createTextRange();
            }

            
// 如果找到了,就选取(高亮显示)该关键字;否则弹出消息。 
            if (searchRange.findText(keyword, 10))
            {
                searchRange.select();
            }
            
else
            {
                MessageBox.Show(
"已搜索到文档结尾。");
            }
        }
    } 

到此为止,简单的查找就搞定了。至于替换功能,看了下一个例子,我相信你就可以触类旁通轻松搞定了。

Study Case 4:高亮显示

上一个例子中我们学会了查找文本——究跟到底,对Web页面还是只读不写。那么,如果说要把所有的搜索结果高亮显示呢?我们很快会想到把所有匹配的文字颜色、背景改一下就可以了。

首先想到的可能是直接修改HTML文本吧……但是,与SourceCode的高亮显示不同,我们需要并且只需要高亮页面中的文本部分。HTML标签、脚本代码等等是绝对不应该去改动的。因此我们不能把整个页面的Source Code读进来然后replace,那样有破坏HTML文件结构的可能;我们只能在能够分离出文本与其他内容(标签,脚本……)的前提下进行。

具体方法有很多,下面提供两个比较简单的方法。

方法一:使用TextRange(IHTMLTxtRange)
有了上一个Case的基础,相信大家立刻会想到使用TextRange。没错,TextRange除了提供查找方法之外,还提供了一个pasteHTML方法,以指定的HTML文本替换当前TextRange中的内容。代码片断如下:

    public partial class HilightDemo : Form
    {
        
// 定义高亮显示效果的标签。
        string tagBefore = "";
        
string tagAfter = "";

        
// ……

        
private void btnHilight_Click(object sender, EventArgs e)
        {
            HtmlDocument htmlDoc 
= webBrowser.Document;
            
string keyword = txtKeyword.Text.Trim();
            if (keyword == "")
                return;

            
object oTextRange = htmlDoc.Body.InvokeMember("createTextRange");

            mshtml.IHTMLTxtRange txtrange 
= oTextRange as mshtml.IHTMLTxtRange;

            
while (txtrange.findText(keyword, 14))
            {
                
try
                {
                    txtrange.pasteHTML(tagBefore 
+ keyword + tagAfter);
                }
                
catch { }
                txtrange.collapse(
false);
            }
        }
    }


※这段代码里获取IHTMLTxtRange的方式和上面的例子稍稍不同,其实所谓条条大路通罗马,本质是一样的。

方法二:使用DOM(文档对象模型)
将HTML文档解析为DOM,然后遍历每个节点,在其中搜索关键字并进行相应替换处理即可。

    public partial class HilightDemo : Form
    {
        
//……

        
private void btnHilight_Click(object sender, EventArgs e)
        {
            HTMLDocument document 
= (HTMLDocument)webBrowser.Document.DomDocument;
            IHTMLDOMNode bodyNode 
= (IHTMLDOMNode)webBrowser.Document.Body.DomElement;
            
string keyword = txtKeyword.Text.Trim();
            
if (keyword == "")
                
return;

            HilightText(document, bodyNode, keyword);
        }

        
private void HilightText(HTMLDocument document, IHTMLDOMNode node, string keyword)
        {
            
// nodeType = 3:text节点
            if (node.nodeType == 3)
            {
                
string nodeText = node.nodeValue.ToString();
                
// 如果找到了关键字
                if (nodeText.Contains(keyword))
                {
                    IHTMLDOMNode parentNode 
= node.parentNode;
                    
// 将关键字作为分隔符,将文本分离,并逐个添加到原text节点的父节点
                    string[] result = nodeText.Split(new string[] { keyword }, StringSplitOptions.None);
                    
for (int i = 0; i < result.Length - 1; i++)
                    {
                        
if (result[i] != "")
                        {
                            IHTMLDOMNode txtNode 
= document.createTextNode(result[i]);
                            parentNode.insertBefore(txtNode, node);
                        }
                        IHTMLDOMNode orgNode 
= document.createTextNode(keyword);
                        IHTMLDOMNode hilightedNode 
= (IHTMLDOMNode)document.createElement("SPAN");
                        IHTMLStyle style 
= ((IHTMLElement)hilightedNode).style;
                        style.color 
= "black";
                        style.backgroundColor 
= "yellow";
                        hilightedNode.appendChild(orgNode);

                        parentNode.insertBefore(hilightedNode, node);
                    }
                    
if (result[result.Length - 1!= "")
                    {
                            IHTMLDOMNode postNode 
= document.createTextNode(result[result.Length - 1]);
                            parentNode.insertBefore(postNode, node);
                    }
                    parentNode.removeChild(node);
                } 
// End of nodeText.Contains(keyword)
            }
            
else
            {
                
// 如果不是text节点,则递归搜索其子节点
                IHTMLDOMChildrenCollection childNodes = node.childNodes as IHTMLDOMChildrenCollection;
                
foreach (IHTMLDOMNode n in childNodes)
                {
                    HilightText(document, n, keyword);
                }
            }
        }
    }

上面的两段代码都是为了清晰易懂而精简得不能再简的,有很多地方很不完善。比如,没考虑到如何从高亮显示状态复原;也没有大小写匹配等等。当然,掌握了原理之后相信这些都不会太难。

这两种方法各有优缺点:
使用TextRange较轻量迅速,而且有一个特长,就是可以把跨标签(Tag)的关键字挑出来。例如,有这么一段HTML:

<b>Helb>lo World!

先不管作者出于什么目的让Hel三个字母成为粗体,总之显示在页面上的是一句“Hello World!”。在我们希望高亮页面中的“Hello”这个关键字时,如果用DOM分析的话,会得出含有“Hel”的节点和文本节点 “lo World!”两个节点,因此无法将其挑出来。而TextRange则能正确识别,将其设置为高亮。因此也可以说TextRange是只和文本有关,和 HTML语法结构无关的对象。

但是,TextRange也有其致命缺点,加亮容易,反向的话就很难。换句话说,去除高亮显示的时候不能再用TextRange,而需要采用其他方法。

而DOM方法则正好相反, 由于DOM的树状结构特性,虽然不能(或者很难)跨越Tag搜索关键字,但是去除高亮显示并不繁琐。

Study Case 5:与脚本的互操作

在Case 1当中,我们已经看到,Web页面的HTML元素的事件,可以由Windows Form端来响应,可以在某种程度上看作是Web页面调用WinForm;那么反过来,WinForm除了可以直接访问Web页面的HTML元素之外,能否调用Web页面里的各种Script呢?

首先是调用Web页面的脚本中已经定义好的函数。假设HTML中有如下Javascript:

function DoAdd(a, b) {
    
return a + b;
}

那么,我们要在WinForm调用它,只需如下代码即可:

object oSum = webBrowser.Document.InvokeScript("DoAdd"new object[] { 12 });
int sum = Convert.ToInt32(oSum);

其次,如果我们想执行一段Web页面中原本没有的脚本,该怎么做呢?这次.Net的类没有提供,看来还要依靠COM了。IHTMLWindow2可以将任意的字符串作为脚本代码来执行。

string scriptline01 = @"function ShowPageInfo() {";
string scriptline02 = @"     var numLinks = document.links.length; ";
string scriptline03 = @"     var numForms = document.forms.length; ";
string scriptline04 = @"     var numImages = document.images.length; ";
string scriptline05 = @"     var numScripts = document.scripts.length; ";
string scriptline06 = @"     alert('网页的统计结果:\r\n链接数:' + numLinks + ";
string scriptline07 = @"        '\r\n表单数:' + numForms + ";
string scriptline08 = @"        '\r\n图像数:' + numImages + ";
string scriptline09 = @"        '\r\n脚本数:' + numScripts);}";
string scriptline10 = @"ShowPageInfo();";

string strScript = scriptline01 + scriptline02 + scriptline03 + scriptline04 + scriptline05 +
                   scriptline06 
+ scriptline07 + scriptline08 + scriptline09 + scriptline10;

IHTMLWindow2 win 
= (IHTMLWindow2)webBrowser.Document.Window.DomWindow;
win.execScript(strScript, 
"Javascript");

posted @ 2006-12-26 08:52 井泉 阅读(457) | 评论 (2)编辑

2006年12月25日

ICallbackEventHandler实现无刷新回调

AJAX技术所提倡的无刷新回调,在原来的技术中需要写大量的JavaScript代码或使用一些AJAX框架,使得开发效率和可维护性大大降低。其实ASP.NET2.0中,已经提供了这样的接口,这就是ICallbackEventHandler。
    关于ICallbackEventHandler网上已经有很多文章介绍了,这篇实为画蛇添足。

ICallbackEventHandler存在于System.Web.UI中,我们先做一个非常简单的例子来试用一下。

   第一步,在VS2005中建立一个新的WEB窗件。
   第二步,在ASPX中,放上一段HTML代码(如下):


1
2   


3   

4       
5   

6   

7

   第三步,然后在中放入一段JavaScript脚本:


 1
 

   第四步,在此ASPX的后台CS代码中,继承ICallbackEventHandler接口,并实现接口中的两个方法:
 ICallbackEventHandler.GetCallbackResult()
    和
 ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)

   第五步,增加一个变量CallBackValue,并修改接口的两个方法为:


 1 private string CallBackValue = string.Empty;
 2   
 3 string ICallbackEventHandler.GetCallbackResult()
 4 {
 5  return CallBackValue + ",ok";
 6 }
 7
 8 void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
 9 {
10  this.CallBackValue = eventArgument;
11 }
12
 

    第六步,运行,界面上会出现一个按钮,点击后,会将“测试”这个字符串传至后台,后台C#代码将字符串加上“,OK”后返回给客户端的JavaScript代码,并显示。

    以上六步,就可以实现无刷新回调了。现在,我们来分析一下几段代码。
    先看第三步中的JavaScript代码,其中的CallServer()方法中进行了回调,回调的语句为:
<%= ClientScript.GetCallbackEventReference(this, "product", "ReceiveServerData",null)%>;
   
    里面四个参数中第二个参数指定将product这个JavaScript中的字符串变量传回后台,第三个参数指定了从后台返回时接收返回信息的JavaScript方法ReceiveServerData(string Value)。

    第五步中后台的两个方法,一个ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)用来接收前台JavaScript中传来的字符串变量,并赋值给内部变量this.CallBackValue,另一个方法 ICallbackEventHandler.GetCallbackResult()将变更后的内部变量this.CallBackValue返回给前台JavaScript方法ReceiveServerData(string Value)。

    调用的顺序是: (前台)CallServer() --> (后台)ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) --> (后台)ICallbackEventHandler.GetCallbackResult() --> (前台)ReceiveServerData(string Value)。

    整个调用过程非常简单,而其中非常关键的一步是第三步的
<%= ClientScript.GetCallbackEventReference(this, "product", "ReceiveServerData",null)%>;
这个方法,以下是从网上找来的一段资料,大家可以看看。

GetCallbackEventReference使得客户端方法在客户端请求结束时得到回收。 它也让CallBackManager 确定产生哪种回叫方法。 在这个例子内使用的被重载的方法是:

   public string GetCallbackEventReference(
      string target, string argument,
      string clientCallback, string  context,
string clientErrorCallback)
Table 1. GetCallBackEventReference 方法的参数描述。
Parameters Description target ID of the page where the callback invocation is handled. For more see the other overloaded options available in the next immediate section.In our sample "this" is the argument value, since the callback is handled in the same page.  argument This is the parameter defintion used to send value to the server. This value is received by parameter "eventArgument" at the server end using the RaiseCallbackEvent event."arg" becomes the first parameter name in our sample. The value is passed through this argument from the client. clientCallback Method name of the callback that is invoked after successful server call."CallBackHandler" is the method name that handles the callback.   context A parameter that is associated with the "argument" from the client. It usually should be used to identify the context of the call. You will understand this better from the sample implementation.In the sample "ctx" is just another parameter definition used. The value for this is passed from the client. clientErrorCallback Name of the method that is called from the CallBackManager in case of any errors.
从这个方法返回的string是:

  
   __doCallback('__Page',arg,CallBackHandler,ctx, ErrorCallBack)
 
另一个重载方法是:

   public string GetCallbackEventReference(
      Control control, string argument,
      string clientCallback, string  context)
  
   public string GetCallbackEventReference(
      Control control, string argument,
      string clientCallback,  string  context,
string clientErrorCallback)

posted @ 2006-12-25 16:26 井泉 阅读(195) | 评论 (0)编辑

2006年2月25日

创建完全可编辑的 DataGrid

在论坛中我看到过许多相同或相似的问题:我怎样在我的DataGrid的每一行中放置检查框、文本框等等?怎样更新它们的值?答案相当简单,在这篇文章中,我将向你展示如何完成它。

我们都知道,DataGrid是一个功能非常强大的工具。根据我的经验,在90%以上的时间中, DataGrid 都被用来显示数据,并可能一次编辑一行数据。  而某些时候,可能需要一次编辑多行,甚至是所有数据。一个实际的例子就是在网上销售物品的应用程序中, 顾客可能一次要变更他们篮子中的一种或多种物品,单击检查框移去他们不想要的商品。

构想

在这个例子中,我写了一个简单的WebForm来管理存储在XML中的联系人列表。 这个需求是非常简单的:具有添加新联系人,编辑/删除现有联系人的能力。用户可以一次修改或删除多个联系人,我也允许用户按他们选定的列来对数据网格进行排序。

我的例子是用 C# 编写的。 如果你更喜欢这些代码的VB版本,在下载文件中有这两种格式的代码。

Contacts.xml

这个例子中的 XML 数据文件非常简单直观。由于它非常简单,所以我没有创建规划。



 
    myaddress@mycompany.com
    John
    Doe
 

 
    youraddress@yourcompany.com
    Jane
    Doe
 

ContactList.aspx

设置 WebForm 非常简单。我放置了一个新的 DataGrid 到我的窗体中,并且设置它为4列,第一列都包含了用来删除联系人的检查框。你会注意到我在这里做的主要工作就是以模板列( TemplateColumn)的形式创建了每一列。 这允许我放置文本框和检查框对象到数据模板(ItemTemplate)中 . 这是一个在网格每一行中显示文本以外的其它东西的技巧。 除此以外,你还会注意到我使用 FooterTemplate 来使新建联系人变得简单而直观。

我也包含了一个链接按钮(LinkButton),用来保存用户所做的修改及删除操作。但它并不用来添加新联系人。添加新联系人的操作由最后一列的页脚模板中链接按钮(LinkButton)来完成。


 
 
 
 
 
   
     
       
       

     

     
       
     

   

   
     
       
       

     

     
       
     

   

   
     
       
       

     

     
       
     

   

   
     
     
       
     

      
     
       
     

   

 


ContactList.cs

当我选择用XML文件来存取数据后,我就决定要使用DataSet来存取它。因为 DataSet 对象有 ReadXmlWriteXml 方法,所以这是非常合理的选择。第一步是在XML中读取数据。正如你从代码中所看到的,  我创建了一个成员用来处理数据排序。

private DataSet _dsContacts;
private string _sSort;

private void Page_Load(object sender, System.EventArgs e)
{
  // 装载 XML 文件.
  _dsContacts = new DataSet();
  _dsContacts.ReadXml(Server.MapPath("Contacts.xml"));
  DataColumn[] dcPk = {_dsContacts.Tables["Contact"].Columns["Email"]};
  _dsContacts.Tables["Contact"].PrimaryKey = dcPk;

  if (!Page.IsPostBack )
  {
    // 如果是第一次装载的话,绑定数据。
    BindContacts();
    _sSort = "FirstName";
  }
  else
  {
    // 否则,从视图状态中读取排序状态.
    _sSort = (string)ViewState["Sort"];
  }
}

第二步,我创建了一个用来绑定数据到网格的方法,它包含了数据排序逻辑以及从磁盘读取数据的方法。

private void BindContacts()
{
  // 保存排序状态到视图状态中.
  ViewState["Sort"] = _sSort;

 
// 绑定网格到已排序的数据视图中.
  DataView dv = new DataView(_dsContacts.Tables["Contact"]);
  dv.Sort = _sSort;
  dgContacts.DataSource = dv;
  dgContacts.DataBind();
}

private void
SaveContacts()
{
  _dsContacts.WriteXml(Server.MapPath(
"Contacts.xml"));
}

ItemCommand 事件用来处理向列表中添加新联系人。注意:我检查了  CommandName 参数是否为 Add.  它是来处理ASPX页中网格最后一列的页脚模板(FooterTemplate)中的链接按钮(LinkButton)的返回值。

private void dgContacts_ItemCommand(object source , System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  // 添加新数据到 dataset.  这里我使用了数组以提高处理效率.
  if (e.CommandName == "Add")
  {
   
string[] sContact = {"", "", ""};
    sContact[0] = ((TextBox)e.Item.FindControl(
"NewEmail")).Text;
    sContact[1] = ((TextBox)e.Item.FindControl(
"NewFirst")).Text;
    sContact[2] = ((TextBox)e.Item.FindControl(
"NewLast")).Text;

    _dsContacts.Tables[
"Contact"].Rows.Add(sContact);

    SaveContacts();
  }

  BindContacts();
}

我跳过了 SortCommand 代码,因为有许多其它文档已经非常详细地讨论过如何排序了。 如果你下载了这个例子的源代码,它就包含在里面。

最后,我将窗体上链接按钮(LinkButton)的单击事件(OnClick)移到了这里。  这里我通过循环检测DataGrid中的数据项来执行任何必需的删除及更新操作。

private void btnUpdate_Click(object sender, System.EventArgs e)
{
  // 循环处理每个数据项.
  foreach (DataGridItem di in dgContacts.Items)
  {
   
// 确信是数据项而不是页首或页尾.
    if (di.ItemType == ListItemType.Item || di.ItemType == ListItemType.AlternatingItem)
    {
     
// 取得更新或删除操作执行以后的当前行.
      DataRow dr = _dsContacts.Tables["Contact"].Rows.Find(dgContacts.DataKeys[di.ItemIndex]);

     
// 检查是否需要删除某行.
      if (((CheckBox)di.FindControl("chkDelete")).Checked)
      {
        _dsContacts.Tables[
"Contact"].Rows.Remove(dr);//删除指定行
      }
     
else
      {
       
//更新数据行.
        dr["Email"] = ((TextBox)di.FindControl("Email")).Text;
        dr[
"FirstName"] = ((TextBox)di.FindControl("First")).Text;
        dr[
"LastName"] = ((TextBox)di.FindControl("Last")).Text;
      }
    }
  }

  
// 如果有变化则保存它.
   if (_dsContacts.HasChanges())
  {
    SaveContacts();
  }

  BindContacts();//绑定
}

结束语

我可以很容易地通过控件的位置找到控件中每一个 DataGridItem的单元(Cells)。 有多种方法可以实现它,我确信你可以找到完成这项任务的更好的方法。正如你所看到的, 一次编辑整个数据网格是非常简单的。 同样的方法经过轻微的修改也可用于分页网格。


posted @ 2006-02-25 12:13 井泉 阅读(402) | 评论 (1)编辑

2006年2月15日

[Oracle]对数据库字段使用默认值

在创建数据库表时,你可以指定一个 DEFAULT 值(即默认值)。对数据库字段使用默认值有助于将数据库设计问题与应用程序代码隔离。

可以在以后某个时候通过一条 ALTER TABLE 命令改变一个字段的默认值,改变之后应用程序代码会立即开始使用新值。

有一点是很重要的,即 DEFAULT 值只有当一个字段没有在 INSERT 或 MERGE 中指定值,或者使用了 DEFAULT 关键字时才会使用。如果你不显式地声明一个 DEFULAT 值,Oracle 将隐式地将默认值定义为 NULL,而且 DEFAULT 关键字也是这样。从 Oracle 9i开始,可以在 DEFAULT 子句中使用诸如 SYSDATE 或 CURRENT_TIMESTAMPE 之类的伪字段。例如:

create table t1
(
    id$ integer not null,
    charcol char default 'Y',
    datecol date default sysdate,
    strcol varchar2(30) default user,
    intcol integer default 12
);
insert into t1 (id$) values (1);
select * from t1;

       ID$ C DATECOL   STRCOL                             INTCOL
---------- - --------- ------------------------------ ----------
         1 Y 28-MAY-04 SCOTT                                  12

DEFAULT 关键字与INSERT、MERGE 或UPDATE 语法比起来可以看上去没有那么必要,但是想一下如果你希望在插入一列数据时使用所有默认值,那么你就不会这么认为了。Oracle 不接受INSERT INTO

或INSERT INTO
VALUES () 作为有效的 SQL。必须指定至少一个字段,但是可以使用 DEFAULT 关键字来允许使用默认值,而非硬编码值,所以下面是有效的语法,它将使用所有 DEFAULT 值创建一行记录。

create table t2(charcol char default 'Y',datecol date default sysdate);
insert into t2 (charcol) values (default);
select * from t2;

C DATECOL
- ---------
Y 28-MAY-04

一个常见的问题

一个常见的问题是模拟其它数据库提供商的 Autonumber 功能,该功能是使用某种顺序数字自动地填充某个字段。在 Oracle 数据库中,不能指定一个顺序数字作为一个字段的 DEFAULT 值;然而,可以使用触发器模拟这一功能。即使一个字段声明为 NOT NULL,也依然可以在 INSERT 语句中忽略这个字段,而使用一个触发器来填充该字段的值。注意使用 DEFAULT 关键字比使用显式的 NULL 可读性要好。

create sequence t3_seq;
create table t3(id$ integer constraint t3_pk primary key);
create or replace trigger t3_autonumber
before insert on t3 for each row
begin
    if :new.id$ is null then
        select t3_seq.nextval into :new.id$ from dual;
    end if;
end;
/
show errors;

insert into t3(id$) values (default);
select * from t3;

       ID$
----------
         1

可以使用SYS_CONTEXT 值的集合中的默认值来填充字段,并收集有关某处一个会话的重要信息:

create table t4
(
    when date default SYSDATE,
    db_domain varchar2(200) default SYS_CONTEXT('USERENV','DB_DOMAIN'),
    host varchar2(256) default SYS_CONTEXT('USERENV','HOST'),
    ip_address varchar2(256) default SYS_CONTEXT('USERENV','IP_ADDRESS'),
    language varchar2(256) default SYS_CONTEXT('USERENV','LANGUAGE'),
    protocol varchar2(200) default SYS_CONTEXT('USERENV','NETWORK_PROTOCOL'),
    terminal varchar2(200) default SYS_CONTEXT('USERENV','TERMINAL')
);
insert into t4 (when) values (default);
select * from t4;

WHEN
---------
DB_DOMAIN
------------------------------------------------------------------------------
HOST
------------------------------------------------------------------------------
IP_ADDRESS
------------------------------------------------------------------------------
LANGUAGE
------------------------------------------------------------------------------
PROTOCOL
------------------------------------------------------------------------------
TERMINAL
------------------------------------------------------------------------------
28-MAY-04
scott.bn
MSHOME\SCOTT-LAP
AMERICAN_AMERICA.AL32UTF8
SCOTT-LAP

还可以使用伪字段SYS_GUID 来填充一个字段;它具有全局唯一性的优点,并且不需要顺序数字或触发器开销:

create table t5(id$ raw(16) default sys_guid()
    constraint t5_pk primary key);
insert into t5(id$) values (default);
select * from t5;

ID$
--------------------------------
643718A07DCC43F2AC95312FD43617BA

阅读(1460) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~