🏠 主窗口框架设计
🎨 在上一章搭建好基础环境后,现在我们要为视频播放器设计一个现代化、专业的主窗口界面。一个好的界面设计不仅要美观,更要实用和符合用户习惯。
🎯 本章目标
- 🏗️ 完善主窗口的整体布局设计
- 🎨 实现现代化的UI风格和主题
- 📱 创建响应式布局适配不同窗口大小
- 🛠️ 优化菜单栏、工具栏和状态栏
- 🎪 为视频显示区域预留合适的空间
🎨 界面设计规划
整体布局结构
我们的主窗口将采用经典的视频播放器布局:
🎪 现代化设计风格
我们将采用扁平化设计风格,具有以下特点:
- 深色主题:减少视觉疲劳,专业感强
- 圆角元素:现代化视觉效果
- 渐变背景:增加层次感
- 图标字体:清晰的矢量图标
🏗️ 代码实现
1️⃣ 更新主窗口头文件
src/main/mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSplitter>
#include <QLabel>
#include <QSlider>
#include <QPushButton>
#include <QListWidget>
#include <QTextEdit>
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QProgressBar>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void resizeEvent(QResizeEvent *event) override;
void closeEvent(QCloseEvent *event) override;
private slots:
void onOpenFile();
void onPlay();
void onPause();
void onStop();
void onVolumeChanged(int volume);
void onFullScreen();
void onAbout();
private:
void setupUI();
void setupMenuBar();
void setupToolBar();
void setupStatusBar();
void setupCentralWidget();
void setupVideoArea();
void setupControlBar();
void setupSidePanel();
void loadStyleSheet();
void updateWindowTitle(const QString &fileName = QString());
private:
// UI组件
QWidget *m_centralWidget;
QSplitter *m_mainSplitter;
// 视频区域
QWidget *m_videoArea;
QLabel *m_videoPlaceholder;
// 控制栏
QWidget *m_controlBar;
QPushButton *m_playButton;
QPushButton *m_stopButton;
QSlider *m_progressSlider;
QLabel *m_timeLabel;
QSlider *m_volumeSlider;
QPushButton *m_muteButton;
QPushButton *m_fullscreenButton;
// 侧边栏
QWidget *m_sidePanel;
QListWidget *m_playlist;
QTextEdit *m_videoInfo;
// 状态栏
QLabel *m_statusLabel;
QProgressBar *m_progressBar;
QLabel *m_volumeLabel;
// 工具栏动作
QAction *m_openAction;
QAction *m_playAction;
QAction *m_pauseAction;
QAction *m_stopAction;
QAction *m_fullscreenAction;
};
#endif // MAINWINDOW_H
2️⃣ 主窗口实现
src/main/mainwindow.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSplitter>
#include <QLabel>
#include <QSlider>
#include <QPushButton>
#include <QListWidget>
#include <QTextEdit>
#include <QProgressBar>
#include <QFileDialog>
#include <QMessageBox>
#include <QResizeEvent>
#include <QCloseEvent>
#include <QSettings>
#include <QStandardPaths>
#include <QDir>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_centralWidget(nullptr)
, m_mainSplitter(nullptr)
, m_videoArea(nullptr)
, m_videoPlaceholder(nullptr)
, m_controlBar(nullptr)
, m_playButton(nullptr)
, m_stopButton(nullptr)
, m_progressSlider(nullptr)
, m_timeLabel(nullptr)
, m_volumeSlider(nullptr)
, m_muteButton(nullptr)
, m_fullscreenButton(nullptr)
, m_sidePanel(nullptr)
, m_playlist(nullptr)
, m_videoInfo(nullptr)
, m_statusLabel(nullptr)
, m_progressBar(nullptr)
, m_volumeLabel(nullptr)
{
setupUI();
setupMenuBar();
setupToolBar();
setupStatusBar();
loadStyleSheet();
// 设置窗口属性
setWindowTitle("Video Player");
setMinimumSize(900, 650);
resize(1400, 900);
// 居中显示
move(QApplication::desktop()->screen()->rect().center() - rect().center());
// 恢复窗口状态
QSettings settings;
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowState").toByteArray());
}
MainWindow::~MainWindow()
{
// 保存窗口状态
QSettings settings;
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState());
}
void MainWindow::setupUI()
{
setupCentralWidget();
}
void MainWindow::setupCentralWidget()
{
m_centralWidget = new QWidget(this);
setCentralWidget(m_centralWidget);
// 创建主分割器
m_mainSplitter = new QSplitter(Qt::Horizontal, this);
// 设置主布局
QHBoxLayout *mainLayout = new QHBoxLayout(m_centralWidget);
mainLayout->setContentsMargins(8, 8, 8, 8);
mainLayout->addWidget(m_mainSplitter);
// 设置左侧视频区域
setupVideoArea();
// 设置右侧面板
setupSidePanel();
// 添加到分割器
m_mainSplitter->addWidget(m_videoArea);
m_mainSplitter->addWidget(m_sidePanel);
// 设置分割器比例 (视频区域:侧边栏 = 3:1)
m_mainSplitter->setStretchFactor(0, 3);
m_mainSplitter->setStretchFactor(1, 1);
m_mainSplitter->setSizes(QList<int>() << 900 << 300);
}
void MainWindow::setupVideoArea()
{
m_videoArea = new QWidget();
m_videoArea->setMinimumWidth(600);
QVBoxLayout *videoLayout = new QVBoxLayout(m_videoArea);
videoLayout->setContentsMargins(0, 0, 0, 0);
videoLayout->setSpacing(0);
// 视频显示占位符
m_videoPlaceholder = new QLabel();
m_videoPlaceholder->setAlignment(Qt::AlignCenter);
m_videoPlaceholder->setText(
"🎬\n\n拖拽视频文件到此处\n或点击 \"打开文件\" 开始播放"
);
m_videoPlaceholder->setStyleSheet(
"QLabel {"
" font-size: 18px;"
" color: #7f8c8d;"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #2c3e50, stop:1 #34495e);"
" border: 2px dashed #7f8c8d;"
" border-radius: 10px;"
" min-height: 400px;"
"}"
);
videoLayout->addWidget(m_videoPlaceholder);
// 播放控制栏
setupControlBar();
videoLayout->addWidget(m_controlBar);
}
void MainWindow::setupControlBar()
{
m_controlBar = new QWidget();
m_controlBar->setFixedHeight(80);
m_controlBar->setStyleSheet(
"QWidget {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #34495e, stop:1 #2c3e50);"
" border-radius: 5px;"
"}"
);
QHBoxLayout *controlLayout = new QHBoxLayout(m_controlBar);
controlLayout->setContentsMargins(15, 10, 15, 10);
// 播放控制按钮
m_playButton = new QPushButton("▶");
m_playButton->setFixedSize(50, 50);
m_playButton->setStyleSheet(
"QPushButton {"
" background: #3498db;"
" color: white;"
" border: none;"
" border-radius: 25px;"
" font-size: 16px;"
" font-weight: bold;"
"}"
"QPushButton:hover {"
" background: #2980b9;"
"}"
"QPushButton:pressed {"
" background: #21618c;"
"}"
);
connect(m_playButton, &QPushButton::clicked, this, &MainWindow::onPlay);
m_stopButton = new QPushButton("⏹");
m_stopButton->setFixedSize(40, 40);
m_stopButton->setStyleSheet(
"QPushButton {"
" background: #e74c3c;"
" color: white;"
" border: none;"
" border-radius: 20px;"
" font-size: 14px;"
"}"
"QPushButton:hover {"
" background: #c0392b;"
"}"
);
connect(m_stopButton, &QPushButton::clicked, this, &MainWindow::onStop);
// 进度条
m_progressSlider = new QSlider(Qt::Horizontal);
m_progressSlider->setStyleSheet(
"QSlider::groove:horizontal {"
" height: 6px;"
" background: #7f8c8d;"
" border-radius: 3px;"
"}"
"QSlider::handle:horizontal {"
" background: #3498db;"
" border: none;"
" width: 16px;"
" border-radius: 8px;"
" margin: -5px 0;"
"}"
"QSlider::sub-page:horizontal {"
" background: #3498db;"
" border-radius: 3px;"
"}"
);
// 时间标签
m_timeLabel = new QLabel("00:00 / 00:00");
m_timeLabel->setStyleSheet(
"QLabel {"
" color: #ecf0f1;"
" font-size: 12px;"
" min-width: 80px;"
"}"
);
// 音量控制
m_muteButton = new QPushButton("🔊");
m_muteButton->setFixedSize(35, 35);
m_muteButton->setStyleSheet(
"QPushButton {"
" background: transparent;"
" color: #ecf0f1;"
" border: none;"
" border-radius: 17px;"
" font-size: 14px;"
"}"
"QPushButton:hover {"
" background: #7f8c8d;"
"}"
);
m_volumeSlider = new QSlider(Qt::Horizontal);
m_volumeSlider->setMaximum(100);
m_volumeSlider->setValue(70);
m_volumeSlider->setFixedWidth(100);
m_volumeSlider->setStyleSheet(m_progressSlider->styleSheet());
connect(m_volumeSlider, &QSlider::valueChanged, this, &MainWindow::onVolumeChanged);
// 全屏按钮
m_fullscreenButton = new QPushButton("⛶");
m_fullscreenButton->setFixedSize(35, 35);
m_fullscreenButton->setStyleSheet(m_muteButton->styleSheet());
connect(m_fullscreenButton, &QPushButton::clicked, this, &MainWindow::onFullScreen);
// 布局
controlLayout->addWidget(m_playButton);
controlLayout->addWidget(m_stopButton);
controlLayout->addSpacing(10);
controlLayout->addWidget(m_progressSlider);
controlLayout->addSpacing(10);
controlLayout->addWidget(m_timeLabel);
controlLayout->addSpacing(20);
controlLayout->addWidget(m_muteButton);
controlLayout->addWidget(m_volumeSlider);
controlLayout->addSpacing(10);
controlLayout->addWidget(m_fullscreenButton);
}
void MainWindow::setupSidePanel()
{
m_sidePanel = new QWidget();
m_sidePanel->setMaximumWidth(350);
m_sidePanel->setMinimumWidth(250);
QVBoxLayout *sideLayout = new QVBoxLayout(m_sidePanel);
sideLayout->setContentsMargins(5, 0, 0, 0);
// 播放列表
QLabel *playlistLabel = new QLabel("📋 播放列表");
playlistLabel->setStyleSheet(
"QLabel {"
" font-size: 14px;"
" font-weight: bold;"
" color: #2c3e50;"
" padding: 8px 5px;"
"}"
);
m_playlist = new QListWidget();
m_playlist->setStyleSheet(
"QListWidget {"
" background: #ecf0f1;"
" border: 1px solid #bdc3c7;"
" border-radius: 5px;"
" padding: 5px;"
"}"
"QListWidget::item {"
" padding: 8px;"
" border-bottom: 1px solid #d5dbdb;"
"}"
"QListWidget::item:selected {"
" background: #3498db;"
" color: white;"
"}"
"QListWidget::item:hover {"
" background: #d6eaf8;"
"}"
);
// 视频信息
QLabel *infoLabel = new QLabel("ℹ️ 视频信息");
infoLabel->setStyleSheet(playlistLabel->styleSheet());
m_videoInfo = new QTextEdit();
m_videoInfo->setMaximumHeight(200);
m_videoInfo->setReadOnly(true);
m_videoInfo->setStyleSheet(
"QTextEdit {"
" background: #ecf0f1;"
" border: 1px solid #bdc3c7;"
" border-radius: 5px;"
" padding: 8px;"
" font-size: 11px;"
" color: #2c3e50;"
"}"
);
m_videoInfo->setPlainText(
"文件名: 未选择文件\n"
"格式: --\n"
"分辨率: --\n"
"时长: --\n"
"比特率: --\n"
"编码: --"
);
sideLayout->addWidget(playlistLabel);
sideLayout->addWidget(m_playlist, 1);
sideLayout->addSpacing(10);
sideLayout->addWidget(infoLabel);
sideLayout->addWidget(m_videoInfo);
}
void MainWindow::setupMenuBar()
{
// 文件菜单
QMenu *fileMenu = menuBar()->addMenu("&文件");
m_openAction = fileMenu->addAction("&打开文件...");
m_openAction->setShortcut(QKeySequence::Open);
m_openAction->setStatusTip("打开视频文件");
connect(m_openAction, &QAction::triggered, this, &MainWindow::onOpenFile);
fileMenu->addSeparator();
QAction *exitAction = fileMenu->addAction("&退出");
exitAction->setShortcut(QKeySequence::Quit);
exitAction->setStatusTip("退出程序");
connect(exitAction, &QAction::triggered, this, &QWidget::close);
// 播放菜单
QMenu *playMenu = menuBar()->addMenu("&播放");
m_playAction = playMenu->addAction("&播放/暂停");
m_playAction->setShortcut(QKeySequence(Qt::Key_Space));
connect(m_playAction, &QAction::triggered, this, &MainWindow::onPlay);
m_stopAction = playMenu->addAction("&停止");
m_stopAction->setShortcut(QKeySequence(Qt::Key_S));
connect(m_stopAction, &QAction::triggered, this, &MainWindow::onStop);
playMenu->addSeparator();
m_fullscreenAction = playMenu->addAction("&全屏");
m_fullscreenAction->setShortcut(QKeySequence(Qt::Key_F11));
connect(m_fullscreenAction, &QAction::triggered, this, &MainWindow::onFullScreen);
// 工具菜单
QMenu *toolsMenu = menuBar()->addMenu("&工具");
toolsMenu->addAction("&视频转码...", []() {
QMessageBox::information(nullptr, "提示", "转码功能将在后续章节实现");
});
// 帮助菜单
QMenu *helpMenu = menuBar()->addMenu("&帮助");
helpMenu->addAction("&关于", this, &MainWindow::onAbout);
}
void MainWindow::setupToolBar()
{
QToolBar *mainToolBar = addToolBar("主工具栏");
mainToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
mainToolBar->setMovable(false);
// 添加工具栏动作
mainToolBar->addAction(m_openAction);
mainToolBar->addSeparator();
mainToolBar->addAction(m_playAction);
mainToolBar->addAction(m_stopAction);
mainToolBar->addSeparator();
mainToolBar->addAction(m_fullscreenAction);
// 工具栏样式
mainToolBar->setStyleSheet(
"QToolBar {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #ecf0f1, stop:1 #d5dbdb);"
" border-bottom: 1px solid #bdc3c7;"
" spacing: 3px;"
" padding: 5px;"
"}"
"QToolButton {"
" background: transparent;"
" border: none;"
" padding: 8px;"
" border-radius: 4px;"
" color: #2c3e50;"
"}"
"QToolButton:hover {"
" background: #d6eaf8;"
"}"
"QToolButton:pressed {"
" background: #3498db;"
" color: white;"
"}"
);
}
void MainWindow::setupStatusBar()
{
m_statusLabel = new QLabel("就绪");
statusBar()->addWidget(m_statusLabel);
// 进度条
m_progressBar = new QProgressBar();
m_progressBar->setVisible(false);
m_progressBar->setMaximumWidth(200);
statusBar()->addPermanentWidget(m_progressBar);
// 音量显示
m_volumeLabel = new QLabel("音量: 70%");
statusBar()->addPermanentWidget(m_volumeLabel);
// 版本信息
QLabel *versionLabel = new QLabel("v1.0.0");
versionLabel->setStyleSheet("color: #7f8c8d;");
statusBar()->addPermanentWidget(versionLabel);
}
void MainWindow::loadStyleSheet()
{
// 应用全局样式
QString styleSheet =
"QMainWindow {"
" background: #ecf0f1;"
"}"
"QMenuBar {"
" background: #ecf0f1;"
" border-bottom: 1px solid #bdc3c7;"
" color: #2c3e50;"
"}"
"QMenuBar::item {"
" background: transparent;"
" padding: 8px 12px;"
"}"
"QMenuBar::item:selected {"
" background: #3498db;"
" color: white;"
"}"
"QMenu {"
" background: white;"
" border: 1px solid #bdc3c7;"
" color: #2c3e50;"
"}"
"QMenu::item {"
" padding: 8px 25px;"
"}"
"QMenu::item:selected {"
" background: #3498db;"
" color: white;"
"}"
"QStatusBar {"
" background: #ecf0f1;"
" border-top: 1px solid #bdc3c7;"
" color: #2c3e50;"
"}";
setStyleSheet(styleSheet);
}
// 槽函数实现
void MainWindow::onOpenFile()
{
QString fileName = QFileDialog::getOpenFileName(
this,
"打开视频文件",
QStandardPaths::writableLocation(QStandardPaths::MoviesLocation),
"视频文件 (*.mp4 *.avi *.mkv *.wmv *.flv *.mov *.m4v);;所有文件 (*.*)"
);
if (!fileName.isEmpty()) {
updateWindowTitle(QFileInfo(fileName).baseName());
m_statusLabel->setText("已选择文件: " + QFileInfo(fileName).fileName());
// 更新播放列表
m_playlist->addItem(QFileInfo(fileName).fileName());
// 更新视频信息(暂时显示文件信息)
QFileInfo fileInfo(fileName);
m_videoInfo->setPlainText(
QString("文件名: %1\n格式: %2\n大小: %3 MB\n路径: %4")
.arg(fileInfo.fileName())
.arg(fileInfo.suffix().toUpper())
.arg(fileInfo.size() / 1024.0 / 1024.0, 0, 'f', 2)
.arg(fileInfo.absolutePath())
);
}
}
void MainWindow::onPlay()
{
if (m_playButton->text() == "▶") {
m_playButton->setText("⏸");
m_statusLabel->setText("播放中...");
} else {
m_playButton->setText("▶");
m_statusLabel->setText("已暂停");
}
}
void MainWindow::onPause()
{
m_playButton->setText("▶");
m_statusLabel->setText("已暂停");
}
void MainWindow::onStop()
{
m_playButton->setText("▶");
m_statusLabel->setText("已停止");
}
void MainWindow::onVolumeChanged(int volume)
{
m_volumeLabel->setText(QString("音量: %1%").arg(volume));
// 更新静音按钮图标
if (volume == 0) {
m_muteButton->setText("🔇");
} else if (volume < 30) {
m_muteButton->setText("🔈");
} else if (volume < 70) {
m_muteButton->setText("🔉");
} else {
m_muteButton->setText("🔊");
}
}
void MainWindow::onFullScreen()
{
if (isFullScreen()) {
showNormal();
m_fullscreenAction->setText("&全屏");
} else {
showFullScreen();
m_fullscreenAction->setText("&退出全屏");
}
}
void MainWindow::onAbout()
{
QMessageBox::about(this, "关于",
"🎬 Video Player v1.0.0\n\n"
"基于Qt开发的现代化视频播放器\n"
"支持多种视频格式播放和转码功能\n\n"
"© 2024 Your Company"
);
}
void MainWindow::updateWindowTitle(const QString &fileName)
{
if (fileName.isEmpty()) {
setWindowTitle("Video Player");
} else {
setWindowTitle(QString("Video Player - %1").arg(fileName));
}
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
QMainWindow::resizeEvent(event);
// 可以在这里添加响应式布局逻辑
if (event->size().width() < 1000) {
m_sidePanel->setVisible(false);
} else {
m_sidePanel->setVisible(true);
}
}
void MainWindow::closeEvent(QCloseEvent *event)
{
// 保存设置
QSettings settings;
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState());
event->accept();
}
3️⃣ 更新CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(VideoPlayer VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Qt组件
find_package(Qt6 REQUIRED COMPONENTS
Core
Widgets
Multimedia
MultimediaWidgets
Network
)
# 启用Qt MOC、UIC、RCC
qt6_standard_project_setup()
# 源文件
set(SOURCES
src/main/main.cpp
src/main/mainwindow.cpp
src/main/mainwindow.h
)
# 资源文件
qt6_add_resources(SOURCES resources/resources.qrc)
# 创建可执行文件
qt6_add_executable(VideoPlayer ${SOURCES})
# 链接库
target_link_libraries(VideoPlayer
Qt6::Core
Qt6::Widgets
Qt6::Multimedia
Qt6::MultimediaWidgets
Qt6::Network
)
# 包含目录
target_include_directories(VideoPlayer PRIVATE src)
# 编译选项
target_compile_definitions(VideoPlayer PRIVATE
QT_DEPRECATED_WARNINGS
QT_DISABLE_DEPRECATED_BEFORE=0x060000
)
# Windows特定设置
if(WIN32)
set_target_properties(VideoPlayer PROPERTIES
WIN32_EXECUTABLE TRUE
)
endif()
🎨 界面效果展示
运行程序后,你将看到:
🏠 主界面特色
- 现代化深色主题:专业的视觉效果
- 响应式布局:窗口大小变化时自动调整
- 直观的控制界面:大按钮、清晰的图标
- 信息丰富的侧边栏:播放列表和视频信息
🎮 交互功能
- 文件选择:支持常见视频格式
- 播放控制:播放/暂停/停止按钮
- 音量调节:滑块控制,图标状态反馈
- 全屏切换:F11快捷键支持
- 窗口状态保存:记住上次的窗口大小和位置
📱 响应式设计
我们的界面具备响应式特性:
void MainWindow::resizeEvent(QResizeEvent *event)
{
QMainWindow::resizeEvent(event);
// 窗口宽度小于1000px时隐藏侧边栏
if (event->size().width() < 1000) {
m_sidePanel->setVisible(false);
} else {
m_sidePanel->setVisible(true);
}
}
🔧 编译运行
cd build
cmake ..
make # 或 cmake --build .
./VideoPlayer # 或在Windows上运行VideoPlayer.exe
📋 本章小结
在第二章中,我们完成了:
✅ 完整的UI框架:菜单栏、工具栏、状态栏、中央区域
✅ 现代化设计:深色主题、圆角按钮、渐变背景
✅ 响应式布局:自适应窗口大小变化
✅ 用户交互:文件选择、播放控制、音量调节
✅ 状态管理:窗口大小位置记忆、设置保存
🎯 下章预告
在下一章《📺 视频显示核心区域》中,我们将:
- 集成QVideoWidget实现真正的视频显示
- 处理视频文件的加载和显示
- 实现视频画面的缩放和适配
- 添加拖拽文件功能