skip to Main Content

I’m developing a GUI framework in Python that is inspired by Flutter. Currently, I handle onPressed events by passing the function name as a string and then registering the callbacks in my main.py file. Here’s a simplified example of what I’m doing:

# ExampleWidget.py
button = Button(onPressed="handleClick")

# main.py
def handleClick():
    print("Button clicked!")

register_callback("handleClick", handleClick)

While this works, it feels cumbersome and error-prone. I would prefer a method that’s as simple and precise as Flutter’s approach, where you can directly pass the function as a callback without dealing with strings or separate registrations.

My goal: Simplify the onPressed logic so that it’s more intuitive and closely resembles Flutter’s design.

Questions:

How would you refactor this to avoid passing function names as strings?
What design patterns or techniques could I use to streamline this process?
Are there any best practices in Python that could help make this more elegant?
Additional Context:

The framework uses HTML and CSS for rendering the GUI in a PyWebView window.
I’m targeting desktop platforms like Ubuntu and Windows.
Any insights or suggestions would be greatly appreciated!

2

Answers


  1. Chosen as BEST ANSWER

    The Problem:

    I wanted to avoid passing function names as strings and manually registering them. I aimed for a more intuitive API where function references are used directly, similar to Flutter.

    Solution: Singleton Api Class: To manage callbacks efficiently, I used a singleton pattern for the Api class, ensuring there's only one instance managing callbacks.

    # framework/api.py
    
    class Api:
        def __init__(self):
            self.callbacks = {}
    
        _instance = None
    
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super(Api, cls).__new__(cls)
            return cls._instance
    
        def register_callback(self, name, callback):
            self.callbacks[name] = callback
    
        def on_pressed(self, callback_name, index=None):
            if callback_name in self.callbacks:
                if index is not None:
                    self.callbacks[callback_name](index)
                else:
                    self.callbacks[callback_name]()
                return f"Callback '{callback_name}' executed successfully."
            else:
                return f"Callback '{callback_name}' not found."
    

    Refactored IconButton Widget:

    The IconButton class now directly uses function references for onPressed. The function name is extracted using name and used to register the callback.

    # framework/widget.py
    
    class IconButton(Widget):
        def __init__(self, child, onPressed=None, style=None):
            self.child = child
            self.onPressed = onPressed
            self.style = style or ButtonStyle()
            self.api = Api()
    
            self.onPressedName = self.onPressed.__name__ if self.onPressed else ''
    
        def to_html(self):
            button_id = f"icon_button_{id(self)}"
            style = self.style.to_css()
            child_html = self.child.to_html() if isinstance(self.child, Widget) else self.child
    
            # Register the callback with the API
            self.api.register_callback(self.onPressedName, self.onPressed)
    
            return f"""
            <button id='{button_id}' style='{style}' onclick='handleClick("{self.onPressedName}")'>
                {child_html}
            </button>
            """
    

    How It Works:

    Callback Registration: The Api class registers callbacks using the function name.

    Button HTML: The IconButton class generates HTML with the function name included in the onclick attribute.

    JavaScript Handling: A JavaScript function (not shown here) uses the function name to call the correct Python callback.

    Request for Feedback:

    I’ve implemented this approach to make the API more intuitive and avoid manual callback registration. I’d appreciate any insights or suggestions on:

    Potential Improvements: Are there more efficient or cleaner ways to handle this?

    Edge Cases: Are there edge cases I might have missed?

    Design Patterns: Are there design patterns that could improve this approach?

    Looking forward to your feedback!


  2. In Python you can simply pass functions as arguments.

    Also you can create a special type for your ButtonPress Function, so that the skeleton of your function is type-safe. Here f.e.

    from type import Callable
    ButtonFunction = Callable[[str, str], str]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search