PyQt5系列教程(36): QQ模拟QListView 2

PyQt5系列教程(36): QQ模拟QListView 2

上期我们介绍了QAbstractListModel子类化之后,我们模型(Model)的实现,本期我们介绍一下QListView的使用,这里的使用也是自定义一个类,当然是继承了QListView。


核心代码

class ListView(QListView):
    map_listview = []
    def __init__(self):
        super().__init__()
        self.m_pModel = ListModel()  
        self.setModel(self.m_pModel)
    def contextMenuEvent(self, event):
        hitIndex = self.indexAt(event.pos()).column()
        if hitIndex > -1:
            pmenu = QMenu(self)
            pDeleteAct = QAction("删除",pmenu)
            pmenu.addAction(pDeleteAct)
            pDeleteAct.triggered.connect(self.deleteItemSlot)
            pSubMenu = QMenu("转移联系人至" ,pmenu)
            pmenu.addMenu(pSubMenu)
            for item_dic in self.map_listview:
                pMoveAct = QAction(item_dic['groupname'] ,pmenu)
                pSubMenu.addAction(pMoveAct)
                pMoveAct.triggered.connect(self.move)
            pmenu.popup(self.mapToGlobal(event.pos()))
    def deleteItemSlot(self):
        index = self.currentIndex().row()
        if index > -1:
            self.m_pModel.deleteItem(index)
    def setListMap(self, listview):
        self.map_listview.append(listview)
    def addItem(self, pitem):
        self.m_pModel.addItem(pitem)
    def move(self):
        tolistview = self.find(self.sender().text())
        if tolistview is self:
            prelistview = self.sender().text()
            QMessageBox.warning(self, "警告", "该联系人就在{},还怎么移动啊!".format(prelistview))
        else:
            index = self.currentIndex().row()
            pItem = self.m_pModel.getItem(index)
            tolistview.addItem(pItem)
            self.m_pModel.deleteItem(index)
    def find(self, pmenuname):
        for item_dic in self.map_listview:
            if item_dic['groupname'] == pmenuname:
                return item_dic['listview']

这段代码分为两大部分:自定义右键菜单(上下文菜单)和增删移的操作。

map_listview = []

map_listview是一个类变量,那么这个类变量与实例变量有什么区别呢?

类变量为所有实例共享的,而实例变量则是每个实例独有的。举个简单的例子:

class P:
    list = [0]
    def __init__(self):
        self.a = 1
    def out(self):
        print(self.list)
        print('a = ' + str(self.a))
pp = P()
ppp = P()
pp.out()
ppp.out()
print('*'*10)
pp.list.append(1)
pp.a = 2
pp.out()
ppp.out()
print(ppp.list)
#执行结果
[0]
a = 1
[0]
a = 1
**********
[0, 1]
a = 2
[0, 1]
a = 1
[0, 1]

从这个例子当中我们可以看出,当pp这个实例给list增加一个数据之后,ppp.list也随之改变了。

为什么写这个呢?

因为我们要利用map_listview保存QListView对象和分组名称的对应关系。因为这样我们就能靠分组名称找到对应的QListView对象,根据QListView对象就行调用对应的QAbstractListModel的子类了。

so,我们每新建一个分组就会自动初始化随机的联系人信息了。

self.m_pModel = ListModel()  
self.setModel(self.m_pModel)

设置要呈现的视图的模型,这里我们用的是自定义的ListModel类。

def contextMenuEvent(self, event):
    hitIndex = self.indexAt(event.pos()).column()
    if hitIndex > -1:
        pmenu = QMenu(self)
        pDeleteAct = QAction("删除",pmenu)
        pmenu.addAction(pDeleteAct)
        pDeleteAct.triggered.connect(self.deleteItemSlot)
        pSubMenu = QMenu("转移联系人至" ,pmenu)
        pmenu.addMenu(pSubMenu)
        for item_dic in self.map_listview:
            pMoveAct = QAction(item_dic['groupname'] ,pmenu)
            pSubMenu.addAction(pMoveAct)
            pMoveAct.triggered.connect(self.move)
        pmenu.popup(self.mapToGlobal(event.pos()))

这个函数主要是实现上下文菜单的,也就是传说中的单击右键菜单。如下图:


hitIndex = self.indexAt(event.pos()).column()

这个函数是说返回鼠标指针相对于接收事件的小部件的位置,然后我们根据这个位置的坐标返回视口坐标点处的项目的模型索引,最后根据索引返回此模型索引引用的列(感觉说得好绕口啊~!)。

if hitIndex > -1:
    pmenu = QMenu(self)
    pDeleteAct = QAction("删除",pmenu)
    pmenu.addAction(pDeleteAct)
    pDeleteAct.triggered.connect(self.deleteItemSlot)
    pSubMenu = QMenu("转移联系人至" ,pmenu)
    pmenu.addMenu(pSubMenu)

这段比较好理解,就是新增上下文菜单,这里不做过多介绍,因为前面的文章讲解过了,可以参考:

当我们点击删除菜单的时候调用deleteItemSlot()函数。

for item_dic in self.map_listview:
    pMoveAct = QAction(item_dic['groupname'] ,pmenu)
    pSubMenu.addAction(pMoveAct)
    pMoveAct.triggered.connect(self.move)

还记得上面介绍map_listview的作用吗?

这里我们将每个分组名称取出,新建一个QAction对象,加入到pSubMenu当中。点击这个每个分组的时候就会执行联系人转移分组操作,这里就是move()的调用。

pmenu.popup(self.mapToGlobal(event.pos()))

显示菜单,以便动作QAction对象在指定的全局位置p处。这里的全局位置p是根据小部件的本地坐标转换为全局坐标的,故使用QWidget.mapToGlobal()。

def deleteItemSlot(self):
    index = self.currentIndex().row()
    if index > -1:
        self.m_pModel.deleteItem(index)

返回当前项目的模型索引,并根据索引返回此模型索引引用的行,然后到模型里面删除相应的数据。

def setListMap(self, listview):
    self.map_listview.append(listview)

将分组名称和QListView对象这个字典增加到map_listview数据列表中。我们会在QQ这个类里面用到。

def addItem(self, pitem):
    self.m_pModel.addItem(pitem)

新增一个联系人,我们在转移联系人的时候会用到。

def move(self):
    tolistview = self.find(self.sender().text())
    if tolistview is self:
        prelistview = self.sender().text()
        QMessageBox.warning(self, "警告", "该联系人就在{},还怎么移动啊!".format(prelistview))
    else:
        index = self.currentIndex().row()
        pItem = self.m_pModel.getItem(index)
        tolistview.addItem(pItem)
        self.m_pModel.deleteItem(index)
def find(self, pmenuname):
    for item_dic in self.map_listview:
        if item_dic['groupname'] == pmenuname:
            return item_dic['listview']

这个函数是实现联系人转移的。

tolistview = self.find(self.sender().text())

我们根据点击的分组名称找到对应的QListView对象。find函数是自定义的。

if tolistview is self: