📄 suggest.htm
字号:
</P>
<pre>
Table1<b>-></b>FieldByName<b>(</b>seq_no<b>)</b><b>-></b>AsVariant <b>=</b> Table2seq_no<b>-></b>AsVariant<b>;</b>
Table1<b>-></b>FieldByName<b>(</b>date<b>)</b> <b>-></b>AsVariant <b>=</b> Table2date <b>-></b>AsVariant<b>;</b>
</pre>
<BR>
<H3>
<A NAME="currency">Don't use TCurrencyField unless you have to</A>
</H3>
<P>
<TT>TCurrencyField</TT> is a poorly named class. This field type is used to represent decimal numbers in a database.
Since the class name is <TT>TCurrencyField</TT>, you might expect that the class uses the <TT>Currency</TT> datatype
internally. This is not the case. <TT>TCurrencyField</TT> uses floating point numbers internally to store data.
</P>
<P>
Unfortunately, floating point numbers can lead to problems in database applications, particularly when dealing with
fields that represent dollar amounts. Your dollar amounts may, at times, appear to be off by a penny, and to some
bankers, this is a big deal! To see what I mean, run a program that displays a <TT>TCurrencyField</TT> in a
<TT>DBEdit</TT>. Edit the field and set the amount to $12.1351. When the value is written, the half penny causes the
.1351 to round up to .14. This is correct. Now try to enter 12.1350. Once again, the amount should be rounded up. When
I test this on the orders database that comes with C++Builder, I see that the amount rounds down to $12.13. This may
not seem like a big deal, but the problem gets worse when you start connecting to SQL servers, such as MS SQL
Server and Oracle, where the database's internal format of a number may be different than the BDE's representation of
the value.
</P>
<P>
The solution to this problem is to switch to the <TT>TBCDField</TT> class whenever possible. <TT>TBCDField</TT>
represents its data internally using the <TT>Currency</TT> datatype. The <TT>Currency</TT> datatype doesn't suffer
from the same formatting and rounding problems that plague floating point numbers. You can tell the BDE to use
<TT>TBCDField</TT>'s by default. To do so, run the BDE administrator, select the configuration tab, find the driver
that you are using, and set the value of <TT>Enable BCD</TT> to <TT>true</TT>. The <TT>Enable BCD</TT> parameter can
also be added to the <TT>Params</TT> property of <TT>TDatabase</TT>.
</P>
<BR>
<H3>
<A NAME="cachedfilter">Don't turn on CachedUpdates when the Filtered property of dataset is on</A>
</H3>
<P>
When you turn on <TT>CachedUpdates</TT>, only the records that are active in the filter are cached.
If this is the behavior that you expect, then it's no big deal. This presents a problem if you were
expecting that all records would get cached, regardless of whether they meet the current filter requirements.
For example:
</P>
<pre>
<font color="navy">// assume that filters and cachedupdates are already off</font>
<font color="navy">// filter out all records where the stat is not iowa</font>
Table1<b>-></b>Filter <b>=</b> <font color="blue">"state = 'IA'"</font><b>;</b>
Table1<b>-></b>Filtered <b>=</b> <b>true</b><b>;</b>
Table1<b>-></b>CachedUpdates <b>=</b> <b>true</b><b>;</b>
</pre>
<P>
In this code example, only records where the state is Iowa will be cached.
</P>
<BR>
<H3>
<A NAME="updatefilter">Don't call ApplyUpdates when the Filtered property of dataset is on</A>
</H3>
<P>
If you call <TT>ApplyUpdates</TT> on a filtered dataset, only the active records that meed the filter requirements
will be updated. Records that are hidden because of the filter will not be updated. This isn't so bad if you want
this behavior. However, you will notice subtle data loss problems if you expect all records to get updated. Inserted
records that are hidden because of the filter won't be inserted, and modified records that are hidden won't be updated.
</P>
<P>
To ensure that all records get applied, turn off filters while applying the updates.
</P>
<pre>
Query1<b>-></b>Filtered <b>=</b> <b>false</b><b>;</b>
Query1<b>-></b>ApplyUpdates<b>(</b><b>)</b><b>;</b>
Query1<b>-></b>CommitUpdates<b>(</b><b>)</b><b>;</b>
Query1<b>-></b>Filtered <b>=</b> <b>true</b><b>;</b>
</pre>
<BR>
<H3>
<A NAME="dataaware">Don't use the VCL's data aware controls</A>
</H3>
<P>
The data aware controls that come with C++Builder are extremely inflexible to change. Deriving a new control from
<TT>TDBEdit</TT> or <TT>TDBCheckBox</TT> doesn't buy you much because just about everything in the base class is
private. If you want to modify a data aware control, you essentially have to create a new control from scratch and
duplicate the existing functionality that exists in the VCL class. Duplicating functionality is bad practice.
</P>
Take the <TT>TDBCheckBox</TT> class for example. I recently wanted to derive a new control that modified the behavior
of the default <TT>DBCheckBox</TT>. All I wanted to do was to update the record as soon as the box was checked,
instead of waiting until the user tabbed to a new control. In order to modify the control, I needed access to the
<TT>TDataLink</TT> member of the base class. Unfortunately it is declared private, so I could't access it my derived
class. Because the <TT>TDataLink</TT> and its associated methods were declared private, I could not modify the behavior
of the control without resorting to hacks (and yes, the <TT>CM_GETDATALINK</TT> message is a hack).
<P>
Before you begin a large database project with C++Builder, I suggest that you copy all of the code from DBCTRLS.PAS
into a new unit. This way, if you need to make a change or derive a new class, you can do so without hacking your way
into the default VCL controls. If you find that you don't need to modify the default functionality, then you haven't lost
anything.
</P>
<P><B>Note:</B> In order to avoid confusion with the existing VCL controls, I rename my altered controls. For example,
<TT>TDBEdit</TT> becomes <TT>TXDBEdit</TT>, and <TT>TDBCheckBox</TT> becomes <TT>TXDBCheckBox</TT>. I then add these
controls to my user package.
</P>
<BR>
<H3>
<A NAME="lookupcontrols">Don't use TDBLookupComboBox or TDBLookupListBox</A>
</H3>
<P>
<B>Update:</B> This suggestion applies to BCB3 and BCB4. The bug has been fixed in BCB5.
</P>
<P>
<TT>TDBLookupComboBox</TT> and <TT>TDBLookupListBox</TT> both derive from <TT>TDBLookupControl</TT>. Unfortunately, as
of version 4.0 of the VCL, <TT>TDBLookupControl</TT> has a nasty bug that can cause access violations whenever a lookup
object is destroyed.
</P>
<P>
The problem resides in the <TT>Destroy</TT> method of <TT>TDBLookupControl</TT>.
</P>
<PRE>
<B>destructor</B> TDBLookupControl.Destroy;
<B>begin</B>
FListFields.Free;
FListLink.FDBLookupControl := <B>nil</B>;
FListLink.Free;
FDataLink.FDBLookupControl := <B>nil</B>;
FDataLink.Free;
inherited Destroy;
<B>end</B>;
</PRE>
<P>
<TT>Destroy</TT> is responsible for deleting two datalink objects called <TT>FListLink</TT> and <TT>FDataLink</TT>. Notice
that neither object is set to <TT>nil</TT> after being deleting. This causes problems in the <TT>Notification</TT> method,
which gets called when control passes to the base class <TT>Destroy</TT> methods via the <TT>inherited</TT> call. The
<TT>Notification</TT> method looks like this.
</P>
<PRE>
<B>procedure</B> TDBLookupControl.Notification(AComponent: TComponent;
Operation: TOperation);
<B>begin</B>
<B>inherited</B> Notification(AComponent, Operation);
<B>if</B> Operation = opRemove <B>then</B>
begin
<B>if</B> (FDataLink <> <B>nil</B>) <B>and</B> (AComponent = DataSource) <B>then</B> DataSource := nil;
<B>if</B> (FListLink <> <B>nil</B>) <B>and</B> (AComponent = ListSource) <B>then</B> ListSource := nil;
<B>end</B>;
<B>end</B>;
</PRE>
<P>
Observe how the <TT>Notification</TT> method first checks to see if the datalink objects are <TT>nil</TT> before it
attempts to dereference them. The problem is that the destructor does not set the datalink objects to <TT>nil</TT>
after it destroys them. This causes the <TT>if</TT> statement to pass even after the datalink objects have been deleted.
As a result, the <TT>Notification</TT> method makes an assigment to deleted memory, which can cause access violations.
</P>
<P>
To work around this bug, you must either not use the lookup controls, or you will have to find a way to recode
<TT>TLookupControl::Destroy</TT> so that it sets the datalink objects to <TT>nil</TT> after deleting them. If you
heeded the advice about <A HREF="#dataaware">not using the built in data aware controls</A>, then you can fix the
<TT>Destroy</TT> function by changing it as shown below.
</P>
<PRE>
<B>destructor</B> TDBLookupControl.Destroy;
<B>begin</B>
FListFields.Free;
FListLink.FDBLookupControl := <B>nil</B>;
FListLink.Free;
FListLink := <B>nil</B>;
FDataLink.FDBLookupControl := <B>nil</B>;
FDataLink.Free;
FDataLink := <B>nil</B>;
inherited Destroy;
<B>end</B>;
</PRE>
<P><B>Note:</B> Borland is aware of this bug.</P>
<BR>
<H3>
<A NAME="active">Don't set the Active property of a dataset to true at design time</A>
</H3>
In my database projects, I have a large number of <TT>TQuery</TT> controls that function as lookup datasets for
combo boxes and lookup fields. These controls usually perform a <TT>select *</TT> out of some lookup table. For these
lookup datasets, it is often convenient to set <TT>Active</TT> to true at design time. However, you should avoid this
temptation.
<P>
Here is the problem that I have witnessed. Whenever I open a data module in the IDE that contains a dataset where
<TT>Active</TT> is true, the dataset attempts to perform the query against that database. What happens if the server is
down, or a connection cannot be made to the database? If the dataset cannot be opened, C++Builder silently sets the
<TT>Active</TT> property of the control back to false. This is bad. No warning is given that the control is being
modified, and you won't notice the problem until you run your program. Lookup fields will appear to be blank, and
lookup combo boxes will be empty.
</P>
<P>
To avoid this problem, especially in a large project, make the assignment to <TT>Active</TT> in code. The constructor
of a data module is a good place to do this.
</P>
<BR>
<H3>
<A NAME="filteredit">Don't change the Filter property of a dataset while the dataset is in edit mode</A>
</H3>
<P>
When you change the <TT>Filter</TT> or <TT>Filtered</TT> property of a dataset, the VCL calls the
<TT>CheckBrowseMode</TT> function. This function posts any changes if the dataset was in insert or edit mode.
This may or may not cause problems in your projects, but it is something to be aware of. Messing with the filter
of a dataset takes the dataset out of edit mode.
</P>
<BR>
<H3>
<A NAME="updatestatus">Don't look at the value of UpdateStatus in the OnUpdateRecord handler of a dataset</A>
</H3>
<P>
<TT>UpdateStatus</TT> returns the cached update record status of the current record. When you apply cached updates,
the VCL loops through and calls your <TT>OnUpdateRecord</TT> handler for each record that was modified, inserted, or
deleted. However, the VCL does not navigate from record to record as it calls your handler. The record pointer, or
cursor, does not move. As such, <TT>UpdateStatus</TT> does not change to reflect the status of the record being
updated. It reflects the status of the current record, and not the record that is being updated.
</P>
<H3>
<A NAME="disablecontrols">Don't call Post after calling DisableControls on a dataset</A>
</H3>
<P>
The dataset classes in BCB provide two methods called <TT>DisableControls</TT> and <TT>EnableControls</TT> that come in
handy when you have to perform some processing on the dataset. Calling <TT>DisableControls</TT> effectively disconnects the dataset
from all of its data aware controls. The benefit of doing this is that you can work with a dataset without having the data-aware controls
refreshing themselves all of the time.
</P>
<P>
There is something you should be aware of though. You never want to call the <TT>Post</TT> method after calling <TT>DisableControls</TT>.
Why is this? Well, let's say that a user has entered something into a <TT>DBEdit</TT> control. A <TT>DBEdit</tt> control updates its field
whenever you tab to a new control, or if you call the <TT>Post</TT> method. However, if you call <TT>DisableControls</TT> before
calling <TT>Post</TT>, the changes that were made in the <TT>DBEdit</TT> control do not get posted to the dataset.
</P>
<P>
<B>Note:</B> This bug will only show up if the <TT>DBEdit</TT> retains the input focus during the entire time that <TT>DisableControls</TT>
and <TT>Post</TT> are called. Well, how likely is that? More likely than you might think. If you call <TT>Post</TT> from a menu click event,
or from the <TT>OnClick</TT> event of a toolbar button, then the <TT>DBEdit</TT> will keep the focus. If you call <TT>DisableControls</TT>
before calling post, then changes that were made to the <TT>DBEdit</tt> control will be lost.
</P>
</TD> </TR>
</TABLE>
</CENTER>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -