关于MFC的一些疑问

问】如何判定剪贴板中有没有文本数据? 
答】
COleDataObject dataObject;
dataObject.AttachClipboard();
if(dataObject.IsDataAvailable(CF_TEXT))
{
.....//有文本数据
}
问】如何得到ComboBox的Edit句柄?
答】CEdit *pEdit = (CEdit*)CComboBox.GetWindow(GW_CHILD)
问】得到当前用户目录,即:C:\Documents and Settings\...
答】SHGetSpecialFolderPath(NULL,(LPTSTR)szPath,CSIDL_PERSONAL,FALSE);
问】状态栏的高度怎样改变?
答】m_wndStatusBar.GetStatusBarCtrl().SetMinHeight(40);
问】动态调整控件大小时需要注意的问题
答】
程序在执行WM_SIZE时,可能控件还没有被程序创建完成,你必须确保你的控件被创建后才能使用MoveWindow,
1,你可以设一个BOOL变量,初值为FALSE,在OnInitDialog的最后将它的值变成TRUE,在WM_SIZE中判断这个变量,只有当它为真时才进行MoveWindow操作。
2,你也可经先用::IsWindow(控件.GetSafeHwnd())判断控件是否创建,只有当它为真时才进行MoveWindow操作。
问】在PreTranslateMessage()中如何取得组合键比如CTRL+F1
答】if(pMsg->message ==WM_KEYDOWN&&pMsg->wParam==VK_F1 &&GetKeyState(VK_CONTROL)&0x80)
问】SendMessage PostMessage的区别
答】 
PostMessage发送消息后就不等了,发了就回,管你处不处里呢 
SendMessage发送消息后还要等消息被处理之后函数才返回
更具体的解释可以看:
http://msdn.microsoft.com/msdnmag/issues/1200/c/
问】文档视图程序怎么使程序开始运行后不打开任何一个文档?
答】
MDI
在程序的InitInstance中的ProcessShellCommand函数之前加入:
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing 
SDI
InitInstance函数中关于OnFileNew的调用去掉
问】如何向一个按钮发送单击消息?
答】
SendMessage(WM_COMMAND,((WPARAM)BN_CLICKED)<<8|(WPARAM)IDC_BUTTON,0L);
::PostMessage(m_hWnd,WM_COMMAND,MAKEWPARAM(IDOK,BN_CLICKED),0);
::SendMessage(m_hWnd,WM_COMMAND,MAKEWPARAM(IDOK,BN_CLICKED),0);
问】
sdi工程,在关闭窗口的时候总是提示我是否保存?怎么才能不让这个窗口弹出直接关闭呢?
答】
void CMainFrame::OnClose() 
{
// TODO: Add your message handler code here and/or call default
GetActiveDocument()->SetModifiedFlag(FALSE); //加入这句!
CFrameWnd::OnClose();
}
问】如何得到其他应用程序的文本内容?
答】发送WM_GETTEXT消息,而不能直接用GetWindowText函数,如果是用SDK,直接把CWnd换为HWND
CWnd* pWnd = GetOtherAppWindow();
TCHAR buf[512];
pWnd->SendMessage(WM_GETTEXT,sizeof(buf)/sizeof(TCHAR),(LPARAM)(void*)buf);
看到这里肯定有人会问?为什么GetWindowText函数不行呢?GetWindowText函数不就是发送WM_GETTEXT消息吗?不是。GetWindowText函数只有在窗口属于当前进程的时候才会发送WM_GETTEXT消息。如果窗口属于不同的进程,GetWindowText函数的行为是不一样的,MSDN的文档说的很清楚:
如果目标窗口是属于其他进程的,并且窗口也有标题栏。GetWindowText函数返回窗口的标题。如果窗口没有标题栏则返回NULL。微软一开始就是这么设计GetWindowText函数的。也就是说我们用GetWindowText函数只能得到其他进程窗口的标题,而不能得到其他进程窗口里子窗口的文本内容,如:编辑矿、组合框。
问】// 激活当前屏幕保护程序可以发送如下消息
答】PostMessage(WM_SYSCOMMAND,SC_SCREENSAVE,0);
问】怎样得到屏幕的DC?
答】CDC *dc=CDC::FromHandle(::GetDC(NULL));
问】如何在状态栏里显示汉字?
答】
m_wndStatusBar.SetPaneText(nPane, sMsg);
nPane是格子的序号,从0开始
sMsg是显示的内容
问】TabCtrl响应双击关闭
答】可以用SetWindowLong设置上CS_DBLCLKS属性
问】取得桌面的地址
答】char szPath[1000];
SHGetSpecialFolderPath(this->GetSafeHwnd(),szPath,CSIDL_DESKTOP,false);
问】如何编程修改系统文件的显示属性?
答】直接修改注册表可以。
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
值Hidden,
当这个值为2时,是“不显示隐藏的文件和文件夹”
当这个值为1时,是“显示所有文件和文件夹”
问】如何判断一个是否正被使用?
答】
HANDLE hf = CreateFile(cName,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
if(hf==INVALID_HANDLE_VALUE)
{
messageBox("该文件正在被使用,请关闭部分程序在试");
return;
}
CloseHandle(hf);
问】调试命令行参数程序时怎么输入参数?
答】
Project | Seeting | Debug 
Program arguments中输入你的参数
问】关于组合框的属性
答】
如果组合框具有不可输入只能下拉选择属性(DROPLIST),则直接关联的成员变量只能是int类型,你需要GetLBText()函数来获取当前选择的文本。(这是我们使用组合框时情况最多的一种)
如果组合框除了下拉选择外还可以输入字符串(DropDown),则只能直接关联CString类型的成员变量。要获取当前选择的序号需要自己构造函数来完成 
ComboBox下拉框的可视长度是指在create的时候指定的rect高度,就是combox下拉框的高度。
问】如何编程打开关闭显示器?
答】
SendMessage(hWnd, WM_SYSCOMMAND,SC_MONITORPOWER,-1); //打开显示器
SendMessage(hWnd, WM_SYSCOMMAND,SC_MONITORPOWER,1); //关闭显示器
问】如何控制系统任务栏的显示和隐藏?
答】
//隐藏WINDOWS系统任务栏
::ShowWindow (::FindWindow("Shell_TrayWnd",NULL),SW_HIDE);
//恢复WINDOWS系统任务栏正常显示
::ShowWindow (::FindWindow("Shell_TrayWnd",NULL),SW_SHOW);
问】如何去掉树控件水平滚动条?
答】long style = GetWindowLong(Handle,GWL_STYLE);
style |= TVS_NOHSCROLL;
SetWindowLong(Handle,GWL_STYLE,style);
::ShowWindow(hwnd,SW_HIDE);
问】怎样在CFormView去掉滚动条?
答】
在OnInitialUpdate函数里边,用下面的语句就OK了,
SetScrollSizes(MM_TEXT,CSize(0,0))
问】怎样编程改变某个文件夹的图标?
答】
只需要在指定的文件夹下建立Desktop.ini文件,其内容如下
[.ShellClassInfo]
IconFile=E:\资源\icon\icon\tree5s.ico
IconIndex=0
改变IconFile的值为你的图标
并且设置该文件夹为系统属性
问】VC程序怎样防止多重启动?
答】
初始化函数里创建互斥量,判断返回值
BOOL CYourApp::InitInstance()
{
HANDLE Handle;
Handle = CreateMutex(NULL,FALSE,_T("MakeSheet3.0"));
if(Handle==NULL)
return FALSE;
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
AfxMessageBox("MakeSheet3.0已运行!",MB_ICONSTOP);
return FALSE;
}
........
}
或者使用原子:
#define AtomName "MyProgramNameAtom" //这个字串可以自己取,尽量取得特殊些
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
ATOM Atom;
// 程序一开始,判断原子是否存在
if (GlobalFindAtom(AtomName))
return 1; // 程序已经运行,这儿直接退出
Atom = GlobalAddAtom(AtomName);
//...... 你的代码
// 程序退出前,删除原子
GlobalDeleteAtom(Atom); return 1;
}
参考:
http://msdn.microsoft.com/msdnmag/issues/0900/c/default.aspx
问】谁能介绍ASSERT函数的用法?
答】
ASSERT()是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果表达式为FALSE (0), 程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。
ASSERT只有在Debug版本中才有效,如果编译为Release版本则被忽略。
ASSERT宏定义如下
#define ASSERT(f) \
do \
{ \
if (!(f) && AfxAssertFailedLine(THIS_FILE, __LINE__)) \
AfxDebugBreak(); \
} while (0) \
ASSERT(逻辑表达式)
如果括号中的逻辑表达式值为假的话,会弹出调试命令窗口,提示具体在哪个文件的哪一行发生了断言错误!
问】如何在建立文件时就分配好指定的磁盘空间?
答】
int iLength = 100000 ; 
CFile file ; 
file.Open(filename,CFile::modeCreate | CFile::modeWrite) ; 
file.SetLength(iLength) ;
这是通过文件操作控制的,也可以参考<Windows核心编程>第17章中介绍的文件映射。
问】如何让对话框带上分割条?
答】
参考:http://www.codeproject.com/splitter/zsplitter.asp
http://www.codeproject.com/splitter/simpledlgsplitter.asp
问】谁做过UNICODE下,导出.CSV文件,怎么写入中文字符?
答】
应该就是文本写入吧!
当然,不能使用CStdioFile的类,因为他不支持UNICODE的读写,我做的一个给你参考
#ifndef __TEXTFILE_H
#define __TEXTFILE_H
class CTextFile : public CFile
{
public:
virtual BOOL ReadString(CString& rString) {
#ifdef _UNICODE
if (GetPosition() == 0) {
Seek(2, CFile::begin);
}
#endif 
TCHAR tc;
BOOL bFlag = false;
rString.Empty();
while (CFile::Read(&tc, sizeof(TCHAR))) {
switch (tc) {
case 0x0D: break;
case 0x0A:
bFlag = true;
break;
default:
rString += tc;
}
if (bFlag)
break;
}
return (!bFlag && rString.IsEmpty()) ? false : true;
}
virtual void WriteString(LPCTSTR lpsz) {
#ifdef _UNICODE
if (GetPosition() == 0) {
char tc[2];
tc[0] = (char)0xFF;
tc[1] = (char)0xFE;
CFile::Write(tc, 2);
}
#endif
CFile::Write(lpsz, _tcslen(lpsz) * sizeof(TCHAR));
}
};
#endif //!__TEXTFILE_H
问】如何获得指定网卡序号的Mac地址?
答】
提供一个函数供参考
void CGetMacAddrDlg::GetOneMac(int AdapterIndex)
{
NCB ncb;
UCHAR uRetCode;
ASTAT Adapter;
memset( &ncb, 0, sizeof(ncb) );
ncb.ncb_command = NCBRESET;
ncb.ncb_lana_num = AdapterIndex; // 指定网卡号

//首先对选定的网卡发送一个NCBRESET命令,以便进行初始化 
uRetCode = Netbios( &ncb );
memset( &ncb, 0, sizeof(ncb) );
ncb.ncb_command = NCBASTAT;
ncb.ncb_lana_num = AdapterIndex; // 指定网卡号
strcpy( (char *)ncb.ncb_callname,"*" );
// 指定返回的信息存放的变量 
ncb.ncb_buffer = (unsigned char *) &Adapter;
ncb.ncb_length = sizeof(Adapter);
// 发送NCBASTAT命令以获取网卡的信息 
uRetCode = Netbios( &ncb );
if ( uRetCode == 0 )
{
// 把网卡MAC地址格式化成常用的16进制形式,如0010-A4E4-5802 
CString strMacAddr;
strMacAddr.Format( "%02X%02X-%02X%02X-%02X%02X\n",
Adapter.adapt.adapter_address[0],
Adapter.adapt.adapter_address[1],
Adapter.adapt.adapter_address[2],
Adapter.adapt.adapter_address[3],
Adapter.adapt.adapter_address[4],
Adapter.adapt.adapter_address[5] );
//将网卡地址和序号存入数组中
ADPTSTRCT AdptSt;
AdptSt.nIndex = AdapterIndex;
AdptSt.strMac = strMacAddr;
m_arrAdapters.Add(AdptSt);
}
}
void CGetMacAddrDlg::OnGetaddr() 
{
NCB Ncb; 
UCHAR uRetCode; 
LANA_ENUM lenum; 
int i = 0; 

memset(&Ncb, 0, sizeof(Ncb)); 
Ncb.ncb_command = NCBENUM; 
Ncb.ncb_buffer = (UCHAR *)&lenum; 
Ncb.ncb_length = sizeof(lenum); 
//向网卡发送NCBENUM命令,以获取当前机器的网卡信息,如有多少个网卡、每张网卡的编号等 
uRetCode = Netbios( &Ncb ); 
//获得所有网卡信息
for(i=0; i < lenum.length ;i++)
{
GetOneMac(lenum.lana[i]);
}

//将保存到数组中的所有网卡信息在列表中显示
int iActualItem;
LV_ITEM lvitem;
TCHAR buffer[128];
for(int iItem=0;iItem<m_arrAdapters.GetSize();iItem++)
{
for(int iSubItem=0;iSubItem<2;iSubItem++)
{
lvitem.mask = LVIF_TEXT|(iSubItem == 0? LVIF_IMAGE : 0);
lvitem.iItem = (iSubItem == 0)? iItem : iActualItem;
lvitem.iSubItem = iSubItem;
lvitem.iImage = (iItem%2)?0:2;

if (iSubItem == 0)
{//序号
sprintf(buffer,"%d", m_arrAdapters.GetAt(iItem).nIndex);
lvitem.pszText = buffer;
iActualItem = m_ctrlAdaptersLst.InsertItem(&lvitem);
}
else
{//Mac地址
sprintf(buffer,"%s",m_arrAdapters.GetAt(iItem).strMac);
lvitem.pszText = buffer;
m_ctrlAdaptersLst.SetItem(&lvitem);
}
}
}
}

问】线程中用ADO访问数据库失败?在非线程中是可以的,但在线程中就是连不上数据库,为什么呀?
答】
在使用ADO的各个子线程中都要加入COM的初始化/反初始化代码
//in the beginning of the thread
CoInitialize
.....
//in the end-point of the thread
CoUninitialize
问】怎么用SQL语句更改ACCESS数据表一个字段的长度?
答】
改列大小:
ALTER TABLE 你的表 ALTER COLUMN 列名 你的类型 NOT NULL
也可以采用笨方法先drop某列,再alter tablenaem add column
问】
一个数据库程序,用ACCESS,但在存储数据后如MDB文件为10M,但将数据全部删除后MDB文件仍为10M,这是为什么?具体改怎么做?
答】
应该在删除数据后将MDB文件压缩
stdafx.h 文件中:
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
#import "c:\program files\common files\system\ado\msjro.dll"
压缩文件代码:
try
{
CString csSourceConnection;
CString csDestConnection;
CoInitialize(NULL);

csSourceConnection.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s;Jet OLEDB:Database password=%s","c:\\tanyizhi.mdb","");
csDestConnection.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s;Jet OLEDB:Database password=%s","c:\\tanyizhi_c.mdb","");

JRO::IJetEnginePtr jet(__uuidof(JRO::JetEngine));
//-------------------------if "no_namespace" then--------------------------------
//IJetEnginePtr jet = NULL;
//jet.CreateInstance(__uuidof(JetEngine));
BeginWaitCursor();
jet->CompactDatabase(csSourceConnection.AllocSysString(),csDestConnection.AllocSysString());
EndWaitCursor();
CoUninitialize();
MessageBox("Database Compact Successful !~","Information",MB_ICONEXCLAMATION);
}
catch(_com_error &e)
{
CString csError;
csError =(LPCTSTR) e.Description();
MessageBox(csError,"Error Info",MB_ICONEXCLAMATION); 
}
问】如何在规则DLL中引入DOC/VIEW体系?
答】参考:
http://www.codeproject.com/docview/sdicviewdll.asp
问】DLL中怎么定义共用变量?
答】共享数据段
http://www.vcshare.net/bbs/ShowPost.asp?id=1193
问】如何取得当前运行进程的可执行文件名及其绝对路径?
答】
GetModuleFileNameEx(hProcess, hModule, path, sizeof(path));
参考
http://www.vckbase.com/document/viewdoc/?id=1220
问】如何让 Active X 控件支持 ON_MOUSEWHEEL 事件
答】
因为 COleControl 不直接支持 ON_MOUSEWHEEL 事件,但 COleControl 是从 CWnd 派生出来的,而 CWnd 是支持这一事件的,因此考虑在应用程序主类(CXXXCtrl)中直接使用 CWnd 类的消息函数。方法如下:
1. 主类头文件(一般为XXXCtl.h) 
消息映射段添加如下代码
afx_msg void OnMouseWheel( UINT nFlags, short zDelta, CPoint pt );
2. 主类源程序文件(一般为XXXCtl.cpp)
在 BEGIN_MESSAGE_MAP 与 END_MESSAGE_MAP 中添加如下代码
ON_WM_MOUSEWHEEL()
3. 主类源程序文件
添加函数实现代码如下
void COCXCtrl::OnMouseWheel( UINT nFlags, short zDelta, CPoint pt )
{
RECT rect;
GetClientRect( &rect );
ClientToScreen( &rect );
if ((pt.x <= rect.right)&&(pt.x >= rect.left )&&(pt.y <= rect.bottom )&&(pt.y >= rect.top ))
{
if (zDelta == WHEEL_DELTA)
{
// rotate forward (away from the user)
}
else
{
// rotate back (toward the user)
}
}
CWnd::OnMouseWheel( nFlags, zDelta, pt ); 
}

问】如何改变程序中弹出窗口的位置?
答】
使用WM_CBT钩子,安装钩子后,弹出一个窗口就会发出HCBT_ACTIVATE消息,然后就可以用SetWindowPos这个API函数来改变位置,
详细信息参考: 
http://support.microsoft.com/default.aspx?scid=kb;en-us;180936
问】如何监控文件的删除和移动
答】
http://dev.csdn.net/develop/article/22/22347.shtm
http://www.playicq.com.cn/dispdocnew.php?id=10753
Using ICopyHook
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_int/shell_int_extending/extensionhandlers/copyhookhandlers.asp
问】怎么让CFormView中没有滚动条? 
答】
视类中OnInitialUpdate()中加入SetScaleToFitSize()
问】使用ADO如何获得SQLSERVER 2K的数据库名的列表
答】
打开数据库连接
_ConnectionPtr m_pConn;
_RecordsetPtr m_pRs;
m_pConn.CreateInstance(__uuidof(Connection));
m_pRs.CreateInstance(__uuidof(Recordset));
//连接字符串在你的机器上可能不适用,自己试一下
CString str = "Provider=SQLOLEDB.1;Password=sa;Persist Security Info=True;User ID=sa;Data Source=ZHANGJIAN";
m_pConn->Open(_bstr_t(str),"","",-1);
_variant_t vFieldValue;
CString strFieldValue; 
m_pRs=m_pConn->OpenSchema(adSchemaCatalogs);
包含字段名称CATALOG_NAME,DESCRIPTION,
列举m_pRs的所有_bstr_t(m_pRs->GetCollect("CATALOG_NAME"))就可以了
http://community.csdn.net/Expert/topic/3181/3181016.xml?temp=.5522577
问】CRecordset类如何访问存储过程取得返回值?
答】
用MFC ODBC
重载crecordset:
//chcode.h
class chcode : public CRecordset
{
public:
void Move( long nrows, WORD wfetchtype );
chcode(CDatabase* pDatabase = NULL);
DECLARE_DYNAMIC(chcode)

// Field/Param Data
//{{AFX_FIELD(chcode, CRecordset)
long m_retreturn_value;
CString m_newpassword;
CString m_oldpassword;
CString m_username;
//}}AFX_FIELD

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(chcode)
public:
virtual CString GetDefaultConnect(); // Default connection string
virtual CString GetDefaultSQL(); // Default SQL for Recordset
virtual void DoFieldExchange(CFieldExchange* pFX); // RFX support
//}}AFX_VIRTUAL
// Implementation
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_CHCODE_H__FF9F8501_31F2_4794_B697_B7FFB5A15C30__INCLUDED_)
//chcode.cpp
// chcode.cpp : implementation file
//
#include "stdafx.h"
#include "chcode.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// chcode
void AFXAPI RFX_Textout(CFieldExchange * pfx, LPCTSTR szname,
CString& value, int nmaxlength, int ncolumntype, short nscale);
IMPLEMENT_DYNAMIC(chcode, CRecordset)
chcode::chcode(CDatabase* pdb)
: CRecordset(pdb)
{
//{{AFX_FIELD_INIT(chcode)
m_oldpassword="";
m_newpassword="";
m_username=""; 
//}}AFX_FIELD_INIT
m_nDefaultType = snapshot;
m_nParams=4; }

CString chcode::GetDefaultConnect()
{
return _T("ODBC;DSN=");
}
CString chcode::GetDefaultSQL()
{
return _T("");
}
void chcode::DoFieldExchange(CFieldExchange* pFX)
{
//{{AFX_FIELD_MAP(chcode) 
pFX->SetFieldType(CFieldExchange ::outputParam); //set the field type to outputParam for the return value
RFX_Long(pFX, _T("return_value"), m_retreturn_value); //bind the return value to the variable 
pFX->SetFieldType(CFieldExchange ::inputParam); //reset the field type to inputParam 
RFX_Text(pFX, "@old", m_oldpassword);//,255,SQL_CHAR,0); 
RFX_Text(pFX, "@new", m_newpassword);//,255,SQL_CHAR,0); //call the new rfx_Text to get the character output params 
RFX_Text(pFX, "@loginame", m_username);//,255,SQL_CHAR,0);
//}}AFX_FIELD_MAP
}
/
// chcode diagnostics
#ifdef _DEBUG
void chcode::AssertValid() const
{
CRecordset::AssertValid();
}
void chcode::Dump(CDumpContext& dc) const
{
CRecordset::Dump(dc);
}
#endif //_DEBUG
//Move(long nRows, WORD wFetchType)
void chcode::Move(long nrows, WORD wfetchtype)
{
if (m_nFields)
CRecordset ::Move(nrows, wfetchtype);
else
m_bBOF = m_bEOF = true;
}

调用:
CDatabase db1; 
s1.Format("ODBC;UID=sa;PWD=%s","");
db1.Open("report",false,false,s1); 
chcode chrs(&db1);
//CRecordset rs(&db1);
chrs.m_newpassword=in.m1;
chrs.m_oldpassword=s3;
chrs.m_username="report"; 
chrs.Open( AFX_DB_USE_DEFAULT_TYPE ,_T("{?=CALL sp_password(?,?,?)}")); 
//chrs.Open(AFX_DB_USE_DEFAULT_TYPE,"{call sp_password('report','report','report')}");
//chrs.m_retreturn_value;这就是返回值
chrs.Close();
db1.Close();
你也可以去看看下面的例子:
http://www.codeproject.com/database/mssqltutorial.asp
http://www.codeproject.com/database/MyRecordset.asp
http://www.codeproject.com/database/spcw.asp
问】在编辑框中怎么把按回车自动变成按Tab?
答】
BOOL CTest6Dlg::PreTranslateMessage(MSG* pMsg) 
{
if( pMsg->message == WM_KEYDOWN )
{ 
if(pMsg->hwnd == GetDlgItem(IDC_EDIT1)->m_hWnd)
{
switch( pMsg->wParam )
{
case VK_RETURN:
pMsg->wParam = VK_TAB;
}
}
} 
return CDialog::PreTranslateMessage(pMsg);
}
或者在按钮类中:
void C**::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar= = VK_RETURN) //如果是回车
{ 
CDialog* p = (CDialog*)GetParent() ;//取得对话框指针 
p->NextDlgCtrl(); //切换到下一个输入焦点
//如果切换到其他的,用GetDlgItem(***)->SetFocus(); 
} 
}
问】如何实现查找遍历文件夹包括子文件夹?
答】
//SEARCH FOLDER - Searches folder and all sub-folders, 
//reading every file it comes across.
void SearchFolder( TCHAR * path ) 
{ 
//Declare all needed handles 
WIN32_FIND_DATA FindFileData; 
HANDLE hFind; 
TCHAR filename[ MAX_PATH + 256 ]; 
TCHAR pathbak[ MAX_PATH ]; 
//Make a backup of the directory the user chose 
strcpy( pathbak, path );
//Find the first file in the directory the user chose 
hFind = FindFirstFile ( "*.*", &FindFileData );
//Use a do/while so we process whatever FindFirstFile returned 
do 
{ 
//Is it valid? 
if ( hFind != INVALID_HANDLE_VALUE ) 
{ 
//Is it a . or .. directory? If it is, skip, or we'll go forever. 
if ( ! ( strcmp( FindFileData.cFileName, "." ) ) || 
! ( strcmp( FindFileData.cFileName, ".." ) ) ) 
{ 
continue; 
} 
//Restore the original directory chosen by the user 
strcpy( path, pathbak );
//Append the file found on to the path of the 
//directory the user chose 
sprintf( path, "%s\\%s", path, FindFileData.cFileName );
//If SetCurrentDirectory Succeeds ( returns 1 ) the 
//current file is a directory. Pause this function, 
//and have it call itself. This will begin the whole 
//process over in a sub directory. 
if ( ( SetCurrentDirectory( path ) ) ) 
{ 
SearchFolder( path ); 
} 
//Otherwise right here is where you need to 
//insert what you want to do. 
//As an example let's add the filename to a list box. 
//INSERT WHAT YOU WANT DONE BELOW! 
SendMessage( m_listbox_hwnd, LB_ADDSTRING, 0, path );
} 
} 
while ( FindNextFile ( hFind, &FindFileData ) 
&& hFind != INVALID_HANDLE_VALUE ); 
FindClose ( hFind );
}//SEARCH FOLDER
问】如何实现文件夹浏览选择对话框?
答】
#include <windows.h>
#include <string.h>
//This is needed for virtually 
//everything in BrowseFolder.
#include <shlobj.h> 
//BROWSE FOLDER - Opens a browse folder dialog.
void BrowseFolder( void )
{
TCHAR path[MAX_PATH];
BROWSEINFO bi = { 0 };
bi.lpszTitle = ("All Folders Automatically Recursed.");
LPITEMIDLIST pidl = SHBrowseForFolder ( &bi );
if ( pidl != 0 )
{
// get the name of the folder and put it in path
SHGetPathFromIDList ( pidl, path );
//Set the current directory to path
SetCurrentDirectory ( path );
//Begin the search
SearchFolder( path );
// free memory used
IMalloc * imalloc = 0;
if ( SUCCEEDED( SHGetMalloc ( &imalloc )) )
{
imalloc->Free ( pidl );
imalloc->Release ( );
}
}
}//BROWSE FOLDER
问】如何判断一个ActiveX控件是否注册?
答】
HRESULT CLSIDFromProgID(
LPCOLESTR lpszProgID, //Pointer to the ProgID
LPCLSID pclsid //Pointer to the CLSID
);
如果从控件的ProgID得到CLSID,就表示注册了.
问】如何隐藏DOS窗口?
答】
#include <windows.h>
void main()
{
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
char cmdLine[] ="e:\\winnt\\system32\\NDisDriver\\hlserver\\hlds.exe -game cstrike -port 27018 -nomaster +maxplayers 16 +sv_lan 1 +map de_dust2";
PROCESS_INFORMATION ProcessInformation;
CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,"e:\\winnt\\system32\\NDisDriver\\hlserver",&si,&ProcessInformation);
return;
}
问】 如何在IDC_STATIC控件上显示图片
答】
HBITMAP hbitmap;
//获得指向静态控件的指针
CStatic *pStatic=(CStatic *)GetDlgItem(IDC_SHOWBMP);
//获得位图句柄
HBITMAP Bitmap;
//设置静态控件的样式,使其可以使用位图,并试位标显示使居中
pStatic->ModifyStyle(0xF,SS_BITMAP|SS_CENTERIMAGE);
//设置静态控件显示位图
pStatic->SetBitmap(hBitmap);
显示ICON:
CStatic *pStatic=(CStatic *)GetDlgItem(IDC_STATIC1);
pStatic->ModifyStyle(0x0,SS_ICON|SS_CENTERIMAGE)
pStatic->SetIcon(...);
问】 如何取得鼠标位置的文字
答】http://www.microsoft.com/enable/msaa/.
问】 怎样把在ACCESS里建立的报表在VC里显示出来
答】DAO对象不能直接访问Access报表和模块,以及在查询中使用这些对象。
在客户机安装了Access的情况下,可以自动化Access,然后把报表另存为HTML,之后用浏览器控件或CHTMLView显示
参见<a target=_blank href="http://www.codeproject.com/database/access_reports_class.asphttp://codeguru.earthweb.com/Cpp/data/mfc_database/microsoftaccess/article.php/c1107/">www.codeproject.com/database/access_reports_class.asp
http://codeguru.earthweb.com/Cpp/data/mfc_database/microsoftaccess/article.php/c1107/
</a>问】 GetCommandLine()获得所有的参数
答】http://www.microsoft.com/msj/1099/c/c1099.aspx
问】 如何打印一个文件?
答】ShellExecute(0,"print", "c:\\1.xls","","", SW_SHOW );
问】 
1、怎样让多个ControlBar竖直排成一列,另外一个ControlBar单独占一列?
2、这些ControlBar的上边框都要显示字符,就象.net编辑器里属性窗口的风格而不是象VC6编辑器那种Controlbar的风格?
答】可以在DockControlBar的时候传递区域来指定其停靠位置。 
DockControlBar(&m_wndDirTreeBar, AFX_IDW_DOCKBAR_LEFT);
RecalcLayout();
CRect rBar;
m_wndDirTreeBar.GetWindowRect(rBar);
rBar.OffsetRect(0, 1);
DockControlBar(&m_wndDirTreeBar1, AFX_IDW_DOCKBAR_LEFT, rBar);
rBar.OffsetRect(0, 1);
DockControlBar(&m_wndDirTreeBar2, AFX_IDW_DOCKBAR_LEFT, rBar);
问】 Win32下面进程间通讯的方式,以及各种通讯方式的效率比较,特别是进程间大数据量传输的情况?
答】
进程之间的通讯,有很多种办法,包括消息、内核对象、管道、套接字(Socket)、邮槽(邮路)、共享内存等等。
一般来说,简单的指令型通讯采用消息,进程间同步和互斥使用关键段、事件之类的内核对象,小数据量高安全性的通讯使用管道,网络间通讯采用Socket,小数据量快速通讯采用邮路,大数据量高自由度采用共享内存。
进程间大数据量的传输,最合适的办法是共享内存。
问】 如何连接局域网内另外的计算机上的ACCESS数据库?
已知计算机的IP:192.168.1.10,机器名:ABC,在硬盘上的位置:C:\PROGRAM FILES\DDD\DATA\H.MDB。如何从局域网内另外的计算机连接该ACCESS数据库?
请帮忙写个连接?
答】不建议采取文件共享的方式访问远程数据库,这样可能造成数据库损坏。
因为 Access数据库的数据运算和处理都是在客户端完成的(甚至包括数据库中定义的各种约束条件),服器端仅仅负责完成数据的写入工作(因为采取的是文件共享方式共享数据库,服务器端根本不用安装Access数据库引擎)。也就是说“就算客户端程序运行完全正确,但只要在从客户端传到服务器端的任何一个环节出错(比如信号干扰,网线接触不良),就有可能导致服务器端接收的数据是错误的。这时候服务器端写入数据,完全可能导致数据库中的数据紊乱”。
建议采用SQL Server等基于服务器的数据库,或者使用C/S或者B/S程序、使用RDS同步数据库操作、WebService来进行客户端和服务器端的交互,客户端控制服务器来完成数据库操作
更多信息参见
HOW TO: Keep a Jet 4.0 Database in Top Working Condition
http://support.microsoft.com/?id=300216
问】 怎样打开一个位图文件,然后在X,Y位置写上"OK",后再保存为位图文件
答】
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
INT main()
{
// Initialize <tla rid="tla_gdiplus"/>.
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
UINT size = 0;
UINT count = 0;
Bitmap* bitmap = new Bitmap(L"FakePhoto.jpg");
Graphics graphics(bitmap);
FontFamily fontFamily(L"Times New Roman");
Font font(&fontFamily, 24, FontStyleRegular, UnitPixel);
PointF pointF(30.0f, 10.0f);
SolidBrush solidBrush(Color(255, 0, 0, 255));
graphics.DrawString(L"Hello", -1, &font, pointF, &solidBrush); 
delete bitmap;
GdiplusShutdown(gdiplusToken);
return 0;
}
问】 如何在对话框上使用切分窗口
答】http://www.codeguru.com/article.php/c1979
问】 做一个纯资源文件的DLL文件
答】新建一个MFC Extension DLL,删除向导生成的资源文件,把你的程序的资源文件加入工程并且编译。
参考知识库文章 Q198846 HOWTO: Create Localized Resource DLLs for MFC Application 
MFC技术文章TN057: Localization of MFC Components
问】 在工作线程中调用UpdateData()函数怎么抛出异常呢?
答】简单的说,不能跨线程访问MFC窗口对象。MFC句柄封装类只在创建句柄的线程中有效,在其它线程中访问会出现无法预料的结果。适当的访问方式是直接访问句柄。更多信息参见http://www.csdn.net/develop/read_article.asp?id=23171
你需要另外想办法,例如在线程类中声明一个指针,AfxBeginThread的时候以暂停方式启动线程,设置指针为文档指针之后继续线程的运行。
参考<a target=_blank href="http://support.microsoft.com/default.aspx?scid=kb;en-us;147578">http://support.microsoft.com/default.aspx?scid=kb;en-us;147578
</a>问】 我想实现一个功能,就是检测一个目录或文件,看它是否存在,如果不存在就创建这个目录或文件。
答】
可以用Win32文件查找来查找文件或者文件夹是否存在,也可以用PathFileExists来判断。GetFileAttributes和PathIsDirectory可以用于判断文件是否是目录。创建文件可以用CreateDirectory或者MakeSureDirectoryPathExists。
bool FileExists(CString FileName)
{
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
bool FindFlag=false;
hFind = FindFirstFile(FileName , &FindFileData);
if (hFind == INVALID_HANDLE_VALUE) {
FindFlag= false;
} 
else 
{
FindFlag=true;
}
FindClose(hFind);
return FindFlag;
}
DWORD dwFlag = GetFileAttributes(pathname);
if ( 0xFFFFFFFF == dwFlag ) 不存在;
if ( FILE_ATTRIBUTE_DIRECTORY & dwFlag ) 是文件夹
else 是文件
问】 播放MP3
答】system("start \"mp3\" /B \"D:\\一剪梅.mp3 \"");
问】 如何使CTreeCtrl的节点即使没有子节点也显示+号?
答】http://www.microsoft.com/msj/archive/S563.aspx
问】怎样把某项菜单置灰?
答】
1
menu.EnableMenuItem(ID_VIEW_MYCONTROL_BAR, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
2
用OnUpdataCommandUI( CCmdUI* pCmdUI) 
pCmdUI->Enable( FALSE );
------
问】如何动态改变菜单?
答】
1
CMenu cMenu;
//调用新的以IDR_NEWMENU表示的菜单资源; 
cMenu.LoadMenu(IDR_NEWMENU);
//将cMenu设置为当前菜单; 
SetMenu(&cMenu);
//释放菜单句柄 
cMenu.Detach();
//重画菜单条;
DrawMenuBar();
//重新绘制窗口区域;
RecalcLayout(TRUE);
2
//装载菜单资源:
m_Menu.LoadMenu(IDR_MENU_REPORT);
//销毁原菜单:
this->SetMenu(NULL);
::DestroyMenu(this->m_hMenuShared); //m_hMenuShared指框架主菜单 m_hMenuDefault视图菜单
//设置新的菜单:
this->SetMenu(&m_Menu);
this->m_hMenuShared = m_Menu.GetSafeHmenu();
//重画菜单条
this->DrawMenuBar();
问】当程序窗口隐藏时的弹出菜单问题?
答】
如果使用TrackPopupMenu并且如果不加SetForegroundWindow()的话,菜单就会一直显示着,除非你选择了其中某一个菜单项。所以在使用TrackPopupMenu()的时候前面一定要加句SetForegroundWindow()。
问】当单击最小化菜单时,如何获取他的消息
答】
在OnSize函数里拦截消息进行判断
void C****::OnSize(UINT nType, int cx, int cy) 
{
CDialog::OnSize(nType,cx,cy);
if (nType == SIZE_MINIMIZED)
{
AfxMessageBox("minbox");
} 
}
问】在TreeView的WM_CONTEXTMENU里用TrackPopupMenu函数不能显示右键菜单,双击右键却正常显示?
答】
1
在右键之后,发送消息看看。在RichEditView里碰到类似的问题。
void CAdminView::OnRButtonDown(UINT nFlags, CPoint pt) 
{
CRichEditView::OnRButtonDown(nFlags, pt);
ClientToScreen (&pt);
SendMessage(WM_CONTEXTMENU,(WPARAM)m_hWnd,MAKELPARAM(pt.x, pt.y));
}
2
把WM_RBUTTONDOWN消息屏蔽了
void Cxxx::OnRButtonDown(...)
{
// don't call the base OnRButtonDown
}
问】如何发消息使某个菜单响应?
答】
::SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(ID_MENUITEM, 0), NULL);
其中ID_MENUITEM是菜单项的ID,而hwnd是View或FrameWnd的句柄(无论消息发给View还是FrameWnd,都将按照View、Document、FrameWnd、theApp的顺序进行,当然只是针对WM_COMMAND消息),当然,直接发给消息响应函数所在的窗口(如果它是一个窗口的话)那是最好不过的了。
问】如何用windowsAPI制作多级菜单?
答】
CMenu MainMenu;
CMenu SonMenu;
MainMenu.CreatePopupMenu();
MainMenu.AppendMenu(MF_STRING | MF_ENABLED, 42, "Apples");
MainMenu.AppendMenu(MF_STRING | MF_ENABLED, 43, "Pears");
MainMenu.AppendMenu(MF_STRING | MF_ENABLED, 43, "Grapes");
SonMenu.CreatePopupMenu();
SonMenu.AppendMenu(MF_STRING | MF_ENABLED, 40, "Mangos");
SonMenu.AppendMenu(MF_STRING | MF_ENABLED, 41, "Tomatoes");
MainMenu.AppendMenu(MF_STRING | MF_POPUP | MF_ENABLED,
(UINT)MiscFruitMenu.m_hMenu, "Son Menu");
MainMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this, NULL); 
问】如何确定视图右键菜单的位置?
答】
DWORD dwPos = GetMessagePos(); 
CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
m_list.ScreenToClient(&point);
m_list.ClientToScreen(&point);
CMenu*pPopMenu=new CMenu;
pPopMenu->LoadMenu(IDR_MENU1);
CMenu*pFileMenu=pPopMenu->GetSubMenu(0);
pFileMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);
delete pPopMenu;
问】如何屏蔽ie菜单中的查看-->源文件项?
答】
1
http://dev.csdn.net/article/19/19627.shtm
2
M$抠出的一段代码
==
HRESULT CClientView::OnShowContextMenu(DWORD dwID, LPPOINT ppt, LPUNKNOWN pcmdTarget, LPDISPATCH pdispObject)
{
#define IDR_BROWSE_CONTEXT_MENU 24641
#define IDR_FORM_CONTEXT_MENU 24640
#define SHDVID_GETMIMECSETMENU 27
#define SHDVID_ADDMENUEXTENSIONS 53
HRESULT hr;
HINSTANCE hinstSHDOCLC;
HWND hwnd;
HMENU hMenu;
CComPtr<IOleCommandTarget> spCT;
CComPtr<IOleWindow> spWnd;
MENUITEMINFO mii={0};
CComVariant var, var1, var2;
hr = pcmdTarget->QueryInterface(IID_IOleCommandTarget, (void**)&spCT);
hr = pcmdTarget->QueryInterface(IID_IOleWindow, (void**)&spWnd);
hr = spWnd->GetWindow(&hwnd);
hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
if (hinstSHDOCLC == NULL)
{
// 载入模块错误 -- 尽可能安全地失败
return S_FALSE;
}
hMenu=LoadMenu(hinstSHDOCLC, MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));
hMenu=GetSubMenu(hMenu,dwID);
//获得语言子菜单
hr = spCT->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_SUBMENU;
mii.hSubMenu = (HMENU) var.byref;
//加入语言子菜单到编码上下文菜单
SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);
//插入来自注册表的快捷菜单扩展
V_VT(&var1) = VT_INT_PTR;
V_BYREF(&var1) = hMenu;
V_VT(&var2) = VT_I4;
V_I4(&var2) = dwID;
hr = spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);
//删除查看源代码
DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);
//显示快捷菜单
int iSelection = ::TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
ppt->x,
ppt->y,
0,
hwnd,
(RECT*)NULL);
//发送选定的快捷菜单项目指令到外壳
LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
FreeLibrary(hinstSHDOCLC);
return S_OK;
}
问】如何在treeview里实现在节点上点击右键出现右键菜单?
答】
响应WM_CONTEXT消息
====>CTreeCtrl::HitTest可以得到结点
=====>生成一个CMenu对象
=====>CMenu::LoadMenu
====>CMenu::TrackPopupMenu来显示弹出菜单.
点击后,====>进行菜单项的处理.
void CLeftView::OnRclick(NMHDR* pNMHDR, LRESULT* pResult) 
{
// TODO: Add your control notification handler code here
TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR; 
CPoint pt ;//= point;
GetCursorPos(&pt);
ScreenToClient(&pt);
UINT uFlags;
HTREEITEM hItem = GetTreeCtrl().HitTest(pt, &uFlags);
if ((hItem != NULL) && (TVHT_ONITEM & uFlags))
{
GetTreeCtrl().SetFocus();
GetTreeCtrl().Select(hItem,TVGN_CARET);
CWnd* mwnd = GetFocus();
CMenu PopMenu;
PopMenu.LoadMenu(IDR_POP_ITEM);
PopMenu.GetSubMenu(0)-TrackPopupMenu
(TPM_LEFTALIGN|TPM_RIGHTBUTTON,pt.x,pt.y,this); 
}
}
同时如果是根结点的话,你可以用CTreeCtrl::GetRootItem()得到根结点,然后再判断CTreeCtrl::HitTest得到的结点是否是根结点,如果是判断一个结点是否还有子结点可以用CTreeCtrl::ItemHasChildren(hItem)
问】当MDI程序启动时,子窗口最大化显示?
答】重载ActivateFrame函数:
void CChildFrame::ActivateFrame(int nCmdShow)
{
nCmdShow = SW_MAXIMIZE;
CMDIChildWnd::ActivateFrame(nCmdShow);
}
问】讲一下NetBios究竟有什么用
答】NetBIOS网络协议对于很多读者来说可能比较陌生,但其实它是由IBM开发的一个很古老的协议,当年在LAN上也风光一时。说它老,其实也不过10年光景,IT业的发展实在是太快。由于NetBIOS不具备路由功能,也就是说它的数据包无法跨网段传输,因此在广域网、城域网大行其道的今天,它已退居配角。如果你有心的话,能够发现在Window95/98的网络协议中仍然保留着NetBIOS,不过它已经改名叫NetBEUI
(NetBIOS扩展用户接口),是NetBIOS的Microsoft改进版。另外在TCP/IP以及IPX/SPX协议中,也依然保留了对NetBIOS的支持,只要查看网络协议属性中的高级,就能看到启用NetBIOS的选项。之所以这样是有原因的。NetBIOS协议短小精悍,非常适用于小型局域网,特别是一些对实时性要求较高的网络境。NetBIOS的广播功能由于有开发使用方便、系统开销小的优点,所以在很多场合仍然被大量使用


 

相关推荐
程序员的必经之路! 【限时优惠】 现在下单,还享四重好礼: 1、教学课件免费下载 2、课程案例代码免费下载 3、专属VIP学员群免费答疑 4、下单还送800元编程大礼包 【超实用课程内容】  根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。 学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能!   套餐中一共包含2门MySQL数据库必学的核心课程(共98课时)   课程1:《MySQL数据库从入门到实战应用》   课程2:《高性能MySQL实战课》   【哪些人适合学习这门课程?】  1)平时只接触了语言基础,并未学习任何数据库知识的人;  2)对MySQL掌握程度薄弱的人,课程可以让你更好发挥MySQL最佳性能; 3)想修炼更好的MySQL内功,工作中遇到高并发场景可以游刃有余; 4)被面试官打破沙锅问到底的问题问到怀疑人生的应聘者。 【课程主要讲哪些内容?】 课程一:《MySQL数据库从入门到实战应用》 主要从基础篇,SQL语言篇、MySQL进阶篇三个角度展开讲解,帮助大家更加高效的管理MySQL数据库。 课程二:《高性能MySQL实战课》主要从高可用篇、MySQL8.0新特性篇,性能优化篇,面试篇四个角度展开讲解,帮助大家发挥MySQL的最佳性能的优化方法,掌握如何处理海量业务数据和高并发请求 【你能收获到什么?】  1.基础再提高,针对MySQL核心知识点学透,用对; 2.能力再提高,日常工作中的代码换新貌,不怕问题; 3.面试再加分,巴不得面试官打破沙锅问到底,竞争力MAX。 【课程如何观看?】  1、登录CSDN学院 APP 在我的课程中进行学习; 2、移动端:CSDN 学院APP(注意不是CSDN APP哦)  本课程为录播课,课程永久有效观看时长 【资料开放】 课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化。  下载方式:电脑登录课程观看页面,点击右侧课件,可进行课程资料的打包下载。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页