يبدو أن نقل الأعمدة في DBGRID ينقل حقول مجموعة البيانات المرفقة

StackOverflow https://stackoverflow.com/questions/1983274

سؤال

لاحظت شيئًا ما لم أتوقعه الأسبوع الماضي ، وسأصف أدناه. أنا فضولي لسبب حدوث هذا. هل هو شيء داخلي لفئة tdataset ، أو قطعة أثرية من tdbgrid ، أو أي شيء آخر؟

تم تغيير ترتيب الحقول في عميل مفتوح. على وجه التحديد ، قمت بإنشاء clientDatAset في الكود عن طريق استدعاء CreateTatetset بعد تحديد بنيتها باستخدام FieldDefs. كان الحقل الأول في هيكل ClientDataset هذا حقل تاريخ يسمى startofweek. بعد لحظات فقط ، فشلت الكود الذي كتبته أيضًا ، والذي افترض أن حقل StartOfWeek كان في موقع Zeroeth ، ClientDataset.fields [0] ، لأن حقل startofweek لم يعد الحقل الأول في ClientDataset.

بعد بعض التحقيقات ، علمت أنه من الممكن أن يظهر كل حقل في ClientDataset ، في لحظة معينة ، في موقف مختلف عن الهيكل الأصلي في الوقت الذي تم فيه إنشاء ClientDataset. لم أكن على دراية بأن هذا قد يحدث ، ولم يظهر البحث على Google أي ذكر لهذا التأثير أيضًا.

ما حدث لم يكن السحر. لم تغير الحقول الموقف بأنفسهم ، ولم يتغيروا بناءً على أي شيء فعلته في الكود الخاص بي. ما تسبب في أن الحقول لتبدو جسديا لتغيير الموضع في ClientDataset هو أن المستخدم قد غير ترتيب الأعمدة في DBGRID التي تم إرفاق ClientDataset (من خلال مكون مصدر البيانات ، بالطبع). قمت بتكرار هذا التأثير في Delphi 7 و Delphi 2007 و Delphi 2010.

لقد قمت بإنشاء تطبيق Delphi بسيط للغاية يوضح هذا التأثير. وهو يتكون من نموذج واحد مع DBGRID واحد ، وموارد البيانات ، واثنين من clientdatasets ، واثنين من الأزرار. يشبه معالج الأحداث oncreate هذا النموذج ما يلي

procedure TForm1.FormCreate(Sender: TObject);
begin
  with ClientDataSet1.FieldDefs do
  begin
    Clear;
    Add('StartOfWeek', ftDate);
    Add('Label', ftString, 30);
    Add('Count', ftInteger);
    Add('Active', ftBoolean);
  end;
  ClientDataSet1.CreateDataSet;
end;

يحتوي Button1 ، الذي يحمل علامة على Enlude ClientDataset ، على معالج أحداث Onclick التالي.

procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    sl.Add('The Structure of ' + ClientDataSet1.Name);
    sl.Add('- - - - - - - - - - - - - - - - - ');
    for i := 0 to ClientDataSet1.FieldCount - 1 do
      sl.Add(ClientDataSet1.Fields[i].FieldName);
    ShowMessage(sl.Text);
  finally
    sl.Free;
  end;
end;

لإظهار تأثير الحقل المتحرك ، قم بتشغيل هذا التطبيق وانقر فوق الزر المسمى ClientDataset بنية. يجب أن ترى شيئًا كهذا يظهر هنا:

The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - - 
StartOfWeek
Label
Count
Active

بعد ذلك ، اسحب أعمدة DBGRID لإعادة ترتيب ترتيب عرض الحقول. انقر فوق الزر "إظهار ClientDatAset بنية" مرة أخرى. هذه المرة سترى شيئًا مشابهًا لتلك الموضحة هنا:

The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - - 
Label
StartOfWeek
Active
Count

ما هو ملحوظ في هذا المثال هو أن أعمدة DBGRID يتم نقلها ، ولكن هناك تأثير واضح على موضع الحقول في ClientDataset ، بحيث كان الحقل الموجود في موضع clientDataset.field [0] في واحد النقطة ليست بالضرورة هناك لحظات في وقت لاحق. ولسوء الحظ ، هذه ليست مشكلة ClientDataset بشكل واضح. لقد أجريت نفس الاختبار مع TTABLES المستندة إلى BDE و adotables المستندة إلى ADO وحصلت على نفس التأثير.

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

إن أبسط ، على الرغم من أنه ليس من الضروري أن تكون الطريقة المفضلة لتجنب هذه المشكلة هي منع المستخدم من إعادة ترتيب الحقول في DBGRID. يمكن القيام بذلك عن طريق إزالة علامة DgresizeColumn من خاصية خيارات DBGrid. على الرغم من أن هذا النهج فعال ، إلا أنه يلغي خيار عرض يحتمل أن يكون ذا قيمة ، من منظور المستخدم. علاوة على ذلك ، فإن إزالة هذا العلامة لا يقيد إعادة ترتيب العمود فحسب ، بل يمنع تغيير حجم العمود. (لمعرفة كيفية الحد من إعادة ترتيب العمود دون إزالة خيار تغيير حجم العمود ، انظر http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)

الحل الثاني هو تجنب الإشارة إلى حقول مجموعة البيانات بناءً على موضعها الحرفي (لأن هذا هو جوهر المشكلة). من خلال الكلمات ، إذا كنت بحاجة إلى الرجوع إلى حقل العد ، فلا تستخدم DataSet.fields [2]. طالما أنك تعرف اسم الحقل ، يمكنك استخدام شيء مثل dataset.fieldbyname ('count').

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

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

var
  CountField: TIntegerField;
  Sum: Integer;
begin
  Sum := 0;
  CountField := TIntegerField(ClientDataSet1.FieldByName('Count'));
  ClientDataSet1.DisableControls;  //assuming we're attached to a DBGrid
  try
    ClientDataSet1.First;
    while not ClientDataSet1.EOF do
    begin
      Sum := Sum + CountField.AsInteger;
      ClientDataSet1.Next;
    end;
  finally
    ClientDataSet1.EnableControls;
  end;

هناك حل ثالث ، ولكن هذا متاح فقط عندما تكون مجموعة البيانات الخاصة بك عبارة عن عميل dataSet ، مثل الحدث في مثالي الأصلي. في تلك المواقف ، يمكنك إنشاء استنساخ من ClientDataset الأصلي ، وسيكون له الهيكل الأصلي. نتيجةً لذلك ، لا يزال هناك أي حقل في وضع Zeroeth في هذا الموقف ، بغض النظر عن ما فعله المستخدم مع DBGrid الذي يعرض بيانات ClientDataSets.

يظهر هذا في الكود التالي ، والذي يرتبط بمعالج أحداث OnClick في بنية ClientDataset ClientDataset.

procedure TForm1.Button2Click(Sender: TObject);
var
  sl: TStringList;
  i: Integer;
  CloneClientDataSet: TClientDataSet;
begin
  CloneClientDataSet := TClientDataSet.Create(nil);
  try
    CloneClientDataSet.CloneCursor(ClientDataSet1, True);
    sl := TStringList.Create;
    try
      sl.Add('The Structure of ' + CloneClientDataSet.Name);
      sl.Add('- - - - - - - - - - - - - - - - - ');
      for i := 0 to CloneClientDataSet.FieldCount - 1 do
        sl.Add(CloneClientDataSet.Fields[i].FieldName);
      ShowMessage(sl.Text);
    finally
      sl.Free;
    end;
  finally
    CloneClientDataSet.Free;
  end;
end;

إذا قمت بتشغيل هذا المشروع وانقر فوق الزر المسمى بنية ClientDataset ClientDataset ، فستحصل دائمًا على الهيكل الحقيقي لـ ClientDataset ، كما هو موضح هنا

The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - - 
StartOfWeek
Label
Count
Active

إضافة:

من المهم أن نلاحظ أن الهيكل الفعلي للبيانات الأساسية لا يتأثر. على وجه التحديد ، إذا ، بعد تغيير ترتيب الأعمدة في DBGRID ، يمكنك استدعاء طريقة savetofile لـ ClientDataset ، فإن الهيكل المحفوظ هو الهيكل الأصلي (الداخلي الحقيقي). أيضًا ، إذا قمت بنسخ خاصية البيانات الخاصة بـ ClientDatAset إلى آخر ، فإن ClientDataset الوجهة تعرض أيضًا الهيكل الحقيقي (الذي يشبه التأثير الذي لوحظ عند استنساخ ClientDataset المصدر).

وبالمثل ، فإن التغييرات في أوامر العمود من DBGrids المرتبطة بمجموعات البيانات التي تم اختبارها الأخرى ، بما في ذلك TTable و Odotable ، لا تؤثر فعليًا على بنية الجداول الأساسية. على سبيل المثال ، لا يغير TTable الذي يعرض البيانات من جدول العميل. DB Paradox Table الذي يشحن مع Delphi في الواقع بنية هذا الجدول (ولا تتوقع ذلك).

ما يمكننا أن نستنتجه من هذه الملاحظات هو أن الهيكل الداخلي لمجموعة البيانات نفسها لا يزال سليما. نتيجة لذلك ، يجب أن أفترض أن هناك تمثيلًا ثانويًا لهيكل مجموعة البيانات في مكان ما. ويجب أن يكون إما مرتبطًا بمجموعة البيانات (التي يبدو أنها مبالغة ، حيث لا تحتاج جميع استخدامات مجموعة البيانات إلى هذا) ، المرتبطة بـ DBGRID (والتي تكون أكثر منطقية لأن DBGRID تستخدم هذه الميزة ، ولكنها ليست كذلك بدعم من ملاحظة أن إعادة ترتيب Tfield يبدو أنها مستمرة مع مجموعة البيانات نفسها) ، أو شيء آخر.

بديل آخر هو أن التأثير يرتبط بـ tgriddatalink ، وهو الفئة التي تعطي عناصر تحكم متعددة الإدراك (مثل DBGrids) وعي البيانات. ومع ذلك ، أميل إلى رفض هذا التفسير أيضًا ، نظرًا لأن هذه الفئة مرتبطة بالشبكة ، وليس مجموعة البيانات ، مرة أخرى لأن التأثير يبدو مستمرًا مع فئات مجموعة البيانات نفسها.

وهو ما يعيدني إلى السؤال الأصلي. هل هذا التأثير شيء داخلي لفئة tdataset ، أو قطعة أثرية من tdbgrid ، أو أي شيء آخر؟

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

هل كانت مفيدة؟

المحلول

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

clientDataset1.fields [0] .index: = 1 ؛

سوف يتسبب في إخراج زر "show clientDataset structure" لتغييره وفقًا لذلك ، إما أن هناك شبكة أم لا. توثيق الدول tfield.index ؛

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

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


الكود الذي يسبب هذا في tcolumn.setIndex. TCUSTOMDBGRID.COLUMNMOVED يضع فهرسًا جديدًا للعمود الذي تم نقله و tcolumn.setIndex يعين الفهرس الجديد لحقل هذا العمود.

procedure TColumn.SetIndex(Value: Integer);
[...]
        if (Col <> nil) then
        begin
          Fld := Col.Field;
          if Assigned(Fld) then
            Field.Index := Fld.Index;
        end;
[...]

نصائح أخرى

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

إن الحل الذي يقودني إليه حل Wodzu هو FieldByNumber ، وهو وسيلة لخاصية الحقول. هناك جانبان مثيران للاهتمام لاستخدام FieldByNumber. أولاً ، يجب أن تؤهل مرجعها مع خاصية Fields الخاصة بمجموعة البيانات الخاصة بك. ثانياً ، على عكس مجموعة الحقول ، التي تأخذ مفهرسًا قائمًا على الصفر ، فإن FieldByNumber هي طريقة تأخذ معلمة واحدة للإشارة إلى موضع tfield الذي تريد الإشارة إليه.

فيما يلي إصدار محدث من معالج أحداث Button1 الذي نشرته في سؤالي الأصلي. يستخدم هذا الإصدار FieldByNumber.

procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    sl.Add('The Structure of ' + ClientDataSet1.Name +
      ' using FieldByNumber');
    sl.Add('- - - - - - - - - - - - - - - - - ');
    for i := 0 to ClientDataSet1.FieldCount - 1 do
      sl.Add(ClientDataSet1.Fields.FieldByNumber(i + 1).FieldName);
    ShowMessage(sl.Text);
  finally
    sl.Free;
  end;
end;

بالنسبة لمشروع العينة ، ينتج هذا الرمز الإخراج التالي ، بغض النظر عن اتجاه الأعمدة في DBGrid المرتبطة:

The Structure of ClientDataSet1 using FieldByNumber
- - - - - - - - - - - - - - - - - 
StartOfWeek
Label
Count
Active

للتكرار ، لاحظ أن الإشارة إلى Tfield الأساسية تتطلب أن تكون FieldByNumber مؤهلة مع الإشارة إلى الحقول. علاوة على ذلك ، يجب أن تقع المعلمة الخاصة بهذه الطريقة في نطاق 1 إلى DataSet.FieldCount. نتيجة لذلك ، للإشارة إلى الحقل الأول في مجموعة البيانات ، يمكنك استخدام الكود التالي:

ClientDataSet1.Fields.FieldByNumber(1)

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

TBlobField(MyDataSet.Fields.FieldByNumber(6)).SaveToFile('c:\mypic.jpg');

لاحظ أنني لا أقترح أنه يجب عليك الرجوع إلى TFields في مجموعة بيانات باستخدام INTEGER DIRCHERALS. شخصياً ، يكون استخدام متغير Tfield الذي يتم تهيئته من خلال استدعاء لمرة واحدة إلى FieldByName أكثر قابلية للقراءة ، وهو محصن من التغييرات في الترتيب المادي لهيكل الجدول (وإن لم يكن محصنًا من التغييرات في أسماء حقولك!).

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

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

إليكم كيف يجب الرجوع إليها:

qry.Recordset.Fields.Item[0].Value

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

اختبر هذا وأخبرني ما هي النتيجة. عملت معي.

يحرر:

بالطبع سوف يعمل فقط لمكونات ADO ، وليس ل tclientdataset ...

EDIT2:

كاري لا أعرف ما إذا كان هذا هو إجابة لسؤالك ، ومع ذلك فقد دفعت الناس في منتديات Embarcadero وأعطاني واين نيدري إجابة مفصلة تمامًا حول كل هذه الحقول.

لجعل القصة الطويلة أقصر: إذا قمت بتحديد الأعمدة الخاصة بك في TDBGrid بشكل صريح ، فإن فهارس الحقل لا تتحرك! لديك المزيد من المعنى الآن ، أليس كذلك؟

اقرأ الموضوع الكامل هنا:https://forums.embarcadero.com/post! Reply.jspa؟messageid=197287

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top