为 WTL CScrollImpl 添加缩放功能






4.35/5 (7投票s)
CZoomScrollImpl 扩展了 WTL 的 CScrollImpl,以支持连续缩放。
引言
缩放功能有助于在小屏幕上查看大型图像文件。
此实现旨在同时在仅提供部分 GDI 功能的 PPC 设备和具有 Win32 系统的台式计算机上执行。
早期版本依赖于针对 PPC 的私有 WTL 适配。当 WTL 7.1 发布并包含 Pocket PC 支持后,是时候将其作为附加组件了。
zoomscrl.h 和示例文件将与 WTL 7.1 在 Visual Studio .NET、EVC 3.0 和 4.0 中干净地编译和执行。
WTL 7.5 即将推出
WTL 现在发展迅速。WTL 7.5 正在 SourceForge.net 进行开发,在我撰写本文时,当前工作版本是 WTL 7.5.4291.0。
此版本仍然缺少 CSize
标量运算符,并且没有解决所有 Pocket PC 问题。因此,如果您使用此版本编译,将会遇到一些奇怪的 PPC 行为……
zoomscrl.h 将与 WTL 7.5.4291.0 在 Visual Studio .NET、EVC 3.0 和 4.0 中干净地编译和执行。
BmpZoom 项目将与 WTL 7.5.4291.0 在 Visual Studio .NET 中干净地编译和执行。
BmpZoomPPC 项目将与 WTL 7.5.4291.0 在 EVC 3.0 和 4.0 中干净地编译但奇怪地执行。
当 WTL 7.5 实现 CSize
标量运算符时,编译器将不喜欢 zoomscrl.h 中相同的外部定义。只需注释掉或删除 zoomscrl.h 第 39 行
#define _WTL_NO_SIZE_SCALAR // remove this line when // WTL supports CSize scalar operators
CSize 标量运算符
在使用比例尺时,CSize
类是常用的载体,但它缺少乘法和除法运算符。在这里,它们是在外部实现的,希望它们能成为最终 WTL 7.5 版本 CSize
类的一部分。
模板化的写法允许最小化和充分的编译。
template < class Num > inline CSize operator * ( tagSIZE s, Num n)
template < class Num > inline void operator *= ( tagSIZE & s, Num n)
template < class Num > inline CSize operator / ( tagSIZE s, Num n)
template < class Num > inline void operator /= ( tagSIZE & s, Num n)
这些运算符允许简单易于维护的写法,例如:
CSize halfSize = mySize / 2;
Class CZoomScrollImpl<T> 成员
该类继承自 CScrollImpl<T>
,后者负责滚动部分。我们添加了一个缩放层,并重写了 CScrollImpl
方法,为它们提供缩放后的尺寸和位置。
数据成员
CSize m_sizeTrue
存储未缩放的图像尺寸,CScrollImpl::m_sizeAll
存储缩放后的尺寸。double m_fzoom
存储缩放因子,可以通过 double GetZoom()
访问。
数据访问补充
该类提供了一些额外的数据访问成员,允许如下写法:
CSize sizePage = GetScrollPage();
这些成员是:
CSize GetScrollSize()
CSize GetScrollPage()
CSize GetScrollLine()
CPoint GetScrollOffset()
缩放操作和访问方法
SetZScrollSize
由重写的 SetScrollSize()
调用,并在计算出缩放后的尺寸后,调用基类 CScrollImpl<T>::SetScrollSize()
。此成员有两种形式:
void SetZScrollSize( CSize sizeTrue, double fzoom = 1., BOOL bRedraw = TRUE )
void SetZScrollSize( int cx, int cy, double fzoom=1., BOOL bRedraw = TRUE )
SetZoom
更改缩放因子,缩放页面和行滚动参数,并使用新的缩放因子保持初始中心点,如果 bRedraw
为 TRUE
则重绘图像。
void SetZoom( double fzoom, BOOL bRedraw = TRUE )
double GetZoom()
返回当前的缩放因子。
辅助坐标方法
WndtoTrue
和 TruetoWnd
方法为单个点、矩形和点数组计算相应的窗口和图像坐标。
CPoint WndtoTrue( CPoint ptW )
CPoint TruetoWnd( CPoint ptT )
voidWndtoTrue( LPPOINT aptW, int nPts ) // 就地坐标转换
voidWndtoTrue( LPRECT prectW ) // 就地坐标转换
void TruetoWnd( LPPOINT aptT, nPts ) // 就地坐标转换
void TruetoWnd( LPRECT prectT ) // 就地坐标转换
绘图操作方法
这些成员假定数据成员已正确设置,至少已调用一次 SetScrollSize()
,并根据当前的缩放和滚动设置绘制位图或内存 DC。
void DrawBitmap( HBITMAP hbm, HDC hdestDC, DWORD dwROP = SRCCOPY)
void DrawDC( HDC hsourceDC, HDC hdestDC, DWORD dwROP = SRCCOPY)
消息映射和处理程序
BEGIN_MSG_MAP(CZoomScrollImpl<T>) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd) #if _WTL_VER <= 0x0710 MESSAGE_HANDLER(WM_SIZE, OnSize) #endif // _WTL_VER <= 0x0710 CHAIN_MSG_MAP(CScrollImpl<T>) END_MSG_MAP()
WTL 7.1 的 CScrollImpl::OnSize
处理程序有时在滚动条的外观/消失方面存在问题,这在当前 WTL 7.5 中已修复。
如果设置了 SCRL_ERASEBACKGROUND
扩展样式(在 CScrollImpl
中为默认值),我们会无闪烁地擦除背景。否则,我们不做任何操作并将 bHandled
设置为 FALSE
。
使用该类
添加缩放功能
从现有的 CScrollImpl
派生类窗口开始,将其继承更改为 CZoomScrollImpl
,并修改 OnDraw
成员以利用绘图方法。
如果存在,请从您的消息映射中删除 WM_ERASEBKGND
处理程序,并链接到 CZoomScrollImpl<CMyView>
。
检查到目前为止没有明显的变化……您现在已经启用了缩放功能,缩放因子为 1.0,并且无法更改。
现在实现一些用户界面来调用 SetZoom()
。这是困难的部分。
绘图问题
该类在栅格图像上进行缩放和滚动。
矢量绘图可以绘制到全尺寸的 DIB 或内存 DC,然后将生成的栅格图像进行缩放和滚动。
如果您想在缩放后的图像上绘制**固定大小的符号或标签**,可以在您的 DoPaint
成员中直接绘制到缩放的背景,例如:
void DoPaint( CDCHandle dc) { DrawBitmap( m_bmp, dc); // Zoomed and scrolled bitmap is shown // Your unzoomed drawing routine here : use the TruetoWnd() // helpers if necessary }
从 BmpView 到 BmpZoom
BmpView 是 WTL 发行版中一个非常丰富的示例。在 WTL 7.1 中,提供了 Pocket PC 版本 BmpViewPPC。
BmpZoom 和 BmpZoomPPC 在不进行其他更改的情况下,为初始示例添加了缩放功能。
通用更改标记为 // ZOOM
,PPC 特有更改标记为 // ZOOMPPC
。一些 BmpViewPPC 的 bug 修复标记为 // BMPVIEWPPC
。
CBitmapView 缩放启用
CBitmapView
被大大简化。我们更改了继承,删除了 WM_ERASEBKGND
处理程序,并将 DoPaint
成员简化为两行。在加载新位图时,我们在 SetBitmap
中调用 SetZScrollSize
以将缩放因子重置为 1.0。
class CBitmapView : public CWindowImpl<CBitmapView>, public CZoomScrollImpl< CBitmapView > // ZOOM { //... void SetBitmap(HBITMAP hBitmap) { //... SetScrollOffset(0, 0, FALSE); SetZScrollSize( m_size); // ZOOM } BEGIN_MSG_MAP(CBitmapView) CHAIN_MSG_MAP(CZoomScrollImpl<CBitmapView>); // ZOOM END_MSG_MAP() void DoPaint( CDCHandle dc) { if( !m_bmp.IsNull()) DrawBitmap( m_bmp, dc); // ZOOM } }
实现滑块
这是比较棘手的部分。我们在工具栏中安装一个滑块通用控件,以便用户可以根据需要设置缩放。
更改在 CMainFrame
中,并且部分适用于桌面和 PPC。
首先,我们向 CMainFrame
添加一个 CTrackbarCtrl
成员。
//... CBitmapView m_view; CTrackBarCtrl m_ZoomCtrl; // ZOOM
在 OnCreate
处理程序的末尾,我们创建已禁用的滑块并正确放置它。
桌面滑块创建
我们将控件创建为框架的子控件,并提供适当的大小。
我们必须 SetLineSize(0)
,因为 AddSimpleReBarBand()
会向我们的控件发送 TB_BUTTONCOUNT
,它会被理解为 TBM_GETLINESIZE
(两者都定义为 WM_USER+24
)。滑块控件将返回 0,Rebar 条带将被正确安装。
我们将滑块的条带大小设置为工具栏留下的空间,并将滑块的行大小重置为 1。
int OnCreate(LPCREATESTRUCT /*lpCreateStruct*/) { //... #ifndef _WIN32_WCE CRect rCtrl(0,0,120,22); m_ZoomCtrl.Create( m_hWnd,rCtrl,NULL ,WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS | TBS_ENABLESELRANGE ); m_ZoomCtrl.SetLineSize(0); // see explanation AddSimpleReBarBand( m_ZoomCtrl, _T("Zoom"), FALSE, 140, FALSE); #if _WTL_VER <= 0x0710 ::SendMessage( m_hWndToolBar, RB_MAXIMIZEBAND, 1, TRUE); #else CReBarCtrl rebar = m_hWndToolBar; rebar.MaximizeBand(1,TRUE); #endif // _WTL_VER <= 0x0710 m_ZoomCtrl.SetLineSize(1); #else // ...
PPC 滑块创建
对于 PPC,我们需要子类化菜单栏 - 在其中我们已经在资源编辑器中创建了一个 ID 为 ID_ZOOM
的菜单项“Zoom Control”。
#ifdef WIN32_PLATFORM_PSPC // ZOOMPPC class CCtrlMenuBar : public CWindowImpl< CCtrlMenuBar, CCECommandBarCtrlT<CToolBarCtrl> > { public: DECLARE_WND_SUPERCLASS( _T("CtrlMenuBar"), _T("ToolbarWindow32") ); BEGIN_MSG_MAP(CCtrlMenuBar) if ( uMsg != WM_NOTIFY ) FORWARD_NOTIFICATIONS() END_MSG_MAP() }; #endif // WIN32_PLATFORM_PSPC // ZOOMPPC
子类化的菜单栏会将 WM_HSCROLL
消息转发给我们的框架。
我们定义一个 CCtrlMenuBar
成员,并在菜单栏创建后对其进行子类化。
class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>, public CMessageFilter, public CIdleHandler #ifndef _WIN32_WCE , public CPrintJobInfo #endif // _WIN32_WCE { public: DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME) #ifndef _WIN32_WCE CCommandBarCtrl m_CmdBar; CRecentDocumentList m_mru; CMruList m_list; #else CCtrlMenuBar m_CmdBar; // ZOOMPPC #endif // _WIN32_WCE
//... int OnCreate(LPCREATESTRUCT /*lpCreateStruct*/) { //... #else // WIN32_PLATFORM_PSPC CreateSimpleCEMenuBar(IDR_MAINFRAME, SHCMBF_HMENU); m_CmdBar.SubclassWindow(m_hWndCECommandBar); // ZOOMPPC #endif // WIN32_PLATFORM_PSPC #endif // _WIN32_WCE
在 OnCreate
的末尾,我们将控件创建为菜单栏的子控件,并使用我们 ID_ZOOM
按钮的尺寸和位置。
//... #else CRect rZoom; m_CmdBar.GetRect(ID_ZOOM,rZoom); m_ZoomCtrl.Create( m_hWndCECommandBar,rZoom, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | TBS_TOP , 0, ID_ZOOM ); m_ZoomCtrl.SetWindowPos(HWND_TOP,rZoom.left,rZoom.top + 1, rZoom.Width(),rZoom.Height() - 1,SWP_SHOWWINDOW); #endif //_WIN32_WCE m_ZoomCtrl.EnableWindow(FALSE); // ZOOM end
我们在创建时禁用滑块:此时我们没有任何东西可以缩放。
滑块准备
当打开新图像时,我们将滑块范围设置为 100 到*允许完整图像适应当前窗口大小的缩放因子的百倍*。因此,通过将滑块位置(一个整数)除以 100.0,我们可以得到所需的缩放因子(如果位置是218,则缩放因子应为2.18)。当有可缩放内容时,我们会启用滑块。
此代码在 OnFileRecent
和 OnFileOpen
处理程序中重复。
//... CSize sImg=m_view.GetScrollSize(); CSize sClient=m_view.m_sizeClient; m_ZoomCtrl.SetRange(100, max( (100 * sImg.cx) /sClient.cx , (100 * sImg.cy) /sClient.cy)); m_ZoomCtrl.SetPos(100); m_ZoomCtrl.EnableWindow(); //...
当图像被删除时,我们禁用滑块并将其位置设置为最左边。
void OnEditClear(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wnd*/) { //... m_view.SetBitmap(NULL); m_ZoomCtrl.SetPos(0); // ZOOM m_ZoomCtrl.EnableWindow(FALSE); // ZOOM //...
滑块缩放
当我们的框架收到 WM_HSCROLL
消息时,它可能只来自滑块。我们将此行添加到框架消息映射中:
MSG_WM_HSCROLL(OnZoomCtrl) // ZOOM
我们将 SetZoom
设置为滑块缩略图位置的百分之一。
void OnZoomCtrl( int /*iType*/, short /*iTrackPos*/, HWND /*hWndTrackBar*/ ) { if ( !m_view.m_bmp.IsNull()) m_view.SetZoom( m_ZoomCtrl.GetPos() / 100.0 ); }
这应该是我们示例描述的结尾,但 BmpZoomPPC 仍然有些奇怪的行为……
WTL 7.1 PPC 的弱点
我们可以编译甚至运行 BmpZoomPPC,但是:
- 菜单项始终处于活动状态:WTL 7.5.4291.0 中已修复。
- 框架未正确放置。
- 属性表会疯狂断言,并且不符合 PPC 的风格。
这些早期的问题应在 WTL 7.5 最终版本中解决,其中一部分已在 WTL 7.5.4291.0 中修复。
在此期间,我们通过一些本地 CMainFrame
成员进行解决。
LRESULT OnInitMenuPopup(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
void UpdateLayout(BOOL bResizeBars = TRUE)
我们还修改了 props.h 中的 CBmpProperties
和 CPageTwo
类,使属性表和页面符合 PPC 标准。
我们离一个行为良好的 PPC 应用程序还有很长的路要走。我希望在 CodeProject PPC 部分发布一个行为良好的 WTL 图像查看器。到时候再见 这里?
历史
- 更新于 2004-10-31,针对 WTL 7.5.4291.0 问题