Page 1 of 1
NonModal Dialog Demo
Posted: Thu Nov 27, 2025 12:02 pm
by fcsw
This has been tested on:
Win 11 25H2, LibreOffice 25.8.3.2
Ubuntu 24.04.3 LTS, LibreOffice 24.2.7.2
This calls ScriptForge, so it won't work in OO.
This is a demonstration of code that makes it possible to incorporate non-modal dialogs (NMDs) into a project without writing any new code. Since dialogs ordinarily require some code, this makes NMDs available to people who otherwise wouldn't be able to use them. If you decide to incorporate NMDs into your project, you need to know how to create them, open them in dialog design view, rename them, move them from one file to another, etc. In the Base Tutorial section of this forum, I've posted a tutorial on working with dialogs in LibreOffice.
This contains everything needed to add NMDs to an existing project without writing any new code. There are two macros: one for the buttons that open NMDs and one for the buttons that close NMDs There are no limits to the count of NMDs that can be open at the same time, aside from your computer's physical memory.
For this to work, form documents, button controls, and dialogs have to be named in a particular way. The code uses the form doc name and the button name to derive the dialog name so that it knows which dialog to open.
The easiest way to incorporate this into a project is to either use a copy of this demo as the starting point, or to copy the forms and dialogs from an existing project file to a copy of this file. In the first case, if you start with the correct names, or in the second case if you change the names so that the code can find the dialogs, you won't need to do much else.
In addition to a general purpose informational NMD, the demo includes a second type that can be used to type notes relating to a particular form document, store them, and retrieve them later.
For coders, this demonstrates:
- Use of ScriptForge regular expressions
- Avoiding some of the pitfalls of window listeners
- Ensuring that the X in the title bar and the close button cause the same code to run when the dialog closes
- Using one open procedure for two different types of NMDs
Multiple versions explained in comments below:
Re: NonModal Dialog Demo
Posted: Thu Nov 27, 2025 6:02 pm
by JPL
fcsw wrote: ↑Thu Nov 27, 2025 12:02 pm
Instructions in the odb file.
Where is that file ?
Re: NonModal Dialog Demo
Posted: Thu Nov 27, 2025 11:01 pm
by fcsw
I've made several attempts over the past 24 hours to upload the file. It looks like it finally worked.
Re: NonModal Dialog Demo
Posted: Fri Nov 28, 2025 12:43 pm
by JeJe
If you want the dialog's titlebar close to work instead of needing a button
Code: Select all
'in your sub to create the dialog, before the setvisible line
objCurrentDlg.addTopWindowListener(CreateUnoListener("NonModalWindowListener_", "com.sun.star.awt.XTopWindowListener"))
Code: Select all
'in the module
'XTopWindowListener subs
Sub NonModalWindowListener_disposing(ev)
End Sub
Sub NonModalWindowListener_windowOpened(ev)
End Sub
Sub NonModalWindowListener_windowClosing(ev)
ev.source.dispose 'if want closeable need this
End Sub
Sub NonModalWindowListener_windowClosed(ev)
End Sub
Sub NonModalWindowListener_windowMinimized(ev)
End Sub
Sub NonModalWindowListener_windowNormalized(ev)
End Sub
Sub NonModalWindowListener_windowActivated(ev)
End Sub
Sub NonModalWindowListener_windowDeactivated(ev)
End Sub
'/XTopWindowListener subs
Re: NonModal Dialog Demo
Posted: Fri Nov 28, 2025 6:40 pm
by fcsw
Thanks for the tip. I made a crash copy of the demo and inserted your code. It worked ... once. After that I couldn't open the dialog again.
Code: Select all
Sub NonModalWindowListener_windowClosing(ev)
ev.source.dispose 'if want closeable need this
End Sub
This leaves the dialog object in the dictionary. The code won't let you add a second copy of a dialog to the dictionary. So, I tried
Code: Select all
Sub NonModalWindowListener_windowClosing(objEvent As Object)
mcrCloseDlg( objEvent )
End Sub
This resulted in a "Object variable not set" error in mcrCloseDlg at this line:
Code: Select all
strDlgName = objEvent.Source.Context.model.Name
Obviously the window listener event has a different source than the button Execute Action event. I replaced the line above with
Code: Select all
If IsNull( gdictOpenDlgs ) Then Exit Sub
If CBool( InStr( objEvent.Source.implementationName, "Button" ) ) Then
strDlgName = objEvent.Source.Context.model.Name
ElseIf CBool( InStr( objEvent.Source.implementationName, "Dialog" ) ) Then
strDlgName = objEvent.Source.Model.Name
End If
This works. The X closes the dialog, the dictionary is updated, and the dialog is ready to be opened again.
However, the IsNull test is because the windowClosing event runs twice. If only one dialog has been opened, the first run sets the dictionary variable to null. So my question is, why does the windowClosing event run twice?
Once again, thanks for the tip.
Re: NonModal Dialog Demo
Posted: Sat Nov 29, 2025 11:51 am
by JeJe
In case its any use, here's my full code for creating a nonmodal dialog, it only works for location=application which isn't what you want, but I've not found a problem with multiple instances of dialogs with it and no need to store a reference anywhere to any created dialogs.
Code: Select all
'CREATE NON-MODAL DIALOG
'your library with this code has to be in MyMacros not in the document or the URI will give an error.
Sub Test
oParent = ThisComponent.getCurrentController().getFrame().getContainerWindow()
dlg = CreateNonModalDialog(oparent,"JeTest.Dialog1",true)
dlg.setpossize 100,100,500,500,15
dlg.setvisible true
End Sub
Function CreateNonModalDialog(oParent,DialogParam, Closeable as boolean) As Variant
dim dlg,CWP,sURL As String
sURL = "vnd.sun.star.script:" & DialogParam & "?location=" & "application"
CWP = CreateUnoService("com.sun.star.awt.ContainerWindowProvider")
dlg = CWP.createContainerWindow(sURL, "", oParent, nothing)
if Closeable then 'if want closeable need this
dlg.addTopWindowListener(CreateUnoListener("NonModalWindowListener_", "com.sun.star.awt.XTopWindowListener"))
end if
CreateNonModalDialog = dlg
End Function
'XTopWindowListener subs
Sub NonModalWindowListener_disposing(ev)
End Sub
Sub NonModalWindowListener_windowOpened(ev)
End Sub
Sub NonModalWindowListener_windowClosing(ev)
ev.source.dispose 'if want closeable need this
End Sub
Sub NonModalWindowListener_windowClosed(ev)
End Sub
Sub NonModalWindowListener_windowMinimized(ev)
End Sub
Sub NonModalWindowListener_windowNormalized(ev)
End Sub
Sub NonModalWindowListener_windowActivated(ev)
End Sub
Sub NonModalWindowListener_windowDeactivated(ev)
End Sub
'/XTopWindowListener subs
sub commandclose(ev) 'if window is not closeable you might want a button perhaps to close it
ev.source.context.dispose
end sub
'********************************************************************
Re: NonModal Dialog Demo
Posted: Sat Nov 29, 2025 6:25 pm
by JeJe
Adapting my code to use your GetProcessServiceManager method we can show a document dialog.
Code: Select all
REM ***** BASIC *****
'CREATE NON-MODAL DIALOG
'your library with this code has to be in MyMacros not in the document or the URI will give an error.
Sub Test
oParent = ThisComponent.getCurrentController().getFrame().getContainerWindow()
dlg = CreateNonModalDialog(oparent,"Standard.Dialog1",true)
dlg.setpossize 100,100,500,500,15
dlg.setvisible true
End Sub
Function CreateNonModalDialog(oParent,DialogParam, Closeable as boolean) As Variant
dim dlg,CWP,sURL As String,objPsm, avarArgs( 0 ) As New com.sun.star.beans.NamedValue
sURL = "vnd.sun.star.script:" & DialogParam & "?location=document"
objPsm = GetProcessServiceManager.createInstanceWithArguments( "com.sun.star.awt.DialogProvider2", Array( Thiscomponent ) )
avarArgs( 0 ).Name = "ParentWindow"
avarArgs( 0 ).Value = thiscomponent.CurrentController.Frame.ContainerWindow
dlg =objPsm.createDialogWithArguments( sURL, avarArgs)
if Closeable then 'if want closeable need this
dlg.addTopWindowListener(CreateUnoListener("NonModalWindowListener_", "com.sun.star.awt.XTopWindowListener"))
end if
CreateNonModalDialog = dlg
End Function
'XTopWindowListener subs
Sub NonModalWindowListener_disposing(ev)
End Sub
Sub NonModalWindowListener_windowOpened(ev)
End Sub
Sub NonModalWindowListener_windowClosing(ev)
ev.source.dispose 'if want closeable need this
End Sub
Sub NonModalWindowListener_windowClosed(ev)
End Sub
Sub NonModalWindowListener_windowMinimized(ev)
End Sub
Sub NonModalWindowListener_windowNormalized(ev)
End Sub
Sub NonModalWindowListener_windowActivated(ev)
End Sub
Sub NonModalWindowListener_windowDeactivated(ev)
End Sub
'/XTopWindowListener subs
sub commandclose(ev) 'if window is not closeable you might want a button perhaps to close it
ev.source.context.dispose
end sub
Re: NonModal Dialog Demo
Posted: Sat Nov 29, 2025 6:30 pm
by JeJe
Looks like we can replace the named arguments with an empty array and still get the dialog.
Code: Select all
Function CreateNonModalDialog(oParent,DialogParam, Closeable as boolean) As Variant
dim dlg,sURL As String,objPsm
sURL = "vnd.sun.star.script:" & DialogParam & "?location=document"
objPsm = GetProcessServiceManager.createInstanceWithArguments( "com.sun.star.awt.DialogProvider2", Array( Thiscomponent ) )
dlg =objPsm.createDialogWithArguments( sURL, ARRAY())
if Closeable then 'if want closeable need this
dlg.addTopWindowListener(CreateUnoListener("NonModalWindowListener_", "com.sun.star.awt.XTopWindowListener"))
end if
CreateNonModalDialog = dlg
End Function
Re: NonModal Dialog Demo
Posted: Sun Nov 30, 2025 5:24 am
by fcsw
Code: Select all
'your library with this code has to be in MyMacros not in the document or the URI will give an error.
Unfortunately that rules out your last snippets of code. All of my code comes from a larger project that must be distributed as a single odb file. That's too bad because I really don't like global variables. If there were a way to do this with code in the document, I would be very interested.
Re: NonModal Dialog Demo
Posted: Sun Nov 30, 2025 11:47 am
by JeJe
Sorry - that's a leftover comment from the original code I didn't remove - it no longer applies when using GetProcessServiceManager. Works fine with the code in a document.
Re: NonModal Dialog Demo
Posted: Mon Dec 01, 2025 8:06 am
by fcsw
Works fine with the code in a document.
It does! Actually, if you call it from a command button, it needs a little tweaking. This
should be this
or this
Also this
Code: Select all
objPsm.createDialogWithArguments( strDlgCreator, Array() )
causes LO to bring the main window to the front, so that the dialog floats on top of the main window and not the form doc window it was called from. Passing in the arguments as per my original code restores the correct behavior.
I've eliminated the ugly global variable, Ive removed the code that handles the dictionary, and form docs no longer need to call a cleanup macro when they close. I've eliminated a couple of steps for non-coders using this to include NMDs in their projects. Definite improvement!
Thanks.
Re: NonModal Dialog Demo
Posted: Thu Dec 04, 2025 6:10 am
by fcsw
This demo originally used a more complicated approach to managing multiple NMDs. User jeje suggested a simpler way. His approach was clearly superior, but it involves an aspect of UNO programming that I had little experience with. Working out the implementation details hasn't been straightforward, and the upshot is that I'm now on version 4 of the demo. I apologize to everyone who downloaded a previous version, but having undertaken to demonstrate how this is done, I feel obligated to demonstrate the best coding techniques.
Version 3 opened the dialog with the dialog's setVisible method, but closed the dialog with the dispose method from the
dialog's model, even though there is a dispose method at the dialog level. Furthermore, when setVisible executes, it passes program control to the window listener, but when the window listener exits and passes control back to the procedure, the procedure doesn't resume at the point where it passed control, but at a later, seemingly random point. In Version 3, this doesn't cause a problem, but it obviously could cause a problem in different circumstances.
You can open the dialog with the dialogs execute method, and close it with the endExecute method. However, execute passes control to the window listener, which doesn't return control until after the dialog closes. In other words, the open procedure runs until the execute method, and then halts until the close procedure runs, and the rest of the open procedure runs only after the dialog is closed.
In both cases, the solution is to put execute or setVisible( True ) at the end of the open procedure, ensuring that all the code runs before the procedure passes control to the window listener.
You should always follow endExecute or setVisible( False ) with the dispose method. I tried commenting out dispose and watched memory usage creep up when I repeatedly opened and closed dialogs. Dispose removes dialogs from memory so this doesn't happen.
Again, my apologies for multiple versions, but I started with an approach I understood and switched to something I was not completely familiar with. The end result is a better demo, but the process of getting there was messy.