QT 实现电子相册(二)--双击展示图片与切换

双击目录树展示图片

页面布局

在ProTreeWidget的构造函数中添加双击信号的槽函数连接

1
connect(this, &ProTreeWidget::itemDoubleClicked, this, &ProTreeWidget::SlotDoubleClickItem);

接下来实现双击逻辑,判断为鼠标左键,就用一个成员变量_selected_item缓存双击的item,然后发送SigUpdateSelected信号通知右侧区域刷新显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void ProTreeWidget::SlotDoubleClickItem(QTreeWidgetItem* doubleItem, int col){
qDebug() << "ProTreeWidget::SlotDoubleClickItem" << endl;

if(QGuiApplication::mouseButtons() == Qt::LeftButton) //判断是否为左键
{
auto * tree_doubleItem = dynamic_cast<ProTreeItem*>(doubleItem);
if(!tree_doubleItem){
return;
}
int itemtype = (int)(tree_doubleItem->type());
if(itemtype == TreeItemPic){
emit SigUpdateSelected(tree_doubleItem->GetPath());
_selected_item = doubleItem;
}
}
}

之前在MainWindow的构造函数里添加了ProTree,这次我们要在添加一个PicShow类,PicShow类是我们新增的Qt设计师界面类,这里介绍它的ui内容。
1 在PicShow里添加一个网格布局,然后将PicShow设置为水平布局。
2 然后拖动一个QWidget放到PicShow左侧,再拖动一个QWidget放到PicShow的右侧,拖动一个label放到中间。
3 然后设置两个Widget的宽度都为固定的80像素。将两个widget设置为垂直布局,然后分别添加一个button。
4 拖放两个水平的spacer和垂直的spacer分别放在label的上下左右,保证label居中。
效果如下
https://cdn.llfc.club/1674899963320.jpg
ui信息
https://cdn.llfc.club/1674900340012.jpg
然后在MainWindow里添加PicShow

1
2
3
4
5
_protree = new ProTree();
ui->proLayout->addWidget(_protree,0);

_picshow = new PicShow();
ui->picLayout->addWidget(_picshow);

同时我们为PicShow新增qss文件

1
2
3
4
5
6
7
PicShow {
border-color: #9F9F9F;
border-style: solid;
border-width: 1px 1px 1px 1px;
padding: 10px;
background: rgb(64,66,68);
}

自定义按钮

现在需要实现按钮的悬浮,点击效果,所以需要继承QPushButton,实现我们自己定义的按钮类PicButton。
添加C++类PicButton,基类选择QPushButton。
实现一个设置图标的函数,参数分别为正常状态,悬浮状态,以及按下状态的效果

1
2
3
4
5
6
7
8
9
10
11
12
void PicButton::SetIcons(const QString &normal, const QString &hover, const QString &pressed)
{
_normal = normal;
_hover = hover;
_pressed = pressed;

QPixmap tmpPixmap;
tmpPixmap.load(normal);
this->resize(tmpPixmap.size());
this->setIcon(tmpPixmap);
this->setIconSize(tmpPixmap.size());
}

重载event函数,实现根据事件类型刷新按钮样式的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool PicButton::event(QEvent *event)
{
switch (event->type())
{
case QEvent::Enter:
setHoverIcon();
break;
case QEvent::Leave:
setNormalIcon();
break;
case QEvent::MouseButtonPress:
setPressIcon();
break;
case QEvent::MouseButtonRelease:
setHoverIcon();
break;
default:
break;
}
return QPushButton::event(event);
}

设置按钮样式的函数很简单,都是通过QPixmap加载的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void PicButton::setNormalIcon(){
QPixmap tmpPixmap;
tmpPixmap.load(_normal);
this->setIcon(tmpPixmap);
}

void PicButton::setHoverIcon(){
QPixmap tmpPixmap;
tmpPixmap.load(_hover);
this->setIcon(tmpPixmap);
}

void PicButton::setPressIcon(){
QPixmap tmpPixmap;
tmpPixmap.load(_pressed);
this->setIcon(tmpPixmap);
}

所以回到picshow.ui中,将两个QPushButton升级为PicButton。
这样我们在界面上将鼠标在按钮上点击和悬浮等就能看到效果了。

按钮的渐隐动画

接下来我们要实现将鼠标滑动到PicShow区域才显示前进和后退按钮,滑出PicShow区域则不显示。
为使按钮实现渐隐渐现的效果,所以我们通过动画实现。在PicShow的构造函数里创建两个渐隐渐现的动画,将效果绑定到两个按钮上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
QGraphicsOpacityEffect *opacity_pre = new QGraphicsOpacityEffect(this);
opacity_pre->setOpacity(0); //设置透明度0.5,透明范围:[0,1]
ui->previousBtn->setGraphicsEffect(opacity_pre);


QGraphicsOpacityEffect *opacity_next = new QGraphicsOpacityEffect(this);
opacity_next->setOpacity(0); //设置透明度0.5,透明范围:[0,1]
//应用到需要透明变化的控件;
ui->nextBtn->setGraphicsEffect(opacity_next);

//使用属性动画类让控件在透明度范围内变化
_animation_show_pre = new QPropertyAnimation(opacity_pre, "opacity",this);
_animation_show_pre->setEasingCurve(QEasingCurve::Linear);
_animation_show_pre->setDuration(500); //动效时长3s


_animation_show_next = new QPropertyAnimation(opacity_next, "opacity",this);
_animation_show_next->setEasingCurve(QEasingCurve::Linear);
_animation_show_next->setDuration(500); //动效时长3s

除此之外还重写PicShow的event函数,捕获其中的enter和leave事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool PicShow::event(QEvent *event)
{
switch (event->type())
{
case QEvent::Enter:
ShowPreNextBtns(true);
break;
case QEvent::Leave:
ShowPreNextBtns(false);
break;
default:
break;
}
return QDialog::event(event);
}

根据enter还是leave设置按钮显示和隐藏。显示隐藏的函数通过bool参数控制,根据是否可见并且是否隐藏综合控制动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void PicShow::ShowPreNextBtns(bool b_show)
{
if(!b_show&&_b_btnvisible){
_animation_show_pre->stop();
_animation_show_pre->setStartValue(1);
_animation_show_pre->setEndValue(0);
_animation_show_pre->start();

_animation_show_next->stop();
_animation_show_next->setStartValue(1);
_animation_show_next->setEndValue(0);
_animation_show_next->start();
_b_btnvisible = false;
return;
}

if(_selected_path ==""){
return;
}

if(b_show&&!_b_btnvisible){
_animation_show_pre->stop();
_animation_show_pre->setStartValue(0);
_animation_show_pre->setEndValue(1);
_animation_show_pre->start();

_animation_show_next->stop();
_animation_show_next->setStartValue(0);
_animation_show_next->setEndValue(1);
_animation_show_next->start();
_b_btnvisible = true;
}

}

这样鼠标滑动和移出PicShow区域就能显示和隐藏button了。

双击左侧目录树实现图片切换

为ProTreeWidget绑定双击的槽函数,并且实现双击后发送信号通知PicShow显示图片
ProTreeWidget构造函数添加

1
connect(this, &ProTreeWidget::itemDoubleClicked, this, &ProTreeWidget::SlotDoubleClickItem);

双击逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void ProTreeWidget::SlotDoubleClickItem(QTreeWidgetItem* doubleItem, int col){
qDebug() << "ProTreeWidget::SlotDoubleClickItem" << endl;

if(QGuiApplication::mouseButtons() == Qt::LeftButton) //判断是否为左键
{
auto * tree_doubleItem = dynamic_cast<ProTreeItem*>(doubleItem);
if(!tree_doubleItem){
return;
}
int itemtype = (int)(tree_doubleItem->type());
if(itemtype == TreeItemPic){
emit SigUpdateSelected(tree_doubleItem->GetPath());
_selected_item = doubleItem;
}
}
}

在mainwindow构造函数中添加信号连接

1
2
3
auto * pro_pic_show = dynamic_cast<PicShow*>(_picshow);
connect(pro_tree_widget, &ProTreeWidget::SigUpdateSelected,pro_pic_show,
&PicShow::SlotSelectItem);

在PicShow里实现SlotSelectItem显示选中图像

1
2
3
4
5
6
7
8
9
10
11
void PicShow::SlotSelectItem(const QString& path)
{
_selected_path = path;

_pix_map.load(path);
auto width = this->width()-20;
auto height = this->height()-20;
_pix_map = _pix_map.scaled(width,height,Qt::KeepAspectRatio);
ui->label->setPixmap(_pix_map);

}

这样就实现了点击左侧不同的目录树item显示不同的图片。

图像重绘

因为每次图像形状不同,都会导致重回,这样会造成资源浪费,所以将重回事件写在MainWindow,只在MainWindow改变时重绘

1
2
3
4
5
6
7
void MainWindow::resizeEvent(QResizeEvent *event)
{
auto * pro_pic_show = dynamic_cast<PicShow*>(_picshow);
pro_pic_show->ReloadPic();
QMainWindow::resizeEvent(event);
}

picshow实现重新加载逻辑

1
2
3
4
5
6
7
8
9
10
11
void PicShow::ReloadPic()
{
if(_selected_path != ""){
const auto &width = ui->gridLayout->geometry().width();
const auto &height = ui->gridLayout->geometry().height();
_pix_map.load(_selected_path);

_pix_map = _pix_map.scaled(width,height,Qt::KeepAspectRatio);
ui->label->setPixmap(_pix_map);
}
}

qss样式补充

因为目录树双击选中效果,悬浮,以及折叠等效果,我们补充一下qss.
以及按钮需要设置为无边框效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
PicShow {
border-color: #9F9F9F;
border-style: solid;
border-width: 1px 1px 1px 1px;
padding: 10px;
background: rgb(64,66,68);
}

QWizard {
color:rgb(231,231,231);
background-color:rgb(46,47,48);
}

QWizard QLabel#tips {
color: red;
}

QTreeView {
color:rgb(231,231,231);
background-color:rgb(46,47,48);
border: 0px;
}

QTreeView::item:hover {
background: rgb(38, 95, 153);
}

QTreeView::item:selected {
background: rgb(38, 95, 153);
}

/*设置选中条目字体变粗*/
#TreeProActiveItem {
font-weight:bold;
}


QTreeView::branch:open:has-children:!has-siblings,
QTreeView::branch:open:has-children:has-siblings
{image: url(:/icon/down.png);}


QTreeView::branch:closed:has-children:!has-siblings,
QTreeView::branch:closed:has-children:has-siblings
{image: url(:/icon/right.png);}

#nextBtn,#closeBtn,#previousBtn {
border: 0px;
}

前进后退按钮切换图片

在picshow的构造函数中绑定按钮点击信号

1
2
connect(ui->nextBtn,&QPushButton::clicked,this, &PicShow::SigNextClicked);
connect(ui->previousBtn,&QPushButton::clicked,this, &PicShow::SigPreClicked);

当按钮点击后左侧目录树的选中条目也进行更新,所以在MainWindow构造函数中

1
2
3
connect(pro_pic_show, &PicShow::SigPreClicked,pro_tree_widget,&ProTreeWidget::SlotPreShow);
connect(pro_pic_show, &PicShow::SigNextClicked,pro_tree_widget,&ProTreeWidget::SlotNextShow);
connect(pro_tree_widget,&ProTreeWidget::SigUpdatePic,pro_pic_show,&PicShow::SlotUpdatePic);

ProTreeWidget的更新逻辑, 设置选中item,并且发送更新图片信号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void ProTreeWidget::SlotPreShow(){
if(!_selected_item){
return;
}

auto * curItem = dynamic_cast<ProTreeItem*>(_selected_item)->GetPreItem();
if(!curItem){
return;
}
emit SigUpdatePic(curItem->GetPath());
_selected_item = curItem;
this->setCurrentItem(curItem);
}
void ProTreeWidget::SlotNextShow(){

if(!_selected_item){
return;
}

auto * curItem = dynamic_cast<ProTreeItem*>(_selected_item)->GetNextItem();
if(!curItem){
return;
}
emit SigUpdatePic(curItem->GetPath());
_selected_item = curItem;
this->setCurrentItem(curItem);
}

SigUpdatePic信号会被PicShow的SlotUpdatePic函数绑定,该函数实现了图片的切换展示。

1
2
3
4
5
6
7
8
9
10
11
12
void PicShow::SlotUpdatePic(const QString &_path)
{
_selected_path = _path;
if(_selected_path != ""){
const auto &width = ui->gridLayout->geometry().width();
const auto &height = ui->gridLayout->geometry().height();
_pix_map.load(_selected_path);

_pix_map = _pix_map.scaled(width,height,Qt::KeepAspectRatio);
ui->label->setPixmap(_pix_map);
}
}

关闭和激活项目

前文提到了我们要实现关闭项目的功能,在这里补充一下。ProTreeWidget的构造函数添加

1
2
3
4
 _action_setstart = new QAction(QIcon(":/icon/core.png"), tr("设置活动项目"),this);
_action_closepro = new QAction(QIcon(":/icon/close.png"), tr("关闭项目"), this);
connect(_action_closepro, &QAction::triggered, this, &ProTreeWidget::SlotClosePro);
connect(_action_setstart, &QAction::triggered, this, &ProTreeWidget::SlotSetActive);

关闭项目的槽函数中现弹出一个删除对话框,这个也是我们添加的设计师界面类,不再赘述了。
删除对话框的布局ui是这样的
https://cdn.llfc.club/1674963688531.jpg
可以勾选同时删除本地文件夹项目文件,这样我们的程序也会把copy的文件删除。
关闭项目逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void ProTreeWidget::SlotClosePro()
{
RemoveProDialog remove_pro_dialog;
auto res = remove_pro_dialog.exec();
bool b_remove = remove_pro_dialog.IsRemoved();
auto index_right_btn = this->indexOfTopLevelItem(_right_btn_item);
auto * protreeitem = dynamic_cast<ProTreeItem*>(_right_btn_item);
auto * selecteditem = dynamic_cast<ProTreeItem*>(_selected_item);

auto delete_path = protreeitem->GetPath();
qDebug() << "remove project from path: " << delete_path;
_set_path.remove(delete_path);
if(b_remove){
QDir delete_dir(delete_path);
delete_dir.removeRecursively();
}

if(protreeitem == _active_item){
_active_item = nullptr;
}

if(selecteditem && protreeitem == selecteditem->GetRoot()){
selecteditem = nullptr;
emit SigClearSelected();
}

delete this->takeTopLevelItem(index_right_btn);
_right_btn_item = nullptr;

}

判断当前选中的item是否在删除的项目中,如果在则发送SigClearSelected信号。
如果是设置活动项目,我们只需要在槽函数里设置字体变粗即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void ProTreeWidget::SlotSetActive()
{
if(!_right_btn_item){
return;
}

QFont nullFont;
nullFont.setBold(false);
if(_active_item){
_active_item->setFont(0,nullFont);
}

_active_item = _right_btn_item;
nullFont.setBold(true);
_active_item->setFont(0,nullFont);

}

到目前为止我们点击按钮和item等切换图片的功能就实现了,下一篇实现幻灯片放映。

源码链接

源码链接
https://gitee.com/secondtonone1/qt-learning-notes