Как включить прокрутку в приложении MFC на основе диалога?

#c #visual-studio #scroll #mfc

#c #visual-studio #прокрутите #мфц

Вопрос:

Прошу прощения за этот вопрос, но я новичок в MFC. У меня пустое приложение на основе диалогового окна MFC.

Я загружаю изображение в приложение, и если изображение больше, чем диалоговое окно, прокрутка не включена. Это мой displayImage() метод m_img , где CImage :

 void CTestAppDlg::displayImage(CString path)
{
    m_img.Load(path.GetBuffer());

    CRect rectWindow;
    GetClientRect(amp;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 класса. Я прикрепил помощник прокрутки к диалоговому окну, вызвав это в конструкторе диалогового окна:

    m_scrollHelper->AttachWnd(this);

но в результате я могу прокручивать диалоговое окно, а изображение по-прежнему не прокручивается. Я не могу присоединить его к CImage, потому что он может быть присоединен к классу, производному от CWnd или CDialog.

Любые предложения или подсказки приветствуются. Заранее спасибо.

Комментарии:

1. Создайте элемент управления над диалоговым окном и отобразите изображение из элемента управления.

2. @seccpur спасибо за комментарий, я думал о чем-то таком, например, создать компонент CWnd внутри моего диалога и прикрепить к нему картинку, а затем применить помощник прокрутки к этому CWnd.

3. @seccpur не могли бы вы, пожалуйста, если у вас есть время, опубликовать пример hwo, чтобы создать другой элемент управления диалогом и прикрепить помощник прокрутки к t?

Ответ №1:

Есть ли причина, по которой вы используете приложение на основе диалогов? Это очень ограничивает…

Архитектура представления документа предоставляет CScrollView, который сделает все за вас.

https://docs.microsoft.com/en-us/cpp/mfc/reference/cscrollview-class?view=msvc-160

Комментарии:

1. спасибо за ваш ответ, честно говоря, я выбрал приложение на основе диалога, потому что это было проще (для меня), чем приложение с одним документом. Но, похоже, я просто создал большие проблемы на потом. Я попробую сейчас с вашим предложением и опубликую обновление, если оно сработает. Еще раз спасибо.

2. @user123445151156 — добро пожаловать! На мой взгляд, приложения на основе диалоговых окон подходят только для самых простых приложений (например, показать / установить некоторые настройки). На протяжении многих лет я видел, как люди прыгали через обручи, чтобы добавить меню или панель инструментов, правильно маршрутизировать команды и т. Д. Вы получаете это бесплатно из приложения на основе форм (если вам нужен просмотр диалога) или любых реализаций, основанных на CView.

3. @VladFeinstein здесь новичок. Следуя вашему совету, я создал новое приложение SDI. Я подумал, что если я создам подкласс диалогового CFormView окна, то я мог бы использовать CScrollView (потому что это базовый класс), но конструктор CFormView таков protected , что я не могу сделать что-то подобное CDialog dlg; dlg.DoModal(); . Я могу использовать полосу прокрутки в приложении на основе диалогового OnHScroll окна, но как я могу использовать CScrollView с приложением MFC с одним документом? Если вы считаете, что я должен опубликовать новый вопрос, я это сделаю 🙂

4. @starriet Зачем вам подкласс CFormView с диалоговым окном? Просто используйте этот CFormView как приложение на основе диалога.

5. @VladFeinstein Потому что я подумываю использовать CFormView или CScrollView в качестве всплывающего окна (как обычное диалоговое окно). Я хочу, чтобы мое приложение показывало разные типы объектов (например, большую прокручиваемую диаграмму, большой список и т.д.), Когда я нажимаю соответствующую кнопку. Я не хочу отображать все вещи одновременно. Не могли бы вы дать мне какой-нибудь совет? Спасибо.

Ответ №2:

Вы можете использовать элементы управления изображениями и переходить к комбинации в соответствии с вашими потребностями.

Ниже приведен проект Displaying Bitmap with Scrolling . В этой статье показано, как отображать изображение в диалоговом окне и добавлять полосы прокрутки там, где это необходимо для просмотра всего изображения. Вы могли бы обратиться к нему.

Поместите элемент управления изображением в свой диалог. Отрегулируйте его размер по своему усмотрению. Помните, что вы собираетесь отображать растровое изображение в этом элементе управления. Большие растровые изображения будут ограничивать всю область этого элемента управления вертикальной полосой прокрутки с правой стороны (полоса прокрутки будет отображаться, если высота изображения больше высоты этого элемента управления) и горизонтальной полосой прокрутки в нижней части этого элемента управления (полоса прокрутки будет отображаться, если высота изображения больше высоты этого элемента управления).ширина изображения больше ширины этого элемента управления). Небольшие растровые изображения будут отображаться в центре этого элемента управления без полос прокрутки, с равным зазором слева и справа, а также сверху и снизу по отношению к элементу управления. Поэтому разместите элемент управления с художественным штрихом, чтобы придать вашему диалогу приятный внешний вид.

Возьмите свойства элемента управления изображением. Измените его идентификатор на IDC_STATIC1 и введите как Frame, а цвет — как серый. Также снимите видимую кнопку проверки, чтобы с нее была удалена галочка.

Используя мастер классов, создайте управляющую переменную типа CStatic для IDC_STATIC1. Пусть это будет m_st1.

В заголовочном файле вашего диалога (скажем, 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( amp;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(amp;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, 
                   amp;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(),   // 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( amp;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( amp;rectStaticClient );
        ScreenToClient( amp;rectStaticClient);
        
        m_pt.x = rectStaticClient.left;
        m_pt.y = rectStaticClient.top;
        GetObject( m_hBmpNew , sizeof(BITMAP), amp;m_bmInfo );
        VERIFY(m_hBmpOld = (HBITMAP)SelectObject(m_dcMem, m_hBmpNew )  );
        offsetx= m_pt.x;
        offsety=m_pt.y;    
        InvalidateRect(amp;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.

Используя мастер классов, создайте переменные-члены типа CScrollBar для ваших горизонтальных и вертикальных полос прокрутки. Пусть они будут

 CScrollBar m_vbar; //For vertical Scroll Bar
CScrollBar    m_hbar; //For Horizontal Scroll Bar.
 

Вам необходимо создать две структуры SCROLLINFO для хранения параметров полосы прокрутки как для вертикальной, так и для горизонтальной полос прокрутки, поэтому объявите две структуры SCROLLINFO в заголовочном файле вашего диалогового окна.

 public:
    CRect rectStaticClient;
    int sourcex, sourcey,offsetx,offsety;
    SCROLLINFO horz,vert;
 

Вам нужно показывать полосы прокрутки только в том случае, если размер растрового изображения больше размера вашего элемента управления изображением. Поэтому сначала скройте полосы прокрутки, написав следующий код в функции OnInitDialog () вашего диалога.

 BOOL CMyDlg::OnInitDialog()
{
    // TODO: Add extra initialization here
    CClientDC dc(this);
    m_dcMem.CreateCompatibleDC( amp;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
}
 

При загрузке растрового изображения в предварительно определенный элемент управления изображением возникают четыре ситуации. Они:

Пример 1: ширина и высота загруженного растрового изображения больше, чем у элемента управления picture. В таких ситуациях для отображения всего растрового изображения необходимы как горизонтальные, так и вертикальные полосы прокрутки. Растровое изображение отображается с использованием техники прокрутки. Диапазон вертикальной прокрутки равен высоте растрового изображения-высоте элемента управления изображением. Высота и ширина растрового изображения определяются следующим кодом, который включен в код, необходимый для отображения растровых изображений, который воспроизводится здесь снова как:

 m_size.cx = rectStaticClient.Width();    // zero based
m_size.cy = rectStaticClient.Height();    // zero based
GetObject( m_hBmpNew , sizeof(BITMAP), amp;m_bmInfo );
 

Максимальный диапазон вертикальной прокрутки равен m_bmInfo.bmHeight — m_size.cy , а максимальный диапазон горизонтальной прокрутки равен m_bmInfo.bmWidth — m_size.cx . Сделайте видимыми горизонтальную и вертикальную полосы прокрутки, вызвав

 m_hbar.ShowWindow(true);
m_vbar.ShowWindow(true);
 

Случай 2: ширина загруженного растрового изображения больше, чем у элемента управления изображением, а высота равна или меньше, чем у элемента управления изображением. В таких ситуациях горизонтальная полоса прокрутки необходима для отображения всего растрового изображения. Растровое изображение отображается с использованием техники прокрутки. Диапазон горизонтальной прокрутки задается m_bmInfo.bmWidth-m_size.cx .

В этом случае вертикальная полоса прокрутки не нужна, но для отображения растрового изображения, централизованного по отношению к элементу управления изображением, растровое изображение должно быть нарисовано со смещением от верхнего угла элемента управления изображением, заданного

 offsety = m_pt.y   ((m_size.cy - m_bmInfo.bmHeight)/2);
 

Где смещение — смещенная координата ‘y1’ в системе координат (x1,y1) (x2,y2), а m_pt.y — исходная координата ‘y1’.

Разрешение на (m_size.cy — m_bmInfo.bmHeight)/2) также создается из нижней части элемента управления изображением. Таким образом, горизонтальная полоса прокрутки должна быть сдвинута вверх на величину (m_size.cy — m_bmInfo.bmHeight) /2 с помощью функции MoveWindow( ), как описано ниже.

 m_hbar.MoveWindow(offsetx,offsety m_bmInfo.bmHeight,m_size.cx,18);
 

Сделайте горизонтальную полосу прокрутки видимой, а вертикальную полосу прокрутки невидимой, вызвав

 m_hbar.ShowWindow(true);
m_vbar.ShowWindow(false);
 

Случай 3: высота загруженного растрового изображения больше, чем у элемента управления изображением, а ширина равна или меньше, чем у элемента управления изображением. В таких ситуациях вертикальная полоса прокрутки необходима для отображения всего растрового изображения. Растровое изображение отображается с использованием техники прокрутки. Диапазон вертикальной прокрутки задается m_bmInfo.bmHeight-m_size.cy .

В этом случае горизонтальная полоса прокрутки не требуется, но для отображения растрового изображения, централизованного по отношению к элементу управления изображением, растровое изображение должно отображаться со смещением от верхнего угла элемента управления изображением, заданного:

 offsetx= m_pt.x   ((m_size.cx - m_bmInfo.bmWidth)/2);
 

Где offsetx — смещенная координата ‘x1’ в системе координат (x1, y1) (x2,y2), а m_pt.x — исходная координата ‘x1’.

Разрешение ( (m_size.cx — m_bmInfo.bmWidth)/2) также создается с крайней правой стороны элемента управления изображением. Таким образом, вертикальная полоса прокрутки должна быть сдвинута влево от правого края элемента управления изображением на величину (m_size.cx — m_bmInfo.bmHeight) /2 с помощью функции MoveWindow( ), как описано ниже.

 m_vbar.MoveWindow(offsetx m_bmInfo.bmWidth,offsety,18,m_size.cy);
 

Сделайте вертикальную полосу прокрутки видимой, а горизонтальную полосу прокрутки невидимой, вызвав

 m_hbar.ShowWindow(false);
m_vbar.ShowWindow(true);
 

Случай 4. высота и ширина загруженного растрового изображения равны или меньше, чем у элемента управления picture. В таких ситуациях вертикальные и горизонтальные полосы прокрутки не нужны для отображения всего растрового изображения. Растровое изображение отображается централизованно, как таковое, в элементе управления изображением. Для отображения растрового изображения централизованно по отношению к элементу управления изображением растровое изображение должно отображаться со смещением от верхнего угла элемента управления изображением, заданного:

 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’.

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.

 //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(amp;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(amp;vert);
 

Now display the picture by invalidating the picture control.

 InvalidateRect(amp;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); //Release Memory holding 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);
 

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) 
{
    // 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(amp;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(amp;rectStaticClient);
    CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
 

Теперь вы можете прокручивать свое растровое изображение, а остальные его части можно просматривать с помощью горизонтальной и вертикальной прокрутки. Все еще существует проблема. Экран будет непрерывно мерцать при прокрутке растрового изображения. Вот еще один метод решения этой проблемы. Последний штрих к этому проекту. Ничего, кроме «метода двойного буфера для рисования без мерцания», который мы примем.

Реализация рисования без мерцания с использованием метода двойной буферизации

Чтобы устранить мерцание при рисовании, вам нужно нарисовать все на DC памяти, а затем скопировать его в реальный DC, используя функции BitBlt или StretchBlt. Этот метод называется двойной буферизацией. Метод рисования, используемый в этой статье, — двойная буферизация. Это объясняется в предыдущих разделах. Во-вторых, вам необходимо переопределить событие OnEraseBackground(). Реализация этого события по умолчанию очищает фон элемента управления с текущим значением свойства BackColor . Однако не всегда необходимо перерисовывать всю область элемента управления, и это может вызвать мерцание без необходимости. Обходите событие OnEraseBackground( ), пока вы перерисовываете свой диалог с помощью InvalidateRect(amp;rectstatclient). Это может быть достигнуто путем сигнализации глобальной переменной. Объявите глобальную переменную типа BOOL, скажем, BOOL erase, и инициализируйте ее значением false. Сопоставьте сообщение 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 и rectstatclient установите для переменной ‘erase’ значение true . Теперь OnEraseBkgnd() обходится.

Сбросьте флаг «стереть» на false в конце вашей функции OnPaint. Это удалит обход события OnEraseBkgnd(pDC), и фон будет удален, когда WM_ERASEBKGND отправляется другими событиями, отличными от InvalidateRect( amp;rectStaticClient ).

 else
{
    CPaintDC dc(this);
     dc.BitBlt(offsetx,offsety,m_size.cx,m_size.cy,
                amp;m_dcMem, sourcex, sourcey,SRCCOPY);
    erase=false;
    CDialog::OnPaint();
}
 

Комментарии:

1. Спасибо за подробное объяснение. Я отмечу этот ответ, потому что он отвечает на вопрос, но я настоятельно рекомендую избегать приложений на основе диалогов, как указал Влад в другом ответе, и по возможности создать проект SDI. Просто Унаследуйте CScrollView его, и обо всем позаботятся. В любом случае, спасибо.

2. Ладно, чувак… вы просто скопировали и вставили это … Неужели так трудно сказать, где ты это взял?