相关 Vcl 如下:
跟随 TApplication 的运行过程, 查看 action 的 Update 如何被调用.
1、
procedure TApplication.Run;
begin
FRunning := True;
try
AddExitProc(DoneApplication);
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if FMainForm.FWindowState = wsMinimized then
Minimize else
FMainForm.Visible := True;
repeat // 每个 Application 都是在不断的循环着
try
HandleMessage; // 进入消息循环处理
except
HandleException(Self); // 意外处理,顺便看一下
end;
until Terminated;
end;
finally
FRunning := False;
end;
end;
// Application 默认的意外处理,看看也挺有意思
procedure TApplication.HandleException(Sender: TObject);
begin
if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
// 返回捕获当前鼠标的窗口,
// The WM_CANCELMODE message is sent to the focus window when a dialog box or message box is displayed;
// this enables the focus window to cancel modes, such as mouse capture.
if ExceptObject is Exception then
begin
if not (ExceptObject is EAbort) then // 疑问一:如果是 EAbort, 那么怎么办? 交给了操作系统?
if Assigned(FOnException) then // 处理用户自定义的 OnException
FOnException(Sender, Exception(ExceptObject))
else
ShowException(Exception(ExceptObject)); // 显示意外信息 E.Message
end else
SysUtils.ShowException(ExceptObject, ExceptAddr);
end;
疑问二:
procedure TApplication.ShowException(E: Exception);
var
Msg: string;
begin
Msg := E.Message;
if (Msg <> '') and (AnsiLastChar(Msg) > '.') then Msg := Msg + '.';
// 在消息的末尾加个 '.' ,但是太不严格了吧。因为在 '.' 的前面还有其他常见的 ascii 字符。
// 这一句到底要实现什么功能呢?谁能告诉我。
// 直接将这一句注释掉,应该更好。或者干脆写 msg = '未知 E.Message';
// 为什么注释掉更好,继续往下看就会更明白了。
MessageBox(PChar(Msg), PChar(GetTitle), MB_OK + MB_ICONSTOP);
end;
再来 AnsiLastChar 源码
function AnsiLastChar(const S: string): PChar;
var
LastByte: Integer;
begin
LastByte := Length(S);
if LastByte <> 0 then
begin
while ByteType(S, LastByte) = mbTrailByte do Dec(LastByte);
Result := @S[LastByte];
end
else
Result := nil; // 如果 S = '' ,则返回 nil
end;
注意了,如果使用 AnsiLastChar 要注意空指针的问题:
比如:
Msg := '';
if (AnsiLastChar(Msg) >= '.') then
将会出现访问非法内存 $00000000 的错误。
再返回来看
Msg := E.Message;
if (Msg <> '') and (AnsiLastChar(Msg) > '.') then Msg := Msg + '.';
也就是说, Delphi 并不能保证 E.Message 不为空。因此,加了 (Msg <> '') 的条件。
问题又来了,既然不能保证 E.Message 不为空,又因为 if A and B 这种条件会因为编译条件的不同而产生不同的结果。
因此,将编译选项改为完全计算,在Compiler Options 对话框中选择Complete Boolean Evaluation 选项,
那么,"Read of Address 00000000" 应该是意外处理中的意外了。
2、
procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then // 如果没有获得任何消息,进入自定义过程 Idle
Idle(Msg);
end;
3、
procedure TApplication.Idle(const Msg: TMsg);
var
Control: TControl;
Done: Boolean;
begin
Control := DoMouseIdle;
if FShowHint and (FMouseControl = nil) then
CancelHint;
Application.Hint := GetLongHint(GetHint(Control));
Done := True;
try
if Assigned(FOnIdle) then FOnIdle(Self, Done); // 用户自定义 Idle
if Done then DoActionIdle; // 如果 Done = true , 处理 action Idle
except
HandleException(Self);
end;
if (GetCurrentThreadID = MainThreadID) and CheckSynchronize then
Done := False;
if Done then WaitMessage;
end;
4、
action相关
procedure TApplication.DoActionIdle;
var
I: Integer;
begin
for I := 0 to Screen.CustomFormCount - 1 do
with Screen.CustomForms[I] do
if HandleAllocated and IsWindowVisible(Handle) and
IsWindowEnabled(Handle) then
UpdateActions;
// 满足3个条件,才会处理 action:
//可视对象已经被创建、可视的、使能的
end;
procedure TCustomForm.UpdateActions;
var
I: Integer;
procedure TraverseClients(Container: TWinControl);
var
I: Integer;
Control: TControl;
begin
if Container.Showing then
for I := 0 to Container.ControlCount - 1 do
begin
Control := Container.Controls[I];
if (csActionClient in Control.ControlStyle) and Control.Visible then
Control.InitiateAction;
if Control is TWinControl then
TraverseClients(TWinControl(Control));
end;
end;
begin
if (csDesigning in ComponentState) or not Showing then Exit; // 设计态或者非显示则退出
{ Update form }
InitiateAction; // 执行 action 的 Update
{ Update main menu's top-most items }
if Menu <> nil then
for I := 0 to Menu.Items.Count - 1 do // 更新顶级菜单
with Menu.Items[I] do
if Visible then InitiateAction;
{ Update any controls }
TraverseClients(Self); // 执行控件关联的 action Update
end;
InitiateAction 调用:
procedure TControl.InitiateAction;
begin
if ActionLink <> nil then ActionLink.Update;
end;
function TBasicAction.Update: Boolean;
begin
if Assigned(FOnUpdate) then
begin
FOnUpdate(Self);
Result := True;
end
else Result := False;
end;
5、
消息处理
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end
else
FTerminate := True;
end;
end;
"The WM_IDLE message will not be processed for modal dialogs; that is, when a DoModal is executing. There is, however, a MFC "private" message that is somewhat documented. It is the the WM_KICKIDLE message. You must add an entry to the message map for the dialog class header. "
如果 Delphi 使用 WM_IDLE 是不是也要考虑 ShowModal 的问题?
[2] http://www.tutorials-ne.com/ui/Drag-Drop-62308/
WM_IDLE
If you do a search on the web for the WM_IDLE windows message there will be
many hits. According to many sites, the WM_IDLE message is generated by the
operating system itself when the application's message queue is empty.
However, I couldn't find WM_IDLE anywhere within MSDN. Is this message indeed
defined by Windows? And if so, would it be possible for me to hook it?
WM_IDLE 在哪里?
[3] http://www.cs.sjsu.edu/faculty/beeson/courses/cs130/LectureNotes/2-HowWindowsWorks/2-HowWindowsWorks.html
This loop executes until there are no more messages in the message queue, and then it terminates.
Premature termination is prevented by WM_IDLE messages
that the operating system generates when nothing else is happening.
什么叫 "operating system generates when nothing else is happening" ?
去年我维护一个程序时,发现的问题。
不过当时我并不知道 Complete Boolean Evaluation 选项的作用。
通过 Cpu View 看,是最后才判断是否跳过。
setnz al
setnz bl
and al,bl
....
因此,使用了内部 if Length(S) =0 then 处理了此代码。
源代码段:
// 协议分析
while (Length(S) <> 0) and (S[1] <> '@') do
begin
//
S := Copy(....)
if Length(S) =0 then
break;
end;