前言】PySide6是用來做圖形界面開發的強有力模塊,能夠輕松設計出復雜的圖形界面,樣式美觀,靈活多變。尤其是該模塊里面的Qt設計師工具軟件,讓我們在可視化環境里通過拖拽組件設計程序窗體,而且在Qt設計師里還可以連接信號和槽,實現了一些預設功能的直接封裝,免去一部分代碼的書寫過程,效率非常高。
在上一篇筆記里,已經對Qt設計師Designer做了全面介紹,但是對組件及其屬性和方法介紹的不全,并且基本沒有涉及代碼部分,所以在這篇筆記里將結合實例對這些內容進行重點介紹。
1、動態加載和靜態加載
在Qt設計師中設計的圖形界面,保存在擴展名為ui的文件中,實際使用時需要在程序里加載,有兩種方式:動態加載和靜態加載。動態加載是直接讀入圖形界面ui文件,靜態加載是先將ui文件轉換成py文件,然后在程序中以模塊的形式進行導入。相比較而言,靜態加載比動態加載多了一個轉換的步驟,但是動態加載有一個最大的問題,那就是在程序中編寫代碼時,涉及到圖形界面里面的組件的代碼部分沒有提示。因為動態加載方式讀入的是ui文件,python不能識別出ui文件里面的組件,而靜態加載導入的是圖形界面ui文件轉換后的py文件,因此不存在代碼沒有提示的問題。如果將轉換工具配置在集成開發環境里,轉換ui文件非常方便,所以我現在只用靜態加載方式。
2、靜態加載模板
初學時,在python主程序和圖形界面之間總是存在著一個斷層,就是很難把圖形界面跟主程序代碼連接起來形成一個完整程序。首先是因為導入的庫太多,然后涉及到的文件又多,包括ui文件、ui轉換的py文件、qrc資源文件以及qrc轉換的py資源文件,一時之間很難理清這些文件的關系。其實真正掌握之后,加載圖形界面的代碼條理還是很清晰的,下面就來理順一下從設計窗體到靜態加載的流程。下面涉及的一些細節操作可以到我上一篇筆記中查看。
(1)操作流程
【注】靜態加載圖形界面用到的是ui和qrc文件轉換出來的py文件,并不依賴于ui和qrc文件本身。qrc轉換成的py文件里面都是圖像、圖標和聲音等資源文件的二進制數據,主程序的運行也不再依賴于圖像、圖標和聲音的源文件。py格式的資源文件我們不用管,它的調用是在圖形界面py文件里,程序已經為我們自動生成,如果主程序中還需用到資源文件,那么在主程序中也需要導入。
(2)代碼加載流程
(3)靜態加載代碼模板
為了代碼結構合理,我們通常為主窗口自定義一個類,繼承PySide6的QMainWindow類,把加載圖形界面的代碼都寫在自定義類里,主程序體里只有創建QApplication類對象、顯示窗口和窗口循環等幾條語句,這樣整體代碼條理非常清晰。模板代碼如下:
# 從PySide6的相應模塊中導入必須用到的類
from PySide6.QtWidgets import QApplication, QMainWindow
# 從圖形界面py文件中導入主窗口類Ui_Form
# 如果在Qt設計師里創建的窗口是Main Window,這里導入的就是Ui_MainWindow
from main import Ui_Form
# 定義主窗口類,繼承QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__() # 調用父類的初始化方法,pycharm里一鍵輸入
self.ui=Ui_Form() # 創建窗口對象
self.ui.setupUi(self) # 調用窗口對象的初始化方法對窗口初始化
app=QApplication([]) # 創建app對象,注意參數[]空列表必須有,暫時不需要有內容
window=MainWindow() # 創建自定義主窗口類的窗口對象
window.show() # 調用窗口對象的show方法顯示窗口
app.exec() # 調用app對象的exec方法循環顯示窗口
1、布局技巧
布局演示如下:
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
2、布局微調
上面的布局完成之后,還存在著一些問題,需要進行微調。例如,中間部位兩個顯示區域在橫向上是對等的,而我們需要左邊區域小一些右邊區域大一些。但是,總體布局只要形成,就不能用鼠標拖動的方式來更改布局的大小。像這種需要在橫向上調整大小的布局,必須在兩個獨立的布局外邊再套上一個橫向布局,然后通過調整兩個獨立區域的占比就可以調整大小了,縱向亦然。那么問題又來了,現在總體布局已經形成,怎么樣單獨更改呢?解決辦法就是,先把這兩個區域外邊的布局打散,再重新設計這部分布局。另外,每個布局都可以調整其上下左右的間隔,有些地方可以用隔板(彈簧)組件進行分隔,以達到調整組件間距的目的。布局微調演示如下:
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
3、使用qss樣式調節組件
每種布局里面的組件都在布局方向上受到影響,例如橫向布局里面的組件的寬度就會受到布局的控制,想要修改就可以采取上面布局微調里介紹的辦法,但是在另一個方向上的尺寸還是無法調節,這時就可以采用qss樣式表來完成。
qss樣式表跟HTML里面的css是一樣的,書寫格式代碼就能夠對組件的字體、文字顏色、邊框顏色、背景顏色、寬高等進行調節,非常方便。qss樣式可以在每個組件自己的樣式表里定義,也可以統一在窗口對象的樣式表里定義,推薦采用統一在窗口里定義,否則如果組件很多,那么在書寫和修改樣式表時需要挨個組件去找,效率太低。并且,統一在窗口里定義qss樣式表還有一個好處,可以針對某一類組件進行統一調整,例如針對按鈕類定義了qss樣式,那么所有按鈕都可以隨著變化。
color: rgb(255, 0, 0);
font-size:15px;
background-color: rgb(85, 255, 127);
border-color: rgb(255, 255, 0);
width:50px;height:20px;
*{color: rgb(255, 0, 0);font-size:15px;}
QPushButton{color: rgb(255, 0, 0);
font-size:15px;
background-color: rgb(85, 255, 127);
border-color: rgb(255, 255, 0);
width:50px;height:20px;}
#pushButton{height:30px;}
QPushButton:hover {color: red;border:none;}
*[myClass=top] {height:30px;}
qss樣式演示如下:
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
(一)PushButton
PushButton就是普通按鈕,它的屬性由四部分構成:QObject、QWidget、QAbstractButton、QPushButton,功能和作用如下:
1、重要屬性
(1)shortcut
shortcut是快捷鍵屬性,它的最強大之處在于允許將任意鍵定義為快捷鍵,支持幾個普通按鍵組合,例如隨意定義一個jkl快捷鍵,在窗口運行界面,正常速度連續按jkl就能夠觸發按鈕的事件。注意,在定義普通按鍵組合快捷鍵時,先在shortcut屬性右側框里點一下,然后快速按下需要定義的幾個按鍵,如果按的慢,系統檢測就結束了,后面再按的鍵就變成了另外一組快捷鍵。
快捷鍵設置
(2)checkable、checked和autoExclusive
checkable是否可選擇屬性,位置在快捷鍵屬性下面,見上圖。如果checkable屬性為真,則按鈕被點擊后不會彈起(樣式會跟正常時不同),就像電源插排上的開關按鈕,按下后就會卡住,然后再按一下彈起。利用isChecked()方法可以檢測按鈕是否為選中狀態,這樣就可以實現一些特殊功能。
checked屬性用來設置按鈕是否選中,需要與checkable配合使用。如果將這兩個屬性都設置為真,那么程序運行后按鈕就處于按壓狀態,點擊一下才恢復到正常按鈕狀態。在程序中可以使用setChecked(bool)方法來設置checked屬性。
autoExclusive是自動排他屬性,如果同一父組件內的一組按鈕都設置了checkable屬性為真,那么當一個按鈕按下變成按壓狀態后,其它按鈕都會彈起,就像過去的錄音機按鍵一樣。并且,如果一個按鈕設置了自動排他屬性,那么點擊自己將無法彈起。
(3)auto Repeat、auto RepeatDelay和auto RepeatInterval
auto Repeat自動重復屬性,如果鼠標按住按鈕不抬起,那么按鈕會重復發射信號。在重復發射信號的過程中,有延遲和間隔時間,就是auto RepeatDelay和auto RepeatInterval屬性,單位為毫秒。
(4)default和auto Default
default和auto Default屬性都是將按鈕設置為默認按鈕,如果焦點在該按鈕上(鼠標點擊一次或者在代碼里設定焦點),那么一直按回車鍵就可以一直發射信號。目前沒發現二者在功能上有什么差別,只是default設置為真后,按鈕會出現邊框。
(5)flat
flat屬性為真則將按鈕設為平面按鈕,就是沒有邊框也沒有突出顯示的按鈕,沒有其它功能。
2、常用方法
常用方法就是PySide6里所謂的信號,介紹如下:
3、實例演示
下面用一個實例演示一下以上介紹的內容。程序運行后,直接按回車鍵是沒有反應的,這時我們單擊第一個加長按鈕,文本編輯框里的文本會放大,現在一直按回車鍵文字就可以一直放大了。這是因為我們在Qt設計大師里將第一個加長的按鈕的auto Default屬性設置為真,然后將它的單擊信號關聯到文本編輯框的ZOOM in(放大顯示)槽上,不需要寫代碼。程序運行后,默認焦點不在這個按鈕上,因此按回車鍵沒有反應,用鼠標點擊一下后它就獲得了焦點,由于它是自動默認按鈕,所以點擊完畢后系統默認把焦點又交還給它,因此一直按回車鍵都有效。另外,在Qt設計大師里為這個按鈕設置了”JKL”組合快捷鍵,在程序運行后,按順序正常按“JKL”就可以觸發按鈕信號,看到文字放大顯示,但是快捷鍵并不能把焦點移動到這個按鈕上。
用鼠標點擊“按住連續縮放”按鈕沒有反應,單擊“放大顯示”按鈕,然后再點擊“按住連續縮放”按鈕不放開,這時文字就一直放大;單擊“縮小顯示”按鈕,再點擊“按住連續縮放”按鈕不放開,這時文字就一直縮小。這是因為“按住連續縮放”按鈕的auto Repeat屬性設為真,按住后會連續發射信號;而“放大顯示”和“縮小顯示”這兩個按鈕的checkable和autoExclusive屬性為真,由于是排他性的,所以點擊這兩個按鈕時,它們的checked屬性是一起改變的,在代碼進行判斷處理就可以了。
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
4、演示代碼
from PySide6.QtWidgets import QApplication, QMainWindow
from ui_test1 import Ui_Form
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui=Ui_Form()
self.ui.setupUi(self)
# “按住連續縮放”按鈕單擊信號
self.ui.pushButton.clicked.connect(self.auto_expanding)
# 文字縮放自定義方法
def auto_expanding(self):
if self.ui.pushButton_4.isChecked():
self.ui.plainTextEdit.zoomIn()
elif self.ui.pushButton_2.isChecked():
self.ui.plainTextEdit.zoomOut()
if __name__=='__main__':
app=QApplication([])
window=MainWindow()
window.show()
app.exec()
(二)Tool Button
Tool Button是工具按鈕,非常靈活的按鈕組件,可以單獨或者混合顯示圖標和文本,還可以顯示箭頭(三角形符號)。既可以當作普通按鈕使用,又可以彈出菜單,彈出菜單又有立即彈出、長按彈出和點擊菜單區彈出三種方式。善加利用,是非常實用的工具按鈕。
最典型的應用場景是Word、Excel等辦公軟件的工具欄按鈕,例如顏色按鈕,直接點擊按鈕是設置為當前顏色,點擊右側三角符號則打開顏色選擇器。還有瀏覽器的返回按鈕,即瀏覽器地址欄左側的 ← 這個按鈕,既可以單擊返回上一頁,又可以長按顯示瀏覽的歷史記錄。
1、重要屬性
QAbstractButton類屬性不用再贅述,重點介紹QToolButton里面的幾個屬性。
(1)popupMode
菜單彈出方式,一共有三種方式:
(2)toolButtonStyle
工具按鈕樣式,共有五種類型:
(3)autoRaise
自動升起屬性,也就是設為真之后變成平面按鈕,否則按鈕帶有邊界,且有凹陷效果,所以把該屬性稱為自動升起。
(4)arrowType
箭頭類型,按鈕自動顯示黑色三角符號,會覆蓋圖標和文字,共有五種方式:
2、常用方法
3、實例演示
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
4、演示代碼
from PySide6.QtWidgets import QApplication, QMainWindow
from ui_test2 import Ui_MainWindow
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui=Ui_MainWindow()
self.ui.setupUi(self)
# 根據需要,可以將整個菜單欄隱藏
# self.ui.menubar.setVisible(False)
# 將工具按鈕使用的菜單項隱藏
self.ui.menuFile.menuAction().setVisible(False)
self.ui.menuEdit.menuAction().setVisible(False)
self.ui.menuRenew.menuAction().setVisible(False)
self.ui.menuView.menuAction().setVisible(False)
# 為工具按鈕設置菜單
self.ui.toolButton.setMenu(self.ui.menuFile)
self.ui.toolButton_2.setMenu(self.ui.menuEdit)
self.ui.toolButton_3.setMenu(self.ui.menuRenew)
self.ui.toolButton_4.setMenu(self.ui.menuView)
if __name__=='__main__':
app=QApplication([])
window=MainWindow()
window.show()
app.exec()
(三)Radio Button
Radio Button是單選按鈕,具有排他性,成組使用。特別要注意一點,一組單選按鈕必須要放置在一個容器類組件中,也就是說一組單選按鈕必須具有同一個父類(不能是主窗口),否則單選按鈕的排他性就會出現問題,放置在不同容器內的單選按鈕互不影響。
1、重要屬性
Radio Button沒有自己獨特的屬性,都是繼承屬性。在Qt設計大師里,除了把一個單選按鈕設置成默認選擇狀態,幾乎不需要設置任何屬性。組件尺寸和位置基本都用鼠標拖拉的方式設定,可以在屬性里進行微調。
2、常用方法
3、創建按鈕組
如果圖形界面里有很多單選按鈕,那么在代碼里就會定義對應數量的槽函數,勢必會增加代碼量以及不方便閱讀。為了避免這種情況,可以為同組的單選按鈕創建一個按鈕組,在程序代碼里只需要將按鈕組的clicked信號連接到一個自定義槽函數就可以了。在自定義槽函數里,通過檢測id號來確定哪個單選按鈕被單擊了。
(1)在Qt設計大師里創建按鈕組
在Qt設計大師里將同組單選按鈕全部選中,然后依次點按鼠標右鍵→Assign to button group→New button group,這樣就為這一組單選按鈕創建了按鈕組,每個單選按鈕都有一個系統自動分配的id號,可以在代碼里使用按鈕組的checkedId()方法獲取。
(2)在程序代碼里創建按鈕組
這種方式相對比較費事,需要事先導入QButtonGroup類,然后創建按鈕組實例對象,把同組的單選按鈕利用addButton()方法加入到一個按鈕組里,并定義每個單選按鈕的id號。如果一組的單選按鈕很多,可以將同組的單選按鈕以序列形式命名,如radioButton_1、radioButton_2等等,然后用循環的方法向按鈕組里添加單選按鈕。
下面的實例就是采用在代碼里創建按鈕組方式,剛開始在網上查閱相關資料時,都說要創建按鈕組,就是沒人介紹如何在Qt設計大師里創建。我就以為按鈕組是一個組件,翻遍了組件區還是沒找到,無奈就只能寫代碼創建了。后來在Qt設計大師里無意中按了一下右鍵,才發現了這個無比淺顯又折磨人的奧秘!其實,在PySide6里面,像這種不大不小的坑實在是無計其數,或許還是自己學習和掌握的不到位吧!
4、實例演示
本例實現利用單選按鈕控制一個Label組件里圖片的對齊方式,雖然單選框在設計時很簡單,但是實際在代碼里操作時需要用到很多技巧。實例里動態設置Label組件的圖片對齊方式也是一個技巧,感覺挺繁瑣,還需要使用QtCore模塊的Qt類進行設置。
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
5、演示代碼
from PySide6 import QtGui
from PySide6.QtWidgets import QApplication, QMainWindow, QButtonGroup
from PySide6.QtCore import Qt
from ui_radio import Ui_Form
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui=Ui_Form()
self.ui.setupUi(self)
# 如果一組的Radio Button過多,可以創建一個按鈕組對象來統一管理,這樣只需要定義一個槽函數
self.btn_group=QButtonGroup(self) # 創建按鈕組對象
# 利用循環將單選按鈕radioButton_1到radioButton_9加入到按鈕組
for i in range(1, 10):
widget=f'self.btn_group.addButton(self.ui.radioButton_{i},i)'
eval(widget)
# 把按鈕組的按鈕單擊信號關聯到自定義槽函數
self.btn_group.buttonClicked.connect(self.rbtnClicked)
# 未使用按鈕組管理的單選按鈕的單擊信號處理方式,每個按鈕都需要定義一個槽函數
self.ui.radioDisplay.clicked.connect(self.displayImage)
self.ui.radioNodisplay.clicked.connect(self.nodisplayImage)
# 單選按鈕組單擊槽函數
def rbtnClicked(self):
self.flag=self.btn_group.checkedId()
# 自動計算顯示位置坐標
if self.flag <=3:
h=self.flag - 1
v=0
elif 4 <=self.flag <=6:
h=self.flag - 4
v=1
else:
h=self.flag - 7
v=2
# 根據上面確定的位置坐標,確定橫向和縱向對齊方式
h_align=['Qt.AlignLeft', 'Qt.AlignHCenter', 'Qt.AlignRight']
v_align=['Qt.AlignTop', 'Qt.AlignVCenter', 'Qt.AlignBottom']
align=f'{h_align[h]} | {v_align[v]}'
self.ui.label.setAlignment(eval(align))
# 顯示圖片單選按鈕被單擊槽函數
def displayImage(self):
self.ui.label.setPixmap(QtGui.QPixmap(r'resfile\open.png'))
# 不顯示圖片單選按鈕被單擊槽函數
def nodisplayImage(self):
self.ui.label.setPixmap(QtGui.QPixmap())
if __name__=='__main__':
app=QApplication([])
window=MainWindow()
window.show()
app.exec()
(四)CheckBox
CheckBox是復選框組件,可以獨立使用,也可以像單選按鈕那樣加到一個按鈕組里。如果加入了按鈕組,使用QButtonGroup的exclusive屬性來控制單選還是多選,exclusive屬性為真,則復選框就變成了單選按鈕功能。
1、重要屬性
CheckBox都是繼承屬性,唯一獨有屬性是tristate。
2、常用方法
3、實例演示
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
4、演示代碼
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QApplication, QMainWindow
from ui_checkBox import Ui_Form
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui=Ui_Form()
self.ui.setupUi(self)
# 創建一個自定義字體實例對象
self.myfont=QFont()
# 為字體對象設置默認字號和字體
self.myfont.setPointSize(72)
self.myfont.setFamily('微軟雅黑')
# 用自定義字體對象設置文本編輯框的字體
self.ui.plainTextEdit.setFont(self.myfont)
# 復選框的狀態改變信號和槽
self.ui.checkBox.stateChanged.connect(self.chk_Bold)
self.ui.checkBox_2.stateChanged.connect(self.chk_italic)
self.ui.checkBox_3.stateChanged.connect(self.chk_underline)
# 字體加粗
def chk_Bold(self):
self.myfont.setBold(self.ui.checkBox.isChecked())
self.ui.plainTextEdit.setFont(self.myfont)
# 字體傾斜
def chk_italic(self):
self.myfont.setItalic(self.ui.checkBox_2.isChecked())
self.ui.plainTextEdit.setFont(self.myfont)
# 字體加下劃線
def chk_underline(self):
self.myfont.setUnderline(self.ui.checkBox_3.isChecked())
self.ui.plainTextEdit.setFont(self.myfont)
if __name__=='__main__':
app=QApplication([])
window=MainWindow()
window.show()
app.exec()
(五)Command Link Button
命令鏈接按鈕,外觀與平面按鈕相同,按鈕標題允許設置描述信息,描述信息以小字形式顯示在標題下方。功能與普通按鈕沒有區別,允許設置一組互斥按鈕。典型應用場景是Windows11操作系統的設置界面,如圖:
PySide6的命令鏈接按鈕樣式如下:
(六)Dialog Button Box
對話框按鈕盒子,用于生成成套按鈕,感覺沒有什么實際意義。只有一個重要屬性standardButtons,里面包含所有對話框按鈕,通過點選自由搭配按鈕。
容器組件用來承載其它組件,形成分組效果,也可以做成分頁面,對于窗口布局非常有用。在上一篇筆記里已經詳細介紹了Scroll Area、Tab Widget、Stacked widget這三個組件,在這里介紹一下其它容器組件。
(一)Tool Box
Tool Box是工具箱組件。顧名思義,可以放置常用工具,能夠分層疊放,不占空間,使用靈活。
1、操作技巧
(1)增加頁面
在窗體的Tool Box組件上或者對象查看器的Tool Box對象上按右鍵,選擇彈出菜單中的Insert Page選項,就可以看到在當前頁之前和當前頁之后兩個選項,點擊就在相應位置插入一個新頁面。
(2)刪除頁面
把要刪除的頁面展開,即變為當前頁,然后在窗體的Tool Box組件上或者對象查看器的Tool Box對象上按右鍵,在彈出菜單上有Page 1 of 3(具體數字視實際情況)的菜單項,再選擇Delete Page即可刪除。這個菜單項就在插入頁面上面,就不再另行配圖了。
2、重要屬性
工具箱的每個頁面標題必須在屬性里才能更改,屬性設置頁面見上圖。幾個重要屬性如下:
3、實例演示
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
4、演示代碼
import os
from PySide6.QtWidgets import QApplication, QMainWindow
from ui_toolbox import Ui_Form
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui=Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_8.clicked.connect(self.notepad)
self.ui.pushButton_9.clicked.connect(self.calc)
def notepad(self):
os.system('notepad')
def calc(self):
os.system('calc')
if __name__=='__main__':
app=QApplication([])
window=MainWindow()
window.show()
app.exec()
(二)Dock Widget
Dock Widget是停靠組件,本身又是容器組件,可以在窗口上下左右四個邊進行拖拽停靠。雙擊標題欄或者點擊浮動窗口按鈕,就可以從工具欄形式變成浮動窗口,點擊Dock Widge的關閉按鈕可以隱藏該組件,還可以利用它的show()和hide()方法進行顯示和隱藏。
【注意】使用Dock Widget組件,創建的窗體必須是MainWindow,這樣Dock組件才能夠實現停靠和浮動功能。
1、重要屬性
Dock Widget的重要屬性都在Features(特征)里,內容如下:
2、常用方法
3、實例演示
本例是在Qt設計師里制作了一個簡易的記事本界面,用Dock Widget組件做了一個工具欄,菜單里有兩項:顯示和隱藏Dock Widget組件。界面設計好之后,直接在Qt設計師里將功能按鈕的clicked()信號與文本編輯框的相應預設槽函數進行了關聯,不需要到主程序里寫代碼,直接在Qt設計師里就可以實現全部功能。演示如下:
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
未完,待續……
DevExpress提供了一個比較強大的圖形繪制工具,可以用于繪制各種圖形,如流程圖、組織機構圖等等,本篇隨筆介紹XtraDiagram.DiagramControl的使用,以及利用代碼對其屬性進行控制,以及利用圖形模具的自定義操作,實現一些簡單流程圖形的繪制和處理。
DiagramControl是類似Visio的繪圖控件,以前我2006年的就接觸使用Visio的二次開發,當時開始還是利用VB6 + VIsio2003進行二次開發的,后來把它改良為C# + Visio進行二次開發,DiagramControl的對象模型很類似Visio的相關對象模型,如對于工具欄的形狀,稱之為模具(Stencil),Visio也是稱之為Stencil, DiagramControl里面的很多接口名稱依舊采用Stencil進行命名,因此估計也是借鑒了很多Visio的對象設計知識,如果您對Visio二次開發感興趣,可以參考我的隨筆文章《Visio二次開發》,里面有很多相關的內容。
而如果想了解這個控件的相關知識和使用,參考官網的案例和說明應該是比較好的教程(https://docs.devexpress.com/WindowsForms/118290/controls-and-libraries/diagrams/getting-started )。
DiagramControl是一個界面控件,類似Visio SDK里面的DrawingControl的存在,可以通過它進行圖形的繪制,各種窗口的顯示和隱藏,以及跟蹤各種事件的處理。
DiagramControl控件拖動到窗體中后,會自動增加一些屬性窗口,上排的繪圖工具中的按鈕是我添加的,用來測試該控件的一些屬性的控制。
1)屬性窗口的顯示和隱藏(折疊)
這個通過控制diagramControl1.OptionsView.PropertiesPanelVisibility 屬性就可以實現對這個屬性窗口的控制了。
里面顯示一些系統位置和內容信息,以及一些自定義信息的窗口,后面我會介紹如何自定義處理這些模具的屬性。
通過按鈕處理的代碼,我們可以實現對這個窗口的顯示或者隱藏處理。
//切換屬性窗口的顯示或關閉
var status=diagramControl1.OptionsView.PropertiesPanelVisibility;
diagramControl1.OptionsView.PropertiesPanelVisibility=(status==PropertiesPanelVisibility.Visible ? PropertiesPanelVisibility.Collapsed : PropertiesPanelVisibility.Visible);
2)模具形狀窗口的顯示或隱藏
模具形狀的窗口,它是放在一個面板里面,我們只需要通過控制該面板的顯示或者隱藏就可以了,如下代碼所示。
//切換模具形狀窗口的顯示或關閉
var status=diagramToolboxDockPanel1.Visibility;
diagramToolboxDockPanel1.Visibility=(status==DevExpress.XtraBars.Docking.DockVisibility.Visible ? DevExpress.XtraBars.Docking.DockVisibility.Hidden : DevExpress.XtraBars.Docking.DockVisibility.Visible);
或者通過控件的Toolbar屬性進行控制,一樣的效果。
//切換模具形狀窗口的顯示或關閉
var status=this.diagramControl1.OptionsView.ToolboxVisibility;
this.diagramControl1.OptionsView.ToolboxVisibility=status==ToolboxVisibility.Closed ? ToolboxVisibility.Full : ToolboxVisibility.Closed;
3)放大縮小窗口的顯示或者隱藏
同樣我們也可以控制放大縮小窗口的顯示或者隱藏,它也是圖形繪制的一個常見的窗口。我們只需要判斷或者設置diagramControl1.OptionsView.ShowPanAndZoomPanel 屬性就可以了,如下代碼所示。
//切換放大縮小窗口的顯示或關閉
var status=diagramControl1.OptionsView.ShowPanAndZoomPanel;
diagramControl1.OptionsView.ShowPanAndZoomPanel=!status;
4)其他屬性的處理
另外,我們可以通過控制一些屬性,實現對標尺、網格、只讀視圖等模式進行控制。
//是否顯示標尺
this.diagramControl1.OptionsView.ShowRulers=this.chkRuler.Checked;
//是否顯示網格
this.diagramControl1.OptionsView.ShowGrid=this.chkGrid.Checked;
//是否只讀視圖
this.diagramControl1.OptionsProtection.IsReadOnly=this.chkReadOnly.Checked;
在繪制圖形的時候,一般來說我們可能需要切換點選模式或者連接線模式,因此可以通過它的屬性ActiveTool進行設置。在點選模式下,可以對圖形進行拖動、放大縮小、旋轉等處理,連接線模式下,則會加亮連接點,便于自動繪制連接線。
private void btnPointerMode_Click(object sender, EventArgs e)
{
diagramControl1.OptionsBehavior.ActiveTool=diagramControl1.OptionsBehavior.PointerTool;
}
private void btnConnectorMode_Click(object sender, EventArgs e)
{
diagramControl1.OptionsBehavior.ActiveTool=diagramControl1.OptionsBehavior.ConnectorTool;
}
當然,我們也可以通過對鼠標行為的分析來進行控制,如果鼠標懸停或者放置在圖形上,就自動切換模式為連接線模式,否則為點選模式,那么只需要判斷鼠標的移動行為即可自動處理,如下代碼所示。
/// <summary>
/// 實現對圖形自動切換到連接點模式
/// </summary>
private void diagramControl1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button==MouseButtons.Left)
return;
DiagramItem item=diagramControl1.CalcHitItem(e.Location);
if (item==null)
{
diagramControl1.OptionsBehavior.ActiveTool=diagramControl1.OptionsBehavior.PointerTool;
return;
}
else if (item is DiagramConnector)
{
diagramControl1.OptionsBehavior.ActiveTool=diagramControl1.OptionsBehavior.ConnectorTool;
return;
}
Rect itemBounds=new Rect(new Point(item.Position.X, item.Position.Y), new Size(item.Width, item.Height));
PointFloat documentPoint=diagramControl1.PointToDocument(new PointFloat(e.Location));
DiagramHitInfo[] hitInfo=diagramControl1.CalcHitInfo(documentPoint);
if (itemBounds.Contains(new Point(documentPoint.X, documentPoint.Y)))
{
itemBounds.Inflate(-5, -5);
if (!itemBounds.Contains(new Point(documentPoint.X, documentPoint.Y)))
{
diagramControl1.OptionsBehavior.ActiveTool=diagramControl1.OptionsBehavior.ConnectorTool;
return;
}
}
diagramControl1.OptionsBehavior.ActiveTool=diagramControl1.OptionsBehavior.PointerTool;
}
另外圖形的保存xml、PNG、PDF處理和加載代碼如下所示。
/// <summary>
/// 保存XML和圖片文件
/// </summary>
private void SaveXml()
{
var xml=Path.Combine(Application.StartupPath, "MyFlowShapes.xml");
diagramControl1.SaveDocument(xml);
var pngFile=Path.Combine(Application.StartupPath, "MyFlowShapes.png");
diagramControl1.ExportDiagram(pngFile);
}
private void btnLoadXml_Click(object sender, EventArgs e)
{
var xml=FileDialogHelper.OpenXml();
if(!string.IsNullOrEmpty(xml))
{
diagramControl1.LoadDocument(xml);
}
}
最終案例的效果如下所示。
在實際的圖形繪制開發中,我們可以需要創建一些指定的形狀模具,那么我們弄好后一般可以存放在XML中,然后進行加載到控件上來,如下代碼就是注冊自定義的形狀的處理。
/// <summary>
/// 注冊自定義的形狀。
/// 自定義圖形是以XML文件形式進行保存,圖形需要按照規定XML格式進行繪制
/// </summary>
private void LoadShapes2()
{
var projectName="SmallExampleDemo.Examples.XtraDiagram";
using (var stream=Assembly.GetExecutingAssembly().GetManifestResourceStream(projectName + ".CustomContainers.xml"))
{
var stencil=DiagramStencil.Create(MyStencilId, MyStencilName, stream, shapeName=> shapeName);
DiagramToolboxRegistrator.RegisterStencil(stencil);
}
diagramControl1.SelectedStencils=new StencilCollection(MyStencilId);//(MyStencilId, BasicShapes.StencilId);
}
我們只需要設置選中的圖形就可以了,其他有需要的可以從More Shapes中選擇即可。
我們如果需要在屬性窗口中顯示自定義的屬性,那么我們需要一些代碼開發才能實現。
我們首先需要繼承一個DiagramShape的子類,然后實現自己自定義的屬性定義,如下代碼所示。
對自定義屬性的處理,需要在事件中實現
diagramControl1.CustomGetEditableItemProperties +=DiagramControl_CustomGetEditableItemProperties;
通過對它進行判斷可以實現自定義屬性的顯示處理
void DiagramControl_CustomGetEditableItemProperties(object sender, DiagramCustomGetEditableItemPropertiesEventArgs e)
{
if (e.Item is DiagramShapeEx)
{
e.Properties.Add(TypeDescriptor.GetProperties(typeof(DiagramShapeEx))["Status"]);
e.Properties.Add(TypeDescriptor.GetProperties(typeof(DiagramShapeEx))["TypeName"]);
}
}
然后我們可以注冊創建自己的模具形狀集合,如下代碼所示。
/// <summary>
/// 創建自定義的模具
/// </summary>
/// <returns></returns>
DiagramStencil CreateCustomDrawShapesStencil()
{
var stencilId="CustomedFlowShape";
var stencilName="流程圖";
var shapeSizeSmall=new Size(100, 37.5);
var shapeSize=new Size(100, 75);
DiagramControl.ItemTypeRegistrator.Register(typeof(DiagramShapeEx));
var stencil=new DiagramStencil(stencilId, stencilName);
//流程類型
stencil.RegisterTool(new FactoryItemTool("StartEnd", ()=> "流程開始", diagram=> {
var shape=new DiagramShapeEx(BasicFlowchartShapes.StartEnd, "流程開始");
shape.Appearance.BackColor=Color.Red;
return shape;
}, shapeSizeSmall));
stencil.RegisterTool(new FactoryItemTool("Decision", ()=> "流程條件", diagram=> {
var shape=new DiagramShapeEx(BasicFlowchartShapes.Decision, "流程條件");
shape.Appearance.BackColor=Color.FromArgb(199, 115, 1);//Color.Red;
return shape;
}, shapeSize));
這兩個流程開始,流程條件,我們直接是從 BasicFlowchartShapes 集合中借用過來,構建自己的自定義對象的,默認創建的對象是方形的。
如果我們需要動態構建其他自定義類型,我們可以指定它的顏色等樣式,從而構建不同類型的圖形。
//循環添加相關流程節點
var procNames=new List<string> { "審批", "歸檔", "閱辦", "會簽", "領導批示分閱"};
//定義幾個初始化顏色順序
var colors=new List<Color> { Color.DeepSkyBlue, Color.ForestGreen, Color.Violet, Color.Yellow, Color.Blue, Color.Orange, Color.Indigo, Color.Purple, Color.Black, Color.Brown, Color.Pink };
int i=0;
foreach (string name in procNames)
{
var shapeId=string.Format("Process_{0}", i++);
stencil.RegisterTool(new FactoryItemTool(shapeId, ()=> name, diagram=>
{
var shape=new DiagramShapeEx(name, Status.Inactive);
var index=procNames.IndexOf(name);
var color=colors[index % 10];//Color.Red;
var fontColor=(color==Color.Yellow) ? Color.Black : Color.White;
//沒什么作用
//shape.ThemeStyleId=GetStyle(index); //從Accent1樣式開始 DiagramShapeStyleId.Styles[index];//
shape.Appearance.BackColor=color;
shape.Appearance.BorderSize=3;
shape.Appearance.Font=new Font("宋體", 12f, FontStyle.Bold);
shape.Appearance.ForeColor=fontColor;
return shape;
}, shapeSize));
}
這樣就有不同顏色的圖形對象了。
根據這些我們就可以繪制出自己的各種流程圖了,并且也可以根據數據庫的信息,進行動態繪制展示。
文章來自https://www.cnblogs.com/wuhuacong/p/16404257.html