WTL for MFC Programmers 学习笔记(四) Part IV Dialogs and Controls

Part IV Dialogs and Controls

Refresher on ATL Dialogs

       ATL中有两种对话框实现:CDialogImpleCAxDialogImpl,后者在窗口作为ActiveX控件宿主的时候使用。通过三步创建新的对话框类:

1.       创建对话框资源;

2.       CDialogImpl继承新对话框子类;

3.       在新类中添加公有变量IDD,并将对话框资源ID赋值给它。

然后就可以和Frame一样添加消息处理了。

 

Control Wrapper Classes

       WTL中类名、方法名基本和MFC中的相似,所以可以参考MFC的文档来使用WTL,也可以通过F12参看源码来了解,如果喜欢的话。

       一下列出内置的控件:

1.       用户控件:CStatic, CButton, CListBox, CComboBox, CEdit, CSrollBar, CDragListBox

2.       普通控件:CImageList, CListViewCtrl(CListCtrl in MFC), CTreeViewCtrl(CTreeCtrl in MFC), CHeaderCtrl, CToolBarCtrl, CStatusBarCtrl, CTabCtrl, CToolTipCtrl, CTrackBarCtrl(CSliderCtrl in MFC), CUpDownCtrl(CSpinButtonCtrl in MFC), CProgressBarCtrl, CHotKeyCtrl, CMonthCalenderCrl, CIPAddressCtrl

3.       MFC中不存在的普通控件:CPagerCtrl, CFlatScrollBar, CLinkCtrl(可点击的超链接,XP及以后版本可用)

还有一些WTL特有的类:CBitmapButton, CCheckListViewCtrl(带复选框的List view) CTreeViewCtrlExCTreeItem(一起使用,CTreeItem是对HTREEITEM的封装), CHyperLink(可点击超链接,可在所有OS使用)

大部分的封装类都是对窗口句柄的封装,就像CWindow一样,他们封装了HWND和窗口消息(比如:CListBox::GetCurSel()是对LB_GETCURSEL的封装)。所以很容易封装一个窗体,只需要把他附加在已存在的控件中。同CWindow一样,当控件封装类销毁时控件并没有被真正销毁,除了CBitmapButton, CCheckListViewCtrl, CHyperLink

 

Create a Dialog-Based App with the AppWizard

int WINAPI _tWinMain (

    HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,

    LPTSTR lpstrCmdLine, int nCmdShow )

{

    HRESULT hRes = ::CoInitialize(NULL); // 初始化COM,如果是非ActiveX宿主,可以注释掉

 

    AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); // 封装的InitCommonControlsEx

 

    hRes = _Module.Init(NULL, hInstance);

 

    int nRet = 0;

    // BLOCK: Run application

    {

        CMainDlg dlgMain;

        nRet = dlgMain.DoModal();

    } // 重要的代码块,不能省略{},如果这样的话dlgMain的析构函数在_Module.Term()后执行,会导致异常,而且很难调试

 

    _Module.Term();

    ::CoUninitialize();  //如果是非ActiveX宿主,可以注释掉

    return nRet;

}

       在对话框中增加一个CListViewCtrl, CEditor,由于使用了list view control,需要改变AtlInitCommonControls的调用:AtlInitCommonControls(ICC_WIN95_CLASSES);这种方法注册了比实际需要的更多的类,免去了当需要增加一种类型控件时需要增加ICC_开头的常量的困扰。

 

Using the Control Wrapper Classer

       有很多种方法使成员变量与控件(感觉是控件资源)关联。有些直接用窗口对象类CWindows(或者其他窗体接口类,如CListViewCtrl),有些用CWindowImpl派生类。如果仅需要窗体对象的临时变量的话用CWindow,但用CWindowImpl的话可以子类化控件并处理控件的消息。

 

ATL Way 1- Attaching a CWindow

       声明一个CWindow或其他窗体接口类,然后调用Attach的方法、或者用带参构造函数的方法、或者用赋值运算符来将变量与控件句柄HWND关联。

HWND hwndList = GetDlgItem(IDC_LIST);

CListViewCtrl wndList1(hwndList); // use constructor

CListViewCtrl wndList2, wndList3;

wndList2.Attach(hwndList); // use Attach method

wndList3 = hwndList;    // use assignment operator

       注意:CWindow的析构函数不会销毁控件句柄,所以不需要在离开作用域的时候detach。当然如果需要可以将变量作为成员变量,然后在OnInitDialog的时候绑定attach

 

ATL Way 2 – CcontainedWindow

       采用CContainedWindow是介于CWindowCWindowImpl之间的一种方法。这种方法通过子类化控件并且将控件消息处理放在父窗体来实现。这样就将所有消息处理放在对话框中,而不需要为控件编写独立的CWindowImpl派生类。注意:不能用CContainedWindow方法处理控件的WM_COMMANDWM_NOTIFY消息,因为这些消息总是发送到控件的父窗体的。

       实际上使用的是CContainedWindowT模板类,CContainedWindow的定义如下

Typedef CContainedWindowT<CWindow> CContainedWindow

       如果需要使用其他窗体接口类只需要修改模板参数,如:CContainedWindowT<CListViewCtrl>

       如何和CContainedWindow挂钩呢?步骤如下:

1.       在对话框中创建CContainedWindowT成员变量;

2.       将控件的消息处理放在对话框消息映射的ATL_MSG_MAP区中;

3.       在对话框构造的时候,调用CContainedWindowT构造函数,设置需要路由的ALT_MSG_MAP

4.       OnInitDialog中调用CContainedWindowT::SubclassWindwo()将变量与控件关联。

class CMainDlg : public CDialogImpl<CMainDlg>

{

// ...

protected:

    CContainedWindow m_wndOKBtn, m_wndExitBtn;

};

class CMainDlg : public CDialogImpl<CMainDlg>

{

public:

    BEGIN_MSG_MAP_EX(CMainDlg)

        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)

        COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)

        COMMAND_ID_HANDLER(IDOK, OnOK)

        COMMAND_ID_HANDLER(IDCANCEL, OnCancel)

    ALT_MSG_MAP(1)

        MSG_WM_SETCURSOR(OnSetCursor_OK)

    ALT_MSG_MAP(2)

        MSG_WM_SETCURSOR(OnSetCursor_Exit)

    END_MSG_MAP()

 

    LRESULT OnSetCursor_OK(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);

    LRESULT OnSetCursor_Exit(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);

};

CMainDlg::CMainDlg() : m_wndOKBtn(this,  // CMessageMap* 消息路由处

1),  // 哪个ALT_MSG_MAP

                       m_wndExitBtn(this, 2)

{

}

       【提示】如果使用WTL7.0/7.1,会出现断言错误,在CWindowImplCDialogImpl派生类满足如下情况时:

1.       消息映射采用BEGIN_MSG_MAP 而不是BEGIN_MSG_MAP_EX

2.       消息映射中有ALT_MSG_MAP

3.       CContainedWindowT变量将消息路由到ALT_MSG_MAP

4.       ALT_MSG_MAP区中使用新WTL消息处理宏(如:MSG_开头)

解决办法是:使用BEGIN_MSG_MAP_EX代替BEGIN_MSG_MAP

       最后一部,绑定:

LRESULT CMainDlg::OnInitDialog(...)

{

// ...

    // Attach CContainedWindows to OK and Exit buttons

    m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );

    m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );

 

    return TRUE;

}

LRESULT CMainDlg::OnSetCursor_OK (

    HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )

{

static HCURSOR hcur = LoadCursor ( NULL, IDC_HAND );

 

    if ( NULL != hcur )

        {

        SetCursor ( hcur );

        return TRUE;

        }

    else

        {

        SetMsgHandled(false);

        return FALSE;

        }

}

      控件消息处理方法:

LRESULT CMainDlg::OnSetCursor_Exit (

    HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )

{

static HCURSOR hcur = LoadCursor ( NULL, IDC_NO );

 

    if ( NULL != hcur )

        {

        SetCursor ( hcur );

        return TRUE;

        }

    else

        {

        SetMsgHandled(false);

        return FALSE;

        }

}

       如果想使用CButton的特性,可以将变量声明改为:

       CContainedWindowT<CButton> m_wndOKBtn;

 

ATL Way 3 – Subclassing

       创建CWindowImpl派生类并子类化控件。方法同Way2,但是消息处理都在派生类中实现,而不是在对话框中完成。

class CButtonImpl : public CWindowImpl<CButtonImpl, CButton>

{

    BEGIN_MSG_MAP_EX(CButtonImpl)

        MSG_WM_SETCURSOR(OnSetCursor)

    END_MSG_MAP()

 

    LRESULT OnSetCursor(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg)

    {

    static HCURSOR hcur = LoadCursor ( NULL, IDC_SIZEALL );

 

        if ( NULL != hcur )

            {

            SetCursor ( hcur );

            return TRUE;

            }

        else

            {

            SetMsgHandled(false);

            return FALSE;

            }

    }

};

class CMainDlg : public CDialogImpl<CMainDlg>

{

// ...

protected:

    CContainedWindow m_wndOKBtn, m_wndExitBtn;

    CButtonImpl m_wndAboutBtn;

};

LRESULT CMainDlg::OnInitDialog(...)

{

// ...

    // Attach CContainedWindows to OK and Exit buttons

    m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );

    m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );

 

    // CButtonImpl: subclass the About button

    m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );

 

    return TRUE;

}

 

WTL Way 1 – DDX_CONTROL

       WTLDDX(对话框数据交换)的支持和MFC类似,可以很方便的将变量和控件关联。首先,需要CWindowImpl派生类,本例中会子类化edit control,实现了新的CEditImpl。同时要使用DDX,需要在stdafx.h#include atlddx.h

       让对话框拥有DDX功能,需要继承自CWinDataExchange

Class CMainDlg : public CDialogImpl<CMainDlg>

                            Public CWinDataExchange<CMainDlg>

{ … }

       下一步创建DDX映射。对于不同类型的数据存在响应的DDX_*宏实现数据映射,这里我们用DDX_CONTROL来将变量和控件关联。

Class CEditImpl : public CWindowImpl<CEditImpl, CEdit>

{

       BEGIN_MSG_MAP_EX(CEditImpl)

              MSG_WM_CONTEXTMENU(OnContextMenu)

       END_MSG_MAP()

 

       Void OnContextMenu(HWND hwndCtrl, CPoint ptClick)

{

              MessageBox(“Edit control handled WM_CONTEXTMENU”);

}

};

Class CMainDlg : public CWindowImpl<CMainDlg>,

                            Public CWinDataExchange<CMainDlg>

{

// …

       BEGIN_DDX_MAP(CMinDlg)

              DDX_CONTROL(IDC_EDIT, m_wndEdit)

       END_DDX_MAP()

 

Protected:

       CContainedWindow m_wndOKBtn, m_wndExitBtn;

       CButtonImpl m_wndAboutBtn;

       CEditImpl m_wndEdit;

};

       最后,在OnInitDialog中调用从CWinDataExchange中继承的DoDataExchange方法。当DoDataExchange第一次被调用的时候,会实现必要的控件子类化(由DDX映射决定)。

LRESULT CMainDlg::OnInitDialog(...)

{

// ...

    // Attach CContainedWindows to OK and Exit buttons

    m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );

    m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );

 

    // CButtonImpl: subclass the About button

    m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );

 

    // First DDX call, hooks up variables to controls.

    DoDataExchange(false);

 

    return TRUE;

}

      

WTL Way 2 – DDX_CONTROL_HANDLE

       DDX_CONTROL_HANDLE宏是WTL7.1的新增特征。在WTL7.0DDX_CONTROL只能绑定CWindowImpl派生类,其他扁平窗体接口类如CWindow,CListViewCtrl不能使用该宏进行绑定。DDX_CONTROL_HANDLE没有这样的限制。

       如果还在使用WTL7.0,可以通过以下宏来实现DDX_CONTROL对非CWindowImpl派生类的通用(本质上是对扁平窗体接口类封装成CWindowImpl子类):

#define DDX_CONTROL_IMPL(x) /

    class x##_ddx : public CWindowImpl<x##_ddx, x> /

        { public: DECLARE_EMPTY_MSG_MAP() };

       对需要绑定的类行使用该宏:

DDX_CONTROL_IMPL(CListViewCtrl)

       这样就有了一个CListViewCtrl_ddx类来代替CListViewCtrl功能,但是是可以被DDX_CONTROL接受。

 

More on DDX

       当然,DDX实际上完成的是数据交换。WTL支持在编辑框和字符串变量件进行字符串数据交换。同样也可以通过解析字符串为数值,然后进行整型、浮点型数值交换。还支持对复选框或单选按钮组状态值交换。

 

DDX macros

       每个DDX宏都解释为CWinDataExchange方法来完成数据交换。这些宏具有相同的格式:DDX_FOO(controlID, variable)。每个宏支持不同数据类型变量。

DDX_TEXT CString(需要define_WTL_USE_CSTRING,LPTSTR, BSTR, CComBSTR, or statically-acclocated character array。不支持堆字符串

DDX_INT int

DDX_UINT unsigned int

DDX_FLOAT float or double

DDX_CHECK 复选框状态 int or bool (int 0,1,and2 对应 BST_UNCHECKED, BST_CHECKED, BST_INDETERMINATE) (bool WTL7.1开始添加,表示没有中间状态的复选框,原中间状态返回false)

DDX_RADIO 单选按钮组 int

DDX_FLOAT_P(controlID, variable, precision) WTL7.1版本增加,指定浮点精度

       【注意】出于优化考虑,对浮点数交换默认是不启用的。需要使用DDX_FLOATDDX_FLOAT_P的话需要在stdafx.h中:

#define _ATL_USE_DDX_FLOAT

      

More about DoDataExchange()

       DoDataExchange方法和MFCUpdateData()功能一样,原型:

BOOL DoDataExchange ( BOOL bSaveAndValidate = FALSE,  // true表示保存数据同DDX_SAVE,从控件到数据变量;false表示刷新数据同DDX_LOAD,从变量到控件。

                      UINT nCtlID = (UINT)-1 ); // 指定需要交换的控件,-1表示所有控件

       数据交换成功返回true,否则返回false。对于出错情况,可以重写两个错误处理方法:一个是OnDataExchangeError(),出现任何数据交换错误时调用,默认实现发出警告音并将焦点定位在出错控件。另一个是OnDataValidateError()这将在介绍DDV时介绍。

class CMainDlg : public ...

{

//...

    BEGIN_DDX_MAP(CMainDlg)

        DDX_CONTROL(IDC_EDIT, m_wndEdit)

        DDX_TEXT(IDC_EDIT, m_sEditContents)  // 可输入文本、数值(其实就是文本)

        DDX_INT(IDC_EDIT, m_nEditNumber) // 必须输入整型

              DDX_CHECK(IDC_SHOW_MSG, m_bShowMsg)

    END_DDX_MAP()

 

protected:

    // DDX variables

    CString m_sEditContents;

int     m_nEditNumber;

       bool   m_bShowMsg;

};

LRESULT CMainDlg::OnOK ( UINT uCode, int nID, HWND hWndCtl )

{

CString str;

 

    // Transfer data from the controls to member variables.

    if ( !DoDataExchange(true) )

        return;

 

    m_wndList.DeleteAllItems();

 

    m_wndList.InsertItem ( 0, _T("DDX_TEXT") );

    m_wndList.SetItemText ( 0, 1, m_sEditContents );

 

    str.Format ( _T("%d"), m_nEditNumber );

    m_wndList.InsertItem ( 1, _T("DDX_INT") );

m_wndList.SetItemText ( 1, 1, str );

 

if(m_bShowMsg)

       MessageBox(_T(“DDX complete!”), _T(“ConstrolManial”), MB_ICONINFOMATION);

}

       如果输入非整型文本,就会出错,错误处理:

void CMainDlg::OnDataExchangeError ( UINT nCtrlID, BOOL bSave )

{

CString str;

 

    str.Format ( _T("DDX error during exchange with control: %u"), nCtrlID );

    MessageBox ( str, _T("ControlMania1"), MB_ICONWARNING );

    

    ::SetFocus ( GetDlgItem(nCtrlID) );

}

       增加一个复选框,来测试复选框状态交换。

 

Handling Notificaitons from Controls

       控件将通知以WM_COMMANDWM_NOTIFY消息的形式发送给父窗体,由父窗体负责处理这些消息。父窗体可以自己处理这些消息或者将消息返回给控件

 

Handling Notifications in the parent

       WM_NOTIFYWM_COMMAND发送的通知包含各种信息。WM_COMMAN的参数包含:发送消息的控件ID、控件句柄HWND、通知码。WM_NOTIFY消息参数包含同样内容,以NMHDR结构表示。这里介绍WTL中的通知消息映射宏,要使用这些宏必须在stdafx.h中引入atlcrack.h

COMMAND_HANDLER_EX(id, code, func)  指定控件、指定通知码

COMMAND_CODE_HANDLER_EX(id, func)  指定控件、任何通知码

COMMAND_ID_HANDLER_EX(code, func)  任何控件、指定通知码

COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func)  指定多个控件、任何通知码

COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func)  指定多个控件、指定通知码

示例:

COMMAND_HANDLER_EX(IDC_USERNAME, EN_CHANGE, OnUsernameChange): 处理 IDC_USERNAME.发出的EN_CHANGE通知

COMMAND_ID_HANDLER_EX(IDOK, OnOK): 处理从IDOK发出的所有通知

COMMAND_RANGE_CODE_HANDLER_EX(IDC_MONDAY, IDC_FRIDAY, BN_CLICKED, OnDayClicked): 处理IDIDC_MONDAY IDC_FRIDAY控件发出的 BN_CLICKED 通知

       对于WM_NOTIFY的消息映射宏只需把COMMAND_换成NOTIFY_即可。

       WM_COMMAND处理函数原型:

Void func(UINT uCode, int nCtrlID, HWND hwndCtrl);

       WM_NOTIFY处理函数原型:

LRESULT func(NMHDR* phdr);

 

class CMainDlg : public ...

{

    BEGIN_MSG_MAP_EX(CMainDlg)

        NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)

    END_MSG_MAP()

 

    LRESULT OnListItemchanged(NMHDR* phdr);

//...

};

LRESULT CMainDlg::OnListItemchanged ( NMHDR* phdr )

{

NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr;

int nSelItem = m_wndList.GetSelectedIndex();

CString sMsg;

 

    // If no item is selected, show "none". Otherwise, show its index.

    if ( -1 == nSelItem )

        sMsg = _T("(none)");

    else

        sMsg.Format ( _T("%d"), nSelItem );

 

    SetDlgItemText ( IDC_SEL_ITEM, sMsg );

    return 0;   // retval ignored

}

 

Reflecting Notifications

       如果是用CWindowImpl派生类实现的控件如CEditImpl,可以在这个类中处理通知消息,而不需要在父窗体中实现。这叫做通知反馈,同MFC中的消息反馈一样。不同的是在反馈过程中父窗体和控件都参与了,而MFC中只有控件参与。

       当需要把通知反馈给控件的话,只需要在对话框的消息映射中增加REFLECT_NOTIFICATIONS()宏。

Class CMainDlg : public …

{

Public:

       BEGIN_MSG_MAP_EX(CMainDlg)

              NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)

              REFLECT_NOTIFICATIONS()

       END_MSG_MAP()

};

       REFLECT_NOTIFICATIONS增加了一些处理所有未经处理的通知消息的代码。根据消息对应的HWND将消息发送到该窗体。消息的值不再是WM_开头的了,而是改成具有相同消息反射系统的OLE控件消息,以OCM_开头,处理方式同非反射消息一致。

       总共有18种反射消息:

控件通知:WM_COMMAND, WM_NOTIFY, WM_PARENTNOTIFY

自绘消息:WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM, WM_DELETEITEM

列表框键盘消息:WM_VKEYTOITEM, WM_CHARTOITEM

其他:WM_HSCROLL, WM_VSCROLL, WM_CTLCOLOR

       在控件类中,添加需要处理的反射消息映射,并在最后加上DEFAULT_REFLECTION_HANDLER()宏。这个宏把未处理的反射消息交由DefWindowProc()处理。下面看一个按钮自绘的例子:

Class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>

{

Public:

       BEGIN_MSG_MAP_EX(CODButtonImpl)

              MSG_OCM_DRAWITEM(OnDrawItem)

              DEFAULT_REFLECTION_HANDLER()

       END_MSG_MAP()

 

       Void OnDrawItem(UINT idCtrl, LPDRAWITEMSTRUCT lpdis)

       {

              // do drawing here..

       }

};

WTL macros for handling reflected messages

       上例中一个WTL反射消息MSG_OCM_DRAWITEM,还有17个以MSG_OCM_开头的可以被反馈的消息宏。因为WM_NOTIFYWM_COMMAND需要将参数解析,WTL为他们提供了特殊的宏,这些宏很像COMMAND_HANDLER_EXNOTIFY_HANDLER_EX,但是都加上了REFLECTED_前缀。如:

Class CMyTreeCtrl, : public CWindowImpl<CMyTreeCtrl, CTreeViewCtrl>

{

Public:

       BEGIN_MSG_MAP_EX(CMyTreeCtrl)

              REFLECTED_NOTIFY_CODE_HANDLER_EX(TVN_ITEMEXPANDING, OnItemExpanding)

              DEFAULT_REFLECTION_HANDLER()

       END_MSG_MAP()

 

       LRESULT OnItemExpanding(NMHDR* phdr);

};

LRESULT CBuffyTreeCtrl::OnItemExpanding(NMHDR* phdr)

{

       NMTREEVIEW* pnmtv = (NMTREEVIEW*)phdr;

       If(pnmtv->action & TVE_COLLAPSE)

              Return TRUE; // don’t allow it

       Else

              Return FLASE; // allow it

}

 

Dialog Fonts

       对话框中默认的字体是Sans Serif而不是Tahoma,在VC6中如果需要修改的话只能修改资源文件,需要修改三个地方:

1.       对话框类型:DIALOG -> DIALOGEX

2.       窗体类型:增加DS_SHELLFONT

3.       对话框字体:将MS Sans Serif改为MS Shell Dlg

但是不幸的是每次修改资源并保存的时候前两项都会被还原。

       【改前】

IDD_ABOUTBOX DIALOG DISCARDABLE  0, 0, 187, 102

STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "About"

FONT 8, "MS Sans Serif"

BEGIN

  ...

END

       【改后】

IDD_ABOUTBOX DIALOGEX DISCARDABLE  0, 0, 187, 102

STYLE DS_SHELLFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "About"

FONT 8, "MS Shell Dlg"

BEGIN

  ...

END

       VC7中可以方便的通过对话框属性修改字体,当把Use System Font改为True的时候编辑器将字体改变。

_ATL_MIN_CRT

       ATL存在一个优化属性,可以创建一个不需要连接C运行时库的应用,只需要在预处理设置中增加_ATL_MIN_CRT选项就行。向导生成的工程中设置了该属性。当时如果在使用CStringDDX中使用了浮点特性就必须去掉该选项。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值