用CScrollView作view类的基类生成了一个SDI的MFC程序,然后用memory dc画图,再把图像帖回dc中。为了实现缩放功能,使用了StetchBlt。当水平滚动范围小于等于数据长度时,缩放采取改变水平滚动范围的方法,此时程序功能正常。当水平滚动范围等于数据长度时,缩放采用隔点画线的方法,但是此时如果水平滚动条不在最左边(或垂直滚动条不在最上边),帖的图只能看到当前可视范围的右边(或下边),往左(或上)拖没图像。当然在缩放时将滚动条用SetScrollPos()归零可以解决问题,但是看起来很奇怪。在memory dc画图之前将滚动条归零然后再复原,这种办法在数据少时可以忍受,数据多了(memory dc绘图耗时长了)就会发现滚动条的来回移动。不动滚动条位置能否显示完整的视图?
另外,我发现当数据大于2^19多一点的时候,memory dc贴出来的图是黑的,直接画就没问题。怎么解决数据点数问题?
请各位老大帮帮忙
部分代码:
CScrollTestView::CScrollTestView()
:ready(false)
,length(1<<15)
,zoom(1)
,hPos(0)
{
}void CScrollTestView::OnDraw(CDC* pDC)
{
CScrollTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return; if (!ready)
{
hPos = GetScrollPos(SB_HORZ);
SetScrollPos(SB_HORZ, 0);
PrepDraw();
SetScrollPos(SB_HORZ, hPos);
} pDC->StretchBlt(-300, -3000, sizeTotal.cx, 6000, &dcMem, -300, -3000, length, 6000, SRCCOPY);
}void CScrollTestView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
sizeTotal.cx = length;
sizeTotal.cy = 6000;
SetScrollSizes(MM_TWIPS, sizeTotal); CMainFrame *pMain=(CMainFrame *)AfxGetApp()->m_pMainWnd;
pMain->m_wndStatusBar.SetPaneText(pMain->m_wndStatusBar.CommandToIndex(ID_INDICATOR_X),"      0");
pMain->m_wndStatusBar.SetPaneText(pMain->m_wndStatusBar.CommandToIndex(ID_INDICATOR_Y),"      0");
pMain->m_wndStatusBar.SetPaneText(pMain->m_wndStatusBar.CommandToIndex(ID_INDICATOR_SIZE),"      0");
}void CScrollTestView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
CScrollView::OnPrepareDC(pDC, pInfo);
CRect rect;
GetClientRect(rect);
pDC->SetWindowOrg(-300, 3000);
}void CScrollTestView::PrepDraw(void)
{
CClientDC dc(this);
if (dcMem.m_hDC)
dcMem.DeleteDC();
dcMem.CreateCompatibleDC(&dc);
OnPrepareDC(&dcMem); CSize p1(1, 1);
dcMem.DPtoLP(&p1); CBitmap* pbmNew = new CBitmap;
CRect rect, flRect;
rect.right = length/p1.cx;
rect.bottom = 6000/p1.cy;
pbmNew->CreateCompatibleBitmap(&dc, rect.right, rect.bottom); CBitmap* pOldBitmap = dcMem.SelectObject(pbmNew);
CBrush brBkg(::GetSysColor(COLOR_WINDOW));
flRect.right = length;
flRect.bottom = 6000;
dcMem.FillRect(flRect, &brBkg);

int x = length - 500, y = 140;
//x for x scope, y for y scope
CPen* ppBlue = new CPen;
ppBlue->CreatePen(PS_SOLID, 1, RGB(0,0,255));
dcMem.SelectObject(ppBlue); dcMem.MoveTo(-150, 0);
dcMem.LineTo(x, 0);//draw x axis
dcMem.MoveTo(x, 0);
dcMem.LineTo(x-100, 50);//draw x axis arrow
dcMem.MoveTo(x, 0);
dcMem.LineTo(x-100, -50);//draw x axis arrow
dcMem.MoveTo(0, -20*y);
dcMem.LineTo(0, 20*y);//draw y axis
dcMem.MoveTo(0, 20*y);
dcMem.LineTo(50, 20*y-100);//draw y axis arrow
dcMem.MoveTo(0, 20*y);
dcMem.LineTo(-50, 20*y-100);//draw y axis arrow CPen* ppRed = new CPen;
ppRed->CreatePen(PS_SOLID, 1, RGB(255,0,0));
dcMem.SelectObject(ppRed);
dcMem.MoveTo(0, 0);
for(int i=0; i<x/zoom; i++)
{
dcMem.LineTo(i*zoom, exp(-i/10000.)*20*sin(i/50.)*100);
} delete ppBlue;
delete ppRed;
delete pbmNew; ready = true;
}void CScrollTestView::OnLButtonUp(UINT nFlags, CPoint point)
{
if (sizeTotal.cx>1<<10 && sizeTotal.cx<=length && zoom==1)
sizeTotal.cx /= 2;
else if (sizeTotal.cx>length || zoom>1)
{
if (zoom > 1)
zoom --;
else
zoom = 1;
ready = false;
}
else
sizeTotal.cx = 1<<10;
SetScrollSizes(MM_TWIPS, sizeTotal);
CString valSize;
valSize.Format("%d", sizeTotal.cx);
CMainFrame *pMain=(CMainFrame *)AfxGetApp()->m_pMainWnd;
pMain->m_wndStatusBar.SetPaneText(pMain->m_wndStatusBar.CommandToIndex(ID_INDICATOR_SIZE),valSize);
Invalidate();
CScrollView::OnLButtonUp(nFlags, point);
}void CScrollTestView::OnRButtonUp(UINT nFlags, CPoint point)
{
if (sizeTotal.cx<=length/2 && zoom==1)
sizeTotal.cx *= 2;
else
{
sizeTotal.cx = length;
if (zoom < 16)
zoom ++;
else
zoom = 16;
ready = false;
}
SetScrollSizes(MM_TWIPS, sizeTotal);
CString valSize;
valSize.Format("%d", sizeTotal.cx);
CMainFrame *pMain=(CMainFrame *)AfxGetApp()->m_pMainWnd;
pMain->m_wndStatusBar.SetPaneText(pMain->m_wndStatusBar.CommandToIndex(ID_INDICATOR_SIZE),valSize);
Invalidate();
CScrollView::OnRButtonUp(nFlags, point);
}void CScrollTestView::OnMouseMove(UINT nFlags, CPoint point)
{
CClientDC dc(this);
OnPrepareDC(&dc);
dc.DPtoLP(&point); CString valX, valY;
valX.Format("%d", point.x);
valY.Format("%d", point.y); CMainFrame *pMain=(CMainFrame *)AfxGetApp()->m_pMainWnd;
pMain->m_wndStatusBar.SetPaneText(pMain->m_wndStatusBar.CommandToIndex(ID_INDICATOR_X),valX);
pMain->m_wndStatusBar.SetPaneText(pMain->m_wndStatusBar.CommandToIndex(ID_INDICATOR_Y),valY); CScrollView::OnMouseMove(nFlags, point);
}

解决方案 »

  1.   

    编译环境:
    VS.net 2k3, win xp sp2
    查了很多资料都没查到,难道要去读mfc的源码?
      

  2.   

    我把那个vc6的程序转化为vs2003,并且设置 length为( (1<<19)+1000 ),也没有发现你所说的“数据大于2^19多一点的时候,memory dc贴出来的图是黑的”问题,一切很正常
      

  3.   

    1. 数据多于2^19的就发黑的问题是以前在放大时同样采用StretchBlt时出现的,重现方法:
    把length初始化为1<<18,然后修改PrepDraw()中的背景颜色:
    .....
    CBrush brBkg(::GetSysColor(COLOR_WINDOW));
    //CBrush brBkg(RGB(0, 0, 0));
    .....
    最后将上述代码中的OnRButtonUp函数中的代码修改如下
    if .....
    else
    {
    sizeTotal.cx *= 2;
    //sizeTotal.cx = length;
    if (zoom < 16)
    zoom ++;
    else
    zoom = 16;
    //ready = false;
    }
    .....
    再点几下鼠标右键,就可以看到黑色了。2. 水平滚动条来回移动很明显的问题只有在数据量大时看得到,顶楼代码不变,在我的机器上length初始化为1<<20,然后将水平滚动条往右拖动一下,再点右键就会发现水平滚动条明显地来回移动了。这样的话,看起来效果就很差了。如果length初始化为1<<21或更多,那么出来的是白板!不解中...3. 往左拖没图像的重现:
    把OnDraw()中的
    if (!ready)
    {
    hPos = GetScrollPos(SB_HORZ);
    SetScrollPos(SB_HORZ, 0);
    PrepDraw();
    SetScrollPos(SB_HORZ, hPos);
    }
    改为
    if (!ready)
    {
    //hPos = GetScrollPos(SB_HORZ);
    //SetScrollPos(SB_HORZ, 0);
    PrepDraw();
    //SetScrollPos(SB_HORZ, hPos);
    }
    运行时往右拖滚动条,点右键,再往左拖,就可以发现左面没图像了。当时就是发现滚动条的位置导致了该问题,所以才在OnDraw()中使用SetScrollPos移动滚动条。但奇怪的是,在sizeTotal<length的时候,往左拖就有图像。个人认为,移动滚动条只是权益之计,因为有上述的问题2,所以才问“不动滚动条位置能否显示完整的视图?”
      

  4.   

    我也有这种问题,郁闷 [email protected]
      

  5.   

    那就是说跟我的xpsp2和vs.net2k3无关了,up一下
      

  6.   

    哦,是内存大小的问题啊。楼上说的是CreateCompatibleBitmap的大小?我在PrepDraw()中创建deMem时考虑了这个问题的:
    CSize p1(1, 1);
    dcMem.DPtoLP(&p1); CBitmap* pbmNew = new CBitmap;
    CRect rect, flRect;
    rect.right = length/p1.cx;
    rect.bottom = 6000/p1.cy;
    pbmNew->CreateCompatibleBitmap(&dc, rect.right, rect.bottom);
    因为CreateCompatibleBitmap使用的是device unit,而我知道的却是logic unit,所以用上述代码作了一下转换。length小的时候没有问题,大的时候就发黑。你所说的好像是有一部分是黑的,我所说的是length大的时候全发黑。
      

  7.   

    不会是那个地方溢出了吧?仅仅是猜测。你贴的这段代码比较有问题,我指语义不明白。CreateCompatibleBitmap的后两个参数是宽和高,如果你的意思是那个rect的宽和高,那么也应该是 rect.right-rect.left 和 rect.bottom-rect.top。可这段代码又不是这样,很让人迷糊,感觉这个rect是一个多余的变量,还不如去掉直接在 CreateCompatibleBitmap 里面放两个数呢。而且 rect.left 和 rect.top 不初始化,鬼知道它的值是什么,没准对程序有影响。把你的代码重构一下看看,加上注释的加上,可以提出成为函数的提出来,语意模糊的理清,命名简单的给个有意义的名字……
      

  8.   

    我没办法了,折腾了一个上午没结果,我只能说这个StretchBlt函数有问题,放大到一定倍数时就会变黑。我给你发了一个代码,你打开“帮助”菜单里的那个对话框拖动滚动条,会发现放大倍数大于65倍时就会变黑(可以到调试窗口看TRACE出来的数据,临界点就在65)。我的这份代码除了改动了那个“帮助”里的对话框以外,就是在OnDraw里加了点代码,其他什么也没动。msdn上也没解释为什么,我投降!
      

  9.   

    收到了,谢谢。可能我们运气不错,发现了MS的bug。
    ......
    郁闷,考虑读mfc源码或者....换C#
      

  10.   

    To newkaka1981:
    我研究了你的代码,终于发现了问题1的所在!
    你在OnDraw()中没有采用特殊的映射模式才使我找到了问题的大致所在。你的代码中width=500,而500*65=32500<32768<33000=500*66!而我采用的MM_TWIPS与MM_TEXT的比例是17:1,正好17*32768=557056>524288=2^19!这可能意味着在MFC内部,StretchBlt甚至BitBlt的宽度和高度采用的都是signed short类型,一旦超出2^15(32768)就会溢出了。不过这个问题已经不重要了,我限制了数据点数,让它不能超出17*32768。问题是,那个滚动条拖动时有的部分不能显示,不管数据多少有这个问题!Bill help me...
      

  11.   

    哦,果然如此,我把我那个代码直接写成
    pDC->StretchBlt(0,1,32767,300,&memDC,0,0,width,height,SRCCOPY);
    没事。一旦写成,
    pDC->StretchBlt(0,1,32768,300,&memDC,0,0,width,height,SRCCOPY);
    马上变黑。可它怎么会变黑?溢出了以后,成为0了,应该变白才对嘛顺便猜测一下,那个滚动条没准也是溢出
      

  12.   

    顺便说一下,MM_TWIPS与MM_TEXT的比例好像不一定是17:1吧?在我的机器上计算的结果大致等于16:1,这似乎和设备有关。但是让我感到迷惑的是,改动第一个参数会导致第三个参数的临界点变化,试验如下:你把
    pDC->StretchBlt(-300, -3000, sizeTotal.cx, 6000, &dcMem, -300, -3000, length, 6000, SRCCOPY);
    改成
    pDC->StretchBlt(-300, -3000, 526103, 6000, &dcMem, -300, -3000, length, 6000, SRCCOPY);然后把 526103 改成 526103+1,变黑(在我的机器上),那就是说526103是个临界点。
    映射比例:MM_TWIPS:MM_TEXT = 526103 / 32767 = 16.055879390850550859096041749321。如果把-300改成-303,那么那个临界数值就成了526106。
    映射比例:MM_TWIPS:MM_TEXT = 526106 / 32767 = 16.055970946378978850672933133946。继续试验,-300改成0,那么那个临界数值就成了526108。
    映射比例:MM_TWIPS:MM_TEXT = 526108 / 32767 = 16.056031983397930845057527390362。查了一下资料,MM_TWIPS的一个单位为1/20磅(1/72英寸),对应不同的显示器,其与MM_TEXT(像素映射)的比例数据不是固定的,这点可以理解。可是这和第一个参数有什么关系?毕竟第三个参数的语义是“宽”,如果是复制区域矩形相对于前两个参数的点的对应对角线另一个端点,那两者之间你变我也变可以理解。可它定义的是“宽”,那没道理有影响。就算StrechBlt内部对于这个参数的处理是short而不是int这一点可能是个bug,但是这个“bug”也不能解释这种现象。我快疯了。
      

  13.   

    贴图的时候用原图x - GetScrollPosition().cx 和原图y - GetScrollPosition().cy 做贴图的left,top
      

  14.   

    那个MM_TWIPS在不同的机器上应该有不同的结果,我的17的结果是直接用本机执行PrepDraw中的
    CSize p1(1, 1);
    dcMem.DPtoLP(&p1);
    的结果。
    原以为是MFC的代码有错,但是跟踪到windows内部好像代码都是正确的,一直是用的32位寄存器来存放那个srcWidth。在我的机器上跟踪到windows内部的结果(倍数为66):
    77EFC72F  push        dword ptr [ebp+30h] 
    77EFC732  mov         dword ptr [eax+6D0h],edi 
    77EFC738  push        dword ptr [ebp+2Ch] 
    77EFC73B  push        dword ptr [ebp+28h] 
    77EFC73E  push        dword ptr [ebp+24h] 
    77EFC741  push        dword ptr [ebp+20h] 
    77EFC744  push        dword ptr [ebp+1Ch] 
    77EFC747  push        dword ptr [ebp+18h] 
    77EFC74A  push        dword ptr [ebp+14h]   ;[ebp+14h]=0x80e8=500*66
    77EFC74D  push        dword ptr [ebp+10h] 
    77EFC750  push        dword ptr [ebp+0Ch] 
    77EFC753  push        esi  
    77EFC754  call        77EFC764 
    一直是用的dword,我在77EFC74D时观察了内存[ebp+14h]中的数值,依然是32位的。不知道是怎么回事。我试试flyback的方法,看起来像是可以解决最重要的问题3的样子:)
      

  15.   

    谢谢flyback。最重要的问题3终于解决了,原来是原点坐标映射有问题。不过flyback方法需要注意一点,不能在OnDraw和OnPrepareDC中使用GetScrollPosition()获得的坐标来画图,否则拖动时图像会有残影。问题3的解决步骤:
    1. 添加private成员变量CPoint ScrPos;
    2. 在OnLButtonUp和OnRButtonUp中加入ScrPos = GetScrollPosition();
    3. 在OnPrepareDC中修改原来的原点映射为:pDC->SetWindowOrg(-300-ScrPos.x, 3000-ScrPos.y);
    4. 在OnDraw中修改贴图坐标为:pDC->StretchBlt(-300-ScrPos.x, -3000, sizeTotal.cx, 6000, &dcMem, -300, -3000, length, 6000, SRCCOPY);然后去掉来回移动水平滚动条的语句。
    如此一来,就不用考虑问题2了。
    问题1真的是MS的bug吗?......没有人回答......
      

  16.   

    要打印的话最好用onPrint,并且在OnPreparePrinting里重新计算位置,都调用OnDraw的话要调整原点坐标映射,OnPrint只需要按要打印页的坐标就可以了