VC 点滴 之 重绘

一基本知识:

1.OnPaint和OnDraw函数

View的父类的OnPaint函数调用了OnDraw函数,若在子类为WM_PAINT消息添加响应函数OnPaint,OnDraw函数将不会被调用。

2.CpaintDC和CclietnDC

CpaintDC的构造函数中调用了BeginPaint(),析构函数中调用了EndPaint();

CclietnDC的构造函数中调用了GetDC(),析构函数中调用了ReleaseDC()。

而BeginPaint(),EndPaint()只能用于响应WM-PAINT消息,否则将会出错。

二、            利用动态数组:

1.定义结构体LINE,用于保存线的数据。

struct LINE

{

       CPoint m_pt1;

       CPoint m_pt2;

};

2.在View中定义一个动态数组,保存每一根线的指针。

CPtrArray m_ptrLines;

    定义两个Cpoint的成员变量,保存线的起点和终点:

           CPoint m_ptOld;

       CPoint m_ptNew;

3.在View中加入WM_LBUTTONDOWN,WM_LBUTTONUP的响应函数,在OnLButtonDown中为m_ptNew赋值,

          m_ptOld=point;

4.在OnLButtonUp中加入代码:

   m_ptNew=point;

       CClientDC dc(this);

       dc.MoveTo(m_ptOld);

       dc.LineTo(point);

       LINE *pLn=new LINE;

       pLn->m_pt1=m_ptOld;

       pLn->m_pt2=m_ptNew;

       m_ptrLines.Add(pLn);

5.在OnDraw()中加入:

int sum= m_ptrLines.GetSize();

for(int i=0;i

{

       pDC->MoveTo(((Line *)m_ptrLines.GetAt(i))->m_pt1);

       pDC->LineTo(((Line *)m_ptrLines.GetAt(i))->m_pt2);

}

6.加入滚动条:将View的cpp文件和h文件中的Cview全部替换成CscrollView。

7.在view中加入虚函数OnInitialUpdate(),这个函数在View第一次刷新前被调用,在其中加入代码:

     SetScrollSizes(MM_TEXT,CSize(1024,768));

              这个函数也可在View的构造函数中调用。

8.在OnLButtonUp中生成DC后加入

OnPrepareDC(&dc);

              dc.DPtoLP(&m_ptOld);

       dc.DPtoLP(&m_ptNew);

三、            利用CmetaFileDC重绘

1.在View中定义成员变量:

       CMetaFileDC m_dcMetaFile;

2.在View的OnCreate中加入代码:

m_dcMetaFile.Create();

3.在View的OnLButtonUp中,注释有关数组的代码,加入:

m_dcMetaFile.MoveTo(m_ptOld);

m_dcMetaFile.LineTo(m_ptNew);

4.在OnDraw()中

       HMETAFILE hmetafile;

       hmetafile=m_dcMetaFile.Close();

       pDC->PlayMetaFile(hmetafile);

       m_dcMetaFile.Create();

       m_dcMetaFile.PlayMetaFile(hmetafile);

       ::DeleteMetaFile(hmetafile);

5.保存文件:

加入菜单响应函数,OnFileSave,加入代码:

       HMETAFILE hmetafile;

       hmetafile=m_dcMetaFile.Close();

       ::CopyMetaFile(hmetafile,"c://2.ddd");

       m_dcMetaFile.Create();

       m_dcMetaFile.PlayMetaFile(hmetafile);

       ::DeleteMetaFile(hmetafile);

6.读出文件:

加入菜单响应函数,OnFileLoad,加入代码:

              HMETAFILE hmetafile;

              hmetafile=::GetMetaFile("c://2.ddd");

              m_dcMetaFile.PlayMetaFile(hmetafile);

              ::DeleteMetaFile(hmetafile);

       Invalidate();

四、            利用兼容DC重绘:

1.在View中定义成员变量:

CDC        m_dcCompa;   

2.在OnLButtonDown中加入代码:

CClientDC dc(this);

       if(!m_dcCompa.m_hDC)

       {

              m_dcCompa.CreateCompatibleDC(&dc);

              CBitmap bmp;

              CRect rect;

              GetClientRect(&rect);

              bmp.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height());

              m_dcCompa.SelectObject(&bmp);

m_dcCompa.BitBlt(0,0,rect.Width(),rect.Height(),&dc,0,0,SRCCOPY);

       }

       m_dcCompa.MoveTo(m_ptOld);

       m_dcCompa.LineTo(m_ptNew);

3.在OnDraw中加入:

CRect rect;

GetClientRect(&rect);

pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompa,0,0,SRCCOPY);

 

 

 

 

 

CWnd对象在构造函数里成员被初始化为0。但CPoint、CRect等在构造函数里成员没有做这种操作。MFC写的类一般在构造函数里要将成员初始化,除非它做了特殊说明

API函数发出的消息是SendMessage方式。

 

在CxxView里画线,当我们画出图形后,修改了窗口的尺寸、覆盖了窗口、最小化、最大化这些操作后,由于窗口要重新显示出来,所以用窗口的定义好的画刷将窗口刷出来。窗口重绘后报告一个WM_PAINT通知你,并默认用父类的OnPaint响应该消息,父类的OnPaint要调用一个函数OnDraw。如果我们要把线在OnDraw里也画一遍的话,即使窗口重绘了,线还能看到,但是当我们覆盖了父类的OnPaint,发现线没有了。究其原因CxxView的OnPaint没有为你调用OnDraw。我们可以跟踪进入查看父类的OnPaint的实现。其实我们也可以在子类的OnPaint里把线重画出来,但是最好是在OnDraw里重画,为什么呢我们为CxxView覆盖一个虚函数OnPrint(安装一个打印机),发现它的父类也调用OnDraw,OnDraw是一个虚函数,它的参数DC可以接受任何设备的DC(这里可以接受打印机的DC,窗口的DC等,OnPaint传窗口的DC,OnPrint传打印机的DC),你对这个DC的操作就是在相对应的设备上输出,如输出到窗口或者输出到打印机,这样就可以让在OnDraw所写图形输出既能输出到窗口上又能输出到打印机上,实现了操作的统一性(虚函数的原理)和代码的复用性。

在OnPaint里为你产生了一个CPaintDC,它和CClientDC有什么区别能

查看CPaintDC的说明。CClientDC的封装与之不同。

在WinMain程序里的WM_LBUTTONDOWN和WM_LBUTTONUP用GetDC和ReleaseDC画线没问题,但用BeginPaint和EndPaint画线出错。同样在WM_PAINT消息里不能用GetDC和ReleaseDC画线,容易出错,所以CPaintDC只能在响应WM_PAINT消息的函数里使用,CClientDC只能在响应非WM_PAINT消息的函数里使用。

An application should not call BeginPaint except in response to a WM_PAINT message. Each call to BeginPaint must have a corresponding call to the EndPaint function.

 

              case WM_PAINT:

                     HDC dc1;

                     //PAINTSTRUCT ps1;

                     //dc1=::BeginPaint(hwnd,&ps1);

                     dc1=::GetDC(hwnd);

                     ::TextOut(dc1,100,100,"hello world",strlen("hello world"));

                     //::MoveToEx(dc,20,20,NULL);

                     //::LineTo(dc,100,100);

                     //dc=::GetDC(hwnd);

                     //::MoveToEx(dc,x1,y1,NULL);

                     //::LineTo(dc,x2,y2);

                     ::ReleaseDC(hwnd,dc1);

                     //::EndPaint(hwnd,&ps1);

                     break;

              case WM_LBUTTONDOWN:

                     x1=LOWORD(lParam);

                     y1=HIWORD(lParam);

                     break;

              case WM_LBUTTONUP:

                     x2=LOWORD(lParam);

                     y2=HIWORD(lParam);                   

                     HDC dc;

                     PAINTSTRUCT ps;

                     dc=::BeginPaint(hwnd,&ps);

                     //dc=::GetDC(hwnd);

                     ::MoveToEx(dc,x1,y1,NULL);

                     ::LineTo(dc,x2,y2);

                     //::ReleaseDC(hwnd,dc);

                     ::EndPaint(hwnd,&ps);

                     break;

 

 

刚才的图形保存下来可以考虑把原来绘图操作的一些元素保存下来,然后在窗口发生刷新操作后,再根据保存的这些元素把图形在OnDraw里重画一遍(元素包括原点、终点),它给我刷完后我又把图形画一遍。我们可以在每一次画图之后把刚才的元素保存一次。这里保存这些元素最好是用一个类来完成。但是我们要把这些类生成的对象保留下来用用数组存的话无法确定数组的大小,我们用一种集合类,这种集合类有三种(能够动态分配空间的动态数组、链表、map)它们都能实现类似链表的功能,动态产生空间。map有点像VB里的集合,可以定义关键字,方便用户的访问,能动态产生内存。如CMapPtrToWord从一个指针查找一个Word。通过一个CObArray找到Collection的说明,然后查看choosing a collection class来分析这些集合类的优缺点,Array的动态实现其实是在增长空间的时候又分配了一个比原来大一个元素的数组空间,然后把数据拷过来,干掉原来的空间,所以它要增加元素时速度慢一些。今天我们用动态数组CObArray.

基本上这些集合类的成员函数差不多。

 

作完绘图操作后可以在CxxView的PostNCDestroy里delete这些指针。

 

这个类的成员正是我们要保存的数据。并给这个类定义一个带参数的构造函数来始化对象。New Class(CGraph),Class Type为Generic Class.为这个新类定义成员变量和带参数的构造函数。在CxxView里OnLButtonUp里定义一个CGraph的变量,在CxxView里定义成员变量CObArray(这些都是链表类) m_obArray. 用m_obArray来保存这个对象,注意在保存的时候要转成CObject*。然后我们在CxxView的OnDraw函数里将图形重画一遍。我们发现这些操作完成后,测试发现图形一个都没有保存下来。原因是:

在OnLButtonUp函数中

m_obArray.Add(&graph)            CGraph graph;(地址为0x0012ff7c)

0x0012ff7c
 
graph
 
在graph超过作用域的时候发生析构,它所在的内存被操作系统收回

 
 
 

 

                                                                                                   

 

 

这里考虑在堆上分配这个CGraph对象,然后把它的指针保存起来,以便重画和释放堆上的内存。讲清new这个对象的原理,用树桩上栓了只甲鱼为例,用两根绳子和两个树桩来栓住这个甲鱼,虽然一个树桩被水冲走了,但另一个树桩仍然能找到这个甲鱼。

void CMyReDrawView::OnLButtonUp(UINT nFlags, CPoint point)

{

       // TODO: Add your message handler code here and/or call default

       m_ptEnd=point;

       CClientDC dc(this);           

       dc.MoveTo(m_ptOrigin);

       dc.LineTo(m_ptEnd);

       CGraph* pGraph=new CGraph(m_ptOrigin,m_ptEnd);

       m_obArray.Add((CObject*)pGraph);    

 

       CView::OnLButtonUp(nFlags, point);

}

 

void CMyReDrawView::OnDraw(CDC* pDC)

{

       CMyReDrawDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       for(int i=0;i

       {

              pDC->MoveTo(((CGraph*)m_obArray.GetAt(i))->m_ptOrigin);

              pDC->LineTo(((CGraph*)m_obArray.GetAt(i))->m_ptEnd);

       }

}

 

void CMyReDrawView::PostNcDestroy()

{

       // TODO: Add your specialized code here and/or call the base class

       for(int i=0;i

       {

              delete (CGraph*)m_obArray.GetAt(i);     //一定要转换,否则会清除错误,因为delete的时候要调用不转的CObject的析构函数,就会出现未知异常。

       }

       m_obArray.RemoveAll();  //其实不删除也可以,这不导致内存泄露,如果不清除的话这个工作由系统完成,但最好这样做。

       CView::PostNcDestroy();

}

 

如果想让绘图的窗口变得更大,这就需要带滚动条的窗口。可以在生成工程的时候将CxxView的父类指定为CScrollView:

CEditView将生成记事本。

CFormView生成像VB中的Form窗口

CHTMLView生成IE浏览器

CListView生成像资源管理器右边的部分。

CTreeView生成像资源管理器左边的部分。

CRichEditView生成写字板的程序。

这里可以演示CHTMLView.修改CxxView里的OnInitUpdate的连接站点可以取到网页。这个函数是在View第一次和Document建立关联之后,在View显示之前被调用。

这里我们也可以将工程里的CView替换为CScrollView,在两个文件中替换,xxView.cpp和xxView.h.运行发现程序启动就报错,这是因为加了滚动条后在CxxView的后面仿佛有一个更大的窗口(逻辑窗口),拉动滚动条就显示了大窗口的一部分,这些操作都需要告诉滚动条这个背后的大窗口有多大,我才能显示出大窗口的某一部分,在CxxView显示之前来告诉它,可以在构造函数里设置,也可以在CxxView的OnInitUpdate里设置。

SetScrollSizes(MM_TEXT,CSize(1024,768));

MM_TEXT是像素,在不同分辨率下,像素不同。800*600的一个像素的值要比1024*786的大。

MM_HIMETRIC是0.01毫米,是一个绝对值,不管在什么分辨率,大小都是一致的。当我们要输出到打印机上时,就要定义成绝对的值,在不同的打印机(打印机也有分辨率)上打出的结果一致。

Positive y-axis Extends.定义了y的正轴方向MM_TEXT是向下y为正。

sizeTotal指定背后窗口有多大。

sizePage指定响应鼠标点击滚动条的滚动杆时,水平和垂直方向CxxView移动(翻页)的大小。默认由前面的CX和CY指定,。

sizeLine指定响应鼠标点击滚动条的箭头时,水平和垂直方向CxxView移动(行数)的大小。默认由前面的CX和CY指定。

 

滚动条产生后,当我们在CxxView的最右下角画线,线的位置的显示就出现问题了。这是因为,画图的时候,DC以当前CxxView的客户区的左上角作为原点,当窗口重绘后DC以背后大窗口的左上角为原点。所以我们在画图的时候,设置DC的原点始终为背后大窗口的原点。这样做的目的主要是想把得到的鼠标的点调整成相对于背后大窗口左上角的坐标得到的值

我们也可以看OnLButtonDown的说明得知:

Specifies the x- and y-coordinate of the cursor. These coordinates are always relative to the upper-left corner of the window.

OnPrepareDC是根据滚动条的位置调整DC的原点。调用这个函数后原来原点的坐标是(0,0),当你拉动滚动条后DC的原点将变为(-50,-50),这样保证DC的原点始终是以大窗口的左上角作为(0,0)点。

DptoLp将设备坐标调整为逻辑坐标,这样可以将OnLButtonDown点的值改为以大窗口(逻辑窗口)的左上角为原点的参照的值。

大窗口(0,0)
 
m_ptEnd=point;

小窗口(-50,-50)
 
     CClientDC dc(this);     

     CPoint pt=dc.GetViewportOrg();

小窗口(view当前显示的窗口)
 
     this->OnPrepareDC(&dc);

     pt=dc.GetViewportOrg();

    

     dc.DPtoLP(&m_ptOrigin);  

     dc.DPtoLP(&m_ptEnd);

可以调试查看值的变化。

 

经过转换后点的坐标始终是以大窗口的左上角
为坐标原点参照。

如果我们是在CxxView的OnPaint进行图形重现,我们也要调用OnPrepareDC.

 

下面用一个元文件的DC来进行保留图形的操作。

CMetaFileDC:

A Windows metafile contains a sequence of graphics device interface (GDI) commands that you can replay to create a desired image or text.

一个Windows的元文件包含了一系列的GDI命令,那么你可以重新播放创建的图形和文本。

To implement a Windows metafile, first create a CMetaFileDC object. Invoke the CMetaFileDC constructor, then call the Create member function, which creates a Windows metafile device context and attaches it to the CMetaFileDC object.

我们用CMetaFileDC来绘图,它将会把你所做的绘图操作全部记录下来,以便你在下次能够用重新播放的方式将图形重新显示出来。

创建完CMetaFileDC后,发送CMetaFileDC的一系列的GDI的命令以便你能重新播放。如MoveTo and LineTo等。

Next send the CMetaFileDC object the sequence of CDC GDI commands that you intend for it to replay. Only those GDI commands that create output, such as MoveTo and LineTo, can be used.

 

你在这个元文件上发送了希望的命令后,调用Close成员函数,它将关闭元文件DC,并返回一个元文件的句柄。然后丢弃这个CMetaFileDC object。

首先在CxxView里定义一个元文件DC成员CMetaFileDC m_metaFile;,在构造函数里创建它:调用它的Create函数,参数说明:指向一个反斜杠字符结尾字符串。指定创建的元文件的名字,这样的话,这个元文件就会产生在硬盘中。如果填为空,将有一个在内存中的元文件被创建。接着把刚才用DC的绘图操作全部用元文件DC再操作一遍。

然后在CxxView的OnDraw里

void CMyReDrawView::OnDraw(CDC* pDC)

{

         CMyReDrawDoc* pDoc = GetDocument();

         ASSERT_VALID(pDoc);

         // TODO: add draw code for native data here

         HMETAFILE hMetaFile=m_metaFile.Close();//报告画完了,关掉画布,可以进行展示了。

         pDC->PlayMetaFile(hMetaFile);

         m_metaFile.Create();

         m_metaFile.PlayMetaFile(hMetaFile);

         ::DeleteMetaFile(hMetaFile);//如果没有这句话,发生重画的时候,内存泄露,可以通过任务管理器查看。

}

 

m_metaFile(c++对象)        元文件DC实体(Windows资源)

 
 
 
 
 

 

                        close后关系就断开了

                                                                                   必须销毁,否则出现内存泄露。

 

 

                       再Create                   销毁前一个之前,将原来的图形在这个新的上面重新播放一遍

                      

 


 
 

 

                                                       新元文件DC实体(Windows资源)

 

元文件有个好处就是可以产生内存中的元文件,也可以产生硬盘中的元文件,同样可以将内存中的元文件拷贝到硬盘中作为元文件保留,如果产生硬盘中元文件,就可以在程序下次启动的时候将硬盘中的元文件装载,以便图形重现。

void CMyReDrawView::OnSave()

{

                                                       // TODO: Add your command handler code here

                                                       HMETAFILE hMetaFile=m_metaFile.Close();

                                                       CopyMetaFile(hMetaFile,"c://200");

                                                       m_metaFile.Create();

                                                       m_metaFile.PlayMetaFile(hMetaFile);

                                                       ::DeleteMetaFile(hMetaFile);

}

void CMyReDrawView::OnLoad()

{

                                                       // TODO: Add your command handler code here

                                                       HMETAFILE hMetaFile=::GetMetaFile("c://200");

                                                       m_metaFile.PlayMetaFile(hMetaFile);

                                                       ::DeleteMetaFile(hMetaFile);

                                                       Invalidate();

                                             }

注意普通的DC画完之后,里面就没内容了。

用兼容DC来实现图形重现。

原理是在绘图的时候将图形在兼容DC里也画一遍,在窗口重绘后,再将在兼容DC里所画的图形以贴图片的方式拷贝到与窗口关联的DC上。从而达到图形重现。不同于元文件DC,那个是记录了你的操作。

 

首先创建一个兼容DC:

When a memory device context is created, GDI automatically selects a 1-by-1 monochrome stock bitmap for it. GDI output functions can be used with a memory device context only if a bitmap has been created and selected into that context.

当一个内存DC(兼容DC)创建后,GDI自动为它选择一个1*1像素的单色的库存的位图,GDI 输出功能能被内存DC使用,只有一个位图被创建并且选入了内存DC。

为了让我们的兼容DC里的位图和源DC的位图信息(CxxView窗口就是一副位图)一致(因为我们最后要将兼容DC里的图形原封不动的拷到源DC上来)。我们还要通过源DC产生兼容的位图,将这个兼容的位图选入兼容DC。由于位图包含头信息和像素块两个信息,所以这里仅仅是兼容DC的位图和源DC的位图的头信息一致了(两个DC上的图片的宽度与高度一致等),要把像素块拷过来就要用兼容DC调用BitBlt,从源DC将数据拷过来。

 

定义一个兼容DC,它是CxxView的一个成员      CDC m_comdc;,注意由于它是一个窗口源DC 的兼容DC,所以它的产生必须是在窗口产生好后才能产生,因为只有当一个Windows的窗口资源产生好了才能得到源DC,不能在CxxView的构造里来产生这个兼容DC。这不同与我们前一个元文件DC,它不与窗口建立关联,所以,可以在窗口还未产生的时候产生。

 

void CMyReDrawView::OnDraw(CDC* pDC)

{

                                             CMyReDrawDoc* pDoc = GetDocument();

                                             ASSERT_VALID(pDoc);

                                             // TODO: add draw code for native data here

 

                                             CRect rect;

                                             GetClientRect(&rect);     

                                             if(!comdc.m_hDC)

                                             {

                                                 comdc.CreateCompatibleDC(pDC);       

                                                 CBitmap bmp;

                                                 bmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());

                                                 comdc.SelectObject(&bmp);

                                                 comdc.BitBlt(0,0,rect.Width(),rect.Height(),pDC,0,0,SRCCOPY);          

                                             }

                                             pDC->BitBlt(0,0,rect.Width(),rect.Height(),&comdc,0,0,SRCCOPY);

}

 

 

void CMyReDrawView::OnLButtonUp(UINT nFlags, CPoint point)

{

                                             // TODO: Add your message handler code here and/or call default

 

 

                                             m_ptEnd=point;

                                             CClientDC dc(this);        

                                             CPoint pt=dc.GetViewportOrg();

                                             this->OnPrepareDC(&dc);

                                             pt=dc.GetViewportOrg();

                                            

                                             dc.DPtoLP(&m_ptOrigin);     

                                             dc.DPtoLP(&m_ptEnd);

                                            

 

                                             dc.MoveTo(m_ptOrigin);

                                             dc.LineTo(m_ptEnd);

                                            

                                             comdc.MoveTo(m_ptOrigin);

                                             comdc.LineTo(m_ptEnd);

                                             CScrollView::OnLButtonUp(nFlags, point);

}

 

将CxxView的OnDraw里产生兼容DC的判断语句全部放到OnLButtonDown里的兼容DC画图的上面,这样做是为了灵活改变OnLButtonDown里所产生的源DC关联的窗口,我们最后不要改动OnDraw里的pDC。那是系统传给我的,由于是指针,是还要返回以便后面操作的。此时如果想抓桌面的图。可以把OnLButtonDown里所产生的源DC关联的窗口改为CClientDC dc(NULL);如果想抓本程序主窗口的客户取则:CClientDC dc(GetParent());如果想抓本程序主窗口的整个窗口:CWindowDC dc(GetParent());

void CReDrawTestView::OnLButtonUp(UINT nFlags, CPoint point)

{

                                             // TODO: Add your message handler code here and/or call default

                                             m_ptEnd=point;

                                             CClientDC dc(NULL);

                                             OnPrepareDC(&dc);

                                             dc.DPtoLP(&m_ptOrigin);

                                             dc.DPtoLP(&m_ptEnd);

                                             dc.MoveTo(m_ptOrigin);

                                             dc.LineTo(m_ptEnd);

                                             //m_metaFile.MoveTo(m_ptOrigin);

                                             //m_metaFile.LineTo(m_ptEnd);

                                             //CGraph* pGraph=new CGraph(m_ptOrigin,m_ptEnd);

                                             //m_obArray.Add((CObject*)pGraph);

                                             CRect rect;       

                                             GetClientRect(&rect);

                                             if(!m_comdc.m_hDC)

                                             {         

                                                 CBitmap bmp;            

                                                 bmp.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height());        

                                                 m_comdc.CreateCompatibleDC(&dc);

                                                 m_comdc.SelectObject(&bmp);

                                                 m_comdc.BitBlt(0,0,rect.Width(),rect.Height(),&dc,0,0,SRCCOPY);

                                             }

                                             m_comdc.MoveTo(m_ptOrigin);

                                             m_comdc.LineTo(m_ptEnd);

 

                                             CScrollView::OnLButtonUp(nFlags, point);

}

 

 

综合比较发现元文件DC重绘图形最快

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值