📄 ch15.htm
字号:
#include <typinfo.hpp>
#include <sysutils.hpp>
#pragma hdrstop
#include "Main.h"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall
TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::ApplyBtnClick(TObject *Sender)
{
OrdersTable->ApplyUpdates();
}
void __fastcall TForm1::RevertBtnClick(TObject *Sender)
{
OrdersTable->RevertRecord();
}
void __fastcall TForm1::CancelClick(TObject *Sender)
{
OrdersTable->CancelUpdates();
}
void __fastcall TForm1::OkBtnClick(TObject *Sender)
{
Close();
}
void __fastcall TForm1::OrdersTableUpdateError(TDataSet *DataSet,
EDatabaseError *E, TUpdateKind UpdateKind, TUpdateAction &UpdateAction)
{
TTypeInfo TypeInfo;
AnsiString UpdateKindStr[] = {"Modified", "Inserted", "Deleted"};
AnsiString S(UpdateKindStr[UpdateKind]);
S
+= ": " + E->Message;
AnsiString Temp = DataSet->Fields[0]->OldValue;
Temp = + ": " + S;
ListBox1->Items->Add(Temp);
UpdateAction = uaSkip;
}
void __fastcall TForm1::DataSource1DataChange(TObject
*Sender,
TField *Field)
{
AnsiString UpdateStat[] = {"Unmodified", "Modified", "Inserted", "usDeleted"};
Panel1->Caption = UpdateStat[OrdersTable->UpdateStatus()];
if
(OrdersTable->UpdateStatus() == usModified)
{
Edit1->Text = OrdersTable->Fields[0]->OldValue;
Edit2->Text = OrdersTable->Fields[0]->NewValue;
}
else
{
Edit1->Text = "Unmodified";
Edit2->Text = "Unmodified";
};
}
void __fastcall TForm1::ListBox1DblClick(TObject *Sender)
{
AnsiString S(ListBox1->Items->Strings[ListBox1->ItemIndex]);
if (S.Length() > 0)
ShowMessage(S);
}
</FONT></PRE>
<P>The first thing to notice about the CachedUpdates program is that it tracks which
records have been modified. For example, change the <TT>OrderNo</TT> field of the
first two records to the values <TT>1</TT> and <TT>2</TT>. If you now select one
of
these records, you will see that the small panel in the lower left corner of the
screen gets set to <TT>Modified</TT>. This means that the update status for this
field has been set to modified.</P>
<P>Here is the <TT>TUpdateStatus</TT> type:</P>
<PRE><FONT COLOR="#0066FF">TUpdateStatus = (usUnmodified, usModified, usInserted, usDeleted);
</FONT></PRE>
<P>Any particular record in a database is going to be set to one of these values.</P>
<P>Here is the code that sets the value in the
<TT>TPanel</TT> object:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::DataSource1DataChange(TObject *Sender,
TField *Field)
{
AnsiString UpdateStat[] = {"Unmodified", "Modified", "Inserted",
"usDeleted"};
Panel1->Caption = UpdateStat[OrdersTable->UpdateStatus()];
if (OrdersTable->UpdateStatus() == usModified)
{
Edit1->Text = OrdersTable->Fields[0]->OldValue;
Edit2->Text =
OrdersTable->Fields[0]->NewValue;
}
else
{
Edit1->Text = "Unmodified";
Edit2->Text = "Unmodified";
};
}
</FONT></PRE>
<P>The relevant line in this case is the second in the body of the function.
In particular,
notice that it reports on the value of <TT>OrdersTable->UpdateStatus</TT>. This
value will change to reflect the update status of the currently selected record.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT
COLOR="#000077"><B>NOTE:</B></FONT><B> </B>Notice that in this case, I explicitly
type out the names associated with the elements of the <TT>TUpdateStatus</TT> type.
In some cases, you can use an alternative means to accomplish the same end without
explicitly typing out the strings. This second technique involves using the advanced
RTTI supported by BCB. To show this value to the user, the code could call the <TT>GetEnumName</TT>
routine from the <TT>TypInfo</TT> unit. This routine retrieves
the name of an enumerated
value. To use this routine, pass in the type that you want to examine, as well as
the ordinal value of the element in that type whose name you want to see:</P>
<PRE><FONT COLOR="#0066FF">PPropInfo PropInfo =
GetPropInfo(PTypeInfo(ClassInfo(__classid(TForm1))), "Borderstyle");
ShowMessage(GetEnumName(PropInfo->PropType, int(bsDisabled)));</FONT></PRE>
<P>Unfortunately, this type of code will work only for VCL-style classes and for
properties of VCL-style classes. Because a <TT>TDataSet</TT> does not have an <TT>UpdateStatus</TT>
property, the code in the <TT>DataSource1DataChange</TT> method must use the more
pedantic method outlined previously.
<HR>
</BLOCKQUOTE>
<P>At
the same time that the CachedUpdates program reports that a record has been
modified, it also reports on the old and new value of the <TT>OrderNo</TT> field
for that record. In particular, if you change the first record's <TT>OrderNo</TT>
field to
<TT>1</TT>, it will report that the old value for the field was <TT>1003</TT>,
and the new value is <TT>1</TT>. (This assumes that you have the original data as
it shipped with BCB. Remember that if you end up ruining one of these tables performing
these kinds of experiments, you can always copy the table over again from the CD.)</P>
<P>In the code that reports on the old and new value of the <TT>OrderNo</TT> field,
you should examine these lines in particular:</P>
<PRE><FONT
COLOR="#0066FF">Edit1->Text = OrdersTable->Fields[0]->OldValue;
Edit2->Text = OrdersTable->Fields[0]->NewValue;
</FONT></PRE>
<P>As you can see, this information is easy enough to come by--you just have to know
where to look.</P>
<P>If you enter the values <TT>1</TT> and <TT>2</TT> into the <TT>OrderNo</TT> fields
for the first two records, you will encounter errors when you try to commit the data.
In particular, if you try to apply the data, the built-in referential integrity
will
complain that there is no way to link the <TT>Orders</TT> and <TT>Items</TT> table
on the new <TT>OrderNo</TT> you have created. As a result, committing the records
is not possible. The code then rolls back the erroneous records to their original
state.</P>
<P>When viewing these kinds of errors, choose Options | Environment | Preferences
and then turn off the Break on Exception option. The issue here is that you want
the exception to occur, but you don't want to be taken to the line in your
program
where the exception surfaced. You don't need to view the actual source code because
these exceptions are not the result of errors in your code. In fact, these exceptions
are of the kind you want and need to produce and which appear to the user
in an orderly
fashion via the program's list box.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>Referential integrity is a means
of enforcing the rules in a database. This subject is discussed in
some detail in
Chapter 16 and also in Chapter 12, "Understanding Relational Databases."
For now, you should not be concerned with the details of how referential integrity
works. The key point is simply that some tables have to obey rules,
and the BDE will
not let users enter invalid data that violates these rules.
<HR>
</BLOCKQUOTE>
<P>Here is the code that reports on the errors in the <TT>OrderNo</TT> field and
rolls back the data to its original state:</P>
<PRE><FONT
COLOR="#0066FF">void __fastcall TForm1::OrdersTableUpdateError(TDataSet *DataSet,
EDatabaseError *E, TUpdateKind UpdateKind, TUpdateAction &UpdateAction)
{
TTypeInfo TypeInfo;
AnsiString UpdateKindStr[] = {"Modified",
"Inserted", "Deleted"};
AnsiString S(UpdateKindStr[UpdateKind]);
S += ": " + E->Message;
AnsiString Temp = DataSet->Fields[0]->OldValue;
Temp = + ": " + S;
ListBox1->Items->Add(Temp);
UpdateAction = uaSkip;
}
</FONT></PRE>
<P>This particular routine is an event handler for the <TT>OnUpdateError</TT> event
for the <TT>Table1</TT> object. To create the routine, click once on the <TT>Table1</TT>
object, select its Events page in the Object Inspector, and then double-click the
<TT>OnUpdateError</TT> entry.</P>
<P>The <TT>OrdersTableUpdateError</TT> method will get called only if an error occurs
in attempting to update records. It will get
called at the time the error is detected
and before BCB tries to commit the next record.</P>
<P><TT>OrdersTableUpdateError</TT> gets passed four parameters. The most important
is the last, which is a <TT>var</TT> parameter. You can set this parameter
to one
of the following values:</P>
<PRE><FONT COLOR="#0066FF">TUpdateAction = (uaAbort, uaSkip, uaRetry, uaApplied);
</FONT></PRE>
<P>If you set the <TT>UpdateAction</TT> variable to <TT>uaAbort</TT>, the entire
attempt to commit the updated data
will be aborted. None of your changes will take
place, and you will return to edit mode as if you had never attempted to commit the
data. The changes you have made so far will not be undone, but neither will they
be committed. You are aborting the
attempt to commit the data, but you are not rolling
it back to its previous state.</P>
<P>If you choose <TT>uaSkip</TT>, the data for the whole table will still be committed,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -