Profilo di bzcyerEffective TestingFotoBlogElenchiAltro Strumenti Guida

Blog


24/03/2008

为什么doc一直是null....

  webBrowser1.Navigate("http://www.baidu.com");

          HtmlDocument doc = (HtmlDocument)webBrowser1.Document.DomDocument;

          HtmlElement keyword = (HtmlElement)doc.GetElementById("kw");
          keyword.SetAttribute("kw", "bzcy");
          Thread.Sleep(2000);
          HtmlElement sub = (HtmlElement)doc.GetElementById("sb");
          sub.Click();

  在定义doc时,是从webbrowser1.Document.DomDocument来,但这样doc一直是Null,直接导致后面引用doc来拿  keyword出现错误:未将对象引用设置到对象的实例。

不明白为什么。。。

.Net 2.0实例学习:WebBrowser页面与WinForm交互技巧

 

(http://smalldust.cnblogs.com/archive/2006/03/08/345561.html)

话说有了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的DHTML Reference

※关于表单的提交,的确还有另一种方法就是获取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, 1, 0))
            {
                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 = "<span style='background-color:yellow;color:black'>";
string tagAfter = "</span>";
// ……
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, 1, 4))
            {
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>Hel</b>lo World!

先不管作者出于什么目的让Hel三个字母成为粗体,总之显示在页面上的是一句“Hello World!”。在我们希望高亮页面中的“Hello”这个关键字时,如果用DOM分析的话,会得出含有“Hel”的<b>节点和文本节点“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[] { 1, 2 });
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");

OK,今天就写到这里吧,再想起什么来再补充吧。欢迎大家多多指正,欢迎讨论。

21/03/2008

一些下载地址

 
http://www.05sun.com/downinfo/436.html  这个是VS2005,比较好用
 
vs.net2003下载1
    注:这里提供的是HTTP下载,除了VS的3张CD,还附有MSDN的3张CD!
http://oooo.upc.edu.cn/download/Software.asp?id=102
vs.net2003下载2
msdn
http://soft.pdsnc.edu.cn/list.asp?id=764
vs.net2003下载3
http://www.lm8.cn/SoftView/SoftView_130.html
vs.net2003下载4
http://soft.zt169.com/Software/Catalog152/19.html
vs.net2003下载5
http://www.a5d.com/SoftView/SoftView_74.asp
vs.net2003下载6
http://www.xfly.com.cn/Soft/netsoft/netsafe/200603/Soft_14.html
vs.net2003下载7
http://bt.1he.net:6969/torrent.html?info_h...8d25dec191e612d
vs.net2003下载8
http://www.qjedu.net/Soft_Show.asp?SoftID=183
vs.net2003下载9
http://www.programbbs.com/tool/show.asp?ID=27
vs.net2005下载1
http://soft666.com/soft/10/273/2006/2006071611646.html
vs.net2005下载2
http://www.xfly.com.cn/Soft/netsoft/netsafe/200603/Soft_13.html
vs.net2005下载3
http://www.05sun.com/downinfo/436.html
vs.net2005下载4
Visual Studio.net 2005 简体中文版
http://it90.com/down/program/net/2006/20060722361.html
软件大小 1540 MB
Visual Studio 2005注册升级
正式key:KYTYH-TQKW6-VWPBQ-DKC8F-HWC4J
找到SETUP文件夹下的setup.sdb,用记事本打开它,找到[Product Key],将下面的一行序列号删除,改为正式Key,保存后再安装就是正式版了,记住,中间没有横线!! 如果先前已经安装好180天的版本,请在添加删除Visual Studio 2005时,可以输入序列号,进行升级。
vs.net2005下载5
thunder://QUFmdHA6Ly9uaWMuY2NzdS5jbi9NaWNyb3NvZnQlMjBWaXN1YWwlMjBTdHVkaW8lMjAyMDA1cHJvX2Nocy5pc29aWg==
用WEB讯雷下载,
vs.net2005下载6
http://www.programbbs.com/tool/show.asp?ID=22

Windows外壳名字空间的浏览/姜伟华

http://blog.csdn.net/kingcom_xu/archive/2003/03/18/18943.aspx

Windows95/98对Dos/Win3.x作了许多重大改进,在文件系统方面,它除了采用长文件名替代Dos中的8.3文件名以外,引入外壳名字空间(Shell Name Space)来代Dos文件系统是其又一大突破.本文将简要地介绍如何在Windows 95/98或Windows NT4.0以上版本。

  1. 概述
  1. 简介

      在Dos/Win3.x中,每个逻辑分区构成一棵目录树,文件系统由这一统一的根,而且每个目录或文件必须一一对应于文件系统中客观存在的项。但Windows引入了“外壳名字空间”( Shell Name Space)的概念之后,这一切就都变了。

     外壳名字空间是Windows下的标准文件系统,它大大扩展了Dos文件系统,形成了以“桌面”(Desktop)为根的单一的文件系统树,原有的C盘、D盘等目录树变成了“我的电脑”这一外壳名字空间子树的下一级子树,而像“控制面板”、“回收站”、“网上邻居”等应用程序及“打印机”等设备也被虚拟成了外壳名字空间中的节点。另外,与DOS中物理存储只能和文件系统项一一对应这一点不同的是,一个实际目录在外壳名字空间中可以表现为不同的项。例如“我的文档”与“C;\My Documents”其实都指向“C;\My Documents”目录,但它们在外壳名字空间中是不同的项。如果我们运行Windows 自带的“Windows资源管理器”看一下的话,那么在它的左部树型视图中我们就可以清楚的看到整个外壳名字空间替代DOS文件系统,Windows在文件系统的组织与管理上终于有了质的飞跃。

      为了区别于DOS中“目录”的概念,Windows引入了“文件夹”(Folder)的概念。“文件夹”一般是指外壳名字空间树中的非叶节点,既可以是DOS下的目录,也可是“控制面板”、“回收站”这类虚拟的目录。但外壳名字空间中有些项本身并不是文件夹(即不具有文件夹属性),但却含有子文件夹,比如“网上邻居”等。以下为讲座方便,我们也认为它们是文件夹。

      在下面的讲座过程中我们将用“文件系统”一词来指代DOS文件系统,而用“外壳名字空间”一词来指代Windows中的外壳名字空间:另外用“文件”一词来指代外壳名字空间这棵树中的叶节点(虽然它们不都是物理存储上的文件)。

      在Windows中,Win3.x的文件操作函数,如FindFirstFile、FindNextFile、SetCurrentDirectory等,虽然仍可使用,但用它们只能浏览文件系统,却无法浏览与操纵整个外壳名字空间。要浏览Windows中的外壳名字空间,就必须使用一套全新的、基于COM(组件对象模型)基础上的方法。

  2. 新的“路径”PIDL

     在讨论基于 COM的方法之前,我们先来介绍一下外壳名字空间中“路径”的表示问题。DOS的字符串路径只能表示文件系统,而无法表示整个外壳名字空间,所以外壳名字空间提供了一种“路径”的替代物椩乇晔斗斜恚虺莆?/FONT>PIDL)。

      PIDL是一个元素类型为ITEMIDLIST结构的数组,数组中元素的个数是未知的,但紧接着数组末尾的必是一个双字节的零。每个数组元素代表了外壳名字空间树中的一层(即一个文件夹或文件),数组中的前一元素代表的是后一元素的父文件夹。由此可见,PIDL实际上就是指向一块由若干个顺序排列的ITEMIDLIST结构组成、并在最后有一个双字节零的空间的指针。所以PIDL的类型就被Windows定义为ITEMIDLIST结构的指针。

    PIDL亦有“绝对路径”与“相对路径”的概念。表示“相对路径”的PIDL(本文简称为“相对PIDL”)只有一个ITEMIDLIST结构的元素,用于标识相对于父文件夹的“路径”;表示“绝对路径”的PIDL(简称为“绝对PIDL”)有若干个ITEMIDLIST结构的元素,第一个元素表示外壳名字空间根文件夹(“桌面”)下的某一子文件夹A,第二个元素则表示文件夹A下的某一子文件夹B,其余依此类推。这样绝对PIDL就通过保存一条从“桌面”下的直接子文件夹或文件的绝对PIDL与相对PIDL是相同的,而其他的文件夹或文件的相对PIDL就只是其绝对PIDL的最后一部分了。

      但现在就出现了一个问题:即“桌面”的表示问题。外壳名字空间中其他各项都可以用从“桌面”开始的绝对PIDL加以表示,但“桌面”的PIDL数组显然一个元素都没有。这样就只剩下PIDL数组最后的那个双字节的零了。所以,“桌面”的PIDL就是一个16位的零。注意:“桌面”内容是一个双字节的零。另外,虽然“桌面”表示的是“C:\ Windows \Desktop”文件夹(这里假定Windows的系统目录为“C:\ Windows”)的内容,但“桌面”与“C:\ Windows \Desktop”文件夹的PIDL是完全不同的。这一点同样适用于“我的文档”与“C:\ My Documents”等文件夹。

    DOS中的路径是一个字符串,但PIDL是一种二进制结构,所以我们不能直接从PIDL中获知它所代表的到底是哪个文件夹或文件,而必须调用相应的函数把它转换成代表路径的字符串。如果某绝对PIDL是文件系统的一部分,则调用SHGetPathFromIDList函数即可;但如不是,就无法获得路径字符串了,因为DOS中根本就不存在这种路径。但很可惜的是,Windows并没有提供一个函数来让我们方便地把文件系统的路径字符串转换成PIDL。不过我们可用一个我们自己实现的函数ParsePidlFromPath()来达目的(具体函数的实现见下文)。

    PIDL的创建与释放一般并不使用C++的new和delete操作或C语言的malloc和free函数,而必须使用专门的方法进行.首先调用SHGEetMallocI函数得到Malloc接口(COM接口的一种,关于COM接口下面将详述)的指针,再调用该接口的Alloc方法为PIDL分配空间,或调用该接口的Free方法释放某个PIDL占用的空间。最后调用该接口的Release方法释放该接口。

     除了下面将要介绍的IShellFolder、IEnumIDList等COM接口可以操作PIDL外,还有很多以SH开头的Windows API函数也可操作PIDL,不过一般这些函数都要求使用绝对PIDL作参数。例如SHGetFileInfo函数可得到某一PIDL所指对象的各种信息,包括名字、图标、属性等;SHFileOperation函数可对外壳名字空间中的项进行拷贝、移动、改名、删除等操作;SHBrowseForFolder可以显示一个让用户选择外壳名字空间中某一文件夹的浏览对话框.

  3. 基于COM的方法

  讨论清楚了PIDL的概念之后,我们回过头来讨论基于COM之上的浏览外壳名字空间的方法。如果说PIDL是外壳的名字空间中的“路径”的话,那么下面所说的两个COM接口IshellFolder与IEnumIDList就起着与Win 3.x中的FindFirstFile、FindNextFile等函数类似的功能。

  在Windows中,每个文件夹都由操作系统实现了一个派生自Iunknown接口(COM接口的最基本类)的接口IshellFolder。通过调用某个文件夹的该接口,即可实现对该文件夹的浏览,得到该文件夹中子项(子文件夹或文件)的各种相关信息。

  我们可以调用SHGetDesktopFolder函数来获得外壳名字空间的根文件夹(即“桌面”)的IshellFolder接口。对于某个文件夹A,以它的子文件夹B的相对PIDL为参数,调用它的IshellFolder接口的BindToObject方法即可得到子文件夹B的IshellFolder接口。如要枚举某个文件夹下的子项,则只需调用它的IshellFolder接口的EnumObjects方法即可获得一个IEnumIDList接口。通过调用该IEnumIDList接口的Next方法我们即可枚举出该文件夹的所有子项(包括文件夹和文件等对象),获得它们的相对PIDL。使用父文件夹的IshellFolder接口和这些相对PIDL,我们即可获得这些子项的各种相关信息,包括显示名称、图标、属性等,甚至还可以获得它的右键菜单。例如,调用该接口的GetDisplayNameOf方法可获得该文件夹下子项的显示名称;调用ParseDisplayName方法可把某个子项的用Unicode内码表示的字符串路径翻译成对应的PIDL。这样通过PIDL和这两个接口,我们就可以遍历和操纵整个外壳名字空间了。

  除了IshellFolder和IEnumIDList接口以外,Windows 外壳名字空间还提供了很多其他COM接口,例如IshellBrowser、IshellLink、IshellIcom、IshellView等。通过这些接口,应用于程序就可以更好的与外壳名字空间交互。由于本文篇幅有限,这些接口就不详细介绍了,有兴趣的读者可参阅相关资料。

  值得注意的是,COM中的接口虽然在使用上与C++中的类是非常相似(事实上COM接口在C ++中就是以类的形式声明的),但维护其正确的引用计数机制是非常重要的。每增加一个对该接口的引用,就要调用一次它的AddRef( )方法;而在使用完后必须调用它的Release( )方法释放该接口。关于COM及COM接口的细节请参见相关资料,这里不再赘述。

  可惜的是,虽然我们可依照上文给出的方法实现外壳名字空间的逐层展开,但外壳名字空间却并没有提供一种让我们自由跳转到某一文件夹的方法,也没有提供返回到上一级文件夹的方法,因为我们无法方便地获得父文件夹的IshellFolder接口。如果要返回,就必须由应用程序自己想方法获得父文件夹的IshellFolder接口。一种可行的方法是在展开外壳名字空间时保存每个文件夹的IShellFolder接口指针和它的绝对PIDL,这样就可以相对容易地实现自由跳转了。

  但无论如何,外壳名字空间提供的浏览和操作的方法比起DOS/ Windows 3.x的函数来还是有着巨大的飞跃的。只要我们理解清楚了这种方法的优点与不足,我们就可以扬长避短,开发出各种各样的使用外壳名字空间的程序来。

  1. 相关接口、函数和数据结构

  对于本文所涉及的一些比较复杂的接口、函数和数据结构,以下仅列举出作者在Visual C++6.0查到的声明与定义,并配上相应的注释.一些较简单的则从略,未列出的请参见相关资料。

  1. 数据结构

typedf IshellFolder*LPSHELLFOLDER;

//IshellFolder接口指针的声明

typedef struct _ITEMIDLIST{//ITEMIDLIST结构的定义

SHITEMID mkid;

}ITEMIDLIST, * LPITEMIDLIST;

typedef struct _SHITEMID{//ITEMIDLIST结构中元素的定义

USHORT cb;//本结构的长度(以字节计)

BYTE abID[1];//可变长的元素标识符

}SHITEMID, *LPSHITEMID;

typedef struct _SHFILEINFO{//SFFILEINFO结构的定义

HICON hicon;//文件图标的句柄

Int ilcon;//图标在系统图像列表中的序号

DWORD dwAttributes;//文件的属性

Char szDisplayName [MAX_PATH];//显示名称或路径

Char szTypeName[80];//表示文件类型的字符串

} SHFILEINFO;

2.相关接口

2.1 IshellFolder接口的方法

  1. BindToObject

    格式:HRESULT BindToObject( LPCITEMIDLLIST pidl, LPBC pbcreserved, REFIID riid, LPVOID *ppvOut);

    作用:得到本文件夹中某一子文件夹的IShellFolder接口。

    参数:Riid应为IID_IshellFolder, pbcReserved应为NNUL,pidl为表示该子文件夹的“相对路径”的PIDL,从ppvOut中返回要求的IshellFolder接口的指针。

  2. EnumObjects

格式:HRSULT EnumObjects( HWND hwndOwner, DWORD grfFlags, LPENUMIDLIST*ppenumIDList);

作用:枚举本文件夹的成员。

参数:hwndOwmer为父窗口句柄,grfFlags决定枚举世闻名的内容,可为SHCONTF_FOLDERS、SHCONTF_NONFOLDERS、SHCONTF_INCLUDEHIDDEN的组合,从ppenumIDList返回IEnumIDList接口的指针。

(3)GetDisplayNameOf

格式:HRESULT GetDisplayNameOf (LPCITEMIDLIST pidl, DWORD uFlags, LPSTRRERT lpName);

作用:得到本文件夹中某一对象的显示名称。

参数:pidl为表示该子文件夹的“相对路径”的PIDL, uFlags为 SHGDN_NORMAL、SHGDN_INFOLDER、SHGFI_SYSICONINDEX、SHGFI_EXETYPE、SHGFI_ATTRIBUTES、SHGFI_PIDL、SHGFI_DISPLAYNAME、SHGFI_LARGEICON等。

返回值:如uFlags包含SHGFI-EXETYPE标志,则返回值为该可执行文件夹类型;如uFlags包含SHGFI_SYSICONINDEX标志,则返回值为系统图像列表的句柄。否则,如本函数调用成功则返回非零值,失败则返回零。

  1. 应用举例
  1. 几个非常有用的函数的实现

1.1ParsePidlFromPath

描述:将文件系统路径翻译成对应的PIDL。LPITEMIDLIST ParsePidlFromPath(LPCSTR path)

{

//存放以Unicode内码表示的路径字符串的缓冲区

OLECHAR szOleChar[MAX_PATH];

//“桌面“的IshellFolder接口指针

LPSHELLFOLDER IpsfDeskTop;

//返回的PIDL

LPITEMIDLIST Ipifq;

ULONG ulEaten, ulAttribs;

HRESULT hres;

//得到“桌面”的IshellFolderr 接口指针

SHGetDesktopFolder(&lpsfDeskTop);

//将Ansi字符集的路径字符串转换成Unicode字符串,

存入szOleChar

MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,

Path,-1,szOleChar,sizeof(szOleChar));

//将szOleChar,中的路径径字符串翻译成相应的PIDL,存入lpifq

hres=lpsfDeskTop->Release( );

//如果翻译失败,则返回NULL

if(FAILED(hres))return NULL;

return lpifq;

1.2 GetItemIcon

描述:返回lpi这个绝对PIDL所指项的图标在系统图像列表中的序号,uFlags为要求的图标类型。

Int Getltemlcon(LPITEMIDLIST lpi, UINT uFlags)

{

//存放文件信息的结构

SHFILEINFO sfi;

//给uFlags增加一些公共标志(lpi为PIDL、要求返回系统图像列表、要求小图标)

uFlags|=SHGFI-PIDL |SHGFI_SYSICONINDEX |SHGFI_SMALLICON;

获得图标

SHGetFileinfo( (LPCSTR) lpi, 0, & sfi , sizeof(SHFILEINFO),uFlags);

//返回图标在系统图像列表中的序号

return sfi,ilcon;

}

1.3 GetName

描述:lpio lpsf所指的IshellFolder接口代表的文件夹下的相对PIDL,本函数获得lpi所指项的显示名称,dwFlags表明欲得到的显示名称类型,lpFriendlyName为存放显示名称的缓冲区。

BOOL GetName(LPSHELLFOLDER lpsf,LPITEMIDLIST lpi,DWORD dwFlags,LPSTR lpFriendlyName)

{

STRRET str;

//得到显示名称

if(NOERROR!=lpsf->GetDisplayNameOf()lpi,dwFlags,&str))

return FALSE;

//根据返回值进行转换

switch(str uType)

{

//如为Unicode字符串,则转成Ansi字符集的字符串case STRRET_WSTR:

WideCharToMultiByte(CP_ACP,0,str.pOleStr,-1,ipFriendlyName,sizeof(lpFriendlyName),NULL,NULL);

Break;

//如为偏移量,则去除偏移量

case STRRET_OFFSET:

lstrcpy(lpFriendlyName,(LPSTR)lpi+str.uOffset);

break;

如为Ansi字符串,则直接拷贝

case STRRET_CSTR:

Lstrcpy(lpFriendlyName,(LPSTR)str.cStr);

Break;

//非法情况

default:

return FALSE;

}

return TRUE;

  1. 一个实例

以下我们将用Visual C++6.0制作一个例子来演示外壳名了空间的浏览。具体为使用Ctreer View,展开外壳名字空间中的“桌面”文件夹,枚举出该文件夹下的所有子文件夹。

在这个项目中,CtreeView 的图像列表我们使用Windows 的系统图像列表,而不是自己创建一个。

首先,用AppWizard新建一个项目,类型为MFC AppWizard(exe),项目名为Test;在第一步中选择Single document;在第六步中将CtestView的基类改为CtreeView。其它均使用默认设置。

其次,在CtestView中加一个私有成员变量m_ImageList,类型为CimageList,用于保存系统列表。(Windows中所有的图标都保存在系统图像列表中,我们可以在程序中得到这个图像列表)。

第三步,将上文提到的GetName 和GetItemIcon 这两个函数的实现拷贝到CtestView.cpp的较开头的位置。

第四步,在CtestView的OnInitialUpdate( )函数中加入以下代码:

//系统图像列表的句柄

HIMAGELIST himlSmall;

//存放文件信息的结构

SHFILEINFO sfi;

//存放树型控件中的节点的信息

TV_INEM tvi;

//向树型控件中插入节点时使用的结构

TV_INSERTSTRUCT tvis;

//欲插入节点的前一节点的句柄

HTREEITEM hParent=TVI_FIRST;

//欲节点的父节点的句柄

HTREEITEM hParent=TV_ROOT;

//某一文件夹的IshellFolder接口指针

LPSHELLFOLDER lpsf=0;

//IenumiDList接口的指针

LPENUMIDLIST lpe=0;

//lpi为一PIDL

LPITEMIDLIST lpi=0;

//IMalloc接口的指针

LPMALLOC lpMalloc=0;

//枚举的个数

ULONG ulFetched;

//存放显示名称的缓冲区

char szBuff[MAX_PATH];

//获得系统图像列表,并把它赋给CtestView的CtreeCtrl控件

himlsmall=(HIMAGELIST)SHGetFileinfo(“C:\\”,0,&

sfi, sizeof(SHFIEINFO), SHGFI_SYSICONINDEX|SHGFI_SMALLICON);

m_lmageList.Attach(himlsmall);

GetTreeCtrl().SetlmageList(&m_imageList,TVSIL_NORMAL);

//获得Imalloc接口的指针

SHGetMalloc(&lpMalloc);

//获得“桌面”文件夹的IshellFolder接口指针

SHGetDesktopFloder(&lpsf);

//创建一个“桌面”的绝对PIDL

lpi=(LPITEMIDLIST)lpMalloc->Alloc(sizeof(USHORT0));

*((USHORT*)lpi)=0

//设置要插入的树节点信息

tvi,mask=TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE|

TVIF_CHILDREN;

tvi.cchTextMax=MAX_PATH;

//设置显示名称

tvi.pszText=_T(“桌面”);

//获得标准图标和展开时的图标

tvi.ilmage=Tetltemlcon(lpi,NULL);

tvi.iSelectedlmage=Getltemlcon(lpi,SHGFI_OPENICON);

//设置插入位置

tvis.item=tvi;

tvis.hlnsertAfter=TVI_FIRST;

tvis.hParent=TVI_ROOT;

//插入根节点

hpParent=GetTreeCtrl().Instrtltem(& tvis);

//释放lpi所占的空间

lpMalloc->Free(lpi);

//获得“桌面”文件夹的IenumiDList接口指针lpe

lpsf->EnumObjects(m_hWnd, SHCONTF_FOLDERS |

SHCONTF_NONFOLDERS,& lpe);

//枚举“桌面”下的各个子文件夹

while(S_OK= =lpe-> Next(1,&lpi,&ulFetched))

{

//获得lpi表示的子文件夹的显示名称

GetName(lpsf,lpi,SHGDN_NORMAL,szBuff);

tvi.pszText=szBuff;

// 获得该项的图标

//由于是“桌面”下的直接子项,所以它的相对PIDL与绝对PIDL是一致的

tvi.ilmage=Getltemlcon(lpi,NULL);

tvi.iSelectedimage=Getltemlcon(lpi,SHGFI_OPENICON);

//设置插入位置

tvis.item=tvi;

tvis.hinsertAfter=hPrev;

tvis.hParent=hParent;

//插入节点

hPrev=GetTreeCtrl(). insertltem(& tvis);

//释放lpi所占的空间

ipMalloc->Free(lpi);

}

//释放Imalloc和IsshellFolder接口

lpMalloc->Release();

lpsf->Release();

//对生成的节点进行排序

GetTreeCtrl( ).SortChildren(hParent);

//将CtestView中的“桌面”节点展开

GetTreeCtrl( ).Selectltem(hParent);

GetTreeCtrl( ).Expand(hParent,TVE_EXPAND);

最后,响应CtestView的WM_DESTROY消息,加入以下代码:

//由于使用了系统图像列表,退出时必须释放对它的所有权

//否则,退出后Windows将一个图标没有

m_imageList.Detach( );

这个演示程序的效果如下图所示:

  1. 后记

  由于篇幅的关系,本文所举的例子只能非常简单的演示一下外壳名字空间的浏览,很多较复杂的编程方法都没有表现出来。最后,希望本文能够起到抛砖引玉的作用,让更多的开发者认识与使用外壳名字空间,开发出更好的程序来。

参考文献

  1. MicrosoftCorporation. Microsoft Windows95程序员指南,清华大学出版社,1996
  2. StefamoMaruzzi.Windows95开发者必读,电子工业出版社,1997

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=18943

15/03/2008

Foxmail中配置收取Gmail邮件

配置电子邮件客户端:Foxmail 5.0
在 Gmail 帐户 中启用 POP。

打开 Foxmail 5.0。
单击"帐户"菜单,然后选择"属性"。

选择"个人信息",输入您的姓名,作为外发邮件"发件人:"字段中显示的内容。
输入您的完整 Gmail 电子邮件地址 (username@gmail.com) 。
打开"服务器信息"页, 选中"我的服务器需要验证"旁边的复选框。
在"接收邮件(POP3)服务器:"字段中输入"pop.gmail.com"。
在"发送邮件服务器 (SMTP):"字段中输入"smtp.gmail.com"。
在"登录信息"部分,输入您的 Gmail 用户名(包括"@gmail.com")及输入您的 Gmail 密码。
单击"高级"标签。
选中此服务器要求安全连接 (SSL)"旁边的复选框。
在"发送邮件 (SMTP):"字段中输入"465"。
选中"接收邮件 (POP3)"下"此服务器要求安全连接 (SSL)"旁边的复选框。此端口将更改为 995。单击"确定"。
恭喜!您已经完成Foxmail的客户端配置,可以发送和接收 Gmail 邮件了。
Gmail 中启用 POP 之后单击"保存更改"了吗??为确保 Gmail 可以与您的电子邮件客户端通信,请务必在 Gmail"邮件设置"网页上单击"保存更改"。
11/03/2008

fctix deb安装包

 

'GirlDog' 3.5-070703-xft .deb文件

fcitx--'GirlDog' 3.5-070403-xft .deb包
创建于 Thu, 26 Apr 2007 22:38:08 +0800

下载

http://www.debsir.org/download/debs/fcitx_3.5-3_i386.deb

或添加源
deb http://www.debsir.org/download debs/

/etc/apt/sources.list
然后运行
#apt-get update
#apt-get install fcitx

更新至 fcitx-070507.tar.bz2
--Tue May 8 10:02:52 CST 2007

更新至 fcitx-3.5-070703.tar.bz2
Wed Aug 1 08:24:59 CST 2007

 

 

PS:文章来自http://www.debsir.org/main/?q=node/164

debsir是个不错的网站

关于Scim安装

尝试制作scim1.4的deb,记录过程如下:

sudo apt-get install gtk2-engines-dev
sudo apt-get install build-essential
sudo apt-get install dh-make
sudo apt-get install debhelper
sudo apt-get install fakeroot
#scim 完成
mkdir scim
cd scim
wget http://jaist.dl.sourceforge.net/sourceforge/scim/scim-1.4.0.tar.gz
tar zxvf scim-1.4.0.tar.gz
cd scim-1.4.0
dh_make -e oneleaf@gmail.com -f ../scim-1.4.0.tar.gz
dpkg-buildpackage -rfakeroot
cd ../../
#scim-tables 完成
mkdir scim-tables
cd scim-tables
wget http://jaist.dl.sourceforge.net/sourceforge/scim/scim-tables-0.5.1.tar.gz
tar zxvf scim-tables-0.5.1.tar.gz
cd scim-tables-0.5.1
dh_make -e oneleaf@gmail.com -f ../scim-tables-0.5.1.tar.gz
dpkg-buildpackage -rfakeroot
cd ../../
#scim-pingyin 完成
mkdir scim-pingyin
cd scim-pingyin
wget http://jaist.dl.sourceforge.net/sourceforge/scim/scim-pinyin-0.5.0.tar.gz
tar zxvf scim-pinyin-0.5.0.tar.gz
cd scim-pinyin-0.5.0
dh_make -e oneleaf@gmail.com -f ../scim-pinyin-0.5.0.tar.gz
dpkg-buildpackage -rfakeroot
cd ../../
#scim-qtimm 制作失败,需要打补丁的Qt lib,放弃了。
sudo apt-get install libqt3-dev
wget http://jaist.dl.sourceforge.net/sourceforge/scim/scim-qtimm-0.8.95.tar.bz2
tar jxvf scim-qtimm-0.8.95.tar.bz2
cd scim-qtimm-0.8.95
dh_make -e oneleaf@gmail.com -f ../scim-qtimm-0.8.95.tar.bz2
dpkg-buildpackage -rfakeroot
configure: error:
The Qt library was not compiled with the qt-immodule patch
applied please download it from
http://immodule-qt.freedesktop.org/Software/ImmoduleQtDownload and recompile Qt.
make: *** [config.status] 错误 1
cd ../../
#skim 完成
mkdir skim
cd skim
sudo apt-get install kde-core
sudo apt-get install kdelibs4-dev
wget http://jaist.dl.sourceforge.net/sourceforge/scim/skim-1.4.0.tar.bz2
tar jxvf skim-1.4.0.tar.bz2
cd skim-1.4.0
dh_make -e oneleaf@gmail.com -f ../skim-1.4.0.tar.bz2
export QTDIR=/usr/lib/kde3
vim debian/rules
#将 CFLAGS="$(CFLAGS)" ./configure --host=$(DEB_HOST_GNU_TYPE) --build=$(DEB_BUILD_GNU_TYPE) --prefix=/usr --mandir=\$${prefix}/share/man --infodir=\$${prefix}/share/info 行,修改为 CFLAGS="$(CFLAGS)" ./configure --prefix=/usr
sudo dpkg-buildpackage -rfakeroot
cd ../../
#scim-fcitx 完成
mkdir scim-fcitx
cd scim-fcitx
wget http://jaist.dl.sourceforge.net/sourceforge/scim/scim-fcitx.3.1.1.tar.bz2
tar jxvf scim-fcitx.3.1.1.tar.bz2
mv fcitx scim-fcitx-3.1.1
cd scim-fcitx-3.1.1
dh_make -e oneleaf@gmail.com -f ../scim-fcitx.3.1.1.tar.bz2
sudo dpkg-buildpackage -rfakeroot
cd ../../

./configure的问题

./configure的问题

 

(PS:ubuntu默认没装gcc,需要先装下sudo apt-get install gcc,在configure时,会出错configure: error: C compiler cannot create executables,应该是gcc的一些依赖的lib没装好。。。

)

 

 

 

错误: C compiler cannot create executables
原因:
解决:sudo apt-get gcc libc6-dev
错误:checking for C compiler default output... configure: error: C compiler cannot create executables
原因:
解决:sudo apt-get install libc6-dev
错误:configure: error: C++ preprocessor "/lib/cpp" fails sanity check
原因:gcc的组件没装全
解决:apt-get install build-essential
错误: Can't find X includes. Please check your installation and add the correct paths!
原因:没有X的包含文件
解决:安装xlibs-dev即可
错误: Qt (>= Qt 3.0) (headers and libraries) not found. Please check your installation!
原因:查找提供qt的lib&&headers的软件包,并安装之
解决:apt-get install libqt3-headers libqt3-mt-dev
错误:in the prefix, you've chosen, are no KDE headers installed. This will fail.
So, check this please and use another prefix!
原因:install a KDE application in a Gnome environment。
解决:which basically means its going to want to install a lot of KDE specific packages to work. This 'configure:error'
is due to it expecting you to be running KDE and again refers to some 'headers'.
sudo apt-get update
sudo apt-get install kdelibs4-dev kdelibs4c2a
错误:./admin/cvs.sh: 585: autoconf: not found
原因:
解决:apt-get install autoconf
错误: *** GTK >= 2.4.0 not installed! ***
原因:没装GTK
解决:apt-get build-dep gedit
错误:heching for gtk-config... no
checking for GTK - version = 1.2.0... no
*** The gtk-config script installed by GTK could not be found
*** If GTK was installed in PREFIX, make sure PREFIX/bin is in
*** your path, or set the GTK_CONFIG enviroment variable to the
*** full path to gtk-config.
configure: error: Cannot find GTK: Is gtk-config in path?
原因:
解决:sudo apt-get install libgtk1.2-dev
问题:eclipse中encoding不支持中文
解决:编辑/var/lib/locales/supported.d/local,加一行zh_CN.GBK GBK,执行sudo locale-gen
错误:gnome.h: No such file or directory
错误: No package 'libpanelapplet-2.0' found
原因:
解决:sudo apt-get install gnome-panel
问题:eva不弹出输入法
解决:sudo apt-get install scim-qtimm
问题:No package 'gtk+-2.0' found
No package 'gtksourceview-1.0' found
No package 'libgnomeui-2.0' found
No package 'libglade-2.0' found
No package 'libgnomeprintui-2.2' found
解决:sudo apt-get install libgtk2.0-dev libgtksourceview-dev libgnomeui-dev libglade2-dev libgnomeprint2.2-dev
问题:No package 'libpanelapplet-2.0' found
解决:sudo apt-get install libpanelappletmm-2.6-dev

07/03/2008

成功测试管理的九大原则

http://www.51testing.com/?uid/35    songfun

  简介
  许多测试管理者是从技术部门进到管理阶层的。尽管他们有可能受过很多测试或软件工程的培训和指导,但他们还是很难经常从失败和错误中学到管理技巧。作为一个管理者,你有两项基本工作:找出为你工作的最好的员工并且建立一个能够使员工完成工作的环境(使他们最好地完成工作)。这篇文章讲述了一些我学过的关于这些管理工作的经验。
  总是那些人――帮助人们最好地完成工作
  1. 为工作雇佣最好的员工
  我遇到许多管理者,他们要雇佣的员工仅仅是他们上一个雇佣的翻版。作为一个测试管理者,你必须对你需要什么人做出评估。假设现在你的部门满是极好的探索型的测试员。如果你还要雇另一个探索型的测试员,也许比你现在的要好,但是他对你的空白领域有作用吗?也许有,也许没有。
  工作的最佳人选也许就是你现在这个小组里所没有的类型。最佳人选或许并不“适合”你通常的工作方式。作为一个测试管理者,雇佣一个最佳的员工要用发展的雇佣策略,面试时要检验他是否符合这个策略。这可以让你找到最适合这份工作的人员,他能够完成必要的工作。
  2.安排每周与你的每个小组成员在不被打扰的环境进行谈话
  最为一个测试经理,主要工作之一就是定期的评定你的组织做了些什么并且是怎样做的。你还要为你的员工做一个报告,关于充分了解他们正在做什么和他们是怎样做的,以此来给做他们正式的和非正式的工作成绩考核。如果你没有了解到每个人的动态你就不应该对你的报告满意。
  我定期地给我的小组成员每周在不被打扰的条件下做一对一的谈话。(当我管理12个员工的时候,我安排在另外一周会见另一些人)。我每周用30分钟来和每个员工谈谈他们的工作:他们工作中的问题或是意见;他们是否需要帮助,他们的表现和他们达到目标的兴奋。我一般安排一周的某天来进行一对一谈话。我事先安排出和每个人的特定时间,接下来我亲自会见他们每个人。如果我们不能把所有需要谈到的细节都包括,我们会安排另外一个时间来继续。
  许多管理者说他们没有时间在一周会见每一个员工来谈他们的工作。据我的经验,如果我不能安排时间和我的员工进行每周的谈话,他们会来打扰我的工作,因为他们无论如何还是必须要来找我。
  如果你安排和你员工的谈话,你必须减少计划外的打扰(既有他们的也有你自己的),并且更多的了解他们在做什么。当你清楚你的小组正在做的,你才能更有效率地帮助他们明确优先应该做的工作,重聚资源,重新计划工程的部分,排除障碍等等。
  3.假定员工知道如何完成他们的工作的人员
  因为很多管理者起初做的是技术工作,他们知道他们的员工现在从事的工作。他们认为他们现在知道。如果你已经管理了两三年,你也许还没有你的技术员工知道的多,尤其是怎么样完成日常工作。
  你或者你的前任者雇佣你小组的员工。假设你雇佣这些人因为你认为他们能够完成工作,如果你设想每个人都知道如何完成他们的工作,你将得到比假设他们都不知道怎么完成的更好的效果。即使有些员工在无论你设想是否都能成功完成工作,但是有些员工将会被你对他们的想法所影响工作。
  因为我知道我的员工都知道怎样做他们的工作,我给他们分派任务。问他们是否需要帮助,然后留他们独自完成(除非他们寻求帮助)。我的意思并不是你不应该在他们工作的时候和他们说话,你只是不该打扰他。打扰可以分为几种不同的形式:
  · 如果你在不知不觉的情况下来到他身后,来到他的肩膀旁边,问他:“进行的如何了?”,尤其是在他们绞尽脑汁仍不得其解时, 这将仍然不能使你对他们的要求达到。。
  · 如果你每天都问,更糟的是每个钟头都问,他们是如何做的。这看起来就像对你员工进行微机管理,很惹人烦。毕竟,你没有工作要做吗?另外, 他们会以为你认为他们不知道该如何完成工作。
  · 如果当他们没有问你意见,你说“我会用这种方法”。这种予以打击的帮助不会有用。
.如果你不确定怎样能知道你的员工是否胜任,和每一个小组成员商讨寻求帮助的时机。每个人,包括你自己,应该选择一个规则来知道他或她什么时候成为了一个令人讨厌的家伙了。我的一个客户有一个15分钟法规。如果有人在某方面令人讨厌持续15分钟以上,他就必须停止并且和别人谈谈他的工作。
  当你分配工作时,问问你的员工是否明白该做什么,他或她是否有方法完成。确定工作进程,如果员工遇到麻烦,他应该主动找你寻求帮助,但是如果你坚决干涉,你的员工将会把找你寻求帮助作为最后的解决方法。
  4.对待你的员工要用他们能接受的方式,而不是你可以自己可以接受的方式“对待别人要用你愿意接受的方式”(己之不予,勿施于人)――这条黄金法则可能会对许多生活中的纯的社交因素有效,但是并不是总对工作有用。
  有效率的管理者知道他的每一个员工需要怎样的对待方式。当其他的人更乐意接受更多的信息。一些人去需要特定的任务和指示。一些因为解决新的,很棒的,复杂的问题而更有冲劲,但是还有一些只是对他们已经知道如何去处理的问题而感到舒服。
  另外,针对于不同的工作,我们都喜欢不同的认同方式。金钱不是表示认同的唯一方式,你可以用其他的方式来酬劳你的员工。有些人喜欢对他个人的感谢,有人乐意在公众面前的认可,一些喜欢以M﹠M方式,或者是奖励电影票,还有人希望有团队的排队来庆祝。记住无论什么的激励你的方式都不一定能激励你小组的每一个其他成员。和你的小组成员们通过讨论来了解他们每个人对奖励比较喜欢的给予方式。创造一个好的工作环境
  5.重视结果而非时间
  许多认可建立在员工完成工作的时间上,而不是他们最后的成绩上。但是,花费在工作上的时间不一定和创造性有必然的联系。如果你真的想改善对创作性和工作效率的认可的话,不妨考虑保证你员工每周只工作40个小时。 我常常听到一种表示对员工的异议就是“你整整一天什么都做不出来。”假设你自己处在一个巨大的障碍前,考虑你可以做什么来解决的时候,你是不是取消了会议?你的小组成员能否井然有序地安排他们的工作以至于能够最大限度发挥创造性?
  当人们每周工作超过40个小时的时候,他们开始在工作的时候关心他们自己的事。他们花钱,他们给很久没有联系的人打电话,因为他们一直都在工作。
  一旦你创造了一个环境,让员工在工作时间完成工作,开始鼓励他们每周不要超过40小时,接着你就可以基于他们在40个小时能够完成的工作量给他们酬劳。我总是发现这样能够提升创造力(因为员工不能在太疲劳的状态下完成工作,这是因为他们在工作时不能关心自己)。
  当你开始注意规律,不仅仅是时间,还包括更容易地给员工的表现给予精确的适度的评价。你的员工是否完成了他们的计划和测试设计?当他们开发测试的时候,他们还要修订那些他们需要的改进的部分?(如果你只是注意有多少测试可以使用,我可以重复地开展相同的测试)计划每周工作40个小时,并且为你要在这段时间完成的工作付报酬。
  6.承认自己的错误
  每个人都会犯错。他们会因为忘记开会而使客户发怒。承认你犯错是令人尴尬的。我们中的许多人认为对小组承认自己犯错会失去尊严。
  如果你不是经常犯错,你承认错误的时候其实能够赢得尊敬。如果你忘记一次会议,你为此道歉,其他的人会理解你并且最终原谅你。
  不管你做了什么,不要否认或故意忽略你的失误。故意忽略不会让错误消失这只会让错误成长为怪物。最近,有一个委托人在会上对他的员工大声斥责。会后,他认识到他不应该那样在小组会上那样做。他只是想让他们安心工作,等过几天再道歉。
  我建议在他们对他积累怒气之前,应该用正确的方式和他的员工交谈。他起初不愿意,但是他后来还是温和地在两天后和他的每个员工单独进行了交谈。每个人都是这样对他说的:“我只是在会后才对你感到生气的。如果您能够立即和我谈谈,我就不会把它们积累起来。但是现在已经两天过去了,我仍然对您感到生气,事实上,我还更加恼火,我现在不能确信现在是否能相信您。我不介意你对我们大吼,但是我不能确定是否还会再这样做。
  我的客户不知道应该如何处理这种情况。他认为他的等待是正确的,但是却让问题变得更加严重。.他已经决定再也不会犯这样的错误了,而且会立刻和会员工交流。
  他的员工用了好几个月来重新相信他,但是我的这位客户的确通过承认过错而增加了他的个人魅力。现在,他和他的员工对这件事可以当做是玩笑来说。他们说这是他的认知和能力的巨大转折点。
  7.决定承担一个项目必须首先问你的组员是否有能力完成当你是一个下级的员工,你的老板对你说“我们是否可以在下个十月完成项目?”回答说“当然”会令人惊讶。但是,你的员工会因为你回答“我要考虑一下。”而表示赞赏。
  即使你已经在考虑这个问题,告诉了你的员工你们将来做它,你还是应该得到足够的信息来考虑。你应该从这几方面来看:
  ·一段时间内,你也许会因为另一件工作而感到对这个问题迷惑。
  · 也许有你正被其它需要考虑的问题所累,因为你不再有相同的时间像你第一次看到它的时候。
  · 如果你“训练”你的上司让你的回答有漏洞,你的上司会继续给你让你回答他的压力。
  当你与你的员工在做决定之前讨论问题时,你应该把这些和他们说一说:
  · 我想知道是什么让你想做这个项目。
  · 我不怕告诉我的上司要怎么处置它。
  在决定做一个项目之前先好好做考虑是一种对你员工的尊重。另外,考虑他们的想法也会使你从他们那里赢得尊重和忠诚。
  8.计划定期的培训
  作为管理学的一部分,测试是一种挑战和对规则的经常性的改变。因为经常的改变,要制定定期的培训计划。如果你没有基于不断的变化而培训你的员工,你就会有损失。
  培训可以是关于特定项目或者是技术。你可以进行训练用不同的方法:
  · 提供一个简单的午餐,让每个你的每个组员讨论一个特定的领域。这特别对同时要做很多不同项目的小组有用。当每个人做不同的项目,这会有助于每个人了解你小组所有的工程。
  · 做一个对每个部门的阶段说明。无论幸运与否,每个部门都会有和你小组相仿的工作,但是一般来说其它部门都不知道另外的部门在做什么。
  · 如果你们有交叉利益的小组,你可以让两个小组都展现他们各自为公司所作的项目,或者只是针对你的测试小组。
  · 邀请外面的专家来讲一个特定的技术或者一种项目。这些专家或者是专业的顾问,善演讲的人,也或是一个博学的朋友或同事。
  · 如果你买了工具或者已经进行了培训,考虑组织一个内部的“使用者”会议。人们可以在那里分享他们使用这种工具的感受并且讨论它的问题,优点和恶作剧。这特别对有缺陷的追踪系统和构造管理系统有效果。
  9.计划测试
  作为一个测试经理,你不可能有时间去做所有你想做的事。所以,计划你和你小组能做什么。作为测试经理,首先应该确定自己的任务,是在发布之前找到大的缺陷?还是评估软件的状态?或者是帮助开发经理在发布之前做风险评估?你的任务有可能是其中一项又或者是其中几项结合。无论是怎样,在进入的玩命工作时期之前, 对测试进行计划, 你组里的每个人都要竭尽全力你不需要做所有的事。你不是对所有事都计划而是精简,你就会有时间, 然后你就可以计算出你能再做什么。
  测试的计划是对每个产品或是对各个开发阶段的产品开展测试的策略。测试要多严格?什么测试不用进行?你在测试里要用硬件和软件的那些组合?什么样的组合不能作为彻底的,可能的,在所有的测试里都运用到的。
  测试是一种危险的评估。你和你项目里的其它成员能够进一步做出决定:你乐意对产品的测试部分和非测试部分冒多大的的险?
  一旦你决定要测试什么,对每个产品发展发布标准。发布标准是对每一项发布挑剔的重要性评判的客观的标准。“如果那样将会不错”不是步伐标准。“如果不那样做客户将会置我们于死地”才是发布标准的组成部分。
  如果你计划一个测试并和你的组员一起开展项目, 你不能一直只扮演一个守门人的角色。你无须停止准许运用.你和你的项目小组或者你和项目经理一起制定评判的标准。当你们都通过了这些标准,就可以交货了。加入你们没有达成共识 ,诚实的,决定你下一步该做什么。我所有做过的项目当中,我们都必须对发布标准达成共识,所以我们为此一直工作.一些客户提出了很苛刻的标准,我们最后也达成了共识。他们更换了文件当中的发布标准,
  解释他们在项目小组里的位置, 并且支配管理, 最后交接工作。

06/03/2008

TOMCAT集群配置

运行环境:Windows2003 Server SP4 + J2SDK1.5.0 +Tomcat5.5.9

准备软件:Tomcat 5.5.9   JDK1.5.0

一.配置过程

1、安装JDK1.5.0。采用默认安装就可以。

2、安装tomcat到C:\ tomcat 50,采用完全安装,该程序用于实现负载均衡功能。

3、将tomcat50的内容进行完全复制,生成C:\ tomcat 51、C:\ tomcat 52、C:\ tomcat 53,分别用做集群中的节点。

4、修改负载均衡规则,使其遵循轮循算法(RoundRobin)。

4.1将testLB.jsp复制到c:\tomcat50\webapps\balancer文件夹中

4.2将文件夹classes复制到c:\tomcat50\webapps\balancer\WEB-INF文件夹中

4.3修改c:\web\tomcat50\webapps\balancer\WEB-INF\web.xml文件如下:
BalancerFilter
/LoadBalancer

5、在集群中每个节点下,部署clusterapp应用。Clusterapp包含sessiondata.jsp,test.jsp,脚本。test.jsp是用来验证节点状态的页面;sessiondata.jsp是用来响应用户所发送的请求,同时记录会话ID,会话的起始和最后时间,提供用户增加、修改、删除会话的属性字段和属性值,可以通过此来判断会话的持续与否。

6、将log4j的log4j-1.2.9.jar复制到每个%tomcat%/common/ lib下,使其将会话日志统一存储到指定的文件中。

7、修改每个tomcat的server.xml配置文件,参数如下表所示。

配置              Instance 1    Instance 2       Instance 3    Instance 4
Instance Type    Load Balancer    Node 1       Node 2             Node 3
Code name             TC-LB             TC01       TC02             TC03
Home Directory    c:/tomcat50    c:/tomcat51      c:/tomcat52    c:/tomcat53
Server Port    8005          9005      10005            11005
Connector             8080         9080               10080            11080
JK2 AJP Connector    8009         9009               10009            11009
Cluster mcastAddr    228.0.0.4         228.0.0.4    228.0.0.4           228.0.0.4
Cluster mcastPort    45564        45564              45564           45564
tcpListenAddress    127.0.0.1        127.0.0.1    127.0.0.1           127.0.0.1
Cluster tcpListenPort 4000        4001             4002           4003

8、修改c:\web\tomcat50\webapps\balancer\WEB-INF\config\ruler.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>

        serverInstance="1"
        maxServerInstances="3"
        tcpListenAddress="127.0.0.1"
        tcpListenPort="4001"
        testWebPage="http://localhost:9080/clusterapp/test.jsp"
        redirectUrl="http://localhost:9080/clusterapp/sessiondata.jsp" />
        serverInstance="2"
        maxServerInstances="3"
        tcpListenAddress="127.0.0.1"
        tcpListenPort="4002"
        testWebPage="http://localhost:10080/clusterapp/test.jsp"
    redirectUrl="http://localhost:10080/clusterapp/sessiondata.jsp" />

        serverInstance="3"
        maxServerInstances="3"
        tcpListenAddress="127.0.0.1"
        tcpListenPort="4003"
        testWebPage="http://localhost:11080/clusterapp/test.jsp"
    redirectUrl="http://localhost:11080/clusterapp/sessiondata.jsp" />

        redirectUrl="http://localhost:8080/balancer/testLB.jsp" />

9、启动tomcat的应用。手动双击每个tomcat/bin的startup.bat程序。

二.Web请求集群环境下流程

1、启动访问起始页(http://localhost:8080/balancer/testLB.jsp)

2、JSP重定向请求到负载均衡过滤文件(http://localhost:8080/balancer/LoadBalancer)

3、负载均衡的tomcat接受请求,根据制定的负载均衡算法,重定向到可用的集群节点(TC01、TC02、TC03)

4、对应集群中节点的sessiondata.jsp(位于clusterapp应用下)页面将启动。

5、sesiondata.jsp将在web上显示会话的详细信息(如会话ID,最后访问时间)
在测试的过程中采用RoundRobin算法,通过对Instance 1发起多个http://localhost:8080/balancer/testLB.jsp测试请求,发现每个请求返回页面的端口号不完全一致,在9080、10080、11080端口不规则的出现,即客户端的访问请求按照RoundRobin算法被重定向到不同的服务器上进行处理,说明该负载均衡规则在这个测试中得到正确的体现。在关闭集群中一个节点后再启用它,能够自动发现该节点,并为该节点分配请求。

负载均衡技术介绍

          由于网络的数据流量多集中在中心服务器一端,所以现在所说的负载均衡,多指的是对访问服务器的负载进行均衡(或者说分担)措施。负载均衡,从结构上分为本地负载均衡和地域负载均衡(全局负载均衡),前一种是指对本地的服务器集群做负载均衡,后一种是指对分别放置在不同的地理位置、在不同的网络及服务器群集之间作负载均衡。
   每个主机运行一个所需服务器程序的独立拷贝,诸如Web、FTP、Telnet或e-mail服务器程序。对于某些服务(如运行在Web服务器上的那些服务)而言,程序的一个拷贝运行在群集内所有的主机上,而网络负载均衡则将工作负载在这些主机间进行分配。对于其他服务(例如e-mail),只有一台主机处理工作负载,针对这些服务,网络负载均衡允许网络通讯量流到一个主机上,并在该主机发生故障时将通讯量移至其他主机。
   ■DNS
   最早的负载均衡技术是通过DNS来实现的,在DNS中为多个地址配置同一个名字,因而查询这个名字的客户机将得到其中一个地址,从而使得不同的客户访问不同的服务器,达到负载均衡的目的。
   DNS负载均衡是一种简单而有效的方法,但是它不能区分服务器的差异,也不能反映服务器的当前运行状态。当使用DNS负载均衡的时候,必须尽量保证不同的客户计算机能均匀获得不同的地址。由于DNS数据具备刷新时间标志,一旦超过这个时间限制,其他DNS服务器就需要和这个服务器交互,以重新获得地址数据,就有可能获得不同IP地址。因此为了使地址能随机分配,就应使刷新时间尽量短,不同地方的DNS服务器能更新对应的地址,达到随机获得地址,然而将过期时间设置得过短,将使DNS流量大增,而造成额外的网络问题。DNS负载均衡的另一个问题是,一旦某个服务器出现故障,即使及时修改了DNS设置,还是要等待足够的时间(刷新时间)才能发挥作用,在此期间,保存了故障服务器地址的客户计算机将不能正常访问服务器。
   尽管存在多种问题,但它还是一种非常有效的做法,包括Yahoo在内的很多大型网站都使用DNS。
   ■代理服务器
   使用代理服务器,可以将请求转发给内部的服务器,使用这种加速模式显然可以提升静态网页的访问速度。然而,也可以考虑这样一种技术,使用代理服务器将请求均匀转发给多台服务器,从而达到负载均衡的目的。
   这种代理方式与普通的代理方式有所不同,标准代理方式是客户使用代理访问多个外部服务器,而这种代理方式是代理多个客户访问内部服务器,因此也被称为反向代理模式。虽然实现这个任务并不算是特别复杂,然而由于要求特别高的效率,实现起来并不简单。
   使用反向代理的好处是,可以将负载均衡和代理服务器的高速缓存技术结合在一起,提供有益的性能。然而它本身也存在一些问题,首先就是必须为每一种服务都专门开发一个反向代理服务器,这就不是一个轻松的任务。
   代理服务器本身虽然可以达到很高效率,但是针对每一次代理,代理服务器就必须维护两个连接,一个对外的连接,一个对内的连接,因此对于特别高的连接请求,代理服务器的负载也就非常之大。反向代理方式下能应用优化的负载均衡策略,每次访问最空闲的内部服务器来提供服务。但是随着并发连接数量的增加,代理服务器本身的负载也变得非常大,最后反向代理服务器本身会成为服务的瓶颈。
   ■地址转换网关
   支持负载均衡的地址转换网关,可以将一个外部IP地址映射为多个内部IP地址,对每次TCP连接请求动态使用其中一个内部地址,达到负载均衡的目的。很多硬件厂商将这种技术集成在他们的交换机中,作为他们第四层交换的一种功能来实现,一般采用随机选择、根据服务器的连接数量或者响应时间进行选择的负载均衡策略来分配负载。由于地址转换相对来讲比较接近网络的低层,因此就有可能将它集成在硬件设备中,通常这样的硬件设备是局域网交换机。
   当前局域网交换机所谓的第四层交换技术,就是按照IP地址和TCP端口进行虚拟连接的交换,直接将数据包发送到目的计算机的相应端口。通过交换机就能将来自外部的初始连接请求,分别与内部的多个地址相联系,此后就芏哉庑┮丫⒌男槟饬咏薪换弧R虼耍恍┚弑傅谒牟憬换荒芰Φ木钟蛲换换湍茏魑桓鲇布涸鼐馄鳎瓿煞衿鞯母涸鼐狻?
   由于第四层交换基于硬件芯片,因此其性能非常优秀,尤其是对于网络传输速度和交换速度远远超过普通的数据包转发。然而,正因为它是使用硬件实现的,因此也不够灵活,仅仅能够处理几种最标准的应用协议的负载均衡,如HTTP 。当前负载均衡主要用于解决服务器的处理能力不足的问题,因此并不能充分发挥交换机带来的高网络带宽的优点。
   ■协议内部支持
   除了这三种负载均衡方式之外,有的协议内部支持与负载均衡相关的功能,例如HTTP协议中的重定向能力等,HTTP运行于TCP连接的最高层。客户端通过端口号80的TCP服务直接连接到服务器,然后通过TCP连接向服务器端发送一个HTTP请求。在服务器分清客户端所需的网页和资源之前,至少要进行四次TCP的数据包交换请求。由于负载平衡设备要把进入的请求分配给多个服务器,因此,它只能在TCP连接时建立,且HTTP请求通过后才能确定如何进行负载的平衡。当一个网站的点击率达到每秒上百甚至上千次时,TCP连接、HTTP报头信息以及进程的时延已经变得很重要了。在HTTP请求和报头中有很多对负载平衡有用的信息。首先,也是最重要的一点是,我们可以从这些信息中获知客户端所请求的URL和网页,利用这个信息,负载平衡设备就可以将所有的图像请求引导到一个图像服务器,或者根据URL的数据库查询内容调用CGI程序,将请求引导到一个专用的高性能数据库服务器。惟一能局限这些信息获取的因素是负载平衡设备本身的灵活程度。事实上,如果网络管理员熟悉Web内容交换技术,他可以仅仅根据HTTP报头的cookie字段来使用Web内容交换技术改善对特定客户的服务,如果能从HTTP请求中找到一些规律,还可以充分利用它作出各种决策。除了TCP连接表的问题外,如何查找合适的HTTP报头信息以及作出负载平衡决策的过程,是影响Web内容交换技术性能的重要问题。
   但它依赖于特定协议,因此使用范围有限。根据现有的这些负载均衡技术,并应用优化的均衡策略,来实现后端服务器负载分担的最优状态。