Location>code7788 >text

Development of cross-platform desktop applications using wxpython, abstract encapsulation of base class list form processing

Popularity:157 ℃/2024-11-11 18:49:17

In the development of a system framework, in addition to focusing on the realization of the system's functionality, we should strive for excellence in all aspects of the system to do the best thing with the least amount of coding, in the development of various levels, including the front-end and back-end, interface processing, back-end processing, commonly used auxiliary classes, control encapsulation and so on, we can be through the abstraction, reuse and other ways to achieve the optimization and simplification of the code , so as to achieve rapid development purposes. in order to achieve the purpose of rapid development. This essay we will talk about the interface of the abstract iterative processing, as well as the final realization of the process.

1, the list form interface of the abstraction of iterative abstraction

For example, for the system's form, generally we can distinguish by the main functional view, a list of display interface, one is to edit/view the details of the interface, there is a previous article "TheDeveloping cross-platform desktop applications using wxpython, base class dialog form wrapper processingI've specialized in the abstract design of edit dialogs, so this essay is focused on thetabular interfaceMake a presentation.

If we roughly need to display a list of the interface, the list interface is generally divided into the query area, the list interface display area and paging information area, we divided it into two main parts, as shown in the following interface.

For the content of the query condition labeled as Zone 1, it can be further subdivided, and according to different business modules, the content has a change zone and a fixed zone, as identified below.

Different business modules, the query conditions must be different, this part for the content change area.

As for the common function buttons, basically fixed, we can later according to some conditions for dynamic button show/hide, but here business buttons on these, although the trigger interface is certainly different, but the processing logic of these buttons will not change, so it is called the fixed logic area.

For content changes, we can send them down to subclasses. Each business module's list interface is a subclass that inherits from the base class.

The business list form interface is divided into two parts

For the list interface part of the display, although it is divided into the list content and the content of the page column, but their data changes, the control is not changed, as shown in the following interface screenshot.

That is, the typographic information related to these controls we can abstract to the parent class to create, and the data can just be updated and changed by the child class.

This includes the table's column name, Chinese name corresponds to, the width of the column, the data set and other information, and paging columns which is based on the size of each page, the current page number, the total number of information such as the state of the button control can be.

 

2、Introduce generic definitions to realize richer interface control

I wrote about this in my article, "ThePython development framework based on SqlAlchemy+Pydantic+FastApiThe use of generics to build more resilient base class processing was introduced in the book

As in the case of routers, we make the interface of the base class a bit more personalized by handling the generic parameters, as shown in the following code.

In Python, theGeneric is a feature that allows type parameterization, often used for type annotations and type checking.Python's generic support is primarily provided through thetyping Modules are implemented with type hints, such asGenericTypeVarList[T] etc. There are several benefits of using generalizations to define base classes:

1. Improved type security

Generics allow classes and functions to be written with explicitly specified type parameters so that they can be used with stricter type checking and fewer type errors.

If the base class uses the genericT, so that the specific type can be specified when subclassing or instantiating (e.g., theUser), thus gaining type safety at compile time (when using an IDE or type checking tool) and avoiding type errors.

2. Enhanced code reuse

Generics allow more generalized base classes to be written without having to reimplement similar functionality for each data type, increasing code reusability.

3. Improve code readability and maintainability

Generics make type parameters explicit in class definitions, which helps developers understand the expected type of a class or function more clearly, reduces the complexity of type inference, and improves readability.

4. Integration with type hints and type checking tools

The use of type hinting has become a best practice in modern Python development. Generic definitions work better with type-checking tools such asmypypyright etc.) integration to help detect potential type errors when writing code.

5. Enhanced code flexibility

Defining base classes using generics allows classes to be more generic in their functionality so that different data handling logic can be implemented by type parameterization without modifying the class code.

 

These advantages make it especially important to use generics in large projects or library development, especially when designing generic data structures, tool classes, or frameworks.

For a generic definition, we declare a type as follows.

ModelType = TypeVar("ModelType")  # Defining a generic base class

Then you can adopt a generic type as needed. The following is the definition of the form base class, which adopts a generic type to define the subclass DTO object of the business module, thus making many interfaces of this parent class well typed and defined.

# Create the generic base class BaseFrameList and inherit the
class BaseFrameList(, Generic[ModelType]):

And for the subclasses of the business module, such as where the subclasses of the business list screen are defined as shown below.

# Inherit the BaseFrameList class and pass in the entity class SystemTypeInfo as the generic type
class FrmSystemType(BaseFrameList[SystemTypeDto]):

This way we build the list interface with the following parent-child relationship, which includes the list interface for two business modules: System Type Definition, and Customer Information.

For example, we analyze the subclass code of the customer list interface as shown below. We only need to pass in some of the required fields to display and Chinese parsing, and pass in the relevant DTO object, as shown in the following code.

# Inherit the BaseFrameList class and pass in the entity class CustomerInfo as a generic type
class FrmCustomer(BaseFrameList[CustomerDto]):
    """customer information"""

    # Displayed field names, comma-separated
    display_columns = "id,name,age,creator,createtime"
    # Column name mapping (field name to display name mapping)
    column_mapping = {
        "id": "serial number", "name": "name and surname", "age": "(a person's) age", "creator": "founder", "createtime": "Creation time",
    }

    def __init__(self, parent):
        # Initialize base class information
        super().__init__(
            parent,
            model=CustomerDto,
            display_columns=self.display_columns,
            column_mapping=self.column_mapping
        )

These elements are definitely required, in addition, if we need to customize the width of the cell columns in the list interface, we can also specify the dictionary reference for the width, and if we don't, we can just use the default width (define the default width in the base class, e.g., as 150 pixels).

For example, I have included a dictionary reference for the width of the list in the system type definition, as shown in the code below.

# Inherit the BaseFrameList class and pass in the entity class SystemTypeInfo as the generic type
class FrmSystemType(BaseFrameList[SystemTypeDto]):
    """System Type Definition"""

    # Displayed field names, comma-separated
    display_columns = "id,name,customid,authorize,note"
    # Column name mapping (field name to display name mapping)
    column_mapping = {
        "id": "system identification",
        "name": "System name",
        "customid": "customer code",
        "authorize": "authorization code",
        "note": "note",
    }
    # Column width of table display
    column_widths = {"id": 150, "name": 250, "customid": 100, "authorize": 100}

    def __init__(self, parent):
        # Initialize base class information
        super().__init__(
            parent,
            model=SystemTypeDto,
            display_columns=self.display_columns,
            column_mapping=self.column_mapping,
            column_widths=self.column_widths,
            use_left_panel=False,
        )

 

3、Extracting unchanged logic from changes, simplification of interface code

As for the content of the subclass query condition, we said earlier that it is dynamically different and therefore needs to be implemented specifically by the subclass.

The following is the query content of the customer information, we only need to add the corresponding label and input controls can be, do not need to pay attention to the layout of the processing, the default FlexGridSizer for 4 * 2 = 8 columns, each column interval 5px.

The following is the list screen for customer information, rewriting the implementation code of the parent class function

    def CreateConditions(self, pane: ) -> List[]:
        """Creating a query condition input box control in a collapsed panel"""
        # Create the control, don't worry about the layout, leave it to the CreateConditionsWithSizer control logic.
        # The default FlexGridSizer is 4*2=8 columns with 5px spacing between each column.

        lblName = (pane, -1, "Name.")
         = (pane, -1, size=(150, -1))
        lblAge = (pane, -1, "Age.")
         = (pane, -1, size=(150, -1))

        return [lblName, , lblAge, ]

If you sometimes need to redefine the layout, you can override the function that contains the layout at the level above it to achieve a higher level of customization.

For example, for the system type list screen, as follows

In order to better introduce the control of the panel layout, I rewrite its higher level function to contain the definition information of the FlexGridSizer, where our code is shown below.

    def CreateConditionsWithSizer(self, pane: ):
        """Subclasses can override this method to create the query conditions in the folded panel, including the layout of the FlexGridSizer."""

        # Create the control first
        lblSystemType = (pane, -1, "System identification.")
         = (pane, -1, "", size=(150, -1))

        lblName = (pane, -1, "System Name.")
         = (pane, -1, "", size=(150, -1))

        lblCustomCode = (pane, -1, "Customer Code.")
         = (pane, -1, "", size=(150, -1))

        lblAuthCode = (pane, -1, "Authorization code.")
         = (pane, -1, "", size=(150, -1))

        # Add Condition Panel
        input_sizer = (cols=4 * 2, hgap=5, vgap=5)

        # Unified handling of the addition of query condition controls, using the default layouts
        list = [lblSystemType, , lblName, , lblCustomCode, , lblAuthCode, ]
        for i in range(len(list)):
            input_sizer.Add(list[i], 0,  | wx.ALIGN_CENTER_VERTICAL, 5)

        # input_sizer.AddGrowableCol(1)
        # input_sizer.Add((5, 5))

        return input_sizer

 

For subclasses of the Edit View dialog box, we trigger the button to pop-up, or right-click the menu to pop-up, are the same logic, but it is the interface content is different, we can let the subclasses can be implemented.

Subclasses for the addition of new editing interface to achieve the code is shown below, which Add and OnEditById are empty functions of the parent class, by the subclass to achieve specific can be (override rewrite).

    def OnAdd(self, event: ) -> None:
        """Subclass Override - Open Add Dialog"""
        dlg = FrmSystemTypeEdit(self)
        if () == wx.ID_OK:
            # Add successfully, refresh the form
            self.update_grid()

        ()

    def OnEditById(self, entity_id: Any | str):
        """Subclass Override - Open Edit Dialog Based on Primary Key Value"""
        dlg = FrmSystemTypeEdit(self, entity_id)
        if () == wx.ID_OK:
            # Update grid list data
            self.update_grid()
        ()

Other processing, such as deletion of records, import, export processing, much the same, from the change to find the unchanged logic to the parent class processing, the subclass is responsible for the most primitive changes in the content can be.

Of course, for some complex list interface, you may also need to consider placing some tree list on the left side in order to quickly select different categories of data, such as the following Winform interface.

The content of this interface is, on the left, the two collapsed tree lists: the list of organizations and the list of roles, in order to facilitate the selection of user information.

So for such an effect, we in the base class form can be abstracted, the answer is of course yes, remember our earlier "TheDevelopment of cross-platform desktop applications using wxpython, dynamic tool creation handlingWhen the toolbar was introduced, the Manager class was used to realize the effect.

This is at the main form level though, we need to define a similar effect for the specific business listing screen.

We define a switch variable in the parent form that is used to turn on or off the left tree list panel as shown in the following code.

This way, the construction of tree lists is left to the function create_tree_panels to realize, it will build one or more tree lists, the parent interface form is responsible for integrating them to display.

If the subclass definition overrides the function that creates the tree list, as shown in the following code.

    def create_tree_panels(self) -> dict[str, ]:
        """Subclass overrides this method to create the left tree list panel - can be multiple tree lists"""
        dict = {}

        # sample code (computing)
        for key in ["List of organizations", "Character List"]:
            tree_panel = (self)
            tree_sizer = ()
            tree = (tree_panel, style=wx.TR_DEFAULT_STYLE)
            self._populate_tree(tree)
            tree_sizer.Add(tree, 1, )
            tree_panel.SetSizer(tree_sizer)

            dict[key] = tree_panel

        return dict

Then the interface effect will get as shown below.

We can just implement specific tree data displays as needed.

The above is my analysis of the interface and the gradual abstract processing, the main logic is extracted to the parent class, the change of the small part of the content, to the subclass difference in the realization of the code can be reduced to improve efficiency.