zhiyiYo / Groove

A cross-platform music player based on PyQt5.
https://groove-music.readthedocs.io
GNU General Public License v3.0
489 stars 79 forks source link

How to elegantly implement a searchable, filterable custom list view #79

Closed rainzee closed 1 year ago

rainzee commented 1 year ago

I want to implement a searchable, filterable, sortable view that displays custom widgets, such as cards, QTableView and QStyledItemDelegate in Qt are the most relevant widgets I can find, but it can only display tables, the relevant implementation in Groove is to use StackedWidget and manually control the removal of all widgets. Is there a more elegant way to do this? Because I will have a lot of widgets, probably thousands, and I want to be able to work with both paging and streaming loading.

I hope you can provide some ideas, or keywords, I really have no ideas. Or simply tell me if Model-View in Qt is a dead end.

1

2

zhiyiYo commented 1 year ago

重写委托应该就行了,qt 也有提供过滤 item 的类

rainzee commented 1 year ago

那为什么Groove选择手动移除所有卡片,然后再排序等等,而不是使用委托来进行。这里有什么取舍吗?

zhiyiYo commented 1 year ago

groove的卡片交互行为比较复杂,委托不太好写

zhiyiYo commented 1 year ago

像是 AlbumCard 上还有按钮,估计委托做不到这一点

rainzee commented 1 year ago

我的卡片上面也有按钮,但是和Groove比起来很简单,只有一个简单地PushButton,但是我希望能够想Fluent-Widget里的Icon-Interface一样,用Flowlayout来布局,这样可以做到吗?

zhiyiYo commented 1 year ago

理论上你可以画一个按钮出来

rainzee commented 1 year ago

image

简单来说,我希望能够进行搜索,筛选,排序,然后在卡片上有按钮和控件,在您看来,最合适的方案是什么

zhiyiYo commented 1 year ago

但是我觉得会很麻烦,得判断鼠标点击位置以及绘制不同状态下按钮的样式,不如直接 setIndexWidgets 来的方便

zhiyiYo commented 1 year ago

让我来写的话肯定还是写一个自定义组件,然后用 QListWidget.setIndexWidget

zhiyiYo commented 1 year ago

虽然速度慢一些,但是好写

zhiyiYo commented 1 year ago

实际上分页查询应该也不会很慢,不需要创建新的卡片,只要更新既有的卡片就行

rainzee commented 1 year ago

但是您Groove和Icon里都是用的ScroolArea,然后手动控制移除,这是出于什么考虑呢?

rainzee commented 1 year ago
    def search(self, keyWord: str):
        """ search icons """
        items = self.trie.items(keyWord.lower())
        indexes = {i[1] for i in items}
        self.flowLayout.removeAllWidgets()

        for i, card in enumerate(self.cards):
            isVisible = i in indexes
            card.setVisible(isVisible)
            if isVisible:
                self.flowLayout.addWidget(card)

上面是您在Icon中的实现,用前缀树和setVisable来手动实现搜索。

def sortByAddTime(self):
        """ sort album cards by added time """
        self.sortMode = "Date added"
        self.__removeViewFromLayout()
        self.__createAlbumCardView(self.albumCards)
        self.__addViewToLayout()

    def sortByFirstLetter(self):
        """ sort album cards by first letter """
        self.sortMode = "A to Z"

        self.__removeViewFromLayout()

        firstLetters = {}  # type:Dict[str, List[AlbumCard]]

        for card in self.albumCards:
            letter = pinyin.get_initial(card.album[0])[0].upper()
            letter = letter if 65 <= ord(letter) <= 90 else "..."

            if letter not in firstLetters:
                firstLetters[letter] = []

            firstLetters[letter].append(card)

        # sort group
        groupCards = sorted(firstLetters.items(), key=lambda i: i[0])

        # remove ... group to the last position
        if "..." in firstLetters:
            groupCards.append(groupCards.pop(0))

        # create views
        for letter, cards in groupCards:
            view = self.__createAlbumCardView(cards, letter)
            view.titleClicked.connect(
                lambda: signalBus.switchToLabelNavigationInterfaceSig.emit(list(firstLetters.keys()), "grid"))

        self.__addViewToLayout()

    def sortByYear(self):
        """ sort album cards by release year """
        self.sortMode = "Release year"
        self.__removeViewFromLayout()

        years = {}  # type:Dict[str, List[AlbumCard]]

        for card in self.albumCards:
            year = card.year if card.year else self.tr('Unknown')

            if year not in years:
                years[year] = []

            years[year].append(card)

        # sort groups by year
        groupCards = sorted(years.items(), key=lambda i: i[0], reverse=True)
        years = sorted(years.keys(), reverse=True)

        if self.tr("Unknown") in years:
            groupCards.append(groupCards.pop(0))

        # create views
        for year, cards in groupCards:
            view = self.__createAlbumCardView(cards, year)
            view.titleClicked.connect(
                lambda: signalBus.switchToLabelNavigationInterfaceSig.emit(years, "list"))

        self.__addViewToLayout()

    def sortBySinger(self):
        """ sort album cards by singer """
        self.sortMode = "Artist"
        self.__removeViewFromLayout()

        singers = {}  # type:Dict[str, List[AlbumCard]]

        for card in self.albumCards:
            singer = card.singer

            if singer not in singers:
                singers[singer] = []

            singers[singer].append(card)

        # sort groups
        groupCards = sorted(
            singers.items(), key=lambda i: pinyin.get_initial(i[0])[0].lower())

        # create views
        for singer, cards in groupCards:
            view = self.__createAlbumCardView(cards, singer)
            view.titleClicked.connect(
                lambda: signalBus.switchToLabelNavigationInterfaceSig.emit(list(singers.keys()), "grid"))

        self.__addViewToLayout()

下面是Groove中的相关实现,直接移除所有控件,我不清楚这在很多歌曲时候的表现,您这么写当时是有什么考量吗?

zhiyiYo commented 1 year ago

速度挺快的,因为没有创建新的卡片,只是改了一下位置

zhiyiYo commented 1 year ago

当初这么写是因为希望能够自己掌控界面布局,不受到 QListWidget 的影响

rainzee commented 1 year ago

QListWidget中能够完全自己布局吗,不是一条表格,而是类似于IconInterface中的FlowLayout

zhiyiYo commented 1 year ago

IconInterface 没用 QListWidget 的原因是我忘了可以这么写🥲

zhiyiYo commented 1 year ago

QListWidget 改一下视图模式就行,日历组件就是 QListWidget

rainzee commented 1 year ago

非常感谢您的快速回复,稍后会在爱发电支持。还有困惑的地方再请教您。

zhiyiYo commented 1 year ago

😄小问题