diff --git a/列表/QListView/显示自定义Widget.py b/QListView/CustomWidgetItem.py similarity index 100% rename from 列表/QListView/显示自定义Widget.py rename to QListView/CustomWidgetItem.py diff --git a/列表/QListView/显示自定义Widget并排序.py b/QListView/CustomWidgetSortItem.py similarity index 100% rename from 列表/QListView/显示自定义Widget并排序.py rename to QListView/CustomWidgetSortItem.py diff --git a/QListView/README.en.md b/QListView/README.en.md index e69de29..810c5c0 100644 --- a/QListView/README.en.md +++ b/QListView/README.en.md @@ -0,0 +1,11 @@ +# QListView + +## 1. Custom Widget Item +[Run](CustomWidgetItem.py) + +![CustomWidgetItem](ScreenShot/CustomWidgetItem.png) + +## 2. Custom Widget Sort Item +[Run](CustomWidgetSortItem.py) + +![CustomWidgetSortItem](ScreenShot/CustomWidgetSortItem.gif) \ No newline at end of file diff --git a/QListView/README.md b/QListView/README.md index e69de29..9dee335 100644 --- a/QListView/README.md +++ b/QListView/README.md @@ -0,0 +1,11 @@ +# QListView + +## 1、显示自定义Widget +[运行](CustomWidgetItem.py) + +![CustomWidgetItem](ScreenShot/CustomWidgetItem.png) + +## 2、显示自定义Widget并排序 +[运行](CustomWidgetSortItem.py) + +![CustomWidgetSortItem](ScreenShot/CustomWidgetSortItem.gif) \ No newline at end of file diff --git a/列表/QListView/ScreenShot/显示自定义Widget.png b/QListView/ScreenShot/CustomWidgetItem.png similarity index 100% rename from 列表/QListView/ScreenShot/显示自定义Widget.png rename to QListView/ScreenShot/CustomWidgetItem.png diff --git a/列表/QListView/ScreenShot/显示自定义Widget并排序.gif b/QListView/ScreenShot/CustomWidgetSortItem.gif similarity index 100% rename from 列表/QListView/ScreenShot/显示自定义Widget并排序.gif rename to QListView/ScreenShot/CustomWidgetSortItem.gif diff --git a/列表/QListWidget/删除自定义Item.py b/QListWidget/DeleteCustomItem.py similarity index 100% rename from 列表/QListWidget/删除自定义Item.py rename to QListWidget/DeleteCustomItem.py diff --git a/列表/QListWidget/自定义可拖拽Item.py b/QListWidget/DragDrop.py similarity index 100% rename from 列表/QListWidget/自定义可拖拽Item.py rename to QListWidget/DragDrop.py diff --git a/列表/QListWidget/腾讯视频热播列表/pic_v.png b/QListWidget/HotPlaylist/Data/pic_v.png similarity index 100% rename from 列表/QListWidget/腾讯视频热播列表/pic_v.png rename to QListWidget/HotPlaylist/Data/pic_v.png diff --git a/列表/QListWidget/腾讯视频热播列表/README.md b/QListWidget/HotPlaylist/README.md similarity index 96% rename from 列表/QListWidget/腾讯视频热播列表/README.md rename to QListWidget/HotPlaylist/README.md index f023863..8580067 100644 --- a/列表/QListWidget/腾讯视频热播列表/README.md +++ b/QListWidget/HotPlaylist/README.md @@ -1,29 +1,29 @@ -# 腾讯视频热播列表 - -简单思路说明: - - - 利用QScrollArea滚动显示,QGridLayout或者FlowLayout做布局来放置自定义的Widget - - QNetworkAccessManager异步下载网页和图片 - - QScrollArea滚动到底部触发下一页加载 - -自定义控件说明: - - - 主要是多个layout和控件的结合,其中图片QLabel为自定义,通过setPixmap设置图片,重写paintEvent绘制底部渐变矩形框和白色文字 - - 字体颜色用qss设置 - - 图标利用了QSvgWidget显示,可以是svg 动画(如圆形加载图) - - -# 截图 - -使用QGridLayout 固定列数效果图 - -![截图1](ScreenShot/1.gif) - -使用自定义布局FlowLayout 自动列数效果图 -![截图2](ScreenShot/2.gif) - -使用QListWidget 配合setFlow(QListWidget.LeftToRight)和 -setWrapping(True)和 -setResizeMode(QListWidget.Adjust)达到类似FlowLayout的效果 - +# 腾讯视频热播列表 + +简单思路说明: + + - 利用QScrollArea滚动显示,QGridLayout或者FlowLayout做布局来放置自定义的Widget + - QNetworkAccessManager异步下载网页和图片 + - QScrollArea滚动到底部触发下一页加载 + +自定义控件说明: + + - 主要是多个layout和控件的结合,其中图片QLabel为自定义,通过setPixmap设置图片,重写paintEvent绘制底部渐变矩形框和白色文字 + - 字体颜色用qss设置 + - 图标利用了QSvgWidget显示,可以是svg 动画(如圆形加载图) + + +# 截图 + +使用QGridLayout 固定列数效果图 + +![截图1](ScreenShot/1.gif) + +使用自定义布局FlowLayout 自动列数效果图 +![截图2](ScreenShot/2.gif) + +使用QListWidget 配合setFlow(QListWidget.LeftToRight)和 +setWrapping(True)和 +setResizeMode(QListWidget.Adjust)达到类似FlowLayout的效果 + ![截图3](ScreenShot/3.gif) \ No newline at end of file diff --git a/列表/QListWidget/腾讯视频热播列表/ScreenShot/1.gif b/QListWidget/HotPlaylist/ScreenShot/1.gif similarity index 100% rename from 列表/QListWidget/腾讯视频热播列表/ScreenShot/1.gif rename to QListWidget/HotPlaylist/ScreenShot/1.gif diff --git a/列表/QListWidget/腾讯视频热播列表/ScreenShot/2.gif b/QListWidget/HotPlaylist/ScreenShot/2.gif similarity index 100% rename from 列表/QListWidget/腾讯视频热播列表/ScreenShot/2.gif rename to QListWidget/HotPlaylist/ScreenShot/2.gif diff --git a/列表/QListWidget/腾讯视频热播列表/ScreenShot/3.gif b/QListWidget/HotPlaylist/ScreenShot/3.gif similarity index 100% rename from 列表/QListWidget/腾讯视频热播列表/ScreenShot/3.gif rename to QListWidget/HotPlaylist/ScreenShot/3.gif diff --git a/列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay.py b/QListWidget/HotPlaylist/TencentMovieHotPlay.py similarity index 97% rename from 列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay.py rename to QListWidget/HotPlaylist/TencentMovieHotPlay.py index 21c9dd6..457dbe2 100644 --- a/列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay.py +++ b/QListWidget/HotPlaylist/TencentMovieHotPlay.py @@ -1,336 +1,336 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -''' -Created on 2018年2月4日 -@author: Irony."[讽刺] -@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 -@email: 892768447@qq.com -@file: TencentMovieHotPlay -@description: -''' -import os -import sys -import webbrowser - -from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal -from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ - QBrush, QPaintEvent, QPixmap -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest -from PyQt5.QtSvg import QSvgWidget -from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ - QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QGridLayout,\ - QAbstractSlider - -from lxml.etree import HTML # @UnresolvedImport - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - -# offset=0,30,60,90 -Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" - -# 播放量图标 -Svg_icon_play_sm = ''' -'''.encode() - -Svg_icon_loading = ''''''.encode() - -# 主演 -Actor = '''{title} ''' - - -class CoverLabel(QLabel): - - def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): - super(CoverLabel, self).__init__(*args, **kwargs) - self.setCursor(Qt.PointingHandCursor) - self.setScaledContents(True) - self.setMinimumSize(220, 308) - self.setMaximumSize(220, 308) - self.cover_path = cover_path - self.cover_title = cover_title - self.video_url = video_url - self.setPixmap(QPixmap(cover_path)) - - def setCoverPath(self, path): - self.cover_path = path - - def mouseReleaseEvent(self, event): - super(CoverLabel, self).mouseReleaseEvent(event) - webbrowser.open_new_tab(self.video_url) - - def paintEvent(self, event): - super(CoverLabel, self).paintEvent(event) - if hasattr(self, "cover_title") and self.cover_title != "": - # 底部绘制文字 - painter = QPainter(self) - rect = self.rect() - # 粗略字体高度 - painter.save() - fheight = self.fontMetrics().height() - # 底部矩形框背景渐变颜色 - bottomRectColor = QLinearGradient( - rect.width() / 2, rect.height() - 24 - fheight, - rect.width() / 2, rect.height()) - bottomRectColor.setSpread(QGradient.PadSpread) - bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) - bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) - # 画半透明渐变矩形框 - painter.setPen(Qt.NoPen) - painter.setBrush(QBrush(bottomRectColor)) - painter.drawRect(rect.x(), rect.height() - 24 - - fheight, rect.width(), 24 + fheight) - painter.restore() - # 距离底部一定高度画文字 - font = self.font() or QFont() - font.setPointSize(8) - painter.setFont(font) - painter.setPen(Qt.white) - rect.setHeight(rect.height() - 12) # 底部减去一定高度 - painter.drawText(rect, Qt.AlignHCenter | - Qt.AlignBottom, self.cover_title) - - -class ItemWidget(QWidget): - - def __init__(self, cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): - super(ItemWidget, self).__init__(*args, **kwargs) - self.setMaximumSize(220, 380) - self.setMaximumSize(220, 380) - self.img_path = img_path - self.cover_url = cover_url - layout = QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - # 图片label - self.clabel = CoverLabel(cover_path, figure_info, video_url, self) - layout.addWidget(self.clabel) - - # 片名和分数 - flayout = QHBoxLayout() - flayout.addWidget(QLabel(figure_title, self)) - flayout.addItem(QSpacerItem( - 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) - flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) - layout.addLayout(flayout) - - # 主演 - layout.addWidget( - QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) - - # 播放量 - blayout = QHBoxLayout() - count_icon = QSvgWidget(self) - count_icon.setMaximumSize(16, 16) - count_icon.load(Svg_icon_play_sm) - blayout.addWidget(count_icon) - blayout.addWidget( - QLabel(figure_count, self, styleSheet="color: #999999;")) - layout.addLayout(blayout) - - def setCover(self, path): - self.clabel.setCoverPath(path) - self.clabel.setPixmap(QPixmap(path)) -# self.clabel.setText(''.format(os.path.abspath(path))) - - def sizeHint(self): - # 每个item控件的大小 - return QSize(220, 380) - - def event(self, event): - if isinstance(event, QPaintEvent): - if event.rect().height() > 20 and hasattr(self, "clabel"): - if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 - # print("start download img:", self.cover_url) - req = QNetworkRequest(QUrl(self.cover_url)) - # 设置两个自定义属性方便后期reply中处理 - req.setAttribute(QNetworkRequest.User + 1, self) - req.setAttribute(QNetworkRequest.User + 2, self.img_path) - self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 - return super(ItemWidget, self).event(event) - - -class GridWidget(QWidget): - - Page = 0 - loadStarted = pyqtSignal(bool) - - def __init__(self, *args, **kwargs): - super(GridWidget, self).__init__(*args, **kwargs) - self._layout = QGridLayout(self, spacing=20) - self._layout.setContentsMargins(20, 20, 20, 20) - # 异步网络下载管理器 - self._manager = QNetworkAccessManager(self) - self._manager.finished.connect(self.onFinished) - - def load(self): - if self.Page == -1: - return - self.loadStarted.emit(True) - # 延迟一秒后调用目的在于显示进度条 - QTimer.singleShot(1000, self._load) - - def _load(self): - print("load url:", Url.format(self.Page * 30)) - url = QUrl(Url.format(self.Page * 30)) - self._manager.get(QNetworkRequest(url)) - - def onFinished(self, reply): - # 请求完成后会调用该函数 - req = reply.request() # 获取请求 - iwidget = req.attribute(QNetworkRequest.User + 1, None) - path = req.attribute(QNetworkRequest.User + 2, None) - html = reply.readAll().data() - reply.deleteLater() - del reply - if iwidget and path and html: - # 这里是图片下载完毕 - open(path, "wb").write(html) - iwidget.setCover(path) - return - # 解析网页 - self._parseHtml(html) - self.loadStarted.emit(False) - - def splist(self, src, length): - # 等分列表 - return (src[i:i + length] for i in range(len(src)) if i % length == 0) - - def _parseHtml(self, html): - # encoding = chardet.detect(html) or {} - # html = html.decode(encoding.get("encoding","utf-8")) - html = HTML(html) - # 查找所有的li list_item - lis = html.xpath("//li[@class='list_item']") - if not lis: - self.Page = -1 # 后面没有页面了 - return - lack_count = self._layout.count() % 30 # 获取布局中上次还缺几个5行*6列的标准 - row_count = int(self._layout.count() / 6) # 行数 - print("lack_count:", lack_count) - self.Page += 1 # 自增+1 - if lack_count != 0: # 上一次没有满足一行6个,需要补齐 - lack_li = lis[:lack_count] - lis = lis[lack_count:] - self._makeItem(lack_li, row_count) # 补齐 - if lack_li and lis: - row_count += 1 - self._makeItem(lis, row_count) # 完成剩下的 - else: - self._makeItem(lis, row_count) - - def _makeItem(self, li_s, row_count): - li_s = self.splist(li_s, 6) - for row, lis in enumerate(li_s): - for col, li in enumerate(lis): - a = li.find("a") - video_url = a.get("href") # 视频播放地址 - img = a.find("img") - cover_url = "http:" + img.get("r-lazyload") # 封面图片 - figure_title = img.get("alt") # 电影名 - figure_info = a.find("div/span") - figure_info = "" if figure_info is None else figure_info.text # 影片信息 - figure_score = "".join(li.xpath(".//em/text()")) # 评分 - # 主演 - figure_desc = "主演:" + \ - "".join([Actor.format(**dict(fd.items())) - for fd in li.xpath(".//div[@class='figure_desc']/a")]) - # 播放数 - figure_count = ( - li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] - path = "cache/{0}.jpg".format( - os.path.splitext(os.path.basename(video_url))[0]) - cover_path = "pic_v.png" - if os.path.isfile(path): - cover_path = path - iwidget = ItemWidget(cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, path, self) - self._layout.addWidget(iwidget, row_count + row, col) - - -class Window(QScrollArea): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - self.resize(800, 600) - self.setFrameShape(self.NoFrame) - self.setWidgetResizable(True) - self.setAlignment(Qt.AlignCenter) - self._loadStart = False - # 网格窗口 - self._widget = GridWidget(self) - self._widget.loadStarted.connect(self.setLoadStarted) - self.setWidget(self._widget) - # 连接竖着的滚动条滚动事件 - self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) - # 进度条 - self.loadWidget = QSvgWidget( - self, minimumHeight=120, minimumWidth=120, visible=False) - self.loadWidget.load(Svg_icon_loading) - - def setLoadStarted(self, started): - self._loadStart = started - self.loadWidget.setVisible(started) - - def onActionTriggered(self, action): - # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 - # 同时防止多次加载同一个url - if action != QAbstractSlider.SliderMove or self._loadStart: - return - # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 - if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): - # 可以下一页了 - self._widget.load() - - def resizeEvent(self, event): - super(Window, self).resizeEvent(event) - self.loadWidget.setGeometry( - int((self.width() - self.loadWidget.minimumWidth()) / 2), - int((self.height() - self.loadWidget.minimumHeight()) / 2), - self.loadWidget.minimumWidth(), - self.loadWidget.minimumHeight() - ) - - -if __name__ == "__main__": - os.makedirs("cache", exist_ok=True) - app = QApplication(sys.argv) - w = Window() - w.show() - w._widget.load() - sys.exit(app.exec_()) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Created on 2018年2月4日 +@author: Irony."[讽刺] +@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 +@email: 892768447@qq.com +@file: TencentMovieHotPlay +@description: +''' +import os +import sys +import webbrowser + +from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal +from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ + QBrush, QPaintEvent, QPixmap +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest +from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ + QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QGridLayout,\ + QAbstractSlider + +from lxml.etree import HTML # @UnresolvedImport + + +__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" +__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" +__Version__ = "Version 1.0" + +# offset=0,30,60,90 +Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" + +# 播放量图标 +Svg_icon_play_sm = ''' +'''.encode() + +Svg_icon_loading = ''''''.encode() + +# 主演 +Actor = '''{title} ''' + + +class CoverLabel(QLabel): + + def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): + super(CoverLabel, self).__init__(*args, **kwargs) + self.setCursor(Qt.PointingHandCursor) + self.setScaledContents(True) + self.setMinimumSize(220, 308) + self.setMaximumSize(220, 308) + self.cover_path = cover_path + self.cover_title = cover_title + self.video_url = video_url + self.setPixmap(QPixmap(cover_path)) + + def setCoverPath(self, path): + self.cover_path = path + + def mouseReleaseEvent(self, event): + super(CoverLabel, self).mouseReleaseEvent(event) + webbrowser.open_new_tab(self.video_url) + + def paintEvent(self, event): + super(CoverLabel, self).paintEvent(event) + if hasattr(self, "cover_title") and self.cover_title != "": + # 底部绘制文字 + painter = QPainter(self) + rect = self.rect() + # 粗略字体高度 + painter.save() + fheight = self.fontMetrics().height() + # 底部矩形框背景渐变颜色 + bottomRectColor = QLinearGradient( + rect.width() / 2, rect.height() - 24 - fheight, + rect.width() / 2, rect.height()) + bottomRectColor.setSpread(QGradient.PadSpread) + bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) + bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) + # 画半透明渐变矩形框 + painter.setPen(Qt.NoPen) + painter.setBrush(QBrush(bottomRectColor)) + painter.drawRect(rect.x(), rect.height() - 24 - + fheight, rect.width(), 24 + fheight) + painter.restore() + # 距离底部一定高度画文字 + font = self.font() or QFont() + font.setPointSize(8) + painter.setFont(font) + painter.setPen(Qt.white) + rect.setHeight(rect.height() - 12) # 底部减去一定高度 + painter.drawText(rect, Qt.AlignHCenter | + Qt.AlignBottom, self.cover_title) + + +class ItemWidget(QWidget): + + def __init__(self, cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): + super(ItemWidget, self).__init__(*args, **kwargs) + self.setMaximumSize(220, 380) + self.setMaximumSize(220, 380) + self.img_path = img_path + self.cover_url = cover_url + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + # 图片label + self.clabel = CoverLabel(cover_path, figure_info, video_url, self) + layout.addWidget(self.clabel) + + # 片名和分数 + flayout = QHBoxLayout() + flayout.addWidget(QLabel(figure_title, self)) + flayout.addItem(QSpacerItem( + 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) + layout.addLayout(flayout) + + # 主演 + layout.addWidget( + QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) + + # 播放量 + blayout = QHBoxLayout() + count_icon = QSvgWidget(self) + count_icon.setMaximumSize(16, 16) + count_icon.load(Svg_icon_play_sm) + blayout.addWidget(count_icon) + blayout.addWidget( + QLabel(figure_count, self, styleSheet="color: #999999;")) + layout.addLayout(blayout) + + def setCover(self, path): + self.clabel.setCoverPath(path) + self.clabel.setPixmap(QPixmap(path)) +# self.clabel.setText(''.format(os.path.abspath(path))) + + def sizeHint(self): + # 每个item控件的大小 + return QSize(220, 380) + + def event(self, event): + if isinstance(event, QPaintEvent): + if event.rect().height() > 20 and hasattr(self, "clabel"): + if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 + # print("start download img:", self.cover_url) + req = QNetworkRequest(QUrl(self.cover_url)) + # 设置两个自定义属性方便后期reply中处理 + req.setAttribute(QNetworkRequest.User + 1, self) + req.setAttribute(QNetworkRequest.User + 2, self.img_path) + self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 + return super(ItemWidget, self).event(event) + + +class GridWidget(QWidget): + + Page = 0 + loadStarted = pyqtSignal(bool) + + def __init__(self, *args, **kwargs): + super(GridWidget, self).__init__(*args, **kwargs) + self._layout = QGridLayout(self, spacing=20) + self._layout.setContentsMargins(20, 20, 20, 20) + # 异步网络下载管理器 + self._manager = QNetworkAccessManager(self) + self._manager.finished.connect(self.onFinished) + + def load(self): + if self.Page == -1: + return + self.loadStarted.emit(True) + # 延迟一秒后调用目的在于显示进度条 + QTimer.singleShot(1000, self._load) + + def _load(self): + print("load url:", Url.format(self.Page * 30)) + url = QUrl(Url.format(self.Page * 30)) + self._manager.get(QNetworkRequest(url)) + + def onFinished(self, reply): + # 请求完成后会调用该函数 + req = reply.request() # 获取请求 + iwidget = req.attribute(QNetworkRequest.User + 1, None) + path = req.attribute(QNetworkRequest.User + 2, None) + html = reply.readAll().data() + reply.deleteLater() + del reply + if iwidget and path and html: + # 这里是图片下载完毕 + open(path, "wb").write(html) + iwidget.setCover(path) + return + # 解析网页 + self._parseHtml(html) + self.loadStarted.emit(False) + + def splist(self, src, length): + # 等分列表 + return (src[i:i + length] for i in range(len(src)) if i % length == 0) + + def _parseHtml(self, html): + # encoding = chardet.detect(html) or {} + # html = html.decode(encoding.get("encoding","utf-8")) + html = HTML(html) + # 查找所有的li list_item + lis = html.xpath("//li[@class='list_item']") + if not lis: + self.Page = -1 # 后面没有页面了 + return + lack_count = self._layout.count() % 30 # 获取布局中上次还缺几个5行*6列的标准 + row_count = int(self._layout.count() / 6) # 行数 + print("lack_count:", lack_count) + self.Page += 1 # 自增+1 + if lack_count != 0: # 上一次没有满足一行6个,需要补齐 + lack_li = lis[:lack_count] + lis = lis[lack_count:] + self._makeItem(lack_li, row_count) # 补齐 + if lack_li and lis: + row_count += 1 + self._makeItem(lis, row_count) # 完成剩下的 + else: + self._makeItem(lis, row_count) + + def _makeItem(self, li_s, row_count): + li_s = self.splist(li_s, 6) + for row, lis in enumerate(li_s): + for col, li in enumerate(lis): + a = li.find("a") + video_url = a.get("href") # 视频播放地址 + img = a.find("img") + cover_url = "http:" + img.get("r-lazyload") # 封面图片 + figure_title = img.get("alt") # 电影名 + figure_info = a.find("div/span") + figure_info = "" if figure_info is None else figure_info.text # 影片信息 + figure_score = "".join(li.xpath(".//em/text()")) # 评分 + # 主演 + figure_desc = "主演:" + \ + "".join([Actor.format(**dict(fd.items())) + for fd in li.xpath(".//div[@class='figure_desc']/a")]) + # 播放数 + figure_count = ( + li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] + path = "cache/{0}.jpg".format( + os.path.splitext(os.path.basename(video_url))[0]) + cover_path = "pic_v.png" + if os.path.isfile(path): + cover_path = path + iwidget = ItemWidget(cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, path, self) + self._layout.addWidget(iwidget, row_count + row, col) + + +class Window(QScrollArea): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(800, 600) + self.setFrameShape(self.NoFrame) + self.setWidgetResizable(True) + self.setAlignment(Qt.AlignCenter) + self._loadStart = False + # 网格窗口 + self._widget = GridWidget(self) + self._widget.loadStarted.connect(self.setLoadStarted) + self.setWidget(self._widget) + # 连接竖着的滚动条滚动事件 + self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) + # 进度条 + self.loadWidget = QSvgWidget( + self, minimumHeight=120, minimumWidth=120, visible=False) + self.loadWidget.load(Svg_icon_loading) + + def setLoadStarted(self, started): + self._loadStart = started + self.loadWidget.setVisible(started) + + def onActionTriggered(self, action): + # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 + # 同时防止多次加载同一个url + if action != QAbstractSlider.SliderMove or self._loadStart: + return + # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 + if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): + # 可以下一页了 + self._widget.load() + + def resizeEvent(self, event): + super(Window, self).resizeEvent(event) + self.loadWidget.setGeometry( + int((self.width() - self.loadWidget.minimumWidth()) / 2), + int((self.height() - self.loadWidget.minimumHeight()) / 2), + self.loadWidget.minimumWidth(), + self.loadWidget.minimumHeight() + ) + + +if __name__ == "__main__": + os.makedirs("cache", exist_ok=True) + app = QApplication(sys.argv) + w = Window() + w.show() + w._widget.load() + sys.exit(app.exec_()) diff --git a/列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay_Flow.py b/QListWidget/HotPlaylist/TencentMovieHotPlay_Flow.py similarity index 97% rename from 列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay_Flow.py rename to QListWidget/HotPlaylist/TencentMovieHotPlay_Flow.py index b3b6092..0566d73 100644 --- a/列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay_Flow.py +++ b/QListWidget/HotPlaylist/TencentMovieHotPlay_Flow.py @@ -1,320 +1,320 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -''' -Created on 2018年2月4日 -@author: Irony."[讽刺] -@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 -@email: 892768447@qq.com -@file: TencentMovieHotPlay_Flow -@description: -''' -import os -import sys -import webbrowser - -from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal -from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ - QBrush, QPaintEvent, QPixmap -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest -from PyQt5.QtSvg import QSvgWidget -from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ - QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QAbstractSlider - -from flowlayout import FlowLayout # @UnresolvedImport -from lxml.etree import HTML # @UnresolvedImport - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - -# offset=0,30,60,90 -Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" - -# 播放量图标 -Svg_icon_play_sm = ''' -'''.encode() - -Svg_icon_loading = ''''''.encode() - -# 主演 -Actor = '''{title} ''' - - -class CoverLabel(QLabel): - - def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): - super(CoverLabel, self).__init__(*args, **kwargs) -# super(CoverLabel, self).__init__( -# '
'.format(os.path.abspath(cover_path)), *args, **kwargs) - self.setCursor(Qt.PointingHandCursor) - self.setScaledContents(True) - self.setMinimumSize(220, 308) - self.setMaximumSize(220, 308) - self.cover_path = cover_path - self.cover_title = cover_title - self.video_url = video_url - self.setPixmap(QPixmap(cover_path)) - - def setCoverPath(self, path): - self.cover_path = path - - def mouseReleaseEvent(self, event): - super(CoverLabel, self).mouseReleaseEvent(event) - webbrowser.open_new_tab(self.video_url) - - def paintEvent(self, event): - super(CoverLabel, self).paintEvent(event) - if hasattr(self, "cover_title") and self.cover_title != "": - # 底部绘制文字 - painter = QPainter(self) - rect = self.rect() - # 粗略字体高度 - painter.save() - fheight = self.fontMetrics().height() - # 底部矩形框背景渐变颜色 - bottomRectColor = QLinearGradient( - rect.width() / 2, rect.height() - 24 - fheight, - rect.width() / 2, rect.height()) - bottomRectColor.setSpread(QGradient.PadSpread) - bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) - bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) - # 画半透明渐变矩形框 - painter.setPen(Qt.NoPen) - painter.setBrush(QBrush(bottomRectColor)) - painter.drawRect(rect.x(), rect.height() - 24 - - fheight, rect.width(), 24 + fheight) - painter.restore() - # 距离底部一定高度画文字 - font = self.font() or QFont() - font.setPointSize(8) - painter.setFont(font) - painter.setPen(Qt.white) - rect.setHeight(rect.height() - 12) # 底部减去一定高度 - painter.drawText(rect, Qt.AlignHCenter | - Qt.AlignBottom, self.cover_title) - - -class ItemWidget(QWidget): - - def __init__(self, cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): - super(ItemWidget, self).__init__(*args, **kwargs) - self.setMaximumSize(220, 420) - self.setMaximumSize(220, 420) - self.img_path = img_path - self.cover_url = cover_url - layout = QVBoxLayout(self) - layout.setContentsMargins(10, 20, 10, 0) - # 图片label - self.clabel = CoverLabel(cover_path, figure_info, video_url, self) - layout.addWidget(self.clabel) - - # 片名和分数 - flayout = QHBoxLayout() - flayout.addWidget(QLabel(figure_title, self)) - flayout.addItem(QSpacerItem( - 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) - flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) - layout.addLayout(flayout) - - # 主演 - layout.addWidget( - QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) - - # 播放量 - blayout = QHBoxLayout() - count_icon = QSvgWidget(self) - count_icon.setMaximumSize(16, 16) - count_icon.load(Svg_icon_play_sm) - blayout.addWidget(count_icon) - blayout.addWidget( - QLabel(figure_count, self, styleSheet="color: #999999;")) - layout.addLayout(blayout) - - def setCover(self, path): - self.clabel.setCoverPath(path) - self.clabel.setPixmap(QPixmap(path)) -# self.clabel.setText(''.format(os.path.abspath(path))) - - def sizeHint(self): - # 每个item控件的大小 - return QSize(220, 420) - - def event(self, event): - if isinstance(event, QPaintEvent): - if event.rect().height() > 20 and hasattr(self, "clabel"): - if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 - # print("start download img:", self.cover_url) - req = QNetworkRequest(QUrl(self.cover_url)) - # 设置两个自定义属性方便后期reply中处理 - req.setAttribute(QNetworkRequest.User + 1, self) - req.setAttribute(QNetworkRequest.User + 2, self.img_path) - self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 - return super(ItemWidget, self).event(event) - - -class GridWidget(QWidget): - - Page = 0 - loadStarted = pyqtSignal(bool) - - def __init__(self, *args, **kwargs): - super(GridWidget, self).__init__(*args, **kwargs) - self._layout = FlowLayout(self) # 使用自定义流式布局 - # 异步网络下载管理器 - self._manager = QNetworkAccessManager(self) - self._manager.finished.connect(self.onFinished) - - def load(self): - if self.Page == -1: - return - self.loadStarted.emit(True) - # 延迟一秒后调用目的在于显示进度条 - QTimer.singleShot(1000, self._load) - - def _load(self): - print("load url:", Url.format(self.Page * 30)) - url = QUrl(Url.format(self.Page * 30)) - self._manager.get(QNetworkRequest(url)) - - def onFinished(self, reply): - # 请求完成后会调用该函数 - req = reply.request() # 获取请求 - iwidget = req.attribute(QNetworkRequest.User + 1, None) - path = req.attribute(QNetworkRequest.User + 2, None) - html = reply.readAll().data() - reply.deleteLater() - del reply - if iwidget and path and html: - # 这里是图片下载完毕 - open(path, "wb").write(html) - iwidget.setCover(path) - return - # 解析网页 - self._parseHtml(html) - self.loadStarted.emit(False) - - def _parseHtml(self, html): - # encoding = chardet.detect(html) or {} - # html = html.decode(encoding.get("encoding","utf-8")) - html = HTML(html) - # 查找所有的li list_item - lis = html.xpath("//li[@class='list_item']") - if not lis: - self.Page = -1 # 后面没有页面了 - return - self.Page += 1 - self._makeItem(lis) - - def _makeItem(self, lis): - for li in lis: - a = li.find("a") - video_url = a.get("href") # 视频播放地址 - img = a.find("img") - cover_url = "http:" + img.get("r-lazyload") # 封面图片 - figure_title = img.get("alt") # 电影名 - figure_info = a.find("div/span") - figure_info = "" if figure_info is None else figure_info.text # 影片信息 - figure_score = "".join(li.xpath(".//em/text()")) # 评分 - # 主演 - figure_desc = "主演:" + \ - "".join([Actor.format(**dict(fd.items())) - for fd in li.xpath(".//div[@class='figure_desc']/a")]) - # 播放数 - figure_count = ( - li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] - path = "cache/{0}.jpg".format( - os.path.splitext(os.path.basename(video_url))[0]) - cover_path = "pic_v.png" - if os.path.isfile(path): - cover_path = path - iwidget = ItemWidget(cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, path, self) - self._layout.addWidget(iwidget) - - -class Window(QScrollArea): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - self.resize(800, 600) - self.setFrameShape(self.NoFrame) - self.setWidgetResizable(True) - self.setAlignment(Qt.AlignCenter) - self._loadStart = False - # 网格窗口 - self._widget = GridWidget(self) - self._widget.loadStarted.connect(self.setLoadStarted) - self.setWidget(self._widget) - # 连接竖着的滚动条滚动事件 - self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) - # 进度条 - self.loadWidget = QSvgWidget( - self, minimumHeight=120, minimumWidth=120, visible=False) - self.loadWidget.load(Svg_icon_loading) - - def setLoadStarted(self, started): - self._loadStart = started - self.loadWidget.setVisible(started) - - def onActionTriggered(self, action): - # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 - # 同时防止多次加载同一个url - if action != QAbstractSlider.SliderMove or self._loadStart: - return - # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 - if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): - # 可以下一页了 - self._widget.load() - - def resizeEvent(self, event): - super(Window, self).resizeEvent(event) - self.loadWidget.setGeometry( - int((self.width() - self.loadWidget.minimumWidth()) / 2), - int((self.height() - self.loadWidget.minimumHeight()) / 2), - self.loadWidget.minimumWidth(), - self.loadWidget.minimumHeight() - ) - - -if __name__ == "__main__": - os.makedirs("cache", exist_ok=True) - app = QApplication(sys.argv) - w = Window() - w.show() - w._widget.load() - sys.exit(app.exec_()) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Created on 2018年2月4日 +@author: Irony."[讽刺] +@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 +@email: 892768447@qq.com +@file: TencentMovieHotPlay_Flow +@description: +''' +import os +import sys +import webbrowser + +from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal +from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ + QBrush, QPaintEvent, QPixmap +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest +from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ + QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QAbstractSlider + +from flowlayout import FlowLayout # @UnresolvedImport +from lxml.etree import HTML # @UnresolvedImport + + +__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" +__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" +__Version__ = "Version 1.0" + +# offset=0,30,60,90 +Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" + +# 播放量图标 +Svg_icon_play_sm = ''' +'''.encode() + +Svg_icon_loading = ''''''.encode() + +# 主演 +Actor = '''{title} ''' + + +class CoverLabel(QLabel): + + def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): + super(CoverLabel, self).__init__(*args, **kwargs) +# super(CoverLabel, self).__init__( +# ''.format(os.path.abspath(cover_path)), *args, **kwargs) + self.setCursor(Qt.PointingHandCursor) + self.setScaledContents(True) + self.setMinimumSize(220, 308) + self.setMaximumSize(220, 308) + self.cover_path = cover_path + self.cover_title = cover_title + self.video_url = video_url + self.setPixmap(QPixmap(cover_path)) + + def setCoverPath(self, path): + self.cover_path = path + + def mouseReleaseEvent(self, event): + super(CoverLabel, self).mouseReleaseEvent(event) + webbrowser.open_new_tab(self.video_url) + + def paintEvent(self, event): + super(CoverLabel, self).paintEvent(event) + if hasattr(self, "cover_title") and self.cover_title != "": + # 底部绘制文字 + painter = QPainter(self) + rect = self.rect() + # 粗略字体高度 + painter.save() + fheight = self.fontMetrics().height() + # 底部矩形框背景渐变颜色 + bottomRectColor = QLinearGradient( + rect.width() / 2, rect.height() - 24 - fheight, + rect.width() / 2, rect.height()) + bottomRectColor.setSpread(QGradient.PadSpread) + bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) + bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) + # 画半透明渐变矩形框 + painter.setPen(Qt.NoPen) + painter.setBrush(QBrush(bottomRectColor)) + painter.drawRect(rect.x(), rect.height() - 24 - + fheight, rect.width(), 24 + fheight) + painter.restore() + # 距离底部一定高度画文字 + font = self.font() or QFont() + font.setPointSize(8) + painter.setFont(font) + painter.setPen(Qt.white) + rect.setHeight(rect.height() - 12) # 底部减去一定高度 + painter.drawText(rect, Qt.AlignHCenter | + Qt.AlignBottom, self.cover_title) + + +class ItemWidget(QWidget): + + def __init__(self, cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): + super(ItemWidget, self).__init__(*args, **kwargs) + self.setMaximumSize(220, 420) + self.setMaximumSize(220, 420) + self.img_path = img_path + self.cover_url = cover_url + layout = QVBoxLayout(self) + layout.setContentsMargins(10, 20, 10, 0) + # 图片label + self.clabel = CoverLabel(cover_path, figure_info, video_url, self) + layout.addWidget(self.clabel) + + # 片名和分数 + flayout = QHBoxLayout() + flayout.addWidget(QLabel(figure_title, self)) + flayout.addItem(QSpacerItem( + 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) + layout.addLayout(flayout) + + # 主演 + layout.addWidget( + QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) + + # 播放量 + blayout = QHBoxLayout() + count_icon = QSvgWidget(self) + count_icon.setMaximumSize(16, 16) + count_icon.load(Svg_icon_play_sm) + blayout.addWidget(count_icon) + blayout.addWidget( + QLabel(figure_count, self, styleSheet="color: #999999;")) + layout.addLayout(blayout) + + def setCover(self, path): + self.clabel.setCoverPath(path) + self.clabel.setPixmap(QPixmap(path)) +# self.clabel.setText(''.format(os.path.abspath(path))) + + def sizeHint(self): + # 每个item控件的大小 + return QSize(220, 420) + + def event(self, event): + if isinstance(event, QPaintEvent): + if event.rect().height() > 20 and hasattr(self, "clabel"): + if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 + # print("start download img:", self.cover_url) + req = QNetworkRequest(QUrl(self.cover_url)) + # 设置两个自定义属性方便后期reply中处理 + req.setAttribute(QNetworkRequest.User + 1, self) + req.setAttribute(QNetworkRequest.User + 2, self.img_path) + self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 + return super(ItemWidget, self).event(event) + + +class GridWidget(QWidget): + + Page = 0 + loadStarted = pyqtSignal(bool) + + def __init__(self, *args, **kwargs): + super(GridWidget, self).__init__(*args, **kwargs) + self._layout = FlowLayout(self) # 使用自定义流式布局 + # 异步网络下载管理器 + self._manager = QNetworkAccessManager(self) + self._manager.finished.connect(self.onFinished) + + def load(self): + if self.Page == -1: + return + self.loadStarted.emit(True) + # 延迟一秒后调用目的在于显示进度条 + QTimer.singleShot(1000, self._load) + + def _load(self): + print("load url:", Url.format(self.Page * 30)) + url = QUrl(Url.format(self.Page * 30)) + self._manager.get(QNetworkRequest(url)) + + def onFinished(self, reply): + # 请求完成后会调用该函数 + req = reply.request() # 获取请求 + iwidget = req.attribute(QNetworkRequest.User + 1, None) + path = req.attribute(QNetworkRequest.User + 2, None) + html = reply.readAll().data() + reply.deleteLater() + del reply + if iwidget and path and html: + # 这里是图片下载完毕 + open(path, "wb").write(html) + iwidget.setCover(path) + return + # 解析网页 + self._parseHtml(html) + self.loadStarted.emit(False) + + def _parseHtml(self, html): + # encoding = chardet.detect(html) or {} + # html = html.decode(encoding.get("encoding","utf-8")) + html = HTML(html) + # 查找所有的li list_item + lis = html.xpath("//li[@class='list_item']") + if not lis: + self.Page = -1 # 后面没有页面了 + return + self.Page += 1 + self._makeItem(lis) + + def _makeItem(self, lis): + for li in lis: + a = li.find("a") + video_url = a.get("href") # 视频播放地址 + img = a.find("img") + cover_url = "http:" + img.get("r-lazyload") # 封面图片 + figure_title = img.get("alt") # 电影名 + figure_info = a.find("div/span") + figure_info = "" if figure_info is None else figure_info.text # 影片信息 + figure_score = "".join(li.xpath(".//em/text()")) # 评分 + # 主演 + figure_desc = "主演:" + \ + "".join([Actor.format(**dict(fd.items())) + for fd in li.xpath(".//div[@class='figure_desc']/a")]) + # 播放数 + figure_count = ( + li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] + path = "cache/{0}.jpg".format( + os.path.splitext(os.path.basename(video_url))[0]) + cover_path = "pic_v.png" + if os.path.isfile(path): + cover_path = path + iwidget = ItemWidget(cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, path, self) + self._layout.addWidget(iwidget) + + +class Window(QScrollArea): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(800, 600) + self.setFrameShape(self.NoFrame) + self.setWidgetResizable(True) + self.setAlignment(Qt.AlignCenter) + self._loadStart = False + # 网格窗口 + self._widget = GridWidget(self) + self._widget.loadStarted.connect(self.setLoadStarted) + self.setWidget(self._widget) + # 连接竖着的滚动条滚动事件 + self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) + # 进度条 + self.loadWidget = QSvgWidget( + self, minimumHeight=120, minimumWidth=120, visible=False) + self.loadWidget.load(Svg_icon_loading) + + def setLoadStarted(self, started): + self._loadStart = started + self.loadWidget.setVisible(started) + + def onActionTriggered(self, action): + # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 + # 同时防止多次加载同一个url + if action != QAbstractSlider.SliderMove or self._loadStart: + return + # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 + if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): + # 可以下一页了 + self._widget.load() + + def resizeEvent(self, event): + super(Window, self).resizeEvent(event) + self.loadWidget.setGeometry( + int((self.width() - self.loadWidget.minimumWidth()) / 2), + int((self.height() - self.loadWidget.minimumHeight()) / 2), + self.loadWidget.minimumWidth(), + self.loadWidget.minimumHeight() + ) + + +if __name__ == "__main__": + os.makedirs("cache", exist_ok=True) + app = QApplication(sys.argv) + w = Window() + w.show() + w._widget.load() + sys.exit(app.exec_()) diff --git a/列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay_ListWidget.py b/QListWidget/HotPlaylist/TencentMovieHotPlay_ListWidget.py similarity index 97% rename from 列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay_ListWidget.py rename to QListWidget/HotPlaylist/TencentMovieHotPlay_ListWidget.py index 9af820f..e1031d4 100644 --- a/列表/QListWidget/腾讯视频热播列表/TencentMovieHotPlay_ListWidget.py +++ b/QListWidget/HotPlaylist/TencentMovieHotPlay_ListWidget.py @@ -1,312 +1,312 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -''' -Created on 2018年2月4日 -@author: Irony."[讽刺] -@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 -@email: 892768447@qq.com -@file: TencentMovieHotPlay_ListWidget -@description: -''' -import os -import sys -import webbrowser - -from PyQt5.QtCore import QSize, Qt, QUrl, QTimer -from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ - QBrush, QPaintEvent, QPixmap -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest -from PyQt5.QtSvg import QSvgWidget -from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ - QHBoxLayout, QSpacerItem, QSizePolicy, QAbstractSlider,\ - QListWidget, QListWidgetItem - -from lxml.etree import HTML # @UnresolvedImport - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - -# offset=0,30,60,90 -Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" - -# 播放量图标 -Svg_icon_play_sm = ''' -'''.encode() - -Svg_icon_loading = ''''''.encode() - -# 主演 -Actor = '''{title} ''' - - -class CoverLabel(QLabel): - - def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): - super(CoverLabel, self).__init__(*args, **kwargs) -# super(CoverLabel, self).__init__( -# ''.format(os.path.abspath(cover_path)), *args, **kwargs) - self.setCursor(Qt.PointingHandCursor) - self.setScaledContents(True) - self.setMinimumSize(220, 308) - self.setMaximumSize(220, 308) - self.cover_path = cover_path - self.cover_title = cover_title - self.video_url = video_url - self.setPixmap(QPixmap(cover_path)) - - def setCoverPath(self, path): - self.cover_path = path - - def mouseReleaseEvent(self, event): - super(CoverLabel, self).mouseReleaseEvent(event) - webbrowser.open_new_tab(self.video_url) - - def paintEvent(self, event): - super(CoverLabel, self).paintEvent(event) - if hasattr(self, "cover_title") and self.cover_title != "": - # 底部绘制文字 - painter = QPainter(self) - rect = self.rect() - # 粗略字体高度 - painter.save() - fheight = self.fontMetrics().height() - # 底部矩形框背景渐变颜色 - bottomRectColor = QLinearGradient( - rect.width() / 2, rect.height() - 24 - fheight, - rect.width() / 2, rect.height()) - bottomRectColor.setSpread(QGradient.PadSpread) - bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) - bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) - # 画半透明渐变矩形框 - painter.setPen(Qt.NoPen) - painter.setBrush(QBrush(bottomRectColor)) - painter.drawRect(rect.x(), rect.height() - 24 - - fheight, rect.width(), 24 + fheight) - painter.restore() - # 距离底部一定高度画文字 - font = self.font() or QFont() - font.setPointSize(8) - painter.setFont(font) - painter.setPen(Qt.white) - rect.setHeight(rect.height() - 12) # 底部减去一定高度 - painter.drawText(rect, Qt.AlignHCenter | - Qt.AlignBottom, self.cover_title) - - -class ItemWidget(QWidget): - - def __init__(self, cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, img_path, manager, *args, **kwargs): - super(ItemWidget, self).__init__(*args, **kwargs) - self.setMaximumSize(220, 420) - self.setMaximumSize(220, 420) - self.img_path = img_path - self.cover_url = cover_url - self._manager = manager - layout = QVBoxLayout(self) - layout.setContentsMargins(10, 20, 10, 0) - # 图片label - self.clabel = CoverLabel(cover_path, figure_info, video_url, self) - layout.addWidget(self.clabel) - - # 片名和分数 - flayout = QHBoxLayout() - flayout.addWidget(QLabel(figure_title, self)) - flayout.addItem(QSpacerItem( - 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) - flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) - layout.addLayout(flayout) - - # 主演 - layout.addWidget( - QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) - - # 播放量 - blayout = QHBoxLayout() - count_icon = QSvgWidget(self) - count_icon.setMaximumSize(16, 16) - count_icon.load(Svg_icon_play_sm) - blayout.addWidget(count_icon) - blayout.addWidget( - QLabel(figure_count, self, styleSheet="color: #999999;")) - layout.addLayout(blayout) - - def setCover(self, path): - self.clabel.setCoverPath(path) - self.clabel.setPixmap(QPixmap(path)) -# self.clabel.setText(''.format(os.path.abspath(path))) - - def sizeHint(self): - # 每个item控件的大小 - return QSize(220, 420) - - def event(self, event): - if isinstance(event, QPaintEvent): - if event.rect().height() > 20 and hasattr(self, "clabel"): - if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 - # print("start download img:", self.cover_url) - req = QNetworkRequest(QUrl(self.cover_url)) - # 设置两个自定义属性方便后期reply中处理 - req.setAttribute(QNetworkRequest.User + 1, self) - req.setAttribute(QNetworkRequest.User + 2, self.img_path) - self._manager.get(req) # 调用父窗口中的下载器下载 - return super(ItemWidget, self).event(event) - - -class Window(QListWidget): - - Page = 0 - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - self.resize(800, 600) - self.setFrameShape(self.NoFrame) # 无边框 - self.setFlow(self.LeftToRight) # 从左到右 - self.setWrapping(True) # 这三个组合可以达到和FlowLayout一样的效果 - self.setResizeMode(self.Adjust) - - self._loadStart = False - # 连接竖着的滚动条滚动事件 - self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) - # 进度条 - self.loadWidget = QSvgWidget( - self, minimumHeight=120, minimumWidth=120, visible=False) - self.loadWidget.load(Svg_icon_loading) - - # 异步网络下载管理器 - self._manager = QNetworkAccessManager(self) - self._manager.finished.connect(self.onFinished) - - def load(self): - if self.Page == -1: - return - self._loadStart = True - self.loadWidget.setVisible(True) - # 延迟一秒后调用目的在于显示进度条 - QTimer.singleShot(1000, self._load) - - def _load(self): - print("load url:", Url.format(self.Page * 30)) - url = QUrl(Url.format(self.Page * 30)) - self._manager.get(QNetworkRequest(url)) - - def onFinished(self, reply): - # 请求完成后会调用该函数 - req = reply.request() # 获取请求 - iwidget = req.attribute(QNetworkRequest.User + 1, None) - path = req.attribute(QNetworkRequest.User + 2, None) - html = reply.readAll().data() - reply.deleteLater() - del reply - if iwidget and path and html: - # 这里是图片下载完毕 - open(path, "wb").write(html) - iwidget.setCover(path) - return - # 解析网页 - self._parseHtml(html) - self._loadStart = False - self.loadWidget.setVisible(False) - - def _parseHtml(self, html): - # encoding = chardet.detect(html) or {} - # html = html.decode(encoding.get("encoding","utf-8")) - html = HTML(html) - # 查找所有的li list_item - lis = html.xpath("//li[@class='list_item']") - if not lis: - self.Page = -1 # 后面没有页面了 - return - self.Page += 1 - self._makeItem(lis) - - def _makeItem(self, lis): - for li in lis: - a = li.find("a") - video_url = a.get("href") # 视频播放地址 - img = a.find("img") - cover_url = "http:" + img.get("r-lazyload") # 封面图片 - figure_title = img.get("alt") # 电影名 - figure_info = a.find("div/span") - figure_info = "" if figure_info is None else figure_info.text # 影片信息 - figure_score = "".join(li.xpath(".//em/text()")) # 评分 - # 主演 - figure_desc = "主演:" + \ - "".join([Actor.format(**dict(fd.items())) - for fd in li.xpath(".//div[@class='figure_desc']/a")]) - # 播放数 - figure_count = ( - li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] - path = "cache/{0}.jpg".format( - os.path.splitext(os.path.basename(video_url))[0]) - cover_path = "pic_v.png" - if os.path.isfile(path): - cover_path = path - iwidget = ItemWidget(cover_path, figure_info, figure_title, - figure_score, figure_desc, figure_count, video_url, cover_url, path, self._manager, self) - item = QListWidgetItem(self) - item.setSizeHint(iwidget.sizeHint()) - self.setItemWidget(item, iwidget) - - def onActionTriggered(self, action): - # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 - # 同时防止多次加载同一个url - if action != QAbstractSlider.SliderMove or self._loadStart: - return - # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 - if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): - # 可以下一页了 - self.load() - - def resizeEvent(self, event): - super(Window, self).resizeEvent(event) - self.loadWidget.setGeometry( - int((self.width() - self.loadWidget.minimumWidth()) / 2), - int((self.height() - self.loadWidget.minimumHeight()) / 2), - self.loadWidget.minimumWidth(), - self.loadWidget.minimumHeight() - ) - - -if __name__ == "__main__": - os.makedirs("cache", exist_ok=True) - app = QApplication(sys.argv) - w = Window() - w.show() - w.load() - sys.exit(app.exec_()) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Created on 2018年2月4日 +@author: Irony."[讽刺] +@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 +@email: 892768447@qq.com +@file: TencentMovieHotPlay_ListWidget +@description: +''' +import os +import sys +import webbrowser + +from PyQt5.QtCore import QSize, Qt, QUrl, QTimer +from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ + QBrush, QPaintEvent, QPixmap +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest +from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ + QHBoxLayout, QSpacerItem, QSizePolicy, QAbstractSlider,\ + QListWidget, QListWidgetItem + +from lxml.etree import HTML # @UnresolvedImport + + +__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" +__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" +__Version__ = "Version 1.0" + +# offset=0,30,60,90 +Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" + +# 播放量图标 +Svg_icon_play_sm = ''' +'''.encode() + +Svg_icon_loading = ''''''.encode() + +# 主演 +Actor = '''{title} ''' + + +class CoverLabel(QLabel): + + def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): + super(CoverLabel, self).__init__(*args, **kwargs) +# super(CoverLabel, self).__init__( +# ''.format(os.path.abspath(cover_path)), *args, **kwargs) + self.setCursor(Qt.PointingHandCursor) + self.setScaledContents(True) + self.setMinimumSize(220, 308) + self.setMaximumSize(220, 308) + self.cover_path = cover_path + self.cover_title = cover_title + self.video_url = video_url + self.setPixmap(QPixmap(cover_path)) + + def setCoverPath(self, path): + self.cover_path = path + + def mouseReleaseEvent(self, event): + super(CoverLabel, self).mouseReleaseEvent(event) + webbrowser.open_new_tab(self.video_url) + + def paintEvent(self, event): + super(CoverLabel, self).paintEvent(event) + if hasattr(self, "cover_title") and self.cover_title != "": + # 底部绘制文字 + painter = QPainter(self) + rect = self.rect() + # 粗略字体高度 + painter.save() + fheight = self.fontMetrics().height() + # 底部矩形框背景渐变颜色 + bottomRectColor = QLinearGradient( + rect.width() / 2, rect.height() - 24 - fheight, + rect.width() / 2, rect.height()) + bottomRectColor.setSpread(QGradient.PadSpread) + bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) + bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) + # 画半透明渐变矩形框 + painter.setPen(Qt.NoPen) + painter.setBrush(QBrush(bottomRectColor)) + painter.drawRect(rect.x(), rect.height() - 24 - + fheight, rect.width(), 24 + fheight) + painter.restore() + # 距离底部一定高度画文字 + font = self.font() or QFont() + font.setPointSize(8) + painter.setFont(font) + painter.setPen(Qt.white) + rect.setHeight(rect.height() - 12) # 底部减去一定高度 + painter.drawText(rect, Qt.AlignHCenter | + Qt.AlignBottom, self.cover_title) + + +class ItemWidget(QWidget): + + def __init__(self, cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, img_path, manager, *args, **kwargs): + super(ItemWidget, self).__init__(*args, **kwargs) + self.setMaximumSize(220, 420) + self.setMaximumSize(220, 420) + self.img_path = img_path + self.cover_url = cover_url + self._manager = manager + layout = QVBoxLayout(self) + layout.setContentsMargins(10, 20, 10, 0) + # 图片label + self.clabel = CoverLabel(cover_path, figure_info, video_url, self) + layout.addWidget(self.clabel) + + # 片名和分数 + flayout = QHBoxLayout() + flayout.addWidget(QLabel(figure_title, self)) + flayout.addItem(QSpacerItem( + 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) + layout.addLayout(flayout) + + # 主演 + layout.addWidget( + QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) + + # 播放量 + blayout = QHBoxLayout() + count_icon = QSvgWidget(self) + count_icon.setMaximumSize(16, 16) + count_icon.load(Svg_icon_play_sm) + blayout.addWidget(count_icon) + blayout.addWidget( + QLabel(figure_count, self, styleSheet="color: #999999;")) + layout.addLayout(blayout) + + def setCover(self, path): + self.clabel.setCoverPath(path) + self.clabel.setPixmap(QPixmap(path)) +# self.clabel.setText(''.format(os.path.abspath(path))) + + def sizeHint(self): + # 每个item控件的大小 + return QSize(220, 420) + + def event(self, event): + if isinstance(event, QPaintEvent): + if event.rect().height() > 20 and hasattr(self, "clabel"): + if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 + # print("start download img:", self.cover_url) + req = QNetworkRequest(QUrl(self.cover_url)) + # 设置两个自定义属性方便后期reply中处理 + req.setAttribute(QNetworkRequest.User + 1, self) + req.setAttribute(QNetworkRequest.User + 2, self.img_path) + self._manager.get(req) # 调用父窗口中的下载器下载 + return super(ItemWidget, self).event(event) + + +class Window(QListWidget): + + Page = 0 + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(800, 600) + self.setFrameShape(self.NoFrame) # 无边框 + self.setFlow(self.LeftToRight) # 从左到右 + self.setWrapping(True) # 这三个组合可以达到和FlowLayout一样的效果 + self.setResizeMode(self.Adjust) + + self._loadStart = False + # 连接竖着的滚动条滚动事件 + self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) + # 进度条 + self.loadWidget = QSvgWidget( + self, minimumHeight=120, minimumWidth=120, visible=False) + self.loadWidget.load(Svg_icon_loading) + + # 异步网络下载管理器 + self._manager = QNetworkAccessManager(self) + self._manager.finished.connect(self.onFinished) + + def load(self): + if self.Page == -1: + return + self._loadStart = True + self.loadWidget.setVisible(True) + # 延迟一秒后调用目的在于显示进度条 + QTimer.singleShot(1000, self._load) + + def _load(self): + print("load url:", Url.format(self.Page * 30)) + url = QUrl(Url.format(self.Page * 30)) + self._manager.get(QNetworkRequest(url)) + + def onFinished(self, reply): + # 请求完成后会调用该函数 + req = reply.request() # 获取请求 + iwidget = req.attribute(QNetworkRequest.User + 1, None) + path = req.attribute(QNetworkRequest.User + 2, None) + html = reply.readAll().data() + reply.deleteLater() + del reply + if iwidget and path and html: + # 这里是图片下载完毕 + open(path, "wb").write(html) + iwidget.setCover(path) + return + # 解析网页 + self._parseHtml(html) + self._loadStart = False + self.loadWidget.setVisible(False) + + def _parseHtml(self, html): + # encoding = chardet.detect(html) or {} + # html = html.decode(encoding.get("encoding","utf-8")) + html = HTML(html) + # 查找所有的li list_item + lis = html.xpath("//li[@class='list_item']") + if not lis: + self.Page = -1 # 后面没有页面了 + return + self.Page += 1 + self._makeItem(lis) + + def _makeItem(self, lis): + for li in lis: + a = li.find("a") + video_url = a.get("href") # 视频播放地址 + img = a.find("img") + cover_url = "http:" + img.get("r-lazyload") # 封面图片 + figure_title = img.get("alt") # 电影名 + figure_info = a.find("div/span") + figure_info = "" if figure_info is None else figure_info.text # 影片信息 + figure_score = "".join(li.xpath(".//em/text()")) # 评分 + # 主演 + figure_desc = "主演:" + \ + "".join([Actor.format(**dict(fd.items())) + for fd in li.xpath(".//div[@class='figure_desc']/a")]) + # 播放数 + figure_count = ( + li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] + path = "cache/{0}.jpg".format( + os.path.splitext(os.path.basename(video_url))[0]) + cover_path = "pic_v.png" + if os.path.isfile(path): + cover_path = path + iwidget = ItemWidget(cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, path, self._manager, self) + item = QListWidgetItem(self) + item.setSizeHint(iwidget.sizeHint()) + self.setItemWidget(item, iwidget) + + def onActionTriggered(self, action): + # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 + # 同时防止多次加载同一个url + if action != QAbstractSlider.SliderMove or self._loadStart: + return + # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 + if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): + # 可以下一页了 + self.load() + + def resizeEvent(self, event): + super(Window, self).resizeEvent(event) + self.loadWidget.setGeometry( + int((self.width() - self.loadWidget.minimumWidth()) / 2), + int((self.height() - self.loadWidget.minimumHeight()) / 2), + self.loadWidget.minimumWidth(), + self.loadWidget.minimumHeight() + ) + + +if __name__ == "__main__": + os.makedirs("cache", exist_ok=True) + app = QApplication(sys.argv) + w = Window() + w.show() + w.load() + sys.exit(app.exec_()) diff --git a/列表/QListWidget/腾讯视频热播列表/flowlayout.py b/QListWidget/HotPlaylist/flowlayout.py similarity index 100% rename from 列表/QListWidget/腾讯视频热播列表/flowlayout.py rename to QListWidget/HotPlaylist/flowlayout.py diff --git a/QListWidget/README.md b/QListWidget/README.md index e69de29..b02e844 100644 --- a/QListWidget/README.md +++ b/QListWidget/README.md @@ -0,0 +1,16 @@ +# QListView + +## 1、删除自定义Item +[运行](DeleteCustomItem.py) + +1. 删除item时先要通过`QListWidget.indexFromItem(item).row()`得到它的行数 +2. 通过`takeItem`函数取出该Item并删除掉,`item = self.listWidget.takeItem(row)` +3. 移除item对应的自定义控件`self.listWidget.removeItemWidget(item)` +4. 如果是清空所有Item,可以通过循环删除,但是删除的时候行号一直是0即可,原因和删除list数组一样。 + +![CustomWidgetItem](ScreenShot/DeleteCustomItem.gif) + +## 2、自定义可拖拽Item +[运行](DragDrop.py) + +![CustomWidgetSortItem](ScreenShot/DragDrop.gif) \ No newline at end of file diff --git a/列表/QListWidget/ScreenShot/删除自定义Item.gif b/QListWidget/ScreenShot/DeleteCustomItem.gif similarity index 100% rename from 列表/QListWidget/ScreenShot/删除自定义Item.gif rename to QListWidget/ScreenShot/DeleteCustomItem.gif diff --git a/列表/QListWidget/ScreenShot/自定义可拖拽Item.gif b/QListWidget/ScreenShot/DragDrop.gif similarity index 100% rename from 列表/QListWidget/ScreenShot/自定义可拖拽Item.gif rename to QListWidget/ScreenShot/DragDrop.gif