VC控件

CEdit重新审视

一个从没想过的问题今天终于出现了!我自己都感到以外!呵呵,什么问题呢?可能你也没想过哦。即使想过,你解答了没有?

是一个聊天的程序,类似于QQ的界面。简化一点说把。(注意是简化)一个基于对话框的的程序,只有2个编辑框m_edit1,m_edit2, 还有一个是按钮。现在我要做的就是在 m_edit2中输入,然后点击按钮,内容就跑到了m_edit1中。并且每次点击后,上次 m_edit1的内容还会留下来,当然是聊天嘛,每次发送的内容要在m_edit1的最后显示。呵呵,听起来很简单哦

注意我最后一句话。如果你采用连加的话,像这样:

m_edit1.GetWindowText(m_str1);

m_edit2.GetWindowText(m_str2);

m_str1+=m_str2;

m_edit1.SetWindowText(m_str1);

内容是出现了,但是光标还在第一个位置,这样如果m_edit1是一个多行的程序,有垂直滚动条,那么就麻烦了,前面我已经说了,要求是  “每次发送的内容要在m_edit1的最后显示”,也就是要求滚动条要移动到最后,按照上面那种方法,滚动条都在上面。

怎么办哪?没有关于滚动条的函数啊。其实不是滚动条的原因,原因是光标。光标还在第一个位置啊。明白了,我们要把光标移动到最后。

于是上msdn了。找“CEdit”,“class members“,这么多函数,还不知哪个是移动光标的函数呢。一个一个的试。posfromchar,SetCaretPos ,......都不行啊,晕了。。。

正绝望呢,突然想起了久违的setsel和GetSel  我们都知道CEdit是用来选中编辑框中的内容的,它们会不会有用呢?管他呢,先 全部选中再说。

m_edit1.SetFocus();

m_edit1.SetSel(0,-1);

啊!选中了!不,我不是惊喜这个(不然我会被看贴的砍死的)。除了选中之余,光标也移动到了最后!呵呵,成功了!

再想想,还不行,差一点。这样岂不是我每次要全部选中?不好。既然选中是移动光标的话,那我干脆一不做二不休,函数照用,但是我不选,呵呵,这样写:

m_edit.SetFocus();

m_edit1.SetSel(m_str1.GetLength(),m_str1.GetLength());

搞定了!

转载 关于控件 - 鹏 - 个人主页 :)

高兴之余,我又想了一下这个问题。SetSel可以确定光标位置,那么GetSel不就可以知道光标的位置吗?试试:(另外一个程序)

 DWORD   curPos  =   m_edit.GetSel();  

 CString   sCurStr ;

 m_edit.GetWindowText(sCurStr);  

 sCurStr.Insert(LOWORD(curPos),   str);  

 m_edit.SetWindowText(sCurStr);

这样的话,随便定义光标在一个位置,都可以插入东西了,呵呵。

感慨就是函数的功能还真多!看来要慢慢理解!

 

 控件的实时拖动

由于一个小项目的需要,必须做控件的拖曳。以前还真没有想过这种问题。先来看看有没有处理消息,比如drag什么的,很遗憾没有。再来看看有没有函数,好像也没有,郁闷。没办法了。只好用绝招,自己做一个派生类把,下面我就以编辑框为例,简单说明一下拖曳的过程。

1。从CEdit派生一个类为CmyEdit。

2。添加左键处理函数:

 void Cmyedit::OnLButtonDown(UINT nFlags, CPoint point) 
{
          // TODO: Add your message handler code here and/or call default
  
        pp=point;      // pp是Cmyedit的成员变量
          SetTimer(5,10,NULL);  // 引发定时器
          CEdit::OnLButtonDown(nFlags, point);
}

 

void Cmyedit::OnLButtonUp(UINT nFlags, CPoint point) 
{
         // TODO: Add your message handler code here and/or call default
        KillTimer(5);  // 关闭定时器
        CEdit::OnLButtonUp(nFlags, point);
}

 

3。添加定时消息处理:

void Cmyedit::OnTimer(UINT nIDEvent) 
{
 // TODO: Add your message handler code here and/or call default
 
if(nIDEvent==5){ 

//获得鼠标当前位置,转换为父窗口内的坐标

 CPoint cursor_Pos;
 GetCursorPos(&cursor_Pos);
 GetParent()->ScreenToClient(&cursor_Pos);

//鼠标原来单击位置pp,转换为所在父窗口内的坐标

 CPoint edit_Pos=pp;
 ClientToScreen(&edit_Pos);
 GetParent()->ScreenToClient(&edit_Pos);

// 获得控件的大小,并且转换到父窗口的坐标,主要是为了获得左上角的坐标,方便下面的movewindow调用

 CRect edit_rect;
 GetWindowRect(edit_rect);
 GetParent()->ScreenToClient(&edit_rect);

// 利用向量相等的性质求得鼠标移动后的新左上角坐标

 int x=edit_rect.left+(cursor_Pos.x-edit_Pos.x);
 int y=edit_rect.top+(cursor_Pos.y-edit_Pos.y);
 MoveWindow(x,y,edit_rect.Width(),edit_rect.Height());

 }
CEdit::OnTimer(nIDEvent);

}

 

4.改变鼠标形状

BOOL Cmyedit::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
        // TODO: Add your message handler code here and/or call default

        SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEALL));
        return TRUE;

}

这样就结束了。可以到处用了。符合拖动的要求了。

 

抽屉菜单的简单实现

 

看到 QQ的抽屉,也想自己做一个,于是做了个最简单的,界面如图所示

 

转载 关于控件 - 鹏 - 个人主页 :)

 

 

 

 

这是一个基于对话框的程序,有3个按钮,有4个对话框,1个是主窗口,还有3个是子窗口,因此需要3个对话框的资源风格为:  “下层,对话框架“

 

 

下面是具体设计过程:

 

1.给3个子窗口(对话框)分别派生一个对话框类,名为Cmydlg1,Cmydlg2,Cmydlg3;

 

 

2. Cmydlg (主窗口中添加成员:

 

       Cmydlg1 *dlg1;

       Cmydlg2 *dlg2;

       Cmydlg3 *dlg3;

       int m_left;    // 按钮的左边坐标

       int m_right;  // 按钮的右边坐标

       int m_buttonHeight;// 按钮的高度

       int m_buttonWidth; // 按钮的宽度

       int m_dlgHeight;  // 子对话框的高度,而宽度与按钮应该相同

       CButton m_button[3]; // 3个按钮,下面会动态创建

 

 

3.初始化

 

BOOL CMyDlg::OnInitDialog()

{

       CDialog::OnInitDialog();

……….

       dlg1=new Cmydlg1;

       dlg2=new Cmydlg2;

       dlg3=new Cmydlg3;

       // 创建3个按钮,注意它的位置   

       for(int i=0;i<3;i++)

       {

              m_button[i].Create("hehe",WS_CHILD|WS_VISIBLE, \

                     CRect(m_left,m_buttonHeight*i,m_right,m_buttonHeight* (i+1)),this,IDC_BUTTON+i);

       }

       // 创建非模态窗口(即充当子窗口)

       dlg1->Create(IDD_DIALOG1,this);

       dlg2->Create(IDD_DIALOG2,this);

       dlg3->Create(IDD_DIALOG3,this);

       // 首先移动3个子窗口的位置,以后都不用再移动了,只需要显示或者不显示

       dlg1->MoveWindow(m_left,m_buttonHeight,m_buttonWidth,m_dlgHeight);

       dlg2->MoveWindow(m_left,m_buttonHeight*2,m_buttonWidth,m_dlgHeight);

       dlg3->MoveWindow(m_left,m_buttonHeight*3,m_buttonWidth,m_dlgHeight);

       // 成员函数

       show(0);

       return TRUE;  // return TRUE  unless you set the focus to a control

}

 

 

4.显示函数

void CMyDlg::show(int index)

{

       dlg1->ShowWindow(! (index-0));       // 判断是显示哪个窗体

       dlg2->ShowWindow(! (index-1));

       dlg3->ShowWindow(! (index-2));

// 显示窗体以后就要移动相应按钮的位置,处理一下几何位置关系,不细说了

       for(int i=0;i<=index;i++)

       {

m_button[i].MoveWindow(  m_left, m_buttonHeight*i,m_buttonWidth,

m_buttonHeight  );

       }

       for(i=index+1;i<3;i++)

       {

              m_button[i].MoveWindow( m_left,m_buttonHeight*i+m_dlgHeight,m_buttonWidth,m_buttonHeight  );

       }

}

 

 

5.添加按钮的消息映射

.h :

       // Generated message map functions

       //{{AFX_MSG(CMyDlg)

       virtual BOOL OnInitDialog();

       afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

       afx_msg void OnPaint();

       afx_msg HCURSOR OnQueryDragIcon();

      

       //}}AFX_MSG

       afx_msg void OnClick(UINT nID);

 

 .cpp:

BEGIN_MESSAGE_MAP(CMyDlg, CDialog)

       //{{AFX_MSG_MAP(CMyDlg)

       ON_WM_SYSCOMMAND()

       ON_WM_PAINT()

       ON_WM_QUERYDRAGICON()

      

       //}}AFX_MSG_MAP

       ON_COMMAND_RANGE(IDC_BUTTON+0,IDC_BUTTON+2,OnClick)

END_MESSAGE_MAP()

 

 

6. 消息响应

void CMyDlg::OnClick(UINT nID)

{

       int index=nID-IDC_BUTTON;

       show(index);

}

 

 

7.画边框

void CMyDlg::OnPaint()

{

。。。。。。。。。          

              CPaintDC dc(this);

              CBrush brush(RGB(255,0,0));

             dc.FrameRect(CRect(m_left-1,-1,m_right+1,m_buttonHeight*3+m_dlgHeight+1),&brush);

              CDialog::OnPaint();

       }

}

 

 

完成!值得干一杯了!当然你还可以优化一下界面!

 

轻松画背景

 

在用MFC编程的时候,尤其是设置界面的时候,大家都会觉得VC的控件属性设置还比不上VB.连个颜色都设置不了.但也正是这样,MFC提供给我们的自由也就越多,我们可以随意派生自己的控件类,想怎么做就怎么做.VB不行.

 

但是派生一个自己的控件类,然后去自绘,也不是容易的事情.在不断的探索中,我发现了一个简单的设置背景的方法,就是直接利用PICTURE控件.把它设置成容纳位图,然后就可以充当背景了.

 

 

比如有一个static控件.现在再绘制一个,PICTURE控件,放在static上面.这个时候,图像是会遮盖住static控件的.我们可以重新设置一下Tab,PICTURE控件排在前面即可.下面看看我的作品把:

 

 

转载 关于控件 - 鹏 - 个人主页 :)

 

 

 

CListCtrl学习笔记(1)---基础篇

 

背景:CListCtrl 在小型数据库中应用的还是比较多的.因此掌握它是一个很重要的技能.最近在做老师布置的一个项目,用到它,开始的时候不知所措.后来上网找了一些资料,再加上自己的探索,终于对它有一个比较全面的认识.下面我简单讲讲它的一些用法,以此和大家共享一下.

  

 

1.     基本风格设置

 

      (1)函数 ModifyStyle( )

      (2)重要参数:    LVS_ICON  // 大图标

                                  LVS_SMALLICON // 小图标

                                  LVS_LIST  // 列表

                                  LVS_REPORT  // 报表

   (3)说明:

用的比较多的是最后的报表视图.因为它可以有多列,正好代表数据库中的多个属性.所以下面的用法都是针对这种风格的.当然这些风格也可以在控件的属性中设置

(4) 注意

             我们知道在窗口各种各样的风格之间,有时是可以用” |”,表示属性叠加.:

                          WS_CHILD | WS_VISIBLE;

但是上面的四种风格是不可能放在一起的.所以不要用到 “ |”操作符.

也正是因此,防止用户出错微软干脆把LVS_ICON, LVS_SMALLICON, LVS_LIST, LVS_REPORT这些都不设置成位标志,因此不能叠加.同时还设置了一个多余的掩码:

LVS_TYPEMASK.它是用来屏蔽的.

 

(5) 举例:

如果我们要判断一个CListCtrl的风格:

              DWORD dwStyle = m_listctrl.GetStyle( );

// 判断是否大图标样式

              If ( dwStyle & LVS_ICON )

                            …….

       这种写法是错误的.正确的写法涉及到掩码:

                     DWORD dwStyle = m_listctrl.GetStyle( ) & LVS_TYPEMASK;

                     If ( dwStyle = = LVS_ICON)

                     …….

       同理,我们在改变风格时,应该这样写:

                     ModifyStyle( LVS_TYPEMASK, LVS_ICON);

 

 

2.     扩展风格设置

 

    (1) 函数 : SetExtendedStyle( )   GetExtendedStyle ( )

(2) 重要参数:

              LVS_EX_FULLROWSELECT //选中某行使整行高亮(只适用与报表风格)

              LVS_EX_GRIDLINES //网格线(只适用与报表风格)

              LVS_EX_CHECKBOXES //设置checkbox状态

(3)举例

                     DWORD dwStyle = GetExtendedStyle();

                   dwStyle |= LVS_EX_FULLROWSELECT;

                 dwStyle |= LVS_EX_GRIDLINES;

                 SetExtendedStyle(dwStyle);

 

 

3.其他风格设置:

 函数: SetTextColor ( )  // 设置文字颜色

SetBkColor ( ) // 设置边框颜色

SetTextBkColor ( ) // 设置文字背景颜色

 

 

//下面直接举例说明

 

4.图标设置

可以给大图标风格和小图标风格设置图标:

HICON icon=AfxGetApp()->LoadIcon(IDI_ICON1);

 m_icon.Add(icon);

m_listctrl.SetImageList(&m_icon,LVSIL_SMALL);//小图标

      m_listctrl.SetImageList(&m_icon,LVSIL_NORMAL)// 大图标

 

 

5插入一列

m_listctrl.InsertColumn(0,”哈哈”,LVCFMT_LEFT,80);

其中: 0是索引项,”哈哈是列标题,LVCFMT_LEFT是显示方式(靠左),80表示列的宽

 

 

6插入一行

m_listctrl.InsertItem(0, “123”,0);// 插入为第一行第一列的内容,最后一个0是图标的索引

m_listctrl.SetItemText( 0,1, “123”) ; // 设置第一行第2列的内容

       m_ listctrl.SetItemText(0,2, “123”) ; // 设置第一行第3列的内容

 

 

7得到所有的行数

m_listctrl.GetItemCount( ) ;

 

 

8.得到所有的列数

m_listctrl.GetHeaderCtrl().GetItemCount( ) ;

 

 

9.得到被单击的项的行列号

void Cmylist::OnClick(NMHDR* pNMHDR, LRESULT* pResult) //单击消息

{

      

       NM_LISTVIEW *info=(NM_LISTVIEW*)pNMHDR;

       selectedIndex=info->iItem; // 行号

       selectedsub=info->iSubItem; // 列号

 }    

(CmylistCListCtrl的派生类,selectedIndexselectedsub为接口,方便使用)        

 

            

10.得到被单击的列头索引号

void Cmylist::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)

{

       NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

        selectedField=pNMListView->iSubItem;//列头号

}

同上,也是以selectedField为接口,方便用户调用

 

 

11.选中或者取消一行

plistctrl->SetItemState(index,LVIS_SELECTED,LVIS_SELECTED);//选中一行

plistctrl->SetItemState(index,0,LVIS_SELECTED);//取消一行

DWORD style=plistctrl->GetItemState(index,LVIS_SELECTED);//获得选中信息

 

 

12.获得被选中的多行

              POSITION p=m_listctrl.GetFirstSelectedItemPosition();// 得到第一次选中的位置

              while(p)

              {

                     int index=m_listctrl.GetNextSelectedItem(p);

                     …

              }

 

 

13.删除一行或者一列

        m_listctrl.DeleteItem( index );

       m_listctrl.DeleteColumn(index);

 

 

14.删除多行或者多列

       注意要从后面开始删起,比如,如果我想删除第一行和第二行,应该:

              m_listctrl.DeleteItem(1);

              m_listctrl.DeleteItem(0);

       而不能:

              m_listctrl.DeleteItem(0);

m_listctrl.DeleteItem(1);

       因为每次删除一行或者一列,后面的索引号都要变化,所以从后面开始删除就没关系.

如果用for循环,应该递减:

       For(int k=m_listctrl.GetItemCount( );k>=-1;k++)

              m_listctrl.DeleteItem(k);

这就是删除所有行,当然也可以用CListCtrl::DeleteAllItems

 

 

15 根据索引号得到某一列的信息

                     HDITEM   hdi;  

                     TCHAR     lpBuffer[256];    

                     hdi.mask   =   HDI_TEXT;  

                     hdi.pszText   =   lpBuffer;  

                     hdi.cchTextMax   =   256;  

                     plistctrl->GetHeaderCtrl()->GetItem(index,   &hdi);

                     CString str=hdi.pszText;

 

CListCtrl学习笔记(2)---中级篇(1)

 

专题1: 如何使CListCtrl完全可编辑?

 

 

1.       背景 : 我们知道如果CListCtrl是报表样式,那么CListCtrl所提供的编辑功能只局限于第一列.也就是说只有第一列可编辑.这样显然无法满足一般数据库的要求.我们想要每个子项都能编辑.

 

 

2.       思路 : CEdit是一个很好的可控制编辑控件.如何把CEdit和我们的CListCtrl联系起来?一种很好的想法是------一般我们如果想编辑某一项,那么就应该去双击.双击以后就让CEdit在那里显示,当然要把大小调整和子项表格一样.如果CEdit失去了焦点,表示修改完毕,那么立即更改子项的数据,同时让CEdit隐藏.因为每次只能编辑一项,所以只需要一个CEdit就够了.

 

 

3.       方法:

(1)    首先从CListCtrl派生一个类,其他已经有的变量或者函数设置我已经介绍,如果不清楚的读者,可以去参考基础篇”.

 

(2)    有一点可以肯定,我们必须响应双击事件:

void Cmylist::OnLButtonDblClk(UINT nFlags, CPoint point)

{

int index;//行号

int colnum;//列号

GetWindowRect(r);//稍后说明

GetParent()->ScreenToClient(r);//稍后说明

 

if((index=HitTestEx(point,&colnum))!=-1)

    EditSubItem(index,colnum);

 

CListCtrl::OnLButtonDblClk(nFlags, point);

}

其中HitTestEx是用来求出双击点所在的行列号,如果行号不为-1,那么就调用函数EditSubItem. 这个函数会根据行列号求出该子项具体坐标,方便CEdit调整位置.

 

 

(3)    如何求出行列号?行号是很好求出来的 ,但是列号就不是很简单了,必须详细判断.

int Cmylist::HitTestEx(CPoint &point, int *pcolumn)

{

       int columnNum=0;

//获取页面内首行索引号,不一定是0,要考虑滚动条的情况

int row=GetTopIndex();

// GetCountPerPage()获取在页面内行的总数

       int bottom=row+this->GetCountPerPage();

       // 防止超出范围

       if(bottom>this->GetItemCount())

              bottom=GetItemCount();

       //获取列的总数

       int ncolumncount=this->GetHeaderCtrl()->GetItemCount();

       //可以肯定双击点肯定在页面内,因此从页面首行索引号开始判断

       for(;row<=bottom;++row)

       {

              CRect rect;

              //求出行的rect

              GetItemRect(row,&rect,LVIR_BOUNDS);

              //点是否在行的矩形内

              if(rect.PtInRect(point))

              //如果点在行的矩形内,求出点在哪一列

                     for(columnNum=0;columnNum<ncolumncount;columnNum++)

                     {

                            //求出列的宽度

                            int colwidth=this->GetColumnWidth(columnNum);

                            if(point.x>=rect.left&&point.x<=(rect.left+colwidth))

                            {

                                   *pcolumn=columnNum;

                                   return row;

                            }

                            rect.left+=colwidth;

                     }

       }

       return -1;

}

当然上面那种方法有点复杂,是完全从头开始判断.其实我们可以先利用CListCtrl提供的函数求出行号,再求列号,这样稍微简单点

int Cmylist::HitTestEx(CPoint &point, int *pcolumn)

{

       int columnNum=0;

       int row=HitTest(point);//求出行号

   int ncolumncount=this->GetHeaderCtrl()->GetItemCount();

      

    (2007.1.3更新)       

        LVHITTESTINFO Info;
       Info.pt=point;
       this->SubItemHitTest(&Info);
      *pcolumn=Info.iSubItem;

      if(*pcolumn>=0&&*pcolumn<ncolumncount)
      return row;

      else

       return -1;

      /* int ncolumncount=this->GetHeaderCtrl()->GetItemCount();

      

              CRect rect;

              GetItemRect(row,&rect,LVIR_BOUNDS);

              if(rect.PtInRect(point))

                     for(columnNum=0;columnNum<ncolumncount;columnNum++)

                     {

                            int colwidth=this->GetColumnWidth(columnNum);

                            if(point.x>=rect.left&&point.x<=(rect.left+colwidth))

                            {

                                   *pcolumn=columnNum;

                                   return row;

                            }

                            rect.left+=colwidth;

                     }*/

      

       }

 

 

(4)    求出具体CEdit移动坐标

int Cmylist::Item_X(int row, int column,CRect& rect_X)

{

 

       int offset=0;

       for(int i=0;i<column;i++)

              offset+=GetColumnWidth(i);

       CRect rect;

       GetItemRect(row,rect,LVIR_BOUNDS);

       //注意水平滚动条的影响,如果已经移动了水平滚动条,可能left0,或者超出总大小

       if(offset+rect.left<0||offset+rect.left>client_rect.right)

       {

              CSize size;

              //offset肯定为正,如果出现了rect.left为负

              if(offset+rect.left>0)

                     size.cx=- (offset+rect.left);

              else

                     size.cx=offset+rect.left;

              size.cy=0;//垂直不用管

              //如果某一列的一半在滚动条左边,一半在右边,就再次调整滚动条的位置.

              Scroll(size);

              rect.left - =size.cx;

       }

       rect.left+=offset+2;

       rect.right=rect.left+GetColumnWidth(column)-2;

       //bottomtop不用管

       rect_X=rect;

       return rect.right;

}

 

 

 

(5)    移动CEdit

 

void Cmylist::EditSubItem(int Item, int Column)

{

       CRect rect;

       //求出行列所在rect

       this->Item_X(Item,Column,rect);

       EditCellShow(rect,Item,Column,r);

}

 

 

 

void Cmylist::EditCellShow(CRect rect, int Item, int Column,CRect r)

 

{

       //还记得r?在开始的双击函数OnLButtonDblClk,它是CListCtrl在父窗口中的位置

    rect.left+=r.left;

       rect.top+=r.top+2;

       rect.right+=r.left;

       rect.bottom+=r.top+2;

//peditCEdit对象的指针,提供接口,只要在程序中让pedit指向一个对象即可

       pedit->MoveWindow(rect,TRUE);

       pedit->ShowWindow(TRUE); 

       pedit->SetFocus();

}

 

 

 

^_^!这样就完成了.效果还可以.当然你还要去响应CEdit失去焦点和得到焦点的事件.这个就不是我的任务了,因为每个人的要求不一样啊!

看看我的效果!

 

转载 关于控件 - 鹏 - 个人主页 :)


 

对话框条的制作CDialogBar

简单来说,就是对话框条的制作

1.创建对话框资源:在对话框资源编辑器内生成一个Dialog资源,并将其风格(Style)属性必须设置为Child,不能设置为OverlappedPopup,否则运行肯定出错;至于边界属性则随用户自己喜欢,一般都是选择None。其余属性也随用户选择,一般没有特殊要求还是选择默认的好。

2.自己派生一个继承于CDialogBar的类,注意此时由于ClassWizard没有把CDialogBar列出来,所以只好自己手动编写.h和.cpp,然后加上必要的处理函数,如:

class CmyDlgWnd : public CDialogBar  
{
public:
 CmyDlgWnd(CWnd *pParent=NULL);
 
 virtual ~CmyDlgWnd();
  //{{AFX_VIRTUAL(CmyDlgWnd)
 protected:
 virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
 //}}AFX_VIRTUAL
// Implementation
protected:
 
 // Generated message map functions
 //{{AFX_MSG(myDlgWnd)
  // NOTE: the ClassWizard will add member functions here
 //}}AFX_MSG

 DECLARE_MESSAGE_MAP()

};

3.假设在对话框添加了一个按钮,现在要响应,可以手动编写处理函数:

.h:

afx_msg void OnClose();

.cpp:

BEGIN_MESSAGE_MAP(CmyDlgWnd, CDialogBar)
 //{{AFX_MSG_MAP(CmyDlgWnd)
  // NOTE: the ClassWizard will add message map macros here
 //}}AFX_MSG_MAP

 ON_BN_CLICKED(IDC_CLOSE,OnClose)
END_MESSAGE_MAP()

4.同上,如果想添加关联变量,可以这样做:

.h:

CString  m_edit;

.cpp:

void CmyDlgWnd::DoDataExchange(CDataExchange* pDX)
{
 CDialogBar::DoDataExchange(pDX);
 //{{AFX_DATA_MAP(Csql)
 DDX_Text(pDX, IDC_EDIT1, m_edit);
 //}}AFX_DATA_MAP
}

5.处理完毕后,在CMainFrame中添加:


int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 ...

//创建控制条    
 if(!m_wndDlg.Create(this,IDD_DIALOG2,CBRS_LEFT,100))
  return -1;
 //停泊控制条

 m_wndDlg.EnableDocking(CBRS_ORIENT_HORZ);
 m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
 EnableDocking(CBRS_ALIGN_ANY);
 DockControlBar(&m_wndToolBar);
 DockControlBar(&m_wndDlg);
 }

6.注意此时对话框控制条并不能响应消息,因为消息被CMainFrame截获,因此要想办法把消息转发给CDialogBar,方法就是重载OnCmdMsg,具体的消息转发知识理论将在下次专题中推出:

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) 
{
  if(CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
  return TRUE;
  return m_wndDlg.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

 

以上就是一种处理方法,还有一种方法是:(转载),该方法可以

提供ClassWizard帮助,比较好

 

用对话框创建CDialogBar派生的类并在CReBar上添加

 

摘要:本文详细解说了CDialogBar的具体使用过程,可以做为VC++MFC新手学习总结用。

 

一、创建DialogBar的派生类

首先,创建对话框资源:在对话框资源编辑器内生成一个Dialog资源,并将其风格(Style)属性必须设置为Child,不能设置为OverlappedPopup,否则运行肯定出错;至于边界属性则随用户自己喜欢,一般都是选择None。其余属性也随用户选择,一般没有特殊要求还是选择默认的好。

其次,创建基于CDialog的派生类:打开ClassWizard,为以上创建的资源添加一个以CDialog为基类的派生类(因为ClassWizard没有将CDialogBar列在基类目录清单中,所以用户只能先以CDialog类派生)。

再次,修改派生类以CDialogBar为基类:通常需要手工修改几处代码,在本例中派生类以CDataStatus命名。(注:以后讲解中凡是手工改动都是以灰背景显示)

1  在头文件中修改继承关系

class CDataStatus : public CDialog  改为class CDataStatus : public CDialogBar

2  在代码文件中修该构造函数继承关系

CDataStatus::CDataStatus(CWnd* pParent /*=NULL*/)

: CDialog(CDataStatus::IDD, pParent)

{

       //{{AFX_DATA_INIT(CDataStatus)

              // NOTE: the ClassWizard will add member initialization here

       //}}AFX_DATA_INIT

}

改为

CDataStatus::CDataStatus(CWnd* pParent /*=NULL*/)

{

       //{{AFX_DATA_INIT(CDataStatus)

              // NOTE: the ClassWizard will add member initialization here

       //}}AFX_DATA_INIT

}

3  DDX绑定函数中的继承关系去掉

即将void CDataStatus::DoDataExchange(CDataExchange* pDX)

{

       CDialog::DoDataExchange(pDX);

       //{{AFX_DATA_MAP(CCurrentCheckDlg)

       ………..

       //}}AFX_DATA_MAP

}

改为

void CDataStatus::DoDataExchange(CDataExchange* pDX)

{

       //{{AFX_DATA_MAP(CCurrentCheckDlg)

       ………….

       //}}AFX_DATA_MAP

}

4  重新初始化函数(这个相当重要,如果不这么做的话,DDX函数形同虚设,当然用户的工具条如果没有用到DDX的话当然可以不加这段代码):

首先在ClassWizardMessageMap中对消息该CDataStatus类的WM_INITDIALOG消息添加处理函数默认名为OnInitDialog

其次手工修改代码如下:

1             添加消息映射函数。由于对话框形式的初始化函数消息并未加载到消息映射内,为此我们需要手工添加,要不然代码无法拦截该工具条的初始化消息,形式如下:

BEGIN_MESSAGE_MAP(CDataStatus, CDialogBar)

       //{{AFX_MSG_MAP(CDataStatus)

       .......

       //}}AFX_MSG_MAP

END_MESSAGE_MAP()

改为:

BEGIN_MESSAGE_MAP(CDataStatus, CDialogBar)

       //{{AFX_MSG_MAP(CDataStatus)

       .......

       ON_MESSAGE(WM_INITDIALOG,OnInitDialog)

       //}}AFX_MSG_MAP

END_MESSAGE_MAP()

2             修改OnInitDialog函数,此函数并未传递参数,但是在这里我们需要让它传递参数,代码如下修改(当然头文件中,对声明也要做修改,在这里就不作赘述了)

BOOL CDataStatus::OnInitDialog()

{

       CDialogBar::OnInitDialog();

   

       // TODO: Add extra initialization here

       return TRUE;  // return TRUE unless you set the focus to a control

              // EXCEPTION: OCX Property Pages should return FALSE

}

改为:

BOOL CDataStatus::OnInitDialog(UINT wParam,LONG lParam)

{

       //CDialogBar::OnInitDialog();

        

       // TODO: Add extra initialization here

       BOOL bRet = HandleInitDialog(wParam,lParam);

       if (!UpdateData(FALSE))

       {

              TRACE("InitCDataStatus Failed");

       }

       return TRUE;  // return TRUE unless you set the focus to a control

                     // EXCEPTION: OCX Property Pages should return FALSE

}

 

二、在框架类中实现该派生类的对象化

首先,在框架类的头文件内声明实例对象,本例实例化:CDataStatus   m_wndDataStatus;当然头文件中不可避免要包含新派生类的头文件。

其次,在框架类的OnCreate函数内创建对象并将对象绑定对话框资源。形式与创建ToolBar原理一样,本例实例如下:

if (!m_wndDataStatus.Create(this,IDD_DATASTATUS,WS_VISIBLE|WS_CHILD

|CBRS_SIZE_DYNAMIC|CBRS_BOTTOM,IDD_DATASTATUS))

      {

             TRACE0("Failed to create CDataStatus bar!");

             return -1;

      }

再次,最为关键的一点就是重写框架类的OnCmdMsg虚函数。如果不重写该函数,那么不光DDX功能无法实现,连最基本的OnCommand事件都无法实现。而且还得手工添加代码,形式如下:

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo)

{

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

      return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

}

改为:

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)

{

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

      if (m_wndDataStatus.OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))

             return    TRUE;

      return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

}

三、在CReBar上添加该实例化对象

其实这一步倒是相当简单,只是自己以前没用过这个类,所以在这里也顺便用了一下。

首先,在框架类的头文件中用CRebar声明一个对象,如CReBar   m_wndReBar;

其次,在框架类的代码文件中的OnCreat函数体内,生成对象,代码如下:

if (!m_wndReBar.Create(this,RBS_BANDBORDERS,WS_CHILD |

WS_VISIBLE| CBRS_BOTTOM|WS_CLIPSIBLINGS|WS_CLIPCHILDREN))

      {

             TRACE0("Failed to create  Rebar \n");

             return -1;

      }

再次,就是将所要添加的toolbar以及新生成的CDataStatus对象m_wndDataStatus加进Rebar的对象m_wndReBar中,代码如下:

m_wndReBar.AddBar(&m_wndDataStatus,NULL,NULL,

RBBS_GRIPPERALWAYS|RBBS_FIXEDBMP);

 

 

CListCtrl学习笔记(3)---中级篇(2)

 

 

专题2:CListCtrl中如何实现排序功能?

1.排序功能的实现需要用到下面这个函数:

BOOL SortItems( PFNLVCOMPARE pfnCompare, DWORD dwData );

其中 第一个参数是回调函数的指针,后面是所要传递的参数

而回调函数一般定义如下:

int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, 
   LPARAM lParamSort);

其中lParam1是其中list的一项数据,而lparam2是另外一项,第三个参数是刚才传递过来的参数.

2.举例如下:

(1)回调函数的编写

注意回调函数必须是全局或者静态的,并且不能调用成员变量,所以要凭借最后一个参数传递某些数据.

int CALLBACK CompareFunc(LPARAM lParam1,LPARAM lParam2,LPARAM lParamSort)
{

//传递list指针
 Cmylist *list=(Cmylist*)lParamSort;

//获得指定行的索引号
 LVFINDINFO findInfo; 
 findInfo.flags = LVFI_PARAM; 
 findInfo.lParam = lParam1; 
 int iItem1 = list->FindItem(&findInfo, -1); 
 findInfo.lParam = lParam2; 
 int iItem2 = list->FindItem(&findInfo, -1);

//根据指定列的索引号,得到所在项的数据
 CString strItem1 =list->GetItemText(iItem1,list->selectedField); 
 CString strItem2 =list->GetItemText(iItem2,list->selectedField);

//判断是升序还是降序
  if(!list->shunxu)
  if(index==1)//判断数据类型是整型还是字符串
  return strcmp(strItem2, strItem1);
  else
  return atoi(strItem2)-atoi(strItem1);
 
 else
  if(index==1)
  return -strcmp(strItem2, strItem1);
  else
  return atoi(strItem1)-atoi(strItem2);
}

(2)调用函数的编写

如果是点击列头响应排序功能,那么请响应LVN_COLUMNCLICK:

如果是其他,例如响应菜单项:

void Cmylist::OnSheng() 
{
 // TODO: Add your command handler code here
 shunxu=TRUE;
 SortItems(CompareFunc,(LPARAM)this);
}

void Cmylist::OnJiang() 
{
 // TODO: Add your command handler code here
 shunxu=FALSE;
 SortItems(CompareFunc,(LPARAM)this);
}

即可完成排序功能!!!

 

 

CListCtrl学习笔记(4)---中级篇(3)

 

专题3:如何使列头响应右击消息?

我们知道在ClistCtrl中是可以响应列头单击的,如响应消息LVN_COLUMNCLICK,但是不能响应右击.怎么办呢?

(1)

实际上我们可以把CListCtrl(Report风格)看成由子窗口CHeaderCtrl和下面的视图组成,所以我们决定重写CHeaderCtrl.

派生一个类于CHeaderCtrl,响应右击:

void CMyHeadCtrl::OnRButtonDown(UINT nFlags, CPoint point) 
{
 // TODO: Add your message handler code here and/or call default
 CHeaderCtrl::OnRButtonDown(nFlags, point);
 
}

在其中编写你想要的代码.这里完成了第一步.

 

(2)

此时还没有安装到ClistCtrl 上呢?想什么办法安装呢?以前在重写控件的时候,是通过在ClassWizard中定义变量来实现的,现在显然不行.因为ClistCtrl自己就是一个控件了.难道就没有办法了吗?

有.在CListCtrl中不是有个函数是GetHeaderCtrl吗?它不就是获得列头的指针吗?我们应该怎样利用它呢?

在想想子类化的方法:

如果我们重写了一个控件,如CMyEdit(派生于CEdit),现在要安装,除了在classwizard中安装以外,还可以在OnInitDialog中进行子类化:

.h:

CMyEdit m_edit;

.cpp:

m_edit.SubClassDlgItem(IDC_EDIT1,this);

 

(3) 回到我们这里的话题.显然不能用subclassDlgItem了,因为列头没有什么标识号,不过还可以用SubClassWindow,参数正好是一个句柄.

实现如下:

重写Cmylist(CListCtrl的派生类)虚函数:

void Cmylist::PreSubclassWindow() 
{
 // TODO: Add your specialized code here and/or call the base class
 m_headctrl.SubclassWindow(GetHeaderCtrl()->GetSafeHwnd());
 CListCtrl::PreSubclassWindow();
}
安装成功!!!

 

CTreeCtrl学习笔记1--基础篇

 

以下的说明中,注意以下变量的定义:

 CTreeCtrl  m_treectrl;//是关联变量

 

 

  1. 如何获得选中项句柄?

HTREEITEM h=m_treectrl.GetSelectedItem();

 

2.如何获得右击项句柄?

 

void CLayerDialog::OnRclick(NMHDR* pNMHDR, LRESULT* pResult)

{

       // TODO: Add your control notification handler code here

       NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

       //右击获取所选项

       CPoint   point,p;  

       TVHITTESTINFO   HitTestInfo;  

       GetCursorPos(&point);

       m_treectrl.ScreenToClient(&point);  

       HitTestInfo.pt   =   point;  

       HTREEITEM h   =   m_treectrl.HitTest(&HitTestInfo);

       if(h!=NULL)

       {

              。。。。。//需要代码 

       }

}

3.如何响应checkbox被单击?

 

响应单击事件,并且进行判断

void CLayerDialog::OnLclick(NMHDR *pNMHDR,LRESULT *pResult)

{

    NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

    // TODO: Add your control notification handler code here

    CPoint p;

    GetCursorPos(&p);

    m_treectrl.ScreenToClient(&p);

    UINT nFlag;

    HTREEITEM h=m_treectrl.HitTest(p,&nFlag);

   if   ((h  !=   NULL)   &&   (TVHT_ONITEMSTATEICON   &   nFlag))  

    {   

           。。。。。//需要代码

    }

}

 

4.设置和获取checkbox的状态函数

    GetCheck( )              SetCheck( )

 

 

5.选中特定项

            HTREEITEM h=…;

            m_treectrl.SelecteItem(h);

 

    6.插入标记

这是拖曳时经常用到的函数。

BOOL SetInsertMark( HTREEITEM hItem, BOOL fAfter = TRUE );

TRUE表示在hItem下面显示横杠,而FALSE则表示在上面。

同类函数还有:

SetInsertMarkColorGetInsertMarkColor

 

    7 .获得兄弟姐妹项

HTREEITEM GetPrevSiblingItem( HTREEITEM hItem );

HTREEITEM GetNextSiblingItem( HTREEITEM hItem );

很好用的函数。但是要注意如果hItem是第一项,那么GetPrevSiblingItem返回NULL。此时要注意判断。GetNextSiblingItem同理。

 

 

 

CTreeCtrl学习笔记2-专题篇

 

如何对CTreeCtrl的项进行拖曳

 

1 取消TVS_DISABLEDRAGDROP 样式,该样式会禁止发送TVN_BEGINDRAG消息

 

 

2 派生一个类于CTreeCtrl,定义几个成员变量:

    CImageList *m_pDragImage;//拖曳图像列表指针

    BOOL m_bLDragging;//是否拖曳

    HTREEITEM m_hitemDrag,m_hitemDrop;//拖曳项和目标项

 

 

3)响应拖曳消息TVN_BEGINDRAG

void CTreeCtrlEx::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)

{

    NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

    // TODO: Add your control notification handler code here

    m_hitemDrag=pNMTreeView->itemNew.hItem;//获得拖曳项

    m_hitemDrop=NULL;

    m_pDragImage=CreateDragImage(m_hitemDrag);//创建拖曳图像

    if(!m_pDragImage)

           return;

    this->m_bLDragging=TRUE;

    m_pDragImage->BeginDrag(0,CPoint(-15,-15));//后面的point表示拖曳图像离拖曳项的相对位置

    POINT pt=pNMTreeView->ptDrag;

    ClientToScreen(&pt);

    m_pDragImage->DragEnter(NULL,pt);//开始显示拖曳图像

    SetCapture();//锁定鼠标

    *pResult = 0;

}

 

 

4)响应鼠标移动消息

void CTreeCtrlEx::OnMouseMove(UINT nFlags, CPoint point)

{

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

       HTREEITEM hitem;

       UINT flags;

       if(m_bLDragging)

       {

              POINT pt=point;

              ClientToScreen(&pt);

              CImageList::DragMove(pt);//当鼠标移动时也移动拖曳的图像

              if((hitem=HitTest(point,&flags))!=NULL)//确定鼠标所在的项

              {

                     CImageList::DragShowNolock(FALSE);//隐藏拖曳图像

                     SelectDropTarget(hitem);

                     m_hitemDrop=hitem;//确定目标项

                     CImageList::DragShowNolock(TRUE);//重新显示

              }

       }

 

       CTreeCtrl::OnMouseMove(nFlags, point);

}

 

 

5)鼠标弹起消息

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

{

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

       if(m_bLDragging)//是否拖动

       {

              m_bLDragging=FALSE;

              CImageList::DragLeave(this);

              CImageList::EndDrag();//结束拖曳

              ReleaseCapture();

              delete m_pDragImage;

              SelectDropTarget(NULL);

              if(m_hitemDrag==m_hitemDrop)

                     return;

      

              MessageBox("拖曳成功!");

。。。。现在已经获知了拖曳项和目标项,就看你自己需要什么代码了,略去。

       }

       CTreeCtrl::OnLButtonUp(nFlags, point);

}

 

 

CTreeCtrl学习笔记3--专题篇

 

如何给树控件加入工具提示

 


首先给树控件加入TVS_INFOTIP属性风格,如下所示:
if (!m_ctrlTree.Create(WS_CHILD|WS_VISIBLE|
TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT|TVS_SHOWSELALWAYS|TVS_INFOTIP, //
加入提示TVS_INFOTIPjingzhou xu(树控件ID:100)
  CRect(0, 0, 0, 0), &m_wndTreeBar, 100))
  {
    TRACE0("Failed to create instant bar child\n");
     return -1;
  }

其次加入映射消息声明,如下所示:
afx_msg void OnGetInfoTip(NMHDR* pNMHDR,LRESULT* pResult);       //
树控件上加入提示消息
ON_NOTIFY(TVN_GETINFOTIP, 100, OnGetInfoTip)                     //
树控件条目上加入提示
3最后加入呼应涵数处理:
void CCreateTreeDlg::OnGetInfoTip(NMHDR* pNMHDR, LRESULT* pResult) 
  {
  *pResult = 0;
  NMTVGETINFOTIP* pTVTipInfo = (NMTVGETINFOTIP*)pNMHDR;
  LPARAM itemData = (DWORD) pTVTipInfo->lParam;
  //
对应每个条目的数据
  HTREEITEM hItem = pTVTipInfo->hItem;
  CString tip;
  HTREEITEM hRootItem = m_chassisTree.GetRootItem();
  if (hRootItem != pTVTipInfo->hItem)
  {
    tip = "
树结点的提示";
  }
  else
  {
    tip = "
树根上的提示";
  }
  strcpy(pTVTipInfo->pszText, (LPCTSTR) tip);
 }

 

 

 

工具栏上快速添加文字

 

m_wndToolBar.SetButtonText(0," 控制 ");
 m_wndToolBar.SetButtonText(1," 编辑 ");
 /调整工具条/
 CRect rc(0, 0, 0, 0);
 CSize sizeMax(0, 0);
 CToolBarCtrl& bar = m_wndToolBar.GetToolBarCtrl();
 //可能由于字数的不同,所以要找到一个图形最大的工具栏项
 for (int nIndex = bar.GetButtonCount() - 1; nIndex >= 0; nIndex--)
 {
  bar.GetItemRect(nIndex, rc);
  rc.NormalizeRect();
  sizeMax.cx = __max(rc.Size().cx, sizeMax.cx);
  sizeMax.cy = __max(rc.Size().cy, sizeMax.cy);
 }
 m_wndToolBar.SetSizes(sizeMax, CSize(16,15));

 

c中的陷阱

先看个题目:
void fun(char *p1)
{
p1=p1+1;}
main()
{
char *p="abcd";
fun(p);
cout<<*p;
}

这个题目看起来非常简单,但对于初学者来说却是个巨大的陷阱.很多初学者在学习c的时候就会听老师说过:指针是可以用来传值,甚至可以不要return;这句话本身没有错误,错的是初学者的理解!
首先说一下答案:结果是a不是b
这也许出乎你的意料.但是仔细一想就明白了.首先定义了一个字符型指针p,让它指向一个字符串的首地址,如果没有fun函数,那么*p='a';但是现在调用了fun,难道对p没有影响?
是的.调用fun的时候,会将p的值传给p1,也就是说现在p1也指向这个字符串的首地址了.然后执行:p1=p1+1;这样p1会指向下一个字符,也就是'b',但是p还是指向首字符!!!
所以结果也就出来了.这时候你可能会怀疑你以前学的指针是不是出现了问题.再来看这个题目,你就明白了:
void fun(char *p1)
{
*p1=*p1+2;
}
主函数不变,结果是'c'(a+2),同样p1开始指向了首地址,就当作一个箱子,然后把箱子里的东西换了(*p1),等调用结束以后,下个人(主函数)再来看的时候,箱子里的东西已经变化了!
总之,希望初学者在学习的时候多加琢磨一些关键的话语,这对你的分析能力是很有帮助的.
2.
main()
{
char p[]={'a','b','c'};
char q[]="abc";
printf("%d,%d,%d,%d",strlen(p),strlen(q),sizeof(p),sizeof(q));
}

感兴趣的读者,你能在不上机的情况下说出结果吗?
答案 是3,3,3,4.

我们知道strlen测的 是字符串的实际长度,不包括'\0',而sizeof测的是开始分配的空间.对于char q[]="abc";实际上应该是q[4],而对于char p[]={'a','b','c'};这种写法在很多C语言书上是不允许的,因为没有结束符.所以它的结果可能会出乎意料.在此不多作申明.不过如果写成char p[]={'a','b','c','\0'};那么sizeof(p)=4

3.new的问题

初学者都知道new是用来动态分配空间的,可以不用初始化,而直接赋值.如:

int *p=new int;

*p=5;//ok

但是

int *p;//只是一个地址变量,并不知道所指的空间

*p=5;//error

除了这些之外,你还知道关于它的什么吗?可能你还知道要释放.

那不释放会出现什么呢?

在<<c++程序设计教程>>(钱能著)的第14章有段很重要的话:

"c++程序的内存格局通常有4个区,

  • 全局代码区
  • 代码区
  • 栈区
  • 堆区

全局变量,静态数据,常量存放在全局数据区,所有类成员函数和非成员函数代码存放在代码区,为运行函数而分配的局部变量,函数参数,返回数据,返回地址,等存放在栈区,余下的空间都被作为堆区."

也就是说我们用new创建的空间是分配在堆区了,那堆区有什么性质呢?

"操作堆内存时,如果分配了内存,就有责任回收它,否则运行的程序会造成内存泄漏.但是在栈区,只有局部对象退出了作用域,系统自动回收.注意,对于类而言,堆对象的作用域是整个程序生命期,除非调用了delete,否则就不调用其析构函数."

如此说来,如果不用delete释放内存,就会造成内存泄漏,问题可就大了,但是有的时候并不是刚用new创建就要求析构.它的好处就是和整个程序同生命周期,那这样只要你保存了内存地址,就可以随时访问了.

最后再DELETE也不迟.

下面举出一个在MFC中非常经典的例子:

创建模态窗口与非模态是不同的,前者通常用如下代码:

Cmydlg dlg;

dlg.Domodal();

有人担心dlg是局部对象会被析构,并非如此,在执行到domodal时,程序会暂停执行,等待执行结果,传值给domodal,不过这里不是我要讲的主题.

再看看非模态窗口的创建过程:

(1)Cmydlg dlg;

dlg.Create(..);

dlg.ShowWindow();

这样的创建程序是会出错的,因为dlg获得是栈上内存,会自动析构,那关联的资源就没了.

(2)Cmydlg *dlg=new Cmydlg;

dlg->Create(..);

dlg->ShowWindow();

这种就是对的了.因为此时分配的是堆上内存,生命期为整个程序,所以不用担心内存被回收,也就不用担心窗口会显示不出了.

当然如果关闭了对话框那么这段内存就要回收了,可以用下面的代码:

void CMydlg::PostNcDestroy()

{

delete this;

}

意思就是关闭对话框后回收内存.

---------待续

人工智能的应用---四色定理

四色定理

 

地图 四色定理(Four color theorem)最先是由一位叫古德里(Francis Guthrie)的 英国大学生提出来的。德·摩尔根(Augustus De Morgan,1806~1871)1852年10月23日致 哈密顿的一封信提供了有关四色定理来源的最原始的记载。他在信中简述了自己证明四色定理的设想与感受。一个多世纪以来,数学家们为证明这条定理绞尽脑汁,所引进的概念与方法刺激了 拓扑学与图论的生长、发展。1976年 美国数学家 阿佩尔(K.Appel)与哈肯(W.Haken)宣告借助 电子计算机获得了四色定理的证明,又为用计算机证明数学定理开拓了前景。

四色问题又称 四色猜想,是世界近代三大数学难题之一。

四色问题的内容是:“任何一张 地图只用四种颜色就能使具有共同边界的国家着上不同的颜色。”用数学语言表示,即“将平面任意地细分为不相重迭的区域,每一个区域总可以用1,2,3,4这四个数字之一来标记,而不会使相邻的两个区域得到相同的数字。”

这里所指的相邻区域,是指有一整段边界是公共的。如果两个区域只相遇于一点或有限多点,就不叫相邻的。因为用相同的颜色给它们着色不会引起混淆。

四色猜想的提出来自英国。1852年,毕业于伦敦大学的弗南西斯·格思里来到一家科研单位搞地图着色工作时,发现了一种有趣的现象:“看来,每幅地图都可以用四种颜色着色,使得有共同边界的国家都被着上不同的颜色。”这个现象能不能从数学上加以严格证明呢?他和在大学读书的弟弟格里斯决心试一试。兄弟二人为证明这一问题而使用的稿纸已经堆了一大叠,可是研究工作没有进展。

1852年10月23日,他的弟弟就这个问题的证明请教了他的老师、著名数学家德·摩尔根,摩尔根也没有能找到解决这个问题的途径,于是写信向自己的好友、著名数学家汉密尔顿爵士请教。汉密尔顿接到摩尔根的信后,对四色问题进行论证。但直到1865年汉密尔顿逝世为止,问题也没有能够解决。

1872年,英国当时最著名的数学家 凯利正式向伦敦数学学会提出了这个问题,于是四色猜想成了世界数学界关注的问题。世界上许多一流的数学家都纷纷参加了四色猜想的大会战。1878~1880年两年间,著名的律师兼数学家肯普(Alfred Kempe)和泰勒(Peter Guthrie Tait)两人分别提交了证明四色猜想的论文,宣布证明了四色定理,大家都认为四色猜想从此也就解决了。

肯普的证明是这样的:首先指出如果没有一个国家包围其他国家,或没有三个以上的国家相遇于一点,这种地图就说是“正规的”(左图)。如为正规地图,否则为非正规地图(右图)。一张地图往往是由正规地图和非正规地图联系在一起,但非正规地图所需颜色种数一般不超过正规地图所需的颜色,如果有一张需要五种颜色的地图,那就是指它的正规地图是五色的,要证明四色猜想成立,只要证明不存在一张正规五色地图就足够了。

肯普是用 归谬法来证明的,大意是如果有一张正规的五色地图,就会存在一张国数最少的“极小正规五色地图”,如果极小正规五色地图中有一个国家的邻国数少于六个,就会存在一张国数较少的正规地图仍为五色的,这样一来就不会有极小五色地图的国数,也就不存在正规五色地图了。这样肯普就认为他已经证明了“四色问题”,但是后来人们发现他错了。

不过肯普的证明阐明了两个重要的概念,对以后问题的解决提供了途径。第一个概念是“构形”。他证明了在每一张正规地图中至少有一国具有两个、三个、四个或五个邻国,不存在每个国家都有六个或更多个邻国的正规地图,也就是说,由两个邻国,三个邻国、四个或五个邻国组成的一组“构形”是不可避免的,每张地图至少含有这四种构形中的一个。

肯普提出的另一个概念是“可约”性。“可约”这个词的使用是来自肯普的论证。他证明了只要五色地图中有一国具有四个邻国,就会有国数减少的五色地图。自从引入“构形”,“可约”概念后,逐步发展了检查构形以决定是否可约的一些标准方法,能够寻求可约构形的不可避免组,是证明“四色问题”的重要依据。但要证明大的构形可约,需要检查大量的细节,这是相当复杂的。

11年后,即1890年,在 牛津大学就读的年仅29岁的赫伍德以自己的精确计算指出了肯普在证明上的漏洞。他指出肯普说没有极小五色地图能有一国具有五个邻国的理由有破绽。不久,泰勒的证明也被人们否定了。人们发现他们实际上证明了一个较弱的命题——五色定理。就是说对地图着色,用五种颜色就够了。后来,越来越多的数学家虽然对此绞尽脑汁,但一无所获。于是,人们开始认识到,这个貌似容易的题目,其实是一个可与费马猜想相媲美的难题。

进入20世纪以来,科学家们对四色猜想的证明基本上是按照肯普的想法在进行。1913年,美国著名数学家、哈佛大学的伯克霍夫利用肯普的想法,结合自己新的设想;证明了某些大的构形可约。后来美国数学家富兰克林于1939年证明了22国以下的地图都可以用四色着色。1950年,有人从22国推进到35国。1960年,有人又证明了39国以下的地图可以只用四种颜色着色;随后又推进到了50国。看来这种推进仍然十分缓慢。

高速数字计算机的发明,促使更多数学家对“四色问题”的研究。从1936年就开始研究四色猜想的海克,公开宣称四色猜想可用寻找可约图形的不可避免组来证明。他的学生丢雷写了一个计算程序,海克不仅能用这程序产生的数据来证明构形可约,而且描绘可约构形的方法是从改造地图成为数学上称为“对偶”形着手。

他把每个国家的首都标出来,然后把相邻国家的首都用一条越过边界的铁路连接起来,除首都(称为顶点)及铁路(称为弧或边)外,擦掉其他所有的线,剩下的称为原图的对偶图。到了六十年代后期,海克引进一个类似于在电网络中移动电荷的方法来求构形的不可避免组。在海克的研究中第一次以颇不成熟的形式出现的“放电法”,这对以后关于不可避免组的研究是个关键,也是证明四色定理的中心要素。

电子计算机问世以后,由于演算速度迅速提高,加之人机对话的出现,大大加快了对四色猜想证明的进程。美国伊利诺大学哈肯在1970年着手改进“放电过程”,后与阿佩尔合作编制一个很好的程序。就在1976年6月,他们在美国伊利诺斯大学的两台不同的电子计算机上,用了1200个小时,作了100亿判断,终于完成了四色定理的证明,轰动了世界。

这是一百多年来吸引许多数学家与数学爱好者的大事,当两位数学家将他们的研究成果发表的时候,当地的邮局在当天发出的所有邮件上都加盖了“四色足够”的特制邮戳,以庆祝这一难题获得解决。

“四色问题”的被证明仅解决了一个历时100多年的难题,而且成为数学史上一系列新思维的起点。在“四色问题”的研究过程中,不少新的数学理论随之产生,也发展了很多数学计算技巧。如将地图的着色问题化为图论问题,丰富了图论的内容。不仅如此,“四色问题”在有效地设计航空班机日程表,设计计算机的编码程序上都起到了推动作用。

不过不少数学家并不满足于计算机取得的成就,他们认为应该有一种简捷明快的书面证明方法。直到现在,仍由不少数学家和数学爱好者在寻找更简洁的证明方法。由柳洪平创建。

 

 

 

兄弟们,好东西,大家看一看!

1. ESRI公司的ARCGIS8中文教程下载
http://bbs.gissky.net/ShowPost.asp?id=1176

2. 快速制图基本操作-ArcGIS应用案例
http://www.gissky.net/netresdetail.asp?ID=201

3. 数据的后期处理-ArcGIS应用案例
http://www.gissky.net/netresdetail.asp?ID=200

4. ArcGIS World第一、二、四期
http://share.gissky.net/esri/ArcGISWorld/

5. ArcGIS中国通讯第14、15、16期
http://share.gissky.net/esri/ArcGIS中国通讯 /

6. Modeling our World中文版
http://share.gissky.net/esri/Modeling%20our%20World中文版/

7. 第六届ArcGIS暨ERDAS中国用户大会相关资料
http://share.gissky.net/esri/ArcGIS中国用户大会资料/

8. ArcGIS8.3的安装的傻瓜式指南(图解)
http://bbs.gissky.net/ShowPost.asp?id=204

9. arcgis 9 中文环境
http://bbs.gissky.net/ShowPost.asp?id=5092

10. ArcInfo workstation命令行列表
http://www.gissky.net/netresdetail.asp?ID=188

11. ArcGIS Desktop和ArcPad的集成应用
http://www.gissky.net/netresdetail.asp?ID=215

12. Shapefile白皮书
http://www.gissky.net/netresdetail.asp?ID=233

13. Using ArcScan中文版本
http://www.gissky.net/netresdetail.asp?ID=240

14. 《arcview上机试验》
http://www.gissky.net/netresdetail.asp?ID=256

15. ArcGIS中坐标系统小议
http://www.gissky.net/gaindetail.asp?ID=22

16. 《ArcGIS 3D扩展模块学习指南》
http://www.gissky.net/gaindetail.asp?ID=9

17. 《ArcGIS地统计扩展模块学习指南》 
http://www.gissky.net/gaindetail.asp?ID=8

18. 《ArcGIS Spatial Analyst学习指南》 
http://www.gissky.net/gaindetail.asp?ID=10

19. ESRI ArcGIS网上教学资料
http://bbs.gissky.net/ShowPost.asp?id=700

 

开源GIS系统

作者:      来源:zz     发表时间:2006-06-22     浏览次数: 5237      字号:    

 

平台的对峙

  开发者都希望自己的软件能够运行在尽可能多的计算机上。然而事与愿违,摆在 GIS开发者面前的仍然是对峙的平台。J2EE随着Java5。0的发布,已经正式更名为JavaEE,而微软也正式发布了。NET2。0以及集成开发环境利器Visual Studio 2005。到底是。NET还是Java? 面对旗鼓相当的Java和。NET阵营,其实GIS平台开发商的答案早已揭晓,那就是都要!由于。NET和Java比较起来并无明显的优劣之分,只是随应用的需要和习惯的差别而略有不同,因此提供Java和。NET的双份开发接口来满足不同的需求也就不难理解了。可实际中,若要同时支持Java和。NET 谈何容易!要知道GIS的出现是上个世纪60年代的事,在当代众多IT缩写词出现之前,GIS就已经在城市规划、土地管理、军事等行业得到了应用。几十年的积累,很多代码已经成为了固化的资产。GIS平台开发商经历过二次开发语言的繁荣和凋敝,组件时代的兴起和衰落,而今又要面对平台对峙的挑战。也经历许我们应该考虑一些更好的、更彻底的解决办法,能够让我们在这个多变的时代找到相对稳定的支点。事实上,这个问题已经有了比较好的答案,办法其实也很简单,那就是重回C/C++的荣耀之都,实现GIS内核和外壳分离,以适应不断变化的外部世界。其实这个办法也不是GIS一家的专利,使用C/C++编写程序,实现一次编写,到处编译。这也是很多软件采取的跨平台策略。相比之下,无论是在Java组件和COM之间架桥还是在Linux和Windows之间修路,总显得有些不够优雅。不出意外,平台的对峙在未来的几年还会持续下去,在这对峙的平台下开发,也许最能彰显我们开发者智慧。

全球的数据

  数据是GIS的重要基石,无论何种行业应用,离开了数据都是无源之水,无本之木。空间数据不同于其他信息系统中的业务数据,能够在系统运行过程中自然产生,它需要专门的人员采集、编辑、更新,空间数据生产本身就是一个不断壮大的行业。遥感技术的飞速发展,使我们能够获得的GIS数据不断膨胀,单是这些数据的存储管理就是一个很大的问题。GIS需要管理管理的数据是全球范围的,面对如此庞大的数据,使用数据库是自然的选择。我们很难将地理信息技术和数据库分割开来,从来都没有人对使用数据库有过怀疑,问题的焦点是如何使用。应用是多样的,工程、摄影测量和其他技术或行业都有其利用空间数据的特定方式,如果我们试图把多样的需求统一到一种空间数据库的解决方案中,这种努力很可能是无益的。也许我们需要的是一种开放、灵活和可扩展的结构,能够动态适应变化。

  海量影像数据的建库和发布技术在2005年形成了一轮高潮。 GIS、遥感等领域的多家厂商都推出了各具特色的产品。现在我们可以在各种设备、各种环境下轻松浏览高分辨率的遥感影像。影像库的规模也非常庞大,几十G 的数据有时候都不好意思和人家说,建TB级影像库的大有人在。然而各种影像发布技术在Google Earth所表现出的震撼人心的效果面前都显得黯然失色,一家从事搜索的企业做出来的产品一下子让在GIS行业浸淫多年的正规军们觉得很没面子。其实, GIS企业和Google这样的网络公司有着不同的盈利模式,GIS企业可能都具备显示三维地球的技术实力,但是它们不可能仅仅是为了好看好玩来开发产品,必须要根据市场的需要,为那些真正买单的人开发产品。如果开发一个全球三维浏览的产品只是用来广告的话,那这笔广告费未免也过于昂贵了。所幸的是有 Google这样的新贵,非常大手笔地做足了遥感和GIS的科普工作,也提升了大家对地理信息系统的三维表现技术的热情。

  大众地理信息服务

  GIS业界人士一直都期望融入IT主流,让GIS走进千家万户,让GIS成为人们日常生活的一部分,就像我们每天都会收发E-mail,每天都要使用字处理软件那样。大家有这个想法不是偶然的。毕竟我们无论旅游、约会、购物,只要和出行相关,都免不了求助于地图。手机通话中使用频率最高的语句除了“喂,你好”之外,恐怕就是“你在哪里”了。大众地理信息服务就是希望为人们的日常生活提供位置信息,解决大家最常问的“在哪里”,“怎么去”的问题,这方面典型的业务就是以Google,百度,新浪本地搜索为代表的公众地图服务。在GIS开发者的眼里,也许公众地图服务技术并不复杂,有经验的开发者也许都考虑过这个颇有诱惑力的市场。事实上,公众地图服务除了要做的简单易用功能强大之外,还有数据的获取和更新途径,以及最重要的问题:如何从公众地图服务中赚钱?赢利模式是GIS企业在公众地图服务的门槛外看了又看,却始终裹足不前的根本原因。无法清楚地看到利润的增长点,而前期的高投入和大量的公关协调工作使得这项有巨大潜力的业务理所当然地被Google这样有雄厚财力和丰富IT赢利经验的大公司所占领。

  应当注意的是,大众地理信息服务的范围远非提供一个本地搜索业务,我们日常中的各种软件都可能和GIS沾点关系。比如可以在即时通信工具如MSN Messenger, QQ中增加地图聊天功能,方便网友会面;再比如在手机等移动设备上提供和位置信息相关的商业广告。这些由于都是和位置信息相关的增值业务,所以它们就有一个共同的名字——基于位置的服务(LBS)。LBS看上去市场很大,机会很多,但实际运行起来却很不容易。除了需要运营商的大力支持外,赢利模式仍然是最重要的因素。

  移动的地图

  移动开发由于其庞大的终端数量形成了一个潜在的巨大市场,并已形成了一条比较清晰的产业链。最上端是集成电路、通信器件等手机电子器件生产商,下游是应用软件开发商,而连接应用软件开发和硬件制造商的是操作系统供应商。

  和网络游戏类似,手机上的地图服务业务也主要掌握在运营商手中。如果中移动或联通有意提供这种业务,那我们对着电话大喊“喂,你在哪里”的这种“定位基本靠吼”的日子就可以一去不复返了。目前,中国移动增值业务有SMS,彩信/彩E,WAP,Java/BREW和IVR五块。移动运营商提供的服务主要集中在短信、WAP和KJAVA这三块。移动应用市场尽管在全球范围内已达到了几十亿美金,但商机周围也密布着风险,因此手机上的地图服务能否成为现实还是个未知数。

  GIS移动开发领域还包括很多其他的重要方向。比较典型和成熟的业务是野外数据采集,车载导航等和定位系统的集成应用。事实上,谈到移动GIS开发,我们就不能不说到无线定位技术。大家所熟知的GPS是一种定位手段,其实还有很多其他的定位方法。比如利用无线局域网定位,移动通信基站定位,有线电视台网也能提供位置信息。这些定位方法可以弥补GPS在室内环境中无法应用的不足,从而为移动地理信息服务业务提供了更广阔的空间。

  开源的追求

  和充满金钱气息的商业GIS开发领域相比,开源GIS的世界则显得朴素沉静,是技术爱好者的乐园。打开Source Forge网站,在下载排行榜Top10上,我们会赫然发现一个GIS门类的软件——World Wind。这个由NASA策划,用C#编写,调用微软SQL Server影像库Terrain Server来进行全球地形三维显示的软件和Google Earth非常相似。由于三维地球仿真具有强大的视觉冲击力,令人百看不厌,爱不释手,所以使得World Wind这个相当专业的软件能够跻身充斥着电驴、电骡等BT下载客户端软件的Source Forge Top10排行榜。

  不同于商业GIS软件,开源GIS软件不用背负数据兼容、易用性等问题的包袱,开发者能够集中精力于功能的开发,因此开源GIS软件普遍功能很强,技术也非常先进,其背后是来自技术狂热者和学院研究生的大力支持。开源GIS软件目前已经形成了一个比较齐全的产品线。打开www.freegis.org网站,我们会发现众多各具特色的GIS软件。老牌的综合GIS软件GRASS,数据转换库OGR、GDAL,地图投影算法库Proj4、Geotrans,也有比较简单易用的桌面软件Quantum GIS,Java平台上有Map Tools,Map Server则是优秀的开源Web GIS软件。各种空间分析,模型计算尤其是开源GIS领域的强项。动态语言如Python在开源世界中颇受宠爱,开源GIS软件也不例外,很多GIS工具都提供了Python接口,以便于系统集成。Python优雅的语法和超强的粘合能力实在是一种挡不住的诱惑。

  开源GIS世界虽然繁荣,但其影响还是很小,其身份在外人眼里看来是高深莫测的专业工具,现有的Linux发行版中也没有哪个集成了开源GIS工具。开源GIS技术虽然先进,但是缺乏良好的能够满足商用的发行版本,因此涉足开源GIS领域的多是技术爱好者和科学家,而少有商业人士问津。如果能够提供一个比较系统的、达到商用要求的开源GIS解决方案,并能获得稳定的发行版,如同Linux-Apache-MySQL-PHP那样,开源GIS前途将是不可限量。

  开放、集成、标准和互操作

  我们可以把GIS看作是一个和众多高新技术相关的综合性,交叉性的技术群。它涉及面广,牵扯的东西多,技术发展的趋势也是见仁见智。那么,这纷纭复杂现象背后的密义是什么呢?其实不难发现,GIS的要旨是开放。包括体系结构的开放,数据模型的开放,以及我们开发者思想观念的开放。只有开放,才能最大限度地提供扩展能力和灵活性,只有开放才能和应用领域充分融合,也只有开放才能让我们有更多的创新机会。和开放相应的,是GIS在应用中表现出来的重要特点 ——集成。GIS自从走出实验室,成为一项服务于信息化建设的技术工具,就没离开过各种集成。GIS可以集成到业务数据中,可以集成到数据表现中,可以集成到办公软件中…… 集成能力是GIS的活力所在,如果失去了和各种业务系统的集成能力,GIS就只能孤芳自赏,失去生命力了。那么,如何保证GIS的开放和集成能力呢?关键是标准。标准以及它的派生物互操作,是GIS行业中一个非常重要的问题。对于任何地理信息技术或市场,一致和有效的标准的使用,能够创造机会,激发创新,增加价值,缩短投资周期,并降低风险。不仅如此,标准还是控制市场的制高点,是GIS厂商的必争之地,因此我们就不难理解大家为何如此热衷于制定标准了。

  GIS发展的另一个重要趋势是从软件向服务的转变。尤其是2001年以来网格计算概念的爆发,在学术界和信息技术领域掀起了一股网格的热潮。针对网格在 GIS中的应用,有很多深入的研究和讨论。我们姑且不管伴随网格计算的那一长串难懂的IT缩写词,先看看网格之父Ian Foster对网格概念的三点说明:协同非集中管理的资源,使用标准的协议,提供高质量的服务。GIS作为一个与生俱来的分布式系统,在标准和互操作方面一直没有停止过努力。在网格热浪来临的时候,GIS业界人士也许更多地是会心一笑,因为我们心里很明了Foster所说的三个要点,其实一直都是GIS开发者追求的目标。

 

ArcGIS中坐标系统小议[转]


最先发表www.arcgisworld.com/forum和bbs.nju.edu.cn的GIS版

ArcGIS中坐标系统小议

要明确两个概念:Geographic coordinate system和projected coordinate system的区别。
1、首先理解Geographic coordinate system,Geographic coordinate system直译为地理坐标系统,是以经纬度为地图的存储单位的。很明显,Geographic coordinate system是球面坐标系统。我们要将地球上的数字化信息存放到球面坐标系统上,如何进行操作呢?地球是一个不规则的椭球,如何将数据信息以科学的方法存放到椭球上?这必然要求我们找到这样的一个椭球体。这样的椭球体具有特点:可以量化计算的。具有长半轴,短半轴,偏心率。以下几行便是Krasovsky_1940椭球及其相应参数。

Spheroid: Krasovsky_1940
Semimajor Axis: 6378245.000000000000000000
Semiminor Axis: 6356863.018773047300000000
Inverse Flattening: 298.300000000000010000

然而有了这个椭球体以后还不够,还需要一个大地基准面将这个椭球定位。在坐标系统描述中,可以看到有这么一行:

Datum: D_Beijing_1954

表示,大地基准面是D_Beijing_1954。

有了Spheroid和Datum两个基本条件,地理坐标系统便可以使用。
完整参数:
Alias: 
Abbreviation: 
Remarks: 
Angular Unit: Degree (0.017453292519943299)
Prime Meridian: Greenwich (0.000000000000000000)
Datum: D_Beijing_1954
Spheroid: Krasovsky_1940
Semimajor Axis: 6378245.000000000000000000
Semiminor Axis: 6356863.018773047300000000
Inverse Flattening: 298.300000000000010000


2、接下来便是Projection coordinate system(投影坐标系统),首先看看投影坐标系统中的一些参数。

Projection: Gauss_Kruger
Parameters:
False_Easting: 500000.000000
False_Northing: 0.000000
Central_Meridian: 117.000000
Scale_Factor: 1.000000
Latitude_Of_Origin: 0.000000
Linear Unit: Meter (1.000000)
Geographic Coordinate System: 
Name: GCS_Beijing_1954
Alias: 
Abbreviation: 
Remarks: 
Angular Unit: Degree (0.017453292519943299)
Prime Meridian: Greenwich (0.000000000000000000)
Datum: D_Beijing_1954
Spheroid: Krasovsky_1940
Semimajor Axis: 6378245.000000000000000000
Semiminor Axis: 6356863.018773047300000000
Inverse Flattening: 298.300000000000010000


从参数中可以看出,每一个投影坐标系统都必定会有Geographic Coordinate System。

投影坐标系统,实质上便是平面坐标系统,其地图单位通常为米。
那么为什么投影坐标系统中要存在坐标系统的参数呢?
这时候,又要说明一下投影的意义:将球面坐标转化为平面坐标的过程便称为投影。
好了,投影的条件就出来了:
a、球面坐标
b、转化过程(也就是算法)

也就是说,要得到投影坐标就必须得有一个“拿来”投影的球面坐标,然后才能使用算法去投影!即每一个投影坐标系统都必须要求有Geographic Coordinate System参数。

3、我们现在看到的很多教材上的对坐标系统的称呼很多,都可以归结为上述两种投影。其中包括我们常见的“非地球投影坐标系统”。):

转载于:https://www.cnblogs.com/For-her/p/3457955.html

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值