现在我有一个使用ADO运行查询的函数,并返回recordset:
Recordset Execute(Connection connection, String commandText)
{
//[pseudo-code]
Recordset rs = new Recordset();
rs.CursorLocation = adUseClient;
rs.CursorType = adOpenForwardOnly;
rs.Open(commandText, connection,
adOpenForwardOnly, //CursorType; the default
adLockReadOnly, //LockType
adCmdText);
return rs;
}
这很好。它同步运行,并返回查询的记录集。
现在我想要一个类似的版本,它显示ProgressDialog,让用户可以取消长时间运行的查询:
Recordset Execute(HWND parenthWnd, String caption, Connection connection, String commandText)
{
//[pseudo-code]
//Construct a progressDialog and show it
IProgressDialog pd = new ProgressDialog();
pd.SetTitle(caption); //e.g. "Annual Funding Report"
pd.SetCancelMsg("Please wait while the operation is cancelled");
pd.StartProgressDialog(parenthWnd, null, PROGDLG_MODAL | PROGDLG_NOTIME | PROGDLG_NOMINIMIZE, null);
pd.SetLine(1, "Querying server", False, null);
try
{
//Query the server
Recordset rs = new Recordset();
rs.Open(commandText, connection,
adOpenForwardOnly, //CursorType
adLockReadOnly, //LockType
adCmdText | adAsyncExecute);
while (rs.State and (adStateConnecting+adStateExecuting+adStateFetching) <> 0)
{
if pd.HasUserCancelled()
throw new EUserCancelledOperationException();
Sleep(100);
};
finally
{
//Hide and destroy the progress dialog
pd.StopProgressDialog();
pd = null;
}
//Now we have our results for the client
return rs;
}
检查用户是否已取消操作的方法是,如果用户按下取消按钮,则定期询问进度对话框:
pd.HasUserCancelled(); //returns true if user has clicked Cancel
现在我面临着如何定期检查用户是否已取消(以及知道查询是否已完成,或者是否已发生错误)以及如何成为一名优秀的程序员而无需轮询的情况。
知道错误发生的唯一方法是在Recordset的FetchCompleteEvent上设置一个处理程序:
pError
一个Error对象。它描述了adStatus的值为adStatusErrorsOccurred时发生的错误;否则它没有设置。
adStatus
EventStatusEnum状态值。调用此事件时,如果导致事件的操作成功,则将此参数设置为adStatusOK;如果操作失败,则将此参数设置为adStatusErrorsOccurred。在此事件返回之前,请将此参数设置为adStatusUnwantedEvent以防止后续通知。
pRecordset
一个Recordset对象。检索记录的对象。
所以这意味着我将不得不让我的函数构造一个辅助对象,所以我可以有一个FetchComplete
处理程序。但是我必须立即阻止我的同步功能。然后我们进入MsgWaitForSingleObject
,这是非常难以正确使用的。
所以我要求帮助或罐装代码。
我应该更明确:我正在寻找使用此方法签名的函数实现:
Recordset ExecuteWithCancelOption(Connection connection, String commandText)
显示一个带有取消按钮的对话框。
挑战在于,该功能现在必须创建实现该功能所需的一切。如果这涉及一个隐藏的形式,它有一个计时器,等等 - 好吧。
但我正在寻找一个显示取消按钮的同步功能。
该功能将成为
的近乎(或确切)替代品Recordset Execute(Connection connection, String commandText)
考虑到Windows的实际考虑因素,我需要为该函数提供一个父窗口句柄,它将对话框的父对象:
Recordset ExecuteWithCancelOption(HWND parentHwnd, Connection connection, String commandText)
鉴于这将是一个可重用的函数,我将让调用者提供将要显示的文本:
Recordset ExecuteWithCancelOption(HWND parenthWnd, String caption, Connection connection, String commandText)
鉴于这些都是我的TADOHelper
类中的类函数,我可以给它们相同的名称,让它们相互重载:
Recordset Execute(HWND parenthWnd, String caption, Connection connection, String commandText)
我认为在Delphi以外的语言中,匿名代表是有帮助的。但我仍然害怕必须处理MsgWaitForMultipleObjects
。
答案 0 :(得分:1)
答案 1 :(得分:1)
为了使GUI响应按钮点击,您应该将控制权返回到窗口的消息循环。 while循环 while(rs.State和(adStateConnecting + adStateExecuting + adStateFetching)&lt;&gt; 0)不会将控制权返回给消息循环,从而阻止GUI。
以下是使用异步ADO查询的Delphi代码的摘录。此代码不允许非模态提取数据,但可确保在获取数据期间重新绘制主窗体,并允许取消查询。
通过设置:
实现异步执行和提取
FOpeningDataSet.ExecuteOptions := [eoAsyncExecute, eoAsyncFetchNonBlocking];
通过调用
取消执行查询
DataSet.Recordset.Cancel;
在FetchProgress
事件中。
任何TADODataSet都应通过以下方法打开:
OpenDataSetInBackground(DataSourceData.DataSet as TADODataSet);
支持主要表格中的代码:
procedure TOperatorForm.OpenDataSetInBackground(DataSet: TADODataSet);
begin
if DataSet.Active then Exit;
FOpeningDataSet := DataSet;
if not FAsyncDataFetch then
begin
FOpeningDataSet.Open;
Exit;
end;
FFetchCancel := False;
FExecuteOptions := FOpeningDataSet.ExecuteOptions;
FFetchProgress := FOpeningDataSet.OnFetchProgress;
FFetchComplete := FOpeningDataSet.OnFetchComplete;
FRecordsetCreate := FOpeningDataSet.OnRecordsetCreate;
FAfterScroll := FOpeningDataSet.AfterScroll;
FOpeningDataSet.ExecuteOptions := [eoAsyncExecute, eoAsyncFetchNonBlocking];
FOpeningDataSet.OnFetchProgress := DataSetFetchProgress;
FOpeningDataSet.OnFetchComplete := DataSetFetchComplete;
FOpeningDataSet.OnRecordsetCreate := DataSetRecordsetCreate;
FOpeningDataSet.AfterScroll := DataSetAfterScroll;
FOpeningDataSet.CursorLocation := clUseClient;
FOpeningDataSet.DisableControls;
try
DataSetProgressForm.Left := Left + (Width - DataSetProgressForm.Width) div 2;
DataSetProgressForm.Top := Top + (Height - DataSetProgressForm.Height) div 2;
DataSetProgressForm.cxButton1.OnClick := DataSetProgressClick;
DataSetProgressForm.cxButton1.Visible := FShowProgressCancelButton;
FOpeningDataSet.Open;
DataSetProgressForm.ShowModal;
finally
FOpeningDataSet.EnableControls;
FOpeningDataSet.ExecuteOptions := FExecuteOptions;
FOpeningDataSet.OnFetchProgress := FFetchProgress;
FOpeningDataSet.OnFetchComplete := FFetchComplete;
FOpeningDataSet.OnRecordsetCreate := FRecordsetCreate;
FOpeningDataSet.AfterScroll := FAfterScroll;
end;
end;
procedure TOperatorForm.DataSetProgressClick(Sender: TObject);
begin
FFetchCancel := True;
end;
procedure TOperatorForm.DataSetFetchProgress(DataSet: TCustomADODataSet; Progress, MaxProgress: Integer; var EventStatus: TEventStatus);
begin
if FFetchCancel then
DataSet.Recordset.Cancel;
end;
procedure TOperatorForm.DataSetFetchComplete(DataSet: TCustomADODataSet; const Error: Error; var EventStatus: TEventStatus);
begin
PostMessage(DataSetProgressForm.Handle, WM_CLOSE, 0, 0);
MessageBeep(MB_ICONEXCLAMATION);
end;
procedure TOperatorForm.DataSetFetchComplete(DataSet: TCustomADODataSet; const Error: Error; var EventStatus: TEventStatus);
begin
PostMessage(DataSetProgressForm.Handle, WM_CLOSE, 0, 0);
MessageBeep(MB_ICONEXCLAMATION);
end;
procedure TOperatorForm.DataSetRecordsetCreate(DataSet: TCustomADODataSet; const Recordset: _Recordset);
begin
if Assigned(FRecordsetCreate) then FRecordsetCreate(DataSet, Recordset);
end;
procedure TOperatorForm.DataSetAfterScroll(DataSet: TDataSet);
begin
// From TBetterADODataSet 4.04
// Ole Willy Tuv's fix 03-10-00 for missing first record
with TADODataSet(DataSet) do
begin
if (eoAsyncFetchNonBlocking in ExecuteOptions) and
(Bof or Eof) and
(CursorLocation = clUseClient) and
(stFetching in RecordSetState) then
begin
if Recordset.RecordCount > 0 then
if Bof then
Recordset.MoveFirst
else if Eof then
Recordset.MoveLast;
CursorPosChanged;
Resync([]);
end;
end;
if Assigned(FAfterScroll) then
FAfterScroll(DataSet);
end;
进度表:
unit uDataSetProgressForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, ExtCtrls, StdCtrls;
type
TDataSetProgressForm = class(TForm)
AnimateProgress: TAnimate;
Label1: TLabel;
Bevel1: TBevel;
Bevel2: TBevel;
Button1: TButton;
Shape1: TShape;
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure FormHide(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
DataSetProgressForm: TDataSetProgressForm;
implementation
{$R *.dfm}
{$R servertimeout.res} // contains IDR_SERVAVI animation resource
procedure TDataSetProgressForm.FormCreate(Sender: TObject);
begin
AnimateProgress.ResName := 'IDR_SERVAVI';
end;
procedure TDataSetProgressForm.FormShow(Sender: TObject);
begin
AnimateProgress.Active := True;
end;
procedure TDataSetProgressForm.FormHide(Sender: TObject);
begin
AnimateProgress.Active := False;
end;
end.
和dfm
object DataSetProgressForm: TDataSetProgressForm
Left = 590
Top = 497
BorderStyle = bsNone
ClientHeight = 104
ClientWidth = 205
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
FormStyle = fsStayOnTop
OldCreateOrder = False
Position = poDefaultSizeOnly
OnCreate = FormCreate
OnHide = FormHide
OnShow = FormShow
DesignSize = (
205
104)
PixelsPerInch = 96
TextHeight = 13
object Bevel1: TBevel
Left = 0
Top = 0
Width = 205
Height = 104
Align = alClient
Style = bsRaised
end
object Bevel2: TBevel
Left = 12
Top = 12
Width = 181
Height = 80
Anchors = [akLeft, akTop, akRight, akBottom]
end
object Shape1: TShape
Left = 1
Top = 1
Width = 203
Height = 102
Anchors = [akLeft, akTop, akRight, akBottom]
Brush.Style = bsClear
Pen.Color = clWindowFrame
end
object AnimateProgress: TAnimate
Left = 25
Top = 23
Width = 32
Height = 32
end
object Label1: TLabel
Left = 70
Top = 31
Width = 106
Height = 17
Hint = 'Selecting data...'
Caption = 'Selecting data...'
TabOrder = 1
end
object Button1: TButton
Left = 63
Top = 64
Width = 80
Height = 23
Caption = 'Cancel'
Default = True
TabOrder = 2
end
end
答案 2 :(得分:0)
如果是Delphi,您可以删除TTimer组件并使用它来检查HasUserCancelled值是否为True。我没有在我面前使用Delphi所以我以后必须发布一个例子。
编辑:
这是一个TTimer OnTimer事件的示例,它检查当前时间和最后一个活动时间,以确定如果程序保持“Up”状态如何处理表单:
procedure TForm_Main.Timer1Timer(Sender: TObject);
begin
// return to opening screen if no activity for a while:
if Now - LastActivity > TimeOut
then
begin
Form_Select.SBtn_Defendant.Down:= False;
Form_Select.SBtn_Officer.Down:= False;
Form_Select.SBtn_Attorney.Down:= False;
Form_Main.ModalResult:= mrCancel;
Exit;
end;
Form_Main.Caption:= FormatDateTime('dddd mmmm d, yyyy h:nn:ss AM/PM', Now);
end;