简体   繁体   English

如何使数据感知控件和非数据感知控件混合在一起,并与它们作用的数据库保持同步?

[英]How can I keep a mix of data-aware and non-data-aware controls in sync with one another and with the database to which they act?

I have a form with 7 controls. 我有一个带有7个控件的表单。 Two controls are data aware, a TDBGrid and a TDBNavigator. 两个控件可以识别数据,一个TDBGrid和一个TDBNavigator。 Three others are not data-aware, a TJvCalendar2 and two TjvDateEdits. 另外三个不是数据感知的,一个是TJvCalendar2,另一个是两个TjvDateEdits。 The last two controls are a TDataSource and a TTzDbf as the dataSource's dataset. 最后两个控件是TDataSource和TTzDbf作为dataSource的数据集。

I cannot, for the life of me, figure out how to update the current database record with the dates on the JvCalendar or either of the JvDateEdits without precipitating a catastrophic race condition that crashes the program. 我一生无法解决如何使用JvCalendar或任何一个JvDateEdits上的日期更新当前数据库记录,而又不会导致程序崩溃的灾难性竞争情况。

In the form's OnActivate method I copy data from the record that the database is currently positioned into a form variable. 在表单的OnActivate方法中,我从数据库当前位于表单变量中的记录中复制数据。 I then call two methods, one to update the JvCalendar and the other to update the two JvDateEdits. 然后,我调用两个方法,一个方法更新JvCalendar,另一个方法更新两个JvDateEdits。

These two methods save and then set to nil their respective control's OnChange handlers, set their control's date(s), restore the control's original OnChange handlers and then exit. 这两个方法保存,然后设置为各自控件的OnChange处理程序为零,设置其控件的日期,还原该控件的原始OnChange处理程序,然后退出。

To track when the dataset is being moved, I save and substitute the dataSet's AfterScroll and BeforeScroll events. 为了跟踪何时移动数据集,我保存并替换了dataSet的AfterScroll和BeforeScroll事件。 When the current row in the dbGrid is changed, either by a mouse click or cursor movement in the dbGrid or by a record change in the dbNavigator, these handlers update the database's record from the form variable during BeforeScroll or retrieve, set the form variable and then update the JvCalendar and JvDateEdits. 当通过单击鼠标或dbGrid中的光标移动或通过dbNavigator中的记录更改来更改dbGrid中的当前行时,这些处理程序将在BeforeScroll或检索期间从form变量更新数据库的记录,设置,设置表单变量和然后更新JvCalendar和JvDateEdits。

Saving, updating the database's record during the BeforeScroll event causes a reread of the record, an updating of the controls and then a rewrite of the database's record. 在BeforeScroll事件期间保存,更新数据库的记录会导致记录的重新读取,控件的更新以及数据库记录的重写。 All of which leads to a loop, exhaustion of stack space and a crash. 所有这些都会导致循环,堆栈空间耗尽和崩溃。

What am I missing from my understanding and implementation of event handlers and data-aware controls, please? 请问,我对事件处理程序和数据感知控件的理解和实现缺少什么?

Full example code follows: 完整的示例代码如下:

----------------------- RaceCondition.dpr ---------------------- ----------------------- RaceCondition.dpr ----------------------

/// <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.

----------------------- RaceConditionFrm.pas ---------------------- ----------------------- RaceConditionFrm.pas ----------------------

/// <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.

----------------------- RaceConditionFrm.dfm --------------------- ----------------------- RaceConditionFrm.dfm ---------------------

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

Firstly, a couple of things to note: 首先,需要注意以下几点:

  • I'm not sure if you know but there is a db-aware version of the TJvDateEdit, tJvDBDateEdit - http://wiki.delphi-jedi.org/wiki/JVCL_Help:TJvDBDateEdit 我不确定是否知道,但是否有TJvDateEdit的数据库感知版本tJvDBDateEdit- http: //wiki.delphi-jedi.org/wiki/JVCL_Help:TJvDBDateEdit

  • There is an Embarcadero tutorial about making a db-aware version of TMonthCalendar, which should be readily adaptable to TJvMonthCalender 有一个Embarcadero教程,关于制作具有数据库感知能力的TMonthCalendar版本,该版本应易于适应TJvMonthCalender

Secondly, I thought I would include an example of how to make a TMonthCalendar functionally db-aware without having to write a db-aware descendant of it. 其次,我想我将提供一个示例,该示例如何使TMonthCalendar在功能上具有数据库意识,而不必编写它的数据库意识后代。 Doing it this way avoids the need for catching and handling TDataSet and TMonthCalendar event to keep them manually synchronised. 这样做避免了捕获和处理TDataSet和TMonthCalendar事件以使其手动同步的需求。

The example below works by creating a TFieldDataLink descendant which can be created within your project and can make a standard TMonthCalendar (or TJvMonthCalendar, with trivial modification) behave as db-aware without having to create a custom TDBMonthCalendar component and install it in the Component Palette. 下面的示例通过创建一个TFieldDataLink子孙来工作,该子孙可以在您的项目中创建,并且可以使标准TMonthCalendar(或经过细微修改的TJvMonthCalendar)表现为db-aware,而无需创建自定义TDBMonthCalendar组件并将其安装在Component Palette中。 The minor downside of this is that a bit of set-up code is required. 这样做的次要缺点是需要一些设置代码。 This TCalendarDataLink class automtaically handlres all the synchronisation necessary. TCalendarDataLink类自动处理所有必需的同步。

Code

  type
    TCalendarDataLink = class(TFieldDataLink)
    private
      FCalendar: TMonthCalendar;
    protected
      property Calendar : TMonthCalendar read FCalendar write FCalendar;
      procedure CalendarClick(Sender : TObject);
      procedure DataChange(Sender : TObject);
      procedure UpdateData(Sender : TObject);
    public
      constructor Create(AOwner : TComponent; ACalendar : TMonthCalendar; ADataSource : TDataSource; const AFieldName : String);
    end;

    TForm1 = class(TForm)
      DBGrid1: TDBGrid;
      CDS1: TClientDataSet;
      DataSource1: TDataSource;
      CDS1ID: TAutoIncField;
      CDS1Value: TStringField;
      Button1: TButton;
      CDS1Name: TStringField;
      DBNavigator1: TDBNavigator;
      cbNormal: TCheckBox;
      CDS1Number: TIntegerField;
      CDS1Date: TDateField;
      MonthCalendar1: TMonthCalendar;
      procedure FormCreate(Sender: TObject);
    private
    protected
      Link : TCalendarDatalink;
    public
    end;

  [...]

  procedure TForm1.FormCreate(Sender: TObject);
  var
    i : Integer;
  begin
    CDS1.CreateDataSet;
    for i := 1 to 200 do begin
      CDS1.Insert;
      CDS1.FieldByName('Value').AsString := 'A' + Chr(Ord('A') + i);
      if Odd(i) then
        CDS1.FieldByName('Value').Clear;
      CDS1.FieldByName('Date').AsDateTime := Now - i;
      CDS1.Post;
    end;
    Link := TCalendarDataLink.Create(Self, MonthCalendar1, DataSource1, 'Date');
    CDS1.First;
  end;

  { TCalendarDataLink }

  procedure TCalendarDataLink.CalendarClick(Sender: TObject);
  var
    ADate : TDateTime;
  begin
    ADate := Calendar.Date;
    Edit;
    Calendar.Date := ADate;
    Field.Text := DateToStr(Calendar.Date);
  end;

  procedure TCalendarDataLink.DataChange(Sender: TObject);
  begin
    inherited;
    if Field <> Nil then
      if Field.IsNull then
        Calendar.Date := Now
      else
        Calendar.Date := Field.AsDateTime;
  end;

  procedure TCalendarDataLink.UpdateData(Sender: TObject);
  begin
    Field.AsDateTime := Calendar.Date;
  end;

Obviously, the TCalendarDataLink code could be included in its own unit and used from there, if desired. 显然,如果需要,TCalendarDataLink代码可以包含在其自己的单元中并从那里使用。

I use BeforePost methods for reading the values in the non db-aware controls and setting the record's values, and the AfterScroll methods for setting the non db-aware controls. 我使用BeforePost方法来读取非db-aware控件中的值并设置记录的值,并使用AfterScroll方法来设置非db-aware控件中的值。

[Edit to show some basic example code] The whole concept of BeforePost is to have a chance to change fields in the record. [编辑以显示一些基本的示例代码] BeforePost的整个概念是有机会更改记录中的字段。 This is a stripped down pseudo example of what I have been doing. 这是我一直在做的精简伪示例。 I am using the Win10 DatePicker in this example. 在此示例中,我使用的是Win10 DatePicker。 My unit also has a private variable for the date in question, since I need to convert to the Hebrew calendar, as well. 由于我还需要转换为希伯来语日历,因此我所在的单位还具有用于该日期的私有变量。 I check to see if the date picker has changed against the original date in AfterScroll in the BeforePost method, to then set the field in the record. 我检查日期选择器是否已与BeforePost方法中AfterScroll中的原始日期进行了BeforePost ,然后在记录中设置了该字段。

    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;

The BeforePost method is a great place to do all sorts of validation prior to writing the changes to the record to the database (eg, stripping trailing spaces from text fields). BeforePost记录的更改写入数据库之前, BeforePost方法是进行各种验证的好地方(例如,从文本字段中BeforePost尾随空格)。

In a situation like this, it's wise to use a global flag that you can check for (and avoid unnecessary) recursion. 在这种情况下,明智的做法是使用可以检查(并避免不必要的)递归的全局标志。

var
   FImCallingMyself: Boolean;

procedure callsitself;
begin
   if FImcallingmyself then
     EXIT;
   FImcallingmself := True;
   try
     // do stuff
   finally
      FImcallingmyself := False;
   end;
end;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM