[Solved]Using a description instead of an enumerated type lazarus / 31-LCL ---------------------------------------------------------------------- Knipfty · 2012-08-27 16:53 Hi, I have an enumerated type defined as Code: [Select]Type TBookReverse = (brNew, brReverse); In the DB table, the enumerated values are stored as integers, so using the type above that would be a 0 or 1. On a form I have a DBGrid that is connected to the TTable object.  The DBGrid's column shows the 0 or 1, however, I would like it to display 'New' or 'Reverse' instead. I looked at the OnGetText/OnSetText event handlers that are associated with TField, however, the documentation states:QuoteIt is not allowed to change the state of the dataset or the contents of the field during the execution of this event handler. Is there another way to do this?  The user is not allowed to edit the data directly on the DBGrid. ---------------------------------------------------------------------- ezlage · 2012-08-27 16:58 You can do this way: Code: [Select]var   t:string; begin   WriteStr(t,brNew); //same of t:='brNew'; end;[code] ---------------------------------------------------------------------- Knipfty · 2012-08-27 17:16 Hi ezlage, I'm trying to display 'New' in cell in a DBGrid.  I don't understand how a Writeln will work or where to put it if it does. If I could use the onGetText event, I would expect the code to look something like this: OnGetTExt procedure definition Code: [Select]   procedure enumBR(Sender: TField; var aText: string; DisplayText: boolean);    begin      case integer(aText) of        0: aText := 'New';        1: aText := 'Reverse'      end    end; And then later on its assigned Code: [Select]  AdsTable1.FieldByName('EntryType').OnGetText:=@enumBR; BTW, the above code does not compile.  I get the following error msg: Quotemain.pas(239,49) Error: Incompatible types: got "
" expected ""   So, I'm not even sure how to get OnGetText to work. ---------------------------------------------------------------------- ludob · 2012-08-27 17:24 OnGetText is the correct way of changing the text displayed in a dbcontrol. You don't change the dataset when modifying the text displayed. In your case you would do something like Code: [Select]procedure TForm1.SQLQuery1idGetText(Sender: TField; var aText: string;   DisplayText: Boolean); begin   writestr(aText,TBookReverse(Sender.AsInteger)); end;This will display 'brNew' or 'brReverse'. If you want to to customise the string do something like: Code: [Select]const   REVSTRINGS:array [TBookReverse] of string = ('New','Reverse'); ... aText:=REVSTRINGS[TBookReverse(Sender.AsInteger)]; ---------------------------------------------------------------------- taazz · 2012-08-27 17:30 Quote from: Knipfty on August 27, 2012, 07:16:49 pmHi ezlage, I'm trying to display 'New' in cell in a DBGrid.  I don't understand how a Writeln will work or where to put it if it does. If I could use the onGetText event, I would expect the code to look something like this: OnGetTExt procedure definition Code: [Select]   procedure enumBR(Sender: TField; var aText: string; DisplayText: boolean);    begin      case integer(aText) of        0: aText := 'New';        1: aText := 'Reverse'      end    end; type casting a string to an integer does not return the value of the string as integer you need to use strtoint function or simple   if aText = '0' then atext := 'New' also although you are not allowed to change the state of the dataset or fields data it doesn't mean you can read the data from inside this event eg   if Sender.AsInteger = 0 then atext := 'New'; Quote from: Knipfty on August 27, 2012, 07:16:49 pmAnd then later on its assigned Code: [Select]  AdsTable1.FieldByName('EntryType').OnGetText:=@enumBR; BTW, the above code does not compile.  I get the following error msg: Quotemain.pas(239,49) Error: Incompatible types: got "
" expected ""   So, I'm not even sure how to get OnGetText to work. well this error tells me that the enumBR procedure is not a method of an object or it has been declared with a call type other than register eg   procedure enumBR(Sender: TField; var aText: string; DisplayText: boolean); cdecl; from your code I assume that the procedure is not part of an object. ---------------------------------------------------------------------- ezlage · 2012-08-27 17:38 Sorry, I didn't understood well. WriteStr(not WriteLn) stores name of enumerated value in a var as string. So, your field must be ftString. But, my suggestion maybe not helpful... ---------------------------------------------------------------------- Knipfty · 2012-08-27 18:02 Working off if taazz and ludob, I created a procedure as part of teh TForm1 object: Code: [Select]procedure TForm1.AdsTable1OnGetText(Sender: TField; var aText: string; DisplayText: boolean); begin   case Sender.AsInteger of     0: aText := 'New';     1: aText := 'Reverse';     otherwise aText := 'undefined'   end end; That seems to work as far as the compiler is concerned. But now when I try to assign the procedure to the event handler with this code in the TForm1.ForShow method: (And where should I make this assignment?) Code: [Select]AdsTable1.FieldByName('EntryType').OnGetText:=@TForm1.Ad sTable1GetText; I ge tthe following compiler error msg Quotemain.pas(284,59) Error: identifier idents no member "AdsTable1GetText" ---------------------------------------------------------------------- typo · 2012-08-27 18:09 You have mistyped the method name: AdsTable1OnGetText. ---------------------------------------------------------------------- Knipfty · 2012-08-27 18:13 Well now, that's embarrassing... And it now compiles...  I'll test this all when I get home this evening.  Thanks ---------------------------------------------------------------------- taazz · 2012-08-27 18:21 Quote from: Knipfty on August 27, 2012, 08:02:04 pmCode: [Select]AdsTa ble1.FieldByName('EntryType').OnGetText:=@TForm1.AdsTable1GetText; I ge tthe following compiler error msg Quotemain.pas(284,59) Error: identifier idents no member "AdsTable1GetText" You do know that you can't use the class to assign a non class method to an event right? You need to to use a created object (or the variable that hold the reference to that object) in this case I assume that form1 is the global variable of this test case and if the code above is part of the form code then self would be a better choice. ---------------------------------------------------------------------- Knipfty · 2012-08-27 18:41 Hi taazz, In my test code that I set up to figure this out, I used the TForm1.FormShow event handler Code: [Select]procedure TForm1.FormShow(Sender: TObject); begin     AdsTable1.FieldByName('EntryType').OnGetText:=@TForm1.AdsTable1OnG etText; end; Is this incorrect? Are you suggesting: Code: [Select]procedure TForm1.FormShow(Sender: TObject); begin     AdsTable1.FieldByName('EntryType').OnGetText:=@Self.AdsTable1OnGet Text; end; ---------------------------------------------------------------------- Knipfty · 2012-08-27 18:52 One more question: Do I need to also create an OnSetText event handler?  Such as Code: [Select]procedure TForm1.AdsTable1OnSetText(Sender: TField; var aText: string; DisplayText: boolean); begin   case Sender.AsString of     'New':     aText := '0';     'Reverse': aText := '1'   end end; The user cannot edit directly in the DBGrid, so I don;t know if its needed.  I believe I have the DBGrid set to nto allow editting. Thanks ---------------------------------------------------------------------- taazz · 2012-08-27 19:48 Quote from: Knipfty on August 27, 2012, 08:41:08 pmHi taazz, In my test code that I set up to figure this out, I used the TForm1.FormShow event handler Code: [Select]procedure TForm1.FormShow(Sender: TObject); begin     AdsTable1.FieldByName('EntryType').OnGetText:=@TForm1.AdsTable1OnG etText; end; Is this incorrect? I am not sure how FPC handles this kind of things but in my mind Tform1 is a class not an object I do not know if an object has been created for that class unless I use a variable that has a reference to an object of TForm1 class. This means that it needs extra checking in order to make sure that everything is in place or you will get various SIGSEGV errors. Quote from: Knipfty on August 27, 2012, 08:41:08 pmAre you suggesting: Code: [Select]procedure TForm1.FormShow(Sender: TObject); begin     AdsTable1.FieldByName('EntryType').OnGetText:=@Self.AdsTable1OnGet Text; end; although self in this context is not needed it makes more sense to me and does not raise any red flags when I read it. So yes its a lot better from my point of view Quote from: Knipfty on August 27, 2012, 08:52:32 pmOne more question: Do I need to also create an OnSetText event handler?  Such as Code: [Select]procedure TForm1.AdsTable1OnSetText(Sender: TField; var aText: string; DisplayText: boolean); begin   case Sender.AsString of     'New':     aText := '0';     'Reverse': aText := '1'   end end; The user cannot edit directly in the DBGrid, so I don;t know if its needed.  I believe I have the DBGrid set to nto allow editting. Thanks You do create a OnSetText event if you want to allow the user to enter something other than the expected value for example if you want the end user to be able to type in New, N or Reverse, R and translate it to 0 or 1 then you use the OnSetText event. If you use a DBcomboBox for the selection then you ignore the onsetText and work with the combobox it self. In any case when the OnSetText event is fired Sender.asstring will contain the existing value not the new one so you can't use it to check for the new value in this case you need to use the aText variable. eg Code: [Select]procedure TForm1.AdsTable1OnSetText(Sender: TField; var aText: string; DisplayText: boolean); begin   case UpperCase(aText) of     'NEW', 'N'     : aText := '0';     'REVERSE', 'R' : aText := '1';   end; end; In my defense I have only used those 2 events on password fields where I encrypt and decrypt them on the fly on the user edit form and the ongettext only to show some data inside a grid for a memo fields. Regards. ---------------------------------------------------------------------- Knipfty · 2012-08-27 20:06 Hi taazz, I see what you are saying about TForm1 vs. Self.  I've changed the code to 'Self' and will test it out this evening when I am home.  (I don't have my project here at work to play with  ). Since the user is not going to be entering any data on the DBGrid, I will skip the OnSetText for now.  I have them coded, just in case, and can bring them in pretty quickly. And finally, no need to get defensive, it compiles in my little test environment and that was a major hurdle.  Now I can at least test it and see what happens. From where I am sitting, if you want to make good use of Enumerated types, then you need a good way to make it transparent all the way thru to the DB.  So hopefully this will work and I can expand my usage of them. I really appreciate your (really everyone's) help and will post what I find over the next couple of days, Knipfty ---------------------------------------------------------------------- taazz · 2012-08-27 20:19 Enumerated types are great and you can use a number of visual components to help the user choose (combo boxes, radio groups etc) which can be automated using rtti. In most cases I prefer a master detail relationship where I can define any number of enumeration after the application has been written and delivered but this makes the code a lot trickier to write and test for various conditions. Glad I could be of help. ---------------------------------------------------------------------- Knipfty · 2012-08-27 20:38 I've used them with DBComboLookup and DBRadioGroups where it was straight forward and natural (at least to me).  This is a bit more work on a DBGrid. I actually have a similar requirement on creating a fee schedule in my application, but have to leave it to the user to define at a later date.  Not sure yet how I will implement it, but it could be via enumerate types if I get this working.  Or perhaps a simple DB look up. Thanks again Knipfty ---------------------------------------------------------------------- Knipfty · 2012-08-28 01:24 OK, here is what I ended doing: 1.  I created two TField OnGetText event handlers (one for each column I wanted to affect) Code: [Select]procedure TTransRevenueBrowser.AdsRevenueTableBROnGetText(Sender: TField; var aText: string; DisplayText: boolean); begin   aText := BookReverseDesciption(TBookReverse(Sender.AsInteger)) end; procedure TTransRevenueBrowser.AdsRevenueTableRTOnGetText(Sender: TField; var aText: string; DisplayText: boolean); begin   aText := RevenueDesciption(TRevenue(Sender.AsInteger)) end; 2.  Then in the table's data source DataChange event handler I added the following: Code: [Select]procedure TTransRevenueBrowser.DatasourceRevenueDataChange(Sender: TObject;   Field: TField); begin   AdsRevenueTable.FieldByName('EntryType').OnGetText:=@AdsRevenueTable BROnGetText;   AdsRevenueTable.FieldByName('RevenueType').OnGetText:=@AdsRevenueTab leRTOnGetText end; I tried the above code in a number of event handlers, this one seems to work in all cases.  I am open to other suggestions. In the TForm object definition I added: Code: [Select]    procedure AdsRevenueTableBROnGetText(Sender: TField; var aText: string; DisplayText: boolean);     procedure AdsRevenueTableRTOnGetText(Sender: TField; var aText: string; DisplayText: boolean); And that was it.  The code replaces the enumerated values with the desired strings.  I suspect, but can't figure out, that I should be able to use the Sender and/or the Field in step two rather than the code I used.  TField is always nil.  My guess is that its because But it is working. Thanks everyone for your help! Knipfty ---------------------------------------------------------------------- taazz · 2012-08-28 02:00 I wouldn't use the datasource.datachange event for that what you can use depends heavily on how your application is designed. Why you do not use the designer to select the events on the fields and you are assigning them through code? ---------------------------------------------------------------------- Knipfty · 2012-08-28 08:54 Where?  I couldn't find any other place that worked.  Certainly not on the field level. ---------------------------------------------------------------------- avra · 2012-08-28 09:07 Knipfty, you do not need to assign OnGetText when ever data is changed. It is enough if you do it just once, like after creation. Just think about it - the same value is reassigned over and over again. In this particular case using Form Designer and Object Inspector is more productive then assigning events programmaticaly as you do. Just click on your Dataset component, and in Object Inspector choose Events tab and find OnDataChange. Double clicking to it's dropdown box is a standard way to assign events in IDE. Also, if you have zillion rows to show you will find that FieldByName() is very slow, and you use it twice for each row. If you want to speed it up then you can also do it just once and use the result where ever needed. For more info take a look at this thread: http://stackoverflow.com/questions/4765931/why-is-my-code-so-slow. ---------------------------------------------------------------------- taazz · 2012-08-28 09:22 In general if the dataset is on the form where the event is written and the fields are persistent then use the object inspector to select the events directly from the fields. if the dataset is on the same form but with out persistent fields use the datasets afteropen event. if the dataset is not on the same form and there are persistent fields then use the forms onCreate event making sure that the container that holds the dataset is created before the form. if the dataset is not on the same form and there  are no persistent fields then things start to get interesting, there are a number of things here as well is the datasource (not dataset) on the form or not, what is the dataset's container? Well as you can see there are to many variables for me to give a specific answer so it would be beneficial to both of us a small test project which duplicates your design as close as possible. Regards ---------------------------------------------------------------------- Knipfty · 2012-08-28 10:33 Hi taazz, No need for a sample project.  The dataset is on the same form as the DBGrid AND the fields are not persistent.  I'll recode this evening and see what I end up with.  DO I use the same line I currently have? Code: [Select]procedure TTransRevenueBrowser.DatasourceRevenueDataChange(Sender: TObject;   Field: TField); begin   AdsRevenueTable.FieldByName('EntryType').OnGetText:=@AdsRevenueTable BROnGetText;   AdsRevenueTable.FieldByName('RevenueType').OnGetText:=@AdsRevenueTab leRTOnGetText end; Just with the different procedure definition. Or, do I somehow use TField and Sender?  I played with these in other event handlers, but could not get them to work. Knipfty ---------------------------------------------------------------------- taazz · 2012-08-28 17:15 To be sure you need to double click the AfterOpen event editor on your object inspector but I think it should create the following procedure for you Code: [Select]procedure TForm1.AdsRevenueTableAfterOpen(DataSet: TDataSet); begin   AdsRevenueTable.FieldByName('EntryType').OnGetText:=@AdsRevenueTable BROnGetText;   AdsRevenueTable.FieldByName('RevenueType').OnGetText:=@AdsRevenueTab leRTOnGetText end; In the rare occasion where the dataset sql or underline table is changed you will get a sigsegv error if the fields are not found in the result set. I usually code it like this Code: [Select]procedure TForm1.AdsRevenueTableAfterOpen(DataSet: TDataSet); var   FldType1, fldType2 : TField; begin   FldType1 := AdsRevenueTable.FieldByName('EntryType');   FldType2 := AdsRevenueTable.FieldByName('RevenueType');   if assigned(FldType1) then  FldType1.OnGetText:=@AdsRevenueTableBROnGetText   else Showmessage('Field < EntryType > Not found in the underline dataset');   if Assigned(FldType2) then FldType2.OnGetText:=@AdsRevenueTableRTOnGetText   else Showmessage('Field < RevenueType > Not found in the underline dataset'); end; well not exactly the message is a resource string, I'm using format and I"m raising a exception instead of show message but you get the main point. ---------------------------------------------------------------------- Knipfty · 2012-08-28 17:24 Hi taazz, Instead of hard coding the table name, could I use DataSet?  So something like this" Code: [Select]procedure TForm1.AdsRevenueTableAfterOpen(DataSet: TDataSet); begin   DataSet.FieldByName('EntryType').OnGetText:=@AdsRevenueTableBROnGetT ext;   DataSet.FieldByName('RevenueType').OnGetText:=@AdsRevenueTableRTOnGe tText end; I do like you second example and will probably follow it. Thanks Knipfty ---------------------------------------------------------------------- taazz · 2012-08-28 19:03 As long as you are sure that the dataset you attach this event has the required fields I see no problem to use the Dataset instead of the table it self. ---------------------------------------------------------------------- Knipfty · 2012-08-28 19:09 Cool.  I'll play with it this evening and see what happens. Thanks, your help is greatly appreciated Knipfty ---------------------------------------------------------------------- Knipfty · 2012-08-28 23:38 Hi taazz, worked like a charm! Code: [Select]procedure TTransRevenueBrowser.AdsRevenueTableAfterOpen(DataSet: TDataSet); begin   AdsRevenueTable.FieldByName('EntryType').OnGetText:=@AdsRevenueTable BROnGetText;   AdsRevenueTable.FieldByName('RevenueType').OnGetText:=@AdsRevenueTab leRTOnGetText end; Thanks! ---------------------------------------------------------------------- Knipfty · 2012-08-30 13:30 avra, I finally had a chance to look at the link you provided.  THANKS!  Gave me something to think about.  It would appear that FieldByName loops thru the table structure until it finds the field I am looking for.  Using Field[some index value] would be more efficient program performance wise, but less efficient my time wise as my table structures are still changing.  Ugh! I would agree though that FieldByName should be banished from loops. Knipfty