Top window crashes when a MenuBar is added

I have a Top Window class DialogMb below.

When I add a MenuBar to the Window it randomly crashes every 2 to 10 times I open the window.
The crash is fatal and LibreOffice completly disappears. If I don’t add a menu bar it does not crash.
I tried adding the menu bar before or after the frame was created but it did not make a difference.

Am I doing something wrong of is this a bug?

In this example I am using OooDev but only to create the popup menu that is added to the MenuBar.

I am launching it with a simple macro.

from __future__ import annotations

import uno
from libre_pythonista_lib.dialog.py.dialog_mb import DialogMb

def show_win_mb(*args):
    mb = DialogMb()
from __future__ import annotations
import time
from typing import Any, cast, TYPE_CHECKING, Tuple

import uno
import unohelper
from com.sun.star.awt import XTopWindowListener
from com.sun.star.awt import WindowDescriptor
from com.sun.star.awt.WindowClass import TOP
from com.sun.star.frame import XFrame

from ooodev.loader import Lo

# from ooo.dyn.awt.window_attribute import WindowAttributeEnum
from ooo.dyn.awt.rectangle import Rectangle
from ooodev.events.args.event_args import EventArgs
from ooodev.gui.menu.popup_menu import PopupMenu
from ooodev.dialog.dl_control import CtlButton
from ooodev.dialog import BorderKind


if TYPE_CHECKING:
    from com.sun.star.awt import MenuBar
    from com.sun.star.awt import MenuEvent
    from com.sun.star.lang import EventObject
    from ooodev.dialog.dl_control.ctl_base import DialogControlBase
    from .....___lo_pip___.oxt_logger.oxt_logger import OxtLogger
    from ..window_type import WindowType
else:
    from ___lo_pip___.oxt_logger.oxt_logger import OxtLogger

com_sun_star_awt_WindowAttribute_SHOW = uno.getConstantByName("com.sun.star.awt.WindowAttribute.SHOW")
com_sun_star_awt_WindowAttribute_BORDER = uno.getConstantByName("com.sun.star.awt.WindowAttribute.BORDER")
com_sun_star_awt_WindowAttribute_SIZEABLE = uno.getConstantByName("com.sun.star.awt.WindowAttribute.SIZEABLE")
com_sun_star_awt_WindowAttribute_MOVEABLE = uno.getConstantByName("com.sun.star.awt.WindowAttribute.MOVEABLE")
com_sun_star_awt_VclWindowPeerAttribute_CLIPCHILDREN = uno.getConstantByName(
    "com.sun.star.awt.VclWindowPeerAttribute.CLIPCHILDREN"
)


class DialogMb(XTopWindowListener, unohelper.Base):
    FONT = "DejaVu Sans Mono"
    MARGIN = 3
    BUTTON_WIDTH = 100
    BUTTON_HEIGHT = 26
    WIDTH = 600
    NB_TAB = 4
    HEADER = 30  # can create space for menus at the top
    FOOTER = 40
    HEIGHT = 310
    MIN_HEIGHT = HEADER + FOOTER + 30
    MIN_WIDTH = 225

    def __init__(self) -> None:
        XTopWindowListener.__init__(self)
        # XTerminateListener.__init__(self)
        unohelper.Base.__init__(self)
        self._log = OxtLogger(log_name=self.__class__.__name__)
        self._disposed = False
        self._width = DialogMb.WIDTH
        self._height = DialogMb.HEIGHT
        self._border_kind = BorderKind.BORDER_3D
        self._current_tab_index = 1
        if self._border_kind != BorderKind.BORDER_3D:
            self._padding = 10
        else:
            self._padding = 14
        self._width = DialogMb.WIDTH
        self._height = DialogMb.HEIGHT
        self._btn_width = DialogMb.BUTTON_WIDTH
        self._btn_height = DialogMb.BUTTON_HEIGHT
        self._margin = DialogMb.MARGIN
        self._frame = None

        self._mb = cast("MenuBar", None)
        try:
            self.parent = self.get_parent()
            self.tk = self.parent.Toolkit  # type: ignore
            self._init_handlers()
            self._init_dialog()
            self._add_frame()
            self._add_menu_bar()
            self._init_buttons()
            self._dialog.addTopWindowListener(self)

        except Exception:
            self._log.exception(f"Error in DialogMb.__init__:")

    def _init_handlers(self) -> None:

        self._fn_on_btn_ok_click = self.on_btn_ok_click
        self._fn_on_btn_cancel_click = self.on_btn_cancel_click

    def _init_dialog(self) -> None:
        """Create dialog and add controls."""
        rect = Rectangle()
        rect.Width = self._width
        rect.Height = self._height

        ps = self.parent.getPosSize()
        rect.X = int((ps.Width - self._width) / 2 - 50)
        rect.Y = int((ps.Height - self._height) / 2 - 100)

        desc = WindowDescriptor()
        desc.Type = TOP
        desc.WindowServiceName = "window"
        desc.ParentIndex = -1
        desc.Parent = None  # type: ignore
        desc.Bounds = rect

        desc.WindowAttributes = (
            com_sun_star_awt_WindowAttribute_SHOW
            + com_sun_star_awt_WindowAttribute_BORDER
            + com_sun_star_awt_WindowAttribute_SIZEABLE
            # + WindowAttributeEnum.CLOSEABLE.value
            + com_sun_star_awt_WindowAttribute_MOVEABLE
            + com_sun_star_awt_VclWindowPeerAttribute_CLIPCHILDREN
        )

        dialog_peer = self.tk.createWindow(desc)

        # create new empty frame and set window on it

        self._dialog = cast("WindowType", dialog_peer)

    def _add_frame(self) -> None:
        """Add frame to dialog"""
        frame = Lo.create_instance_mcf(XFrame, "com.sun.star.frame.Frame", raise_err=True)
        frame.setName("DialogMb_Frame")
        frame.initialize(self._dialog)

        self._frame = frame

    # region Misc Methods

    def _set_tab_index(self, ctl: DialogControlBase) -> None:
        ctl.tab_index = self._current_tab_index
        self._current_tab_index += 1

    def get_parent(self):
        """Returns parent frame"""
        try:
            window = Lo.desktop.get_active_frame().getContainerWindow()
            if window is None:
                raise Exception("Window is None")
            return window
        except Exception:
            self._log.error("Error in get_parent", exc_info=True)
            return None

    # endregion Misc Methods

    # region Controls
    def _init_buttons(self) -> None:
        """Add OK, Cancel and Info buttons to dialog control"""
        sz = self._dialog.getPosSize()

        btn_y = sz.Height - DialogMb.BUTTON_HEIGHT - self._padding - DialogMb.HEADER
        btn_x = sz.Width - DialogMb.BUTTON_WIDTH - self._padding

        self._ctl_btn_cancel = CtlButton.create(
            self._dialog,
            x=btn_x,
            y=btn_y,
            width=DialogMb.BUTTON_WIDTH,
            height=DialogMb.BUTTON_HEIGHT,
            Label="Cancel",
        )
        self._set_tab_index(self._ctl_btn_cancel)
        sz = self._ctl_btn_cancel.view.getPosSize()
        self._ctl_btn_ok = CtlButton.create(
            self._dialog,
            x=sz.X - sz.Width - self._margin,
            y=sz.Y,
            width=self._btn_width,
            height=self._btn_height,
            Label="OK",
            DefaultButton=True,
        )
        self._set_tab_index(self._ctl_btn_ok)
        self._ctl_btn_ok.add_event_action_performed(self._fn_on_btn_ok_click)  # type: ignore
        self._ctl_btn_cancel.add_event_action_performed(self._fn_on_btn_cancel_click)  # type: ignore

    # endregion Controls

    # endregion button handlers
    def on_btn_ok_click(self, source: Any, event: EventArgs, control_src: CtlButton) -> None:
        if self._frame is not None:
            self._frame.dispose()

    def on_btn_cancel_click(self, source: Any, event: EventArgs, control_src: CtlButton) -> None:
        if self._frame is not None:
            self._frame.dispose()

    # endregion Event Handlers

    # region XTopWindowListener
    def windowOpened(self, event: EventObject) -> None:
        """is invoked when a window is activated."""
        self._log.debug("Window Opened")

    def windowActivated(self, event: EventObject) -> None:
        """is invoked when a window is activated."""

        self._log.debug("Window Activated")

    def windowDeactivated(self, event: EventObject) -> None:
        """is invoked when a window is deactivated."""
        self._log.debug("Window De-activated")

    def windowMinimized(self, event: EventObject) -> None:
        """Is invoked when a window is iconified."""
        self._log.debug("Window Minimized")

    def windowNormalized(self, event: EventObject) -> None:
        """is invoked when a window is deiconified."""
        self._log.debug("Window Normalized")

    def windowClosing(self, event: EventObject) -> None:
        """
        is invoked when a window is in the process of being closed.

        The close operation can be overridden at this point.
        """
        self._log.debug("Window Closing")
        self._is_shown = False

    def windowClosed(self, event: EventObject) -> None:
        """is invoked when a window has been closed."""
        self._log.debug("Window Closed")

    def disposing(self, event: EventObject) -> None:

        self._log.debug("Disposing")
        if not self._disposed:
            try:
                self._disposed = True
                self._dialog.removeTopWindowListener(self)
                self._dialog.setMenuBar(None)  # type: ignore
                self._mb = None
                self._dialog.dispose()
            except Exception:
                self._log.error("Error in disposing", exc_info=True)

    # endregion XTopWindowListener

    # region Menubar
    def _get_popup_menu(self):
        from ooodev.gui.menu.popup.popup_creator import PopupCreator

        creator = PopupCreator()
        new_menu = [{"text": "About", "command": ".uno:About"}]
        pm = creator.create(new_menu)
        pm.add_event_item_selected(on_menu_select)
        return pm

    def _add_menu_bar(self) -> None:
        from com.sun.star.awt import XMenuBar
        from com.sun.star.awt import MenuItemStyle

        mb = Lo.create_instance_mcf(XMenuBar, "com.sun.star.awt.MenuBar", raise_err=True)
        mb.insertItem(1, "~First MenuBar Item", MenuItemStyle.AUTOCHECK, 0)
        mb.insertItem(2, "~Second MenuBar Item", MenuItemStyle.AUTOCHECK, 1)
        pm = self._get_popup_menu()
        mb.setPopupMenu(1, pm.component)
        self._mb = mb
        self._dialog.setMenuBar(mb)

    # endregion Menubar


def on_menu_select(src: Any, event: EventArgs, menu: PopupMenu) -> None:
    print("Menu Selected")
    me = cast("MenuEvent", event.event_data)
    command = menu.get_command(me.MenuId)
    if command:
        # check if command is a dispatch command
        if menu.is_dispatch_cmd(command):
            menu.execute_cmd(command)

com_sun_star_awt_WindowAttribute_SHOW = uno.getConstantByName("com.sun.star.awt.WindowAttribute.SHOW")
com_sun_star_awt_WindowAttribute_BORDER = uno.getConstantByName("com.sun.star.awt.WindowAttribute.BORDER")
com_sun_star_awt_WindowAttribute_SIZEABLE = uno.getConstantByName("com.sun.star.awt.WindowAttribute.SIZEABLE")
com_sun_star_awt_WindowAttribute_MOVEABLE = uno.getConstantByName("com.sun.star.awt.WindowAttribute.MOVEABLE")
com_sun_star_awt_VclWindowPeerAttribute_CLIPCHILDREN = uno.getConstantByName(
    "com.sun.star.awt.VclWindowPeerAttribute.CLIPCHILDREN"
)

wtf, why not:

from com.sun.star.awt.WindowAttribute import SHOW, BORDER, SIZEABLE, MOVEABLE
from com.sun.star.awt.VclWindowPeerAttribute import CLIPCHILDREN

and later:

desc.WindowAttributes = (
            com_sun_star_awt_WindowAttribute_SHOW
            + com_sun_star_awt_WindowAttribute_BORDER
            + com_sun_star_awt_WindowAttribute_SIZEABLE
            # + WindowAttributeEnum.CLOSEABLE.value
            + com_sun_star_awt_WindowAttribute_MOVEABLE
            + com_sun_star_awt_VclWindowPeerAttribute_CLIPCHILDREN
        )

again wtf why not:

desc.WindowAttributes = sum([SHOW, BORDER, SIZEABLE, MOVEABLE, CLIPCHILDREN ] )

It’s that easy to turn 12 overlong, hard-to-read lines of code into 3 clear and readable lines!

and NO, I will not try to debug this code, if only because I would have to install ooodev and its dependencies

I was just following some example code trying to get this to work.

I am not asking any to install OooDev, the concept seems simple enough, I was just thinking some might know something about this crashing that I do not.

I can probably simplify this a bit more.

You can see an working example in MRI extension:

1 Like

Well I cleaned it up and Now it seems to work. I will have to dig in a bit more.

from __future__ import annotations

import uno
from libre_pythonista_lib.dialog.py.dialog_mb import DialogMb

def show_win_mb(*args):
    mb = DialogMb(XSCRIPTCONTEXT)

Cleaned up.

from __future__ import annotations
import time
from typing import Any, cast, TYPE_CHECKING

import uno
import unohelper
from com.sun.star.awt import XTopWindowListener
from com.sun.star.awt import WindowDescriptor
from com.sun.star.awt.WindowClass import TOP
from com.sun.star.awt import Rectangle
from com.sun.star.awt import MenuItemStyle
from com.sun.star.awt import WindowAttribute
from com.sun.star.awt import VclWindowPeerAttribute
from com.sun.star.awt import XMenuListener

if TYPE_CHECKING:
    from com.sun.star.script.provider import XScriptContext
    from com.sun.star.awt import MenuBar  # service
    from com.sun.star.awt import MenuEvent  # struct
    from com.sun.star.lang import EventObject  # struct
    from com.sun.star.frame import Frame  # service
    from com.sun.star.awt import Toolkit  # service
    from com.sun.star.awt import PopupMenu  # service
    from ..window_type import WindowType


class DialogMb(XTopWindowListener, XMenuListener, unohelper.Base):
    FONT = "DejaVu Sans Mono"
    MARGIN = 3
    BUTTON_WIDTH = 100
    BUTTON_HEIGHT = 26
    WIDTH = 600
    NB_TAB = 4
    HEADER = 30  # can create space for menus at the top
    FOOTER = 40
    HEIGHT = 310
    MIN_HEIGHT = HEADER + FOOTER + 30
    MIN_WIDTH = 225

    def __init__(self, script_ctx: XScriptContext) -> None:
        XTopWindowListener.__init__(self)
        XMenuListener.__init__(self)
        unohelper.Base.__init__(self)
        self._ctx = script_ctx.getComponentContext()
        self._srv_mgr = self._ctx.getServiceManager()
        self._disposed = False
        self._width = DialogMb.WIDTH
        self._height = DialogMb.HEIGHT
        self._current_tab_index = 1
        self._padding = 14
        self._width = DialogMb.WIDTH
        self._height = DialogMb.HEIGHT
        self._btn_width = DialogMb.BUTTON_WIDTH
        self._btn_height = DialogMb.BUTTON_HEIGHT
        self._margin = DialogMb.MARGIN
        self._frame = None

        self._mb = cast("MenuBar", None)
        try:
            self.tk = cast("Toolkit", self._srv_mgr.createInstanceWithContext("com.sun.star.awt.Toolkit", self._ctx))
            self._init_dialog()
            self._add_frame()
            self._add_menu_bar()
            self._dialog.addTopWindowListener(self)

        except Exception as e:
            print(e)

    def _init_dialog(self) -> None:
        """Create dialog and add controls."""
        rect = Rectangle()
        rect.Width = self._width
        rect.Height = self._height

        rect.X = 100
        rect.Y = 100

        desc = WindowDescriptor()
        desc.Type = TOP
        desc.WindowServiceName = "window"
        desc.ParentIndex = -1
        desc.Parent = None  # type: ignore
        desc.Bounds = rect

        desc.WindowAttributes = (
            WindowAttribute.SHOW
            + WindowAttribute.BORDER
            + WindowAttribute.SIZEABLE
            + WindowAttribute.MOVEABLE
            + VclWindowPeerAttribute.CLIPCHILDREN
        )

        dialog_peer = self.tk.createWindow(desc)

        self._dialog = cast("WindowType", dialog_peer)

    def _add_frame(self) -> None:
        """Add frame to dialog"""
        # create new empty frame and set window on it
        frame = cast("Frame", self._srv_mgr.createInstanceWithContext("com.sun.star.frame.Frame", self._ctx))
        frame.setName("DialogMb_Frame")
        frame.initialize(self._dialog)

        self._frame = frame

    # region XTopWindowListener
    def windowOpened(self, event: EventObject) -> None:
        """is invoked when a window is activated."""
        print("Window Opened")

    def windowActivated(self, event: EventObject) -> None:
        """is invoked when a window is activated."""

        print("Window Activated")

    def windowDeactivated(self, event: EventObject) -> None:
        """is invoked when a window is deactivated."""
        print("Window De-activated")

    def windowMinimized(self, event: EventObject) -> None:
        """Is invoked when a window is iconified."""
        print("Window Minimized")

    def windowNormalized(self, event: EventObject) -> None:
        """is invoked when a window is deiconified."""
        print("Window Normalized")

    def windowClosing(self, event: EventObject) -> None:
        """
        is invoked when a window is in the process of being closed.

        The close operation can be overridden at this point.
        """
        print("Window Closing")
        self._is_shown = False

    def windowClosed(self, event: EventObject) -> None:
        """is invoked when a window has been closed."""
        print("Window Closed")

    def disposing(self, event: EventObject) -> None:

        print("Disposing")
        if not self._disposed:
            try:
                self._disposed = True
                self._dialog.removeTopWindowListener(self)
                self._dialog.setMenuBar(None)  # type: ignore
                self._mb = None
                self._dialog.dispose()
            except Exception as e:
                print("Error in disposing", e)

    # endregion XTopWindowListener

    # region XMenuListener
    def itemActivated(self, event: MenuEvent) -> None:
        print("Item Activated", event.MenuId)

    def itemDeactivated(self, event: MenuEvent) -> None:
        print("Item De-Activated", event.MenuId)

    def itemHighlighted(self, event: MenuEvent) -> None:
        print("Item Highlighted", event.MenuId)

    def itemSelected(self, event: MenuEvent) -> None:
        print("Item Selected", event.MenuId)

    # endregion XMenuListener

    # region Menubar
    def _get_popup_menu(self):

        # https://wiki.documentfoundation.org/Documentation/DevGuide/Graphical_User_Interfaces#The_Toolkit_Service
        try:
            pm = cast("PopupMenu", self._srv_mgr.createInstanceWithContext("com.sun.star.awt.PopupMenu", self._ctx))
            pm.insertItem(1, "~First Entry", 0, 0)
            pm.insertItem(2, "First Radio Entry", MenuItemStyle.RADIOCHECK + MenuItemStyle.AUTOCHECK, 1)
            pm.insertItem(3, "Second Radio Entry", MenuItemStyle.RADIOCHECK + MenuItemStyle.AUTOCHECK, 2)
            pm.insertItem(4, "Third RadioEntry", MenuItemStyle.RADIOCHECK + MenuItemStyle.AUTOCHECK, 3)
            pm.insertSeparator(5)
            pm.insertItem(6, "Fifth Entry", MenuItemStyle.CHECKABLE + MenuItemStyle.AUTOCHECK, 5)
            pm.insertItem(7, "Fourth Entry", MenuItemStyle.CHECKABLE + MenuItemStyle.AUTOCHECK, 6)
            pm.insertItem(8, "Sixth Entry", 0, 9)
            pm.insertItem(9, "Close Dialog", 0, 10)

            pm.enableItem(2, False)
            pm.checkItem(3, True)
            return pm

        except Exception as e:
            print(e)
        return None

    def _add_menu_bar(self) -> None:
        pm = self._get_popup_menu()
        if pm is None:
            print("_add_menu_bar() No popup menu found. Exiting.")
            return
        mb = cast("MenuBar", self._srv_mgr.createInstanceWithContext("com.sun.star.awt.MenuBar", self._ctx))
        mb.insertItem(1, "~First MenuBar Item", MenuItemStyle.AUTOCHECK, 0)
        mb.insertItem(2, "~Second MenuBar Item", MenuItemStyle.AUTOCHECK, 1)
        pm = self._get_popup_menu()
        pm.addMenuListener(self)
        mb.setPopupMenu(1, pm)  # type: ignore
        self._mb = mb
        self._dialog.setMenuBar(mb)

    # endregion Menubar

Well I found the reason for the crash in my originl post. It was imports inside of a class method that was causing the crash.


When I moved

from com.sun.star.awt import XMenuBar
from com.sun.star.awt import MenuItemStyle

from the _add_menu_bar() method into the header of the file the crashing went away.

def _add_menu_bar(self) -> None:
    # from com.sun.star.awt import XMenuBar
    # from com.sun.star.awt import MenuItemStyle

    pm = self._get_popup_menu()
    if pm is None:
        print("_add_menu_bar() No popup menu found. Exiting.")
        return
    # mb = cast("MenuBar", self._srv_mgr.createInstanceWithContext("com.sun.star.awt.MenuBar", self._ctx))
    mb = Lo.create_instance_mcf(XMenuBar, "com.sun.star.awt.MenuBar", raise_err=True)
    mb.insertItem(1, "~First MenuBar Item", MenuItemStyle.AUTOCHECK, 0)
    mb.insertItem(2, "~Second MenuBar Item", MenuItemStyle.AUTOCHECK, 1)
    # pm.component.addMenuListener(self)
    mb.setPopupMenu(1, pm.component)  # type: ignore
    self._mb = mb
    self._dialog.setMenuBar(mb)