如何为没有焦点的控件添加键盘事件? (有新问题,十万火急) (100分)

  • 主题发起人 主题发起人 dirk
  • 开始时间 开始时间
D

dirk

Unregistered / Unconfirmed
GUEST, unregistred user!
以前没做过控件,现在临时要用了却碰到问题,如何为没有焦点的控件添加键盘事件?
比如TScrollBox、TLabel。

谢谢,解决再加200分!
 
直接截获键盘消息.
 
能给出实际有用的代码吗?我试图这样写:
procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
procedure WMSysKeyDown(var Message: TWMKeyDown); message WM_SYSKEYDOWN;
procedure WMKeyUp(var Message: TWMKeyUp); message WM_KEYUP;
procedure WMSysKeyUp(var Message: TWMKeyUp); message WM_SYSKEYUP;
procedure WMChar(var Message: TWMChar); message WM_CHAR;
但好像没用,又这样写:
constructor TCScrollBox.Create(AOwner: TComponent);
begin
inherited;
WindowProc := sWndProc;
end;
procedure TCScrollBox.sWndProc(var Message: TMessage);
var
Ch:Char;
begin
// if Message.Msg = WM_CHAR then
if Message.Msg = CN_CHAR then
begin
Ch := Char(TWMKey(Message).CharCode );
KeyPress(Ch);
TWMKey(Message).CharCode := Word(Ch);
if Char(TWMKey(Message).CharCode) = #0 then Exit;
end
else
WndProc(Message);
end;
也不行,Message.Msg = CN_CHAR 总是不等。
 
不行,象TLabel是从TGraphicControl继承的,而不是TWinControl.这种控件
没有Handle,根本没有焦点。也接受不到键盘事件.
 
但是TScrollBox呢?我上面是用TScrollBox写的。

TLabel我本来也这么想的,但是TLabel不也是有Mouse事件吗?
 
我觉得没有焦点的控件就肯定不会有键盘事件,所以要使用键盘事件最好直接使用
form的。
 
如果你只是想解决让TScrollBox接受键盘消息
可以用SCROLLBAR自己做个ScrollBox
 
TScrolBox可以,它是从TWinCroll继承的。
TGraphicControl只能处理鼠标消息,TWinControl能处理键盘和鼠标消息.

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;

type
TForm1 = class(TForm)
ScrollBox1: TScrollBox;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
OldProc:TWndMethod;
procedure MyProc(var Message: TMessage);
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
OldProc := ScrollBox1.WindowProc;
ScrollBox1.WindowProc := MyProc;
end;
procedure TForm1.MyProc(var Message: TMessage);
begin
if Message.Msg = WM_KeyDown then
beep;
OldProc(Message);
end;
end.
 
sendmessage函数
 
笨办法
FORM的KEYPREVIEW为TRUE
然后用FORM的KEY事件
 
楼上的“苯办法”可不行。[:)]
 
试试application.OnMessage
不过好象也是笨方法 ^_^
 
定义一个特定按键(注册热键?),当按下这个键后,通知程序,下面的键盘操作将是针对没
有焦点的控件输入,输入完毕,再按一下这个按键。如此而已。
 
问题的标题是: 如何在非可视控件中响应自定义消息? (200分 )

来自:cjs 时间:1998-12-14 13:29:38 ID:94911

1.例如:
const UDF_SOCKETEVENT = WM_USER+500;
type
TComponent1 = class(Tcomponent)
private
procedure NUDFPOPSocketEvent(var Msg: TMessage); message UDF_SOCKETEVENT;
public
procedure proc1;
...
end;

...

implementation
...

TComponent1.NUDFPOPSocketEvent(var Msg: Message);
begin
...
end;

Tcomponent1.proc1; // Tcomponent1 中的一个函数。
begin
POPSocket:=NetPop.OpenSocketConnection(Handle,UDF_SOCKETEVENT,110) // 自定义类
^^^^^^ Tcomponent1为非可视控件,Handle应该如何写?
...
end;

以上定义的Tcomponent1,当调用Tcomponent1.proc1后, NUDFPOPSocketEvent 为什么没有响应?

2.Delphi 又是如何管理消息的?一个窗体有许多的控件,那么消息是 如何传到这些控件的呢?(Tcomponent1 有没有接到窗体传来的任何
消息?)


来自:唐晓锋 时间:1998-12-14 14:13:23 ID:94926

给你我收藏的Tips About Message
<table border="0">
<tr>

DELPHI 中 的 消 息 处 理 机 制

<p>        


<h3>
一.DELPHI VCL 中 消 息 的 传 递
</h3>
        
Delphi 中 每 一 个VCL(Visual Component Library) 构 件( 如Tbutton,Tedit 等) 都 有 一 内 在 的 消 息 处 理 机 制, 其 基 本 点 就 是 构 件 类 接 收 到 某 些 消 息 并 把 它 们 发 送 给 适 当 的 处 理 方 法, 如 果 没 有 特 定 的 处 理 方 法, 则 调 用 缺 省 的 消 息 处 理 句 柄。
<p>        
<p>        
其 中mainwndproc 是 定 义 在Twincontrol 类 中 的 一 个 静 态 方 法, 不 能 被 重 载(Override)。 它 不 直 接 处 理 消 息, 而 是 交 由wndproc 方 法 处 理, 并 为wndproc 方 法 提 供 一 个 异 常 处 理 模 块。Mainwndproc 方 法 声 明 如 下:
<p>        
procedure MainWndProc(var Message: TMessage);
<p>        
Wndproc 是 在Tcontrol 类 中 定 义 的 一 个 虚 拟 方 法, 由 它 调 用dispatch 方 法 来 进 行 消 息 的 分 配, wndproc 方 法 声 明 如 下:
<p>        
procedure WndProc(var Message: TMessage); virtual;
<p>        
dispatch 方 法 是 在Tobject 根 类 中 定 义 的, 其 声 明 如 下:
<p>        
procedure Tobject.dispatch(var Message);
传 递 给dispatch 的 消 息 参 数 必 须 是 一 个 记 录 类 型, 且 这 个 记 录 中 第 一 个 入 点 必 须 是 一 个cardinal 类 型 的 域(field), 它 包 含 了 要 分 配 的 消 息 的 消 息 号 码. 例 如:
<pre>
type
Tmessage=record
Msg:cardinal;
wparam:word;
lparam:longint; .
result:longint;
end;
</pre>
        
而Dispatch 方 法 会 根 据 消 息 号 码 调 用 构 件 的 最 后 代 类 中 处 理 此 消 息 的 句 柄 方 法. 如 果 此 构 件 和 它 的 祖 先 类 中 都 没 有 对 应 此 消 息 的 处 理 句 柄,Dispatch 方 法 便 会 调 用Defaulthandler 方 法.Defaulthandler 方 法 是 定 义 于Tobject 中 的 虚 拟 方 法, 其 声 明 如 下:
<p>        
procedure Defaulthandler(var Message);virtual;
<p>        
Tobject 类 中 的Defaulthandler 方 法 只 是 实 现 简 单 的 返 回 而 不 对 消 息 进 行 任 何 的 处 理. 我 们 可 以 通 过 对 此 虚 拟 方 法 的 重 载, 在 子 类 中 实 现 对 消 息 的 缺 省 处 理. 对 于VCL 中 的 构 件 而 言, 其Defaulthandler 方 法 会 启 动 windows API 函 数Defwindowproc 对 消 息 进 行 处 理.

<h3>
二.DELPHI 中 的 消 息 处 理 句 柄
</h3>
        
在DELPHI 中 用 户 可 以 自 定 义 消 息 及 消 息 处 理 句 柄. 消 息 处 理 句 柄 的 定 义 有 如 下 几 个 原 则:
<ol><li>
消 息 处 理 句 柄 方 法 必 须 是 一 个 过 程, 且 只 能 传 递 一 个Tmessage 型 变 量 参 数.
<br><li>
方 法 声 明 后 要 有 一 个message 命 令, 后 接 一 个 在0 到32767 之 间 的 消 息 标 号( 整 型 常 数).
<br><li>
消 息 处 理 句 柄 方 法 不 需 要 用override 命 令 来 显 式 指 明 重 载 祖 先 的 一 个 消 息 处 理 句 柄, 另 外 它 一 般 声 明 在 构 件 的protected 或private 区.
<br><li>
在 消 息 处 理 句 柄 中 一 般 先 是 用 户 自 己 对 消 息 的 处 理, 最 后 用inherited 命 令 调 用 祖 先 类 中 对 应 此 消 息 的 处 理 句 柄( 有 些 情 况 下 可 能 正 相 反). 由 于 可 能 对 祖 先 类 中 对 此 消 息 的 处 理 句 柄 的 名 字 和 参 数 类 型 不 清 楚, 而 调 用 命 令inherited 可 以 避 免 此 麻 烦, 同 样 如 果 祖 先 类 中 没 有 对 应 此 消 息 的 处 理 句 柄,inherited 就 会 自 动 调 用Defaulthandler 方 法.( 当 然 如 果 要 屏 蔽 掉 此 消 息, 就 不 用inherited 命 令 了)。
</ol>
消 息 处 理 句 柄 方 法 声 明 为:
<br>        
procedure Mymsgmethod(var message:Tmessage); message Msgtype;
<p>        
同 样 用 户 也 可 以 定 义 自 己 的 消 息, 用 户 自 定 义 消 息 应 从WM_USER 开 始.
<p>        
自 定 义 消 息 及 消 息 处 理 句 柄 举 例 如 下:
<pre>
const my_paint=Wm_user+1;
type
Tmypaint=record
msgid:cardinal;
msize:word;
mcolor:longint;
msgresult:longint;
end;
type
Tmycontrol=class(TCustomControl)
protected
procedure change(var message:Tmypaint); message my_paint;
.....
end;
......
procedure Tmycontrol.change(var message:Tmypaint);
begin
size:=message.msize; { 设 置Tmybutton 尺 寸 属 性}
color:=message.mcolor; { 设 置Tmybutton 颜 色 属 性}
{do something else}
inherited; { 交 由Tcustomcontrol 处 理}
end;

</pre>
<h3>
三. 过 滤 消 息
</h3>
        
过 滤 消 息 又 称 消 息 陷 阱。 在 一 定 情 况 下, 用 户 可 能 需 要 屏 蔽 某 些 消 息. 或 者 截 获 某 些 消 息 进 行 处 理。 由 以 上 介 绍 可 以 看 出 过 滤 消 息 一 般 有 三 种 途 径:(1). 重 载 构 件 继 承 的 虚 拟 方 法wndproc. (2). 针 对 某 消 息 编 写 消 息 处 理 句 柄. (3). 重 载 构 件 继 承 的 虚 拟 方 法Defhandler, 在 其 中 对 消 息 进 行 处 理。 其 中 常 用 的 方 法 是 方 法(2), 在 上 节 中 已 介 绍 过 了, 方 法(1) 与 方 法(3) 相 似, 这 里 只 简 单 介 绍 一 下 方 法(1)。
<pre>
重 载 虚 拟 方 法wndproc 的 一 般 过 程 如 下:
procedure Tmyobject.wndproc(var message:Tmessage);
begin
{... 判 断 此 消 息 是 否 该 处 理..}
inherited wndproc(message);
{ 未 处 理 的 消 息 交 由 父 辈wndproc 方 法 处 理}
end;
</pre>
        
由 此 可 以 看 出 在wndproc 方 法 中 处 理 消 息 的 优 势 是 可 以 过 滤 整 个 范 围 内 的 消 息, 而 不 必 为 每 个 消 息 指 定 一 个 处 理 句 柄, 事 实 上Tcontrol 构 件 中 就 是 利 用 它 来 过 滤 并 处 理 所 有 的 鼠 标 消 息 的( 从WM_mousefirst 到WM_mouselast, 如 下 代 码 示). 同 样 利 用 它 也 可 以 阻 止 某 些 消 息 被 发 送 给 处 理 句 柄。
<pre>
procedure TControl.WndProc(var Message: TMessage);
begin
if (Message.Msg>=WM_MOUSEFIRST) and
(Message.Msg <= WM_MOUSELAST)
then
if Dragging then { 处 理 拖 曳 事 件}
DragMouseMsg(TWMMouse(Message))
else
... { 处 理 其 他 鼠 标 消 息}
end;
Dispatch(Message);
{ 否 则 正 常 发 送 消 息}
end;
</pre>
        
下 例 为 一 简 单 的 自 定 义 构 件 例 子 :
<p>        
Tmyedit 类 是 从Tedit 类 派 生 出 的 一 个 新 类, 它 的 特 点 是 在 运 行 中 不 能 获 得 焦 点, 不 能 由 键 盘 输 入( 有 点 类 似Tlabel 构 件). 我 们 可 在 其wndproc 方 法 中 过 滤 出WM_setfocus,WM_mousemove 消 息 并 进 行 处 理 来 达 到 上 述 要 求, 源 程 序 如 下:
<pre>
unit myedit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs,
StdCtrls;
type
Tmyedit = class(TEdit)
private
{ Private declarations }
protected
{ Protected declarations }
{ other fields and methods}
procedure wndproc(var message:Tmessage);override;
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [Tmyedit]);
end;
procedure Tmyedit.wndproc(var message:tmessage);
begin
if message.msg=wm_mousemove then
begin
cursor:=crarrow;
{ 设 置 光 标 为crarrow, 而 不 是 缺 省 的crBeam 光 标}
exit;
end;
if message.msg=wm_SetFocus then exit;
{屏蔽掉WM_setfocus消息,不让Tmyedit控件获得输入焦点}
inherited wndproc(message);
{其他消息交父辈wndproc处理}
end;
end.
</pre>
        
您 可 以 将Tmyedit 加 到Component Palette 中 检 验 其 性 能。
<p>        
</td>
</tr>
</table>



来自:dsp 时间:1998-12-14 15:46:32 ID:94938

以下代码是否对您有用呢?

const UDF_SOCKETEVENT = WM_USER+500;
type
TComponent1 = class(Tcomponent)
private
FWindowHandle: HWND;
FOnMySocket : TNotifyEvent;
procedure WndProc(var Msg: TMessage);
protected
procedure MySocket; dynamic;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure proc1;
Published
property OnMySocket: TNotifyEvent read FOnMySocket
write FOnMySocket;
end;

...

constructor TComponent1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FWindowHandle := AllocateHWnd(WndProc);
end;

destructor TComponent1.Destroy;
begin
DeallocateHWnd(FWindowHandle);
inherited Destroy;
end;

procedure TComponent1.WndProc(var Msg: TMessage);
begin
with Msg do
if Msg = UDF_SOCKETEVENT then
MySocket
else
Result := DefWindowProc(FWindowHandle, Msg, wParam, lParam);
end;

procedure TComponent1.MySocket;
begin
if Assigned(FOnMySocket) then FOnMySocket(Self);
end;

procedure TComponent1.proc1 ;
var
PopSocket : boolean;
begin
POPSocket:=NetPop.OpenSocketConnection(FWindowHandle,
UDF_SOCKETEVENT,110);
...

end;

进一步研究可TTimer的原码.

Good luck to you!





来自:cjs 时间:1998-12-14 22:23:29 ID:94967

非常感谢Dsp和唐晓锋的帮助,特别是采用了Dsp的代码后就解决了消息响应
的问题,
在此我还想问一下,若TForm1 上有 TEdit 和 TButton 两个可视控件
是不是TEdit 和 TButton 都有一个句柄呢?Windows的鼠标和键盘消息是不是
向所有的具有句柄的控件和函数发出鼠标和键盘消息?



来自:dsp 时间:1998-12-15 8:38:47 ID:95005

是的,每一个TWincontrol的继承控件都有一个Handle属性,即窗口的句柄.
Delphi的消息响应似乎是这样的:windows消息发给控件,控件收到后,产生
一个Delphi的自定义消息,CN_/CM_等,然后Inherited,它将消息传给父控件,
继续处理.因此,windows消息只发给产生的控件,控件缺省情况下,会将消息
依次返回给其父控件,当然我们可以通过重载消息处理函数,中断这一过程.
因此,并不是发给所有控件.但delphi对菜单和快捷键进行了特殊处理.

还记得Application吗? 它会事先截获所有消息.


来自:cjs 时间:1998-12-15 9:22:42 ID:95009

多人接受答案了。


 
我想是不是应该使用键盘钩子啊[:)]
 
谢谢各位参与,昨天后来有事没上来,其实只要添加:
procedure WMChar(var Message: TWMChar); message WM_CHAR;
就可以截获键盘事件了(另外WindowProc也是可以的),之前没用反应是因为TCScrollBox
没用获得焦点,因为只有当前焦点控件才能获得键盘事件,只要CScrollBox1.setfocus就可
顺利触发键盘事件了,另外又增加了一些属性、事件,正在改写中,因为这几天很忙,kingdeezj
贴的文章过几天看,另外过段时间等空点了再整理一下,将整理的代码新开个200分的帖子
以兑现我前面的话。
 
能把你实现的代码贴出来吗?
 
当然,但代码实现的效果并不十分好,涉及另一个难解决的问题,我正在想办法解决,但
最近的工作很忙,时间不太够,我还会回来发问的,解决后,就像我上面说的,我会另开
个200分的帖子发代码和分!还请各位多多给我技术支持!
 
TScrollBar当然可以,因为它继承自TWinControl,而TLabel就不行了可以用TStaticText
楼主问题有些问题,没有焦点和能否获得焦点是两码事,对于那种不能获得焦点的东东
我想可以加个容器什么的
而对于TScrollBox这样可以获得焦点的继承自TWinControl的东东,不用那么复杂吧

TCanKeyScrollBox=class(TScrollBox)
published
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
end;

搞定了,做控件时对VCL的体系的认识还是很重要的
 
zjan521:

是的,因为以前没写过控件,一开始很多都不明白,我是通过截获
procedure WMChar(var Message: TWMChar); message WM_CHAR;
来获取事件的。

>没有焦点和能否获得焦点是两码事
有关系,正因为TLabel不能获得焦点,所以,TLabel不可能获得键盘事件,因为windows
只向当前获得了焦点的对象发送键盘消息,TScrollBox是可以直接获取键盘消息,但是,
正如我上面一开始所说,这些键盘消息并未被触发(所以我才来问),后来想了想,才发
现,应该要让TScrollBox先获得焦点,于是,SetFocus了一把,马上响应了(TLabel是没
有SetFocus的)。

但是前面我说效果不好,是因为TScrollBox不会自动获得焦点,比如界面上有两个TEdit,
一个TScrollBox(TScrollBox里没用控件),你按Tab键,焦点只会在Edit1、Edit2之间切
换,自然,TScrollBox响应键盘消息的效果就没用TEdit、TButton等那么好了,这个怎么
处理?让TScrollBox可以排在Tab键的焦点切换列表中?

另外,刚才才发现小雨哥的方法,应该是可以,但是,我要响应键盘事件,总不能把所有
的按键都注册成热键吧[:)]。
 
后退
顶部