《网络吸管》开发手记

本文记录了《网络吸管》的开发过程。该软件用于解决上网资料收集麻烦的问题,通过监视剪贴板实现内容保存。介绍了利用Windows API函数和Delphi的TClipboard类处理剪贴板,还解决了图片命名、文字保存等问题,以及应对防复制网页的方法,最后分享了开发经验。

《网络吸管》开发手记
网络确实是个好东西,文章呀,图片呀什么的都很吸引人。每次上网都能满载而归,但是这些资料的收集过程却很麻烦。对于好文章,每次都要复制、粘贴地在记事本和IE之间切换多次才能保存下来,而且说不定什么时候遇到那种怎么复制也复制不下来的防复制网页;对于图片也要点右键,选择“图片另存为”,再点确定才可以,遇到文件重名问题还要重命名。上网的兴致全被打乱了。网上虽然也有“网文快捕”之类的小软件,但是由于不是为自己“量身定做”的,所以用起来也不是很顺手。既然这样,就自己动手做一个吧,“自己动手丰衣足食”嘛!说干就干!
设计思想很简单:监视剪贴板,当发现剪贴板中有新内容时,就根据内容是文字还是图片来决定不同的保存方式。
如何监视剪贴板呢?很自然地想到放一个定时器,每隔一段时间检测一个剪贴板,将剪贴板地内容于上次检测地内容相比较,如果不同,就说明剪贴板的内容有变化。但是这样效率太低了,并且定时器的时间间隔也不好把握,间隔太短会降低系统的效率,而间隔太长就有可能漏掉复制的内容。这让我想起了CPU与外设之间通讯方式中的查询方式,那么有没有一种像CPU与外设之间的中断方式的东西呢?启动MSDN,搜索ClipBoard,呵呵!终于找到了!是什么呢?听我慢慢道来!
为了使应用程序能自动感知剪贴板的变化,windows提供了两个API函数。使用SetClipBoard可以将窗体注册到剪贴板观测链中,然后程序就能响应剪贴板的变化消息。剪贴板观察器是一个显示剪贴板当前内容的窗口。剪贴板观察链是一系列相互独立的剪贴板观察窗口,它们都能够接受当前发送到剪贴板的内容。
SetClipBoard的原型是:
function SetClipBoard(hwndNewViewer:HWND):HWND;
hwndNewViewer为要注册的窗体句柄。如果注册成功,则返回剪贴板观测链中下一个窗体的句柄;如果发生错误或无其他窗体,则返回NULL。
如果剪贴板发生变化,windows会向窗体发送WM_CHANGECCHAIN或WM_DRAWCLIPBOARD消息,观测链中每个窗体都会调用SendMessage将该消息传送给下一个窗体。当应用程序退出时,要利用API函数ChangeClipboardChain将窗体从剪贴板观测链中移去。其原型为:
function ChangeClipboardChain(hWndRemove, hWndNewNext:HWND):boolean;
hWndRemove将要删除的窗口的句柄, hWndNewNext为SetClipBoard返回的窗体的句柄。
这样我们只要在程序中等待剪贴板变化的消息即可。当消息到来时,我们应该怎样得到剪贴板中的内容呢?Delphi的clipbrd.pas单元中定义了一个类TClipboard,它封装了Windows剪贴板,简化了大量复杂的处理过程。我们在程序中可以直接调用全局函数Clipboard,该函数用于返回TClipboard对象实例,使用这个实例对剪贴板进行剪切、复制和粘贴等操作。下面是TClipboard对象的几个常用的方法和属性的简单介绍:
方法:
procedure Clear; 清空剪贴板。
function HasFormat(Format: Word): Boolean; 查询剪贴板中是否有指定格式的内容。可以有三种取值:CF_TEXT(文字)、CF_BITMAP(位图)、CF_METAFILEPICT(元文件)。
属性:
AsText:用于读写剪贴板文字内容。

如何给用户保存下来的图片文件命名也是个问题。我们可以设置一个全局整型变量,每当保存一个图片文件时,就令这个变量增加1,将这个整型变量转换成字符串做为文件名。如果指定的文件名已经存在,就要给文件重命名。最简单的办法就是在文件名之前(或之后)加上一个字符串(比如'new'),如果加上这个字符串后还是存在重名的文件呢?这就要用到学编程的人在一开始就学到的一个小技巧:递归。这个问题的解决办法见下面的代码:
procedure SaveToPic(APic: TJPegImage; AFileName: string);
Const PICPLUSSTR = 'new';
begin
  if FileExists(AFileName) then
    savetopic(ABmp, PICPLUSSTR+AFileName)
  else
      SaveBmpAsJpg(APic, AFileName);
end;

在实际应用的时候,还应该加上异常处理(如磁盘空间已满,文件名过长等)。图片的保存的基本问题已经解决,我们再来看看文字的保存。为了增强程序的灵活性,我们应该使用用户能方便地将不同地文字保存到不同的文件。继续沿用上面保存图片的方式用数字做文件名吗?当然不可以。一是因为文本文件不像图片那样在资源管理器中可以预览,用户必须打开文件才能知道文件中保存的是什么内容,如果用户想在一大堆“1.txt”、“2.txt”……中找自己想要的内容就太麻烦了;二是因为用户并不要求每次复制下来的内容都保存到单一的文件中,而是要将相关的内容保存到一个文件中。我对这个问题的解决方法是这样的:
用户可以先复制一段文字,然后再按一个热键(比如Ctrl+Alt+S,为什么要选Ctrl+Alt+S做热键呢?后面再说!),这样用户以后复制下的文字就保存到以用户复制的文字做为文件名的文件中。
记得无数位大师说过:“要将用户界面与业务逻辑分开。”好吧,就将上面的东西封装一下,也算是我向OO迈进的第一步吧!(下面之列出了类的部分成员)
  TWebPageSaver = class(TObject)
  private
    FImagePath: string;
    FTextPath: string;
    FImageCount: Integer;
    FTextFileName: string;
    procedure SetImagePath(const Value: string);
    procedure SetTextPath(const Value: string);
  public
    function Save: Boolean;//result is whether the content is saved
    procedure NewTextFile(AFileName:string);
    property ImagePath: string read FImagePath write SetImagePath;
    property TextPath: string read FTextPath write SetTextPath;
  end;
在用户界面中,当用户按下热键Ctrl+Alt+S时,就调用TWebPageSaver.NewTextFile更改文字保存的文件名FTextFileName;当收到剪贴板变化的消息时就调用TWebPageSaver.Save保存剪贴板中的内容。另外还有ImagePath、TextPath等属性,可以由用户来更改图片、文字的保存路径。

核心代码已经完成,来做一下用户界面吧!仿照着“windows优化大师”我做了界面,左边我用的是TSpeedButton组件,右边是TNotePage组件。当用户点击一个TSpeedButton时,调用TNotePage.ActivePage := '页面的代号'就可以激活相应的配置界面。这个软件需要在后台运行,那么就让它在平时缩小到系统托盘吧!将程序缩小到系统托盘很容易做到,网上有很多这样的示例代码。我手头有一个控件cooltray4.3可以用来实现系统托盘的功能,我就懒得自己再去写代码了。
软件运行一切良好。不过一直令我耿耿于怀的就是网上那种防复制的网页:不管你怎么拖动鼠标,那些文字就是无法被选定。仔细想一想,既然文字能够在IE上显示就一定可以得到它们。在MSDN中找了半天,才找到解决方法。可以通过ShellWindows集合来代表属于shell 的当前打开的窗口的集合,而IE就是属于shell的一个应用程序。用CoShellWindows.Create得到当前打开的shell的接口(IShellWindows),调用接口的Count属性得到当前打开的shell的数量,然后遍历这些窗口,尝试从接口中取出IWebbrowser2接口(通过ShellWindow.Item(I) as IWebbrowser2这样的接口类型转换方式),如果结果不为nil说明这个窗口是IE窗口。之后只要调用IWebBrowser2接口的相应方法即可得到窗口中的文字、URL、标题等内容了。
示例代码如下:
{需要使用mshtml,SHdocvw两个单元}  
var
 ShellWindow : IShellWindows;
 WebBrowser : IWebBrowser2;
 I, ShellWindowCount: integer;
 HTMLdocument : IHTMLdocument2;
 URL, Title, Text:string;
begin
  ShellWindow := CoShellWindows.Create;
  ShellWindowCount := ShellWindow.Count;
  for I := 0 to ShellWindowCount-1 do
  begin
    WebBrowser := ShellWindow.Item(I) as IWebbrowser2;
    if WebBrowser <> nil then
        begin
            HTMLDocument := WebBrowser.Document as IHtmlDocument2;
            URL := URL;
            Title := HTMLDocument.title;
            Text := HTMLDocument.body.outerText ;
            ShowMessage(URL+Title+Text);
        end;
  end;
  ShellWindow := nil;
end;

我们定义一个记录类型:
  TWebPageRecord = record
    URL: string;  file://保存网页的URL
    Title: string;//保存网页的标题
    Text: string; file://保存网页的文字
  end;

然后定义一个TWebPageRecord类型的数组FWebPageRecordArray,大小定位20吧(我想一般人不会打开20个以上的IE吧):
Const  MAXPAGECOUNT = 20;
……
FWebPageRecordArray : array [0..MAXPAGECOUNT-1] of TWebPageRecord;
在遍历IE窗口时,向数组中的元素的相应字段复制即可。
对这个复制防复制(好拗口呀:))网页的功能也封装成一个类吧!
type
  TWebCracker = class(TObject)
  private
    FWebPageRecordArray : array [0..MAXPAGECOUNT-1] of TWebPageRecord;
    FWebPageCount: Integer;
  public
    procedure SnapShot;
    function GetWebText(AIndex:integer): string;
    function GetWebTitle(AIndex:integer): string;
    function GetWebURL(AIndex:integer): string;
    procedure Clear;
    procedure Refresh;
    function GetWebPageCount: Integer;
  end;
在用户界面中,可以通过调用TWebCracker.SnapShot;来对打开的IE窗口进行遍历,并保存到FWebPageRecordArray这个数组中。通过TWebCracker.GetWebPageCount方法可以得到FWebPageRecordArray中保存的页面的个数,通过GetWebText、GetWebTitle、GetWebURL就可以得到指定页面的文字、标题或是URL。
一切都已经搞定了!爽!

通过编写这个小软件,我是收获颇丰呀!除了学到了上边这些技巧外,我还有一些小的经验,愿意与大家分享:
1、为用户着想,让用户舒服
用户是上帝嘛!以那个Ctrl+Alt+S热键来说吧:一般用户上网都是右手握鼠标,空下来的只有左手。小拇指按Ctrl,大拇指按Alt,食指刚好能按到S键,不费一点力气!
2、 良好的编码习惯
(1)不要出现魔术数
以TWebCracker定义的那个FWebPageRecordArray数组来说:
Const  MAXPAGECOUNT = 20;
……
FWebPageRecordArray : array [0..MAXPAGECOUNT-1] of TWebPageRecord;
别人一看MAXPAGECOUNT就知道是什么意思,而如果你写成:
FWebPageRecordArray : array [0..19] of TWebPageRecord;
估计除了你自己没有人能够知道19到底是什么意思。
(2)用sender的方式增强代码的健壮性
procedure TMainfrm.CBAutoRunClick(Sender: TObject);
Const
  SIGNINREGISTRY = 'WebSuction';
begin
  if (Sender as TCheckBox).Checked then 
     AddToAutoRun(Application.ExeName,SIGNINREGISTRY)
  else DelAutoRun(SIGNINREGISTRY);
end;
这样即使Checkbox1改了名字也不怕。
又如:
procedure TMainfrm.N1Click(Sender: TObject);
begin
  if (Sender as TMenuItem).Caption = '暂停(&S)' then
    begin
      (Sender as TMenuItem).Caption := '开始(&R)';
      FWebPageSaver.Pause;
    end
  else
    begin
      (Sender as TMenuItem).Caption := '暂停(&S)';
      FWebPageSaver.ReStart;
    end;
end;
(3)不要直接使用Tform2单元的全局Form2变量,那样就破坏了封装性
procedure TMainfrm.SBNextClick(Sender: TObject);
var
  LSelectedIndex : integer;
  FormDisplay : Tform2;
begin
  LSelectedIndex := LBWebPage.ItemIndex;
  if LSelectedIndex <> -1 then
  begin
    FormDisplay := Tform2.Create(self);
    FormDisplay.SetContent(FWebCracker.GetWebText(LSelectedIndex));
    FormDisplay.Show;
  end;
end;
在TForm2中定义 SetContent方法
procedure TWebCrackfrm.SetContent(AText:string);
begin
  Memo.Clear;
  Memo.Lines.Add(AText);
end;

更多请见:http://lincosoft.go.nease.net/

下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中一个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这一议题中,电路板被构建为一个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的一种常用策略。 该算法与回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的一个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排列树,在探索过程中,每个节点仅被赋予一次成为扩展节点的机会,且会一次性生成其全部子节点。 针对布线问题的解决,队列式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将与该扩展节点相邻且可通行的方格加入至活跃节点队列中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队列中提取队首节点作为下一个扩展节点,并将与当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队列。 这一过程将持续进行,直至算法探测到目标方格 b 或活跃节点队列为空。 在实现上述算法时,必须定义一个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和列。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
源码来自:https://pan.quark.cn/s/a4b39357ea24 在VC++开发过程中,对话框(CDialog)作为典型的用户界面组件,承担着与用户进行信息交互的重要角色。 在VS2008SP1的开发环境中,常常需要满足为对话框配置个性化背景图片的需求,以此来优化用户的操作体验。 本案例将系统性地阐述在CDialog框架下如何达成这一功能。 首先,需要在资源设计工具中构建一个新的对话框资源。 具体操作是在Visual Studio平台中,进入资源视图(Resource View)界面,定位到对话框(Dialog)分支,通过右键选择“插入对话框”(Insert Dialog)选项。 完成对话框内控件的布局设计后,对对话框资源进行保存。 随后,将着手进行背景图片的载入工作。 通常有两种主要的技术路径:1. **运用位图控件(CStatic)**:在对话框界面中嵌入一个CStatic控件,并将其属性设置为BST_OWNERDRAW,从而具备自主控制绘制过程的权限。 在对话框的类定义中,需要重写OnPaint()函数,负责调用图片资源并借助CDC对象将其渲染到对话框表面。 此外,必须合理处理WM_CTLCOLORSTATIC消息,确保背景图片的展示不会受到其他界面元素的干扰。 ```cppvoid CMyDialog::OnPaint(){ CPaintDC dc(this); // 生成设备上下文对象 CBitmap bitmap; bitmap.LoadBitmap(IDC_BITMAP_BACKGROUND); // 获取背景图片资源 CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap* pOldBitmap = m...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值