Ask Your Question
0

how can form close listener terminate "endless loop"

asked 2021-04-19 06:38:58 +0200

parsely gravatar image

updated 2021-04-27 11:04:19 +0200

original question has been edited
How can a form open another form, enter a waiting loop, wait until the second form is closed, and then resume program execution after the waiting loop.
The aim is to enter a new record in a table and then populate/update the listbox of a calling form with the new entered ID. The "how to enter new value in list" case.
The incentive is that porting BASIC code to Python does not work.
In the Python sample code a restriction is added to the loop, otherwise LibO remained idle.
Unwanted:The form does not open before the loop is entered, but after the loop is finished.

CONCLUSION: use a dialog

import uno,  unohelper
import time

from com.sun.star.util import XCloseListener
from com.sun.star.frame import FrameActionEvent
from com.sun.star.lang import EventObject
from com.sun.star.awt.PushButtonType import OK as PUSH_OK

ctx = uno.getComponentContext() 
smgr = ctx.ServiceManager

FORMNAME = "frmtest"
DBNAME = "test"

# dialog is called from a form with a listbox that needs a new value

def dialog():    
    waitForSavedForm(FORMNAME, DBNAME)

    # after waiting, execution resumes here, because listener set AB.waitForClosing to false
    # closing of the form is by a macro, 
    # update of listbox boundfield to happen here, because ID now known

    msgbox("done")       


def waitForSavedForm(sFormName, dbName):
    sfa = smgr.createInstance("com.sun.star.sdb.DatabaseContext")
    oDataSource = sfa.getByName(dbName)
    oData = oDataSource.DatabaseDocument.getFormDocuments().getByName(sFormName).open()

    AB=CloseListener()
    oData.addCloseListener(AB)

    i = 0
    while AB.waitForClosing and i<5:
        time.sleep( 1)
        i+= 1

class CloseListener(unohelper.Base, XCloseListener):
    def __init__(self):
        self.waitForClosing = True

    def notifyClosing(self, evt: FrameActionEvent): 
        self.waitForClosing = False
        msgbox("notifyClosing")

    def queryClosing(self,evt: FrameActionEvent,  ownership=True):
        #msgbox("qC")
        return
    def disposing(self, evt: EventObject):
        pass


def msgbox(message, title='LibreOffice', buttons=PUSH_OK, type_msg='messagebox'):

    toolkit = smgr.createInstance('com.sun.star.awt.Toolkit')
    parent = toolkit.getDesktopWindow()
    mb = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message))
    return mb.execute()

In basic it looks like

 Sub frmroot_omschrijving_leave(oEvent As Object)

    oFormCalling = thiscomponent.getdrawpage.getForms.getByName("MainForm")
    idlink = oFormCalling.getColumns.getByName("IDinvoice").getInt()

    if oFormCalling.IsNew() and idlink = 1 then
        oForm2 = ThisDatabaseDocument.FormDocuments.getByName("frminvoice-entry").open()

        call DialogMode()

        ' after oForm2 is closed, the listbox can be filled with the ID of new added record

        oFormCalling.getByName("listbox").boundfield.updateInt(idlink)
        oFormCalling.getByName("listbox").refresh
    End If
End Sub


'====================================================

Sub Form2Close_notifyClosing(ev)
    bWaitForClosing = False             ' terminates "endless loop"
End Sub
Sub Form2Close_disposing(ev)
    ' obtain new entered ID from the form and store in idlink
End Sub
Sub Form2Close_queryClosing(ev, ownership)
End Sub

'====================================================

sub DialogMode()
Dim oCloseListener as object
'oForm2 is entry form, execution is halted until form is closed

    bWaitForClosing = True

    oCloseListener = CreateUnoListener("Form2Close_" , "com.sun.star.util.XCloseListener")
    oForm2.addCloseListener(oCloseListener)

    Do
        wait 500
    loop until bWaitForClosing = False
end Sub
edit retag flag offensive close merge delete

Comments

I've read this question and your comments several times but failed to understand the goal or the problem. You mentioned Basic code that seems to work, so maybe edit the question to show such example code. Also maybe creating a thread would solve the problem, as seen in the link in your question.

Jim K gravatar imageJim K ( 2021-04-20 18:13:48 +0200 )edit

I added code in basic. The problem in python is that without time.sleep(1) LibO does not respond, processor makes overtime. With time.sleep(1) it looks like that the processor is sleeping, and the variable AB.waitForClosing never becomes False.

parsely gravatar imageparsely ( 2021-04-20 23:42:27 +0200 )edit

Apparently I am still somehow failing to understand your goal. If you block the main thread in Python with an infinite loop, then the LibO UI thread will never get the chance to operate. So instead, either finish the routine and listen for the closing event, or run the rest of your code in a separate thread. However for reasons that are obscure to me, it seems that you do not want to do either of these things.

So maybe use Basic? You could have that portion of the code in Basic, with the rest of the code in Python. To do this, see for example https://stackoverflow.com/a/61017740/....

Jim K gravatar imageJim K ( 2021-04-22 00:08:39 +0200 )edit

Thanks for your answer that sheds a new light on the existence of interaction of program code and LibO UI. I am just a simple user, applying complex techniques, trying to naïvely port BASIC code to Python. I understand from your answer that the interaction of code and UI is apparently different in both languages.
I stick to the BASIC version and enter a waiting loop to maintain a logical work flow if a listbox in form (A) needs a new foreign key : form (A) calls entry form(B), waits for input in B to be complete, after new data are available proceed and update form(A) .

parsely gravatar imageparsely ( 2021-04-22 10:35:51 +0200 )edit

Using my second macro in answer (slight mod for running from button) and adding code at closing event the process worked. No need to use a thread. Tested OK with code in MyMacros.

Ratslinger gravatar imageRatslinger ( 2021-04-23 22:28:28 +0200 )edit

Thank you for joining again. Sorry, this is a bit too cryptic for me. Did you work with two forms (A + B)? and what code (the waiting loop / boundfield update) did you add in which of the closing functions?

parsely gravatar imageparsely ( 2021-04-24 13:54:52 +0200 )edit

In experimenting within queryClosing I discovered that bad coding in that section does not throw error messages. You can easily insert i=9/0: there are no errors shown, the form closes, but queryClosing is not handled correctly (the msgbox does not show up). Also a restart of the computer was once required. So LibO can become unstable there.
I filed a bug about no error messages. F.i. def queryClosing(self,evt: FrameActionEvent): #, ownership=True): gives no error message, and a not working listener.

parsely gravatar imageparsely ( 2021-04-24 14:56:48 +0200 )edit

To catch and handle error messages in listeners, I use import logging and log exceptions to a file. For convenience I use a set of decorators to handle listener methods, for example:

@evt_handler.log_exceptions
@evt_handler.do_not_enter_if_handling_event
def itemStateChanged(self, itemEvent):
    pass

def log_exceptions(func):
    def wrapper(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except Exception as exc:
            logger.exception(exc)
            raise
    return wrapper

I wouldn't consider this a bug, but once again, it sounds like you are expecting it to work like Basic, so maybe that language would serve you better.

As far as the restart required, that can easily be caused by bad macro code. So write good code instead! :) A checker such as pylint can help detect problems. Instead of restarting, try killing the uno or soffice processes.

Jim K gravatar imageJim K ( 2021-04-24 20:45:09 +0200 )edit

So my last comment included some advanced concepts that you probably shouldn't worry about at this point. :) The main thing is that yes, exceptions are in fact being raised in your listener code and are being handled properly by the python interpreter. So either catch and report them by writing their messages to a file, or else make corrections blindly to the code until it works.

Jim K gravatar imageJim K ( 2021-04-24 21:44:50 +0200 )edit

Nice to share the logging with us Jim. I intend to have a look at the module. May be more suited for the 2012-2014 raspberry-Pi kids now entering their professional career.

parsely gravatar imageparsely ( 2021-04-27 11:04:56 +0200 )edit

2 Answers

Sort by » oldest newest most voted
0

answered 2021-05-03 12:11:00 +0200

parsely gravatar image

updated 2021-05-03 12:12:32 +0200

This is quite a complex, interleaved, thread the SUMMARY (last comment below) gives context information, concerning the use of dialogs as opposed to forms. The suggestion to use a dialogbox is answered, for BASIC and Python in this post:
solutions. Thanks to Ratslinger.

edit flag offensive delete link more
0

answered 2021-04-19 21:30:42 +0200

Ratslinger gravatar image

updated 2021-04-24 19:18:55 +0200

Hello,

Hopefully I am not missing something in your question. It appears you simply want to know when the form closes.

Edit 2021-04-24:

Deleted the two posted routines. The first was of no value and the second is being replaced by the one to be posted here. There is little change between the delete & posted except in the actual listener close routine. Once executed it updates the list box from the calling form. Then execution ends and the original form continues.

import uno,  unohelper
import time

#import sys
#import os

from com.sun.star.util import XCloseListener
from com.sun.star.frame import FrameActionEvent
from com.sun.star.lang import EventObject
from com.sun.star.awt.PushButtonType import OK as PUSH_OK

ctx = uno.getComponentContext() 
smgr = ctx.ServiceManager

FORMNAME = "frmtest"

def open_form(*args):
    try:
        thisODB = XSCRIPTCONTEXT.getDocument()
        oCC = thisODB.Parent.getCurrentController()
        if not oCC.isConnected():
            oCC.connect()
        opened_form = oCC.loadComponent(2, FORMNAME, 0)
        AB=CloseListener()
        opened_form.addCloseListener(AB)
    except:
        msgbox("code problem")       

class CloseListener(unohelper.Base, XCloseListener):
    def __init__(self):
        pass

    def notifyClosing(self, evt: FrameActionEvent): 
        try:
            desktop = XSCRIPTCONTEXT.getDesktop()
            thisComponent = desktop.getCurrentComponent()
            oModel = thisComponent.getCurrentController().getModel()
            oComponent = oModel.getFormDocuments().getByName("InvoicePrinting").getComponent()
            oForm = oComponent.getDrawPage().getForms().getByName("MainForm")
            oField = oForm.getByName("fmtinvoice_printed")
            oField.refresh()
        except:
            msgbox("closing problem")       

    def queryClosing(self,evt: FrameActionEvent,  ownership=True):
        return
    def disposing(self, evt: EventObject):
        pass


def msgbox(message, title='LibreOffice', buttons=PUSH_OK, type_msg='messagebox'):

    toolkit = smgr.createInstance('com.sun.star.awt.Toolkit')
    parent = toolkit.getDesktopWindow()
    mb = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message))
    return mb.execute()

Please note that I only did this as a proof of concept. Did not spend a lot of time on the code or put in error pocessing. In fact, I thought this may be a somewhat useful process but it occurs to me a dialog may be a better choice (probably modal).

To complete, here is the Base file I tested with --- test.odb

If you place the Scipts->Python folder (MyMacros) and name it closeListener.py the Base form InvoicePrinting should work without modifications.

edit flag offensive delete link more

Comments

If this is not what is wanted, please provide further explanation on what you are attempting to accomplish.

Ratslinger gravatar imageRatslinger ( 2021-04-19 21:49:59 +0200 )edit

thanks for the suggestions, however what i want is part of a broader scope to deal with the 'new value in list' of a listbox.

The naming shows remnants of ms-access. If the (dummy) item 'new value' is selected in a listbox, the entry form is opened to add that new value, among other data. When the last entry field loses focus, the "frmtest" is macro-closed, the listener flips the boolean and execution of the code is resumed after the infinite loop, where the listbox can now be updated and refreshed.
- you can leave the opened frmtest unattended and continue anywhere else in the GUI
+ a new value can be entered in a flow, only using keybord + enter/tab
One can wonder how relevant the question is: the infinite loop is working fine in basic, with a wait instruction, that might be treated different from a sleep command.

parsely gravatar imageparsely ( 2021-04-20 11:04:19 +0200 )edit

Sorry, it appears I am at a loss as to what is being asked for. Maybe someone else can help.

Ratslinger gravatar imageRatslinger ( 2021-04-20 17:41:00 +0200 )edit

SUMMARY
A dialog! In modal mode (the default) it enables pausing of program execution until closed, a disadvantage is that there is no connection to a table? Loading of the entered data into the database has to be programmed after the dialog.execute() statement.
A form, by its function, has a connection to a table. In BASIC a dialog functionality can be simulated using a waiting loop and closelistener, in Python this is next to impossible. Moreover error logging from listeners is difficult.

So use a dialog for a sturdy reliable application design, requiring less complex and laboursome code to develop.
My thanks to Jim and Ratslinger for their effort to sort this out (and more). The question arose from a typical 'taken the wrong turn somewhere'. In the future I will strip the problem, but also include aim and incentive of the question

parsely gravatar imageparsely ( 2021-04-27 11:06:16 +0200 )edit
Login/Signup to Answer

Question Tools

1 follower

Stats

Asked: 2021-04-19 06:38:58 +0200

Seen: 79 times

Last updated: May 03