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 sizbut on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Switching between forms in an application

AndrewMozley

Programmer
Oct 15, 2005
623
GB
This application has a ribbon menu which allows the user to have several forms visible at the same time.

For example, a user could be entering a sales invoice for a product, but he may wish to find out what this customer – or another customer – paid last time; or what deliveries of the product are expected.

In the present case the user is entering a Sales Invoice (form Salinvoice.scx), but he has another form open which uses the customer table (XCUST.dbf), and that other form may have a particular customer record locked, because he is altering various value for that customer on the screen.

On the Sales invoice form, the customer has entered all the details – products & prices &c, maybe some comments. He is ready to post the invoice to the Sales Ledger, and Clicks on a Post button.

The Salinvoice form needs to add records to several tables in the Sales and Nominal ledgers, but it also needs to update a couple of fields in the XCUST table.

For that purpose it needs to RLOCK that particular record. When that fails, the application displays a MessageBox, inviting the user (if it is him!) to unlock that record or to abandon the transaction.

If he decides to unlock the record, I would like the user to be able to click on the Customer Maintenance form (which is on the screen) and Save or Abandon the Edit which he was doing.

Unfortunately it seems not to be possible to switch from Salinvoice.frx while its MessageBox is displayed. Would be grateful for guidance on the best way to set about this.

Thank you. Andrew Mozley
 
Message boxes are modal, they are the only form selectable when they are displayed, no matter from where they are started. That's the first simple fact.

I wonder if you're mixing up things, as you talk of a salesinvoice scx and frx, that's two completely different things. a Report preview window has the same modal nature, you first have to deal with the report preview, either print or cancel printing or, if you added other buttons to the preview toolbar as foxypreviewer allows, save as some file type. Only after that you can go about handling anything else.

If you just have several forms and a message box. The situation is solvable after the message box has been answered, so what actually is your problem after knowing that? Maybe even just running a message box, you could instead run your own form designed like a messagebox that you can make non-modal. And in general, to be able to switch forms, don't design them as modal. But no, that's not really the answer.

As you actually only need to switch from edit mode then put that functionality in a business class the form uses and you can use from anywhere else, too, without the need to a) activate the form and b) do this interactively. The way you talk about it you locked your own code out of being able to do something that's only available by interaction with a form that you can't switch to, because you step on your own foot with a message box and/or report preview and know nothing about modality. Keep control about what you want you and your code to be able to do.

In the core you want to unlock a record, you have the code to do so in a form button click and therefore you think its best to switch to that form. No, it's best to redesign your code so the unlocking of records is not only available in the button click of a form, but in the business class of a table that can be used on a form and elsewhere to be able to call things like locking/unlocking data from anywhere and not be dependent on a single form to offer that functionality. It is good to program something in one place only, but the best place is not intertwined with the UI in one form, as you see for yourself in your current situation. And once something is programmed in one place, in a class, usually, that is available in multiple instances. You have some dependencies, i.e. you can't unlock a record in another datasession, but you don't depend on a button click, if you design your software correctly.
 
Last edited:
I don't think you need to learn how to programmatically switch forms, but for sake of completeness, if someone comes across this thread by searching for form activation:

1. Having a form reference (oForm, type "O", a variable of the form object) you can call oForm.Show()
2. You can set focus to a control of the form, thereby activating not only the form itself and whatever was the last current control on it becomes focussed again, but you explicitly set focus to some control, for example the button for leaving edit mode in your case
3. You can use Windows API based on Windows HWND, knowing the oForm.HWND integer value using ShowWindow API function.
Code:
#Define SW_RESTORE 9
DECLARE INTEGER ShowWindow IN user32;
    INTEGER hwnd,;
    INTEGER nCmdShow
ShowWindow(oForm.hwnd, SW_RESTORE) &&for example when a form is maximized or minimized puts it into the normal state, there are further SW constants
4. Another way of using the Window API with a HWND is sending the WM_ACTIVATE message to the form to activate with SendMessage.
Code:
#Define WM_ACTIVATE 6
DECLARE INTEGER SendMessage IN user32;
    INTEGER hWnd,;
    INTEGER Msg,;
    INTEGER wParam,;
    INTEGER lParam
SendMessage(oForm.hwnd,WM_ACTIVATE,1,0)
5. Yet other ways with Windows API are calls to SetForegroundWindow and BringWindowToTop. I spare to detail this, you can find necessary DECLARE and usage example on https://github.com/VFPX/Win32API

If you only know the forms name or caption you may go through _screen.forms to find your form and then get it's object reference and hwnd from there.

None of that would work when a modal form is active, i.e. the message box or report preview. Such modal state forms have to be responded to and thereby released, before any other form can be activated.

You don't need as many options as listed, but anyone of them can come in handy. If you programmed into Show you might have code there you expected to run only once at form start, when you do something along the lines of oForm = createobject("form") and oForm.Show() and never use Show again. Show is not only the opposite of Hide, so it does not only make a form visible for the first or repeated time after hiding it, it also activates it. And it is a method, not an event. Form.Activate is an event, not a method on the other hand, and calling oForm.Activate does not activate the form, unless you write code into the event that activates the form, which would be counter productive, as that triggers itself, in the worst case. If you made a design flaw you can, at least, use one of the other ways.
 
Last edited:
I have a similar application with a tab interface for selecting between forms:

2025-02-18_20-53-59.png

2025-02-18_20-53-59.png

The first row allows the user to select between different tab groups. The second row allows a user to open a form into the group's tab interface. When the user selects a command button to open a popup child form (over the current form), I use an image class to 'cover' over the current form by taking a screen shot of the parent form and using alpha blending to darken the image, then I set the Visible property to False for all the controls, thus leaving the parent form darkened in the background of the popup child form. The popup child form is always non-modal and linked to the parent form by using the NAME clause on the DO FORM command and assigning it to a property of the parent form (ChildForm). The image class if clicked on will redirect the user back to the popup child form via the ChildForm property of the parent form. This keeps the popup child non-modal form in focus for the parent. When the child form closes, I restore the parent form back to normal appearance. I use the RAISEEVENT command to pass parameters from the child form back to the parent form.

The class for the tab interface takes care of switching between tabs (hiding the appropriate forms and making the selected tab form visible), destroying the associated form (which in turn destroys the child form if present) when the tab is closed, and creating a new tab and positioning the form.
 
Last edited:
For that purpose it needs to RLOCK that particular record. When that fails, the application displays a MessageBox, inviting the user (if it is him!) to unlock that record or to abandon the transaction.
Actually, if you have an RLOCK, you already have an RLOCK. And an open transaction is only a problem as the user later might revert it, undoing any changes you do for the salesinvoice. Well, you're using transactiona wrong, then. A transaction should start right before saving changes not when starting to edit a record already. To be able to revert a change you must use buffering, not a transaction. I say must, not can, because the point of a transaction is not the same as that of buffering and you're misusing transactions, if you start them at the start of an editing mode.

The usage pattern mandatorey for a transaction is starting it in a submit/save changes button, then saving all changes (usually doing several tableupdates to save buffered changes, but can also include any insert/updates/replaces only happening then) and when everything goes through end the transaction, when anything fails rollback the transaction and address the problems.

Locking records can be done before starting a transaction and as the first thing in edi mode to actually prepare a flawless transaction, as other clients/forms/users will then know the record is in editing. But once that's done you already have the ownership of that and on the same client you can also make use of that to do anything else in other processes like that of a sales invoice done during editing of a customer record if the involved customer.

If you do it as I suggest your only remaining problem is how you detect that it's not the current user that has the lock. I think you is ISRLOCKED to determine the lock status and if that's .F. ask to unlock. Well, that's wrong. Use RLOCK and see, if you can lock, if you already have the lock, that doesn't cause double locking needing double unlocking, it just confirms you have the lock, either you just got it now or you already had it. It'll fail, if another user had the lock. And then you can only tell the user, that the sales invoice can't be completed.

Well, and that's the other flaw of your design, if you alrady know from the getgo that a salesinvoice needs a record lock on the customer involved, then get this rlock early on, at the start. Then it doesn't hinder the same user to also access edit mode for the customer to look up information from there, but it locks out other users from both sales invoices from the same customer and editing the customer and you can be sure you get to the end of processing the sales invoice.

I'm not really a fan of locking data to ensure such record by recored exclusive access, as everything can be done intertwined with shared access not locking anything, but when you go that route, and I also did for a company that asked for such a policy you also have to care about users leaving work or going for lunch having a long lasting lock that hinders others to contiune working. So that company had to see the bad side oftheir demand and agree on automatic unlocking after too long idle time of a workstation having the lock. That's actually a lesson not really learned by them.
 
My solution to the problem of RLOCK of a record by one user that another user needs access to, is to not use RLOCK. Instead, I have a table for managing the locks - tablelock.dbf. When a user selects a customer, supplier, etc. record to be edited, I add the record Id to the tablelock record with the table name and user id in the Init() event of the edit form; if another user is already has a lock (exists in the table), then a message is given to the user that another is editing (I also provide the user name). When the form is closed, in the Destory() event, I remove the record from the tablelock.dbf table. I provide an administrative function to override and remove a lock if needed (which has not been needed to be used in over 8 years of the app). Also, if the user Id is the same for a lock record that already exists, then I return the table lock as successful and allow the edit to proceed.

The only time I use the RLOCK function is when I am adding a new record to a table and need the next Id to assign to the record. For this I have a table tableid.dbf for managing the Id assignment (I use a 5-character Id field size that is a base-36 number).
 
I understand that the MessageBox() is modal. So even during the pause when the message is being displayed – the user cannot click on the form where the Customer record is being edited.

So the form which was trying to post the invoice now displays a message to that effect, inviting the user to complete his editing function. It then RETURNS from the Click() method of the “POST” button, allowing the user to complete and Save his edit (or abandon it).

Returning to the Invoicing form, the user can now click on the POST” button again; and in this case the invoice is posted successfully.

Thank you to several correspondents who have helped in this matter.

Andrew Mozley
 
Sounds like you solved the problem. Yet I don't know why you have the problem in itself, as you display the messagebox, don't you? Instead of asking the user to do something in a messagebox, you could do so yourself before and instead of even showing a messagebox. I understand you want to give the user the choice of saving or cancelling the changes, well, that could also be done with a messagebox with a question answered by YES/NO and then be done automatically. But in case you detect a lock that you have yourself, you don't need to ask nor to end the editmode, you can add the changes of posting the sales invoice in the same transaction that saves other changes by the user.

And sill, all in all, though I don't precisely know what you're doing in code, remember transaction are started right before saving changes, not at start of edit mode, they are the major pain, even a larger pain than locks.
 

Part and Inventory Search

Sponsor

Back
Top