尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MFC对话框全屏播放视频:从ActiveX控件到窗口样式与消息处理

MFC对话框全屏播放视频:从ActiveX控件到窗口样式与消息处理 简介这是一份面向Visual C开发者的全屏播放视频示例工程基于MFC对话框框架实现主要解决Windows下视频播放与窗口/全屏切换的核心问题适合有一定C基础、希望入门多媒体开发的读者。压缩包内共13个文件包括MFC对话框对应的头文件与源文件、界面资源脚本与图标以及dsp/dsw工程文件方便在VC环境中直接打开查看整体仅11KB。目前已有250人学习下载可作为轻量级参考样例研读。通过分析源码可学习MFC程序框架、对话框消息映射、视频播放组件的集成方式如DirectShow或Windows Media Player控件并理解进入全屏时调整窗口尺寸、隐藏边框与标题栏的具体写法对视频加载、播放控制与全屏切换等常见需求提供了可运行的最小实现。工程结构紧凑、代码量少便于快速抓住核心要点并迁移到自己的项目中。1. MFC 对话框里最常被低估的全屏播放门槛从老工程师手里接过来的 visual c 全屏播放视频工程往往不是打开就能全屏的。这个 zip 里的“全屏播放视频Dlg.cpp / .h”说明它至少是一个基于 MFC 对话框的程序对话框本身负责 UI播放能力来自 ActiveX 控件或 DirectShow 渲染区。真正要把视频铺满整个屏幕至少要把窗口样式、显示器边界、控件重排和 ESC 退出这四件事一起处理干净。很多资料喜欢给一个 put_FullScreenMode(TRUE) 就当解决但实际产品里那只是两步中的第一步。这篇文章就以这个典型工程为骨架把对话框骨架、播放通道、全屏切换和发布依赖拆开讲整理成可以直接落地到代码里的做法。适合正在维护 C 桌面播放器、或者接手老 MFC 工程的开发者。2. 读工程文件MFC 对话框骨架与播放通道的选择2.1 从文件清单判断这是哪一类 MFC 工程打开压缩包先看文件清单能快速判断工程年代和程序形态。这里最核心的是“全屏播放视频Dlg.h / .cpp”这是典型的 CDialog 派生类文件说明程序主界面是对话框而不是文档视图结构。文件工程中的角色全屏播放改造时的关注点全屏播放视频Dlg.cpp / .hCDialog 派生类负责 UI 与消息映射播放器控件变量、全屏状态、键盘消息Resource.h资源 ID 定义对话框 ID、播放控件 ID、按钮 ID全屏播放视频.rc / .rc2对话框资源、图标、版本信息ActiveX 控件在对话框模板中的 Layout全屏播放视频.dsp / .dswVC6 工程文件与工作区VS2010 打开时的工程升级StdAfx.cpp / .h预编译头MFC 公共头文件集中管理全屏播放视频.clwClassWizard 状态数据库丢失后可删除重建不影响编译.dsp 和 .dsw 是 VC6 到 VC2005 时代的工作区格式。用 VS2010 打开时向导会要求转换成 .sln/.vcxproj用 VS2022 则需要先装“用于最新 v143 生成工具的 C MFC (x86 和 x64)”组件。转换完成后ClassWizard 功能由“类向导”替代.clw 文件可以直接删掉。StdAfx.h 里的内容也暴露了工程底子。常见写法是// StdAfx.h #define VC_EXTRALEAN // 剪掉 Win32 中不常被用到的 API #include afxwin.h // MFC 核心窗口和控件 #include afxext.h // MFC 扩展控件如 CStatusBar #include afxcmn.h // 公共控件如 CListCtrl #include afxdisp.h // OLE AutomationActiveX 控件需要VC_EXTRALEAN会裁掉一部分极少使用的 Windows 头文件缩短编译时间但不要为了省时间把afxdisp.h也剪掉。后面要用 WMP ActiveX 控件CVariant、COleVariant等类型都依赖这个头文件。2.2 播放通道选型WMP ActiveX 控件与 DirectShowMFC 对话框工程里做视频播放历史上只有两条主流路线Windows Media Player ActiveX 控件和 DirectShow。WMP 控件的 CLSID 固定为{6BF52A52-394A-11D3-B153-00C04F79FAA6}。在 .rc 的对话框模板中直接写 CONTROL 语句就可以把这个控件嵌进对话框CONTROL {6BF52A52-394A-11D3-B153-00C04F79FAA6}, IDC_VIDEO_PLAYER, WMPlayer, WS_CHILD | WS_VISIBLE, 10, 10, 640, 400CONTROL 语句的六个参数依次是控件 CLSID、资源 ID、控件类名、窗口样式、控件在对话框坐标系里的位置。这里的类名必须写WMPlayerClsid 和类名同时存在时Windows 优先按 CLSID 创建控件实例。DirectShow 则是更底层的技术。直接用IVideoWindow::put_FullScreenMode(TRUE)可以把画面切到全屏但渲染器在 Windows 7 之后变成了 EVR消息循环、窗口关联、分辨率兼容性都要自己处理工程量明显更大。2.3 对话框工程承载 ActiveX 控件的 OLE 前置条件无论用哪种控件MFC 对话框工程要先初始化 OLE 和 COM。很多老工程只在InitInstance里写了CWinApp::InitInstance()ActiveX 控件创建时会失败表现为对话框弹不出或者播放区域一片空白。// 全屏播放视频.cpp BOOL CPlayerApp::InitInstance() { // ActiveX 控件依赖 OLE必须先于主对话框创建前初始化 if (!AfxOleInit()) { AfxMessageBox(_T(OLE 初始化失败)); return FALSE; } CWinApp::InitInstance(); CPlayerDlg dlg; m_pMainWnd dlg; dlg.DoModal(); return FALSE; }AfxOleInit()内部会调用OleInitialize之后生成的 ActiveX 播放控件才能正常创建窗口和接收事件。如果只调用CoInitialize部分 MFC 控件包装类会出现断言错误。这个函数调用失败时优先检查系统是否缺少 OLE 运行权限以及主进程是否已经初始化过不同线程的 COM。选型上我一般优先用 WMP ActiveX 控件因为对话框资源里可以直接放置UI 控制条不需要自己画事件模型也简单。DirectShow 保留给需要自绘渲染、低延迟或特殊格式的场景。3. 改造 Dlg把 WMP 播放控件挂进对话框并初始化3.1 资源放置与 DDX_Control 绑定拿到这个示例工程后最直接有效的改造方式是在对话框资源上放置一个 Windows Media Player 控件。在 VS 的资源编辑器里打开“全屏播放视频Dlg”右键对话框选择“插入 ActiveX 控件”从注册表里选 Windows Media Player系统会自动生成一个新的控件 ID通常叫IDC_OCX1在 Resource.h 里对应一个整数。把它重命名为IDC_VIDEO_PLAYER方便后面维护。在类向导中为这个控件添加变量类型选择CWMPPlayer4生成后DoDataExchange里会多出这一行void CPlayerDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); // 把对话框模板里的 ActiveX 控件与成员变量绑定 DDX_Control(pDX, IDC_VIDEO_PLAYER, m_wndPlayer); }DDX_Control不是普通控件赋值它会内部调用CWnd::Attach把 ActiveX 控件的窗口句柄关联到m_wndPlayer上。这里必须使用类向导生成的CWMPPlayer4类型而不是裸CWnd否则后续访问put_URL、GetControls会很痛苦。3.2 OnInitDialog 中的播放参数初始化播放控件挂上之后OnInitDialog里就可以对播放器行为做初始化。以下是一组适合全屏场景的参数BOOL CPlayerDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 隐藏右键菜单 m_wndPlayer.put_enableContextMenu(FALSE); // 隐藏控制条全屏下保留系统自带控制条会很突兀 m_wndPlayer.put_uiMode(_T(none)); // 不拉伸画面保持视频原始纵横比 m_wndPlayer.put_stretchToFit(FALSE); CString szFile _T(D:\\media\\demo.wmv); // 做一次存在性检查避免播放控件进入错误状态 if (::GetFileAttributes(szFile) INVALID_FILE_ATTRIBUTES) { AfxMessageBox(_T(测试文件不存在)); return FALSE; } m_wndPlayer.put_URL(szFile); return TRUE; }put_uiMode(_T(none))是关键参数它告诉控件不显示播放、暂停、进度条等系统 UI。全屏播放产品中通常要自己处理这些操作如果只是想快速验证效果uiMode可以保留为full。put_stretchToFit(FALSE)会让视频保留黑边不会把宽高比拉变形代价是播放控件内部会有画面留白后续要自己调整控件尺寸来消除黑边。3.3 播放控制按钮与消息映射对话框里加两个“播放”“全屏”按钮后消息映射如下BEGIN_MESSAGE_MAP(CPlayerDlg, CDialogEx) ON_BN_CLICKED(IDC_BTN_PLAY, CPlayerDlg::OnBtnPlay) ON_BN_CLICKED(IDC_BTN_STOP, CPlayerDlg::OnBtnStop) ON_BN_CLICKED(IDC_BTN_FULLSCREEN, CPlayerDlg::OnBtnFullscreen) END_MESSAGE_MAP()播放与停止的响应函数里直接操作 WMP 控件的高级接口void CPlayerDlg::OnBtnPlay() { // GetControls 返回 IWMPControls 的 MFC 包装对象 if (m_wndPlayer.GetControls().GetIsAvailable(_T(play))) { m_wndPlayer.GetControls().play(); } } void CPlayerDlg::OnBtnStop() { if (m_wndPlayer.GetControls().GetIsAvailable(_T(stop))) { m_wndPlayer.GetControls().stop(); } }GetIsAvailable用来探测当前媒体文件中“play/stop”操作是否可用。部分音频流或未完全加载的 URL 上play可能无效调用前判断可以避免异常。这个动作放在按钮回调里逻辑简单不需要事件接收器。4. 全屏切换窗口样式、坐标和输入焦点4.1 为什么不能只放大窗口把视频放满屏幕不等于把窗口的MoveWindow调到屏幕大小。标题栏、系统菜单、可调整边框这些非客户区如果还在视频画面就不会真正铺满屏幕。MFC 对话框的默认样式通常是WS_POPUP | WS_CAPTION | WS_SYSMENU全屏时要临时移除这些样式恢复时再原样还原。窗口样式窗口模式全屏模式说明WS_CAPTION有无去掉标题栏WS_THICKFRAME有无防止全屏下被拖动改变大小WS_DLGFRAME有无去掉对话框边框残留WS_CLIPCHILDREN有有防止子控件刷新造成花屏WS_EX_TOPMOST视情况有全屏时尽量保持最顶4.2 保存原始状态并切换在全屏切换前先把正常窗口位置、样式和扩展样式保存下来。恢复的时候才能完整回到原来的形态而不是简单恢复成“有标题栏的矩形”。void CPlayerDlg::OnBtnFullscreen() { if (!m_bFullScreen) { // 1. 保存当前窗口矩形与样式 GetWindowRect(m_rcNormal); m_dwOldStyle (DWORD)GetWindowLongPtr(m_hWnd, GWL_STYLE); m_dwOldExStyle (DWORD)GetWindowLongPtr(m_hWnd, GWL_EXSTYLE); // 2. 获取当前窗口所在显示器的完整屏幕区域 HMONITOR hMon MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST); MONITORINFO mi { sizeof(mi) }; GetMonitorInfo(hMon, mi); m_rcMonitor mi.rcMonitor; // 3. 移除非客户区样式加上 TOPMOST SetWindowLongPtr(m_hWnd, GWL_STYLE, (m_dwOldStyle | WS_POPUP) ~(WS_CAPTION | WS_THICKFRAME | WS_DLGFRAME)); SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, m_dwOldExStyle | WS_EX_TOPMOST); // 4. SWP_FRAMECHANGED 让新样式立即生效 SetWindowPos(m_hWnd, HWND_TOPMOST, m_rcMonitor.left, m_rcMonitor.top, m_rcMonitor.Width(), m_rcMonitor.Height(), SWP_FRAMECHANGED | SWP_SHOWWINDOW); // 5. 播放控件铺满客户区 CRect rcClient; GetClientRect(rcClient); m_wndPlayer.SetWindowPos(nullptr, 0, 0, rcClient.Width(), rcClient.Height(), SWP_NOZORDER); } else { // 恢复窗口样式和原始尺寸 SetWindowLongPtr(m_hWnd, GWL_STYLE, m_dwOldStyle); SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, m_dwOldExStyle); SetWindowPos(m_hWnd, nullptr, m_rcNormal.left, m_rcNormal.top, m_rcNormal.Width(), m_rcNormal.Height(), SWP_FRAMECHANGED | SWP_ZORDER); } m_bFullScreen !m_bFullScreen; }GetMonitorInfo返回的rcMonitor是当前显示器完整区域覆盖任务栏rcWork则是去除任务栏后的工作区。视频全屏播放通常要盖住任务栏所以这里用rcMonitor。用GetSystemMetrics(SM_CXSCREEN/SM_CYSCREEN)只能拿到主显示器尺寸在副屏播放时会偏。提示修改GWL_STYLE之后必须再调用一次SetWindowPos并且带上SWP_FRAMECHANGED否则 Windows 不会立刻根据新样式重建非客户区切全屏后标题栏可能还在。4.3 Esc 退出全屏的可靠处理对话框默认按下 Esc 会触发IDCANCEL直接退出整个对话框。全屏状态下Esc 应该只退出全屏模式而不是关闭程序。正确做法是在PreTranslateMessage里拦截键盘消息BOOL CPlayerDlg::PreTranslateMessage(MSG* pMsg) { if (pMsg-message WM_KEYDOWN) { UINT nChar (UINT)pMsg-wParam; // 全屏时 Esc 只退出全屏不关闭窗口 if (nChar VK_ESCAPE m_bFullScreen) { OnBtnFullscreen(); return TRUE; } // AltEnter 也可以做全屏切换 if (nChar VK_RETURN (GetKeyState(VK_MENU) 0x8000)) { OnBtnFullscreen(); return TRUE; } } return CDialogEx::PreTranslateMessage(pMsg); }PreTranslateMessage会在消息分发给窗口过程之前执行所以即使焦点在 WMP ActiveX 控件上键盘消息也能被拦截。这里不要调用CDialogEx::PreTranslateMessage否则 Enter 会被当作默认按钮处理。另一种常踩的坑是鼠标双击切换全屏WMP 控件铺满客户区后会先于父对话框收到鼠标消息所以双击很难传到OnLButtonDblClk。产品中我通常不做双击切换用按钮和 AltEnter 更靠谱。5. 发布期收尾技巧运行库、控件释放与防闪烁5.1 Redistributable 和“VC 14.0 报错”的辨析在开发机上程序能全屏播放搬到别的机器后最常见的失败就是“缺少 mfc140u.dll”或“找不到 MSVCP140.dll”。这类 MFC 工程在动态链接模式下需要目标机器安装对应位数的 Microsoft Visual C Redistributable。如果是 VS2010 之前的工程运行库版本可能是 9.0 或 10.0网上常有人把所有版本的 Visual C Redistributable All-in-One 打包一起装那是最快的排查手段但正规产品发布时应该在安装工程里带上对应合并模块。另一个经常乱入的提示是error: Microsoft Visual C 14.0 or greater is required。它跟这个全屏播放工程没有直接关系通常是 Python pip 安装包时缺少 VC Build Tools 的报错。看到它时先去确认当前是在编译 MFC还是在装 Python 扩展不要在播放器项目里盲目装整套 Build Tools。5.2 ActiveX 控件释放顺序对话框销毁时WMP 控件成员变量一般会随父窗口自动销毁但全屏模式下直接关闭程序有时会出现 WMP 进程残留。稳妥做法是在OnDestroy里先退出全屏再清空 URL最后销毁控件窗口void CPlayerDlg::OnDestroy() { // 关闭前先退出全屏恢复窗口样式 if (m_bFullScreen) { OnBtnFullscreen(); } // 清空播放源释放设备资源 if (::IsWindow(m_wndPlayer.GetSafeHwnd())) { m_wndPlayer.put_URL(_T()); } CDialogEx::OnDestroy(); }put_URL(_T())会停止当前播放并释放 WMP 内部的解码器引用。有时候卡在播放状态直接DestroyWindow在部分显卡驱动上会出现黑屏闪烁。5.3 全屏切换防闪烁全屏切换过程中的闪烁主要来自窗口样式变化后触发的多次重绘。一个低成本技巧是在切换前后用SetRedraw暂时关闭窗口绘制SetRedraw(FALSE); // 修改样式和位置移动控件 OnBtnFullscreen(); SetRedraw(TRUE); // 强制重绘整个客户区 Invalidate(TRUE); UpdateWindow();注意SetRedraw(FALSE)期间不要让用户事件穿插执行否则按钮点击状态会丢失。切换完成后的UpdateWindow替代Invalidate可以让播放控件的画面更早恢复。配合WS_CLIPCHILDREN样式基本能压住大部分老 MFC 工程的全屏闪烁问题。本文还有配套的精品资源点击获取
返回列表