Delphi 如何使数据感知控件和非数据感知控件的组合彼此保持同步,并与它们所操作的数据库保持同步?
我有一个有7个控件的窗体。两个控件是数据感知的,一个TDBGrid和一个TDBNavigator。另外三个不支持数据,一个TJvCalendar2和两个TjvDateEdits。最后两个控件是作为数据源数据集的TDataSource和TTzDbf 在我的一生中,我无法弄清楚如何使用JvCalendar或任何一个JvDateEdits上的日期更新当前数据库记录,而不会引发灾难性的争用情况,导致程序崩溃 在表单的OnActivate方法中,我将数据库当前定位的记录中的数据复制到表单变量中。然后调用两个方法,一个用于更新JvCalendar,另一个用于更新两个JvDateEdits 这两个方法保存各自控件的OnChange处理程序,然后将其设置为nil,设置控件的日期,恢复控件的原始OnChange处理程序,然后退出 为了跟踪数据集何时被移动,我保存并替换数据集的后滚动和前滚动事件。当dbGrid中的当前行发生更改时,通过在dbGrid中单击鼠标或移动光标,或者通过在dbNavigator中更改记录,这些处理程序在BeforeColl或retrieve期间从表单变量更新数据库的记录,设置表单变量,然后更新JvCalendar和JvDateEdits 在BeforeColl事件期间保存、更新数据库记录会导致重新读取记录、更新控件,然后重写数据库记录。所有这些都会导致循环、堆栈空间耗尽和崩溃 我对事件处理程序和数据感知控件的理解和实现中缺少了什么 完整的示例代码如下: -----------------------RaceCondition.dpr----------------------Delphi 如何使数据感知控件和非数据感知控件的组合彼此保持同步,并与它们所操作的数据库保持同步?,delphi,data-aware,Delphi,Data Aware,我有一个有7个控件的窗体。两个控件是数据感知的,一个TDBGrid和一个TDBNavigator。另外三个不支持数据,一个TJvCalendar2和两个TjvDateEdits。最后两个控件是作为数据源数据集的TDataSource和TTzDbf 在我的一生中,我无法弄清楚如何使用JvCalendar或任何一个JvDateEdits上的日期更新当前数据库记录,而不会引发灾难性的争用情况,导致程序崩溃 在表单的OnActivate方法中,我将数据库当前定位的记录中的数据复制到表单变量中。然后调用两
/// <summary>
/// An application to demonstrate one programmer's incomplete
understanding
/// of data control's event system
/// </summary>
program RaceConditionDpr;
uses
/// <summary>
/// Forms, forms and more forms
/// </summary>
Forms,
/// <summary>
/// The application's main form with controls to try to plead for help
/// at understanding data control's interactions
/// </summary>
RaceConditionFrm in 'RaceConditionFrm.pas' {Form5};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm5, Form5);
Application.Run;
end.
/// <summary>
/// Unit containing the application, RaceConditionDpr's main form.Uses
/// several third party controls:
/// <list type="number">
/// <item>
/// JEDI's TJvMonthCalendar2
/// </item>
/// <item>
/// JEDI's TJvDateEdit
/// </item>
/// <item>
/// Topaz' TTzDbf dataset. This might be able to be substituted by
/// another dataset type and still demonstrate the race condition
/// problem that this application is intended to convey.
/// </item>
/// </list>
/// Uses several third party libraries:
/// <list type="number">
/// <item>
/// TurboPower's SysTools for routines in its StDate and StDateSt
/// units
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// Has 7 controls on a single form
/// <list type="bullet">
/// <item>
/// Two controls are data aware, a TDBGrid and a TDBNavigator.
/// </item>
/// <item>
/// Three others are not data aware, a TJvCalendar2 and two
/// TjvDateEdits.
/// </item>
/// <item>
/// The last two controls are a TDataSource and a TTzDbf as the
/// dataSource’s dataset.
/// </item>
/// </list>
/// </remarks>
unit RaceConditionFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, DB, tzprimds, ucommon, utzcds, utzfds, StdCtrls, Mask, JvExMask,
JvToolEdit, JvExControls, JvCalendar, ExtCtrls, DBCtrls, Grids, DBGrids;
{$ifdef WIN32}
{$A-} {byte alignment}
{$else}
{$ifdef LINUX}
{$A-} {byte alignment}
{$endif}
{$endif}
type
/// <summary>
/// Defines the type used to hold a dBase date in 'yyyymmdd' form. The
/// actual .dbf holds the date in this 'yyyymmdd' form but
/// retrieval/storage methods may insert date separators between the three
/// portions of the date, ie: 'mm/dd/yyyy' if the date locality has been
/// set to American.
/// </summary>
Tstring10 = string[10]; { for Date fields }
/// <summary>
/// Record structure reflecting the field structure present in the .dbf.
/// </summary>
TDATES_Record = Record
/// <summary>
/// Can be populated with the status of the .dbf record as on disk
/// </summary>
/// <value>
/// True if the record has been marked as deleted; False if not deleted
/// </value>
Deleted : Boolean;
/// <summary>
/// Field with the first date of the date span stored in the .dbf
/// </summary>
_DATEFIRST : Tstring10; { Date field }
/// <summary>
/// Field with the last date of the date span stored in the .dbf
/// </summary>
_DATELAST : Tstring10; { Date field }
end;
/// <summary>
/// Application's main form
/// </summary>
/// <remarks>
/// Has 7 controls.
/// <list type="bullet">
/// <item>
/// Two controls are data aware, a TDBGrid and a TDBNavigator.
/// </item>
/// <item>
/// Three others are not data aware, a TJvCalendar2 and two
/// TjvDateEdits.
/// </item>
/// <item>
/// The last two controls are a TDataSource and a TTzDbf as the
/// dataSource’s dataset.
/// </item>
/// </list>
/// </remarks>
TForm5 = class(TForm)
/// <summary>
/// dataaware control to display a grid of the database's records' data <br /><br />
/// Linked to DataSource DataSource1 <br />
/// </summary>
DBGrid1: TDBGrid;
/// <summary>
/// <para>
/// dataaware control to ease user re-positioning of the database's
/// record pointer
/// </para>
/// <para>
/// Linked to DataSource DataSource1
/// </para>
/// </summary>
DBNavigator1: TDBNavigator;
/// <summary>
/// <para>
/// Cool calendar control that can be configured to display more than
/// one month at a time. Will also display a time span in days and
/// this across multiple months.
/// </para>
/// <para>
/// Thanks JEDI
/// </para>
/// </summary>
JvMonthCalendar21: TJvMonthCalendar2;
/// <summary>
/// <para>
/// An edit control that drops down a calendar to permit selecting a
/// date in a nice natural way. Selects the date that will become the
/// DateFirst date.
/// </para>
/// <para>
/// Thanks, again, JEDI
/// </para>
/// </summary>
JvDateEditDateFirst: TJvDateEdit;
/// <summary>
/// <para>
/// An edit control that drops down a calendar to permit selecting a
/// date in a nice natural way. Selects the date that will become the
/// DateLast date.
/// </para>
/// <para>
/// Thanks, again, JEDI
/// </para>
/// </summary>
JvDateEditDateLast: TJvDateEdit;
/// <summary>
/// <para>
/// the DataSource for the application.
/// </para>
/// <para>
/// Linked to DataSet TzDbf1
/// </para>
/// </summary>
DataSource1: TDataSource;
/// <summary>
/// <para>
/// the DataSet for the application.
/// </para>
/// <para>
/// Linked to DataSource DataSource1
/// </para>
/// </summary>
TzDbf1: TTzDbf;
/// <summary>
/// When the form gains focus, updates the non-data aware controls with
/// the contents of the current database record
/// </summary>
procedure FormActivate(Sender: TObject);
/// <summary>
/// <para>
/// OnChange event handler called after the DateEdit1 control has
/// been changed, either by user interaction or by having its date
/// programmatically set.
/// </para>
/// <para>
/// With the control possibly having been edited by the user, it then
/// calls UpdateJvMontCalendar to update the calendar too.
/// </para>
/// </summary>
procedure JvDateEditDateFirstChange(Sender: TObject);
/// <summary>
/// <para>
/// OnChange event handler called after the DateEdit2 control has
/// been changed, either by user interaction or by having its date
/// programmatically set.
/// </para>
/// <para>
/// With the control possibly having been edited by the user, it then
/// calls UpdateJvMontCalendar to update the calendar too.
/// </para>
/// </summary>
procedure JvDateEditDateLastChange(Sender: TObject);
/// <summary>
/// <para>
/// OnChange event handler called after the Calendar control has been
/// changed, either by user interaction or by having its StartDate
/// and/or EndDate programmatically set.
/// </para>
/// <para>
/// With the control possibly having been edited by the user, it then
/// calls UpdateJvDateEdits to update the two DateEdit controls too.
/// </para>
/// </summary>
/// <param name="StartDate">
/// The first, earliest date on the calendar control
/// </param>
/// <param name="EndDate">
/// The second, later date on the calendar control. May be the same date
/// as the StartDate if the user has not selected different dates by
/// shift-clicking on a second date. The two dates will have been sorted
/// to supply the handler with the two different dates in ascending
/// order.
/// </param>
procedure JvMonthCalendar21SelChange(Sender: TObject; StartDate,
EndDate: TDateTime);
/// <summary>
/// <para>
/// OnAfterScroll event handler for the DataSet.
/// </para>
/// <para>
/// Called once the dataset has settled on what has become the
/// current record.
/// </para>
/// <para>
/// Causes the data in the FDates instance variable to be read, from
/// the database from its current record
/// </para>
/// </summary>
procedure TzDbf1AfterScroll(DataSet: TDataSet);
/// <summary>
/// <para>
/// OnBeforeScroll event handler for the DataSet. <br /><br />Called
/// before the dataset leaves the current record to begin a move to
/// another.
/// </para>
/// <para>
/// Causes the data in the FDates instance variable to be written,
/// posted, to the database <br />
/// </para>
/// </summary>
procedure TzDbf1BeforeScroll(DataSet: TDataSet);
private
{ Private declarations }
/// <summary>
/// <para>
/// Instance variable to serve as the holder of values read from the
/// .dbf and input by the user by interaction with the form.
/// </para>
/// <para>
/// To be written to the .dbf to replace the field values on the
/// current record when the dataset is about to be repositioned.
/// </para>
/// <para>
/// To be populated by the field values on what comes to be the
/// current record after the dataset has been repositioned to what is
/// now the current record. Will have its field values modified when
/// the user interacts with the controls on the form.
/// </para>
/// </summary>
FDates : TDATES_Record;
/// <summary>
/// Called to update the two date edit controls.
/// <list type="bullet">
/// <item>
/// Updates the DateEdit1 control with the DateFirst value in the
/// FDates record
/// </item>
/// <item>
/// Updates the DateEdit2 control with the DateLast value in the
/// FDates record <br />
/// </item>
/// </list>
/// </summary>
procedure UpdateJvDateEdits;
/// <summary>
/// Called to update the calendar control.
/// <list type="bullet">
/// <item>
/// Updates the DateFirst property with the DateFirst value in
/// the FDates record
/// </item>
/// <item>
/// Updates the DateLast property with the DateLast value in the
/// FDates record <br />
/// </item>
/// </list>
/// </summary>
procedure UpdateJvMonthCalendar;
/// <summary>
/// <para>
/// Update the .dbf wth the values modified by user interaction with
/// the form's controls, that is from instance variable FDates.
/// </para>
/// <para>
/// Writes FDates values to the current database record.
/// </para>
/// </summary>
procedure UpdateDbf;
/// <summary>
/// Utility method to convert a Topaz style date string into a TDateTime
/// equivalent
/// </summary>
/// <param name="aTopazDate">
/// Date as string in 'yyyymmdd' format
/// </param>
/// <returns>
/// the equivalent date as a TDateTime
/// </returns>
function TopazToDate( const aTopazDate : Tstring10 ): TDateTime;
/// <summary>
/// Utility method to convert a TDateTime into the equivalent Topaz style
/// date string in 'yyyymmdd' format
/// </summary>
/// <param name="aDate">
/// Date as TDateTime in format <br />
/// </param>
/// <returns>
/// the equivalent date as a string in 'yyyymmdd' format
/// </returns>
function DateToTopaz( aDate : TDateTime ): Tstring10;
public
{ Public declarations }
end;
var
/// <summary>
/// Instance variable holding the form
/// </summary>
Form5: TForm5;
implementation
{$R *.dfm}
uses
StDate,
StDateSt;
const
/// <summary>
/// constant for use in converting Topaz string dates to and from TDateTime
/// </summary>
zYYYYdMMdDDmask = 'yyyy.mm.dd';
// zyyyymmddMask = 'yyyymmdd';
procedure TForm5.FormActivate(Sender: TObject);
begin
FDates._DATEFIRST := TzDbf1.GetDField( 'DateFirst' );
FDates._DATELAST := TzDbf1.GetDField( 'DateLast' );
UpdateJvDateEdits;
UpdateJvMonthCalendar;
end;
procedure TForm5.TzDbf1AfterScroll(DataSet: TDataSet);
begin
UpdateJvDateEdits;
UpdateJvMonthCalendar;
end;
procedure TForm5.TzDbf1BeforeScroll(DataSet: TDataSet);
begin
UpdateDbf;
end;
procedure TForm5.UpdateDbf;
begin
// TzDbf1.DisableControls;
repeat
asm nop end;
until (TzDbf1.RLock);
TzDbf1.SetDField( 'DateFirst', FDates._DATEFIRST );
TzDbf1.SetDField( 'DateLast', FDates._DATELAST );
TzDbf1.ReplaceRec;
TzDbf1.UnLock;
// TzDbf1.EnableControls;
end;
procedure TForm5.UpdateJvDateEdits;
var
EventSaved : TNotifyEvent;
begin
EventSaved := JvDateEditDateFirst.OnChange;
JvDateEditDateFirst.OnChange := nil;
JvDateEditDateFirst.Date := TopazToDate( FDates._DATEFIRST );
JvDateEditDateFirst.OnChange := EventSaved;
EventSaved := JvDateEditDateLast.OnChange;
JvDateEditDateLast.OnChange := nil;
JvDateEditDateLast.Date := TopazToDate( FDates._DATELAST );
JvDateEditDateLast.OnChange := EventSaved;
end;
procedure TForm5.UpdateJvMonthCalendar;
var
EventSaved : TJvMonthCalSelEvent;
begin
EventSaved := JvMonthCalendar21.OnSelChange;
JvMonthCalendar21.OnSelChange := nil;
JvMonthCalendar21.DateFirst := TopazToDate( FDates._DATEFIRST );
JvMonthCalendar21.DateLast := TopazToDate( FDates._DATELAST );
JvMonthCalendar21.OnSelChange := EventSaved;
end;
procedure TForm5.JvDateEditDateFirstChange(Sender: TObject);
begin
FDates._DATEFIRST := DateToTopaz( JvDateEditDateFirst.Date );
UpdateJvMonthCalendar;
end;
procedure TForm5.JvDateEditDateLastChange(Sender: TObject);
begin
FDates._DATELAST := DateToTopaz( JvDateEditDateLast.Date );
UpdateJvMonthCalendar;
end;
procedure TForm5.JvMonthCalendar21SelChange(Sender: TObject; StartDate,
EndDate: TDateTime);
begin
FDates._DATEFIRST := DateToTopaz( StartDate );
FDates._DATELAST := DateToTopaz( EndDate );
UpdateJvDateEdits;
end;
function TForm5.TopazToDate( const aTopazDate : Tstring10 ): TDateTime;
var
anStDate : StDate.TStDate;
begin
anStDate := stdatest.DateStringToStDate( zYYYYdMMdDDmask, aTopazDate, 2000 );
Result := StDate.StDateToDateTime( anStDate );
end;
function TForm5.DateToTopaz(aDate: TDateTime): Tstring10;
var
anStDate : StDate.TStDate;
begin
anStDate := StDate.DateTimeToStDate( aDate );
Result := StDateSt.StDateToDateString( zYYYYdMMdDDmask, anStDate, False );
end;
end.
object Form5: TForm5
Left = 0
Top = 0
Caption = 'Form5'
ClientHeight = 336
ClientWidth = 628
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnActivate = FormActivate
PixelsPerInch = 96
TextHeight = 13
object DBGrid1: TDBGrid
Left = 8
Top = 8
Width = 320
Height = 120
DataSource = DataSource1
TabOrder = 0
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText
TitleFont.Height = -11
TitleFont.Name = 'Tahoma'
TitleFont.Style = []
end
object DBNavigator1: TDBNavigator
Left = 8
Top = 134
Width = 240
Height = 25
DataSource = DataSource1
TabOrder = 1
end
object JvMonthCalendar21: TJvMonthCalendar2
Left = 168
Top = 168
Width = 451
ParentColor = False
TabStop = True
TabOrder = 2
DateFirst = 43364.000000000000000000
DateLast = 43364.000000000000000000
MaxSelCount = 366
MultiSelect = True
Today = 43364.458842245370000000
OnSelChange = JvMonthCalendar21SelChange
end
object JvDateEditDateFirst: TJvDateEdit
Left = 24
Top = 192
Width = 121
Height = 21
ShowNullDate = False
StartOfWeek = Sun
TabOrder = 3
OnChange = JvDateEditDateFirstChange
end
object JvDateEditDateLast: TJvDateEdit
Left = 24
Top = 240
Width = 121
Height = 21
ShowNullDate = False
StartOfWeek = Sun
TabOrder = 4
OnChange = JvDateEditDateLastChange
end
object DataSource1: TDataSource
DataSet = TzDbf1
Left = 408
Top = 64
end
object TzDbf1: TTzDbf
Active = True
BeforeScroll = TzDbf1BeforeScroll
AfterScroll = TzDbf1AfterScroll
DbfFields.Strings = (
'datefirst, D, 10, 0'
'datelast, D, 10, 0')
DbfFileName =
'f:\delphi projects\theo\fillsound in delphi for mdx on 20161109\' +
'dunit\holidaytracking\race condition\dates.dbf'
HideDeletedRecs = False
TableLanguage = tlOem
ReadOnly = False
CreateIndex = ciNotFound
Exclusive = True
Left = 496
Top = 64
end
end
在这种情况下,明智的做法是使用一个全局标志,您可以检查(并避免不必要的)递归
var
FImCallingMyself: Boolean;
procedure callsitself;
begin
if FImcallingmyself then
EXIT;
FImcallingmself := True;
try
// do stuff
finally
FImcallingmyself := False;
end;
end;
在这种情况下,明智的做法是使用一个全局标志,您可以检查(并避免不必要的)递归
var
FImCallingMyself: Boolean;
procedure callsitself;
begin
if FImcallingmyself then
EXIT;
FImcallingmself := True;
try
// do stuff
finally
FImcallingmyself := False;
end;
end;
我使用
BeforePost
方法读取非db感知控件中的值并设置记录值,使用AfterScroll
方法设置非db感知控件
[编辑以显示一些基本示例代码]
BeforePost
的整个概念是有机会更改记录中的字段。这是我一直在做的一个简单的伪例子。在本例中,我使用的是Win10日期选择器。我的单位还有一个日期的私有变量,因为我还需要转换为希伯来日历。我在BeforePost
方法中的aftercroll
中检查日期选择器是否与原始日期发生了更改,然后在记录中设置字段
unit uYarzheit;
...
type
TYarzheitForm = class(Tform)
...
fdqYz : tTFDQuery;
...
dpCivilDoD : TDatePicker;
...
procedure fdqYzAfterScroll(DataSet : TDataSet);
procedure fdqYzBeforePost(DataSet : TDataSet);
...
private
dbCDod : tdatetime;
....
implementaion
...
procedure TYarzheitForm.fdqYzAfterScroll(DataSet : TDataSet);
begin
....
dbCDoD := fdqYz.FieldByName('MilestoneDate').AsDateTime;
dpCivilDoD.date := dbCDoD;
...
end;
procedure TYarzheitForm.fdqYzBeforePost(DataSet : TDataSet);
begin
if dpCivilDoD.Date <> dbCDoD then
fdqYz.FieldByName('MilestoneDate').AsDateTime := dpCivilDoD.Date;
end;
end;
单位uYarzheit;
...
类型
TYarzheitForm=类(Tform)
...
fdqYz:tTFDQuery;
...
dpdod:TDatePicker;
...
程序fdqYzAfterScroll(数据集:TDataSet);
程序fdqYzBeforePost(数据集:TDataSet);
...
私有的
dbCDod:tdatetime;
....
实施
...
程序TYarzheitForm.fdqYzAfterScroll(数据集:TDataSet);
开始
....
dbCDoD:=fdqYz.FieldByName('MilestoneDate').AsDateTime;
dpCivilDoD.date:=dbCDoD;
...
结束;
过程TYarzheitForm.fdqYzBeforePost(数据集:TDataSet);
开始
如果是dpCivilDoD.Date dbCDoD,则
fdqYz.FieldByName('MilestoneDate').AsDateTime:=dpCivilDoD.Date;
结束;
结束;
BeforePost
方法是在将记录更改写入数据库之前进行各种验证的好地方(例如,从文本字段中删除尾随空格)。我使用BeforePost
方法读取非db感知控件中的值并设置记录的值,以及用于设置非db感知控件的AfterScroll
方法
[编辑以显示一些基本示例代码]
BeforePost
的整个概念是有机会更改记录中的字段。这是我一直在做的一个简单的伪例子。在本例中,我使用的是Win10日期选择器。我的单位还有一个日期的私有变量,因为我还需要转换为希伯来日历。我在BeforePost
方法中的aftercroll
中检查日期选择器是否与原始日期发生了更改,然后在记录中设置字段
unit uYarzheit;
...
type
TYarzheitForm = class(Tform)
...
fdqYz : tTFDQuery;
...
dpCivilDoD : TDatePicker;
...
procedure fdqYzAfterScroll(DataSet : TDataSet);
procedure fdqYzBeforePost(DataSet : TDataSet);
...
private
dbCDod : tdatetime;
....
implementaion
...
procedure TYarzheitForm.fdqYzAfterScroll(DataSet : TDataSet);
begin
....
dbCDoD := fdqYz.FieldByName('MilestoneDate').AsDateTime;
dpCivilDoD.date := dbCDoD;
...
end;
procedure TYarzheitForm.fdqYzBeforePost(DataSet : TDataSet);
begin
if dpCivilDoD.Date <> dbCDoD then
fdqYz.FieldByName('MilestoneDate').AsDateTime := dpCivilDoD.Date;
end;
end;
单位uYarzheit;
...
类型
TYarzheitForm=类(Tform)
...
fdqYz:tTFDQuery;
...
dpdod:TDatePicker;
...
程序fdqYzAfterScroll(数据集:TDataSet);
程序fdqYzBeforePost(数据集:TDataSet);
...
私有的
dbCDod:tdatetime;
....
实施
...
程序TYarzheitForm.fdqYzAfterScroll(数据集:TDataSet);
开始
....
dbCDoD:=fdqYz.FieldByName('MilestoneDate').AsDateTime;
dpCivilDoD.date:=dbCDoD;
...
结束;
过程TYarzheitForm.fdqYzBeforePost(数据集:TDataSet);
开始
如果是dpCivilDoD.Date dbCDoD,则
fdqYz.FieldByName('MilestoneDate').AsDateTime:=dpCivilDoD.Date;
结束;
结束;
BeforePost
方法是在将记录更改写入数据库之前进行各种验证的好地方(例如,从文本字段中删除尾随空格)。首先,需要注意以下几点:
- 我不确定您是否知道,但是TJvDateEdit有一个db感知版本,tJvDBDateEdit-
- 有一个Embarcadero教程,介绍如何制作支持db的TMMonthCalendar版本, 它应该很容易适应TJVM日历