delphi 7

49
17 ﺑﻴﺎﻧﺎت ﻗﻮاﻋﺪ ﱢﻧﺎت ﻣﻜﻮ إﻧﺸﺎء ﻣﻌﻤﻖ ﺑﺸﻜﻞ ﺩﻟﻔﻲﻧﺎﺕ ﻣﻜﻮ ﺗﻄﻮﻳﺮ ﺑﺎﺳﺘﻜﺸﺎﻑ ﺍﻟﺘﺎﺳﻊ ﺍﻟﻔﺼﻞ ﻗﻤﻨﺎ. ﻗﻮﺍﻋﺪ ﺑﺮﳎﺔ ﻧﺎﻗﺸﻨﺎ ﺃﻥ ﻭﺑﻌﺪ ﺑﻴﺎﻧﺎﺕ ﺑﻘﻮﺍﻋﺪ ﺗﺘﻌﻠﻖﻧﺎﺕ ﻣﻜﻮ ﺗﻄﻮﻳﺮ ﻋﻠﻰ ﻭﺍﻟﺘﺮﻛﻴﺰ ﺍﻟﻘﺪﱘ، ﺍﳌﻮﺿﻮﻉ ﺇﱃ ﺍﻟﻌﻮﺩﺓ ﻓﻨﺴﺘﻄﻴﻊ ﺑﻴﺎﻧﺎﺕ. ﺃﺳﺎﺳﻲ ﺑﺸﻜﻞﻧﺎﺕ ﻣﻜﻮ ﻫﻜﺬﺍ ﻣﻦ ﻋﺎﺋﻠﺘﺎﻥ ﺗﻮﺟﺪ: ﻋﻨﺎﺻ ﳝﻜﻦ ﺑﻴﺎﻧﺎﺕ، ﺇﱃ ﻣﺮﺑﻮﻃﺔ ﲢﻜﻢ ﺑﺮﻧﺎﻣﺞ ﳌﺴﺘﺨﺪﻡ ﻛﺎﻣﻼ ﺳﺠﻼ ﺃﻭ ﺣﻘﻞ ﺑﻴﺎﻧﺎﺕ ﻟﺘﻘﺪﱘ ﺍﺳﺘﺨﺪﺍﻣﻬﺎ. ﳝﻜﻦ ﺑﻴﺎﻧﺎﺕ، ﳎﻤﻮﻋﺔﻧﺎﺕ ﻭﻣﻜﻮ ﻣﻦ ﺍﻟﺒﻴﺎﻧﺎﺕ ﺑﻘﺮﺍﺀﺓ ﻭﺫﻟﻚ، ﺳﺎﺑﻘﺎ ﻣﻮﺟﻮﺩﺓ ﺑﻴﺎﻧﺎﺕ ﺇﱃ ﻣﺮﺑﻮﻃﺔ ﲢﻜﻢ ﻟﻌﻨﺎﺻﺮ ﺑﻴﺎﻧﺎﺕ ﻟﺘﺰﻭﻳﺪ ﺗﻌﺮﻳﻔﻬﺎ ﺁﺧﺮ ﺑﻴﺎﻧﺎﺕ ﻣﺼﺪﺭ ﺃﻱ ﺃﻭ ﺑﻴﺎﻧﺎﺕ ﻗﺎﻋﺪﺓ. ﻭﺳﻨﻐﻄﻲ ﺍﻟﻔﺼﻞ ﻫﺬﺍ ﺍﳌﻮﺿﻮﻋﲔ ﻛﻼ. ﺍﻟﺘﺎﻟﻴﺔ ﺍﳌﻮﺍﺿﻴﻊ ﺍﻟﻔﺼﻞ ﻫﺬﺍ ﻳﻐﻄﻲ: ﺑﻴﺎﻧﺎﺕ ﺇﱃ ﻣﺮﺑﻮﻃﺔﻧﺎﺕ ﻣﻜﻮ: ﺍﻟﺒﻴﺎﻧﺎﺕ ﺭﺑﻂ. ﺍﻟﺘﻮﺟﻪ ﺣﻘﻠﻴﺔ ﺑﻴﺎﻧﺎﺕ ﺇﱃ ﺗﺮﺑﻂ ﲢﻜﻢ ﻋﻨﺎﺻﺮ. ﻣﻜﻮTrakBar ProgressBar ﺑﻴﺎﻧﺎﺕ ﺇﱃ ﻣﺮﺑﻮﻃﲔ. ﺑﺎﻟﺴﺠﻞ ﺗﺘﻌﻠﻖ ﺑﻴﺎﻧﺎﺕ ﺇﱃ ﺗﺮﺑﻂ ﲢﻜﻢ ﻋﻨﺎﺻﺮ. ﺳﺠﻞ ﻋﺎﺭﺽ. ﳐﺼﺼﺔ ﺑﻴﺎﻧﺎﺕ ﳎﻤﻮﻋﺎﺕ ﺑﻨﺎﺀ. ﳏﻠﻲ ﳎﺮﻯ ﺑﻴﺎﻧﺎﺕ ﳎﻤﻮﻋﺔ ﺣﻔﻆ. اﻟﺒﻴﺎﻧﺎت رﺑﻂ ﺇﱃ ﺍﳌﺮﺑﻮﻃﺔ ﺍﻟﺘﺤﻜﻢ ﻋﻨﺎﺻﺮ ﺑﻌﺾ ﻋﺎﺩﺓ ﺗﺮﺑﻂ ﻓﺈﻧﻚ ﺩﻟﻔﻲ، ﺑﻴﺎﻧﺎﺕ ﻗﻮﺍﻋﺪ ﺑﺮﻧﺎﻣﺞ ﺗﻜﺘﺐ ﻋﻨﺪﻣﺎ ﻣﻜﻮ ﺇﱃ ﺑﻴﺎﻧﺎﺕDataSource ﺍﳌﻜﻮ ﺗﺮﺑﻂ، DataSource ﺑﻴﺎﻧﺎﺕ ﳎﻤﻮﻋﺔ ﺇﱃ. ﺍﻟﻮﺻﻠﺔ ﻭﺗﺴﻤﻰ ﻋﻨﺼ ﺑﲔ ﻭﺍﳌﻜﻮ ﺑﻴﺎﻧﺎﺕ ﺇﱃ ﺍﳌﺮﺑﻮﻁ ﺍﻟﺘﺤﻜﻢDataSource ﺑﻴﺎﻧﺎﺕ ﺑﺮﺑﻂData link ﺑﻮﺍﺳﻄﺔ ﻭﲤﺜﻞ ؛ ﺍﻟﺼﻨﻒ ﻣﻦ ﻏﺮﺽTDataLink ﻣﻨﻪ ﻣﺸﺘﻖ ﺻﻒ ﻣﻦ ﺃﻭ. ﻫﺬﺍ ﺑﺒﻴﺎﻧﺎﺕ ﺍﳌﺮﺑﻮﻁ ﺍﻟﺘﺤﻜﻢ ﻋﻨﺼﺮ ﻭﻳﻨﺸﺊ ﺍﻟﺒﻴﺎﻧﺎﺕ ﺇﱃ ﺍﻟﻮﺣﻴﺪﺓ ﻭﺻﻠﺘﻪ ﻭﳝﺜﻞ ﻭﻳﺪﻳﺮﻩ، ﺍﻟﻐﺮﺽ. ﻭﺟﻬﺔ ﻣﻦ ﺑﻴﺎﻧﺎﺕ ﺇﱃ ﻣﺮﺑﻮﻁ ﻣﻜﻮ ﻭﻹﻧﺸﺎﺀ

Upload: kimo480

Post on 27-Oct-2014

201 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: Delphi 7

17 إنشاء مكونات قواعد بيانات

وبعد أن ناقشنا برجمة قواعد . قمنا يف الفصل التاسع باستكشاف تطوير مكونات دلفي بشكل معمق .بيانات فنستطيع العودة إىل املوضوع القدمي، والتركيز على تطوير مكونات تتعلق بقواعد بيانات

ر حتكم مربوطة إىل بيانات، ميكن عناص: توجد عائلتان من هكذا مكونات بشكل أساسيومكونات جمموعة بيانات، ميكن . استخدامها لتقدمي بيانات حقل أو سجال كامال ملستخدم برنامج

تعريفها لتزويد بيانات لعناصر حتكم مربوطة إىل بيانات موجودة سابقا، وذلك بقراءة البيانات من .كال املوضوعني يف هذا الفصلوسنغطي . قاعدة بيانات أو أي مصدر بيانات آخر

:يغطي هذا الفصل املواضيع التالية .ربط البيانات: مكونات مربوطة إىل بيانات .عناصر حتكم تربط إىل بيانات حقلية التوجه . مربوطني إىل بياناتProgressBar وTrakBarمكوين .عناصر حتكم تربط إىل بيانات تتعلق بالسجل .عارض سجل .بناء جمموعات بيانات خمصصة .حفظ جمموعة بيانات يف جمرى حملي

ربط البياناتعندما تكتب برنامج قواعد بيانات يف دلفي، فإنك تربط عادة بعض عناصر التحكم املربوطة إىل

وتسمى الوصلة . إىل جمموعة بياناتDataSource، مث تربط املكون DataSourceبيانات إىل مكون ؛ ومتثل بواسطة Data link بربط بيانات DataSourceر التحكم املربوط إىل بيانات واملكون بني عنص

وينشئ عنصر التحكم املربوط ببيانات هذا . أو من صف مشتق منهTDataLinkغرض من الصنف وإلنشاء مكون مربوط إىل بيانات من وجهة . الغرض ويديره، وميثل وصلته الوحيدة إىل البيانات

Page 2: Delphi 7

الفصل السابع عشر ٧٨٤

عملية أكثر، حتتاج إىل إضافة ربط بيانات إليه، وتغطية بعض خصائص هذا الغرض الداخلي، نظر .DataField وDataSourceمثل اخلاصيتني

وتستخدم . من أجل اتصال ثنائي االجتاهDataLink وأغراض DataSourceتستخدم دلفي واألغراض بسبب (بيانات بأن البيانات اجلديدة متاحة جمموعة البيانات الوصلة إلشعار عناصر التحكم املربوطة بوتستخدم عناصر التحكم املربوطة ببيانات ). تفعيل جمموعة البيانات، أو تغيري السجل احلايل، وغريها

.الوصلة لطلب القيمة احلالية حلقل، أو لتعديلها، بإشعار جمموعة بيانات هذا احلدثحقيقة أن بعض الوصالت ميكن أن تكون واحد إىل تتعقد العالقات بني مجيع هذه املكونات نتيجة

فتستطيع مثال ربط مصادر بيانات متعددة إىل نفس جمموعة البيانات، ولديك ). One-to-many(كثري ألنك حتتاج إىل ارتباط واحد لكل مكون (عادة ارتباطات بيانات متعددة إىل نفس مصدر البيانات

.بط عدة عناصر حتكم مربوطة ببيانات إىل كل مصدر بيانات، ويف أغلب احلاالت تر)مرتبط ببيانات

TDataLinkالصنف ، واألصناف املشتقة منه، املعرفة TDataLinkسنتعامل يف القسم األكرب من هذا الفصل مع الصنف

ميلك هذا الصنف جمموعة من املناهج االفتراضية احملمية، اليت هلا دور مماثل . DBيف الوحدة تستطيع إعادة صياغتها يف صنف فرعي معني، ". ال تفعل شيئا يف الغالب"ناهج وهي م. لألحداث

وفيما يلي قائمة ذه املناهج . العتراض عمليات مستخدم أو أحداث مصدر بيانات آخر :مستخرجة من شيفرة املصدر هلذا الصنف

TDataLink = class(TPersistent) protected procedure ActiveChanged; virtual; procedure CheckBrowseMode; virtual; procedure DataSetChanged; virtual; procedure DataSetScrolled(Distance: Integer); virtual; procedure FocusControl(Field: TFieldRef); virtual; procedure EditingChanged; virtual; procedure LayoutChanged; virtual; procedure RecordChanged(Field: TField); virtual; procedure UpdateData; virtual;

، الذي هو نوع من إجرائية DataEventتستدعى مجيع هذه املناهج االفتراضية بواسطة املنهج اخلاص وتنشئ ). TDataEventانظر منط التعداد (نافذة ملصدر بيانات، يقدح بواسطة عدة أحداث بيانات

. تهذه األحداث حقوال أو مصدر بيانات يف جمموعة البيانات، وتطبق عادة على جمموعة بيانا. ملكون جمموعة البيانات ببث األحداث إىل مصادر البيانات املربوطةDataEventقوم املنهج يو

لدفع احلدث قدما إىل كل ارتباط بيانات NotifyDataLinksويستدعي كل مصدر بيانات املنهج . اخلاصني بهOnUpdateData أو OnDataChangeمربوط، مث يقدح مصدر البيانات احلدث

Page 3: Delphi 7

٧٨٥ إنشاء مكونات قواعد بيانات 17.

مشتقة لربط البياناتأصناففعندما حتتاج . ليس صنفا جمردا بشكل تقين، لكن نادرا ما ستستخدمه مباشرةTDataLinkالصنف

إىل إنشاء عناصر حتكم مربوطة إىل بيانات، ستحتاج إىل استخدام أحد األصناف املشتقة منه، أو ر أمهية من األصناف املشتقة الصنف األكثTFieldDataLinkويعترب الصنف . تشتق صنفا خاصا بك

؛ ويستخدم هذا الصنف املشتق من قبل عناصر التحكم املربوطة إىل بيانات اليت TDataLinkمن وتقع أغلب عناصر التحكم املربوطة إىل بيانات ضمن هذه . تتعلق حبقل واحد يف جمموعة البيانات

.ا النمط من املكونات املشاكل األكثر شيوعا هلذTFieldDataLinkالفئة، وحيل الصنف تعرف مجيع عناصر التحكم املرتبطة ببيانات املتعلقة جبدول أو سجل أصنافا فرعية خاصة من

قائمة أحداث متعلقة TFieldDataLinkوميلك الصنف . ، كما سنفعل الحقاTDataLinkالصنف الصنف أكثر بساطة، وهذا جيعل ختصيص . باملناهج االفتراضية للصنف األساس اليت يعيد صياغتها

وفيما يلي . ألنك تستطيع استخدام معاجلات أحداث، بدال من وجوب وراثة صنف جديد منه :مثال إلعادة صياغة منهج، والذي يطلق احلدث املتعلق به، إن وجد

procedure TFieldDataLink.ActiveChanged; begin UpdateField; if Assigned(FOnActiveChange) then FOnActiveChange(Self); end;

، اللتني تسمحان لك بربط FieldName وField أيضا اخلاصيتني TFieldDataLinkيتضمن الصنف وحيافظ االرتباط على مرجع إىل . عنصر حتكم مرتبط ببيانات إىل حقل معني يف جمموعة البيانات

.Controlاملكون املرئي احلايل باستخدام اخلاصية

ر تحكم مرتبطة ببيانات حقلية التوجهإنشاء عناصبعد أن فهمنا القسم النظري لكيفية عمل أصناف ربط البيانات، دعنا نبدأ بناء بعض عناصر

وسيكون املثاالن األوليان نسختني مرتبطتني ببيانات لعنصري التحكم . التحكم املرتبطة ببيانات) مثل نسبة مئوية(ول لعرض قيمة عددية حيث ميكن استخدام األ. TrackBar وProgressBarالعامني

.وميكن استخدام الثاين للسماح للمستخدم بتغيري القيمة العددية. بطريقة مرئية

، الذي MdDataPack توجد شيفرة جميع المكونات المبنية في هذا الفصل في المجلد :مالحظة

مجلدات األخرى برامج اختبار وتحوي ال. يحوي أيضا حزمة برمجية لها نفس االسم لتنصيبها جميعا

.تستخدم هذه المكونات

Page 4: Delphi 7

الفصل السابع عشر ٧٨٦

للقراءة فقطProgressBarعنصر تحكم حالة بسيطة نسبيا لعنصر حتكم مرتبط ProgressBarتعترب النسخة املرتبطة ببيانات لعنصر التحكم

يف بضع ويشتق هذا املكون من النسخة غري املرتبطة ببيانات، ويض. ببيانات، ألنه للقراءة فقط :خصائص لغرض ربط البيانات الذي يغلفه

type TMdDbProgress = class(TProgressBar) private FDataLink: TFieldDataLink; function GetDataField: string; procedure SetDataField (Value: string); function GetDataSource: TDataSource; procedure SetDataSource (Value: TDataSource); function GetField: TField; protected // data link event handler procedure DataChange (Sender: TObject); // useless {procedure Notification (AComponent: TComponent; Operation: TOperation); override;} public constructor Create (AOwner: TComponent); override; destructor Destroy; override; property Field: TField read GetField; published property DataField: string read GetDataField write SetDataField; property DataSource: TDataSource read GetDataSource write

SetDataSource; end;

، كما هو حال مجيع املكونات DataField وDataSourceيتيح عنصر التحكم احلايل اخلاصيتني وجيب كتابة القليل من الشيفرة؛ وذلك لتصدير . املرتبطة ببيانات اليت تربط إىل حقل واحد

:ي، كما يلياخلصائص من غرض الربط الداخلfunction TMdDbProgress.GetDataField: string; begin Result := FDataLink.FieldName; end; procedure TMdDbProgress.SetDataField (Value: string); begin FDataLink.FieldName := Value; end; function TMdDbProgress.GetDataSource: TDataSource; begin Result := FDataLink.DataSource; end; procedure TMdDbProgress.SetDataSource (Value: TDataSource); begin

Page 5: Delphi 7

٧٨٧ إنشاء مكونات قواعد بيانات 17.

FDataLink.DataSource := Value; end; function TMdDbProgress.GetField: TField; begin Result := FDataLink.Field; end;

:أن تنشئ ودم ربط البيانات عند إنشاء أو دمي املكون نفسهوجلعل هذا املكون يعمل جيب constructor TMdDbProgress.Create (AOwner: TComponent); begin inherited Create (AOwner); FDataLink := TFieldDataLink.Create; FDataLink.Control := self; FDataLink.OnDataChange := DataChange; end; destructor TMdDbProgress.Destroy; begin FDataLink.Free; FDataLink := nil; inherited Destroy; end;

وهنا . الحظ يف البناء السابق أن املكون ينصب إحدى مناهجه كمعاجل حدث الرتباط البياناتللبيانات تعديل خرج شريط التقدم، وميكنك عند كل تغري . تتوضع الشيفرة األكثر أمهية للمكون

:لتعكس قيمة احلقل احلايلprocedure TMdDbProgress.DataChange (Sender: TObject); begin if FDataLink.Field is TNumericField then Position := FDataLink.Field.AsInteger else Position := Min; end;

املرتبطة ببيانات، فال يعرض املكون رسالة خطأ إذا كان منط VCL حتكم وباتباع العرف لعناصروبدل من ذلك رمبا يتوجب عليك التحقق من منط احلقل عند . احلقل غري متاح، بل يلغي اخلرج

. إىل عنصر التحكمSetDataFieldإسناد املنهج من عنصر تسمية وشريط تقدم ، الذي يستخدم كال DbProgr مثاال خلرج التطبيق 1-17يبني الشكل

وبذلك تستطيع أن ختطو عرب السجالت، واستطالع طلبيات لعناصر . لعرض معلومة كمية طلبيةوإحدى املكاسب الواضحة هلذا املكون هي أن التطبيق ال حيوي يف الغالب أية شيفرة، ألن . كثرية

.كون، اليت تعرف املMdProgrمجيع الشيفرة اهلامة موجودة يف الوحدة لكن يصبح استخدام هكذا . وهكذا فإن مكون مرتبط ببيانات للقراءة فقط ليس صعب البناء

. أكثر تعقيداDBCtrlGridمكون ضمن حاوية

Page 6: Delphi 7

الفصل السابع عشر ٧٨٨

.DbProgr المرتبط ببيانات أثناء العمل في المثال ProgressBarالمكون : 1-17شكل

في الفصل التاسع، فربما تعجب مما يحدث Notification إذا آنت تتذآر مناقشة المنهج :مالحظة

والخبر الجديد أن مصدر . إذا تم تهديم مصدر البيانات الذي يشير إليه عنصر التحكم المرتبط ببيانات

لذا ال تحتاج عناصر التحكم المرتبطة . البيانات يملك هداما، يحذف نفسه وارتباطات البيانات الخاصة به

الكثير من هذه VCL، رغم أنك سترى آتبا ومقاالت تقترحها، وتحوي Notificationببيانات إلى منهج

.الشيفرة غير المفيدة

عناصر تحكم مرتبطة ببيانات قابلة للنسخ

. معقد وغير موثق بشكل جيدDBCtrlGridإن توسيع عنصر تحكم مرتبط ببيانات لدعم استخدامه داخل مكون

؛ MdDataPack من الحزمة البرمجية MdRepPr لشريط التقدم في الوحدة ويمكنك إيجاد نسخة قابلة للنسخ

يملك المكون . يوصف عملية تطويرهHTML، مرفقا بملف RepProgrويوجد مثال عن استخدامه في المجلد

DBCtrlGridحيث تستطيع . فهو يعرض على الشاشة عدة نسخ لنفس عنصر التحكم الفيزيائي: سلوآا غريبا

، بدال من السجل الحالي، وإعادة توجيه عمليات )buffer(ربط عنصر التحكم إلى مخزن بيانات مؤقت الشبكة

.رسم عنصر التحكم إلى جزء آخر من الشاشة

لعنصر التحكم، حيث يدل هذا العالم على أن csReplicatable يجب ضبط النمط DBCtlGridإلظهار مكون في

أوال يجب أن يستجيب المكون لرسالة دلفي . في شبكة تحكمالمكون يدعم أن يكون مستضافا

cm_GeTDataLinkوأن يعيد مؤشرا إلى ارتباط البيانات، بحيث تستطيع شبكة التحكم استخدامه وتغييره ، .

المناسب، الذي يوفر في ) Canvas( مخصصا لرسم الخرج في غرض النسيج Paintثانيا يحتاج المكون منهج

.True مضبوطا على ControlState للخاصية csPaintCopy إذا آان العالم wm_Paintسالة وسيط للر

ليس صعب االستخدام، لذا قررنا عدم تقديم DBCtrlGridإن شيفرة المثال معقدة، وإن استخدام المكون

ا يلي خرج برنامج وفيم. آامل التفاصيل هنا؛ ويمكنك إيجاد آامل الشيفرة ومعلومات أآثر في شيفرة المصدر

:االختبار، الذي يستخدم هذا المكون

Page 7: Delphi 7

٧٨٩ إنشاء مكونات قواعد بيانات 17.

للقراءة والكتابةTrackBarمكون اخلطوة التالية لبناء مكون هي السماح ملستخدم بتعديل البيانات يف قاعدة بيانات، وليس فقط

نسخة السابقة، وال تكون البنية الكاملة هلذا النمط من املكونات خمتلفة كثريا عن ال. باستعراضهالكن توجد بعض العناصر اإلضافية، وخاصة عندما يبدأ املستخدم بالتفاعل مع املكون، فيجب أن

مث . تضع الشيفرة جمموعة البيانات يف منط حترير، مث إشعار جمموعة البيانات بأنه قد مت تغيري البيانات .ة املعدلة لطلب القيمFieldDataLinkستستخدم جمموعة البيانات معاجل حدث

، لتوضيح كيفية إنشاء مكون مرتبط ببيانات ميكنه تغيري TrackBarوقد وسعنا عنصر التحكم .وهذا املثال ليس األبسط، لكنه يوضح عدة تقنيات مهمة. البيانات

):MdDataPack للحزمة الربجمية MdTrackيوجد يف الوحدة (وفيما يلي تعريف صنف املكون type TMdDbTrack = class(TTrackBar) private FDataLink: TFieldDataLink; function GetDataField: string; procedure SetDataField (Value: string); function GetDataSource: TDataSource; procedure SetDataSource (Value: TDataSource); function GetField: TField; procedure CNHScroll(var Message: TWMHScroll); message CN_HSCROLL; procedure CNVScroll(var Message: TWMVScroll); message CN_VSCROLL; procedure CMExit(var Message: TCMExit); message CM_EXIT; protected // data link event handlers

Page 8: Delphi 7

الفصل السابع عشر ٧٩٠

procedure DataChange (Sender: TObject); procedure UpdateData (Sender: TObject); procedure ActiveChange (Sender: TObject); public constructor Create (AOwner: TComponent); override; destructor Destroy; override; property Field: TField read GetField; published property DataField: string read GetDataField write SetDataField; property DataSource: TDataSource read GetDataSource write

SetDataSource; end; إن هذا الصنف أكثر تعقيدا مقارنة مع عنصر التحكم املرتبط ببيانات الذي بنيناه سابقا، ألنه ميتلك ثالثة معاجلات رسائل، تتضمن معاجلات إشعارات املكون، ومعاجلي حدثني جديدين الرتباط

:عيل املكونوينصب هذا املكون هذه املعاجلات يف البناء، الذي يقوم أيضا بإلغاء تف. البياناتconstructor TMdDbTrack.Create (AOwner: TComponent); begin inherited Create (AOwner); FDataLink := TFieldDataLink.Create; FDataLink.Control := self; FDataLink.OnDataChange := DataChange; FDataLink.OnUpdateData := UpdateData; FDataLink.OnActiveChange := ActiveChange; Enabled := False; end;

، TMdDbProgress تلك املوجودة يف املكون DataChangeيشبه منهجا الضبط واإلعادة ومعاجل احلدث واالختالف الوحيد هو أنه عند تغري مصدر البيانات أو حقل البيانات، يتحقق املكون من احلالة

:ما إذا كان جيب أن يفعل نفسهاآلنية لرؤية فيprocedure TMdDbTrack.SetDataSource (Value: TDataSource); begin FDataLink.DataSource := Value; Enabled := FDataLink.Active and (FDataLink.Field <> nil) and not FDataLink.Field.ReadOnly; end;

وجيب أن يشري االرتباط . جيب أن يكون ربط البيانات فعاال: تفحص هذه الشيفرة ثالث شروط .إىل حقل فعلي، وجيب أال يكون احلقل للقراءة فقط

عندما يغري املستخدم احلقل، جيب أن يأخذ املكون يف االعتبار أن اسم احلقل قد ال يكون صحيحا؛ : لفحص هذا الشرطtry/Finallyويستخدم املكون كتلة

procedure TMdDbTrack.SetDataField (Value: string); begin try FDataLink.FieldName := Value; finally

Page 9: Delphi 7

٧٩١ إنشاء مكونات قواعد بيانات 17.

Enabled := FDataLink.Active and (FDataLink.Field <> nil) and not FDataLink.Field.ReadOnly; end; end;

:البيانات أو عند إلغاء تفعيلهاينفذ عنصر التحكم نفس االختبار عند تفعيل جمموعة procedure TMdDbTrack.ActiveChange (Sender: TObject); begin Enabled := FDataLink.Active and (FDataLink.Field <> nil) and not FDataLink.Field.ReadOnly; end;

أثناء حتريك املستخدم ملؤشر ف. يتعلق اجلزء األكثر أمهية من شيفرة هذا املكون بواجهة استخدامهالتمرير يضع املكون جمموعة البيانات يف منط حترير، ويسمح للصنف األساسي بتعديل موقع املؤشر،

:وفيما يلي الشيفرة لذلك. الذي غريته البيانات) وبذلك يتغري مصدر البيانات(ويغري ارتباط البيانات procedure TMdDbTrack.CNHScroll(var Message: TWMHScroll); begin // edit mode FDataLink.Edit; // update data inherited; // let the system know FDataLink.Modified; end; procedure TMdDbTrack.CNVScroll(var Message: TWMVScroll); begin // edit mode FDataLink.Edit; // update data inherited; // let the system know FDataLink.Modified; end;

فإا تطلبها من املكون ) مثالPostإلجناز عملية (وعندما حتتاج جمموعة البيانات إىل بيانات جديد :TFieldDataLink للصنف OnUpdateDataبواسطة احلدث

procedure TMdDbTrack.UpdateData (Sender: TObject); begin if FDataLink.Field is TNumericField then FDataLink.Field.AsInteger := Position; end;

وأخريا، . إذا حتققت الشروط املناسبة، يعدل املكون البيانات املوجودة يف احلقل املناسب يف اجلدول، حبيث أن أي )إذا تغريت البيانات(إذا فقد املكون تركيز اإلدخال، جيب أن جيرب تعديل البيانات

ذلك احلقل سيقوم بعرض القيمة الصحيحة حاملا ينتقل املستخدم مكون مرتبط ببيانات يظهر قيمة توجد شيفرة املنهج . وإذا مل تتغري البيانات فلن يقلق املكون على تعديل البيانات. إىل حقل خمتلف

Page 10: Delphi 7

الفصل السابع عشر ٧٩٢

:، ويستعار من أجل هذا املكونVCL للمكونات املستخدمة من قبل CMExitالقياسي procedure TMdDbTrack.CMExit(var Message: TCMExit); begin try FDataLink.UpdateRecord; except SetFocus; raise; end; inherited; end;

وحيوي هذا الربنامج . خرجه2-17ويبني الشكل . ، الختبار هذا املكونDBTrackقمنا ببناء الربنامج واملكونات املرئية، وزرين ميكن استخدامهما لفصل مربع اختيار لتفعيل وإلغاء تفعيل اجلدول،

وقد وضعنا هذه العناصر على النموذج الختبار تفعيل . عن احلقل املرتبط بهTrackBarاملكون .TrackBarوإلغاء تفعيل شريط التعقب

قاعدة بإدخال بيانات في جدول DbTrackفي المثال ) TrackBar(تسمح أشرطة التعقب : 2-17شكل

.ويختبر مربع االختيار واألزرار الحالة المفعلة للمكون. بيانات

إنشاء ارتباطات بيانات مخصصة إىل حقول معينة موعة البيانات، -اليت بنيناها حىت اآلن-تشري مجيع عناصر التحكم املرتبطة ببيانات

ودعنا اآلن نبين . البيانات لتثبيت وصلة مع مصدرTFieldDataLinkلذا استخدمنا غرضا من الصنف .record viewer" عارض سجل: "مكونا مرتبطا ببيانات، يتعامل مع جمموعة البيانات ككل

تظهر شبكة قاعدة بيانات يف دلفي قيمة عدة حقول وعدة سجالت بشكل متواقت يقوم املكون

Page 11: Delphi 7

٧٩٣ إنشاء مكونات قواعد بيانات 17.

وسيبني هذا . خمصصةعارض السجل الذي نبنيه بعرض مجيع حقول السجل احلايل باستخدام شبكة .املثال كيفية بناء عنصر حتكم شبكة خمصصة، وارتباط بيانات خمصص للعمل معها

مكون عارض سجلال توجد يف دلفي أية مكونات مرتبطة ببيانات تعاجل حقوال متعددة لسجل واحد، دون عرض

ن اللذان يعرضان حقوال مها املكونان الوحيداDBCtrlGrid وDBGridواملكونان . سجالت أخرى .متعددة من نفس اجلدول، ومها يعرضان عادة حقوال متعددة وسجالت متعددة

يعرض العمود : يستخدم املكون عارض السجل الذي نوصفه يف هذا املقطع شبكة من عمودينويرتبط عدد أسطر . األول أمساء حقول اجلدول، ويعرض العمود الثاين القيم املتعلقة باحلقول

.الشبكة بعدد احلقول، مع شريط مترير شاقويل إذا كانت املساحة املرئية غري كافيةيكون ارتباط البيانات الذي حتتاج إليه لبناء هذا املكون صنفا مربوطا إىل املكون عارض السجل

وهذا هو نفس النهج املستخدم يف. لوحدته الربجميةimplementationفقط، ويصرح عنه يف اجلزء VCLوفيما يلي تعريف الصنف اجلديد. من أجل بعض ارتباطات البيانات اخلاصة:

type TMdRecordLink = class (TDataLink) private RView: TMdRecordView; public constructor Create (View: TMdRecordView); procedure ActiveChanged; override; procedure RecordChanged(Field: TField); override; end;

يف هذه احلالة التفعيل (كما تالحظ، يعيد الصنف صياغة املناهج املرتبطة باألحداث املبدئية activation ن ). أو السجل(، وتغيري البياناتوميكنك بدال من ذلك تصدير أحداث مث ترك املكو

.TFieldDataLinkيعاجلها، كما يفعل الصنف :يتطلب بناء الصنف املكون املرفق كوسيط وحيد

constructor TMdRecordLink.Create (View: TMdRecordView); begin inherited Create; RView := View; end;

:بعد ختزين مرجع إىل املكون املرفق، تستطيع املناهج األخرى التأثري عليه مباشرةprocedure TMdRecordLink.ActiveChanged; var I: Integer; begin // set number of rows

Page 12: Delphi 7

الفصل السابع عشر ٧٩٤

if Assigned (DataSet) then begin RView.RowCount := DataSet.FieldCount; // double the height of memo and graphics for I := 0 to DataSet.FieldCount - 1 do if DataSet.Fields [I] is TBlobField then RView.RowHeights [I] := RView.DefaultRowHeight * 2; // repaint all... RView.Invalidate; end; end; procedure TMdRecordLink.RecordChanged; begin inherited; // repaint all... RView.Invalidate; end;

. وتنتج أغلب الصعوبات يف بناء نتيجة هذا املثال من استخدام شبكة. إن شيفرة ارتباط السجل بسيطة، لتجنب التعامل مع خصائص غري TCustomGridوقد قمنا باشتقاق شبكة عارض السجل من الصنف

ويتضمن هذا الصنف الكثري من شيفرة الشبكات، لكن تكون أغلب خصائصه ومناهجه . مفيدة؛ لذا يكون التصريح عن هذا الصنف طويال جدا، ألنه حيتاج إىل نشر الكثري )protected(ثه حممية وأحدا

):باستبعاد خصائص الصنف األساسي(وفيما يلي جزء من هذا التصريح . من اخلصائص املوجودةtype TMdRecordView = class(TCustomGrid) private // data-aware support FDataLink: TDataLink; function GetDataSource: TDataSource; procedure SetDataSource (Value: TDataSource); protected // redefined TCustomGrid methods procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); override; procedure ColWidthsChanged; override; procedure RowHeightsChanged; override; public constructor Create (AOwner: TComponent); override; destructor Destroy; override; procedure SetBounds (ALeft, ATop, AWidth, AHeight: Integer);

override; // public parent properties (omitted...) published // data-aware properties property DataSource: TDataSource read GetDataSource write

SetDataSource; // published parent properties (omitted...) end;

، باإلضافة إىل إعادة التصريح عن اخلصائص DataSourceصية يعرف الصنف غرض ربط بيانات واخلا

Page 13: Delphi 7

٧٩٥ إنشاء مكونات قواعد بيانات 17.

إن بناء املكون هام . ، ألنه يشري إىل سجل كاملDataFieldال توجد للمكون أية خاصية . اليت ينشرها :جدا، فهو يضبط قيم الكثري من اخلصائص غري املنشورة، ومن ضمنها خيارات الشبكة

constructor TMdRecordView.Create (AOwner: TComponent); begin FDataLink := TMdRecordLink.Create (self); inherited Create (AOwner); // set numbers of cells and fixed cells RowCount := 2; // default ColCount := 2; FixedCols := 1; FixedRows := 0; {grid options -- choose among: goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goDrawFocusSelected, goRowSizing, goColSizing, goRowMoving, goColMoving, goEditing, goTabs, goRowSelect, goAlwaysShowEditor, goThumbTracking} Options := [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRowSizing, goColSizing]; DefaultDrawing := False; ScrollBars := ssVertical; FSaveCellExtents := False; end;

د املثبت ، وال حيوي أسطرا مثبتة، حيث يستخدم العمو)يكون أحدمها مثبتا(حتوي الشبكة عمودين ولسوء احلظ ال يستطيع املستخدم سحب السطر املثبت لتغيري . لتغيري قياس كل سطر يف الشبكة

.قياس األعمدة، ألنه ال ميكن تغيري قياس عناصر مثبتة، والشبكة حتوي عمودا مثبتا

حيث . DBGrid يمكن استخدام نهج بديل، وذلك بوجود عمود إضافي فارغ، آما في عنصر التحكم :مالحظة

.ومع ذلك فإننا نفضل التحقيق الذي قمنا به. يمكن تغيير قياس العمودين اآلخرين بعد إضافة سطر مثبت

الذي حيمل أمساء (حيث ميكن تغيري قياس العمود األول . استخدمنا جا بديال لتغيري قياس األعمدة

ويتم تغيري قياس العمود الثاين . إما باستخدام شيفرة برجمية، أو بشكل مرئي أثناء التصميم) احلقول :ليستخدم املساحة املتبقية من املكون) الذي حيمل قيم احلقول(

procedure TMdRecordView.SetBounds (ALeft, ATop, AWidth, AHeight: Integer); begin inherited; ColWidths [1] := ClientWidth - ColWidths[0]; end;

وتصبح اخلاصية . ياس هذا عند تغري قياس املكون، وعند تغري أحد العمودينجيري تغيري القDefaultColWidth -العرض املثبت للعمود األول-مع هذه الشيفرة .

وحيوي السرد . املعاد صياغته املنهج األساسي للمكونDrawCellبعد إعداد كل شيء يكون املنهج

Page 14: Delphi 7

الفصل السابع عشر ٧٩٦

. التحكم يف هذا املنهج معلومات احلقول وقيمهاحيث يعرض عنصر. تفاصيل هذا املنهج17-1إذا مل يكن ارتباط ) [](حيث تعرض الشبكة إشارة عنصر فارغ . وحتتاج إىل رسم ثالثة أشياء

وعند رسم العمود األول يظهر عارض السجل اخلاصية . البيانات مربوطا إىل مصدر بياناتDisplayNameمها الـ للحقل، اليت هي نفس القيمة اليت تستخدDBGridوعند . من أجل الترويسة

DisplayTextرسم العمود الثاين يصل املكون إىل التمثيل النصي لقيمة احلقل، املستخرج باخلاصية ).memo من أجل حقول AsStringأو باخلاصية (

RECORDVIEW للمكون المخصص DRAWCELLالمنهج : 1-17سرد

procedure TMdRecordView.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); var Text: string; CurrField: TField; Bmp: TBitmap; begin CurrField := nil; Text := '[]'; // default // paint background if (ACol = 0) then Canvas.Brush.Color := FixedColor else Canvas.Brush.Color := Color; Canvas.FillRect (ARect); // leave small border InflateRect (ARect, -2, -2); if (FDataLink.DataSource <> nil) and FDataLink.Active then begin CurrField := FDataLink.DataSet.Fields[ARow]; if ACol = 0 then Text := CurrField.DisplayName else if CurrField is TMemoField then Text := TMemoField (CurrField).AsString else Text := CurrField.DisplayText; end; if (ACol = 1) and (CurrField is TGraphicField) then begin Bmp := TBitmap.Create; try Bmp.Assign (CurrField); Canvas.StretchDraw (ARect, Bmp); finally Bmp.Free; end; end else if (ACol = 1) and (CurrField is TMemoField) then

Page 15: Delphi 7

٧٩٧ إنشاء مكونات قواعد بيانات 17.

begin

DrawText (Canvas.Handle, PChar (Text), Length (Text), ARect, dt_WordBreak or dt_NoPrefix) end else // draw single line vertically centered DrawText (Canvas.Handle, PChar (Text), Length (Text), ARect, dt_vcenter or dt_SingleLine or dt_NoPrefix); if gdFocused in AState then Canvas.DrawFocusRect (ARect); end;

فإذا كان احلقل من . وحقول صورmemoيف اجلزء األخري للمنهج يأخذ املكون يف االعتبار حقول

، لكنه يستخدم dt_SingleLine ال خيصص العالم DrawText، فإن استدعاء التابع TMemoFieldالنمط املكون ويستخدم . من أجل التفاف الكلمات عند عدم وجود مكان إضايفdt_WorldBreakالعالم

جا خمتلفا متاما من أجل حقل صورة، وذلك بإسناد صورة احلقل إىل صورة نقطية مؤقتة، مث .ميددها مللء سطح اخللية

، ويكون مسؤوال أيضا عن False على القيمة DefaultDrawingالحظ أن املكون يضبط اخلاصية InflatRectويستدعي املكون التابع . DrawCellرسم اخللفية ومستطيل التركيز، كما يفعل يف املنهج

APIوينتج اخلرج باستدعاء تابع . لترك مساحة صغرية بني إطار اخللية ونص اخلرج) APIتابع (Windows آخر هو DrawTextالذي ميركز النص عموديا يف خليته ،.

ورمبا . من التصميموز) 3-17كما تالحظ يف الشكل (تعمل شيفرة الرسم هذه يف كل من زمن التنفيذ ال يكون اخلرج مثاليا، لكن ميكن أن يكون هذا املكون مفيدا يف حاالت كثرية، حيث ميكنك استخدام هذه الشبكة لعارض السجل إلظهار بيانات سجل واحد؛ بدال من بناء منوذج خمصص

ل هو للقراءة ومن املهم تذكر أن عارض السج. حيوي عناصر تسمية وعناصر حتكم مرتبطة ببيانات؛ ومع )TCustomGridوهي جزء من الصنف (ومن املمكن توسيعه إلضافة إمكانيات التحرير . فقط

.BLOBذلك فبدال من إضافة هذا الدعم، قررنا جعل املكون أكثر اكتماال، بإضافة دعم لعرض حقول . قول نصية صرفة ضعفي ارتفاع حBLOBلتحسني اخلرج البياين جيعل عنصر التحكم ارتفاع سطور

. وتنجز هذه العملية عند ربط جمموعة البيانات إىل عنصر التحكم املرتبط ببيانات الذي يتم تفعيله املرتبطة RowHeightsChanged الرتباط البيانات من قبل املناهج ActiveChangedويتم قدح املنهج

: للصنف األساسDefaultRowHeightباخلاصية procedure TMdRecordLink.ActiveChanged; var I: Integer; begin // set number of rows

Page 16: Delphi 7

الفصل السابع عشر ٧٩٨

if Assigned (DataSet) then begin RView.RowCount := DataSet.FieldCount; // double the height of memo and graphics for I := 0 to DataSet.FieldCount - 1 do if DataSet.Fields [I] is TBlobField then RView.RowHeights [I] := RView.DefaultRowHeight * 2; // repaint all... RView.Invalidate; end; end;

من قواعد BioLife، باستخدام الجدول RecordView خرج المكون ViewGridيوضح المثال : 3-17شكل

.Borlandبيانات أمثلة

يف املنهج TCustomGridويقوم الصنف . حىت هذه اللحظة ميكن أن تقع يف عدد ضئيل من املشاكلDefineProperties حبفظ قيم اخلاصيتني RowHeightsو ColHeights . وكان بإمكانك إلغاء تفعيل هذا

، لكن ميكنك أيضا )اليت هي عادة تقنية سيئة (inheritedارى بإعادة صياغة املنهج وعدم استدعاء ).كما فعلنا يف شيفرة املكون( إللغاء تفعيل هذه امليزة FSaveCellExtentsعكس حالة احلقل احملمي

DBGridتخصيص المكون ؛ إضافة إىل بناء مكونات مرتبطة DBGridي بشكل عام بتخصيص عنصر التحكم يقوم مربجمو دلف

بنفس نوع اخلرج املخصص DBGridواهلدف من املكون التايل هو تعزيز . ببيانات خمصصة جديدة. وحقول صور بشكل مباشرmemo، وذلك بعرض حقول RecordViewالذي استخدمناه للمكون إىل جعل ارتفاع السطر قابال للتغيري، للسماح مبساحة للصور، وكمية وإلجناز ذلك حتتاج الشبكة

. مثاال هلذه الشبكة أثناء التصميم4-17ويبني الشكل . معقولة من النصوص

Page 17: Delphi 7

٧٩٩ إنشاء مكونات قواعد بيانات 17.

.memo أثناء التصميم، الحظ خرج حقول الصور وحقول MDbGridمثال للمكون : 4-17شكل

ئمة الشيفرة املستخدمة يف املكون عارض السجل، إال رغم أن إنشاء اخلرج كان مسألة بسيطة ملالفرمبا تكون أسطر شيفرة هذه . أن ضبط ارتفاع خاليا الشبكة األخرية يكون مشكلة صعبة احلل

.العملية قليلة، لكنها احتاجت إىل ساعات من العمل

ي هو عارض افتراضDBGrid على عكس الشبكة العامة المستخدمة سابقا، فإن :مالحظة

لمجموعة البيانات، حيث ال توجد أية عالقة بين عدد األسطر المعروضة على الشاشة وعدد أسطر

فعندما تتحرك نحو األعلى ونحو األسفل عبر سجالت البيانات في . البيانات في مجموعة البيانات

ة، وتنتقل ، حيث تكون األسطر ثابتDBGridمجموعة البيانات، فإنك ال تتحرك عبر أسطر عنصر التحكم

لهذا السبب ال يحاول البرنامج ضبط . البيانات من سطر إلى السطر التالي، لتعطي مظهر التحرك

.ارتفاع سطر خاص ليالئم بياناته، بل يضبط ارتفاع جميع أسطر البيانات على قيمة مضاعفة لالرتفاع

من مكون ميلك ال يتوجب على عنصر التحكم هذه املرة إنشاء ربط بيانات خمصص، ألنه مشتق

وميتلك الصنف اجلديد خاصية جديدة، لتعيني عدد أسطر النص لكل . وصلة معقدة مع البيانات :سطر بيانات، ويعيد صياغة بضع مناهج افتراضية

type TMdDbGrid = class(TDbGrid) private FLinesPerRow: Integer; procedure SetLinesPerRow (Value: Integer); protected procedure DrawColumnCell(const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); override; procedure LayoutChanged; override; public

Page 18: Delphi 7

الفصل السابع عشر ٨٠٠

constructor Create (AOwner: TComponent); override; published property LinesPerRow: Integer read FLinesPerRow write SetLinesPerRow default 1; end;

:وفيما يلي منهج ضبط اخلاصية. FLinesPerRowيعني بناء الصنف القيمة االفتراضية للحقل

procedure TMdDbGrid.SetLinesPerRow(Value: Integer); begin if (Value <> FLinesPerRow) and (Value > 0) then begin FLinesPerRow := Value; LayoutChanged; end; end;

، حيث LayoutChangedالتأثري اجلانيب لتغيري عدد أسطر النص هو استدعاء املنهج االفتراضي ويف شيفرة املنهج . يستدعي النظام هذا املنهج بشكل متكرر عند تغري أحد وسائط اخلرج الكثرية

يغة ويستخدم املكون نفس ص. يستدعي املكون أوال النسخة املوروثة، مث يضبط ارتفاع كل سطرفيتم حساب ارتفاع النص باستخدام الكلمة العينة . كأساس هلذا احلسابTCustomDBGridالصنف

)Wg ( يف اخلط احلايل) ،ا تتضمن كال من حمرف كبري ذو ارتفاع كاملتستخدم هذه الكلمة أل :وفيما يلي الشيفرة). وحمرف صغري مع قسم نازل

procedure TMdDbGrid.LayOutChanged; var PixelsPerRow, PixelsTitle, I: Integer; begin inherited LayOutChanged; Canvas.Font := Font; PixelsPerRow := Canvas.TextHeight('Wg') + 3; if dgRowLines in Options then Inc (PixelsPerRow, GridLineWidth); Canvas.Font := TitleFont; PixelsTitle := Canvas.TextHeight('Wg') + 4; if dgRowLines in Options then Inc (PixelsTitle, GridLineWidth); // set number of rows RowCount := 1 + (Height - PixelsTitle) div (PixelsPerRow * FLinesPerRow); // set the height of each row DefaultRowHeight := PixelsPerRow * FLinesPerRow; RowHeights [0] := PixelsTitle; for I := 1 to RowCount - 1 do RowHeights [I] := PixelsPerRow * FLinesPerRow;

Page 19: Delphi 7

٨٠١ إنشاء مكونات قواعد بيانات 17.

// send a WM_SIZE message to let the base component recompute // the visible rows in the private UpdateRowCount method PostMessage (Handle, WM_SIZE, 0, MakeLong(Width, Height)); end;

هي القيم االفتراضية للشبكة، التي يمكن أن تعاد صياغتها من قبل TitleFont و Font القيم :تنبيه

.لكن حاليا يتجاهل هذا المكون هذه اإلعدادات. خاصةDBGridخصائص ألغراض أعمدة

كان احلصول على اجلزء األمين للتعليمات األخرية هو اجلزء الصعب من هذه الطريقة، حيث ميكن

. ، لكن حيتمل أن يكون سطر العنوان يف تلك احلالة طويال جداDefaultRowHeightضبط اخلاصية ، مث ارتفاع السطر األول، لكن هذا النهج يعقد DefaultRowHeightوقد حاولنا أوال ضبط اخلاصية

لقراءة لVisibleRowCountاخلاصية (الشيفرة املستخدمة حلساب عدد األسطر املرئية يف الشبكة ) لتجنب احلصول على أسطر خمفية حتت احلافة السفلية للشبكة(وإذا عينت عدد األسطر ). فقط

وفيما يلي الشيفرة املستخدمة لرسم البيانات، منقولة من . يستمر الصنف األساسي يف إعادة حساا :، ومتت مالءمتها قليال من أجل الشبكةRecordViewاملكون

procedure TMdDbGrid.DrawColumnCell(const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); var Bmp: TBitmap; OutRect: TRect; begin if FLinesPerRow = 1 then inherited DrawColumnCell(Rect, DataCol, Column, State) else begin // clear area Canvas.FillRect (Rect); // copy the rectangle OutRect := Rect; // restrict output InflateRect (OutRect, -2, -2); // output field data if Column.Field is TGraphicField then begin Bmp := TBitmap.Create; try Bmp.Assign (Column.Field); Canvas.StretchDraw (OutRect, Bmp); finally Bmp.Free; end; end else if Column.Field is TMemoField then begin DrawText (Canvas.Handle, PChar (Column.Field.AsString),

Page 20: Delphi 7

الفصل السابع عشر ٨٠٢

Length (Column.Field.AsString), OutRect, dt_WordBreak or dt_NoPrefix) end else // draw single line vertically centered DrawText (Canvas.Handle, PChar (Column.Field.DisplayText), Length (Column.Field.DisplayText), OutRect, dt_vcenter or dt_SingleLine or dt_NoPrefix); end; end; الحظ يف هذه الشيفرة أنه إذا عرض املستخدم سطرا واحدا، تستخدم الشبكة التقنية القياسية للرسم،

.وعندما تزيد عدد األسطر سترى الحقا خرجا أفضل. وحقول صورmemoدون أي خرج حلقول حيوي هذا الربنامج زرين، ميكنك استخدامهما . GridDemoه الشيفرة نفذ الربنامج لرؤية عمل هذ

ويشكل هذا اختبارا هاما، ألن . لزيادة أو إنقاص ارتفاع أسطر الشبكة؛ وزرين إضافيني لتغيري اخلط .ارتفاع كل خلية مقدرا بالبكسل هو ناتج ضرب ارتفاع اخلط يف عدد األسطر

خصصةبناء مجموعة بيانات مأشرنا يف الفصل الثالث عشر إىل إمكانية بناء صنف جمموعة بيانات خمصصة، وذلك عند مناقشة

وقد حان الوقت . ، والعائالت البديلة ملكونات جمموعات البيانات املتاحة يف دلفيTDataSetالصنف لن حتتاج إىل تتعلق أسباب كتابة جمموعة بيانات خمصصة حبقيقة أنك . لالطالع على مثال عن ذلك

نشر حمرك جمموعة بيانات، لكن ستبقى قادرا على االستفادة من بنية قواعد بيانات دلفي بشكل .كامل، ويتضمن ذلك أشياء مثل حقول قواعد بيانات دائمة، وعناصر حتكم مرتبطة ببيانات

لذا يعترب من . اتإن بناء جمموعة بيانات خمصصة من املهام األكثر تعقيدا بالنسبة ملطوري املكونإىل درجة أن عمليات كتابة شيفرة ذات مستوى منخفض، (النطاقات األكثر تقدما يف هذا الكتاب

أي توثيق رمسي حول Borlandوعالوة على ذلك، مل تصدر ). اليت تتضمن أطنانا من املؤشراتجتاوز ما تبقى وإذا كنت مستجدا يف جتربتك مع دلفي، ميكنك . كتابة جمموعات بيانات خمصصة

.من هذا الفصل، والعودة إليه الحقا منهجا يف 23كان ) (abstarct( صنفا جمردا، يصرح عن عدة مناهج جمردة TDataSetيعترب الصنف

اليت مازال يتوجب (وقد مت استبدال أغلبها مبناهج افتراضية فارغة ). ، أما اآلن فيوجد القليل منها5دلفي . صياغة مجيع تلك املناهجTDataSetوجيب أن يعيد كل صنف فرعي من ). عليك إعادة صياغتها

وخاصة التخزين املؤقت للسجل (TDataSetوجيب أن نناقش القليل من العناصر التقنية للصنف record buffering(حيث حيافظ الصنف على قائمة . ، قبل مناقشة تطوير جمموعة بيانات خمصصة

ختزن هذه املخازن البيانات، لكنها ختزن أيضا . م سجالت خمتلفةختزن قي) buffers(خمازن مؤقتة وال متلك هذه املخازن بنية . معلومات أكثر موعة البيانات، كي تستخدمها عند إدارة السجالت

Page 21: Delphi 7

٨٠٣ إنشاء مكونات قواعد بيانات 17.

، ومتلؤها، )buffers(مسبقة التعريف، وجيب أن حتجر كل جمموعة بيانات خمصصة املخازن املؤقتة جمموعة البيانات املخصصة من خمازن السجالت إىل احلقول املختلفة وجيب أن تنسخ. ودمها

أي أن جمموعة البيانات املخصصة مسؤولة بشكل أساسي عن معاجلة . موعة البيانات، وبالعكس .هذه املخازن املؤقتة

ط باإلضافة إىل إدارة املخازن املؤقتة، فإن املكون مسؤول عن التنقل بني السجالت، وإدارة نقا ليس TDataSetإن الصنف . العالم، وتعريف بنية جمموعة البيانات، وإنشاء احلقول املناسبة للبيانات

وحلسن احلظ تتبع أغلب الشيفرة بنية قياسية، . أكثر من إطار عمل، جيب أن متأله بالشيفرة املناسبةة، ستكون قادرا على وحاملا تلتقط األفكار األساسي. TDataSet املشتقة من VCLتستخدمها أصناف

.بناء جمموعات بيانات خمصصة متعددة، مستعريا الكثري من الشيفرةلتبسيط هذا النمط من إعادة االستخدام، قمنا بتجميع املميزات املشتركة، اليت تتطلبها أية جمموعة

تحقيق ومع ذلك لن نناقش الصنف األساس أوال، وال. TMdCustomDataSetبيانات خمصصة يف الصنف وبدال من ذلك سنفصل الشيفرة اليت تتطلبها جمموعة بيانات، . اخلاص الحقا، ألنه سيكون صعب الفهم

.وذلك بتقدمي األصناف العامة واخلاصة يف نفس الوقت بشكل متوافق مع التدفق املنطقي

تعريف األصنافجمموعة : هذا املقطعتكون نقطة االنطالق كالعادة بالتصريح عن الصنفني، الذين ناقشنامها يف

التصريح 2-17حيوي السرد . البيانات املخصصة اليت بنيناها، ومكون خاص خيزن بيانات جمرى ملف يتضمن الصنفان حقوال حممية ) Virtual methods(فإضافة إىل املناهج االفتراضية . عن هذين الصنفني

)Protected Fields ( ،م إلدارة املخازن املؤقتةستخدويالحقان املوضع احلايل وعدد السجالت، تبنية تستخدم لتخزين : يف البدايةrecordوستالحظ أيضا تصريح . ويعاجلان ميزات أخرى كثرية

وتضع جمموعة البيانات هذه . (buffers)بيانات إضافية لكل سجل بيانات، توضع يف خمزن مؤقت :املعلومات يف كل خمزن سجل يلي البيانات

.TMdDataSetStream وTMdCustomDataSetالتصريح عن الصنفين :2-17سرد

// in unit MdDsCustom type EMdDataSetError = class (Exception); TMdRecInfo = record Bookmark: Longint; BookmarkFlag: TBookmarkFlag; end; PMdRecInfo = ^TMdRecInfo; TMdCustomDataSet = class(TDataSet)

Page 22: Delphi 7

الفصل السابع عشر ٨٠٤

protected // status FIsTableOpen: Boolean; // record data FRecordSize, // the size of the actual data FRecordBufferSize, // data + housekeeping (TRecInfo) FCurrentRecord, // current record (0 to FRecordCount - 1) BofCrack, // before the first record (crack) EofCrack: Integer; // after the last record (crack) // create, close, and so on procedure InternalOpen; override; procedure InternalClose; override; function IsCursorOpen: Boolean; override; // custom functions function InternalRecordCount: Integer; virtual; abstract; procedure InternalPreOpen; virtual; procedure InternalAfterOpen; virtual; procedure InternalLoadCurrentRecord(Buffer: PChar);

virtual; abstract; // memory management function AllocRecordBuffer: PChar; override; procedure InternalInitRecord(Buffer: PChar); override; procedure FreeRecordBuffer(var Buffer: PChar); override; function GetRecordSize: Word; override; // movement and optional navigation (used by grids) function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override; procedure InternalFirst; override; procedure InternalLast; override; function GetRecNo: Longint; override; function GetRecordCount: Longint; override; procedure SetRecNo(Value: Integer); override; // bookmarks procedure InternalGotoBookmark(Bookmark: Pointer); override; procedure InternalSetToRecord(Buffer: PChar); override; procedure SetBookmarkData(Buffer: PChar; Data: Pointer);

override; procedure GetBookmarkData(Buffer: PChar; Data: Pointer);

override; procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);

override; function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;

override; // editing (dummy vesions) procedure InternalDelete; override; procedure InternalAddRecord(Buffer: Pointer; Append: Boolean);

override;

Page 23: Delphi 7

٨٠٥ إنشاء مكونات قواعد بيانات 17.

procedure InternalPost; override; procedure InternalInsert; override; // other procedure InternalHandleException; override; published // redeclared data set properties property Active; property BeforeOpen; property AfterOpen; property BeforeClose; property AfterClose; property BeforeInsert; property AfterInsert; property BeforeEdit; property AfterEdit; property BeforePost; property AfterPost; property BeforeCancel; property AfterCancel; property BeforeDelete; property AfterDelete; property BeforeScroll; property AfterScroll; property OnCalcFields; property OnDeleteError; property OnEditError; property OnFilterRecord; property OnNewRecord; property OnPostError; end; // in unit MdDsStream type TMdDataFileHeader = record VersionNumber: Integer; RecordSize: Integer; RecordCount: Integer; end; TMdDataSetStream = class(TMdCustomDataSet) private procedure SetTableName(const Value: string); protected FDataFileHeader: TMdDataFileHeader; FDataFileHeaderSize, // file header size FRecordCount: Integer; // current number of records FStream: TStream; // the physical table FTableName: string; // table path and file name FFieldOffset: TList; // field offsets in the buffer Protected // open and close procedure InternalPreOpen; override; procedure InternalAfterOpen; override;

Page 24: Delphi 7

الفصل السابع عشر ٨٠٦

procedure InternalClose; override; procedure InternalInitFieldDefs; override; // edit support procedure InternalAddRecord(Buffer: Pointer; Append: Boolean);

override; procedure InternalPost; override; // fields procedure SetFieldData(Field: TField; Buffer: Pointer);

override; // custom dataset virutal methods function InternalRecordCount: Integer; override; procedure InternalLoadCurrentRecord(Buffer: PChar); override; public procedure CreateTable; function GetFieldData(Field: TField; Buffer: Pointer): Boolean;

override; published property TableName: string read FTableName write SetTableName; end;

أشرنا إىل كل ) كما تالحظ باالطالع على ملفات شيفرة املصدر(عندما قسمنا املناهج إىل مقاطع

وسترى هذه األرقام ضمن تعليق يوصف املنهج، لذا ستعرف مباشرة يف أي . مقطع بعدد روماين .مقطع أنت عند استعراض الشيفرة

تعيين قيم ابتدائية وفتح وإغالق: Iالمقطع ليت سنتفحصها أوال مسؤولة عن تعيني قيم ابتدائية موعة البيانات، وفتح وإغالق تكون املناهج ا

فإضافة إىل تعيني القيم االبتدائية للبيانات الداخلية . جمرى امللف، املستخدم لتخزين البياناتلصنف للمكون، تكون هذه املناهج مسؤولة عن تعيني القيم االبتدائية وربط األغراض املناسبة من ا

TFieldsن جمموعة البياناتوكل ما جيب أن تقوم به جلعل هذا يعمل هو إسناد قيمة . إىل مكو بتعريفات حقول جمموعة بياناتك، مث استدعاء بعض املناهج القياسية FieldsDefابتدائية للخاصية

:InternalOpenوفيما يلي املنهج العام . TFieldلتوليد وربط أغراض من الصنف procedure TMdCustomDataSet.InternalOpen; begin InternalPreOpen; // custom method for subclasses // initialize the field definitions // (another virtual abstract method of TDataSet) InternalInitFieldDefs; // if there are no persistent field objects, // create the fields dynamically if DefaultFields then CreateFields; // connect the TField objects with the actual fields

Page 25: Delphi 7

٨٠٧ إنشاء مكونات قواعد بيانات 17.

BindFields (True); InternalAfterOpen; // custom method for subclasses // sets cracks and record position and size BofCrack := -1; EofCrack := InternalRecordCount; FCurrentRecord := BofCrack; FRecordBufferSize := FRecordSize + sizeof (TMdRecInfo); BookmarkSize := sizeOf (Integer); // everything OK: table is now open FIsTableOpen := True; end;

للصنف األساس BookmarkSizeاملنهج يضبط أغلب احلقول احمللية للصنف، واحلقل ستالحظ أن TDataSet . ونستدعي ضمن هذا املنهج املنهجني املخصصنيInternalPreOpenو InternalAfterOpen ،

InternalPreOpenحيث يستخدم املنهج األول . اللذين قدمنامها يف هيكلية جمموعة بياناتنا املخصصة عمليات تطلب عند البداية املبكرة، مثل التحقق من إمكانية فتح جمموعة البيانات، وقراءة من أجل

وتتحقق الشيفرة من انسجام رقم النسخة الداخلي مع القيمة . معلومات الترويسة من امللفوتستطيع إيقاف عملية الفتح بإطالق . احملفوظة عند إنشاء اجلدول ألول مرة، كما سترى الحقا

.اء يف هذا املنهجاستثن :وفيما يلي شيفرة هذين املنهجني يف جمموعة البيانات املشتقة املبنية على جمرى

const HeaderVersion = 10; // I: open the table/file procedure TMdDataSetStream.InternalPreOpen; begin // the size of the header FDataFileHeaderSize := sizeOf (TMdDataFileHeader); // check if the file exists if not FileExists (FTableName) then raise EMdDataSetError.Create ('Open: Table file not found'); // create a stream for the file FStream := TFileStream.Create (FTableName, fmOpenReadWrite); // initialize local data (loading the header) FStream.ReadBuffer (FDataFileHeader, FDataFileHeaderSize); if FDataFileHeader.VersionNumber <> HeaderVersion then raise EMdDataSetError.Create ('Illegal File Version'); // let's read this, double check later FRecordCount := FDataFileHeader.RecordCount; end; procedure TMdDataSetStream.InternalAfterOpen; begin

Page 26: Delphi 7

الفصل السابع عشر ٨٠٨

// check the record size if FDataFileHeader.RecordSize <> FRecordSize then raise EMdDataSetError.Create ('File record size mismatch'); // check the number of records against the file size if (FDataFileHeaderSize + FRecordCount * FRecordSize) <>

FStream.Size then raise EMdDataSetError.Create ('InternalOpen: Invalid Record Size'); end;

من أجل عمليات تطلب بعد ضبط تعريفات احلقول، InternalAfterOpenيستخدم املنهج الثاين ويتبع بشيفرة تقارن قياس السجل الذي يقرأ من امللف مع القيمة احملتسبة يف املنهج

InternalInitFieldDefs . وتتحقق الشيفرة أيضا من توافق عدد السجالت املقروءة من الترويسة معورمبا تريد تغيري . وميكن أن يفشل هذا االختبار إذا مل يتم إغالق امللف بشكل صحيح. لفقياس امل

.الشيفرة، لتدع جمموعة البيانات حتدث قياس السجل يف الترويسة، الذي حيدد InternalInitFieldDefs مسؤوال بشكل خاص عن استدعاء InternalOpenيعترب املنهج

وقد قررنا من أجل هذا املثال أن تستند ). تصميم أو زمن التنفيذيف زمن ال(تعريفات احلقول ويتضمن كل مقطع اسم ). ، يزود مقطعا لكل حقلINIملف (تعريفات احلقول على ملف خارجي

امللف 3-17ويبني السرد . Stringاحلقل، ومنط بياناته، باإلضافة إىل قياسه إذا كانت بياناته من منط INI .Contribن املستخدميف تطبيق اختبار املكو .

. لتطبيق المثالContrib.INIالملف : 3-17سرد

[Fields] Number=6 [Field1] Type=ftString Name=Name Size=30 [Field2] Type = ftInteger Name = Level [Field3] Type = ftDate Name = BirthDate [Field4] Type = ftCurrency Name = Stipend [Field5] Type = ftString Name = Email Size = 50

Page 27: Delphi 7

٨٠٩ إنشاء مكونات قواعد بيانات 17.

[Field6] Type = ftBoolean Name = Editor

نفس اسم ملف اجلدول، وجيب أن يكون يف نفس ) أو ملف مماثل(جيب أن يستخدم هذا امللف

م اليت جيدها ، مستخدما القي)4-17املبني يف السرد (InternalInitFieldDefsحيث سيقرأه املنهج . الدويقوم املنهج أيضا بتعيني القيم االبتدائية لغرض . إلعداد تعريفات احلقول، وحتديد قياس كل سجل

TListويستخدم هذا الغرض من النمط . داخلي، خيزن انزياح كل حقل داخل السجلTList .السجل، كما تالحظ يف سرد الشيفرة) buffer(للوصول إىل بيانات احلقول ضمن خمزن

. لمجموعة البيانات المتضمنة مجرىInternalInitFieldDefsالمنهج : 4-17سرد

procedure TMdDataSetStream.InternalInitFieldDefs; var IniFileName, FieldName: string; IniFile: TIniFile; nFields, I, TmpFieldOffset, nSize: Integer; FieldType: TFieldType; begin FFieldOffset := TList.Create; FieldDefs.Clear; TmpFieldOffset := 0; IniFilename := ChangeFileExt(FTableName, '.ini'); Inifile := TIniFile.Create (IniFilename); // protect ini file try nFields := IniFile.ReadInteger ('Fields', 'Number', 0); if nFields = 0 then raise EMdDataSetError.Create ('InitFieldsDefs: 0 fields?'); for I := 1 to nFields do begin // create the field FieldType := TFieldType (GetEnumValue ( TypeInfo (TFieldType), IniFile.ReadString ( 'Field' + IntToStr (I), 'Type', ''))); FieldName := IniFile.ReadString ( 'Field' + IntToStr (I), 'Name', ''); if FieldName = '' then raise EMdDataSetError.Create ( 'InitFieldsDefs: No name for field ' + IntToStr (I)); nSize := IniFile.ReadInteger ( 'Field' + IntToStr (I), 'Size', 0); FieldDefs.Add (FieldName, FieldType, nSize, False); // save offset and compute size FFieldOffset.Add (Pointer (TmpFieldOffset));

Page 28: Delphi 7

الفصل السابع عشر ٨١٠

case FieldType of ftString: Inc (TmpFieldOffset, nSize + 1); ftBoolean, ftSmallInt, ftWord: Inc (TmpFieldOffset, 2); ftInteger, ftDate, ftTime: Inc (TmpFieldOffset, 4); ftFloat, ftCurrency, ftDateTime: Inc (TmpFieldOffset, 8); else raise EMdDataSetError.Create ( 'InitFieldsDefs: Unsupported field type'); end; end; // for finally IniFile.Free; end; FRecordSize := TmpFieldOffset; end;

وجيب أن ). باستخدام بعض االستدعاءات القياسية(يكون إغالق اجلدول مسألة فصل ربط احلقول

حيرر كل صنف البيانات اليت حيجزها، ويعدل ترويسة امللف عند أول مرة تضاف فيها السجالت، :ويف كل مرة يتغري فيها عدد السجالت

Procedure TMCustomDataSet.InternalClose; begin // disconnect field objects BindFields (False); // destroy field object (if not persistent) if DefaultFields then DestroyFiels; // close the file FIsTableOpen := False; end; procedure TMdDataSetStream.InternalClose; begin // if required, save updated header if (FDataFileHeader.RecordCount <> FRecordCount) or (FDataFileHeader.RecordSize = 0) then begin FDataFileHeader.RecordSize := FRecordSize; FDataFileHeader.RecordCount := FRecordCount; if Assigned (FStream) then begin FStream.Seek (0, soFromBeginning); FStream.WriteBuffer ( FDataFileHeader, FDataFileHeaderSize); end; end; // free the internal list field offsets and the stream

Page 29: Delphi 7

٨١١ إنشاء مكونات قواعد بيانات 17.

FFieldOffset.Free; FStream.Free; inherited InternalClose; end; يستخدم تابع آخر لفحص فيما إذا كانت جمموعة البيانات مفتوحة، وميكنك حل ذلك باستخدام

:احلقل احمللي ذو العالقةfunction TMdCustomDataSet.IsCursorOpen: Boolean; begin Result := FIsTableOpen; end;

ومع ذلك . موعة بيانات خمصصةهذه هي مناهج الفتح واإلغالق اليت حتتاج إىل حتقيقها يف أية جم يف هذا املثال CreateTableوينشئ املنهج . ستقوم يف أغلب األوقات بإضافة منهج إلنشاء اجلدول

حيث أنك ال (رقم النسخة املثبتة، وقياس زائف للسجل : ملفا فارغا، ويدرج معلومات يف الترويسة ): الذي يكون صفرا يف البداية( السجالت ، وعدد)تعلم القياس حىت تعني القيم االبتدائية للحقول

procedure TMdDataSetStream.CreateTable; begin CheckInactive; InternalInitFieldDefs; // create the new file if FileExists (FTableName) then raise EMdDataSetError.Create ('File ' + FTableName +

' already exists'); FStream := TFileStream.Create (FTableName, fmCreate or fmShareExclusive); try // save the header FDataFileHeader.VersionNumber := HeaderVersion; FDataFileHeader.RecordSize := 0; // used later FDataFileHeader.RecordCount := 0; // empty FStream.WriteBuffer ( FDataFileHeader, FDataFileHeaderSize); finally // close the file FStream.Free; end; end;

التنقل وإدارة نقاط عالم: IIالمقطع جيب أن حتقق كل جمموعة بيانات إدارة نقاط عالم، اليت تشكل ضرورة للتنقل عرب جمموعة

ومنطقيا فإن نقطة العالم هي مرجع إىل سجل معني يف جمموعة البيانات، وتشكل تعريفا . البياناتوتقنيا . ل إليه، ومقارنته مع سجالت أخرى حبيث تستطيع جمموعة بيانات الوصو.وحيدا للسجلحيث ميكنك حتقيقها كمؤشرات على بىن بيانات معينة ختزن معلومات ). Pointers(فهي مؤشرات

.السجل، أو ميكنك حتقيقها كأرقام سجالت وسنستخدم هذا النهج األخري من أجل البساطة

Page 30: Delphi 7

الفصل السابع عشر ٨١٢

املرتبط ا، لكن إذا كان لديك خمزن إذا كان لديك نقطة عالم ستكون قادرا على إجياد السجل وهذا هو سبب إضافة البنية . سجل جيب أن تكون قادرا على استخراج نقطة العالم املرتبطة به

TMdRecInfoوختزن بنية املعطيات هذه نقطة العالم للسجل . إىل بيانات السجل يف كل خمزن سجل :ت نقاط العالم املعرفة كما يلي، إضافة إىل بعض مؤشرا)buffer(يف املخزن املؤقت

type TBookmarkFlag = (bfCurrent, bfBOF, bfEOF, bfInserted);

سيطلب منك النظام أن تقوم بتخزين هذه املؤشرات يف كل خمزن سجل، وسيطلب الحقا .استخراج هذه املؤشرات من أجل خمزن سجل معني

. بيانات السجل، ونقطة العالم، ومؤشرات نقطة العالم تلخيصا لبنية خمزن سجل خيزن5-17يبني الشكل

في مجموعة البيانات المخصصة، مع الحقول المحلية المختلفة (buffer)بنية لكل مخزن : 5-17شكل

.التي تشير إلى األقسام الجزئية

حويل منط ميكن استخدام انزياح وحجم البيانات للوصول إىل نقطة العالم واملؤشرات؛ وذلك بت بواسطة TMdRecInfo، مث الوصول إىل احلقل املناسب يف البنية PMdRecInfoالقيمة إىل منط املؤشر

:ويوضح منهجا ضبط وإعادة مؤشرات نقطة العالم هذه التقنية. املؤشرprocedure TMdCustomDataSet.SetBookmarkData ( Buffer: PChar; Data: Pointer); begin PMdRecInfo(Buffer + FRecordSize).Bookmark := Integer(Data^); end; function TMdCustomDataSet.GetRecordCount: Longint; begin CheckActive; Result := InternalRecordCount; end;

السابقني، وتكون املنهجان املستخدمان لضبط وإعادة نقاط العالم احلالية لسجل مماثلني للمنهجنيوميكنك احلصول . Dataلكنهما يضيفان تعقيدا ألنك تستقبل مؤشرا إىل نقطة العالم يف الوسيط

:integerعلى قيمة نقطة العالم بتحويل منط القيمة اليت يشري إليها هذا املؤشر إىل النمط

Page 31: Delphi 7

٨١٣ إنشاء مكونات قواعد بيانات 17.

procedure TMdCustomDataSet.GetBookmarkData ( Buffer: PChar; Data: Pointer); begin Integer(Data^) := PMdRecInfo(Buffer + FRecordSize).Bookmark; end; procedure TMdCustomDataSet.SetBookmarkData ( Buffer: PChar; Data: Pointer); begin PMdRecInfo(Buffer + FRecordSize).Bookmark := Integer(Data^); end;

هو املنهج األساسي إلدارة نقطة العالم، وتستخدم جمموعة بياناتك InternalGotoBookmarkاملنهج من الشائع أكثر -وهذه ليست التقنية القياسية للتنقل . هذا املنهج جلعل سجل معني السجل احلايل

يف املقدم GetRecordميكنك إجناز ذلك باستخدام املنهج (-االنتقال إىل السجل التايل أو السابقميكنك إجناز ذلك باستخدام املناهج (، أو االنتقال إىل السجل األول أو األخري )املقطع القادمInternalFirstو InternalLastاملوصفة بعد قليل .(

وسيط نقطة عالم، بل مؤشرا إىل نقطة عالم؛ لذا جيب InternalGotoBookmarkال يتوقع املنهج للقفز InternalSetToRecordويستخدم املنهج . يد قيمة نقطة العالملتحد) derference(حتلل مرجعه

إىل نقطة عالم معينة، لكن جيب أن يستخرج هذا املنهج نقطة العالم من خمزن السجل املمرر وفيما يلي شيفرة هذين . InternalGotoBookmark املنهج InternalSetToRecordكوسيط، مث يستدعي

:املنهجنيprocedure TMdCustomDataSet.InternalGotoBookmark (Bookmark: Pointer); var ReqBookmark: Integer; begin ReqBookmark := Integer (Bookmark^); if (ReqBookmark >= BofCrack) and (ReqBookmark <=

InternalRecordCount) then FCurrentRecord := ReqBookmark else raise EMdDataSetError.Create ('Bookmark ' + IntToStr (ReqBookmark) + ' not found'); end; procedure TMdCustomDataSet.InternalSetToRecord (Buffer: PChar); var ReqBookmark: Integer; begin ReqBookmark := PMdRecInfo(Buffer + FRecordSize).Bookmark; InternalGotoBookmark (@ReqBookmark); end; باإلضافة إىل مناهج إدارة نقاط العالم املوصفة للتو، فإنك تستخدم عدة مناهج أخرى لالنتقال إىل

Page 32: Delphi 7

الفصل السابع عشر ٨١٤

ال ينقل هذان املنهجني . مواضع معينة ضمن جمموعة البيانات، مثل السجل األول والسجل األخريو األخري، بل إىل أحد موضعني خاصني قبل السجل األول مؤشر السجل احلايل إىل السجل األول أ

يأخذ . (Cracks) صدوعا Borlandتسميها : وهذه ليست سجالت فعلية. وبعد السجل األخري، ألن موضع السجل األول )InternalOpenتعني يف املنهج (1- القيمة BofCrackصدع بداية امللف أو

قيمة عدد السجالت، ألن موضع السجل األخري EofCrackويأخذ صدع اية امللف أو . هو صفر جلعل قراءة هذه الشيفرة BofCrack وEofCrackوقد استخدمنا حقلني حمليني . FRecordCount-1هو

:أكثر سهولةprocedure TMdCustomDataSet.InternalFirst; begin FCurrentRecord := BofCrack; end; // II: Go to a special position after the last record procedure TMdCustomDataSet.InternalLast; begin EofCrack := InternalRecordCount; FCurrentRecord := EofCrack; end;

، ألن TMdCustomDataSetيقدم يف صنفنا ) Virtual( هو منهج افتراضي InternalRecordCountاملنهج كما يف حالة (كن إما أن يكون هلا حقل حملي من أجل هذه القيمة جمموعات بيانات خمتلفة مي

.، أو تقوم حبسابه بسرعة)FRecordCount اليت متلك احلقل streamجمموعة البيانات املتضمنة جمرى يستخدمها املكون (تستخدم جمموعة أخرى من املناهج االختيارية للحصول على رقم السجل احلايل

DBGridأو لضبط رقم السجل احلايل، أو لتحديد عدد السجالت، ) مترير شاقويل متناسب إلظهار شريط ، إىل عدد 0 يتراوح من FCurrentFieldوهذه املناهج سهلة الفهم، فإذا تذكرت أن جمال احلقل الداخلي

: إىل عدد السجالت1وبالعكس يتراوح عدد السجالت املقدم إىل النظام من . السجالت ناقصا واحدfunction TMdCustomDataSet.GetRecordCount: Longint; begin CheckActive; Result := InternalRecordCount; end; function TMdCustomDataSet.GetRecNo: Longint; begin UpdateCursorPos; if FCurrentRecord < 0 then Result := 1 else Result := FCurrentRecord + 1; end; procedure TMdCustomDataSet.SetRecNo(Value: Integer); begin

Page 33: Delphi 7

٨١٥ إنشاء مكونات قواعد بيانات 17.

CheckBrowseMode; if (Value >= 1) and (Value <= InternalRecordCount) then begin FCurrentRecord := Value - 1; Resync([]); end; end;

وال . الحظ أن الصنف العام موعة البيانات املخصصة حيقق مجيع املناهج املوجودة يف هذا املقطع . إىل تعديل أي منهاstreamحتتاج جمموعة البيانات املشتقة اليت تتضمن جمرى

مخازن السجالت وإدارة الحقول: IIIالمقطع حيتاج املكون . موعة بيانات خمصصةأما وقد غطينا مجيع املناهج الداعمة، دعنا نتفحص أساس جم

امللف ( stream إىل نقل البيانات من ارى -إضافة إىل فتح وإنشاء سجالت والتنقل فيما بينها- املربوطة إىل عناصر حتكم TFieldإىل خمازن السجالت، ومن خمازن السجالت إىل األغراض ) الدائم

عقدة، ألن جمموعة البيانات حتتاج إىل حجز وتفريغ وتكون إدارة خمازن السجالت م. ترتبط ببيانات .وحترير الذاكرة اليت تتطلبها

function TMdCustomDataSet.AllocRecordBuffer: PChar; begin GetMem (Result, FRecordBufferSize); end; procedure TMdCustomDataSet.FreeRecordBuffer (var Buffer: PChar); begin FreeMem (Buffer); end; حتجز الذاكرة ذه الطريقة، ألن جمموعة البيانات تضيف عادة معلومات أكثر إىل خمزن السجل،

الحظ أن املكون حيجز يف . وهكذا ال ميلك النظام أية طريقة ملعرفة مقدار الذاكرة اليت حيجزهامن بيانات قاعدة البيانات ذاكرة خمزن السجل، اليت تتضمن كال AllocRecordBufferاملنهج

:InternalOpenوقد كتبنا السطر التايل يف املنهج . ومعلومات السجلFRecordBufferSize := FRecordSize + sizeof (TMdRecInfo);

إىل حالته االبتدائية ) buffer(جيب أن حيقق املكون أيضا تابعا إلعادة املخزن املؤقت )InternalInitRecord(مبلئه بأصفار رقمية أو فراغات، وذلك .

. وجيب أن حتقق منهجا يعيد قياس كل سجل، جلزء من البيانات، وليس لكامل خمزن السجل، املستخدمة فقط يف حالتني )للقراءة فقط (RecordSizeويكون هذا املنهج ضروريا لتحقيق اخلاصية

يف FRecordSize قيمة احلقل GetRecordSizeويعيد املنهج . VCLغريبتني يف كامل شيفرة مصدر .جمموعة البيانات املخصصة العامة

بعد أن وصلنا إىل أساس مكون جمموعة البيانات املخصصة، فإن املناهج املوجودة يف هذه اموعة

Page 34: Delphi 7

الفصل السابع عشر ٨١٦

لتعديل : InternalAddRecord وInternalPostاليت تقرأ البيانات من امللف؛ و: GetRecord: هيالذي حيذف بيانات، وهو غري حمقق يف : InternalDeleteيدة إىل امللف؛ واملنهج أو إضافة بيانات جد .جمموعة بيانات املثال

حيث يستخدمه النظام الستخراج البيانات . األكثر تعقيدا يف هذه اموعةGetRecordويعترب املنهج السجل الالحق أو ممرر كوسيط، واستخراج بيانات(buffer)من السجل احلايل، وملء خمزن مؤقت

: فعلهGetModeوحيدد الوسيط . السابقtype TGetMode = (gmCurrent, gmNext, gmPrior);

حىت أن السجل احلايل قد ال يكون موجودا، عندما . بالطبع رمبا ال يوجد سجل سابق أو الحقالت بيانات بل تعيد وال تستخرج يف هذه احلا). أو يف حالة خطأ داخلي(يكون اجلدول فارغا مثال

:وهكذا ميكن أن تكون نتيجة هذا املنهج إحدى القيم التالية. رمز خطأtype TGetResult = (grOK, grBOF, grEOF, grError);

حيث ال يتوجب عليك . يكون التحقق من وجود السجل املطلوب خمتلفا قليال عما قد تتوقعهحيث . لصحيح، إال إذا كان هذا السجل هو املطلوبحتديد إذا كان السجل احلايل يف اال ا

للتعليمة gmCurrent يف الفرع CurrentRecord >= InternalRecordCountتستخدم التعبري القياسي Case .ورمبا جيب أن تقرأ الشيفرة مرتني لفهم خمتلف احلاالت بشكل جيد.

. ، ووقعنا يف بعض األخطاءعند كتابة جمموعة بيانات خمصصة ألول مرة قمنا ببعض التجارب سينجز النظام سلسلة DBGridوالختبار هذا املكون ضع يف االعتبار أنه إذا كنت تستخدم

. grEOF القيمة GetRecord حىت متتلئ الشبكة، أو حىت يعيد املنهج GetRecordاستدعاءات للمنهج : كاملةGetRecordوفيما يلي شيفرة املنهج

// III: Retrieve data for current, previous, or next record // (eventually moving to it) and return the status function TMdCustomDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; begin Result := grOK; // default case GetMode of gmNext: // move on if FCurrentRecord < InternalRecordCount - 1 then Inc (FCurrentRecord) else Result := grEOF; // end of file gmPrior: // move back if FCurrentRecord > 0 then Dec (FCurrentRecord) else Result := grBOF; // begin of file gmCurrent: // check if empty if (FCurrentRecord >= InternalRecordCount) or

Page 35: Delphi 7

٨١٧ إنشاء مكونات قواعد بيانات 17.

(FCurrentRecord < 0) then Result := grError; end; // load the data if Result = grOK then InternalLoadCurrentRecord (Buffer) else if (Result = grError) and DoCheck then raise EMdDataSetError.Create ('GetRecord: Invalid record'); end;

وإذا . استثناءGetRecord سيطلق املنهج True يأخذ القيمة DoCheckإذا وجد خطأ وكان الوسيط جرى كل شيء على ما يرام أثناء حتديد سجل، حيمل املكون البيانات من ارى، منتقال إىل موضع

ن باإلضافة إىل ذلك جيب أن تشح). يعطى جبداء قياس السجل مع رقم السجل(السجل احلايل ، وينجز ذلك بواسطة )أو رقم السجل(املخزن باملؤشر املناسب لنقطة العالم وقيمة نقطة العالم

منهج افتراضي آخر قمنا بتقدميه سابقا، لذا حتتاج األصناف املشتقة إىل حتقيق هذا اجلزء من الشيفرة : دون تغيريGetRecordفقط، بينما يبقى املنهج

procedure TMdDataSetStream.InternalLoadCurrentRecord (Buffer: PChar); begin FStream.Position := FDataFileHeaderSize + FRecordSize * FCurrentRecord; FStream.ReadBuffer (Buffer^, FRecordSize); with PMdRecInfo(Buffer + FRecordSize)^ do begin BookmarkFlag := bfCurrent; Bookmark := FCurrentRecord; end; end;

، الذي AddRecord، يستدعيها املنهج InternalAddRecordيوجد كذلك منهج آخر متعلق هو واملنهجان األخريان عامان . AppendRecord وInsertRecordيستدعى بدوره من قبل املنهجني

)Public(ويشكل هذا بديال إلدراج أو إضافة سجل جديد إىل . ، ويستطيع املستخدم استدعاءمهابيانات، وذلك بتحرير قيم خمتلف احلقول، مث تثبيت البيانات، ألن االستدعائني جمموعة ال

InsertRecordو AppendRecordوكل ما عليك عندها هو نسخ . يستقبالن قيم احلقول كوسائط :InternalPostالشيفرة املستخدمة إلضافة سجل جديد يف املنهج

procedure TMdDataSetOne.InternalAddRecord(Buffer: Pointer; Append: Boolean);

begin // always append at the end InternalLast; FStream.Seek (0, soFromEnd); FStream.WriteBuffer (ActiveBuffer^, FRecordSize); Inc (FRecordCount); end;

. ية شائعة إال أا معقدةلقد توجب حتقيق عملية ملف، تقوم حبذف السجل احلايل، وهذه العمل

Page 36: Delphi 7

الفصل السابع عشر ٨١٨

وإذا سلكت جا بسيطا، مثل إنشاء منطقة فارغة يف امللف، ستحتاج إىل متابعة تلك املنطقة، يوجد حل بديل بإنشاء نسخة . وكتابة شيفرة قراءة وكتابة سجل خاص يعمل حول ذلك املوضعوجود هذين اخليارين شعرنا ب. لكامل امللف عدا السجل املعطى، مث استبدال امللف األصلي بالنسخة

.أننا نستطيع اإلمساك عن دعم حذف السجل يف هذا املثال

من المخازن المؤقتة إلى الحقول: IVالمقطع . رأينا يف املناهج القليلة السابقة كيف تنقل جمموعات البيانات من ملف البيانات إىل خمازن الذاكرة

خمزن السجل هذا، ألا ال تعرف حىت اآلن كيف ومع ذلك يوجد القليل مما تستطيع دلفي فعله مع الذي GetData: وحتتاج إىل تزويد منهجني إضافيني). buffer(ستترجم البيانات املوجودة يف املخزن

الذي يعيد SetDataينسخ البيانات من خمزن السجل إىل أغراض حقول جمموعة البيانات، واملنهج ستنقل دلفي البيانات من أغراض احلقول إىل عناصر و. البيانات من احلقول إىل خمزن السجل

.التحكم املرتبطة ببيانات، وستعيدها بشكل آيلإن شيفرة هذين املنهجني ليست صعبة، ألنك قد حفظت انزياحات احلقول ضمن بيانات السجل

وميكنك احلصول على البيانات اخلاصة اليت تأخذ . TList من النمط FFieldOffsetيف الغرض FieldDataSizeبايت، بزيادة املؤشر إىل املوقع االبتدائي إلنزياح احلقل احلايل يف خمزن السجل .

وقد تظن يف . Buffer ووسيطا Fieldيظهر عنصر تعارض هلذين املنهجني، وهو أما تقبالن وسيطا هو Bufferالبداية أن املخزن املمرر كوسيط هو خمزن السجل، ومع ذلك فقد اكتشفنا أن الوسيط

وإذا استخدمت إحدى مناهج غرض احلقل لنقل تلك البيانات، . مؤشر بيانات شعاع غرض احلقل موعة البيانات، وحيتمل أن يسبب ذلك حلقة SetData أو املنهج GetDataفإا ستستدعي املنهج

للوصول ActiveBuffer املؤشر -بدل ذلك-وجيب أن تستخدم ). infinite recursion(عودية الائية إىل خمزن السجل، وأن تستخدم االنزياح الصحيح للحصول على بيانات احلقل احلايل يف خمزن

والفرق الوحيد بني املنهجني . املقدم للوصول إىل بيانات احلقلBufferمث تستخدم الوسيط . السجل .هو االجتاه الذي تنقل وفقه البيانات

function TMdDataSetOne.GetFieldData (Field: TField; Buffer: Pointer): Boolean;

var FieldOffset: Integer; Ptr: PChar; begin Result := False; if not IsEmpty and (Field.FieldNo > 0) then begin FieldOffset := Integer (FFieldOffset [Field.FieldNo - 1]); Ptr := ActiveBuffer; Inc (Ptr, FieldOffset); if Assigned (Buffer) then

Page 37: Delphi 7

٨١٩ إنشاء مكونات قواعد بيانات 17.

Move (Ptr^, Buffer^, Field.DataSize); Result := True; if (Field is TDateTimeField) and (Integer(Ptr^) = 0) then Result := False; end; end; procedure TMdDataSetOne.SetFieldData(Field: TField; Buffer: Pointer); var FieldOffset: Integer; Ptr: PChar; begin if Field.FieldNo >= 0 then begin FieldOffset := Integer ( FFieldOffset [Field.FieldNo - 1]); Ptr := ActiveBuffer; Inc (Ptr, FieldOffset); if Assigned (Buffer) then Move (Buffer^, Ptr^, Field.DataSize) else raise Exception.Create ( 'Very bad error in TMdDataSetStream.SetField data'); DataEvent (deFieldChange, Longint(Field)); end; end;

للداللة على وجود بيانات يف احلقل أو يكون فارغا False أو True القيمة GetFieldسيعيد املنهج وإذا مل تستخدم مؤشرا خاصا للحقول الفارغة فمن الصعب حتديد هذا الشرط، ). nullحقل (

إذا معىن فقط ptr^<>#0يكون لفحص مثل : وعلى سبيل املثال. ألنك تعزز قيما من أمناط خمتلفةوإذا استخدمت هذا الفحص ستظهر قيم صفر . كنت تستخدم متثيل سلسلة حمارف جلميع احلقول

، ورمبا )وستكون عناصر التحكم املرتبطة ببيانات فارغة (nullصحيح وسالسل حمارف فارغة كقيم م وأسوأ من ذلك عد. Falseوستكون املشكلة يف عدم إظهار القيمة املنطقية . يكون هذا ما تريدهليس هلا قيمة صحيحة وعدد قليل من اخلانات العشرية؛ ) ممثلة بالفاصلة العائمة(إظهار قيم حقيقية

ومع ذلك فلجعل هذا املثال يعمل فقد توجب أن نعترب كل . ألن اجلزء العشري هلا سيكون صفراحتويل وبدون هذه الشيفرة ستحاول دلفي . زمن مع قيمة صفر يف البداية كحقل فارغ/حقل تاريخ

، لكن TDataTimeداخليا ال تستخدم حقول التاريخ منط املعطيات (التاريخ صفر الداخلي الالقانوين .، مطلقا استثناء)تستخدم متثيال خمتلفا

من أجل حقل، فإن IsNull أثناء محاولتنا لحل هذه المشكلة اآتشفنا أنه إذا استدعيت التابع :تنبيه

لملئه، لكن فقط للبحث عن (buffer) دون تمرير أي مخزن GetFieldDataهذا الطلب يحل باستدعاء

. ضمن هذه الشيفرةif Assigned(Buffer)وهذا هو سبب استخدام الفحص . نتيجة استدعاء التابع

، يقوم عادة بإسكات InternalHandleExceptionيوجد منهج أخري ال يقع ضمن أية فئة، وهو

Page 38: Delphi 7

الفصل السابع عشر ٨٢٠

.زمن التصميم فقطاالستثناء، ألنه مفعل يف اختبار مجموعة البيانات المتضمنة مجرى

ستكون جاهزا بعد كل هذا العمل الختبار مثال تطبيق ملكون جمموعة البيانات املخصصة، الذي ويكون النموذج الذي يعرضه الربنامج . ينصب يف احلزمة الربجمية للمكونات يف هذا الفصل

StreamDSDemoفهو حيوي لوحة، حتوي زرين ومربع اختيار . 6-17ظ يف الشكل بسيطا، كما تالح . متأل املساحة الزبون لللوحةDBGridومكون تنقل، إضافة إىل

منوذج املثال يف زمن التصميم، لكننا فعلنا جمموعة البيانات املخصصة، لذا فإن 6-17يبني الشكل امللف الذي ذكرناه سابقا عند (ريف اجلدول الذي حيوي تعINIلقد جهزنا امللف . البيانات مرئية

.، وقد نفذنا الربنامج إلضافة بعض البيانات إىل امللف)مناقشة تعيني القيم االبتدائية موعة البيانات

وقد تم تفعيل مجموعة البيانات المخصصة، لذا يمكنك StreamDSDemoنموذج المثال : 6-17شكل

.مرؤية البيانات أثناء التصمي

يف دلفي، وضبط خصائص األغراض Fields Editorوميكنك أيضا تعديل النموذج باستخدام احملرر . وجيري كل شيء كما حيصل مع أحد عناصر حتكم جمموعات البيانات القياسية. املختلفة للحقول

لكن لتجعل هذا يعمل جيب أن تدخل اسم ملف جمموعة البيانات املخصصة يف اخلاصية TableName،باستخدام طريق الوصول كامال .

يعرف برنامج المثال طريق الوصول المطلق لملف الجدول أثناء التصميم، لذا ستحتاج إلى :تنبيه

في TableNameتستخدم الخاصية . تثبيته إذا نسخت األمثلة إلى سواقة مختلفة أو مجلد مختلف

Page 39: Delphi 7

٨٢١ إنشاء مكونات قواعد بيانات 17.

.ج عن الجدول في المجلد الحاليالمثال أثناء التصميم، أما في زمن التنفيذ فيبحث البرنام

فإذا كان اجلدول . إن شيفرة املثال بسيطة، وخاصة باملقارنة مع شيفرة جمموعة البيانات املخصصة :Create New Tableغري موجود بعد تستطيع النقر على الزر

procedure TForm1.Button1Click(Sender: TObject); begin MdDataSetStream1.CreateTable; MdDataSetStream1.Open; CheckBox1.Checked := MdDataSetStream1.Active; end;

وهذا هو . ، مث تفتح اجلدولCreateTableتقوم أوال بإنشاء امللف، بفتحه وإغالقه ضمن االستدعاء النقر وميكنك ). CreateTableالذي ينجز هذه اخلطوة باستخدام املنهج (TTableنفس سلوك املكون

:على مربع االختيار لفتح اجلدول أو إلغالقهprocedure TForm1.CheckBox1Click(Sender: TObject); begin MdDataSetStream1.Active := CheckBox1.Checked; end;

.أنشأنا أخريا منهجا خيترب شيفرة إدارة نقاط عالم جمموعة البيانات املخصصة

مجلد في مجموعة بياناتمن البيانات، ) set(توجد فكرة هامة تتعلق مبجموعات البيانات يف دلفي، هي أا متثل جمموعة

وملف حملي أمثلة عن SQLويشكل كل من ملقم . بصرف النظر عن املكان الذي تأيت منه البياناتجمموعات بيانات تقليدية، لكن ميكنك استخدام نفس التقنيات إلظهار قائمة مستخدمي نظام،

.، وغريهاXMLائمة ملفات جملد، وخصائص أغراض، وبيانات تتضمن وقوقد بنينا جمموعة . وكمثال، تكون جمموعة البيانات الثانية املقدمة يف هذا الفصل قائمة ملفات

، مث )TObjectListباستخدام غرض من الصنف (بيانات عامة تستخدم قائمة أغراض يف الذاكرة ويكون املثال بسيطا نتيجة حلقيقة أن . ملوجودة فيها مبلفات جملداشتقينا نسخة ترتبط األغراض ا

.هذا املكون للقراءة فقط، لذا قد جتده أكثر مباشرة من جمموعة البيانات السابقة

توجد بعض األفكار المقدمة هنا في مقال نشر على اإلنترنت في الموقع :مالحظة

dbn.borland.com/articale/0,1410,20587,00.html

قائمة آمجموعة بيانات، وتتضمن قائمة أغراض، وهي قائمة TMdLisTDataSetتسمى جمموعة البيانات العامة املتضمنة قائمة

Page 40: Delphi 7

الفصل السابع عشر ٨٢٢

وال ختزن جمموعة البيانات هذه بيانات . تنشأ عندما تفتح جمموعة البيانات، وحتررها عندما تغلقهاملخزن املؤقت مدخل القائمة املتعلق ، وبدل من ذلك حتفظ يف ا)(Bufferالسجل ضمن خمزن مؤقت

:وفيما يلي تعريف الصنف. ببيانات السجلtype TMdListDataSet = class (TMdCustomDataSet) protected // the list holding the data FList: TObjectList; // dataset virtual methods procedure InternalPreOpen; override; procedure InternalClose; override; // custom dataset virtual methods function InternalRecordCount: Integer; override; procedure InternalLoadCurrentRecord (Buffer: PChar); override; end;

تستطيع إعادة صياغة عدة مناهج افتراضية ميكنك مالحظة أنه بكتابة صنف بيانات خمصص، فإنك، وهلذا الصنف املخصص موعة البيانات، واحلصول على جمموعة بيانات شغالة TDataSetللصنف

وجيب أن ). رغم أن هذا يبقى صنفا جمردا، يتطلب شيفرة إضافية من األصناف الفرعية كي يعمل(البيانات، لإلشارة إىل أنك حتفظ دليل القائمة تنشئ القائمة وتعني قياس السجل عند فتح جمموعة

:(Buffer)يف املخزن املؤقت procedure TMdListDataSet.InternalPreOpen; begin FList := TObjectList.Create (True); // owns objects FRecordSize := 4; // an integer, the list item id end;

.ة عند هذه النقطة ملء القائمة بأغراضسيتوجب على األصناف املشتق

تحفظ مجموع بيانات القائمة التي تنشئها بياناتها في الذاآرة، آما هو حال المكون :تلميح

ClientDataSet . ومع ذلك تستطيع باستخدام بعض التقنيات الذآية إنشاء قائمة أغراض مزيفة، ثم

.تحمل األغراض الفعلية عندما تصل إليها

:اليت حتوي عداد سجل يتعلق بقياس القائمة) من الذاكرة( عملية اإلغالق مسألة حترير القائمة تكون

function TMdListDataSet.InternalRecordCount: Integer; begin Result := fList.Count; end;

لومات نقطة قوم املنهج اآلخر الوحيد حبفظ بيانات السجل احلايل يف خمزن السجل، الذي حيوي معيونقطة (وتكون البيانات األساسية هي موقع السجل احلايل، الذي يرتبط بدليل القائمة . العالم

):العالم أيضا

Page 41: Delphi 7

٨٢٣ إنشاء مكونات قواعد بيانات 17.

procedure TMdListDataSet.InternalLoadCurrentRecord (Buffer: PChar); begin PInteger (Buffer)^ := fCurrentRecord; with PMdRecInfo(Buffer + FRecordSize)^ do begin BookmarkFlag := bfCurrent; Bookmark := fCurrentRecord; end; end;

بيانات مجلدجيب أن يوفر الصنف املشتق موعة بيانات الد منهجا لتحميل األغراض يف الذاكرة عند فتح جمموعة

ومتلك أيضا خاصية تشري إىل . ة قيمة تلك احلقولالبيانات، ولتعريف احلقول املناسبة، ولقراءة وكتاب )):C:\docs\*.txtمثل (أو الد، إضافة إىل قناع امللف املستخدم لترشيح امللفات (الد الذي تعمل عليه

type TMdDirDataset = class(TMdListDataSet) private FDirectory: string; procedure SetDirectory(const NewDirectory: string); protected // TDataSet virtual methods procedure InternalInitFieldDefs; override; procedure SetFieldData(Field: TField; Buffer: Pointer); override; function GetCanModify: Boolean; override; // custom dataset virtual methods procedure InternalAfterOpen; override; public function GetFieldData(Field: TField; Buffer: Pointer):

Boolean; override; published property Directory: string read FDirectory write SetDirectory; end;

، يستخدم لتحديد كون جمموعة TDataSet هو منهج افتراضي آخر للصنف GetCanModifyالتابع ال يتوجب عليك كتابة أية شيفرة . Falseوتعيد يف هذه احلالة القيمة . البيانات للقراءة فقط

.(abstract virtual)، لكن جيب أن تعرفها ألا منهج افتراضي جمرد SetFieldDataلإلجرائية وتستخرج بيانات . ا أنك تتعامل مع قائمة أغراض، حتوي الوحدة الربجمية صنفا لتلك األغراضمب

:TFieldData بواسطة بناء الصنف TSearchRecامللف يف هذه احلالة من خمزن مؤقت من الصنف type TFileData = class public ShortFileName: string; Time: TDateTime; Size: Integer; Attr: Integer; constructor Create (var FileInfo: TSearchRec);

Page 42: Delphi 7

الفصل السابع عشر ٨٢٤

end; constructor TFileData.Create(var FileInfo: TSearchRec); begin ShortFileName := FileInfo.Name; Time := FileDateToDateTime (FileInfo.Time); Size := FileInfo.Size; Attr := FileInfo.Attr; end;

:ويستدعى هذا البناء من أجل كل جملد أثناء فتح جمموعة البياناتprocedure TMdDirDataset.InternalAfterOpen; var Attr: Integer; FileInfo: TSearchRec; FileData: TFileData; begin // scan all files Attr := faAnyFile; FList.Clear; if SysUtils.FindFirst(fDirectory, Attr, FileInfo) = 0 then repeat FileData := TFileData.Create (FileInfo); FList.Add (FileData); until SysUtils.FindNext(FileInfo) <> 0; SysUtils.FindClose(FileInfo); end;

كون مثبتة يف هذه احلالة، وتعتمد على وتكون اخلطوة التالية تعريف حقول جمموعة البيانات، اليت ت :البيانات املتاحة للمجلد

procedure TMdDirDataset.InternalInitFieldDefs; begin if fDirectory = '' then raise EMdDataSetError.Create ('Missing directory'); // field definitions FieldDefs.Clear; FieldDefs.Add ('FileName', ftString, 40, True); FieldDefs.Add ('TimeStamp', ftDateTime); FieldDefs.Add ('Size', ftInteger); FieldDefs.Add ('Attributes', ftString, 3); FieldDefs.Add ('Folder', ftBoolean); end;

املشار إليه بواسطة خمزن السجل احلايل يتوجب على املكون أخريا نقل البيانات من غرض القائمة. GetFieldDataإىل مجيع حقول جمموعة البيانات، كما هو مطلوب باملنهج ) ActiveBufferالقيمة (

، اعتمادا على منط البيانات، ويقدم ببعض التحويالت StrCopy أو Moveويستخدم هذا التابع إما املستخرجة ) system من أجل S، و read-Onlyجل من أR، و hidden من أجل H(لرموز املواصفات

:وفيما يلي شيفرة هذا التابع. من املؤشرات املرتبطة، واملستخدمة لتحديد فيما إذا كان ملف ما جملدا

Page 43: Delphi 7

٨٢٥ إنشاء مكونات قواعد بيانات 17.

function TMdDirDataset.GetFieldData ( Field: TField; Buffer: Pointer): Boolean; var FileData: TFileData; Bool1: WordBool; strAttr: string; t: TDateTimeRec; begin FileData := fList [Integer(ActiveBuffer^)] as TFileData; case Field.Index of 0: // filename StrCopy (Buffer, pchar(FileData.ShortFileName)); 1: // timestamp begin t := DateTimeToNative (ftdatetime, FileData.Time); Move (t, Buffer^, sizeof (TDateTime)); end; 2: // size Move (FileData.Size, Buffer^, sizeof (Integer)); 3: begin // attributes strAttr := ' '; if (FileData.Attr and SysUtils.faReadOnly) > 0 then strAttr [1] := 'R'; if (FileData.Attr and SysUtils.faSysFile) > 0 then strAttr [2] := 'S'; if (FileData.Attr and SysUtils.faHidden) > 0 then strAttr [3] := 'H'; StrCopy (Buffer, pchar(strAttr)); end; 4: begin // folder Bool1 := FileData.Attr and SysUtils.faDirectory > 0; Move (Bool1, Buffer^, sizeof (WordBool)); end; end; // case Result := True; end;

كان اجلزء األصعب يف كتابة هذه الشيفرة يف حساب التنسيق الداخلي للتواريخ املخزنة يف العامة املستخدمة يف دلفي، TDataTimeحيث أن هذه الصيغة ليست الصيغة . زمن/حقول تاريخ

، لكنها املسماة داخليا بصيغة تاريخ وزمن أصيلة TTimeStampكما أا ليست الصيغة الداخلية native date and time format . وقد كتبنا تابع حتويل بنسخ منط وجدناه يف شيفرةVCL من أجل

:زمن/حقول تاريخfunction DateTimeToNative(DataType: TFieldType; Data: TDateTime): TDateTimeRec; var TimeStamp: TTimeStamp; begin TimeStamp := DateTimeToTimeStamp(Data); case DataType of ftDate: Result.Date := TimeStamp.Date;

Page 44: Delphi 7

الفصل السابع عشر ٨٢٦

ftTime: Result.Time := TimeStamp.Time; else Result.DateTime := TimeStampToMSecs(TimeStamp); end; end;

مسألة ) طبوجود جمموعة البيانات احلالية فق() 7-17املبني يف الشكل (لقد كان بناء تطبيق العرض عنصر التحكم ( إىل جمموعة البيانات، وإضافة مكون حتديد جملد DBGridربط مكون ShellTreeView .( يتم إعداد هذا العنصر للتعامل مع ملفات فقط، بضبط خاصيتهRoot على C:\ .

بتحديث ShellTreeView لعنصر التحكم OnChangeوعند حتديد جملد جديد يقوم معاجل احلدث :عة البياناتجممو

procedure TForm1.ShellTreeView1Change(Sender: TObject; Node: TTreeNode); begin MdDirDataset1.Close; MdDirDataset1.Directory := ShellTreeView1.Path + '\*.*'; MdDirDataset1.Open; end;

.بيانات غير اعتيادية تظهر بيانات مجلد، الذي يستخدم مجموعة DirDemoخرج المثال : 7-17شكل

المتاحة في Shell المنصبة لديك مع عناصر تحكم Windows عند وجود مشاآل لنسخة :تنبيه

للمثال، الذي يستخدم عناصر تحكم ملفات DirDemoNoShellدلفي، تستطيع استخدام نسخة

.Windows 3.1دلفي المتوافقة مع

مجموعة بيانات أغراض. املثال السابق أن مفهوم قائمة أغراض مياثل مفهوم أسطر جدول يف جمموعة بياناترأينا يف

ومن . TFieldDataوميكنك يف دلفي بناء جمموعة بيانات تغلف قائمة أغراض، كما يف حالة الصنف املثري لالهتمام توسيع هذا املثال، لبناء جمموعة بيانات تدعم أغراضا عامة، الذي ميكن إجنازه بفضل

. املوسعة واملتاحة يف دلفيRTTIاملكتبة

Page 45: Delphi 7

٨٢٧ إنشاء مكونات قواعد بيانات 17.

وجيب أن توفر . ، كما يف املثال السابقTMdListDataSetترث جمموعة البيانات هذه من الصنف انظر التعريف الكامل للصنف (ObjectClassالصنف اهلدف املخزن يف اخلاصية : إعدادا وحيدا

TMdObjDataSet 5-17 يف السرد:(

:TMdObjDataSetيف الكامل للصنف التعر : 5-17سرد

type TMdObjDataSet = class(TMdListDataSet) private PropList: PPropList; nProps: Integer; FObjClass: TPersistentClass; ObjClone: TPersistent; FChangeToClone: Boolean; procedure SetObjClass(const Value: TPersistentClass); function GetObjects(I: Integer): TPersistent; procedure SetChangeToClone(const Value: Boolean); protected procedure InternalInitFieldDefs; override; procedure InternalClose; override; procedure InternalInsert; override; procedure InternalPost; override; procedure InternalCancel; override; procedure InternalEdit; override; procedure SetFieldData(Field: TField; Buffer: Pointer); override; function GetCanModify: Boolean; override; procedure InternalPreOpen; override; public function GetFieldData(Field: TField; Buffer: Pointer):

Boolean; override; property Objects [I: Integer]: TPersistent read GetObjects; function Add: TPersistent; published property ObjClass: TPersistentClass read FObjClass write

SetObjClass; property ChangesToClone: Boolean read FChangeToClone write SetChangeToClone default False; end;

لتحديد حقول جمموعة البيانات اليت InternalInitFieldDefsيستخدم هذا الصنف بواسطة املنهج

:RTTIالذي يستخرج باستخدام معلومات . للصنف اهلدف) published(تستخدم خصائصا معلنة procedure TMdObjDataSet.InternalInitFieldDefs; var i: Integer; begin if FObjClass = nil then raise Exception.Create ('TMdObjDataSet: Unassigned class');

Page 46: Delphi 7

الفصل السابع عشر ٨٢٨

// field definitions FieldDefs.Clear; nProps := GetTypeData(fObjClass.ClassInfo)^.PropCount; GetMem(PropList, nProps * SizeOf(Pointer)); GetPropInfos (fObjClass.ClassInfo, PropList); for i := 0 to nProps - 1 do case PropList [i].PropType^.Kind of tkInteger, tkEnumeration, tkSet: FieldDefs.Add (PropList [i].Name, ftInteger, 0); tkChar: FieldDefs.Add (PropList [i].Name, ftFixedChar, 0); tkFloat: FieldDefs.Add (PropList [i].Name, ftFloat, 0); tkString, tkLString: FieldDefs.Add (PropList [i].Name, ftString, 50); // TODO:

fix size tkWString: FieldDefs.Add (PropList [i].Name, ftWideString, 50); // TODO: fix size end; end;

للوصول إىل SetFieldData وGetFieldData مماثلة يف املنهجني RTTIتستخدم شيفرة تتضمن معلومات وتكمن الفائدة الكربى . خصائص الغرض احلايل، عند طلب عملية وصول إىل حقل جمموعة بيانات

تابة الستخدام خصائص للوصول إىل بيانات جمموعة بيانات يف أنه ميكن ربط عمليات القراءة والكوتستطيع ذه الطريقة كتابة قواعد العمل . مباشرة إىل بيانات؛ واستخدام مناهج متعلقة أيضا

)business rules ( لتطبيقك، بتحقيق قواعد يف مناهج قراءة وكتابة اخلصائص) جOOP أكثر لمنهج وفيما يلي نسخة مبسطة قليال ل). حساسية من ربط شيفرة إىل أغراض حقول، والتحقق منها

GetFieldData) املنهج اآلخر مناظر له:( function TMdObjDataSet.GetFieldData ( Field: TField; Buffer: Pointer): Boolean; var Obj: TPersistent; TypeInfo: PTypeInfo; IntValue: Integer; FlValue: Double; begin if FList.Count = 0 then begin Result := False; exit; end; Obj := fList [Integer(ActiveBuffer^)] as TPersistent; TypeInfo := PropList [Field.FieldNo-1]^.PropType^; case TypeInfo.Kind of tkInteger, tkChar, tkWChar, tkClass, tkEnumeration, tkSet: begin IntValue := GetOrdProp(Obj, PropList [Field.FieldNo-1]); Move (IntValue, Buffer^, sizeof (Integer)); end;

Page 47: Delphi 7

٨٢٩ إنشاء مكونات قواعد بيانات 17.

tkFloat: begin FlValue := GetFloatProp(Obj, PropList [Field.FieldNo-1]); Move (FlValue, Buffer^, sizeof (Double)); end; tkString, tkLString, tkWString: StrCopy (Buffer, pchar(GetStrProp(Obj, PropList

[Field.FieldNo-1]))); end; Result := True; end;

رهيبة، لكن لن جتدها كثرية، وإذا حتملت مناقشة ) Pointer(رمبا تبدو هذه الشيفرة املتضمنة مؤشرا واملعلق عليها (فهي تستخدم بعض بىن املعطيات املعرفة . صيل التقنية لتطوير جمموعة بيانات خمصصةالتفا .، اليت جيب أن تكون مرجعك من أجل أي سؤال عن الشيفرة السابقةTypeInfoيف الوحدة ) قليال

ذا ألغى باستخدام هذا النهج الساذج لتحرير بيانات الغرض بشكل مباشر، رمبا تتعجب مما حيدث إوتوفر جمموعة بياناتنا جني بديلني، ). شيء تأخذه دلفي باحلسبان عادة(املستخدم عملية التحرير

، اليت تستخدم فكرة استنساخ أغراض بنسخ خصائصها ChangesToCloneتتحكم ما اخلاصية نسخ مجيع البيانات مماثلة ملا قد رأيناه لRTTI شيفرة DoCloneوتستخدم اإلجرائية األساسية . املعلنة

).أو استنساخ(املنشورة لغرض إىل غرض آخر بإنشاء نسخة فعالة ، ChangesToCloneجتري عملية االستنساخ هذه يف كال احلالتني، وذلك اعتمادا على قيمة اخلاصية

إما أن تنجز عمليات التحرير على الغرض املستنسخ الذي ينسخ من الغرض الفعلي أثناء العملية Postأو تنجز عمليات على الغرض الفعلي، ويستخدم الغرض املستنسخ إلعادة القيم األصلية إذا ؛

:وفيما يلي شيفرة املناهج الثالثة الالزمة. Cancelانتهت عملية التحرير بطلب Procedure TobjDataSet.InternalEdit; begin DoClone (fList[FCurrentRecord] as TdbPers, ObjClone) end; Procedure begin if FchangeToClone and Assigend (ObjClone) then DoClone (ObjClone, TdbPers (fLIst [fCurrentRecord])); end; procedure TMdObjDataSet.InternalCancel; begin if not FChangeToClone and Assigned (ObjClone) then DoClone (ObjClone, TPersistent(fList [fCurrentRecord])); end;

وجلعل . تعديل إما الغرض املستنسخ أو التمثيل األصليSetFieldDataيتوجب عليك يف املنهج ، فإذا كنت GetFieldDataاألشياء أكثر تعقيدا، جيب أن تأخذ يف االعتبار هذا االختالف يف املنهج

Page 48: Delphi 7

الفصل السابع عشر ٨٣٠

ال وإ(تقرأ احلقول من الغرض احلايل فرمبا يتوجب عليك استخدامها للغرض املستنسخ املعدل ).ستظهر تعديالت املستخدم على حقول أخرى

، اليت تصل إىل البيانات مبنهج غرضي Objects حيوي الصنف مصفوفة 5-17كما تالحظ يف السرد وتنشئ الشيفرة باستدعاء ). )Collectionموعة Add، الذي يشبه املنهج Add؛ ومنهج OOPالتوجه :تضيفه إىل القائمة الداخلية غرضا جديدا من الصنف اهلدف، وAddاملنهج

function TMdObjDataSet.Add: TPersistent; begin if not Active then Open; Result := fObjClass.Create; fList.Add (Result); end;

ويتضمن صنفا هدفا للعرض، . لتوضيح استخدام هذا املكونObjDataSetDemoقمنا ببناء املثال ويقدم . 8-17من بعض احلقول واألزرار إلنشاء أغراض بشكل تلقائي، كما تالحظ يف الشكل ويتض

قم بتشغيل الربنامج، وانظر إىل أعمدة عنصر التحكم . املثال ميزة ممتعة جيب أن جترا بنفسكDBGrid مث قم بتحرير الصنف اهلدف ،TDemo بإضافة خاصية معلنة جديدة إليه، ونفذ الربنامج ،

.ثانية وستتضمن الشبكة عمودا جديدا من أجل اخلاصيةمرة

.RTTI مجموعة بيانات مربوطة بأغراض تستخدم ObjDataSetDemoيعرض المثال : 8-17شكل

ماذا بعد؟تطوير عناصر حتكم مرتبطة تعمقنا يف هذا الفصل يف بنية قواعد البيانات يف دلفي، وذلك باختبار

. ، إلنشاء مكونني موعة بيانات خمصصةTDataSetونات الداخلية للصنف ببيانات، مث دراسة املك أن تكون -بوجود هذه املعلومات واألفكار األخرى املقدمة يف هذا اجلزء من الكتاب-وجيب

.قادرا على اختيار بنية تطبيقات قواعد البيانات اليت تطورها، اعتمادا على احتياجاتك

Page 49: Delphi 7

٨٣١ إنشاء مكونات قواعد بيانات 17.

وسنتابع . جمة قواعد البيانات، وذلك ألن هذا املوضوع أساسي يف دلفيلقد كرسنا عدة فصول لربوسنعود يف الفصلني . 7بتغطيته يف الفصل القادم، الذي خيصص حملرك التقارير اجلديد املتاح يف دلفي

، Webالعشرين واحلادي والعشرين إىل قواعد البيانات عندما نركز على متثيل البيانات على . يف الفصلني الثاين والعشرين والثالث والعشرينSOAP وXML وكذلك عند مناقشة