簡體   English   中英

如何在基於對話框的 MFC 應用程序上啟用滾動?

[英]How to enable scrolling on Dialog Based MFC app?

我很抱歉這個問題,但我是 MFC 的初學者。 我有空的 MFC Dialog Based Application

我正在應用程序中加載圖像,如果圖像大於對話框,則未啟用滾動。 這是我的displayImage()方法,其中m_imgCImage

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);
}

我嘗試過以這種方式解決問題:

  1. 啟用滾動對話框:

在此處輸入圖像描述

通過此嘗試,顯示滾動條,但圖像仍然無法滾動。

  1. 我從這個鏈接中找到了其他解決方案,實現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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM