问与答(12则)
北京 zhangyinglei: VB中的CommonDialog可实现Open、Print等功能,但其位置无法调整到父窗口中心或屏幕中心,请问有何办法修改这些对话框的位置?
李海:如果是在C++或Delphi中,可以使用钩子(hook)函数,然后在钩子函数中设置对话框的位置。不过在VB中使用钩子(hook)函数就麻烦了,这是VB的弱项。不过VB也有自己的办法。要想解决这个问题,首先要找出CommonDialog是如何设置其对话框位置。首先在一个Form中放置一个CommonDialog控件,然后不断移动Form在屏幕的位置,并激活CommonDialog。你会发现CommonDialog总是出现在Form的左上角,当Form出现在屏幕的左侧或上部时,这一点非常明显。但当Form出现在屏幕下方或右侧时,CommonDialog会稍微做调整,以确保整个对话框都能显示在屏幕范围内。如果你的Form比较靠近屏幕中心,那么CommonDialog自然也会出现在屏幕中心。利用这一特点,我们可以建立一个空窗体,称为MyCDForm,然后在其上放置一个CommonDialog控件。这个MyCDForm只用来放置CommonDialog控件,没有其他用途。然后输入下面这个函数。
Private Function ChooseFile(argLeft As Single, argTop As Single) As Boolean
' 设置为没有文件被选择
ChooseFile = False
' 移动MyCDForm位置
MyCDForm.Left = argLeft
MyCDForm.Top = argTop
' 设置CommonDialog控件
MyCDForm!CommonDialog1.CancelError = True
On Error GoTo OpenError
' 显示CommonDialog
MyCDForm!CommonDialog1.ShowOpen
' 卸载MyCDForm
Unload MyCDForm
ChooseFile = True
Exit Function
OpenError:
' 用户按下Cancel键
Unload MyCDForm
Exit Function
End Function
当你的程序需要调用Open对话框时,使用ShowOpen就可以了。argLeft和argTop是Open对话框在屏幕上出现的位置的左上角的坐标。从这个函数可以看出,实际上我们是将MyCDForm的位置该为argLeft和argTop,而利用Open对话框的位置总是出现在其父窗口的左上角这一特性来改变Open对话框的屏幕位置。类似地,你也可以显示其他的对话框。如果你想让对话框出现在屏幕中央,则argLeft = (Screen.Width - 对话框宽度) \ 2,argTop = (Screen.Height - 对话框高度) \ 2。对于屏幕大小为800*600个像素,显示Open对话框的情况,这两个值大致均为1500。
陈雷:在Windows 95中,通常情况下,右单击文件图标,弹出的菜单上总有“Send to...(发送到)”一项。该项右侧子菜单上的发送目标一般都有“3.5" Floppy Disk A(3.5软盘A)”、“My Briefcase(我的公文包)”等项。我的问题是:能否通过修改Registry(Windows注册表)的方法,为该子菜单加上自己的发送目标(如C:\My Documents等)?
李海:这个问题用不着修改Registry。要想加入自己的发送目标,首先需建立一个快捷方式(shortcut),在快捷方式的命令行上填写发送目标,这个发送目标既可以是目录,如“C:\My Documents”(由于这是一个长文件名,所以在输入命令行时要加上引号。),也可以是应用程序,如NotePad,Windows 95将文件名作为命令行参数传递给该程序。然后将这个快捷方式放如Windows 95目录下的SendTo目录。然后退出资源管理器(如不退出资源管理器,有时Send To菜单会显示不正常),再进入资源管理器时,就可以看到自己添加的发送目标了。这里,需要说明一点,Windows 95在向目标传递文件名时,通常传递的是短文件名(如myfile~1.doc),这对大多数程序影响不大,但对个别程序会有影响,如在这种情况下,WordPad在存盘时,会将文件的长文件名改为短文件名。
赵砥:我用VB的sendkeys编写了一个向其他程序模拟键盘发送字符的工具,因VB编的程序体积太大,我想用Delphi重写,使用sendmessage等API函数,但我想找到一个用鼠标点一下其他进程的窗口便可获得该窗口的线程id和窗口句柄的方法,请指点一下。(使用findwindow获得窗口句柄要输入窗口标题,不好。)
李海:首先需要说明要在Delphi 实现Sendkeys功能,应该使用Journal Playback钩子(hook)函数,而不是使用SendMessage函数。下面我们来介绍如何利用鼠标移动让用户选择窗口,而程序进一步得到窗口的句柄。Windows API中有一个函数WindowFromPoint,只要知道鼠标的位置(屏幕坐标),就可以得到该位置所属的窗口的句柄,有了句柄,就可以利用其他的函数得到更多的信息。如果鼠标在程序的窗口中移动,可以得到MouseMove事件。要想鼠标在窗口外部移动时,仍能得到鼠标事件,必须使用SetCapture函数。下面这个例子就是利用这两个函数来实现你所要求的功能。
type
TForm1 = class(TForm)
…………
public
procedure InvertTracker(hwndDest : Integer);
end;
…………
var
Form1: TForm1;
mlngHwndCaptured: Integer;
hWndLast: Integer;
…………
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var pt : TPoint;
begin
if GetCapture() <> 0 then // 处于捕捉状态
begin
pt.X := X;
pt.Y := Y;
ClientToScreen(pt); // 获得鼠标的屏幕位置
// 获得窗口句柄
mlngHwndCaptured := WindowFromPoint(pt);
if hWndLast <> mlngHwndCaptured then
begin
if hWndLast <> 0 then // 使窗口边框加粗
InvertTracker(hWndLast);
InvertTracker(mlngHwndCaptured);
hWndLast := mlngHwndCaptured;
end
end;
// 显示坐标和窗口句柄
Caption := 'X: ' + IntToStr(x) + ', Y: ' + IntToStr(y)
+ ', hWnd: ' + IntToStr(mlngHwndCaptured);
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if SetCapture(handle) <> 0 then // 开始捕捉
Cursor := crUpArrow;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var strCaption: PChar;
begin
If mlngHwndCaptured <> 0 Then
begin // 获得窗口标题
strCaption := StrAlloc(1000);
GetWindowText(mlngHwndCaptured, strCaption, 1000);
Caption := StrPas(strCaption);
InvalidateRect(0, PRect(0), True);
mlngHwndCaptured := 0;
Cursor := crDefault;
ReleaseCapture;
StrDispose(strCaption);
hWndLast := 0;
end
end;
// 使窗口边框变粗
procedure TForm1.InvertTracker(hwndDest: Integer);
var
hdcDest, hPen, hOldPen, hOldBrush : Integer;
cxBorder, cxFrame, cyFrame, cxScreen, cyScreen, cr : Integer;
rc : TRect;
Const NULL_BRUSH = 5;
Const R2_NOT = 6;
Const PS_INSIDEFRAME = 6;
begin
cxScreen := GetSystemMetrics(0);
cyScreen := GetSystemMetrics(1);
cxBorder := GetSystemMetrics(5);
cxFrame := GetSystemMetrics(32);
cyFrame := GetSystemMetrics(33);
GetWindowRect(hwndDest, rc);
hdcDest := GetWindowDC(hwndDest);
SetROP2(hdcDest, R2_NOT);
cr := clBlack;
hPen := CreatePen(PS_INSIDEFRAME, 3 * cxBorder, cr);
hOldPen := SelectObject(hdcDest, hPen);
hOldBrush := SelectObject(hdcDest, GetStockObject(NULL_BRUSH));
Rectangle(hdcDest, 0, 0, rc.Right - rc.Left, rc.Bottom - rc.Top);
SelectObject(hdcDest, hOldBrush);
SelectObject(hdcDest, hOldPen);
ReleaseDC(hwndDest, hdcDest);
DeleteObject(hPen);
end;
// 将窗口移动到左上角,并减少窗口高度,便于操作
procedure TForm1.FormCreate(Sender: TObject);
begin
Left := 0;
Top :=0;
ClientHeight := 76;
end;
运行该程序时,先在程序窗口内点一下,然后按住鼠标左键不放,移动鼠标,这时你会看到程序窗口的标题位置不断显示鼠标的当前位置(窗口坐标)和鼠标所在位置的窗口句柄。同时,被选中的窗口边框加粗,一旦放开左键,则程序窗口的标题就改为所选中窗口的标题。相信许多人对这种操作方式都会感到熟悉,因为象Spy++(Visual C++)、Magic Mouse、Capture Professional等很多软件都是采用类似的操作来选择窗口的。
hehui: 我想在一Windows程序中控制一个DOS程序,模拟键盘动作向DOS程序发一个键盘消息。该DOS程序是别人编好的一个普通的键盘控制的程序。实际是想通过一个Windows程序中转键盘命令,间接地控制DOS程序(不让别人看到DOS程序的界面,但还是让它干活),不知如何实现?进程/线程控制的API如CreateProcess()、PostThreadMessage()等可以实现吗?
李海:这不是一件简单的事情。按照标准的Windows做法,应该利用设备驱动开发包DDK设计虚拟设备驱动VxD,由VxD来控制DOS程序的输入。不过VxD开发被公认是Windows开发中最困难的内容之一。
若想避免复杂的VxD编程,只能从DOS程序的输入缓冲区下手,因为一般的DOS程序都是直接或间接地读取键盘缓冲区。最常见的办法是利用DOS窗口的Paste功能,即选择DOS窗口的系统菜单,然后选择Edit,再选择Paste。这样,Windows将系统剪贴板上的内容输入DOS程序的键盘缓冲区。我们以VB程序为例说明这一过程。
Shell "f:\tc\sample.exe", 1
Clipboard.SetText "f:\tc\data" + Chr(13)
AppActivate "sample.exe"
SendKeys "% ep", True ‘ 激活Paste功能
AppActivate "Project1 - Microsoft Visual Basic [run]"
这里的SendKeys是模拟键盘输入,Delphi和C++可以使用Journal Playback钩子函数实现。这个VB程序调用sample.exe,然后利用粘贴功能输入路径和回车,DOS程序开始工作。我曾经利用这一程序来调用一个DOS下的计算程序,输入路径后,DOS程序将计算结果存盘,然后Windows程序再进行进一步地处理。但是这一过程,最好是让DOS程序显示,但不需用户干预。如果把DOS程序最小化,Sendkeys不容易成功。如果一定要最小化显示,也可以编写一个DOS程序,让该程序向键盘缓冲区写入键码,然后由这个DOS程序调用你想控制的DOS程序,这样可能运行得更稳定一些。但填充键盘缓冲区的这种方法对DOS程序有要求:
一、DOS程序不能清除键盘缓冲区。有些DOS程序在一次输入完成之后,进行下一次输入之前会自动清除键盘缓冲区,以避免误操作,但这会使我们的程序进行不下去。
二、DOS程序没有按任意键终止程序运行的功能。有些DOS程序,如HD-COPY在复制磁盘的过程中一旦用户按任何一个键就终止运行。
三、DOS程序不能使用Ctrl、Atl等组合键。
四、DOS程序的操作步骤不能太多。因为键盘缓冲区的大小是有限的。
总的来说,在Windows程序中控制DOS程序程序并非不可能,但这很大程度上要看DOS程序的工作方式。不过,以我的经验,只要可能最好把DOS程序改写为Windows程序,这才是最好的办法。
汕头 王博飞: 我在编制局域网上的C/S应用程序,不知道VB 5.0和VFP 3.0中有什么函数能够判断工作站是否已连网。是要使用WIN32 API吗?肯请指点。
李海:试试Win32 API的WNetGetConnection、WNetOpenEnum等函数。
YIJIANG:(1)我在JavaScript中用close()命令关闭一网页时会出现一对话框,能否不让它出现?
(2)在Web中如何使一图像象命令按钮一样赋予Event属性,如onClick、onMouseOver等?
李海:(1)Netscape 3.0以上版本支持的JavaScript 1.1规定:如果一个窗口是采用open()方法打开的,则使用close()关闭时不出现确认的对话框,而如果不是采用open()方法打开的,都要出现这一确认对话框。
(2)我们知道如果以图像作为链接时,可以使用这几种事件。我想你的问题是既想使用事件,又不希望链接到其他地方去。这个问题在Internet Explorer 4.x中不是什么问题,因为IE 4.x几乎支持所有元素的onClick、onMouseOver事件,但这在Netscape中就是问题了,因为Netscape只允许在<a></a>、<area></area>和<layer><layer>中使用onMouseOver等事件。我们利用<a>来解决这一问题(当然也可以使用<area></area>)。请看下面这段HTML代码:
<html>
<body>
<SCRIPT LANGUAGE="JavaScript">
function nothing()
{
}
</SCRIPT>
<A href="javascript:nothing()" onmouseover="window.status='Love me tender.';return true" onclick="alert('This link is just an example - it doesn\'t lead anywhere!')" ONMOUSEOUT="window.status='';return true"><IMG SRC="circle.gif" WIDTH="105" HEIGHT="97" BORDER="0"></a>
</body>
</html>
当鼠标在该图像上移动时(onMouseOver和 onMouseOut),窗口下部的状态条会发生变化,而当你点击图像时(onClick),会显示一个消息框,而不是链接到其他地方。这里的关键是我们使用了一个nothing函数,它什么也没做。
dongzy: 请尽可能多地提供免费下载VB控件的网址,最好是一些中文站点。
李海:网上提供免费下载VB控件的站点太多了。如果我要寻找一个VB控件,通常会到以下几个站点进行搜索:
这几个站点大概包括了超过95%的VB控件,如果在这几个站点都找不到线索,成功的机会就比较小了。国内的VB站点很多,大多数都是VB发烧友的个人主页,不过质量大多不高,绝大多数站点我觉得都不值得去2次。值得推荐给大家的是“尹强的VB 憩园”,它的站长尹强比较认真负责,更新得比较快,其中的“控件精选”栏目可能对于各种水平的VB爱好者都有帮助。另外,有三个中文站点,也提供免费下载VB控件,但如果你看上了哪个控件,都要掏钱购买。一个是http://eleauto.com/phpgb/index.php,这个站点的 SunRain ActiveX中文版是一个工业控制控件;另一个是http://www.cellsoft.cc/,它提供一个中文的表处理控件CELL,类似于DBGrid,有些特色;还有一个是上海奥林岛(http://www.grapecity.com/china/),这是一家代理国外的VB控件的公司,其产品包括FXTools Pro、Formula One等等。
刚看到你这个问题有一点疑惑,因为免费下载软件的站点实在很多,好象用不着我聒噪。许多VB控件都很便宜,比KV300等软件还要便宜,只可惜没有美金信用卡,即使下载了,也只能在一旁流口水。也许我应该多介绍一些下载“免费”的VB控件的站点。我只列出那些我觉得不错的站点,好的东西并不在多。由于很多VB站点都采用了ActiveX技术,所以请使用Internet Explorer 4.0访问以下站点,否则有些站点可能在Netscape中可能什么也看不到。
这个站点包括的都是Alvaro Redondo写的控件或软件库。虽然都是些小东西,但很有用。我比较喜欢他的DES加密和Base 64编码的软件库。
这个站点提供几个不错的界面增强方面的控件。
这是个不错的站点,有不少的VB控件,非常实用。
这个站点有三个关于界面方面的控件,外观相当专业。
这个站点的控制不多,功能似乎也不算强大,但都是日常编程中会用到的。
这个站点的控件并不多,比较多的是有关API控件的一些模块,比较适合对API调用感兴趣的读者。
这个站点已经很久没有更新了。
这个站点收集了Steve Bulter的几个ActiveX,并提供了VB源程序。这个站点还有一个GIF89控制(这个控制不是Steve Bulter的),如果你想在程序中加上动画GIF,就使用这个控件。
四川 王云山: 在Windows 95平台上怎样编写中断处理程序?
李海:标准的办法是使用Windows 95的设备驱动程序开发包DDK编写虚拟设备驱动程序。DDK提供了一系列底层控制手段,可以处理中断。不过DDK编程是比较麻烦,被公认是Windows 95编程的难点。
另一个办法是使用TVicHw32.DLL程序库(http://www.entechtaiwan.com/tools.htm)。这个软件可以实际上是一个虚拟设备驱动程序,利用它的接口,你可以在一般的应用程序中控制IRQ中断。这样编程会简单一些,适用于一些特殊的场合。该软件是共享软件,需注册(99美元)。
江苏常州 沙湘鹏:(1)我用VB 5.0编了一个很简单的ActiveX文档,按照微软的提示编译成.exe文件,再用setup wizard生成Internet下载程序,在本机上用IE 4.0浏览正常;但是在LAN上其他机器用IE 4浏览时,屏幕上一个进度条窗口很快一闪而过,然后浏览器窗口什么也不显示。我在微软站点上查找了有关的文章,并按其提示修改了有关文件,可是仍然没有效果。不知是什么原因?
(2)我用Access97建了一个数据库,其中的一个表table1用一个字段ID,类型为自动编号(长整型)。我想用一个SQL查询来查找ID等于某个号码的记录,程序代码片断如下:
Option Explicit
Dim db As Database
Dim rs1 As Recordset
Dim strsql1 As String
Dim tmp1, tmp2 As Variant
Private sub form_load()
Set db = OpenDatabase("c:\db1.mdb")
Private Sub Cmd1_Click()
'tmp1的值由窗体的文本框txt1得到
tmp1=txt1
tmp2=clng(tmp1)
strsql1 = "select * from table1 where table1.ID= '" & tmp2 & "' "
Set rs1 = db.OpenRecordset(strsql1)
rs1.MoveFirst
程序总是报告数据类型错误.我应该如何写这段代码呢?
李海:(1)问题出在Setup Wizard生成HTML文件上。这个文件一般有如下一段:
<SCRIPT LANGUAGE="VBScript">
Sub Window_OnLoad
Document.Open
Document.Write "<FRAMESET>"
Document.Write "<FRAME SRC=""UserDocument1.VBD"">"
Document.Write "</FRAMESET>"
Document.Close
End Sub
</SCRIPT>
这里,ActiveX Document程序是利用<FRAME SRC="...">调用的,这在本机浏览时不会遇到问题。但Internet Explorer在从服务器调用HTML时,它认为凡是出现在<FRAME SRC="...">中的都应该是HTML文档,如果无法识别该文件,就下载并保存该文件,你所看到的“屏幕上一个进度条窗口很快一闪而过”,就是IE在下载文件。若想解决这个问题,不要使用<FRAME SRC="...">,而改用<A></A>链接方式,一般就不会有问题了。
(2)你对SQL命令有一点误解。SQL语句“select * from table1 where table1.ID= 123”表示从表table1中寻找ID字段值为123的记录,这里的ID字段是数字型的。而“select * from table1 where table1.ID= '123'”表示ID字段为字符串型。按你的程序运行,假定tmp1得到的字符串是“123”,尽管你把这个字符串转换成为长整型tmp2,但字符串变量strsql1的值是“select * from table1 where table1.ID= '123'”,这样的结果只能理解为ID字段为字符串型,而实际ID字段为长整型。显然数据类型错误。而你使用clng函数对tmp2变量进行转换并不表明ID字段是长整型!正确的语法是
strsql1 = "select * from table1 where table1.ID= " & txt1
我们把单引号去掉了,而tmp1和tmp2变量也是多余的,直接使用txt1就行了。
zhaoyu:(1)在VB中如何调用矢量图形?矢量图形的数据格式是怎样的?
(2)如何对矢量图的某一部分进行操作(如局部放大)?
李海:经常听到一些朋友抱怨Visual Basic的图形/图像功能比较差。不过这倒不是VB独有的问题,因为一般的语言软件包都不带图形/图像处理的软件包,不管是Delphi,还是C++(唯一的另外大概是Borland C++ 4.5曾经捆绑过ImageKnife/VBX 2.1)。这里面有一个实际问题是不同的用户对图形/图像功能的要求差别比较大。所以一般的办法无外乎自己动手编程,或是购买现成的软件包。
矢量图形并没有统一的标准格式,每个采用矢量编辑的软件通常都会使用自己的格式。当然,比较流行的还是要属AutoCAD所支持的DXF格式。有些人也把HP绘图仪所支持的HGL格式看作矢量文件,这个格式支持的软件也相当多。VB所支持的图形文件中只有图元文件WMF格式是矢量图形。在Windows中,有两种图元文件,一种是在Windows 3.x中定义的,一种是后来改进的增强型图元文件。在Win32 API中有函数来支持这两类图元文件。矢量图形文件中的数据一般是按对象(或称为记录)保存的,比如圆、直线等等。如果你希望对矢量图的某一部分进行操作,首先要获取该图形文件中的相关对象,然后做变形、放大等处理。放大一般不麻烦,因为每个对象都是以坐标的形式描述的,而不是以象素的形式,但获取特定的对象是一件比较麻烦的事。对于图元文件来说,使用EnumEnhMetaFile(或EnumMetaFile)函数获得所有对象,使用PlayEnhMetaFileRecord(或PlayMetaFileRecord)函数来绘制指定对象。
有很多VB控件可以来处理矢量图形,象DbCAD Pro、DXF-IN Pro、GeoView II、MetaDraw、ImageMan、AddFlow等等。不过这些软件的售价都比较惊人,只有大客户才会问津。
深圳·王伟我用VFP 5.0英文版开发一个软件,其中需要在工作站中获取服务器的时间,不知道在Windows环境中是否有VFP本身的函数或命令可以直接获取服务器的时间?还是有其它的如API函数?我现在使用的方法是RUN一条Win 95下的NET命令,即
RUN NET TIME \\<服务器名称> /SET /YES
但每次执行到此处时,电脑屏幕上都会有一个DOS窗口一闪而过,不知如何去掉该DOS窗口?
李海:确实有个API函数NetRemoteTOD可以用来获得服务器时间,但这个函数仅被Windows NT支持,而在Windows 95中没有实现。NetRemoteTOD格式如下:
NET_API_STATUS NetRemoteTOD ( LPTSTR UncServerName, LPBYTE * BufferPtr);
该函数一共有两个参数:第一个是服务器名称,第二个用来返回服务器时间,包括时间、日期和时区等信息。其结构定义如下:
typedef struct _TIME_OF_DAY_INFO {
DWORD tod_elapsedt;
DWORD tod_msecs;
DWORD tod_hours;
DWORD tod_mins;
DWORD tod_secs;
DWORD tod_hunds;
LONG tod_timezone;
DWORD tod_tinterval;
DWORD tod_day;
DWORD tod_month;
DWORD tod_year;
DWORD tod_weekday;
} TIME_OF_DAY_INFO, *PTIME_OF_DAY_INFO, *LPTIME_OF_DAY_INFO; 具体的使用说明请参见Win32 API的帮助。
Windows 95的NET.EXE实际上是一个16位程序,它也没有调用这个API函数。在各种Windows 95下获取服务器时间的办法中,使用NET TIME是最简单的,但要想去掉DOS窗口就不那么容易了,你可以以最小化窗口的方式(就象WinZip调用外部的Arj程序一样)来运行NET TIME命令。
如果您有任何建议,请给我发电子邮件:
。
版权所有 李海,热情软件屋 1997-2006