⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch14.htm

📁 好书《C++ Builder高级编程技术》
💻 HTM
📖 第 1 页 / 共 5 页
字号:
</BLOCKQUOTE>

<H4><A NAME="Heading27"></A><FONT COLOR="#000077">Deleting Data: A First Look at
the Programs Data 
Module</FONT></H4>
<P>Deleting data is the last topic to be covered before moving over to examination
of the <TT>TDataModule</TT> for the application. In fact, you will find that this
subject touches on matters that are specific to the program's data 
module, so it
serves as a good segue into new territory.</P>
<P>Here is the method the main form uses to delete data:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::Delete1Click(TObject *Sender)

{

  AnsiString S;

  AnsiString Msg(&quot;Are 
you sure you want to delete \r %s?&quot;);

  Set&lt;TMsgDlgBtn, 0, 8&gt; Btns;

  Btns &lt;&lt; mbYes &lt;&lt; mbNo;

  switch (dynamic_cast&lt;TComponent&amp;&gt;(*PageControl1-&gt;ActivePage).Tag)

  {

    case 1:

      Msg = Format(Msg, 
OPENARRAY(TVarRec,

        (DMod-&gt;NamesTableFirstLastCompany-&gt;AsString)));

      if (MessageDlg(Msg, mtInformation, Btns, 0) == ID_YES)

        DMod-&gt;CascadingDelete();

      break;

    case 2:

      Msg = Format(Msg, OPENARRAY(TVarRec, 
(DMod-&gt;Address)));

      if (MessageDlg(Msg, mtInformation, Btns, 0) == ID_YES)

        DMod-&gt;AddressTable-&gt;Delete();

      break;

    case 3:

      Msg = Format(Msg, OPENARRAY(TVarRec, (DMod-&gt;Phone)));

      if (MessageDlg(Msg, 
mtInformation, Btns, 0) == ID_YES)

        DMod-&gt;PhoneTable-&gt;Delete();

      break;

    case 4:

      Msg = Format(Msg, OPENARRAY(TVarRec, (DMod-&gt;EMail)));

      if (MessageDlg(Msg, mtInformation, Btns, 0) == ID_YES)

        
DMod-&gt;EMailTable-&gt;Delete();

      break;

    default:

      ShowMessage(GetError(1, S));

  }

}

</FONT></PRE>
<P>As you can see, this code uses the <TT>Tag</TT> field of the <TT>TTabSheet</TT>
to determine which table is focused.</P>
<P>The 
code then pops up a message box asking the user if he or she is sure about
continuing with the deletion. In some cases, you can easily give the user an intelligent
prompt about the contents of the current field:</P>
<PRE><FONT COLOR="#0066FF">Msg = 
Format(Msg, OPENARRAY(TVarRec,

  (DMod-&gt;NamesTableFirstLastCompany-&gt;AsString)));

</FONT></PRE>
<P>In this case, the string garnered from one of the fields of the <TT>NamesTable</TT>
provides all the information the user needs. In fact, the 
<TT>FirstLast</TT> field
of the database is a calculated field. This calculated field consists of combined
information from the <TT>First</TT>, <TT>Last</TT>, and <TT>Company</TT> fields of
the <TT>kdNames</TT>. This combined information uniquely 
identifies a record so the
user can feel secure when deleting it:</P>
<PRE><FONT COLOR="#0066FF"> void __fastcall TDMod::NamesTableCalcFields(TDataSet *DataSet)

{

  AnsiString Temp = NamesTableFirstName-&gt;Value + &quot; &quot; 
+NamesTableLastName-&gt;Value;

  if (Temp == &quot; &quot;)

    NamesTableFirstLastCompany-&gt;Value = NamesTableCompany-&gt;Value;

  else

    NamesTableFirstLastCompany-&gt;Value = Temp;

}

</FONT></PRE>
<P>As you can see, this code combines the 
first and last names into a single string.
If the string is not empty, it is shown to the user as if it were a standard field
of the database. If the current record has no information in either the first or
last field, then the program assumes that 
the record must contain only company information:</P>
<PRE><FONT COLOR="#0066FF">NamesTableFirstLastCompany-&gt;Value = NamesTableCompany-&gt;Value;

</FONT></PRE>
<P>The end result of this system is to show the user records that contain either

someone's first or last name or just a company name. This way, you can ask the database
to perform double duty as both a way of tracking company names and as a means of
tracking the names of people.</P>
<P>This calculated field can be used not only to 
help with deletions, but also as
an index appearing on the extreme left of the main form, as shown in Figure 14.1.
The user will never edit this field directly but will use it as a guide to all the
nearby records in the database. This kind of index is 
useful if you're searching
for a particular name. For example, I use the database to track the members of my
family. As a result, it has lots of Calverts in it. I can use the Last Name search
to find the section where the Calverts are stored and then 
use the index to move
back and forth between members of the family.

<DL>
	<DT></DT>
</DL>



<BLOCKQUOTE>
	<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>You actually have no guarantee that
	the string generated by this calculated field 
will be unique. The program is designed
	to make sure the <TT>NameCode</TT> in the <TT>kdNames</TT> table is unique, but nothing
	in the program prevents you from entering two identical names, addresses, phone numbers,
	and so on. 
<HR>



</BLOCKQUOTE>

<P>If the user wants to delete an address, you once again need to provide information
from several different fields to identify a record uniquely, as you can see in Figure
14.7.<BR>
<BR>
<A NAME="Heading29"></A><A 
HREF="14ebu07.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/14/14ebu07.jpg">FIGURE 14.7.</A><FONT COLOR="#000077">
</FONT><I>A prompt that uniquely identifies the address shown in a particular row.</I></P>
<P>This time, I found it more convenient simply to add to the data module a method
that would 
return a string uniquely identifying a record:</P>
<PRE><FONT COLOR="#0066FF">AnsiString __fastcall TDMod::GetAddress()

{

  return DMod-&gt;AddressTableAddress1-&gt;AsString + `\r' +

    DMod-&gt;AddressTableAddress2-&gt;AsString + `\r' +

    
DMod-&gt;AddressTableCity-&gt;AsString + `\r' +

    DMod-&gt;AddressTableState-&gt;AsString + `\r' +

    DMod-&gt;AddressTableZip-&gt;AsString;

}

</FONT></PRE>
<P>I thought the most sensible approach was to add a read-only property to the data

module to aid in retrieving this information:</P>
<PRE><FONT COLOR="#0066FF">__property AnsiString Address={read=GetAddress};

</FONT></PRE>
<P>You can access this property by writing code that looks like this:</P>
<PRE><FONT 
COLOR="#0066FF">AnsiString S = DMod-&gt;Address;

</FONT></PRE>
<P>In this particular case, it is arguable that a property doesn't do much for you
other than cover the remote contingency that you might change the parameters of the
<TT>GetAddress</TT> 
method. On the other hand, the property doesn't cost you anything
either because the compiler will obviously map any calls to the <TT>Address</TT>
property directly to the <TT>GetAddress</TT> method. In other words, this programming
is very cautious 
because it is unlikely that the <TT>GetAddress</TT> method will
ever change its spots. However, being conservative when writing code is almost always
best, as long as you're not doing serious damage to the performance of your program.</P>
<P>I won't 
bother discussing any of the means for deleting from the other tables
in this program, as they follow the same pattern already established. The key point
to grasp is that you have to show several fields to the user to identify a record
uniquely. 
Furthermore, placing the burden of generating these strings on the program's
data module is probably best. The reason for doing so is simply that the generation
of these strings is dependent on the structure of the tables underlying the program.

Isolating all code dependent on these structures inside one object is best so that
you won't have to hunt all over your program to find code that might need to be modified
because of a change in the program's database.
<H4><A 
NAME="Heading30"></A><FONT COLOR="#000077">The Data Module: Cascading Deletes</FONT></H4>
<P>You have already seen that the data module contains special properties that retrieve
strings uniquely identifying certain records. You have also seen the 
calculated field
that generates a string &quot;uniquely&quot; identifying records from the <TT>kdNames</TT>
table. What's left to explore are methods that aid in posting and deleting records.</P>
<P>The issue here is simply that the database contains 
a number of tables. If the
user wants to delete a name from the database, then he or she is really asking to
not just delete the name, but also the addresses, phone numbers, and other information
associated with that name. This process is known as a 
cascading delete.</P>
<P>Delphi provides support for cascading deletes via the referential integrity dialog
found in the Database Desktop. You can see this option in Figure 14.8.</P>
<P>Many databases do not support cascading deletes, so you can 
implement it on the
client side with just a few lines of code:</P>
<PRE><FONT COLOR="#0066FF">void TDMod::CascadingDelete(void)

{

  EMailDeleteQuery-&gt;ParamByName(&quot;NameCode&quot;)-&gt;AsInteger =

    EMailTableNameCode-&gt;Value;

  
EMailDeleteQuery-&gt;ExecSQL();

  MemoDeleteQuery-&gt;ParamByName(&quot;NameCode&quot;)-&gt;AsInteger =

    MemoTableNameCode-&gt;Value;

  MemoDeleteQuery-&gt;ExecSQL();

  PhoneDeleteQuery-&gt;ParamByName(&quot;NameCode&quot;)-&gt;AsInteger =

    
PhoneTableNameCode-&gt;Value;

  PhoneDeleteQuery-&gt;ExecSQL();

  AddressDeleteQuery-&gt;ParamByName(&quot;NameCode&quot;)-&gt;AsInteger =

    AddressTableNameCode-&gt;Value;

  AddressDeleteQuery-&gt;ExecSQL();

  
NamesDeleteQuery-&gt;ParamByName(&quot;NameCode&quot;)-&gt;AsInteger =

    NamesTableNameCode-&gt;Value;

  NamesDeleteQuery-&gt;ExecSQL();

  NamesTable-&gt;Refresh();

}

</FONT></PRE>
<P><FONT COLOR="#0066FF"><BR>
<A NAME="Heading31"></A></FONT><A 
HREF="14ebu08.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/14/14ebu08.jpg">FIGURE 14.8.</A><FONT
COLOR="#000077"> </FONT><I>Choose Cascade or Restrict to get support for cascading
deletes in databases that support this feature.</I></P>
<P>This code looks a bit complicated, in part because some of 
the lines are long
and need to be wrapped. Underneath, however, its structure is very simple. I simply
walk down the list of tables in the database, accessing the <TT>kdNames</TT> table
last. I have created a SQL statement for each table that will 
delete all the records
in the table that have a particular <TT>NameCode</TT>. For example, here are the
SQL statements for deleting records in the <TT>kdNames</TT> or <TT>kdAdds</TT> tables:</P>
<PRE><FONT COLOR="#0066FF">Delete from kdNames where 
NameCode = :NameCode

Delete from KDAdds where NameCode = :NameCode

</FONT></PRE>
<P>As I said, this technology is very simple, and the act implementing cascading
deletes in your application is trivial. The key to the whole process is simply 
recognizing
that this act is the responsibility of the program's data module. Then you can create
a simple method in the data module to handle the logic of the operation, and after
about five minutes work, you have a method that can be called from 
anywhere in your
application with a single line of code. (You should, however, take more than five
minutes to test your code against sample data to make sure that it is working properly.)

<DL>
	<DT></DT>
</DL>



<BLOCKQUOTE>
	<P>
<HR>
<FONT 
COLOR="#000077"><B>NOTE:</B></FONT><B> </B>You should use the alias system
	built into the BDE to aid in the process of creating sample data against which you
	can run tests. In particular, the ideal way to set up this situation is to include
	a 
<TT>TDatabase</TT> object in your database and then attach each of your tables
	and queries to that single <TT>TDatabase</TT> object. That way, you can change the
	alias for the <TT>TDatabase</TT> object and thereby globally change the data all
	your 
tables are accessing. Needless to say, you should also make a dummy copy of
	your data and place it in a separate database (directory, in Paradox). <BR>
	<BR>
	I don't use this structure in the current program, but I will in the sections on
	InterBase 
programs that are coming up in the next few chapters. For example, the
	Music program from Chapter 16, &quot;Advanced InterBase Concepts,&quot; uses this
	structure. 
<HR>


</BLOCKQUOTE>

<H4><A NAME="Heading33"></A><FONT COLOR="#000077">The Data 
Module: Mass Posts</FONT></H4>
<P>The opposite problem from deleting records occurs when you have to post the data
in your program. In these cases, you want to be sure that all the data in all the
tables is posted. You wouldn't want to post just the 
data in the <TT>kdNames</TT>
table and then leave updates to the <TT>kdAddress</TT> or <TT>kdPhones</TT> tables
stranded.</P>
<P>The methods that handle posting the data look like this:</P>
<PRE><FONT COLOR="#0066FF">void DoPost(TDataSet *Data)

{

  
if ((Data-&gt;State == dsInsert) || (Data-&gt;State == dsEdit))

    Data-&gt;Post();

}

void TDMod::PostAll(void)

{

  int i;

  for (i = 0; i &lt; ComponentCount; i++)

    if (dynamic_cast&lt;TTable*&gt;(Components[i]))

      
DoPost(dynamic_cast&lt;TDataSet*&gt;(Components[i]));

}

</FONT></PRE>
<P>This code iterates through all the components on the program's data module looking
for <TT>TTable</TT> objects. When the code finds one, it passes the object to a method
called 
<TT>DoPost</TT> that calls the <TT>Post</TT> method for the table. The code
in <TT>DoPost</TT> first checks to make sure the table is in <TT>dsInsert</TT> or
<TT>dsEdit</TT> mode, as it is an error to call <TT>Post</TT> on a table that is
in 
<TT>dsBrowse</TT> or some other mode where a <TT>Post</TT> can't occur.</P>
<P>Notice that I use the <TT>ComponentCount</TT> property of <TT>TDataModule</TT>
to determine how many components I need to check. I then call <TT>dynamic_cast</TT>
to check 
whether it is safe to assume the current component is a <TT>TTable</TT>.
The preceding code is actually a bit wasteful of clock cycles, in that it is not
necessary to support the overhead of a <TT>dynamic_cast</TT> after you're sure the
cast will 
succeed:</P>
<PRE><FONT COLOR="#0066FF">void TDMod::PostAll(void)

{

  int i;

  for (i = 0; i &lt; ComponentCount; i++)

    if (dynamic_cast&lt;TTable*&gt;(Components[i]))

      DoPost((TDataSet*)(Components[i]));

}

</FONT></PRE>
<P>In this 
case, the first cast has additional overhead but is safe, in that no exceptions
will be raised if it fails. The second cast is then guaranteed to succeed, so you
can ask the compiler to do it at compile time rather than generate runtime code:</P>

<PRE><FONT COLOR="#0066FF">DoPost((TDataSet*)(Components[i]));

</FONT></PRE>
<H4><A NAME="Heading34"></A><FONT COLOR="#000077">Putting Error Strings in String
Resources</FONT></H4>
<P>The other subject worth touching on briefly in regard to this 
program involves
the matter of using string resources to handle error strings. The program has a very
small string resource that contains only one string:</P>
<PRE><FONT COLOR="#0066FF">#include &quot;kderrs.inc&quot;

STRINGTABLE

{

  
KDERR_CASESTATEMENT, &quot;Command fell through case statement&quot;

}

</FONT></PRE>
<P>In a program used in the real world, you would probably want to generate many
more error strings.</P>
<P>You can use the following method to retrieve error 
strings from the program's
resource:</P>
<PRE><FONT COLOR="#0066FF">#define ERR_STRING_SIZE 255

AnsiString GetError(int ErrNo, AnsiString &amp;S)

{

  S.SetLength(ERR_STRING_SIZE);

  LoadString(HINSTANCE(HInstance), 1, S.c_str(), ERR_STRING_SIZE);

  
return S;

}

</FONT></PRE>
<P>This code calls the Windows API routine called <TT>LoadString</TT> to do the actual
grunt work. Several built-in VCL routines also provide this same functionality. Notice
that an <TT>include</TT> file defines th

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -