C++动态壁纸软件的开发(含源文件)

在上一篇博客中,使用C++和Qt框架完成了Wallpaper的雏形,现于之前的基础上,完善更多功能。

之前已经完成了打开文件,播放暂停,设置父窗体的功能,本文将不再详细说明。

最终效果(视频来自“大污师”的鲸落)

C++动态壁纸软件的开发(含源文件)_第1张图片

 头文件support.h 

这个头文件里专门放宏定义内容,便于修改 

#ifndef SUPPORT_H
#define SUPPORT_H

#define PLAYER_FILTER "视频(*.mp4 *.avi *.wmv);;图片(*.jpg *.png *.bmp);;所有文件(*.*)"
#define BUTTON_TEXT_PLAY "播放"
#define BUTTON_TEXT_PAUSE "暂停"

#define HEKY_AUTORUN "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"
#define OPEN_BROWSER_FILE_PATH "OpenBrowser.exe"
#define OPEN_REFRESHBAKGROUND_FILE_PATH "ReFreshBackground.exe"

#define VOLUME_HIGH "image: url(:/Image/volume-high.png);"
#define VOLUME_LOW "image: url(:/Image/volume-low.png);"
#define VOLUME_OFF "image: url(:/Image/volume-off.png);"

#define SHARED_MEMORY_KEY "wallpaper_by_dearxuan"

#endif // SUPPORT_H

 开机自启

显然每次开机都要手动打开软件非常麻烦,如果能让软件开机自启,就会方便很多

实现开机自启需要读写注册表

HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run 

C++动态壁纸软件的开发(含源文件)_第2张图片

  左边是是键名,右边是键值,键值指向文件地址,系统会在开机后执行对应文件,只要向注册表写入自己程序的地址,就可以实现开机自启。当关闭开机自启时,只要删除这一项就可以

//设置开机自启,flag表示是否开机自启
void Widget::AutoRun(bool flag) {
    QString appName = QApplication::applicationName();
    QString appPath = QApplication::applicationFilePath();
    appPath = appPath.replace("/", "\\");
    //打开注册表
    QSettings *reg = new QSettings(
            HEKY_AUTORUN,//宏定义,自启动的注册表位置,详细内容在头文件support.h里
            QSettings::NativeFormat);
    QString val = reg->value(appName).toString();//根据appName获取键值
    if (flag) {//打开开机自启
        if (val != appPath) {//键值不是本程序地址或者不存在,则写入键值
            reg->setValue(appName, appPath);
        }
        QString v = reg->value(appName).toString();//读取刚刚写入的键值
        if (v != appPath) {//写入失败
            ui->checkBox_1->setChecked(false);
            QMessageBox::warning(this, "拒绝访问", "添加注册表失败.", QMessageBox::Ok, QMessageBox::Ok);
        }
    } else {//关闭开机自启
        reg->remove(appName);//移除键值
        QString v = reg->value(appName).toString();
        if (v != "") {//键值仍然存在,移除失败
            ui->checkBox_1->setChecked(true);
            QMessageBox::warning(this, "拒绝访问", "移除注册表失败.", QMessageBox::Ok, QMessageBox::Ok);
        }
    }
    reg->deleteLater();//延迟删除
}

 同时启动软件时还要判断是否打开了开机自启,如果打开了,则自动把checkbox1设置成勾选状态

//判断是否打开开机自启
bool isAutoRun() {
    QString appName = QApplication::applicationName();
    QString appPath = QApplication::applicationFilePath();
    appPath = appPath.replace("/", "\\");
    QSettings *reg = new QSettings(
            HEKY_AUTORUN,//宏定义,在头文件support.h里面
            QSettings::Registry64Format);
    //如果注册表键值就是本程序的地址,表示打开了开机自启
    QString val = reg->value(appName).toString();
    return val == appPath;
}

任务栏托盘角标

当点击“叉叉”时,关闭窗口,但是不退出程序,用户通过点击任务栏角标再次打开窗口 

 在Widget.h里定义QSystemTrayIcon

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    About about;
    QVideoWidget videoWidget;
    QMediaPlayer player;
    QSystemTrayIcon tray;//任务栏托盘角标
    QMediaPlaylist *list;

 在程序启动时添加菜单,绑定事件,并显示

tray.setToolTip("Wallpaper");//任务栏托盘小图标的标题
tray.setIcon(QIcon(":Icon/wallpaper.ico"));//任务栏托盘小图标
QMenu *menu = new QMenu();
QAction *action_showDialog = new QAction("打开主窗口");
QAction *action_exit = new QAction("退出");
menu->addAction(action_showDialog);//添加菜单
menu->addAction(action_exit);//添加菜单
tray.setContextMenu(menu);//绑定菜单
//绑定托盘小图标点击事件
connect(&tray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(OnSystemTrayClicked(QSystemTrayIcon::ActivationReason)));
//绑定菜单点击事件
connect(action_showDialog, SIGNAL(triggered(bool)), this, SLOT(onShowDialogClick()));
connect(action_exit, SIGNAL(triggered(bool)), this, SLOT(on_exitButton_clicked()));
tray.show();//显示小图标

 点击“打开主窗口”时显示窗口

//点击了任务栏托盘小图标
void Widget::OnSystemTrayClicked(QSystemTrayIcon::ActivationReason reason) {
    if (reason == QSystemTrayIcon::Trigger) {
        this->showNormal();//如果点击了任务栏托盘小图标,则显示本窗口
    }
}

点击“退出”时相当于点击主窗口里面的“退出”按钮,所以直接绑定到“退出”按钮的点击事件

//退出按钮点击事件,同时任务栏小图标的”退出“菜单也会执行这个函数
void Widget::on_exitButton_clicked() {
    if (!firstPlay) {//还没有打开视频,不需要刷新背景
        ReFreshBackground();
    }
    this->hide();//隐藏窗口
    WriteSetting();//写入设置
    qApp->exit(0);//退出
}

二进制读写文件

在程序退出时,自动保存设置,包括:是否自动播放上一次视频,当前音量,当前视频文件地址。

用txt来保存虽然写入很方便,但是读取时候需要自己转换,非常痛苦,而且txt能被人轻易编辑,造成程序崩溃,所以采用二进制方法,防止用户随意编辑。设置将会被保存到当前路径下的setting文件(没有后缀)。

//读取设置
void Widget::ReadSetting() {
    QFile file("setting");//设置文件,不需要后缀
    bool n = false;//是否加载上一次视频
    int v = 100;//音量
    if (file.open(QIODevice::ReadOnly)) {//文件存在
        QDataStream input(&file);
        input >> n >> v >> path;//读取
    }
    file.close();
    ui->checkBox_2->setChecked(n);
    ui->volumeSlider->setValue(v);
    if(!n) path = "";
}
//写入设置
void Widget::WriteSetting() {
    QFile file("setting");
    if (file.open(QIODevice::WriteOnly)) {
        QDataStream output(&file);
        output << ui->checkBox_2->isChecked() << ui->volumeSlider->value() << path;
    }
    file.close();
}

在程序打开时现执行ReadSetting()来读取设置,并自动设置音量,路径等。

音量

添加volumeSlider滑块来控制音量,将范围设置成0~1000,这样可以减小滑动时的卡顿感,但是在处理数据时要把数值除以10,并在用户释放鼠标时自动修正滑块位置,比如把998修正为1000

//音量滑块值改变事件
void Widget::on_volumeSlider_valueChanged(int value) {
    //音量滑块的范围为0~1000,音量有效值是0~100,设置1000是为了减小卡顿感觉
    value = (value + 5) / 10;//四舍五入除以10
    ui->volumeLabel->setText(QString::number(value));
    if (value > 50 && VolumeNowPic != 2) {//高音量
        VolumeNowPic = 2;
        ui->volumePic->setStyleSheet(VOLUME_HIGH);
    } else if (value > 0 && value < 50 && VolumeNowPic != 1) {//低音量
        ui->volumePic->setStyleSheet(VOLUME_LOW);
        VolumeNowPic = 1;
    } else if (value == 0 && VolumeNowPic != 0) {//关闭音量
        ui->volumePic->setStyleSheet(VOLUME_OFF);
        VolumeNowPic = 0;
    }
    player.setVolume(value);//设置音量
}

//音量滑块鼠标释放事件
void Widget::on_volumeSlider_sliderReleased() {
    //当滑块位置在999时,此时音量是100,当用户释放鼠标后自动把滑块位置设置成1000
    ui->volumeSlider->setValue(player.volume() * 10);
}

当用户点击音量图标时,用户希望能够立即静音,但是又不能改变滑块位置,如果用户再次点击或者移动滑块,则关闭静音,类似windows的音量设置那样。

上面的代码里使用了VolumeNowPic这个变量,这个是用来保存当前图标的,0,1,2分别表示关,低音量,高音量。引入这个变量是用来判断当前图标和修改后的图标,如果一致就放弃修改。

音量图标使用QLabel来放置

 由于QLabel不支持点击事件,所以我们要自己添加。

ui->volumePic->installEventFilter(this);//注册音量Label的点击事件

 Widget.h里写上 

C++动态壁纸软件的开发(含源文件)_第3张图片

 在cpp里写上

//事件监听
bool Widget::eventFilter(QObject *obj, QEvent *event) {
    //文件Label被点击
    if (obj == ui->fileLabel && event->type() == QEvent::MouseButtonPress) {
        QMouseEvent *mouse = static_cast(event);
        if (mouse->button() == Qt::LeftButton && path != "") {
            //如果是左键,则打开文件位置
            QProcess process;
            QString p = path.replace("/", "\\");
            QString cmd = "explorer /select," + p;
            process.startDetached(cmd);
        }
    }
    //音量Label被点击
    if (obj == ui->volumePic && event->type() == QEvent::MouseButtonPress) {
        QMouseEvent *mouse = static_cast(event);
        if (mouse->button() == Qt::LeftButton) {
            if (player.volume() == 0) {
                //音量是0,恢复volumeSlider上的正常音量
                on_volumeSlider_valueChanged(ui->volumeSlider->value());
            } else {
                //音量不是0,静音
                player.setVolume(0);
                ui->volumePic->setStyleSheet(VOLUME_OFF);
                VolumeNowPic = 0;
            }
        }
    }
    return false;
}

 上面那个是点击文件QLabel打开文件位置的函数,第二个if语句才是重点,先判断音量是否为0,如果是0,则执行一次value_changed事件,这样就会自动根据滑块位置设置音量大小,如果滑块就在0的位置上,那么执行这个函数也什么都不会发生。如果音量不是0,则把音量改成0,同时修改VolumeNowPic为0,表示当前显示的是静音图片。代码里的VOLUME_OFF是我自己定义的,在头文件support.h里面

 播放速度

与音量滑块同理,速度滑动栏的范围是50~1000,最终结果除以50,因为速度不能为0,所以实际值应该在0.1~2.0。当切换速度时,视频会明显黑屏以下,因此切换次数越少越好,当用户滑动滑块时,会不断触发value_changed,所以首先想到把切换速度写在释放鼠标的函数里,但是用户使用滚轮时,不会触发鼠标事件,所以还是需要在value_changed里写。我们可以设置一个布尔变量,来指示value_changed是否可用,当用户按下鼠标时,把这个值设置成false,这样就不会切换速度,如果用户使用滚轮或者点击滑动栏,仍然会执行切换速度,但是切换次数远少于移动鼠标时的value_changed次数,这是可以接受的。 

bool sliderEnable = true;//速度滑块的value_changed监听是否可用
//播放速度滑块鼠标释放事件
void Widget::on_rateSlider_sliderReleased() {
    int rate = (ui->rateSlider->value() + 25) / 50;//24舍25入除以50,此时rate的范围是1~20
    ui->rateSlider->setValue(rate * 50);//乘上50,校准滑块位置
    sliderEnable = true;//value监听可用
    on_rateSlider_valueChanged(ui->rateSlider->value());//执行一次value监听事件,修改player的播放速度
}

//播放速度滑块value改变事件
void Widget::on_rateSlider_valueChanged(int value) {
    //范围是50~1000,对应速度0.1~2.0,默认为1.0,设置1000是为了减小卡顿感
    int rate = (value + 25) / 50;//24舍25入除以50,此时rate的范围是1~20
    ui->rateLabel->setText(QString::number(rate / 10.0, 'f', 1));//显示数值的Label,把rate除以10并取小数点后一位
    if (sliderEnable) {//如果value监听可用
        //在修改播放速度时会有明显黑屏,为了防止过多黑屏影响视觉感受,当用户滑动滑块时不改变速度,当用户释放鼠标时再改变
        //当用户使用滚轮,或者点击滑动栏,触发stepadd或stepsub时,需要改变播放速度
        player.setPlaybackRate(rate / 10.0);//设置播放速度
    }
}

//速度滑块点击事件
void Widget::on_rateSlider_sliderPressed() {
    sliderEnable = false;//value监听不可用,防止过多黑屏,当用户使用滚轮时不触发本事件,因此value监听有效
}

加载视频

在之前已经实现了加载视频的功能,现在原有基础上进行扩展。

之前直接把整个过程写到了按钮事件里,是因为当时还没有自动加载视频功能,必须通过按钮打开视频,而现在程序支持自动打开上一次视频,这就要求我们把加载过程单独写成一个函数。

//加载视频文件
void Widget::LoadFile(const QString file) {
    QFile video(file);
    if (!video.exists()) {
        //文件不存在
        ui->fileLabel->setText("未打开文件");
        ui->playButton->setText(BUTTON_TEXT_PLAY);
        ui->playButton->setEnabled(false);
        path = "";
        ui->fileLabel->setToolTip(NULL);
        ui->fileLabel->setCursor(Qt::ArrowCursor);//默认鼠标样式
        return;
    }
    QFileInfo fi = QFileInfo(path);//文件信息
    this->ui->fileLabel->setText("正在加载:" + fi.fileName());//文件Label显示文件名
    path = file;
    if (firstPlay) {//是否是第一次播放
        firstPlay = false;
        HWND hwnd = (HWND) videoWidget.winId();//获取视频窗体的句柄
        SetBackground(hwnd);//把视频窗体设置成背景窗体的子窗体
        videoWidget.setWindowFlags(Qt::FramelessWindowHint);
        videoWidget.setAspectRatioMode(Qt::IgnoreAspectRatio);
        videoWidget.showFullScreen();
        list = new QMediaPlaylist;
    }
    //清理原有的播放列表,加入视频
    list->clear();
    list->addMedia(QMediaContent(QUrl::fromLocalFile(file)));
    list->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);//循环播放
    player.setPlaylist(list);
    player.setVideoOutput(&videoWidget);//设置输出窗体
    player.play();
    ui->playButton->setText(BUTTON_TEXT_PAUSE);
    ui->playButton->setEnabled(true);
    ui->fileLabel->setText("正在播放:" + fi.fileName());
    ui->fileLabel->setToolTip(path.replace("/", "\\"));
    ui->fileLabel->setCursor(Qt::PointingHandCursor);//“指向”鼠标样式
}

在按钮事件里调用该函数

//点击了“打开”按钮,打开文件
void Widget::on_openButton_clicked() {
    QString file = QFileDialog::getOpenFileName(
            this,
            "打开文件",
            path,//初始地址
            PLAYER_FILTER);//过滤器,仅显示支持的格式
    if (!file.isEmpty()) {
        LoadFile(file);
    }
}

关于

C++动态壁纸软件的开发(含源文件)_第4张图片

 “关于”窗体里面添加作者信息,并增加“刷新壁纸”和“清理注册表”功能

 当程序异常退出,会导致背景显示异常,点击“刷新壁纸”可以让背景正常显示。

卸载软件之前,可以取消开机自启,或者点击“清理注册表”,来删除启动项。

EXE文件:https://dearx.lanzoui.com/iLUQGs80y2h

源代码:https://dearx.lanzoui.com/iw8x8s80u5g

你可能感兴趣的:(Wallpaper原理和实现,qt,qt5,c++,windows,10)