Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How to change caption color only for active page of a pageframe?

Status
Not open for further replies.

Ravindra Patil

Programmer
Feb 3, 2024
4
IN
Dear friends,
I want to change caption color of active page of a pageframe to blue and turn rest of the pages' captions to black.
I tried the following code in activate event of each page. It's working.
But rather than putting the code in each page's activate event, is there any centralized event that can be used for this purpose?
I tried ChatGPT. It suggested Pageframe1.PageChange event. But such an event/method is not there.
Code:
this.FontBold = .t. 
this.ForeColor = RGB(0,0,255)
FOR EACH pg IN this.Parent.Pages 
	IF pg.name#this.Name 
		pg.FontBold = .f. 
		pg.ForeColor = RGB(0,0,0)
	ENDIF
NEXT pg 	
this.Refresh
 

Hi,

If you have not own pageframe class, create it.


[pre]
DEFINE CLASS _pageframe AS pageframe

Name = "_pageframe"

*-- backup page fore color array
DIMENSION _forecolor[1]


*-- Convert Page Index to PageOrder
PROCEDURE cotoro
LPARAM liCOrder
RETURN This.Pages(liCOrder).PageOrder
ENDPROC


*-- Convert PageOrder to Page Index
PROCEDURE rotoco
LPARAM liPO
FOR liCO=1 TO This.PageCount
IF This.Pages(liCO).PageOrder=liPO
RETURN liCO
ENDIF
NEXT
RETURN 0
ENDPROC



*-- Save pages' fore color
PROCEDURE savepagescolor
LOCAL lii
DIME This._ForeColor(This.PageCount,4)
FOR lii=1 TO This.PageCount
WITH This.Pages(lii)
This._ForeColor(lii,1)=.ForeColor
This._ForeColor(lii,2)=.FontBold
This._ForeColor(lii,3)=.FontItalic
This._ForeColor(lii,4)=.FontUnderLine
ENDWITH
NEXT
ENDPROC

*-- Set Active Page's fore color
PROCEDURE setactivecolor
LPARAM liPage
WITH This.Pages(liPage)
.ForeColor=RGB(0,0,255) && Application.PAGE_FC
*.FontBold=Application.PAGE_FB=1
*.FontItalic=Application.PAGE_FI=1
*.FontUnderline=B_System.PAGE_FU=1
ENDWITH
ENDPROC


* restore page's fore color
PROCEDURE restorecolor
LPARAM liPage
WITH This.Pages(liPage)
.ForeColor=This._ForeColor(liPage,1)
.FontBold=This._ForeColor(liPage,2)
.FontItalic=This._ForeColor(liPage,3)
.FontUnderLine=This._ForeColor(liPage,4)
ENDWITH
ENDPROC


PROCEDURE Click
FOR lii=1 TO This.PageCount
WITH This.Pages(lii)
.ForeColor=This._ForeColor(lii,1)
.FontBold=This._ForeColor(lii,2)
.FontItalic=This._ForeColor(lii,3)
.FontUnderLine=This._ForeColor(lii,4)
ENDWITH
NEXT
This.SetActiveColor(This.RoToCo(This.ActivePage))
ENDPROC



PROCEDURE Init
IF This.PageCount>0
This.SavePagesColor()
This.SetActiveColor(This.RoToCo(1))
ENDIF
ENDPROC

ENDDEFINE
[/pre]

mJindrova
 
Tore's idea is not bad, but doesn't work as Refresh() isn't an event happening. So you'd still need a call to This.Refresh() in every pages Activate event.

Using Eventtracking I see everytime you change pages there is one common event firing: the form.paint().
So you could do that in the Form.Paint event

Code:
With Thisform.pageframe1
   .SetAll("FontBold",.F.,"Page")
   .SetAll("ForeColor",Rgb(0,0,0),"Page")
   .Pages(.ActivePage).FontBold = .T. 
   .Pages(.ActivePage).ForeColor = RGB(0,0,255)
EndWith

It's not great to handle something that only concerns one control on the much more general level of the form. So the idea of Tore could also be taken without even needing a call to This.Refresh, when you'd instead use Bindevent and bind the pages Activate() to the pageframe Refresh(), making it a pseudo event.

As I now see mJindrova's solution, I'm reminded when pageorders are changed, .Pages(.Activepage) could give the wrong page. You don't get directly to the activepage object without trickery with OBJTOCLIENT, so iterate all pagees to find the one with the right pageorder:

Code:
With Thisform.pageframe1
   .SetAll("FontBold",.F.,"Page")
   .SetAll("ForeColor",Rgb(0,0,0),"Page")
   
   For Each pg in .Pages
       If pg.Pageorder = .ActivePage
          pg.FontBold = .T. 
          pg.ForeColor = RGB(0,0,255)
       Endif
   EndFor
EndWith
You could also extend the Pageframe class with a SelectedPage property storing the page object of the active page which you set by this For Each loop. When another page is activated the SelectPage would still be the previously selected page, that could be used to only reset that and not use Setall.

Chriss
 
Dear Tore Bleken,
Thank you.
Yes. PF.Refresh is centralized event.
But it is not invoked automatically when any page is activated.
 
Have you considered creating an Assign method of the ActivePage property? That would be a central place to monitor page changes, with ActivePage containing the value of the page on which you are arriving.

Just a thought.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
xinjie,

it's clearly a solution to have a base class and thereby use the inheritance or simpler "write once, use many times" principle. But pages as memberclasses are a bit of a pain to use.
In an existing project with existing pageframes, when you first set the Memberclass you see this warning message:
settingmemberclass_cbifnt.jpg

What concretely hapens when you change the memberclass of a pageframe is all pages get deleted and replaced by new but empty pages of the pageclass you pick (well, or whatever you put into your pageclass).
So that makes it hardly practical to replace pages of existing pageframes in existing classes or forms of a project. And this also is true, if you'd switch from one memberclass to another.

At runtime you can set the memberclass and membeclasslibrary properties without them affecting existing pages, but that also means they stay whatever they are, the member propertis will only affect new members you create, for example by simply increasing the pagecount. That still doesn't make it easy to change from the usual page to a member pageclass.

If you plan it ahead of time for a new project, it can be very, just also plan ahead it is not simply modifying a property to change to another class, within the designer that triggers deletion and recreation of pages and all details you have in them are lost, all controls of pages, all code you put into them is not copied over into the empty new pageclass.

You could make it your default to not design pageframes at designtime, only add empty pageframes into forms. Give them a list of pageclasses they add one by one at initialization. Then edit/maintain the single pageclasses. It makes maintaining pages - let's say - different. You'd have the single page classes in one or more VCXes or PRGs and each page can be opened in parallel into the class designer (to surprise you with an advantage) to maintain them. This possible parallel editing is contrasted by not being able to navigate a pageframe of a form in design mode to have all your editing in the actual form or form class. It doesn't break encapsulation, as that's not about having all your code in one file, of course, but it becomes less practical when you want to use intellisense to address elements on other pages or on the form outside pageframes, you can still program a path like This.Parent.OtherPage.ControlX.property=value, but intellisense won't list you the controls of Otherpage as if it already was in the pageframe as a sibling page. So you do such programming "blindly". Well, in terms of encapsulation it is indeed bad style to reach outside of THIS, but making the pageframe the encapsulation scope, a page adressing another page is within this boundary, and that's taken away from you.

Anyway you do it in detail, it's not easy to fit memberclasses into already existing pageframes, so solving that page tab styling on the level of a pageframe class would be more practical than on the level of a base class for pages. No matter how nicely it would be to have a page base class that all further pages inherit from.

I can also understand the decision about that IDE step to delete all pages and recreate new ones. A pageclass can include controls and code, even if you would limit yourself to pageclasses that are empty and so could allow all the existing "furniture" of the old pages to move over. In the general case you could have overlap, the old page and the new member pageclass can have code in the same method, how do you merge that?

The different behavior of making a change to memberclass/memeberclasslibrary at design time and at runtime makes it a feature inconsistent to work with. On the other hand, if they'd use the runtime behavior at designtime, setting member classes would do nothing to the pages you already designed and I doubt many developers would then understand the meaning of them at all.

Chriss
 
Hi, Chris

Thank you very much for your detailed discussion. And it's right.

I have an unproven idea that we should be able to programmatically change scx because it's DBF.

This should make it easier to replace the base class with a custom class if we have a "tool".

I will consider whether to make this tool after Chinese New Year. Normally, memberclass is already ready for me.
 
Yes, I tested a bit and it seems to boil down to this: If you just add the memberclass and memberclasslibrary into the lines of the pagframe properties memo, that would be sufficient.

I think you wouldn't even need to change the page names from PageN to pageclassN. Names are names and only need to be unique, which they are already, they don't point out which class a page is, that's the job of the memberclass/memberclasslibrary only. Changing the page names as the IDE does would also require changing them and in the Methods memo of the pageframe and in all further records of page controls in their parent references to pages. So simply keep names and everything would be fine. The programmer would then perhaps need to add in DODEFAULT() calls into page methods to also run pageclass code, unless there is no page code that would override pageclass code anyway.

That pages have no extra record also is the fundamental flaw in the way a VCX or SCX stores pageframes: All pages of them must be the same memberclass. You can't change that with any tool. The only way to change that is designing a pageframe class that sets memberclass and memberclasslibrary to the class needed for each page before adding it, with all the pros and cons you'd need to deal with I already described.

Chriss
 
Chriss,

Thank you very much for testing!

At least that validates my guess. This solves most problems with pages. As for using different page classes in a pageframe, at least we bought some more time to get the problem out of the way once and for all.
 
Well, I didn't check out everything. I found out it's important how the order of the properties is in the properties memo, first the memberclasslibrary/memberclass have to be specified, then the pagecount. And the object names of the pages both in designer and at runtime will be pageclassname+number, so indeed you can't stick to Page1, Page2, etc. names, as I said before.

You need to cascade these name changes to all controls in pages and to the methods memo and compile the form. A tool for that will be helpful, it has more to do than just changing the properties memo.

I also found out its possible to use multiple page classes, just use the properties memo, as if it was a script setting the properties line by line

So this will work:
Code:
memberclass = "mypage"
pagecount=1
memberclss= "myotherpage"
pagecount=2
memberclass = "mypage"
pagecount=3

You'll end up with page 1 being named mypage1, page 2 being named myotherpage1, page 3 being named mypage2. So the numbering will only increase, if you create multiple pages of the same pageclass, just as the numbering increases, if you put one after the other control on a page.

The caveat is you have to still have the property definition mypage1.name = 'mypage1', even though this is self referential. That's not introduced by memberclasses, that was always that way.

To set the caption that needs mypage1.caption = 'Page1', myotherpage1.caption='Page2', and mypage2.caption = 'Page3'. I haven't experimented in detail what happens if you get this all wrong, and remember to also cascade these changes into the methods memo, where the page names also have to match what's generated. One more thing to do as you change code in the methods memo is to compile the scx or vcx to get the objmemo to reflect that, that literally means COMPILE FORM or COMPILE CLASSLIB, not building the project.

If you do all this correctly you then can use multiple page classes. And on top of that the code already in the page is preserved and overrides the class code, just like it would be if the page originally had been that class, code you write into the pages at desig time inherits from the class and overrides it, if you write into the same methods you inherit, so the DODEFAULT() mechanism works for running both your code and the pageclass code.

Chriss

Edit: One more experience with this: If you change page names in the designer, because you'd like a simple naming of page1,2,3 again, you'll sabotage all this and VFP will remove all the details and remove the double definitions of memberlclass and only keep the higherst pagecount within the peroperties memo. So such usage of the properties memo is instable when then modified by the normal IDE designer, again.

Edit2: That last experience makes me think you should perhaps not support that multipageclass option, the step to enable a memberclass without needing to recreate the page contents is good enough for a first step.
 
Going a step back to a pageframe solution, it's simple even to do for an already existing pageframe without redefining it.

You need this in the init:
Code:
BindEvent(thisform,'Paint',This,'Refresh')

And this in the pageframe Refresh:
Code:
With This
   .SetAll("FontBold",.F.,"Page")
   .SetAll("ForeColor",Rgb(0,0,0),"Page")
   
   For Each pg in .Pages
       If pg.Pageorder = .ActivePage
          pg.FontBold = .T. 
          pg.ForeColor = RGB(0,0,255)
       Endif
   EndFor
EndWith


Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top