【问题标题】:How do I convince MFC's CScrollView to scroll by whole lines, not just by pixels?如何说服 MFC 的 CScrollView 按整行滚动,而不仅仅是按像素滚动?
【发布时间】:2016-07-01 15:53:39
【问题描述】:

累死了! 我如何说服 MFC 的 CScrollView 按整行滚动,而不仅仅是按像素滚动? 我非常绝望,甚至拿 Jeff Procise 的“Programming Windows with MFC”复制并粘贴在那里找到的简单示例.没有成功...!

我正在使用一个相当简化的 hexa 编辑器扩展一个更大的项目,但我遇到了这个噩梦。通常情况下,我错过了一些非常小的东西来使它工作。

请参阅下面的代码以及我怀疑是问题根源的OnSize 处理程序的两个版本。在其中的每一个中,我:(a)确定代表文件的行数(每行有 16 个字节,例如,一个 500 字节的文件由 32 行表示),以及(b)设置参数垂直滚动条。如果要显示 32 行,我将范围设置为 0..32。不幸的是,MFC 将此范围视为像素而不是“线”,我无法说服它在我的 OnDraw 方法中这样做。

提到OnDraw 方法,这个问题在视觉上似乎存在一个无法通过调用myDC.SelectClipRgn(NULL); 删除的剪辑区域。 Aka,我无法通过移动原始内容在 Windows 知道如何自行绘制的区域中绘制。然而,问题肯定会出在其他地方......

// MFC message map set correctly

afx_msg int CHexaEditor::OnCreate(LPCREATESTRUCT lpcs){
    // window created; instantiating the window manually
    if (CScrollView::OnCreate(lpcs)==-1) return -1;
    m_nMapMode=MM_TEXT;   // reason - see OnSize version 2
    m_lineDev=CSize(0,1); // reason - see OnSize version 2
}
void CHexaEditor::PostNcDestroy(){
    // window ready to be destroyed
    //nop (I'll do the job)
}

// OnSize handler version 1 (making use of MFC methods)
afx_msg void CHexaEditor::OnSize(UINT nType,int cx,int cy){
    // window size changed
    nLinesTotal=...;
    SetScrollSizes( MM_TEXT,
            CSize(0,nLinesTotal),
            ...page size...,
            CSize(0,1) // scroll up & down by one line
        );
    ShowScrollBar(SB_VERT,TRUE); // force visible
}

// OnSize handler version 2 (making use of API functions)
afx_msg void CHexaEditor::OnSize(UINT nType,int cx,int cy){
    // window size changed
    nLinesTotal=...;
    SCROLLINFO si={ sizeof(si), SIF_RANGE|SIF_PAGE, ... };
    SetScrollInfo( SB_VERT, &si, FALSE );
    ShowScrollBar(SB_VERT,TRUE); // force visible
}

感谢任何帮助,在此先感谢!

托马斯

【问题讨论】:

    标签: c++ winapi mfc hex scrollview


    【解决方案1】:

    CMyScrollView::OnCreate添加如下代码

    int res = CScrollView::OnCreate(lpcs);
    SIZE  sizeTotal = { 0, line_height * line_total };
    SIZE  sizeLine = { 0, line_height };
    SetScrollSizes(MM_TEXT, sizeTotal, sizeTotal, sizeLine);
    return res;
    

    line_height 是每行的高度(以像素为单位)。例如 20 像素。

    line_total是总行数(不是每页可见的总行数,而是从头到尾的总行数,包括不可见的行)

    另请参阅:CScrollView::SetScrollSizes

    只需打印全部内容。例如:

    void CMyView::OnDraw(CDC* pdc)
    {
        CMyDoc* pDoc = GetDocument();
        if (!pDoc) return;
    
        CString s;
        for (int i = 0; i < line_total; i++)
        {
            s.Format(L"line %d", i);
            pdc->TextOut(0, i * line_height, s);
        }
    }
    

    CMyScrollView::OnSize 中的任何内容都不需要更改,除了调整页面大小。



    要更改SCROLLINFO,请勿从CScrollView 开车。从CView开车。将ON_WM_VSCROLL 添加到消息映射以处理滚动消息。

    void CMyView::OnInitialUpdate()
    {
        //you can also overload OnCreate to setup scroller
        CView::OnInitialUpdate();
    
        line_height = 18;
        line_total = 0xffff;
    
        SCROLLINFO info = { sizeof(SCROLLINFO) };
        info.nMin = 0;
        info.nMax = line_total;
        info.nPage = 1;
        info.fMask = SIF_ALL;
        SetScrollInfo(SB_VERT, &info, TRUE);
    }
    
    void CMyView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
    {
        CView::OnVScroll(nSBCode, nPos, pScrollBar);
    
        SCROLLINFO info = { sizeof(SCROLLINFO) };
        GetScrollInfo(SB_VERT, &info, SIF_ALL);
        
        int pos = info.nPos;
        switch (nSBCode)
        {
        case SB_LEFT: pos = info.nMin; break;
        case SB_RIGHT: pos = info.nMax; break;
        case SB_LINELEFT: pos--; break;
        case SB_LINERIGHT: pos++;  break;
        case SB_PAGELEFT: pos -= info.nPage; break;
        case SB_PAGERIGHT: pos += info.nPage; break;
        case SB_THUMBPOSITION: pos = info.nTrackPos; break;
        case SB_THUMBTRACK: pos = info.nTrackPos; break;
        }
    
        //make sure the new position is within range
        if (pos < info.nMin) pos = info.nMin;
        int max = info.nMax - info.nPage + 1;
        if (pos > max) pos = max;
    
        info.cbSize = sizeof(SCROLLINFO);
        info.nPos = pos;
        SetScrollInfo(SB_VERT, &info, FALSE);
    
        Invalidate(FALSE);
    }
    
    void CMyView::OnDraw(CDC* pdc)
    {
        CMyDoc* pDoc = GetDocument();
        ASSERT_VALID(pDoc);
        if (!pDoc)
            return;
    
        CRect rc;
        GetClientRect(&rc);
    
        SCROLLINFO info = { sizeof(SCROLLINFO) };
        info.fMask = SIF_ALL;
        GetScrollInfo(SB_VERT, &info, SIF_POS);
    
        //find the start and end posion, print the visible portion
        int start = info.nPos;
        int end = start + rc.Height() / line_height;
        if (end > line_total) end = line_total;
        for (int i = start, y = 0; i < end; i++, y += line_height)
        {
            CString s;
            s.Format(_T("line %i"), i);
            pdc->TextOut(0, y, s);
        }
    }
    

    【讨论】:

    • 嗨,您的sizeLine 参数是按下箭头按钮时要滚动的像素数,而不是线条的大小。所以它对滑块没有影响。此外,我仍然无法在 Windows 响应滚动时(对我有利)从一个位置移动到另一个位置的区域中进行绘制。
    • 线条大小为 n 像素,它将滚动该数量。您也可以从CView 开车并设置滚动条。查看更新的答案。
    【解决方案2】:

    感谢您的好建议,他们拓宽了我的视野:-)

    无论如何,现在回答我自己的问题,因为我决定拒绝通过CScrollView 的方式并使用CEdit 做事。我会选择更通用的CWnd(或上面建议的CView),但出于某种奇怪的原因,如果CSplitterWnd 为父级,这些不接受输入焦点。所以在相当严格地探索CScrollView 在不同滚动情况下的行为之后,我不再认为这将是一种简单(甚至可行!)的方式。主要原因是CScrollView 本身——它调用ScrollWindow 来设置我无法影响的剪切区域(客户区域减去“Windows 知道如何绘制的区域”)。就像我说的,一个更好的方法是要么从CEdit(或CEditView)派生出东西,要么使用纯WinAPI创建东西——在任何一种情况下,我都可以正确计算,不需要进行像素计数的往返。我选择了 MFC 的 CEdit,因为在复制、粘贴等方面我不想用 COM 打扰自己。如果有人感兴趣,here 是我目前所拥有的(浪费的非交互式查看器目前)。

    关于CScrollView 方式中的错误有两个地方:(1) 主要问题不在OnSize 处理程序中——而是在我结合三种不同想法的绘图例程中(滚动线条,按像素滚动,以及 Jeff Procise 的按像素滚动),这使得在调用 CDC::GetClipBox 之后的绘图代码不连贯——这就是为什么我无法在可见的“画布”中到处绘制的原因; (2) 在OnCreate 处理程序中缺少return 语句 - 返回随机结果(非零,表示失败),我必须手动显示滚动条。

    希望这对希望在 MFC 中按整行滚动内容的人有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-29
      • 2018-04-20
      相关资源
      最近更新 更多