【日常收支賬本】【Day03】完成編輯賬本界面的新增動賬記錄功能——通過ElementTree加XPath實現

来源:https://www.cnblogs.com/LinfengBingyi/archive/2023/09/29/17716008.html
-Advertisement-
Play Games

本文主要涉及的問題:用ElementTree和XPath讀寫XML文件;解決ElementTree新增元素後再寫入格式不統一的問題;QTableWidget單元格設置控制項 ...


一、項目地址

https://github.com/LinFeng-BingYi/DailyAccountBook

二、新增

1. 解析xml文件

1.1 功能詳述

解析所設計的xml文件格式,並將所得數據存入變數。

→→→點擊查看xml格式←←←
<DailyAccountBook>
    <balance>
        <fund>
            <value>5000.00</value>
            <category>0</category>
            <fundName>微信零錢</fundName>
        </fund>
        <fund>
            <value>999.00</value>
            <category>1</category>
            <fundName>中國銀行卡</fundName>
        </fund>
        <fund>
            <value>90.00</value>
            <category>2</category>
            <fundName>羊城通</fundName>
        </fund>
        <fund>
            <value>1700.07</value>
            <category>3</category>
            <fundName>支付寶餘額寶</fundName>
        </fund>
        <fund>
            <value>5000.00</value>
            <category>4</category>
            <fundName>代管存款</fundName>
        </fund>
    </balance>
    <year value="2023">
        <month value="09">
            <day value="11">
                <expenses>
                    <expense necessity="True" associatedFund="None">
                        <value>5.00</value>
                        <category>1</category>
                        <detail>地鐵</detail>
                        <describe>早上上班。羊城通卡餘額=100-5=95元</describe>
                        <from>2</from>
                    </expense>
                    <expense necessity="True" associatedFund="None">
                        <value>5.00</value>
                        <category>1</category>
                        <detail>地鐵</detail>
                        <describe>晚上下班。羊城通卡餘額=95-5=90元</describe>
                        <from>2</from>
                    </expense>
                    <expense necessity="False" associatedFund="None">
                        <value>1.00</value>
                        <category>12</category>
                        <detail>業務手續費</detail>
                        <describe>微信零錢提現1000的手續費</describe>
                        <from>0</from>
                    </expense>
                </expenses>
                <incomes>
                    <income associatedFund="4">
                        <value>3000.00</value>
                        <category>5</category>
                        <!-->代管存款的收支,應屬於“非本人相關”類別<-->
                        <detail>轉賬</detail>
                        <describe>托管人轉給本人3000,幫忙存放。本人微信零錢餘額=3000+3000=6000元;同時代管存款餘額=2000+3000=5000元</describe>
                        <to>0</to>
                    </income>
                    <income associatedFund="None">
                        <value>0.07</value>
                        <category>2</category>
                        <detail>理財</detail>
                        <describe>昨日餘額寶收益。餘額=1700+0.07=1700.07</describe>
                        <to>3</to>
                    </income>
                </incomes>
                <movements>
                    <!-->存款賬戶之間的資金轉移不記錄在收支變化量中,僅修改賬戶餘額。由於兩個賬戶餘額一增一減,而總量不變,記錄下來反而影響個人收支統計<-->
                    <movement>
                        <value>999.00</value>
                        <detail>提現</detail>
                        <describe>從微信零錢向中國銀行卡提現1000元。完成後微信零錢餘額=6000-999-1=5000;中國銀行卡餘額=0+999=999;被收取0.1%的手續費</describe>
                        <from>0</from>
                        <to>1</to>
                    </movement>
                </movements>
                <variation>
                    <fund>
                        <category>0</category>
                        <out>1.00</out>
                        <in>3000.00</in>
                    </fund>
                    <fund>
                        <category>1</category>
                        <out>0.00</out>
                        <in>0.00</in>
                    </fund>
                    <fund>
                        <category>2</category>
                        <out>10.00</out>
                        <in>0.00</in>
                    </fund>
                    <fund>
                        <category>3</category>
                        <out>0.00</out>
                        <in>0.07</in>
                    </fund>
                    <fund>
                        <category>4</category>
                        <out>0.00</out>
                        <in>3000.00</in>
                    </fund>
                </variation>
            </day>
        </month>
    </year>
</DailyAccountBook>

解析目的:

  • balance元素中各項fund子元素存入列表,列表中每一項都是一個代表fund元素的字典;
  • 將對應日期的day元素中各種動賬類型記錄集合存入字典,該字典內容格式如下:
day_dict = {
    'expenses': [expense_dict1, expense_dict2, ...],
    'incomes': [income_dict1, ...],
    'movements': [movement_dict1, ...],
    'variation': [fund_dict1, ...]
}

1.2 代碼實現

解析balance元素:

    def parseBalance(self):
        e_balance = self.e_dailyAccountBook.find(".//balance")
        balance_list = []
        for e_fund in list(e_balance):
            balance_dict = {"value": float(e_fund.find('.//value').text),
                            "category": int(e_fund.find('.//category').text),
                            "fundName": e_fund.find('.//fundName').text}
            balance_list.append(balance_dict)
        return balance_list

解析day元素:

    def getSpecificDateElement(self, date_str):
        """
        Describe: 根據日期字元串獲取指定的day元素
        Args:
            date_str: str
                格式為"yyyyMMdd"
        Returns:
            若找到指定日期的元素,則返回Element類型的day元素.
            若未找到指定year,則返回int類型的0;若未找到指定month,則返回int類型的1;若未找到指定day,則返回int類型的2。
            int型返回值用於控制從何處開始初始化日期元素。
        """
        e_year = self.e_dailyAccountBook.find(".//year[@value='{}']".format(date_str[:4]))
        if e_year is None:
            return 0
        e_month = e_year.find(".//month[@value='{}']".format(date_str[4:6]))
        if e_month is None:
            return 1
        e_day = e_month.find(".//day[@value='{}']".format(date_str[6:]))
        if e_day is None:
            return 2
        return e_day

    def parseSpecificDateElement(self, date_str):
        e_date = self.getSpecificDateElement(date_str)
        if isinstance(e_date, int):
            print("未找到這一天的數據!")
            return None

        parse_dict = dict()
        for child_node in list(e_date):
            print(child_node.tag)
            if child_node.tag == 'expenses':
                e_expenses = e_date.find(".//expenses")
                expenses_list = [self.parseExpense(e_expense) for e_expense in list(e_expenses)]
                parse_dict['expenses'] = expenses_list
            elif child_node.tag == 'incomes':
                e_incomes = e_date.find(".//incomes")
                incomes_list = [self.parseIncome(e_income) for e_income in list(e_incomes)]
                parse_dict['incomes'] = incomes_list
            elif child_node.tag == 'movements':
                e_movements = e_date.find(".//movements")
                movements_list = [self.parseMovement(e_movement) for e_movement in list(e_movements)]
                parse_dict['movements'] = movements_list
            elif child_node.tag == 'variation':
                e_variation = e_date.find(".//variation")
                variation_list = [self.parseVariation(e_fund) for e_fund in list(e_variation)]
                parse_dict['variation'] = variation_list
            else:
                print("未知類型的節點名")

        return parse_dict

    def parseExpense(self, e_expense):
        expense_dict = {
            'necessity': True if (e_expense.attrib['necessity'].lower() == 'true') else False,
            'value': float(e_expense.find('.//value').text),
            'category': int(e_expense.find('.//category').text),
            'detail': e_expense.find('.//detail').text,
            'describe': e_expense.find('.//describe').text,
            'from': int(e_expense.find('.//from').text),
            'associatedFund': int(e_expense.attrib['associatedFund']) if (
                    ('associatedFund' in e_expense.attrib) and e_expense.attrib['associatedFund'] != 'None') else None
        }

        return expense_dict

    def parseIncome(self, e_income):
        pass

    def parseMovement(self, e_movement):
        pass

    def parseVariation(self, e_fund):
        pass

2. 編輯賬本界面-新增行

2.1 功能詳述

在選擇文件或不同的日期後,解析xml文件中對應日期的收支記錄,將其展示在QTableWidget中。此外,表格中最後一列增加兩種操作控制項:

  • 對於已存在記錄的行:包含修改、刪除按鈕;
  • 對於空白行:包含新增按鈕。點擊新增後,該行變成已存在記錄的行,故而操作控制項也相應地變化,同時表格再新增一行空白行

2.2 代碼實現

    def responseSelectedDateChanging(self):
        if not self.lineEdit_file_path.text():
            print("還未選擇文件!")
            return
        self.file_processor = AccountBookXMLProcessor(self.lineEdit_file_path.text())
        self.file_parse_result = self.file_processor.parseSpecificDateElement(self.dateEdit.text().replace('/', ''))
        print(self.file_parse_result)
        if self.file_parse_result is None:
            self.file_parse_result = {}

        if 'expenses' not in self.file_parse_result:
            self.file_parse_result['expenses'] = []
        if 'incomes' not in self.file_parse_result:
            self.file_parse_result['incomes'] = []
        if 'movements' not in self.file_parse_result:
            self.file_parse_result['movements'] = []
        if 'variation' not in self.file_parse_result:
            self.file_parse_result['variation'] = []

        self.updateExpenseTable(self.file_parse_result['expenses'])
        self.updateIncomeTable(self.file_parse_result['incomes'])
        self.updateMovementTable(self.file_parse_result['movements'])

    def updateExpenseTable(self, expenses_list):
        self.tableWidget_expense.setRowCount(0)
        self.tableWidget_expense.setRowCount(len(expenses_list)+1)

        current_row = 0
        for expense_dict in expenses_list:
            self.tableWidget_expense.setItem(current_row, 0, QTableWidgetItem(str(expense_dict['necessity'])))
            self.tableWidget_expense.setItem(current_row, 1, QTableWidgetItem(str(expense_dict['value'])))
            self.tableWidget_expense.setItem(current_row, 2, QTableWidgetItem(str(expense_dict['category'])))
            self.tableWidget_expense.setItem(current_row, 3, QTableWidgetItem(str(expense_dict['detail'])))
            self.tableWidget_expense.setItem(current_row, 4, QTableWidgetItem(str(expense_dict['describe'])))
            self.tableWidget_expense.setItem(current_row, 5, QTableWidgetItem(str(expense_dict['from'])))
            self.tableWidget_expense.setItem(current_row, 6, QTableWidgetItem(str(expense_dict['associatedFund'])))
            self.tableWidget_expense.setCellWidget(current_row, 7, self.buttonsForExistRow(self.tableWidget_expense))
            current_row += 1

        self.tableWidget_expense.setItem(current_row, 4, QTableWidgetItem(' '))
        self.tableWidget_expense.setCellWidget(current_row, 7, self.buttonsForNewRow(self.tableWidget_expense))

    def updateIncomeTable(self, incomes_list):
        pass

    def updateMovementTable(self, movements_list):
        pass

    def buttonsForExistRow(self, tableWidget):
        widget = QWidget()
        # 更新
        updateBtn = QPushButton('更新')
        updateBtn.clicked.connect(lambda: self.updateTableRow(tableWidget))
        # 刪除
        deleteBtn = QPushButton('刪除')
        deleteBtn.clicked.connect(lambda: self.deleteTableRow(tableWidget))

        hLayout = QHBoxLayout(widget)
        hLayout.addWidget(updateBtn)
        hLayout.addWidget(deleteBtn)
        hLayout.setContentsMargins(5, 2, 5, 2)
        return widget

    def buttonsForNewRow(self, tableWidget):
        widget = QWidget()
        # 新增
        newBtn = QPushButton('新增')
        newBtn.clicked.connect(lambda: self.newTableRow(newBtn, tableWidget))

        hLayout = QHBoxLayout(widget)
        hLayout.addWidget(newBtn)
        hLayout.setContentsMargins(5, 2, 5, 2)
        return widget

    def updateTableRow(self, toggledBtn, tableWidget):
        pass

    def deleteTableRow(self, toggledBtn, tableWidget):
        pass

    def newTableRow(self, toggledBtn, tableWidget):
        print('觸發了新增按鈕')
        # 獲取觸發信號的控制項所在行號
        row = tableWidget.indexAt(toggledBtn.parent().pos()).row()
        new_data_dict = dict()
        if tableWidget == self.tableWidget_expense:
            current_column_head = TABLEWIDGET_EXPENSE_COLUMN_HEAD
        elif tableWidget == self.tableWidget_income:
            current_column_head = TABLEWIDGET_INCOME_COLUMN_HEAD
        elif tableWidget == self.tableWidget_movement:
            current_column_head = TABLEWIDGET_MOVEMENT_COLUMN_HEAD
        else:
            print('未知控制項觸發新增按鈕!')
            return
        # 用新增行數據構建字典
        for i in range(tableWidget.columnCount()-1):
            new_data_dict[current_column_head[tableWidget.horizontalHeaderItem(i).text()]] = tableWidget.item(row, i).text()
        print(new_data_dict)

        # 插入新空行
        insert_pos = tableWidget.rowCount()
        tableWidget.insertRow(insert_pos)
        # 新空行"操作"列初始化按鈕
        tableWidget.setCellWidget(insert_pos, tableWidget.columnCount()-1, self.buttonsForNewRow(tableWidget))
        # 新增行"操作"列初始化按鈕
        tableWidget.setCellWidget(insert_pos-1, tableWidget.columnCount()-1, self.buttonsForExistRow(tableWidget))

        if tableWidget == self.tableWidget_expense:
            # 將"描述"欄位預置空格
            tableWidget.setItem(insert_pos, 4, QTableWidgetItem(' '))
            # 用新增行的數據組織文件結構
            self.file_processor.organizeExpense(new_data_dict, self.dateEdit.text().replace('/', ''))
        elif tableWidget == self.tableWidget_income:
            tableWidget.setItem(insert_pos, 3, QTableWidgetItem(' '))
            self.file_processor.organizeIncome(new_data_dict, self.dateEdit.text().replace('/', ''))
        elif tableWidget == self.tableWidget_movement:
            tableWidget.setItem(insert_pos, 2, QTableWidgetItem(' '))
            self.file_processor.organizeMovement(new_data_dict, self.dateEdit.text().replace('/', ''))
        # 將結果文件暫時存放在工作目錄
        self.file_processor.writeXMLFile(self.cwd+'\\AccountBookXMLFile.xml')

3. 將新增行寫入xml文件

3.1 功能詳述

用從編輯賬本界面獲取的新增行字典,寫入xml文件。具體過程如下:

  1. 判斷當前日期是否存在,四種情況:存在年/月/日、只存在年/月、只存在年、均不存在。判斷方法:在方法getSpecificDateElement(date_str)中,通過int型返回值作為標誌,再結合方法switch_caseInitStartDate(init_start, date_str)確定日期元素新增起始點,即判斷從年/月/日開始新增。
  2. 根據新增行字典內容,修改各存款賬戶的餘額
  3. 根據新增行字典內容,新增對應動賬記錄
  4. 若收支記錄關聯了其他存款賬戶,則同時修改關聯賬戶

3.2 代碼實現

    def createChildElement(self, e_parent, child_name, child_text, chile_attr=None):
        if chile_attr is None:
            chile_attr = {}
        e_child = et.SubElement(e_parent, child_name, attrib=chile_attr)
        e_child.text = child_text
        return e_child

    def switch_caseInitStartDate(self, init_start, date_str):
        # 模擬C++中switch-case控制語句,不使用break的情況
        if init_start == 0:
            self.createChildElement(self.e_dailyAccountBook, 'year', None, {'value': date_str[:4]})
            init_start += 1
        if init_start == 1:
            e_year = self.e_dailyAccountBook.find(".//year[@value='{}']".format(date_str[:4]))
            self.createChildElement(e_year, 'month', None, {'value': date_str[4:6]})
        #     init_start += 1
        # if init_start == 2:
        e_year = self.e_dailyAccountBook.find(".//year[@value='{}']".format(date_str[:4]))
        e_month = e_year.find(".//month[@value='{}']".format(date_str[4:6]))
        e_date = self.createChildElement(e_month, 'day', None, {'value': date_str[6:]})
        return e_date

    def organizeExpense(self, expense_dict: dict, date_str):
        e_date = self.getSpecificDateElement(date_str)

        if isinstance(e_date, int):
            print("未找到這一天的數據!")

            e_date = self.switch_caseInitStartDate(e_date, date_str)

        e_expenses = e_date.find(".//expenses") if e_date.find(".//expenses") is not None else self.createChildElement(e_date, 'expenses', None)
        e_expense = et.SubElement(e_expenses, 'expense')

        self.organizeVariation(expense_dict, e_date)

        if 'necessity' in expense_dict:
            e_expense.set('necessity', expense_dict['necessity'])
            del expense_dict['necessity']
        if 'associatedFund' in expense_dict:
            e_expense.set('associatedFund', expense_dict['associatedFund'])
            del expense_dict['associatedFund']
        for key, value in expense_dict.items():
            self.createChildElement(e_expense, key, value)

    def organizeVariation(self, change_dict, e_date):
        e_variation = e_date.find(".//variation") if e_date.find(".//variation") is not None else self.createChildElement(e_date, 'variation', None)
        if 'from' in change_dict:
            if e_variation.find(".//fund[category='{}']".format(change_dict['from'])) is None:
                e_fund = self.createChildElement(e_variation, 'fund', None)
                self.createChildElement(e_fund, 'category', change_dict['from'])
                self.createChildElement(e_fund, 'out', '0.0')
                self.createChildElement(e_fund, 'in', '0.0')
            e_fund_variety = e_variation.find(".//fund[category='{}']/out".format(change_dict['from']))
            e_fund_variety.text = str((Decimal(e_fund_variety.text) + Decimal(change_dict['value'])).quantize(Decimal('0.00')))
            self.modifyBalance(change_dict['from'], Decimal(change_dict['value'])*(-1))

            self.organizeAssociatedFund(e_variation, change_dict, 'from')
        if 'to' in change_dict:
            if e_variation.find(".//fund[category='{}']".format(change_dict['to'])) is None:
                e_fund = self.createChildElement(e_variation, 'fund', None)
                self.createChildElement(e_fund, 'category', change_dict['to'])
                self.createChildElement(e_fund, 'out', '0.0')
                self.createChildElement(e_fund, 'in', '0.0')
            e_fund_variety = e_variation.find(".//fund[category='{}']/in".format(change_dict['to']))
            e_fund_variety.text = str((Decimal(e_fund_variety.text) + Decimal(change_dict['value'])).quantize(Decimal('0.00')))
            self.modifyBalance(change_dict['to'], Decimal(change_dict['value']))

            self.organizeAssociatedFund(e_variation, change_dict, 'to')

    def modifyBalance(self, fund_category, increment_value: Decimal):
        """
        Describe:

        Args:
            fund_category: int or str
                存款賬戶類型
            increment_value: Decimal
                餘額增量,為正或負
        """
        e_value = self.e_dailyAccountBook.find(".//balance").find(".//fund[category='{}']".format(fund_category)).find(".//value")
        e_value.text = str((Decimal(e_value.text) + increment_value).quantize(Decimal('0.00')))

    def organizeAssociatedFund(self, e_variation, change_dict, from_or_to):
        print(change_dict['associatedFund'])
        if 'associatedFund' in change_dict and change_dict['associatedFund'] != 'None':
            print('執行了associatedFund,操作為', from_or_to)
            if e_variation.find(".//fund[category='{}']".format(change_dict['associatedFund'])) is None:
                e_associated_fund = self.createChildElement(e_variation, 'fund', None)
                self.createChildElement(e_associated_fund, 'category', change_dict['associatedFund'])
                self.createChildElement(e_associated_fund, 'out', '0.0')
                self.createChildElement(e_associated_fund, 'in', '0.0')
            if from_or_to == 'from':
                e_fund_variety = e_variation.find(".//fund[category='{}']/out".format(change_dict['associatedFund']))
                flag = -1
            elif from_or_to == 'to':
                e_fund_variety = e_variation.find(".//fund[category='{}']/in".format(change_dict['associatedFund']))
                flag = 1
            else:
                print('未知的收支動作!')
                return
            e_fund_variety.text = str((Decimal(e_fund_variety.text) + Decimal(change_dict['value'])).quantize(Decimal('0.00')))
            self.modifyBalance(change_dict['associatedFund'], Decimal(change_dict['value'])*flag)

三、開發總結

1. ElementTree模塊基本使用方法

//Element基本構成
<tag attrib1=1>text</tag>tail
<元素名 屬性1=1>文本</元素名>尾部

//解析文件
xml_tree = ElementTree.parse("AccountBookFile.xml")
//獲取根元素
root = xml_tree.getroot()

//查找子元素:按元素名查找,返回匹配到的第一個元素
//舉例:查找root元素的第一個year子元素
//情景含義:找到xml文件的第一個年份的動賬記錄
year = root.find(".//year")
//查找子元素:按元素名查找,返回所有匹配到的元素
//舉例:查找year元素的所有month元素
//情景含義:找到年份中所有月份的動賬記錄
month_list = year.findall(".//month")
//查找子元素:查找具有特定屬性值的子元素
//舉例:查找month元素直屬子元素中,符合該條件的子元素——具有屬性value=10
//情景含義:找到第一個月份中10號日期的動賬記錄
day = month_list[0].find(".//day[@value='10']")
//查找子元素:查找具有特定子元素值的子元素
//舉例:查找variation元素直屬子元素中,符合該條件的子元素——包含文本值為0的category子元素
//情景含義:找到10號微信零錢(category=0)的收支總和
variation = day.find(".//variation")
fund = variation.find(".//fund[category='0']")
//查找子元素:查找具有特定屬性值,同時有特定子元素值的子元素
//舉例:查找expenses元素直屬子元素中,符合該條件的子元素——具有屬性necessity=True,同時包含文本值為0的category子元素
//情景含義:找到10號基本開支中用於飲食(category=0)的花銷記錄
expenses = day.find(".//expenses")
expense = expenses.find(".//expense[@necessity='True'][category='0']")
//查找非直屬子元素:根據路徑查找子元素
//舉例:查找root元素下,具有屬性value=2023的year元素下,具有屬性value=09的month元素下,具有屬性value=11的day元素下,所有expenses元素下,包含文本值為1的category子元素的expense元素
//情景含義:找到2023/09/11這一天用於出行(category=1)的花銷記錄
expense = root.findall(".//year[@value='2023']/month[@value='09']/day[@value='11']/expenses/expense[category='1']")

//創建子元素
//舉例:為e_parent元素創建子元素e_child,子元素名(tag)為child_name,子元素屬性(attrib)鍵值對包含在字典attr_dict中,子元素文本值(text)為child_text
e_child = ElementTree.SubElement(e_parent, child_name, attrib=attr_dict)
e_child.text = child_text
//添加子元素
//舉例:將現有的e_child元素設置為e_parent元素的子元素
e_parent.append(e_child)

//修改element元素的名稱和文本
element.tag = "new_tag"
element.text = "new_text"

//新增或修改element元素中名為key的屬性(attrib本質上是字典)
element.attrib[key] = "new_value"
//刪除element元素中名為key的屬性,並返回對應的值value。若不存在該屬性,則返回defalt_value
value = element.attrib.pop(key, defalt_value)

//寫入xml文件
xml_tree.write(file_path, encoding='utf-8', xml_declaration=True)

2. 修改xml文件後,出現原有內容與新增內容格式不一致的情況

參考:https://blog.csdn.net/u012692537/article/details/101395192
通過該函數美化一下,再調用write寫入,問題解決

def pretty_xml(element, indent, newline='\n', level=0):  # elemnt為傳進來的Elment類,參數indent用於縮進,newline用於換行
    if element:  # 判斷element是否有子元素
        if (element.text is None) or element.text.isspace():  # 如果element的text沒有內容
            element.text = newline + indent * (level + 1)
        else:
            element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * (level + 1)
            # else:  # 此處兩行如果把註釋去掉,Element的text也會另起一行
            # element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * level
    temp = list(element)  # 將element轉成list
    for sub_element in temp:
        if temp.index(sub_element) < (len(temp) - 1):  # 如果不是list的最後一個元素,說明下一個行是同級別元素的起始,縮進應一致
            sub_element.tail = newline + indent * (level + 1)
        else:  # 如果是list的最後一個元素, 說明下一行是母元素的結束,縮進應該少一個
            sub_element.tail = newline + indent * level
        pretty_xml(sub_element, indent, newline, level=level + 1)  # 對子元素進行遞歸操作

3. QTableWidget單元格內置控制項

核心方法:

def setCellWidget(row: int, column: int, widget: QWidget) -> None

獲QTableWidget中發出信號的控制項所在行號的槽函數:

def get_triggeredObj_pos():
    triggeredObj = self.sender() # 獲取信號發出者
    # 假設層次關係為triggeredObj放在widget中,而widget放在QTableWidget的單元格中
    widget = triggeredObj.parent()
    table_widget = widget.parent()
    row = table_widget.indexAt(widget.pos()).row()
    print(f"The row number of the button is {row}")

本文來自博客園,作者:林風冰翼,轉載請註明原文鏈接:https://www.cnblogs.com/LinfengBingyi/p/17716008.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • WSL 創建記錄 操作步驟 本文適用於 Windows 10 版本 2004 及更高版本或 Windows 11。 即內部版本 19041 及更高版本. 如果你正在使用 2004 以下版本或你的電腦不支持虛擬化,請閱讀: https://oi-wiki.org/tools/wsl/#手動安裝4. 如 ...
  • 進程感覺就像一個應用程式一樣,比如QQ,火狐瀏覽器等等,他們之間互不幹擾,可以獨立運行。線程就像QQ里的各種功能,比如好友列表,顯示當前是線上還是離線,會話視窗等等去實現各種功能,進程死掉的話,這些線程也會跟著結束。 經過一段時間的學習,發現線程方便好用,線程與線程之間通信非常方便,開銷很小。進程就 ...
  • 1. 複製切換 1.1. 複製是高可用性的基礎 1.1.1. 總是保留一份持續更新的副本數據,會讓災難恢復更簡單 1.2. “切換副本”(promoting a replica)和“故障切換”(failing over)是同義詞 1.2.1. 意味著源伺服器不再接收寫入,並將副本提升為新的源伺服器 ...
  • 1.d3.shuffle D3.shuffle() 方法用於將數組中的元素隨機排序。它使用 Fisher–Yates 洗牌演算法,該演算法是無偏的,具有最佳的漸近性能(線性時間和常數記憶體)。 D3.shuffle() 方法的語法如下: d3.shuffle(array, [start, end]) 其中 ...
  • 針對改動範圍大、影響面廣的需求,我通常會問上線了最壞情況是什麼?應急預案是什麼?你帶開關了嗎?。當然開關也是有成本的,接下來本篇跟大家一起交流下高頻發佈支撐下的功能開關技術理論與實踐結合的點點滴滴。 ...
  • 在前面幾天中,我們學習了Dart基礎語法、可迭代集合,它們是Flutter應用研發的基本功。今天,我們繼續學習Flutter應用另一個必須掌握知識點:非同步編程(即Future和async/await)。它類似於Java中的FutureTask、JavaScript中的Promise。它是後續Flut... ...
  • 一、直充內充(充值方式) 直充: 包裝套餐直接充值到上游API系統。【PID/Smart】 (如:支付寶、微信 話費/流量/語音/簡訊 等 充值系統)。 內充(套餐打包常見物聯卡系統功能): 套餐包裝 適用於不同類型套餐 如 流量、簡訊、語音 等。 (目前已完善流量邏輯) 二、套餐與計費產品 計費產 ...
  • QStandardItemModel 類作為標準模型,主打“類型通用”,前一篇水文中,老周還沒提到樹形結構的列表,本篇咱們就好好探討一下這貨。 還是老辦法,咱們先做示例,然後再聊知識點。下麵這個例子,使用 QTreeView 組件來顯示數據,使用的列表模型比較簡單,只有一列。 #include <Q ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...