[英]How to enable scrolling on Dialog Based MFC app?
我很抱歉这个问题,但我是 MFC 的初学者。 我有空的 MFC Dialog Based Application 。
我正在应用程序中加载图像,如果图像大于对话框,则未启用滚动。 这是我的displayImage()
方法,其中m_img
是CImage
:
void CTestAppDlg::displayImage(CString path)
{
m_img.Load(path.GetBuffer());
CRect rectWindow;
GetClientRect(&rectWindow);
m_rcImg.left = rectWindow.left;
m_rcImg.right = rectWindow.left + m_img.GetWidth();
m_rcImg.top = rectWindow.top;
m_rcImg.bottom = rectWindow.top + m_img.GetHeight();
CDC* pDC = GetDC();
m_img.StretchBlt(pDC->GetSafeHdc(), 0, m_toolbar.GetRowHeight() + 1, m_rcImg.Width(), m_rcImg.Height(), 0, 0, m_img.GetWidth(), m_img.GetHeight(), SRCCOPY);
}
我尝试过以这种方式解决问题:
通过此尝试,显示滚动条,但图像仍然无法滚动。
我从这个链接中找到了其他解决方案,实现ScrollHelper
class。 我通过在对话框构造函数中调用它来将滚动助手附加到对话框:
m_scrollHelper->AttachWnd(this);
但结果是我可以滚动 Dialog Window 并且图像仍然没有滚动。 我无法将它附加到 CImage,因为它可以附加到 CWnd 或 CDialog 派生的 class。
欢迎任何建议或提示。 提前致谢。
您使用基于对话框的应用程序有什么原因吗? 太局限了...
Document-View 架构提供了 CScrollView,它将为您完成所有工作。
https://docs.microsoft.com/en-us/cpp/mfc/reference/cscrollview-class?view=msvc-160
您可以使用图片控件和滚动组合来满足您的需求。
下面是一个关于Displaying Bitmap with Scrolling
的项目。 这篇文章展示了如何在对话框中显示图片,并在需要查看整个图像的地方添加滚动条。 你可以参考一下。
在对话框中放置一个图片控件。 根据需要调整其大小。 请记住,您将在此控件中显示 bitmap。 大位图将限制在该控件的整个区域,右侧有一个垂直滚动条(如果图像的高度大于该控件的高度,将显示滚动条),底部有一个水平滚动条此控件(如果图像的宽度大于此控件的宽度,将显示滚动条)。 小位图将显示在该控件的中心,没有滚动条,左右间距相等,相对于控件的顶部和底部。 因此,让您的控件具有艺术感,让您的对话框具有漂亮的外观。
取图片控件的属性。 将其 ID 更改为 IDC_STATIC1,类型为 Frame,颜色为 Gray。 还取消选中可见检查按钮,以便从中删除刻度线。
使用 Class 向导,为 IDC_STATIC1 创建一个 CStatic 类型的控制变量。 让它成为m_st1。
在对话框的 header 文件(比如 MyDlg.h)中,添加以下代码:
public:
CRect rectStaticClient;
int sourcex, sourcey,offsetx,offsety;
protected:
CDC m_dcMem; // Compatible Memory DC for dialog
HBITMAP m_hBmpOld; // Handle of old bitmap to save
HBITMAP m_hBmpNew; // Handle of new bitmap from file
BITMAP m_bmInfo; // Bitmap Information structure
在对话框的实现文件(比如 MyDlg.cpp)中,添加以下代码:
BOOL CMyDlg::OnInitDialog()
{
// TODO: Add extra initialization here
CClientDC dc(this);
m_dcMem.CreateCompatibleDC( &dc );
return TRUE; // return TRUE unless you set the focus to a control
}
void MyDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
//Add the following Code
CPaintDC dc(this);
dc.BitBlt(offsetx,offsety,m_size.cx,m_size.cy,
&m_dcMem, sourcex, sourcey,SRCCOPY);
CDialog::OnPaint();
}
}
每当您想将 bitmap 加载到对话框中时,请编写以下代码。
m_hBmpNew = (HBITMAP) LoadImage(
AfxGetInstanceHandle(), // handle to instance
filename, // name or identifier of the image .say"C:\\NewFolder\\1.bmp"
IMAGE_BITMAP, // image types
0, // desired width
0, // desired height
LR_LOADFROMFILE);
if( m_hBmpNew == NULL )
{
AfxMessageBox("Load Image Failed");
}
// put the HBITMAP info into the CBitmap (but not the bitmap itself)
else {
m_st1.GetClientRect( &rectStaticClient );
rectStaticClient.NormalizeRect();
m_size.cx=rectStaticClient.Size().cx;
m_size.cy=rectStaticClient.Size().cy;
m_size.cx = rectStaticClient.Width(); // zero based
m_size.cy = rectStaticClient.Height(); // zero based
// Convert to screen coordinates using static as base,
// then to DIALOG (instead of static) client coords
// using dialog as base
m_st1.ClientToScreen( &rectStaticClient );
ScreenToClient( &rectStaticClient);
m_pt.x = rectStaticClient.left;
m_pt.y = rectStaticClient.top;
GetObject( m_hBmpNew , sizeof(BITMAP), &m_bmInfo );
VERIFY(m_hBmpOld = (HBITMAP)SelectObject(m_dcMem, m_hBmpNew ) );
offsetx= m_pt.x;
offsety=m_pt.y;
InvalidateRect(&rectStaticClient);
这么多代码将在运行时直接在图片控件上显示 bitmap。 记住滚动功能和alignment的调整还没有做,所以图片会显示在Picture控件的上角,如果它的尺寸比图片控件的大,它会被剪裁到图片控件的尺寸。 如果图像小于图片控件的大小,它将在不裁剪的情况下显示,但没有中心 alignment。 以下部分描述了如何实现滚动功能和 alignment。
使用滚动以原始大小显示 Bitmap
将垂直滚动条控件添加到对话框中,并将其放置在图片控件的右边缘。 使其长度与图片控件的高度一致。 将水平滚动条控件添加到 Dialog 并将其放置在图片控件的底部边缘。 使其长度与图片控件的宽度相同。
使用 Class 向导,为您的水平和垂直滚动条创建 CScrollBar 类型的成员变量。 别理他们
CScrollBar m_vbar; //For vertical Scroll Bar
CScrollBar m_hbar; //For Horizontal Scroll Bar.
您需要创建两个 SCROLLINFO 结构来存储垂直和水平滚动条的滚动条参数,因此在对话框的 header 文件中声明两个 SCROLLINFO 结构。
public:
CRect rectStaticClient;
int sourcex, sourcey,offsetx,offsety;
SCROLLINFO horz,vert;
仅当 bitmap 的大小大于图片控件的大小时,才需要显示滚动条。 因此,最初通过在对话框的 OnInitDialog() function 中编写以下代码来隐藏滚动条。
BOOL CMyDlg::OnInitDialog()
{
// TODO: Add extra initialization here
CClientDC dc(this);
m_dcMem.CreateCompatibleDC( &dc );
m_vbar.ShowWindow(false); //Hide Vertical Scroll Bar
m_hbar.ShowWindow(false); //Hide Horizontal Scroll Bar
return TRUE; // return TRUE unless you set the focus to a control
}
当您将 bitmap 加载到预定义的图片控件中时,会出现四种情况。 他们是:
案例1:加载的bitmap的宽度和高度都大于图片控件的宽度和高度。 在这种情况下,需要水平和垂直滚动条来显示整个 bitmap。 bitmap 使用滚动技术显示。 垂直滚动范围等于图片控件的bitmap-height的高度。 bitmap的高度和宽度是通过下面的代码得到的,该代码包含在显示位图所需的代码中,这里再次转载为:
m_size.cx = rectStaticClient.Width(); // zero based
m_size.cy = rectStaticClient.Height(); // zero based
GetObject( m_hBmpNew , sizeof(BITMAP), &m_bmInfo );
最大垂直滚动范围为m_bmInfo.bmHeight - m_size.cy,最大水平滚动范围为m_bmInfo.bmWidth - m_size.cx。 通过调用使水平和垂直滚动条可见
m_hbar.ShowWindow(true);
m_vbar.ShowWindow(true);
案例2:加载的bitmap的宽度大于图片控件的宽度,高度等于或小于图片控件的高度。 在这种情况下,需要水平滚动条来显示整个 bitmap。 bitmap 使用滚动技术显示。 水平滚动范围由 m_bmInfo.bmWidth-m_size.cx 给出。
在这种情况下,不需要垂直滚动条,但为了显示相对于图片控件集中的 bitmap,Bitmap 应在与图片控件上角的偏移处绘制
offsety = m_pt.y + ((m_size.cy - m_bmInfo.bmHeight)/2);
其中 offsety 是 (x1,y1) (x2,y2) 坐标系中偏移的 'y1' 坐标,m_pt.y 是原始的 'y1' 坐标。
(m_size.cy - m_bmInfo.bmHeight)/2) 的间隙也从图片控件的底部产生。 因此,水平滚动条应该使用 MoveWindow( ) function 向上移动一个量 (m_size.cy - m_bmInfo.bmHeight)/2,如下所述。
m_hbar.MoveWindow(offsetx,offsety+m_bmInfo.bmHeight,m_size.cx,18);
通过调用使水平滚动条可见,垂直滚动条不可见
m_hbar.ShowWindow(true);
m_vbar.ShowWindow(false);
案例3:加载的bitmap的高度大于图片控件,宽度等于或小于图片控件。 在这种情况下,垂直滚动条是显示整个 bitmap 所必需的。 bitmap 使用滚动技术显示。 垂直滚动范围由 m_bmInfo.bmHeight-m_size.cy 给出。
在这种情况下,不需要水平滚动条,但是为了显示相对于图片控件集中的 bitmap,Bitmap 应该显示在图片控件上角的偏移处,由下式给出:
offsetx= m_pt.x + ((m_size.cx - m_bmInfo.bmWidth)/2);
其中 offsetx 是 (x1,y1) (x2,y2) 坐标系中偏移的“x1”坐标,m_pt.x 是原始“x1”坐标。
((m_size.cx - m_bmInfo.bmWidth)/2) 的间隙也从图片控件的最右侧产生。 所以垂直滚动条应该使用 MoveWindow( ) function 将图片控件的右边缘向左移动一个量 (m_size.cx - m_bmInfo.bmHeight)/2,如下所述。
m_vbar.MoveWindow(offsetx+m_bmInfo.bmWidth,offsety,18,m_size.cy);
通过调用使垂直滚动条可见,水平滚动条不可见
m_hbar.ShowWindow(false);
m_vbar.ShowWindow(true);
案例4:加载的bitmap的高度和宽度等于或小于图片控件的高度和宽度。 在这种情况下,不需要垂直和水平滚动条来显示整个 Bitmap。 Bitmap 被集中显示在画面控制中。 为了在画面控件的中心显示 bitmap,bitmap 应显示在画面控件上角的偏移处,如下所示:
offsetx= m_pt.x + ((m_size.cx- m_bmInfo.bmWidth)/2);
offsety= m_pt.y + ((m_size.cy - m_bmInfo.bmHeight)/2);
其中 'offsetx' 是偏移的 z 坐标,'x1' 坐标在 (x1,y1) (x2,y2) 坐标系中,其中 m_pt.x 是原始的 'x1' 坐标和offsety 是偏移的 y 坐标,'y1' 坐标在 (x1,y1) (x2,y2) 坐标系中,m_pt.y 是原始的 'y1' 坐标。
通过调用使垂直和水平滚动条不可见
m_hbar.ShowWindow(false);
m_vbar.ShowWindow(false);
填充水平滚动条和垂直滚动条的 SCROLLINFO 结构,如下所示。
//Horizontal Scroll Info Structure
horz.cbSize = sizeof(SCROLLINFO);
horz.fMask = SIF_ALL;
horz.nMin = 0;
horz.nMax = m_bmInfo.bmWidth-m_size.cx;
horz.nPage =0;
horz.nPos = 0;
horz.nTrackPos=0;
m_hbar.SetScrollInfo(&horz);
//Vertical Scroll Info Structure
vert.cbSize = sizeof(SCROLLINFO);
vert.fMask = SIF_ALL;
vert.nMin = 0;
vert.nMax = m_bmInfo.bmHeight-m_size.cy;
vert.nPage = 0;
vert.nTrackPos=0;
m_vbar.SetScrollInfo(&vert);
现在通过使图片控件无效来显示图片。
InvalidateRect(&rectStaticClient);
请记住,根据加载图像的要求,滚动条的位置可能会发生变化。 因此,在您的对话框中显示另一个 bitmap 之前,请释放持有当前 bitmap 的 memory 并将滚动条的位置重置为其原始位置,即在您放置它们的对话框中调用滚动条的位置,即通过调用 if(m_hBmpNew; NULL )删除对象(m_hBmpNew); //释放Memory持有Bitmap
// Reset position of Vertical Scroll Bar
m_vbar.MoveWindow(offsetx+m_size.cx,offsety,18,m_size.cy);
// Reset position of Horizontal Scroll Bar
m_hbar.MoveWindow(offsetx,offsety+m_size.cy,m_size.cx,18);
现在您的 bitmap 已准备好显示在带有滚动条的对话框上(如果需要)。 但它仍然无法滚动显示剩余部分。 我们需要处理 WM_VSCROLL 和 WM_HSCROLL 消息以根据滚动条的位置重新绘制 bitmap。
使用 Class 向导,处理 WM_VSCROLL 和 WM_HSCROLL 消息,并在它们的处理程序中编写以下代码。
void CMyDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
switch (nSBCode)
{
case SB_TOP:
sourcey = 0;
break;
case SB_BOTTOM:
sourcey = INT_MAX;
break;
case SB_THUMBTRACK:
sourcey = nPos;
break;
}
m_vbar.SetScrollPos(sourcey);
InvalidateRect(&rectStaticClient);
CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
}
void CMyDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
switch (nSBCode)
{
case SB_TOP:
sourcex = 0;
break;
case SB_BOTTOM:
sourcex = INT_MAX;
break;
case SB_THUMBTRACK:
sourcex= nPos;
break;
}
m_hbar.SetScrollPos(sourcex);
InvalidateRect(&rectStaticClient);
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
现在您可以滚动您的 bitmap 并且可以通过水平和垂直滚动查看它的其余部分。 仍然存在问题。 滚动 bitmap 时屏幕会持续闪烁。 这是解决这个问题的另一种技术。 为这个项目画龙点睛。 无非就是我们将采用的“无闪烁绘图的双缓冲技术”。
使用双缓冲技术实现无闪烁绘图
要消除绘图中的闪烁,您需要在 memory DC 上绘制所有内容,然后使用 BitBlt 或 StretchBlt 函数将其复制到真实 DC。 这种技术称为双缓冲。 本文中使用的绘图技术是双缓冲。 它在前面的部分中进行了解释。 其次,您需要覆盖 OnEraseBackground( ) 事件。 此事件的默认实现使用 BackColor 属性的当前值清除控件的背景。 但是,并不总是需要重新绘制控件的整个区域,并且不必要地这样做会导致闪烁。 在使用 InvalidateRect(&rectStatcClient) 重新绘制对话框时绕过 OnEraseBackground() 事件。 这可以通过发出一个全局变量的信号来实现。 声明一个 BOOL 类型的全局变量,比如 BOOL 擦除,并将其初始化为 false。 Map WM_ERASEBKGND 消息并将其覆盖为
BOOL MyDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
if(erase)
return false;
else
return CDialog::OnEraseBkgnd(pDC);
}
在调用 InvalidateRect 和 rectStatcClient 函数之前,将变量“erase”设置为 true。 现在 OnEraseBkgnd( ) 被绕过了。
在 OnPaint function 的末尾将“擦除”标志重置为 false。 这将消除 OnEraseBkgnd(pDC) 事件的绕过,并且当 InvalidateRect( &rectStaticClient ) 以外的其他事件发送 WM_ERASEBKGND 时,背景将被擦除。
else
{
CPaintDC dc(this);
dc.BitBlt(offsetx,offsety,m_size.cx,m_size.cy,
&m_dcMem, sourcex, sourcey,SRCCOPY);
erase=false;
CDialog::OnPaint();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.