QLineEdit拾遺:數據的過濾、驗證和補全

来源:https://www.cnblogs.com/apocelipes/archive/2019/03/14/10533863.html
-Advertisement-
Play Games

QLineEdit是使用頻率最高的控制項之一,當我們想獲取用戶輸入時自然而然得會用到它。 通常我們會將QLineEdit的信號或其他控制項的信號綁定至槽函數,然後獲取並處理編輯器內的數據。你會覺得我們拿到的是第一手的“熱乎著”的數據,所以理所當然地將過濾和驗證邏輯都加入槽函數中,然而事實並非如此。那麼數 ...


QLineEdit是使用頻率最高的控制項之一,當我們想獲取用戶輸入時自然而然得會用到它。

通常我們會將QLineEdit的信號或其他控制項的信號綁定至槽函數,然後獲取並處理編輯器內的數據。你會覺得我們拿到的是第一手的“熱乎著”的數據,所以理所當然地將過濾和驗證邏輯都加入槽函數中,然而事實並非如此。那麼數據究竟通過了哪些流程最終才經由信號被我們獲取呢?

或者你希望QLineEdit能擁有自動補全或是輸入聯想的功能,這又如何實現呢?

如果你對上面的問題毫無頭緒,那麼本文就是為你量身打造的,請繼續往下閱讀吧!

本文索引

引論

這一節將帶你概覽QLineEdit對數據的處理,並以一個示例引出後續章節的內容。你可以先在此處找到一些粗淺的回答,後續則會有詳細的解釋。

如果要簡單的回答第一問,那麼在我們獲取到text內容前需要經過兩個步驟:

它們分別由inputMaskQValidator實現,前者負責過濾用戶的輸入,後者則用於過濾後的信息的驗證。

inputMaskvalidator的表現很相似,有時它們的功能還會有一些重合,那麼它們是否能取代彼此呢?答案是否定的,看起來像鴨子的鳥有時候其實不是和鴨子沒關係,後面我們仔細說明。

現在輪到回答第二個問題了。要實現補全和自動聯想,你只需要將一個設置好的QCompleter對象傳遞給QLineEdit。是不是夠簡單?大部分時間也確實如此,然而“設置好的”這一形容詞的很抽象的概念,所以有的時候你可能要失望了,不過別擔心,後面我們也會詳細介紹它的使用。

兩問回答完畢,現在該來看看本文的示例了。這次我們將自己實現一個DateEdit(我知道有現成的QDateEdit,不過這裡請允許我為了實踐所學而造一個粗糙的輪子),並根據用戶輸入的日期計算當天是周幾,效果如下:

實現CustomDateEdit

在本節中我們將逐步實現CustomDateEdit,並詳細介紹引論中提到的概念。

按照流程圖的順序,我們首先要講解的便是輸入數據的過濾——inputMask的功能。

過濾用戶輸入——inputMask

在具體介紹一個能控制顯示效果的特性前,我習慣於先描述其大致功能和具體的顯示效果。

inputMask的功能:它是一串特定的規則,所有不符合規則的用戶輸入都會被丟棄,用戶不管是從信號還是text槽都只能獲取符合mask要求的輸入數據,當然這個“用戶”包括我們後面要介紹的QValidator及其派生類。

inputMask的顯示效果:你只能輸入合法的字元,輸入非法字元是輸入內容無法顯示,游標停留在原處;如果你設置了mask的填充字元,則這些字元會顯示在edit中,當輸入合法字元時將覆蓋它們,mask中的保留字元同樣顯示在edit中,但輸入時會被跳過不可覆蓋(類似占位符),引論中的效果圖就是很好的例子。

inputMask就是一串由特殊字元組成的規則,通過規則給定的格式來控制文本的輸入,具體的規則見下表:

特殊字元 對應規則
A 必須輸入的ascii字母,包括A-Z,a-z
a A一樣,但是可選,也就是不輸入這個字元也可以,占位符將保留
N 必須輸入的ascii字母和數字,包括A-Z,a-z,0-9
n N一樣,但是可選
X 必須輸入的任意字元
x X一樣,但是可選
9 必填的ascii的數字字元,包括0-9
0 9一樣,但是可選
D 必填的數字,包括1-9
d D一樣,但可選
# 可選的數字或者加減號
H 必填的16進位的數字,包括A-F, a-f, 0-9
h H一樣,但可選
B 必填的二進位數字,包括0和1
b B一樣,但可選
> 所有在這個特殊字元之後的字元轉換為大寫
< 所有在這個特殊字元之後的字元轉換為小寫
! 關閉前面的大小寫轉換
[ ] { } 保留的特殊字元
\ 將特殊字元轉義為普通字元

inputMask的格式為:([特殊字元]|[普通字元])*;占位符,分號後跟的是占位符,用於填充特殊字元留下的空位,預設為空格。下麵看些例子:

  1. 000,000.00;_:用於輸入一個最大6位,有兩位小數的值,用_填充空位,edit會顯示出類似___,___.__的效果
  2. >AAAA-AAAA!-AAAA-AAAA:用於輸入一個由連字元分割的字母數字組成的uuid或license key,且前八個字母會被轉換為大寫,在edit中顯示為- - -
  3. 9999年09月09日:用於輸入年月日的時間格式,可以輸入2019年03月14日2019年3月14日,顯示效果在引論的效果圖中。
  4. 空字元串:表示沒有任何輸入限制

你可以通過setInputMask設置mask,或inputMask獲取當前的mask。

通過上面的說明和例子你應該已經學會了inputMask的使用,現在可以看看它與validator的區別了:

  1. inputMask在用戶進行輸入時進行過濾,並且只存在符合規則和不符合兩種狀態,validator通常擁有第三種狀態
  2. inputMask只能過濾較為固定的格式,並且對於輸入的最大長度產生限制,validator則要靈活的多

最主要的區別是這兩點。上一節提到inputMask不能替代validator,現在我們揭曉原因:inputMask只能保證輸入數據的格式,但並不保證數據有意義,比如例子3中我們可以在月份上輸入20,但明顯日期中沒有20月,而這種錯誤是inputMask無法處理的,這就是為什麼我們說有時候一隻看起來像鴨子的鳥也許和鴨子沒有半點關係的原因。

因此想要獲得正確的數據,我們還需要驗證器來幫忙。

數據驗證——QValidator

現在該驗證我們的輸入了。因為有了inputMask的幫助,現在我們只需要驗證數據本身是否正確而不用操心它的格式了,真是謝天謝地。

等等,這麼說好像不太對,validator拿到的數據里居然還保留著mask的占位符?你沒看錯,這不是bug,能在edit里顯示出來的數據那麼一定能被獲得,mask本身的占位符是能通過過濾的,所以它會原封不動地傳給validator,只有用戶輸入合法的數據後這些占位符才會被覆蓋。所以在寫自己的驗證器的時候要小心了——我們需要先刪除所有的占位符,因為它們不是數據的一部分!

下麵我們來看看validator的功能和顯示效果。

功能:驗證數據是否合法,不合法會被丟棄,同時還要識別出數據是否輸入完成,這就是validator返回的第三種狀態。

顯示效果:和inputMask一樣。如果數據未輸入完則保留在edit中。

大致概覽後我們可以深入瞭解一下QValidator了,所有的驗證器都是它的派生類。

QValidator本身是一個純虛基類,派生類需要實現QValidator::State QValidator::validate(QString &input, int &pos) const進行數據的驗證,還有一個可選的fixup函數用於修複輸入,不過一般來說很少有自行修複輸入的需求,所以這裡使用預設的實現,也就是什麼都不做。

validate驗證數據後返回數據是否合法,有QValidator::State類型的值表示:

  • QValidator::Invalid 數據不合法
  • QValidator::Intermediate 數據不完整需要進一步的輸入
  • QValidator::Acceptable 數據合法

PyQt5中的介面稍微有些不同,處理第一個返回值的為QValidator::State之外還需要把inputpos原封不動地作為第二和第三個值返回,否則edit無法正確顯示輸入的數據。

你可以通過validatorsetValidator來獲取和設置驗證器。

因為額外引入了第三種狀態,所以實現一個validator遠比設置inputMask來的複雜,這裡我們實現一個自定義的日期驗證器用於配合CustomDateEdit(我知道這個工作交給QRegExpValidator會很簡單),同時介紹如何實現一個驗證器。

下麵看看具體的代碼,首先我們不需要為validator額外增加內容,只需要實現幾個方法,因此不要要關註構造等行為:

class CustomDateValidator(QValidator):
    """驗證輸入的是否是合法的年月日
    """
    def validate(self, input: str, pos: int):
        date = input.replace(' ', '')  # 去除占位符
        y, m, d = self.splitDate(date)
        if not (y and m and d):
            return QValidator.Intermediate, input, pos

        try:
            arrow.get(date, self.dateFormat())  # 如果解析失敗代表日期輸入不合法
        except Exception:
            return QValidator.Invalid, input, pos

        return QValidator.Acceptable, input, pos

    def dateFormat(self):
        """返回arrow庫使用的日期解析格式,具體參見文檔,這裡與CustomDateEdit的inputMask保持一致
        """
        return self.tr('YYYY年M月D日')

    def splitDate(self, date: str):
        """分割日期成年,月,日,以便判斷數據是否輸入完整,
        只要有某一部分為空就表明數據未輸入結束
        """
        y, date = date.split(self.tr('年'))
        m, date = date.split(self.tr('月'))
        d = date.split(self.tr('日'))[0]
        return y, m, d

可以看到驗證器的邏輯其實很簡單。整個驗證器加上幫助函數一共做了三件事:

  1. 首先去除占位符,如前文所述
  2. 接著將輸入信息按年月日分割,如果有某一部分為空則代表輸入不完整
  3. 對於完整的輸入則使用arrow解析成時間對象,失敗則表示輸入數據錯誤

其他的細節都已經在註釋中說明。

如此一來我們既驗證了數據的合法性又處理了所有可能的輸入情況。當然,通常我更建議你使用現有的QDoubleValidatorQRegExpValidator等現有的驗證器,或將它們組合使用,這樣更簡單也更不容易出錯。

自動補全——QCompleter

我們已經講解了輸入的過濾和驗證,最後該講講補全了。

可以說過濾和驗證是比較常用的功能的話,那補全就沒有那麼常見了。或者說,通常我們不需要關心它,比如QComboBox自帶了QCompleter,它工作得也很好,所以我們往往忽略了它的存在。當然不只是下拉框,在QLineEdit中我們也可以用它和它的派生類實現補全效果。

功能:QCompleter包含了一個叫completeModel的數據模型,裡面包含了用於根據輸入信息進行補全的所有數據,通常是個listModel,也可以是設置了補全所用數據位於哪一列的tableModel,當然你還可以用treeModel,不過這超過了我們的討論範圍。

顯示效果:completer從你輸入的第一個字元開始匹配,如果在completeModel中找到了以輸入內容開頭的信息則會在edit下把所有匹配項一次放入一個下拉框並顯示,你也可以設置為將第一個匹配項的數據替換放入edit。

還有一點我想額外補充一下,補全時彈出的下拉框其實是個view視圖對象,因此你可以選擇自己需要的視圖以顯示補全時想顯示的自定義效果。

你可以通過completersetCompleter獲取和設置completer。

可以看到只要把我們用於補全輸入的數據放入合適的model中,再把model設置給completer,就能實現補全功能了。

下麵看個設置completer的例子:

# model是一個QStandardItemModel,後面我們也會使用這個model來設置completer
completer = QCompleter()
model.setParent(completer)
completer.setModel(model)
edit.setCompleter(completer)

另外completer得到的數據是經過驗證的,所以我們無需關心數據的格式和合法性。

現在我們已經把QLineEdit的數據處理流程介紹了一遍,有了這些預備知識下麵該實現CustomDateEdit了。

CustomDateEdit的實現

我們先來看代碼,細節問題基本在註釋中給出了說明:

class CustomDateEdit(QLineEdit):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.setInputMask(self.tr('9999年09月09日'))  # 設置日期格式的inputMask
        validator = CustomDateValidator()
        self.setValidator(validator)  # 設置validator
        # 設置completer
        self._completer = QCompleter()
        self.setCompleter(self._completer)
        self.completerModel = QStandardItemModel(parent=self._completer)
        self._completer.setModel(self.completerModel)
        # 預先填充一些待補全內容
        self.addDateRecord("2019年03月14日")
        self.addDateRecord("2019年03月15日")

    def addDateRecord(self, text: str):
        """當有合法的輸入被確認時就將其添加至completerModel,以便再次輸入時補全
        """
        if self.completerModel.findItems(text):  # 避免重覆添加
            return

        item = QStandardItem(text)
        self.completerModel.appendRow(item)

    def weekDayInfo(self, weekDay: int):
        """返回weekDay對應的名稱,後面測試中會被使用
        """
        week = {
            0: self.tr('周一'),
            1: self.tr('周二'),
            2: self.tr('周三'),
            3: self.tr('周四'),
            4: self.tr('周五'),
            5: self.tr('周六'),
            6: self.tr('周日'),
        }

        return week[weekDay]

整個dateEdit的實現也很簡單,所有複雜的邏輯都已經交給了inputMask,驗證器和completer,而我們唯一要做的是為completer添加新輸入的合法的數據,這在類方法addDateRecord中完成了。

測試CustomDateEdit

實現CustomDateEdit之後,我們就要動手實現引論一節中的程式了。

前面已經說過,最終通過信號傳遞或者由槽函數獲取到的值一定是通過了過濾和驗證通過的值。所以想實現引論中的程式我們只需要正確處理CustomDateEdit的信號即可。

下麵直接上測試代碼:

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        center = QWidget()
        self.dateEdit = CustomDateEdit()
        self.info = QLabel(self.tr('所選日期是'))
        self.dateEdit.textEdited.connect(lambda: self.info.setText(self.tr('所選日期是')))
        # 輸入結束後按回車觸發該信號,同時只有輸入數據通過過濾和驗證後這個信號才會被髮送
        self.dateEdit.returnPressed.connect(self.calcWeekDay)
        layout = QVBoxLayout()
        layout.addWidget(self.dateEdit)
        layout.addWidget(self.info, alignment=Qt.AlignCenter)
        center.setLayout(layout)
        self.setCentralWidget(center)

    def calcWeekDay(self):
        # 計算所選日期是周幾
        t = arrow.get(self.dateEdit.text(), self.dateEdit.validator().dateFormat())
        weekDayInfo = self.dateEdit.weekDayInfo(t.weekday())
        self.info.setText(self.tr('所選日期是') + weekDayInfo)
        # 添加記錄
        self.dateEdit.addDateRecord(self.dateEdit.text())


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    win.show()
    app.exec_()

當用戶輸入一個完整的日期後,按下回車鍵,程式會自動計算結果並更新到下方的label上。很簡單的程式,主要就是為了測試我們的CustomDateEdit

程式的行為和預想的差不多,現在你已經初步掌握所學的知識了。

另外也許你會奇怪,為什麼要大量使用self.tr這個函數,不用擔心,這隻是為了以後介紹國際化時做的準備,現在忽略它也沒問題。

如果你發現了任何錯誤疏漏,或者仍有疑問,歡迎提出,共同進步!


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

-Advertisement-
Play Games
更多相關文章
  • Web框架 什麼是框架? 協助開發者快速開發web應程式的一套功能代碼 開發者只需要按照框架約定要求,在指定位置寫上自己的業務邏輯代碼即可 為什麼要用web框架? 使用web框架的主要目的就是避免重覆造輪子! web網站發展至今,特別是伺服器端,涉及知識內容非常廣泛!隨之,對程式員的技能要求也越來越 ...
  • GitHub代碼練習地址:https://github.com/Neo-ML/PythonPractice/blob/master/SpiderPrac08_useragent.py ...
  • (1)turtle使用pen來繪製圖形 pendown() 放下畫筆,移動到指定點後繼續繪製 penup() 提起畫筆,用於另起一個地方繪製時使用 pensize(width) 設置畫筆線條的粗細為指定大小 (2)turtle運動方法 forward() 沿著當前方向前進指定距離 backward( ...
  • 函數1 (1)定義: (2)參數傳遞: 在python中,一切都是對象,類型也屬於對象,變數是沒有類型的。 a = [1,2,3] a = "helloworld" 以上代碼中,[1,2,3]是list類型,"helloworld"是string類型,而變數a是沒有類型的,它僅僅是一個對象的引用(一 ...
  • 哈哈,今天開始我也是學車人了~ ...
  • <<<模板變數>>> (1)定義視圖函數 通過context傳遞參數來渲染模板,context要是個字典 當模板變數為可調用對象的時候,函數不傳遞參數 (2)配置模板文件 模板裡面引入模板變數用{{ }} 【"."可以用於取方法,屬性,字典的鍵值以及索引】 (3)訪問 模板變數不限於上面舉例的,有興 ...
  • AbstractQueuedSynchronizer 隊列同步器(AQS) 隊列同步器 (AQS), 是用來構建鎖或其他同步組件的基礎框架,它通過使用 int 變數表示同步狀態,通過內置的 FIFO 的隊列完成資源獲取的排隊工作。(摘自《Java併發編程的藝術》) 我們知道獲取同步狀態有獨占和共用兩 ...
  • 廢話不多說!!! 在SpringCloud項目中配置了Feign來調用restful介面,項目啟動的時候報錯,報錯信息如下: 找不到org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory,我在IDE中全局搜索了一下 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...