Page 1 of 1
Some questions re using class vars
Posted: Wed Oct 09, 2024 5:07 pm
by FFF
I have a SLE subclass, for capsuling some "special" behaviour. One of that is simply the sle's background color. Now, there are datawindows with dozens of SLEs, so,
after Chris set me on track re class vars, i realized that i might initialize brush and colorobject once as class var in the owning window, which later on the the sle instances might use. In constructor something like
Code: Select all
CLASS dta_Konzerte INHERIT pdta_Konzerte
STATIC oOldBrush, oPause AS Brush
STATIC oC AS Color
CONSTRUCTOR(oWindow,iCtlID,oServer,uExtra)
...
SUPER(oWindow,iCtlID,oServer,uExtra)
..
dta_Konzerte.oC := Color{COLORYELLOW}
dta_Konzerte.oPause:= Brush{oC}
Return
1) for housekeeping, i added
Code: Select all
METHOD DESTRUCTOR()
dta_Konzerte.oC:= NULL_OBJECT
dta_Konzerte.oPause:=NULL_OBJECT
SUPER:Destroy()
RETURN
Is it safe to assume, the super:destroy does the same as the non existent super:destructor?
2) in the constructor of the sle i set
Code: Select all
SELF:Background:=dta_Konzerte.oPause
which compiles, (why sees the sle the class var at all, there's no warning ), but at runtime no background is set, why?
Code: Select all
SELF:Background:= Brush{Color{COLORYELLOW}}
instead works.
Alternatively it put this in a show() of the sle, but that doesn't help, too.
3) ultimately, the background should depend on the textvalue of the sle, probably the check should be put into the window's notify or better in the editfocuschange?
Sorry for stupid questions, but i finally decided to put away with my shame, to learn something...
Re: Some questions re using class vars
Posted: Wed Oct 09, 2024 8:43 pm
by Chris
Hi Karl,
When you have a STATIC member of a class, this means that there's only one instance of it, no matter how many times you instantiate the class. On the other hand, the (regular) constructor gets called every time you instantiate a new window, so if you put code that creates new objects and assign them to static class members, those will be recreated over and over again. And because the destructor also gets called for every instance of the window, nullifying/destroying them there means they will be destroyed for all windows, even for those that are already open!
So instead you need to instantiate those objects in a (extra) STATIC CONSTRUCTOR(), which gets called only once for the lifetime of the application, so you will only create them once. And there's no need to destroy them ever, they live as long as the application lives and will be released automatically when the application exits.
All that is of course if all your windows need to use the exact same objects. If instead every window needs to use different colors/brushes etc, then you'd need to use regular instance fields (class members) to hold them and indeed destroy them on window close. If it's something in between, for example there are 4-5 possible brushes and each window might use any one of them, then you can (and it's the best way to) still store them all in separate STATIC members and use them as needed in each window.
Btw, NULLifying an object like a brush is not doing much, it only tells the GC that it is not used anymore (unless it is also assigned somewhere else), so it can be eventually collected and destroyed. If you need to release everything needed by a brush or similar object, you need to directly call its Destroy() method instead.
Also no need to call Destroy() from a Window subclass destructor. All of the destructors defined in a class hierarchy are called automatically (and you cannot call manually a SUPER:Destructor()) and the destructor of the most base class (VObject) does call Destroy() itself. You would need to call Destroy() in your own defined classes, if it's not being called already by a destructor in a parent class.
Re: Some questions re using class vars
Posted: Thu Oct 10, 2024 8:58 am
by FFF
Hi Chris,
thx for explaining. As you assumed, it's a "some brushes" case. Added the static constructor and setting the sle background in the sle constructor works.
For conditional setting, depending on the value, i'm obviously still on the wrong track, as it doesn't work. I understand, that in constructor there's no value to check, but i'd think that in a sle:show something like
Code: Select all
IF SELF:Value ="00000"
SELF:Background:=dta_Konzerte.oPause
ENDIF
should do at least for initial display?
Re the destructors: My upbringing was, "if you add a equally named method in a subclass to change behaviour, you have to call at the end it's parent, to avoid missing necessary handling." If i don't have to do so here, the better...
Re: Some questions re using class vars
Posted: Thu Oct 10, 2024 9:59 am
by Chris
Hi Karl,
Ah I see the confusion, METHOD Destroy() and DESTRUCTOR() are actually two completely different things, although they are often used for similar reasons.
A method called Destroy() is generally named like that (in the VOSDK classes) only as a convention (in your own set of classes you could name it "ReleaseUsedResources()" for example instead) and it is a regular method like all other methods. And like all other methods, from within a METHOD Destroy(), you need to call SUPER:Destroy(), for the same named methods to be executed up in the class hierarchy.
Btw, the system libraries in the .Net framework use a different name as a convention, they use "Dispose()", which is intended for the same thing as Destroy() is used in the VOSDK, to do clean up when an object is no longer needed.
A destructor is a different thing, it is a special method that gets executed automatically by the .Net runtime, when an object gets collected by the Garbage Collector and its memory used is freed. And all destructors in the class hierarchy are called (semi-)automatically, you cannot manually call a base destructor (as you can do with constructors). You can do anything you want in a destructor (for example logging about when objects get collected by the GC), but typically destructors are used for freeing resources, by calling another method, which can be Destroy(), Dispose(), ReleaseUsedResources() etc.
The reason why you do not typically release resources inside a destructor directly, but instead use a different regular method, is to be able to call this method manually. Destructors are called only when the GC collects their objects, and this can take a while, or never happen at all, until the app exits. While on the other hand, when you do not need an object anymore, you can call Destroy()/Dispose() on it to immediately release its external resources, close file handles etc and not wait for the GC to do it. In fact the object may be still "alive" for the lifetime of the app (if for example there's a variable always pointing to it, so the GC will never collect it), but it's not usable anymore (unless you tell it to re-allocate the resources it needs!).
To get into more detail, destructors in .Net are actually emitted by the compiler(s) in the exe/dll files as methods, with a special name "Finalize()". And the compiler automatically adds code in every Finalize() method to (among other things) call super:Finalize(), that's how the automatic invocation of all destructors happens under the hood, but that's an implementation detail. It's more or less like the "METHOD Axit()" mechanism that VO has.
Regarding the specific case with the SLE, I suspect the value of the SLE is not the one that you expect when you try to change the color, or your new method does not get executed at all when you expect it for some reason. Try logging the value of the SLE somewhere (in a file, in the console, with System.Diagnostics.Debug.WriteLine() or similar) and see if that's indeed the case.
Re: Some questions re using class vars
Posted: Thu Oct 10, 2024 10:09 am
by Chris
One more thing, I noticed that there's code in the VOSDK for the BitmapObject class, that does what you also used in your code sample:
Code: Select all
CLASS BitmapObject INHERIT ShapeObject
PROTECT oBitmap AS Bitmap
DESTRUCTOR()
IF !InCollect()
oBitmap := NULL_OBJECT
ENDIF
SUPER:Destroy()
This is wrong code, because it means that if the BitmapObject class itself has a Destroy() method, this will not get called (by this destructor at least), instead the parent Destroy() method will be called. Fortunately it's the only class in the SDK that I see is doing this, and also coincidentally it is not harmful, as in fact there's no Destroy() method defined in that level in the class hierarchy, but nevertheless it's bad code and will adjust it.
Re: Some questions re using class vars
Posted: Thu Oct 10, 2024 5:14 pm
by FFF
Chris,
thx for the concise explanation, really helpful - maybe Wolfgang might copy it to save in his knowlegdebase site?
Re the sle case: My problem is, that i don't manage to get to the code, where the check might happen. Ie, my Show() seems to be never called at all. A breakpoint isn't reached, some logoutput is not written to console.
I wonder, if adapting the backgound of a control, depending on its value, is such an exotic wish?
Re: Some questions re using class vars
Posted: Thu Oct 10, 2024 5:28 pm
by Chris
Hi Karl,
I just realized you have put the code in the Show() method of the control, not of the window one. The Show() method is not a callback, it's a method used to show the control if it's hidden, and the VOGUI classes do not call this when initially puttin gthe control on a window, it's in visible state by default. You'd need to explicitly call Show(), for the code to be executed.
But why don't you simply put this code in the constructor of the control?
Re: Some questions re using class vars
Posted: Thu Oct 10, 2024 6:29 pm
by FFF
Because evaluating the condition doesn't work. At that moment the Control:Value is still Nil.
Re: Some questions re using class vars
Posted: Thu Oct 10, 2024 7:53 pm
by robert
Karl,
Is the SLE of your own type?
In that case you could override the TextValue assign and intercept the changes, or route that to the window that owns the control.
Something like:
Code: Select all
INTERFACE ITextValueChanged
METHOD OnTextValueChanged(oC as Control, cText as STRING) as void
END INTERFACE
CLASS MySle INHERIT SingleLineEdit
.
.
ASSIGN TextValue (value)
SUPER:TextValue := value
IF SELF:Owner IS iTextValueChanged VAR oMyOwner
oMyOwner:OnTextValueChanged(SELF, value)
ENDIF
RETURN
END CLASS
CLASS MyDialogWindow ... IMPLEMENTS ITextValueChanged
.
.
METHOD OnTextValueChanged(oControl as Control, cTextValue as string) as void
// your code here
RETURN
END CLASS
CLASS MyDataWindow ... IMPLEMENTS ITextValueChanged
.
.
METHOD OnTextValueChanged(oControl as Control, cTextValue as string) as void
// your code here
RETURN
END CLASS
Robert