Introduction
Displaying large Bitmap files on a Dialog, in its original size is quite difficult in the VC++ Environment. However, it is possible to display a large bitmap to a predefined area of the dialog using the StretchBlt( )
function. The major disadvantage of this is that some of the image's clarity will be lost. This article explains how to display large bitmaps in the desired area of your dialog in its original size with a scrolling technique used to show the entire bitmap.
Although the primary aim of this article is to display a bitmap with scrolling, it also covers the following three main objectives.
- Loading a Bitmap in to the Dialog During Run-time.
- Displaying Bitmap in its Original size by using Scrolling technique.
- Implementing Flicker free drawing Using Double Buffering technique.
Loading Bitmap in to the Dialog During Run-time:
Sometimes the user may require different bitmaps to be loaded in to the dialog depending on the requirements of his GUI. This can be achieved by calling the LoadImage( )
function. The implementation of this is explained below
Place a Picture control in your dialog. Adjust its size as you wish. Remember, you are going to display the bitmap in this control. Big bitmaps will confine to the entire area of this control with a vertical scroll bar on Right side ( the scroll bar will be displayed if the height of the image is greater than the height of this control) and a horizontal scroll bar at the bottom of this control (the scrollbar will be displayed if the width of the image is greater than the width of this control). Small bitmaps will be displayed at the centre of this control without scrollbars, with equal clearance to the left & right, and top & bottom with respect to the control. So place your control with an artistic touch to give your dialog a nice appearance.
Take the properties of the picture control. Change its ID to IDC_STATIC1 and Type as Frame and Colour as Gray. Also uncheck the Visible check button so that the tick mark is removed from it.
Using Class Wizard, create a control variable of type CStatic
for IDC_STATIC1
. Let it be m_st1
.
In your dialog's header file (say MyDlg.h), add the following code:
public:
CRect rectStaticClient;
int sourcex, sourcey,offsetx,offsety;
protected:
CDC m_dcMem;
HBITMAP m_hBmpOld;
HBITMAP m_hBmpNew;
BITMAP m_bmInfo;
In your dialog's implementation file (say
MyDlg.cpp), add the following code:
BOOL CMyDlg::OnInitDialog()
{
CClientDC dc(this);
m_dcMem.CreateCompatibleDC( &dc );
return TRUE;
}
Override the else
part of your dialog's ::OnPaint() function:
void MyDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
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;
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CPaintDC dc(this);
dc.BitBlt(offsetx,offsety,m_size.cx,m_size.cy,
&m_dcMem, sourcex, sourcey,SRCCOPY);
CDialog::OnPaint();
}
}
Write the following code whenever you want to load a bitmap in to your dialog.
m_hBmpNew = (HBITMAP) LoadImage(
AfxGetInstanceHandle(),
filename,
IMAGE_BITMAP,
0,
0,
LR_LOADFROMFILE);
if( m_hBmpNew == NULL )
{
AfxMessageBox("Load Image Failed");
}
else {
m_st1.GetClientRect( &rectStaticClient );
rectStaticClient.NormalizeRect();
m_size.cx=rectStaticClient.Size().cx;
m_size.cy=rectStaticClient.Size().cy;
m_size.cx = rectStaticClient.Width();
m_size.cy = rectStaticClient.Height();
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);
This much code will display a bitmap directly on to the picture control during run time. Remember the scrolling capability and alignment adjustments haven't been done yet and so the image will be displayed at the top corner of the Picture control, and if its size is bigger than that of the picture control, it will be clipped to the picture control's size. If the image is smaller than the size of the picture control it will be displayed without clipping, but without centre alignment. The following section describes how scrolling capability and alignment can be achieved.
Displaying the Bitmap at its original size using scrolling
Add a vertical scroll bar control to your dialog and place it touching the right edge of your picture control. Make its length to that of the height of your picture control. Add a Horizontal scroll bar control to your Dialog and place it touching the bottom edge of your picture control. Make its length to that of the width of your picture control.
Using Class Wizard, create member variables of type CScrollBar
for your horizontal & vertical scrollbars. Let them be
CScrollBar m_vbar;
CScrollBar m_hbar;
You need to create two SCROLLINFO
structures for storing scroll bar parameters for both the vertical and horizontal Scroll Bars, so declare two SCROLLINFO
Structures in your dialog's header file.
public:
CRect rectStaticClient;
int sourcex, sourcey,offsetx,offsety;
SCROLLINFO horz,vert;
You only need to show the scrollbars if the size of the bitmap is greater than that of the size of your picture control. So hide the scrollbars initially by writing the following code in your dialog's OnInitDialog()
function.
BOOL CMyDlg::OnInitDialog()
{
CClientDC dc(this);
m_dcMem.CreateCompatibleDC( &dc );
m_vbar.ShowWindow(false);
m_hbar.ShowWindow(false);
return TRUE;
}
Four situations arise when you are loading a bitmap in to your pre-defined picture control. They are:
Case 1: Both the width and height of the loaded bitmap is greater than that of the picture control. In such situations both the horizontal and vertical scrollbars are necessary to show the entire bitmap. The bitmap is displayed using the scrolling technique. The vertical scrolling range is equal to the height of bitmap-height of the picture control. The height and width of the bitmap is obtained by the following code, which is incorporated in the code which is needed for displaying bitmaps, which is reproduced here again as:
m_size.cx = rectStaticClient.Width();
m_size.cy = rectStaticClient.Height();
GetObject( m_hBmpNew , sizeof(BITMAP), &m_bmInfo );
The maximum vertical scrolling range is m_bmInfo.bmHeight - m_size.cy
, and the maximum horizontal scrolling range is m_bmInfo.bmWidth - m_size.cx
. Make the horizontal and vertical scrollbars visible by calling
m_hbar.ShowWindow(true);
m_vbar.ShowWindow(true);
Case 2:THe width of the loaded bitmap is greater than that of the picture control and height is equal to or less than that of picture control. In such situations the horizontal scroll bar is necessary to show the entire bitmap. The bitmap is displayed using the scrolling technique. The horizontal scrolling range is given by m_bmInfo.bmWidth-m_size.cx
.
In this case vertical scroll bar is not needed, but for displaying the bitmap centralised with respect to the picture control, the Bitmap should be drawn at an offset from the top corner of the picture control given by
offsety = m_pt.y + ((m_size.cy - m_bmInfo.bmHeight)/2);
Where offsety
is the offsetted 'y1' co-ordinate in the (x1,y1) (x2,y2) co-ordinate system and m_pt.y
is the original 'y1' co-ordinate.
A clearance of (m_size.cy - m_bmInfo.bmHeight)/2)
is also produced from the bottom of the picture control. So the horizontal scrollbar should be shifted up by an amount (m_size.cy - m_bmInfo.bmHeight)/2
using the MoveWindow( )
function as explained below.
m_hbar.MoveWindow(offsetx,offsety+m_bmInfo.bmHeight,m_size.cx,18);
Make the horizontal scrollbar visible and the vertical scrollbar invisible by calling
m_hbar.ShowWindow(true);
m_vbar.ShowWindow(false);
Case 3: The height of the loaded bitmap is greater than that of the picture control and the width is equal to or less than that of picture control. In Such situations the vertical scrollbar is necessary to show the entire bitmap. The bitmap is displayed using scrolling technique. The vertical scrolling range is given by m_bmInfo.bmHeight-m_size.cy
.
In this case horizontal scrollbar is not needed, But for displaying the bitmap centralised with respect to the picture control, the Bitmap should be displayed at an offset from the top corner of the picture control given by:
offsetx= m_pt.x + ((m_size.cx - m_bmInfo.bmWidth)/2);
Where offsetx is the offseted 'x1' co-ordinate in (x1,y1) (x2,y2) co-ordinate system and m_pt.x
is the original 'x1' co-ordinate.
A clearance of ( (m_size.cx - m_bmInfo.bmWidth)/2)
is also produced from the extreme right side of the picture control. So the vertical scroll bar should be shifted left of the right edge of picture control by an amount (m_size.cx - m_bmInfo.bmHeight)/2
using the MoveWindow( )
function as explained below.
m_vbar.MoveWindow(offsetx+m_bmInfo.bmWidth,offsety,18,m_size.cy);
Make the vertical scrollbar visible and horizontal scrollbar invisible by calling
m_hbar.ShowWindow(false);
m_vbar.ShowWindow(true);
Case 4: The height and width of the loaded bitmap is equal to or smaller than that of the picture control. In Such situations the vertical and horizontal scrollbars are not needed to show the entire Bitmap. The Bitmap is displayed centrally, as such, in the picture control. For displaying the bitmap centrally with respect to the picture control, the bitmap should be displayed at an offset from the top corner of the Picture control given by:
offsetx= m_pt.x + ((m_size.cx- m_bmInfo.bmWidth)/2);
offsety= m_pt.y + ((m_size.cy - m_bmInfo.bmHeight)/2);
Where 'offsetx' is the offseted z co-ordinate, the 'x1' co-ordinate is in the (x1,y1) (x2,y2) co-ordinate system where m_pt.x
is the original 'x1' co-ordinate and offsety
is the offsetted y co-ordinate, and 'y1' co-ordinate is in the (x1,y1) (x2,y2) co-ordinate system and m_pt.y
is the original 'y1' co-ordinate.
Make the vertical and horizontal scrollbars invisible by calling
m_hbar.ShowWindow(false);
m_vbar.ShowWindow(false);
Fill up the SCROLLINFO
structure for horizontal scrollbar and vertical scrollbar as given below.
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);
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);
Now display the picture by invalidating the picture control.
InvalidateRect(&rectStaticClient);
Remember, depending on the requirements of your loaded image, the positions of the scrollbars may be changed. So before displaying another bitmap in to your dialog, release the memory holding the current bitmap and reset the positions of scrollbars to their original location, i.e. to the positions where you placed them during the design of your dialog, by calling
if(m_hBmpNew != NULL )
DeleteObject(m_hBmpNew);
m_vbar.MoveWindow(offsetx+m_size.cx,offsety,18,m_size.cy);
m_hbar.MoveWindow(offsetx,offsety+m_size.cy,m_size.cx,18);
The code given below summarizes the things explained above:
if(m_hBmpNew != NULL )
DeleteObject(m_hBmpNew);
sourcex=sourcey=0;
m_hBmpNew = (HBITMAP) LoadImage(AfxGetInstanceHandle(),
"Path of Bitmap",
IMAGE_BITMAP,
0,
0,
LR_LOADFROMFILE);
if( m_hBmpNew == NULL ) {
AfxMessageBox("Load Image Failed");
}
else {
m_st1.GetClientRect( &rectStaticClient );
rectStaticClient.NormalizeRect();
m_size.cx=rectStaticClient.Size().cx;
m_size.cy=rectStaticClient.Size().cy;
m_size.cx = rectStaticClient.Width();
m_size.cy = rectStaticClient.Height();
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;
m_vbar.MoveWindow(offsetx+m_size.cx,offsety,18,m_size.cy);
m_hbar.MoveWindow(offsetx,offsety+m_size.cy,m_size.cx,18);
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;
if(m_bmInfo.bmWidth<=m_size.cx)
{
if((m_size.cx-m_bmInfo.bmWidth)==0)
offsetx= m_pt.x;
else
offsetx= m_pt.x+((m_size.cx-m_bmInfo.bmWidth)/2);
m_vbar.MoveWindow(offsetx+m_bmInfo.bmWidth,offsety,18,m_size.cy);
m_hbar.ShowWindow(false);
}
else
m_hbar.ShowWindow(true);
m_hbar.SetScrollInfo(&horz);
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;
if(m_bmInfo.bmHeight<=m_size.cy)
{
if((m_size.cy-m_bmInfo.bmHeight)==0)
offsety= m_pt.y;
else
offsety= m_pt.y+((m_size.cy-m_bmInfo.bmHeight)/2);
m_hbar.MoveWindow(offsetx,offsety+m_bmInfo.bmHeight,m_size.cx,18);
m_vbar.ShowWindow(false);
}
else
m_vbar.ShowWindow(true);
m_vbar.SetScrollInfo(&vert);
InvalidateRect(&rectStaticClient);
}
Now your bitmap is ready to be displayed on the dialog with scrollbars (if needed). But still it's not able to scroll to show the remaining portions. We need to handle the WM_VSCROLL
and WM_HSCROLL
messages to re-draw the bitmap depending on the scroll bar positions for this.
Using Class Wizard, handle WM_VSCROLL
and WM_HSCROLL
messages and write the following code in their handler.
void CMyDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
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)
{
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);
}
Now you can scroll your bitmap and the remaining portions of it can be viewed by horizontal and vertical scrolling. Still there exists a problem. The screen will flicker continuously when you scroll the bitmap. Here comes another technique to tackle this problem. The finishing touch to this Project. Nothing but "Double buffer technique for flicker free drawing", which we will adopt.
Implementing Flicker free drawing Using Double Buffering technique
To eliminate flicker in drawing you need to draw everything on a memory DC, and then copy it to the real DC using BitBlt
or StretchBlt
functions. This technique is called double buffering. The drawing technique used in this article is double buffering. It is explained in the earlier sections. Secondly, you need to override the OnEraseBackground( )
event. The default implementation of this event clears the background of the control with the current value of the BackColor
property. However, it is not always necessary to repaint the entire area of the control, and doing so unnecessarily can cause flickering. Bypass the OnEraseBackground( )
event while you re-paint your dialog with InvalidateRect(&rectStatcClient)
. This can be achieved by signalling a global variable. Declare a global variable of type BOOL
, say BOOL erase
, and initialise it to false. Map the WM_ERASEBKGND
message and override it as
BOOL MyDlg::OnEraseBkgnd(CDC* pDC)
{
if(erase)
return false;
else
return CDialog::OnEraseBkgnd(pDC);
}
Before you call the InvalidateRect
& rectStatcClient
functions, set the variable 'erase' to true
. Now OnEraseBkgnd( )
is bypassed.
Reset 'erase' flag to false
at the end of your OnPaint
function. This will remove the bypassing of OnEraseBkgnd(pDC)
event and the background will be erased when WM_ERASEBKGND
is sent by other events other than InvalidateRect( &rectStaticClient )
.
else
{
CPaintDC dc(this);
dc.BitBlt(offsetx,offsety,m_size.cx,m_size.cy,
&m_dcMem, sourcex, sourcey,SRCCOPY);
erase=false;
CDialog::OnPaint();
}
I hope that you are now able to draw a bitmap with scrolling capability in your dialog. Go through the demo project and learn more from it.
NOTE: This code has been tested on the Windows 2000 platform and it is found to work properly. Your valuable suggestions and corrections are always welcomed. Refer to the MSDN Documentation for more details on 'Double Buffering', 'MoveWindow( )
' and 'BitBlt()
'.