Modify behaviour of subobjects - delphi

Is there any way to modify the behaviour (methods) of a sub-object by redefining them when subclassing a component . e.g I am subclassing Tlistbox , and i want to modify the behaviour of Delete() method of its Items subobject . Is there a formal way to do so ?
Thanks

Based on your comments, you don't actually need to change the behavior of the Items property itself. There are other ways to detect when items are being manipulated in the ListBox so you can update your own internal objects as needed.
When deleting items, the Items.Delete() method simply calls TCustomListBox.DeleteString() passing it the index to delete. DeleteString() can be overridden by a descendant. Simple enough.
When adding/inserting new items, it is not quite so straight forward. Items.Add() sends a LB_ADDSTRING message directly to the ListBox's HWND (unless the ListBox's Style is set to virtual mode, in which case Add() simply exits without doing anything). Same with the Items.Insert() method (LB_INSERTSTRING message). To catch those message, you have to override the ListBox's virtual WndProc() method.
Items.AddObject() calls Items.Add() first, and then uses the Items.Objects[] setter to assign the object to the new item. Same with Items.InsertObject(), calling Items.Insert() instead of Items.Add().
When updating an existing item's string via the Items.Strings[] property, things get a bit complex:
TCustomListBox.InternalGetItemData() (can be overridden) is called to retrieve the item's existing object, if any. By default, InternalGetItemData() calls GetItemData() (which can also be overridden), which by default sends a LB_GETITEMDATA message to the ListBox's HWND.
TCustomListBox.InternalSetItemData() (which can be overridden) is called to set the item's object to nil (in case Delete() in the next step tries to destroy it). By default, InternalSetItemData() calls SetItemData() (which can also be overridden), which by default sends a LB_SETITEMDATA message to the ListBox's HWND.
Items.Delete() is called to remove the item.
Items.InsertObject() is called to insert the new string with a nil object
TCustomListBox.InternalSetItemData() is called to restore the original object to the new item.
When updating an existing item's object via the Items.Objects[] property, if the ListBox's Style is not set to virtual mode then TCustomListBox.SetItemData() is called.
When moving items around using the Items.Exchange() method, if the ListBox's Style is set to virtual mode then Exchange() simply exits without doing anything. Otherwise, it uses the Items.Strings[] property to swap the two item strings, and calls Internal(Get/Set)ItemData() to swap the two item objects.
So, basically, everything you need to manage internal objects boils down to overriding these methods:
TCustomListBox.DeleteString()
TCustomListBox.WndProc() to handle LB_(ADD/INSERT)STRING, and maybe LB_(GET/SET)ITEMDATA.
TCustomListBox.Internal(Get/Set)ItemData() or TCustomListBox.(Get/Set)ItemData()

Related

RxSwift - Class property's binding

I have an question about binding:
I have an array of objects of my custom class: Array. Every object can be updated (change his properties value) in bg.
Also I have separated Controller, which take and store one object from list as variable and can update it (object still the same, so in list it will be updated too)
Is there any way to bind all object.property -> UILabels on Controller in way, when property changes automatically call label update?
Of course, there are multiple ways how to do it, but from your description I would use some kind of subject (because u said there will be changes in background so you will probably need hot observable )....For example Variable or PublishSubject. So you can crate
let myArrayStream: Variable<[MyObject]> = Variable([])
you can pass this variable as dependency to wherever you want, on one side you can subscribe to it, on the other side you can update it's value.

Sending NSNotifications to all objects of a class

I have an object that can be selected by a user click. With the current requirements of the app, at any time, there is no more than one of these items selected at any point during app execution.
I implemented a mechanism to enforce this, as follows:
Each of these objects has a unique identifier as a property.
When each object is created, it subscribes to the NSNotificationCenter listening for the MY_OBJECT_SELECTED notification.
When each object is selected, it posts the MY_OBJECT_SELECTED notification, with its unique Id as part of the userInfo dictionary.
Then, when each object receives the notification, it checks to see if its id is the same as the one in the userInfo. If it is, it does nothing, but if it isn't, it sets itself to unselected.
Is this a decent approach to the problem? If not, how would you do it?
It is a decent way of doing it, although it is not very efficient. The more objects you have, the more time you spend comparing IDs. The easiest way is to store your object pointers and IDs in a map table (or similar) and remember the last selected object. Whenever you select a new object, you clear the selection flag of the last selected object, then look up the new object and set its selection flag. It requires you to keep a collection of your objects, though.
The time required to update selections with this approach is independent of the number of objects you have.
If the object is spread all over the app,i.e. if it is a member in various classes. You can have a global object of same type and assign it to only that object which has been touched. In steps it will be like:
Have a global variable of object type.
At any object touch assign globalObject = currentObject;
do all operations on globalObject throughout app like calling methods and modifying object members(have a check for nil to ensure safety).
Reassign to different object with new touch.

NSFetchedResultsChangeInsert gets called twice for 1 insert with different object IDs

I'm losing my mind around this question.
So I have a Core Data setup in my iOS app done this way:
http://www.cocoanetics.com/2012/07/multi-context-coredata/
I then insert an object by creating a temporary MOC (as explained in the blog post) and perform saves on all 3 contexts in performBlock: methods.
In a view controller I have an NSFetchedResultsController and it gets notified that I did indeed insert a new object. The problem is that the NSFetchedResultsChangeInsert is fired twice and each time the object that is passed trough has a different objectID (it also is a different object instance in memory). What happens is that I then have 2 rows inserted in my table view but un the SQL database there is only one new. It then of course crashes when I scroll to the bottom of the table view.
If I also perform some updates on the object I get NSFetchedResultsChangeUpdate called only once and with the objectID that was passed in the second NSFetchedResultsChangeInsert call.
The first ID looks like this:
<x-coredata:///ReceivedMessage/t605BB9A7-A04E-4B89-B568-65B12E8C259A2>
The second (and all consequent ones) like this:
<x-coredata://02A917C5-850F-4C67-B8E4-1C5790CF3919/ReceivedMessage/p28>
What could this be? Am I missing out something obvious?
PS: I also checked if the notification comes from the same context, thread, etc. It does.
The two IDs you are seeing may very well represent one object. The difference between them is just that the first one is a temporary object ID, assigned to the object on creation, and the second one is the permanent object ID, assigned to the object when it gets stored to the managed object store (see NSManagedObjectID's isTemporaryID).
To work around this issue you could call NSManagedObjectContext's obtainPermanentIDsForObjects:error: just before you save the temporary MOC. This way the inserted object will have just one ID during the save propagation and the NSFetchedResultsControllerDelegate methods should get called just once.

Not able to set WorkItem State using TFS API?

I am trying to set State property value of Test Case Work item. I am creating using TFS API and C# code.
It throws an error while I save the test case using Save() method. I have called the Validate() method of a work item and the ArrayList shows the value I am trying to assign is an invalid state.
testCase.State = TestPointState.Ready.ToString();
ArrayList result = testCase.WorkItem.Validate();
if (!testCase.WorkItem.IsValid())
{
//this block executes
}
When I manually opened the MTM to see what are the different STATE values for existing work items, then I found READY and DESIGN. That's why I tried t assign TestPointState.Ready enum. I tried assiging READY string directly in that statement, but still the same exception whlie saving the test case.
Any idea on how to fix this issue ?
It is possible that when setting the state another field has then an invalid input. e.g.: when you change from Ready to Design it might require that you select who the AssignTo person is and so you'll need to populate these fields as well. You can use the Validate method to get a list of invalid fields after you set the state like below.
ArrayList invalidFields = newWI.Validate();

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