How to identify which control is Sender? - delphi

I use common procedures for all Virtual Treeviews (TVirtualStringTree) so I only have 1 code to maintain, like for OnClick I use Common_VST_OnClick which all VST controls has set:
procedure TForm1.Common_VST_OnClick(Sender: TObject);
And to execute code based on which VST calls this on click procedure, I realized I use many different ways to recognize which control is Sender:
if Sender = VST1 then
if Sender.Name = VST1.Name then
if TVirtualStringTree(Sender) = VST1 then
if TVirtualStringTree(Sender).Name = VST1.Name then
if TVirtualStringTree(Sender).Name = 'VST1' then
The last is probably worst as the name is hardcoded, so I'm trying to only use 1 type of identification, in all procedures.
What is the best way to identify which control is Sender?

You should prefer the test that uses object identity. That is, the first test in your question:
if Sender = VST1 then
An object reference such as Sender or VST1 is the address of the object. If two such addresses are equal, then the references point to same object. And vice versa.
The tests based on control name can work but are brittle. It is possible for multiple controls to have the same name. It is possible to change the control name but not update all the uses of the name in the program.
As for the type casting option
if TVirtualStringTree(Sender) = VST1 then
the type cast has no impact on object identity and so is needless. Don't ever type cast an operand to an object identity test since doing so is spurious.

Related

ABAP: how to compare three internal tables

I've recently started an apprenticeship and I've received a small project to help checking tables.
The request is that I am supposed to write a report that compares a table to our profit center's standard hierarchy and tells you which entries are the same and which ones are not.
Now, I've only started getting into ABAP and while I've had SQL prior, i'm not exactly good in it. My colleagues are nice but I fear that I'd look like an idiot if I kept asking them how to do this.
What i've done so far is giving the table some records to check in our development system, selecting all records in it and also selecting everything I need from SETLEAF and SETNODE.
SELECT * FROM Y_TABLE
INTO TABLE #DATA(lt_compare).
SELECTION-SCREEN BEGIN OF BLOCK set_leaf.
CONSTANTS:
SET_CLASS TYPE SETLEAF-SETCLASS VALUE '0102',
SUB_CLASS TYPE SETLEAF-SUBCLASS VALUE 'D100'.
PARAMETERS:
lv_pcnr TYPE SETLEAF-VALFROM,
setname TYPE SETLEAF-SETNAME.
SELECTION-SCREEN END OF BLOCK set_leaf.
TYPES:
BEGIN OF lt_ausgabe,
SETCLASS TYPE SETLEAF-SETCLASS,
SUBCLASS TYPE SETLEAF-SUBCLASS,
VALFROM TYPE SETLEAF-VALFROM,
SETNAME TYPE SETLEAF-SETNAME,
END OF lt_ausgabe.
DATA:
gt_setleaf TYPE STANDARD TABLE OF lt_ausgabe,
gt_setnode TYPE STANDARD TABLE OF lt_ausgabe.
SETNODE-SUBSETNAME = SETLEAF-SETNAME.
SETNODE-SETNAME = SETNODE-SUBSETNAME.
FORM build_table_setleaf.
SELECT *
FROM SETLEAF
INTO CORRESPONDING FIELDS OF TABLE #gt_setleaf
WHERE SETCLASS = #set_class AND SUBCLASS = #sub_class AND VALFROM = #lv_pcnr AND SETNAME = #setname.
ENDFORM.
FORM build_table_setnode.
SELECT *
FROM SETNODE
INTO CORRESPONDING FIELDS OF TABLE #gt_setnode
WHERE SETCLASS = #set_class AND SUBCLASS = #sub_class AND SUBSETNAME = #setname.
ENDFORM.
I've changed the table name and the constant values because I don't know how much of them I can share.
Now I want to compare these tables and well, do what the request asked for. But I don't really understand how to do this. The code above may not be the way to solve this at all..
I'm sorry in advance if any of this sounds off, I would ask in my own language if.. my people weren't known for being unhelpful and rude when asking code-related stuff.
Thank you in advance.

F# Turning XmlProvider data into Records

I am pulling in some XML data using XmlProvider, and I will be accessing it from C#. As you can't use type provided fields directly from C#, I need create record out of them. I can do this by hand but I believe this should be possible to automate using reflection. If I create record types with the same names and types as the fields in the type provider, I should be able to use something like FSharpValue.MakeRecord(typeof<MyType>,values) where values is an array of objects.
What I don't know is how to get the array of values out of the type provider, and how to handle nested records, for instance:
type Address =
{
Address1 : string
City : string
State : string
}
type Client =
{
Id : int
FullName : string
Address : Address
}
In this case Client contains one Address. Will I need to walk the tree and use MakeRecord on the leaves and work my way up?
If you're willing to hand code the types, why do you need the type provider in the first place?
If you're doing some additional logic on F# side, you'll have no choice but to create the records manually anyway. And if you're not doing anything, you can just use the .NET out of the box serializer (or another library) to create them from xml.

Equality test for a record type with generated fields

If a record type includes generated fields such as an auto-generated id and a timestamp, e.g.:
type UserCreated = {
Id: Guid
Name: string
CreationTime: Instant
}
what is the best way to write unit tests against this? It is not possible to simply assert that the record is equal to any particular value, because it cannot be known in advance what the Id and CreationTime values will be.
Possible solutions:
Make individual assertions for each field
pass a function into the user creation function that handles generation of ids and dates. Unit tests could then inject a stub function that returns pre-determined values. Or indeed make callers pass in an id and timestamp directly.
Use some sort of lenses library
Never auto-generate anything, with the client deciding all fields in advance
Use a custom equality comparer (this doesn't sound like a good idea at all)
Something else?
What is considered the best way to do this?
My approach would indeed be to pass a function into the user creation function that handles generation of ids and dates. If there are too many parameters that you have to pass in, then that's probably a clue that you need to refactor your design.
Here's the domain layer, for example:
// =================
// Domain
// =================
open System
type UserCreated = {
Id: Guid
Name: string
CreationTime: DateTime
}
// the generation functions are passed in
let createCompleteRecord generateGuid generateTime name =
{
Id = generateGuid()
Name = name // add validation?
CreationTime = generateTime()
}
In the application code, you'd use partial application to bake in the generators and create a useful function createRecord
// =================
// Application code
// =================
let autoGenerateGuid() = Guid.NewGuid()
let autoGenerateTime() = DateTime.UtcNow
// use partial application to get a useful version
let createRecord = createCompleteRecord autoGenerateGuid autoGenerateTime
let recForApp = createRecord "myname"
In the test code, you'd use partial application to bake in other generators and create a different function createRecordForTesting
// =================
// Test code
// =================
let explicitGenerateGuid() = Guid.Empty
let explicitGenerateTime() = DateTime.MinValue
// use partial application to get a useful version
let createRecordForTesting = createCompleteRecord explicitGenerateGuid explicitGenerateTime
let recForTest = createRecordForTesting "myname"
Assert.AreEqual(Guid.Empty,recForTest.Id)
Assert.AreEqual("myname",recForTest.Name)
Assert.AreEqual(DateTime.MinValue,recForTest.CreationTime)
and since the "generated" fields now have hard-coded values, you can also test the whole record equality logic too:
let recForTest1 = createRecordForTesting "myname"
let recForTest2 = createRecordForTesting "myname"
Assert.AreEqual(recForTest1,recForTest2)
You don't really test the record type itself - it's just dumb data. What you want to test are functions that operate on that data.
A function is simple to test when it's pure. Both calling Guid.NewGuid() and DateTime.Now, which is what I assume you do for the two problematic fields here, are side effects. So refactoring those two bits out into functions you pass in explicitly as arguments would be the way to go in my book (your bullet point 2). It would make the code simpler to test and a bit more pure (perhaps a bit less readable as well, I guess you need to balance that).
That said - for most test cases that would involve your record you should know the values of those fields when you arrange the test. It's only when you test the function that creates the record 'from nothing' that you don't know them beforehand, and for that case you could just check if the Name field has the expected value (so your bullet point 1 where necessary, comparing records 'as is' everywhere else).
Edit: I don't know if you missed it or not, but one other possibility is to define a dedicated comparison function in the test project, and ensure the 'generated' values are the same in both records before comparing them:
let compareWithoutGenerated (a: UserCreated) (b: UserCreated) =
a = { b with Id = a.Id; CreationTime = a.CreationTime }
And then:
WhateverUnit.Assert.True(compareWithoutGenerated a b)
Obviously this is uglyish, but I would say it's fair game for tests. Arguably there are better ways to do it, but at the very least, this one doesn't inflict test-induced damage on your production code.

Capability error while binding delphi grid wirth stored procedure

I am getting capability error whenever I am trying to set active property tue. I want to bind the grid with the stored procedure that takes one parameter.
If I do it through TQuery How do I specify the fields. I am using wwDBGrid.
MessageMembershipSelectQuery.Params[0].AsString :=
custQuery.FieldByName('cust_code').AsString;
MessageMembershipSelectQuery.Active := True;
Please guide
Replace you Query component by a StoredProc component. Also, check your parameter data type. If it is indeed a string parameter, then it´s Ok to use AsString, but if the parameter has a different data type, like Integer, for instance, then you should assign it´s value by using, for instance, AsInteger.

Strange "A component named TFrm1 already exists" error

I want to let the user to create multiple instances of the same form (let's call it Form1 which is a MDI child form). So I have two procedures like this where I create the forms.
procedure MyProcedure1; // procedure 2 is similar. it also has a var called MyFrm
var MyFrm: TFrm1;
begin
...
MyFrm:= TFrm1.create(MainForm);
MyFrm.BringToFront;
MyFrm.LoadFromFile(someFile);
end;
As you can see MyFrm is local var. This is ok for me as I don't need to programatically access the form after I create it. There is no other global variable named Frm1. In the OnClose event of MyFrm I have Action:= caFree;
What could cause the error above?
A user sent that error. It happened only once and I cannot reproduce it.
Edit:
The error appears in the "MyFrm:= TFrm1.create" line.
Some people suggested that I need to programatically give unique names to my dynamically created forms. I also wondered myself what name a form takes when it is created so I stepped into the code while calling the MyProcedure1 procedure.
Delphi automatically gives unique names like
MyFrm.name= MyFrm, then
MyFrm.name= MyFrm_1,
MyFrm.name= MyFrm_2,
MyFrm.name= MyFrm_3, and so on.
The MyFrm.Name is not altered in LoadFromFile. I have checked (breakpoint) the value of 'MyFrm.Name' at the end of procedure MyProcedure1; after LoadFromFile. The name is unique.
As some people suggested, I override the SetName procedure and checked the name of TMyFrm. Indeed each form gets a unique name.
procedure TMyFrm.SetName(const Value: TComponentName);
begin
ShowMessage(Value);
inherited;
end;
I have many forms in this app but only the MainForm is auto-created.
I don't use threads. Anyway this will not be relevant since the forms are created by user (so multi-threading is irrelevant unless the user can create 2 forms at the same time).
Giving MainForm as the Owner in TFrm1.Create will include the newly created form in the components list of MainForm. A component ensures that this list doesn't contain any two components with the same non-empty name (otherwise FindComponent won't work). This mechanism also works when a component changes its name.
As long as you don't specify the name in TFrm1.Create it is most likely that it is set by the LoadFromFile method, which means that you don't have much influence on the name unless you change the file's content.
A valid workaround is to create the form with nil as Owner, load the form from the file, change the name to a unique value or to an empty string and finally call MainForm.InsertComponent.
procedure MyProcedure1;
var MyFrm: TFrm1;
begin
...
MyFrm:= TFrm1.create(nil);
MyFrm.BringToFront;
MyFrm.LoadFromFile(someFile);
MyFrm.Name := ''; // or some unique name
MainForm.InsertComponent(MyFrm);
end;
The message is caused because each form must be uniquely named.
When you create a form twice, you need to ensure each instance has a unique name, or set the Name to an empty string. The latter also is the trick when using multiple instances of a data module, so that the automatic linking of data-aware controls does not end up always using the first instance.
Add
MyFrm.Name := MyFrm.Name + <something unique>;
MyFrm.Name := '';
after the Create call and you should be fine
MyFrm.Name is the same for both instances...
Make sure than MyFrm.Name is unique...
As far as my exploration along this line, yes the problem of "already exists" stems from having intances of the editor with the same value for the Name property. As another work around do not visually create the editor(s). Create a new component based on TForm/TFrame/TPanel for the editor(s) you want the user to be able to create multiple instances of. You will have to hand code the creation & deletion of any sub-controls, Setting their properties within your code and assigning values - anything from V_Btn = new TBitBtn(this), V_Btn->Color = clTeal, to V_Btn->OnClick = Close_The_Window. BUT NEVER assign a value to the Name property of any component in the new class and do not set the Name property of the editor once you have created an instance of the editor. Treat the Name property for editor as if it did not exist. After you have created the class and added it to your project the following is valid :
TMyeditor* Editor_01 = new TMyeditor(Main_Form);
TMyeditor* Editor_02 = new TMyeditor(Main_Form);
Editor_01->Parent = Tab_Sheet_Addresses;
Editor_02->Parent = Tab_Sheet_Billing;
The more complex the design concept for your editor the more effort you will undergoe to code the class. However this approach will resolve the "already exists" error.
End of answer.
The following is tangental to the original question as it is an extension of what you may want to further do with your code & I write it to help you along should it be the case. The following allows you to efficiently store/retrieve the editor(s) and its published properties such as position on the user's screen, themes, etc. If you've gone the above route, add the following :
void RegisterClassesWithStreamingSystem(void)
{
// Make sure that as part of the startup
// code TMyEditor is registered
// with the streaming system.
#pragma startup RegisterClassesWithStreamingSystem
Classes::RegisterClass(__classid(TMyEditor));
}
You can now ComponentToString <---> StringToComponent[*1] the editor(s).
You can now create a simple database of each editor saving it [*2] and re-creating the editor(s) at runtime. Saving & Recreating is almost entirely done by the TReader/TWriter objects.
{It is worth while to read about TReader/TWriter which is included in the Delphi help file}
[ Presupposing you have an instances of TMyEditor you want to save called Editor_01 & Editor_02 and
you've created the dataset and assigned it to a TClientDataSet named "CDS" ]
//How to write the Editors
String_Version_Of_Editor = ComponentToString(Editor_01);
CDS->Insert();
CDS->FieldByName("Data")->AsString = String_Version_Of_Editor;
CDS->Post();
String_Version_Of_Editor = ComponentToString(Editor_02);
CDS->Insert();
CDS->FieldByName("Data")->AsString = String_Version_Of_Editor;
CDS->Post();
//How to read, create an instance of, set the Owner of
//(allowing for automatic destruction/deletion
// if desired, Vis-à-vis Let the compiler/runtime package handle that),
//& setting the form's Parent
AnsiString String_Version_Of_Editor;
TWinControl* New_Editor;
String_Version_Of_Editor = CDS->FieldByName("Data")->AsString;
//The next line creates/constructs the new editor
New_Editor = StringToComponent(String_Version_Of_Editor);
//The next line sets the new editor's Owner to Main_Form
//It also assigns Main_Form the responsibility of object cleanup
Main_Form->Insert(New_Editor);
//The next line sets the Editor's Parent causing it to be part of the
//displayed user interface (it has been invisble since creation)
New_Editor->Parent = Tab_Sheet_Addresses;
//Move on to the next editor;
CDS->Next();
String_Version_Of_Editor = CDS->FieldByName("Data")->AsString;
New_Editor = StringToComponent(String_Version_Of_Editor);
Main_Form->Insert(New_Editor);
New_Editor->Parent = Tab_Sheet_Billing;
People who read the above who are astute will have noted that in the above code the New_Editor is of type TWincontrol not TMyEditor - though it likely should have been. However I did this to draw attention to the fact that problematically the TReader object in Delphi which is really doing the work of converting a string to a component object instance creates/constructs any object which has been registered with the streaming class via RegisterClass. In this manner explicit creation of the editor via explicitedly naming it's type is avoided. If thought is given to the design of TMyEditor and its descendents the only change required to the code is to change TWinControl* to TMyEditor* - even that is not required if published properties beyond TWinControl* are not accessed outside the scope of TMyEditor - Example TMyEditor has access to the variables whose values it is editing and does not require this information to be supplied to the editor.(If working from a DataModule, #include the datamodule's header into TMyEditor).
Side Note:
You may have a utility to know what class was read from the database so that you may place the instance where it belongs. To do this #include <typeinfo> into your code.
Example : If you have instances of TMyEditor, TMyEditor_Generation_01, TMyEditor_Generation_02, etc writen to the database the following will allow you to examine the instances read at runtime for placement into the user interface :
if (typeid(New).name() == "TMyEditor *")
New_Editor->Parent = Tab_Sheet_Addresses;
else
if (typeid(New).name() == "TMyEditor_Generation_01 *")
New_Editor->Parent = Tab_Sheet_Billing;
else
if (typeid(New).name() == "TMyEditor_Generation_02 *")
New_Editor->Parent = Tab_Sheet_Other_Editor;
typeid(__).name() will return a string which is the name of the class, in this case will also inculde " *".
The above allows ANY object(s) to be stored in the database and recreated. The entries within the database are not required to be related. The TReader object buried in Delphi's code will decide at runtime what they are and use the correct constructor.
[*1] Note : The ComponentToString and StringToComponent are examples in the delpi/c++ help file.
[*2] Note : What is being saved are the published properties, therefore in your editor class any values you want stored and retrieved which are not already inherited and published should be declared in the __published section of your new class. Those items may also be custom objects, for those you will likely code custom specific methods/functions for the read/write access specifiers in defining the _property. I would suggest translating any complex object into a string value for ease of examining your code while under development.

Resources