Pyside6(3): 自动生成UI的Qt参数输入对话框

news2024/9/28 13:22:51

1.前言

参数输入界面是桌面软件开发最繁琐的部分之一。特别是当系统中存在多种可编辑的数值模型时,由于各个模型的字段不同,每个字段的输入类型也不同,需要制作不同的UI,使用不同的UI控件,无疑会耗费大量时间,同时拓展性也非常差,因为每增加一个数值模型,都需要制作相应的参数输入界面。

本文提出的解决思路是将数值模型假定为类似于json的树形结构,采用递归遍历树的方式生成对应的Qt 控件树,从而实现根据不同的数值模型动态生成参数输入界面。当用户编辑完数值模型点击ok按钮时,再次遍历生成的Qt控件树,从而获取相应的输入数据。另外还具有合法值校验的功能,可以定位到非法输入对应的空间并以红色边框高亮显示的功能。效果如下:

2.将数值模型解析为Qt控件树

2.1 输入参数类型

本文涉及的数值模型字段包含字符串浮点数,整数布尔值,枚举值(用list表示)复合字段几种类型。参数类型和Qt控件之间的对应关系如下:

  • 字符串-QLineEdit

  • 浮点数- QDoubleSpinBox

  • 整数-QSpinBox

  • 布尔值-QCheckBox

  • 枚举值-QComboBox

  • 复合字段可以又可以分解成上述几种数据类型,所以对应于一个包含多个前述几种控件的QWidget。

2.2 可编辑数值模型定义

本文假设数值模型可以是不同的类,但是它们都有一个editableField属性,定义可编辑的字段信息。例如:

class A:
    @property
    def editableField(self) -> dict:
        return {
            "name": {
                "name": "名称",
                "value": "",
                "required": True
            },
            "B": {
                "name": "复合字段B",
                "value": B()
            },
        }


class B:
    @property
    def editableField(self) -> dict:
        return {
            "string": {
                "name": "字符串",
                "value": "111111"
            },
            "bool": {
                "name": "布尔值",
                "value": False,
                "required": True
            },
            "float": {
                "name": "浮点值",
                "value": 1.0,
                "unit": "km",
                "min": 0,
                "max": -1
            },
            "int": {
                "name": "整数",
                "value": 1,
                "unit": "km",
                "min": 0,
                "max": -1
            },
            "dataObj": {
                "name": "复合字段C",
                "value": C(),
                "required": True
            },
            "enum": {
                "name": "枚举值",
                "value": [1, 2, 3],
                "required": True
            }
        }


class C:
    @property
    def editableField(self) -> dict:
        return {
            "name": {
                "name": "名称",
                "value": "",
                "required": True
            },
            "gender": {
                "name": "性别",
                "value": "",
                "required": True
            }
        }


class Model:
    @property
    def editableField(self) -> dict:
        return {
            "name": {
                "name": "名称",
                "value": "",
                "required": False
            },
            "he": {
                "name": "人",
                "value": A(),
                "root": True
            },
        }

注意

editableField中定义的所有复合字段不能相互嵌套,比如B包含一个C类型的可编辑字段,C又包含一个B类型的可编辑字段,会导致解析时陷入无限循环。

2.3 对话框基本结构

对话框的基本定义如下:

from PySide6.QtGui import Qt
from PySide6.QtWidgets import QDialog, QMessageBox, QWidget, QTabWidget, QSpacerItem, QSizePolicy, QFormLayout, \
    QScrollArea, QFrame, QDialogButtonBox

from editDialog_utils import isDataObj, generate_widget, getContentLayout, checkInput, showErrorInputWidget


class AEditDialog(QDialog):
    def __init__(self, dataObj):
        """
        数据对象编辑对话框
        :param dataObj:  数据对象
        """
        super().__init__()
        self.__dataObj = dataObj
        # 用户数输入数据
        self.inputData = None
        self.resize(480, 360)
        # 控件树
        self.widget_tree = dict()
        self.__loadWidget()
        self.__connectWidget()

    def __loadWidget(self):
        """
        初始化控件
        :return:
        """
        tab = QTabWidget()
        self.tab = tab
        tab1 = QWidget()
        tab1.setLayout(getContentLayout(direction='v'))
        # 滚动视图
        tab1_scroll = QScrollArea()
        tab1_scroll.setObjectName(u"scroll")
        tab1_scroll.setWidgetResizable(True)
        tab1_scroll.setFrameShadow(QFrame.Raised)
        tab1.layout().addWidget(tab1_scroll)
        tab1_scrollArea = QWidget()
        tab1_layout = QFormLayout(tab1_scrollArea)
        tab.addTab(tab1, "common")
        # 控件所处tab索引,用于错误提示
        tab_index = 0
        # 根据数据对象动态生成ui
        for key, item in self.__dataObj.editableField.items():
            # 值
            value = item["value"]
            if isDataObj(value):
                # 顶级属性是数据对象时生成一个tab
                sub_tree = dict()
                self.widget_tree[key] = sub_tree
                # 新建tab
                tab2 = QWidget()
                tab2.setLayout(getContentLayout(direction='v'))
                # 布局和滚动视图
                scrollAreaWidgetContents = QWidget()
                content_layout = getContentLayout(direction='v')
                tab_index += 1
                # 添加控件
                w = generate_widget(sub_tree, key, item, tab_index)
                content_layout.addWidget(w)
                scrollAreaWidgetContents.setLayout(content_layout)
                # spacer
                vSpacer = QSpacerItem(40, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
                content_layout.addItem(vSpacer)
                # 滚动视图
                scroll = QScrollArea()
                scroll.setObjectName(u"scroll")
                scroll.setFrameShadow(QFrame.Raised)
                scroll.setWidgetResizable(True)
                tab2.layout().addWidget(scroll)
                scroll.setWidget(scrollAreaWidgetContents)
                # 添加tab
                tab.addTab(tab2, item["name"])
            else:
                w = generate_widget(self.widget_tree, key, item, 0)
                if isinstance(w, tuple):
                    tab1_layout.addRow(w[0], w[1])
                else:
                    tab1_layout.addWidget(w)
        # 添加控件到tab1 滚动视图
        tab1_scroll.setWidget(tab1_scrollArea)
        # 外部容器
        container_layout = getContentLayout(direction='v', margin=(4, 4, 4, 4))
        container_layout.addWidget(tab)
        # 添加底部按钮
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setObjectName(u"buttonBox")
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
        container_layout.addWidget(self.buttonBox)
        # 设置布局
        self.setLayout(container_layout)
        # 只有一页,隐藏tabbar
        if tab.count() == 1:
            tab.tabBar().setVisible(False)

    def __connectWidget(self):
        """
        连接槽函数
        :return:
        """
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

    def accept(self) -> None:
        """
        确定
        :return:
        """
        input_data = dict()
        # 输入数据校验
        inputData = checkInput(self.widget_tree, data_key="", input_data=input_data)
        if "widget" in inputData.keys():
            showErrorInputWidget(errorData=inputData, tab=self.tab)
            # 显示数据错误提示
            QMessageBox.critical(self, "错误", str("{} 必须填写!".format(inputData["name"])))
            return
        self.inputData = inputData
        super().accept()

上述QDialog中,控件树被保存在widget_tree字典中。这里最主要的函数是__loadWidget。这个函数首先创建一个QTabWidget添加到对话框中,然后开始遍历数值模型的editableField进行控件解析。弹字段是基本数据类型时,调用generate_widget生成控件添加到当前tab;当字段是个复合字段时,它会新建一个tab并设置滚动视图,然后将它添加到QTabWidget中。其中generate_widget正是递归遍历数值模型字段生成控件树的方法。

accpet函数是用户点击ok按钮时调用的方法。检查用户输入和高亮显示错误输入的方法checkInputshowErrorInputWidget将在后面介绍。

isDataObjgetContentLayout是辅助方法,分别用于判断复合字段和生成样式统一的QVBoxLayoutQHBoxLayout。定义如下:

def isDataObj(data):
    """
    判断数据是否是基本类型之外的数据对象
    :param data:
    :return:
    """
    return type(data) not in [str, float, int, list, tuple, bool]


def getContentLayout(direction: str = "v", margin: tuple = (0, 0, 0, 0)) -> QVBoxLayout:
    """
    生成存放内容的布局
    :param margin:
    :param direction:
    :return:
    """
    if direction == "v":
        layout = QVBoxLayout()
    else:
        layout = QHBoxLayout()
    layout.setContentsMargins(*margin)
    return layout

2.4 解析得到Qt控件树

首先根据基本数据类型生成控件:

def generateFloatWidget(item: dict) -> QDoubleSpinBox:
    """
    生成浮点数值控件
    :param item:
    :return:
    """
    dSpinbox = QDoubleSpinBox()
    minVal = 0
    maxVal = 0
    if "min" in item.keys():
        minVal = item["min"]
    if "max" in item.keys():
        maxVal = item["max"]
    if minVal > maxVal:
        # 设置无穷大
        maxVal = 1e20
        minVal = -maxVal
    dSpinbox.setMinimum(minVal)
    dSpinbox.setMaximum(maxVal)
    dSpinbox.setValue(item["value"])
    dSpinbox.setMinimumHeight(24)
    return dSpinbox

def generateIntWidget(item: dict) -> QSpinBox:
    """
    生成整数控件
    :param item:
    :return:
    """
    spinbox = QSpinBox()
    minVal = 0
    maxVal = 0
    if "min" in item.keys():
        minVal = item["min"]
    if "max" in item.keys():
        maxVal = item["max"]
    if minVal > maxVal:
        # 设置无穷大
        maxVal = int(1e9)
        minVal = -maxVal
    spinbox.setMinimum(minVal)
    spinbox.setMaximum(maxVal)
    spinbox.setValue(item["value"])
    spinbox.setMinimumHeight(24)
    return spinbox


def generateStrWidget(item: dict) -> QLineEdit:
    """
    生成单行文本输入控件
    :param item:
    :return:
    """
    lineEdit = QLineEdit()
    lineEdit.setMinimumHeight(24)
    value = item["value"]
    lineEdit.setText(value)
    return lineEdit


def generateBoolWidget(item: dict) -> QCheckBox:
    """
    生成布尔值输入控件
    :param item:
    :return:
    """
    checkBox = QCheckBox()
    name = item["name"]
    value = item["value"]
    checkBox.setChecked(value)
    checkBox.setText(name)
    return checkBox


def generateEnumWidget(item: dict) -> QComboBox:
    """
    生成枚举值对应的列表控件
    :param item:
    :return:
    """
    comboBox = QComboBox()
    comboBox.setMinimumHeight(24)
    value = item["value"]
    value = [str(i) for i in value]
    comboBox.addItems(value)
    return comboBox

然后递归遍历数值模型字段:

# 展开和收起图标
ic_down_arrow = "ic_down_arrow.svg"
ic_right_arrow = "ic_right_arrow.svg"

def generate_widget(widget_tree: dict, key, item, tab_index: int):
    """
    根据数据对象生成控件
    :param tab_index: 顶层tab索引
    :param item: 数据对象
    :param key: 数据键
    :param widget_tree: 控件树
    :return:
    """
    # 数据项名称
    name = item["name"]
    # 值
    value = item["value"]
    # 单位
    unit = None
    if "unit" in item.keys():
        # 单位
        unit = item["unit"]

    # 控件容器
    w = QWidget()
    w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

    # 字段名称和单位
    if unit is not None:
        label = QLabel("{}({}):".format(name, unit))
    else:
        label = QLabel("{}:".format(name))
    label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)

    required = False
    if "required" in item.keys():
        # 必填字段
        required = item["required"]

    widget_tree_node = {"tab_index": tab_index, "required": required, "name": name}

    if type(value) is float:
        doubleSpinBox = generateFloatWidget(item)
        widget_tree_node["widget"] = doubleSpinBox
        widget_tree[key] = widget_tree_node
        return label, doubleSpinBox

    if type(value) is int:
        intSpinBox = generateIntWidget(item)
        widget_tree_node["widget"] = intSpinBox
        widget_tree[key] = widget_tree_node
        return label, intSpinBox

    if type(value) is str:
        lineEdit = generateStrWidget(item)
        widget_tree_node["widget"] = lineEdit
        widget_tree[key] = widget_tree_node
        return label, lineEdit

    if type(value) is bool:
        checkBox = generateBoolWidget(item)
        widget_tree_node["widget"] = checkBox
        widget_tree[key] = widget_tree_node
        return checkBox

    if type(value) is list:
        comboBox = generateEnumWidget(item)
        widget_tree_node["widget"] = comboBox
        widget_tree[key] = widget_tree_node
        return label, comboBox

    if isDataObj(value):
        # 复合字段容器
        container = QWidget()
        container_layout = getContentLayout(direction='v')
        container_layout.setSpacing(0)
        container.setLayout(container_layout)
        # 子控件容器
        contentWidget = QWidget()
        contentWidget.setObjectName(u"contentWidget")
        if "root" not in item.keys() or not item['root']:
            # 非顶级对象添加收放按钮
            btnCollapse = QPushButton()
            btnCollapse.setObjectName(u"btnCollapse")
            btnCollapse.setLayout(getContentLayout(direction='h'))
            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            btnCollapse.setSizePolicy(sizePolicy)
            btnCollapse.setFocusPolicy(Qt.NoFocus)
            btnCollapse.setFlat(True)
            # 图标
            iconBtn = QPushButton(btnCollapse)
            iconBtn.setObjectName(u"iconBtn")
            iconBtn.setFlat(True)
            iconBtn.setIconSize(QSize(20, 20))
            iconBtn.setIcon(QIcon(ic_down_arrow))
            btnCollapse.layout().addWidget(iconBtn)
            # 名称
            labelName = QLabel(name)
            labelName.setStyleSheet("font-size:12pt")
            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            labelName.setSizePolicy(sizePolicy)
            btnCollapse.layout().addWidget(labelName)
            container.layout().addWidget(btnCollapse)
            # 收放槽函数
            iconBtn.clicked.connect(partial(collapseWidget, iconBtn, contentWidget))
            btnCollapse.clicked.connect(partial(collapseWidget, iconBtn, contentWidget))

        contentWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        contentWidget_layout = QFormLayout()
        # formlayout换行策略
        contentWidget_layout.setRowWrapPolicy(QFormLayout.WrapLongRows)
        contentWidget.setLayout(contentWidget_layout)
        # 添加子控件
        for key, item in value.editableField.items():
            sub_tree = dict()
            widget_tree[key] = sub_tree
            w = generate_widget(sub_tree, key, item, tab_index)
            if isinstance(w, tuple):
                # 子控件有label
                contentWidget_layout.addRow(w[0], w[1])
            else:
                # 无label,跨列
                row_index = contentWidget_layout.rowCount()
                contentWidget_layout.setWidget(row_index, QFormLayout.SpanningRole, w)
        container.layout().addWidget(contentWidget)
        return container

上述函数中,当字段类型是基本数据类型时,生成上述几种基本控件,添加到控件树中;当字段是复合字段时,新建一个QWidget作为容器,向其中添加一个收放按钮,然后新建一个contentWidget,递归生成子控件放入其中。每次遇到一个复合字段类型就向控件树中添加一个树枝,即sub_tree字典。函数collapseWidget用来收放控件,定义为:

def collapseWidget(iconBtn: QPushButton, collapsedWidget: QWidget, hold: bool = None):
    """
    收放控件
    :param hold: 保持某个状态
    :param iconBtn: 指示图标控件
    :param collapsedWidget:
    :return:
    """
    show = not collapsedWidget.isVisible()
    if hold is not None:
        show = hold
    if show:
        icon = QIcon(ic_down_arrow)
    else:
        icon = QIcon(ic_right_arrow)
    collapsedWidget.setVisible(show)
    iconBtn.setIcon(icon)

3.检查并获取用户输入

3.1 遍历控件树获取并检查用户输入

获取用户输入其实就是把控件树,即widget_tree这个字典再遍历一遍。首先定义获取单个控件输入的函数:

def getWidgetInput(widget):
    """
    获取单个控件输入
    :param widget:
    :return:
    """
    if type(widget) == QLineEdit:
        return widget.text()
    if type(widget) == QCheckBox:
        return widget.isChecked()
    if type(widget) == QDoubleSpinBox:
        return widget.value()
    if type(widget) == QSpinBox:
        return widget.value()
    if type(widget) == QComboBox:
        return widget.currentText()

然后定义检查单个控件输入的函数:

def checkWidget(widget, required: bool) -> bool:
    """
    检查单个控件输入
    :param widget:
    :param required: 是否必填
    :return:
    """
    if not required:
        # 非必填
        return True
    # 检查必填
    filled = False
    if type(widget) == QLineEdit:
        filled = widget.text() != ""
    if type(widget) == QCheckBox:
        filled = widget.isChecked()
    if type(widget) == QComboBox:
        filled = widget.currentText() != ""
    return filled

然后递归遍历控件树,获取并检查用户输入:

def checkInput(widget_tree: dict, data_key: str, input_data: dict):
    """
    获取并检查用户输入
    :param input_data: 存储输入数据
    :param data_key: 数据键,a.b.c
    :param widget_tree: 控件树
    :return:
    """
    for key, value in widget_tree.items():
        if "widget" not in value.keys():
            # value是个树节点
            if data_key != "":
                key = "{}.{}".format(data_key, key)
            input_data = checkInput(value, key, input_data)
            if "widget" in input_data.keys():
                # 出错终止
                return input_data
        else:
            # value是叶
            widget = value["widget"]
            tab_index = value["tab_index"]
            required = value["required"]
            name = value["name"]
            if not checkWidget(widget, required):
                # 边框显示为红色
                widget.setStyleSheet("border:1px solid red;")
                # 检查到输入错误,切换到出错的那个tab
                return {"name": name, "widget": widget, "tab_index": tab_index}
            else:
                # 清空错误样式
                widget.setStyleSheet(QWidget().styleSheet())

            if data_key == "":
                # 顶层数据字段
                input_data[key] = getWidgetInput(widget)
            else:
                # 控件树叶的数据字段
                input_data[data_key] = getWidgetInput(widget)
    return input_data

获取到的用户输入如下:

{
  'name': 'comm名称',
  'he.name': '人名',
  'he.dataObj_B.string': '111111',
  'he.dataObj_B.bool': True,
  'he.dataObj_B.float': 1.0,
  'he.dataObj_B.int': 1,
  'he.dataObj_B.dataObj_C.name': 'C名称',
  'he.dataObj_B.dataObj_C.gender': 'C性别',
  'he.dataObj_B.enum': '1'
}

因为python对象有setattr函数可以设置属性,所以这里用户输入不用嵌套成树形结构,可以少用递归,改用循环,提高运行速度,具体见第4节。

3.2 定位并高亮显示错误输入

当发现某个控件存在非法输入时,checkInput函数停止递归并返回出错控件的信息,例如:

{
  'name': '名称',
  'widget': <PySide6.QtWidgets.QLineEdit(0x1b9b2830)at 0x000000001C4A9200>,
  'tab_index': 1
}

其中tab_index表示该控件所处的tab索引。高亮显示错误输入位置的函数如下:

def showErrorInputWidget(errorData: dict, tab: QTabWidget):
    """
    显示输入错误的控件
    :param tab: 顶层tab
    :param errorData: 错误数据
    :return:
    """
    # 切换到所处tab
    tab.setCurrentIndex(errorData["tab_index"])
    # 取出对应错误控件
    widget = errorData["widget"]
    if not widget.isVisible():
        # 展开未展开的父节点显示控件
        parent = widget
        while True:
            parent = parent.parent()
            if parent is None:
                break
            # 查找下拉图标按钮和收放控件
            iconBtn = parent.findChild(QPushButton, "iconBtn")
            contentWidget = parent.findChild(QWidget, u"contentWidget")
            if contentWidget is not None and not contentWidget.isVisible():
                #展开
                collapseWidget(iconBtn, contentWidget, hold=True)

    # 滚动到控件所在位置
    tab_page = tab.currentWidget()
    scrollArea = tab_page.findChild(QScrollArea, "scroll")
    pointTab = tab_page.mapToGlobal(QPoint(0, 0))
    # 计算控件是否在滚动区可视范围内
    y1 = pointTab.y()
    h1 = tab_page.height()
    pointW = widget.mapToGlobal(QPoint(0, 0))
    y2 = pointW.y()
    h2 = widget.height()
    cond1 = y2 + h2 < y1 - 4
    cond2 = y1 + h1 < y2 + 4
    dy = 0
    # 计算滚动距离
    if cond1:
        dy = y2 - y1 - 4
    if cond2:
        dy = (y2 + h2) - (y1 + h1) + 4
    # 滚动到错误控件
    verticalScrollBar = scrollArea.verticalScrollBar()
    verticalScrollBar.setValue(verticalScrollBar.value() + dy)

4.将用户输入写入数值模型对象

将用户输入写入数值模型对象的函数如下,由于Python是个解释型语言,可以动态为对象设置属性,所以这里用了循环而不是递归来提高效率。

class Field:
    """
    字段属性
    """


def setField(self, data: dict):
    """
    设置字段
    :param self: 数值模型
    :param data:
    :return:
    """
    for key, value in data.items():
        if "." not in key:
            setattr(self, key, value)
            continue
        # 根据键定位到复合字段类型
        dataObj = self
        sub_keys = str(key).split(".")
        for sub_key in sub_keys[:-1]:
            try:
                dataObj = getattr(dataObj, sub_key)
            except AttributeError:
                # 没有预创建该属性则新建属性
                field = Field()
                setattr(dataObj, sub_key, field)
                dataObj = field
        setattr(dataObj, sub_keys[-1], value)

可以采用如下方式统一为数值模型添加setField方法:

class A:
    def __init__(self):
        self.setField = partial(setField, self)


class B:
    def __init__(self):
        self.setField = partial(setField, self)


class C:
    def __init__(self):
        self.setField = partial(setField, self)



class Model:
    def __init__(self):
        self.setField = partial(setField, self)

运行结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vite+vue3 proxy配置代理服务器解决本地运行跨域问题

vitevue3 proxy配置代理服务器解决本地运行跨域问题 1. 什么是跨域呢&#xff1f; 首先&#xff0c;明白什么是同源策略&#xff1f;同源就是指 协议、域名、端口 都要相同&#xff0c;其中任何一个不同都会出现跨域。例如&#xff1a; http://www.xxx.com:8000 // http 是协…

2023,为什么我的简历还是石沉大海?

对于应聘者来说 &#xff0c;我们经常见到这样的情况 &#xff0c;投递的简历要么是已送达&#xff08;未读&#xff09;&#xff0c;要么是已读不回 &#xff0c;也有的是沟通上几句就没有了下文 。对于这样的结果我们是既好奇又郁闷 &#xff0c;好奇的是为啥大多数的简历都石…

新库上线 | CnOpenDataA股上市公司交易所监管措施数据

A股上市公司交易所监管措施数据 一、数据简介 证券市场监管是指证券管理机关运用法律的、经济的以及必要的行政手段&#xff0c;对证券的募集、发行、交易等行为以及证券投资中介机构的行为进行监督与管理。 我国《证券交易所管理办法》第十二条规定&#xff0c;证券交易所应当…

数据结构刷题

数据结构刷题 文章目录数据结构刷题计算时间复杂度练习题答案不带头结点的单链表的插入和删除运算数据结构头插法和尾插法建立单链表二叉树各种方法实现数据结构图的练习题习题答案习题计算时间复杂度练习题 1、设 n 为正整数。试确定下列各程序段中前置以记号的语句的频度。 …

课程规范性要求

课程制作规范 图片规范 允许范围&#xff1a;CC协议 / 作者授权 / 网站代理授权书 图片大小要求&#xff1a;1600 x 1200 dpi 图片长宽比&#xff1a;4&#xff1a;3 每章节格式要求 Week number 本周目标 1.通过背景学习&#xff0c;了解四足机器狗mini pupper上的微型控…

Redis实战—黑马点评(二)缓存篇

Redis实战—黑马点评&#xff08;二&#xff09;缓存篇 目录Redis实战—黑马点评&#xff08;二&#xff09;缓存篇1. 什么是缓存1.1 缓存的作用和成本2. 添加 Redis 缓存3. 缓存更新策略3.1 三种更新策略3.1.1 主动更新策略4. 缓存穿透4.1 常见两种解决办法4.1.1 缓存空值4.1.…

数仓理论【范式】【维度建模】

数仓理论 1 范式理论 1.1 范式概念 数据建模要遵循一定的规则&#xff0c;在关系建模中&#xff0c;这种规则就是范式 采用范式结构&#xff0c;可以有效的降低数据的冗余性 范式在获取数据时&#xff0c;需要通过join拼接出数据 范式有第一范式(1NF)&#xff0c;第二范式…

双击-jar包无法运行解决方法

我自己是通过探索出来的方法解决的&#xff0c;网上的方法适合普通问题 网络流传方法 那种-jar和run.bat的就是曲解了问题意思&#xff0c;问题不是如何运行&#xff0c;而是如何双击jar包就可以直接运行。 普通小问题就是修改注册表&#xff0c;将java路径写进去后面加个 %1…

基于JDBC框架的事务管理

事务: Transaction, 是数据库中的一种能够保证多个写操作要么全部成功, 要么全部失败的机制在基于Spring JDBC的数据库编程中, 在业务方法上添加Transactional注解, 即可使得这个业务方法是事务性的举例, 一个银行转账操作, 转账时需要执行的sql语句大致是:UPDATE 存款表 SET 余…

前端必学的CSS制作Switch动画开关按钮演示

目录 前言 CSS 制作的 Switch 动画开关按钮 1.Html构建 2.CSS编写 3.完整代码 index.html文件 style.css文件 总结 前言 随着前端技术的不断发展与进步&#xff0c;界面交互的样式要求和美感也越来越高&#xff0c;很多网页的交互都加上了css动画,这里作者给大家分享一…

【参加CUDA线上训练营】共享内存实例1:矩阵转置实现及其优化①

【参加CUDA线上训练营】共享内存实例1&#xff1a;矩阵转置实现及其优化①1.完整代码2.原理介绍2.1 将各block 线程对应元素放入共享内存tile2.2 实现转置2.3 在此基础上修改参考文献本文参考Nvidia官方blog[An Efficient Matrix Transpose in CUDA C/C及其对应的github代码tra…

表情包可视化编辑、生成配置信息数据工具

合成GIF图片 - 表情包 后续&#xff0c;用于快速、便捷生成 img_config.js 中 要生成的GIF每一帧数据&#xff08;写入头像图片信息参数&#xff09;&#xff1b; 1、先上传 写入GIF中头像 标准图&#xff0c;同时获取图片信息&#xff0c;更新 写入GIF中头像 初始值&#xff0…

5-HT2A靶向药物|适应症|市场销售-上市药品前景分析

据世界卫生组织称&#xff0c;抑郁症是一种多因素疾病&#xff0c;影响全球约3.5 亿人。中枢神经系统最广泛的单胺 - 血清素 (5-HT) 被认为在这种情况的病理机制中起着至关重要的作用&#xff0c;并且神经递质的重要性被“血清素假说”提升&#xff0c;将抑郁症的存在联系起来 …

配置Qt Creator

前言 为了使Qt Creator更像您最喜欢的代码编辑器或IDE&#xff0c;您可以更改键盘快捷键、配色方案、通用高亮显示、代码片段和版本控制系统的设置。 检查生成和运行设置 Qt Creator是一个集成开发环境(IDE)&#xff0c;可以用来开发Qt应用程序。虽然您可以使用Qt Installer…

聊天不发表情包会不习惯吗,Python带你轻松采集上万个表情包

前言 (&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e;嗨 大家好&#xff0c;这里是小圆 聊天没表情包你会有点不习惯的感jio吗 就比如新注册了个qq或者微信再或者企业微信 emmm我就是这样拿到新账号后&#xff0c;跟别人聊聊天想发送表情包 &#xff0c;…

C++-类和对象(下)

C-类和对象&#xff08;下&#xff09;一&#xff0c;const成员函数二&#xff0c;再谈构造函数1&#xff0c;初始化列表2&#xff0c;explicit关键字三&#xff0c;static成员四&#xff0c;友元&#xff08;friend&#xff09;1&#xff0c;全局函数做友元2&#xff0c;类做友…

冷冻电镜 - ChimeraX Density Map 密度图 操作

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://blog.csdn.net/caroline_wendy/article/details/129055160 由冷冻电镜所生成的Volume,需要观察其内部结构,使用ChimeraX进行操作。 加载Volumes,例如my_volume.mrc 效果如下: 高斯滤波 在命令行(Co…

python 数据分析可视化实战 超全 附完整代码数据

代码数据&#xff1a;https://download.csdn.net/download/qq_38735017/873799141.1 数据预处理1.1.1 异常值检测①将支付时间转为标准时间的过程中发生错误&#xff0c;经排查错误数据为‘2017/2/29’,后将其修改为‘2017/2/27’。②经检测发现部分订单应付金额与实付金额都为…

解决jupyter以及windows系统中pycharm编译器画图的中文乱码问题大全

一、jupyter环境下中文乱码问题解决 我们在jupyter的notebook中使用matplotlib画图的时候&#xff0c;经常性的会遇见一些中文乱码显示□的情况,如下所示: 在此&#xff0c;网上给出的方法大多是以下的解决方法&#xff1a; import matplotlib.pyplot as pltplt.rcParams[fo…

界面组件Telerik UI for WinForms R1 2023——全新的Windows 11主题

Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件。所有的UI for WinForms控件都具有完整的主题支持&#xff0c;可以轻松地帮助开发人员在桌面和平板电脑应用程序提供一致美观的下一代用户体验。Telerik UI for WinForms组件发布了2023年第一个重大版本…