Chinaunix首页 | 论坛 | 博客
  • 博客访问: 14497432
  • 博文数量: 5645
  • 博客积分: 9880
  • 博客等级: 中将
  • 技术积分: 68081
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-28 13:35
文章分类

全部博文(5645)

文章存档

2008年(5645)

我的朋友

分类:

2008-04-28 20:54:53

下载本文示例代码
  文档视图结构中,应用程序类对象实例初始操作的分析 BOOL CSomeApp::InitInstance(){Enable3dControls();LoadStdProfileSettings();AddDocTemplate(...) ...... ShowWindow(...);m_pMainWnd->DragAcceptFiles();EnableShellOpen();RegisterShellFileTypes(TRUE);CCommandLineInfo cmdInfo;ParseCommandLine(cmdInfo);if (!ProcessShellCommand(cmdInfo))return FALSE;return TRUE;}   下面对InitInstance中的一些操作及其流程进行分析   1.常规设置   如:  SetDialogBkColor()  Enable3dControls()..  (如果设置了后者,则前者就不必要了)  SetRegistryKey(指定注册表键,替代INI文件)   2.LoadStdProfileSettings()   LoadStdProfileSettings 完成最近文件列表功能,在菜单中添加最近的文件作为菜单项过程:   建立一个CRecentFileList从注册表或INI文件中读入最近文件列表;   当菜单建立时,文件列表将添加到菜单中ID_FILE_MRU_FILE*位置;   3.m_pMainWnd->DragAcceptFiles()接收文件拖入   使主窗口能响应文件拖入消息WM_DROPFILES;   当有文件拖入时, 框架窗口的OnDropFiles将处理,以打开这些文件。 void CFrameWnd::OnDropFiles(HDROP hDropInfo){SetActiveWindow(); // activate us first !UINT nFiles = ::DragQueryFile(hDropInfo, (UINT)-1, NULL, 0); CWinApp* pApp = AfxGetApp();ASSERT(pApp != NULL);for (UINT iFile = 0; iFile < nFiles; iFile ){TCHAR szFileName[_MAX_PATH];::DragQueryFile(hDropInfo, iFile, szFileName, _MAX_PATH);//应用程序打开拖入文档pApp->OpenDocumentFile(szFileName);}::DragFinish(hDropInfo);}   4.EnableShellOpen();   为在Windows中使用外壳操作打开文件作准备 void CWinApp::EnableShellOpen(){ASSERT(m_atomApp == NULL && m_atomSystemTopic == NULL); // do once m_atomApp = ::GlobalAddAtom(m_pszExeName);m_atomSystemTopic = ::GlobalAddAtom(_T("system"));}   5.RegisterShellFileTypes   向系统注册文件类型,以使用外壳操作。   将调用m_pDocManager->RegisterShellFileTypes()   (CDocManager::RegisterShellFileTypes()源码附后)   要点:将所有文档模板的类型,外壳命令等写入注册表   包括type ID、shell\open\ddeexec = [open("%1")]、shell\print\ddeexec = [print("%1")]、shell\printto\ddeexec = [printto("%1","%2","%3","%4")]等等。   6.ProcessShellCommand   处理命令行、外壳命令等 CCommandLineInfo cmdInfo;ParseCommandLine(cmdInfo);if (!ProcessShellCommand(cmdInfo))return FALSE;   ①先建立CCommandLineInfo对象   ②再将命令行参数等分解到cmdInfo; void CWinApp::ParseCommandLine(CCommandLineInfo& rCmdInfo){for (int i = 1; i < __argc; i ){LPCTSTR pszParam = __targv[i];BOOL bFlag = FALSE;BOOL bLast = ((i 1) == __argc);if (pszParam[0] == '-' || pszParam[0] == '/'){// remove flag specifierbFlag = TRUE; pszParam;}rCmdInfo.ParseParam(pszParam, bFlag, bLast);}}   通过该操作,命令行被转化为cmdInfo;   命令行的意义  app (新建文件)  app filename(打开文件)   app /p filename(打印文件)  app /pt filename printer driver port (用指定的打印机打印)  app /dde (运行并接收DDE命令)  app /Automation (启动为自动化服务器)  app /Embedding (内嵌式运行)   ParseCommandLine后,操作类型(打开、新建、打印..)存放在m_nShellCommand; 文件名存放在m_strFileName......   ③处理命令   主要操作: switch (rCmdInfo.m_nShellCommand){case CCommandLineInfo::FileNew:OnFileNew()....break;case CCommandLineInfo::FileOpen:OpenDocumentFile(rCmdInfo.m_strFileName)....break;case CCommandLineInfo::FilePrint:case CCommandLineInfo::FilePrintTo:打开文件,发送ID_FILE_PRINT_DIRECT,返回FALSE值(导致立即程序退出)case CCommandLineInfo::FileDDE: m_nCmdShow = SW_HIDE;(程序被运行,但被隐藏,m_nCmdShow作为ShowWindow的参数)等等操作 }   附一:CDocManager::RegisterShellFileTypes void CDocManager::RegisterShellFileTypes(BOOL bCompat){ASSERT(!m_templateList.IsEmpty()); // must have some doc templates CString strPathName, strTemp; AfxGetModuleShortFileName(AfxGetInstanceHandle(), strPathName); POSITION pos = m_templateList.GetHeadPosition();//针对每种文档模板进行注册for (int nTemplateIndex = 1; pos != NULL; nTemplateIndex ){CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetNext(pos); CString strOpenCommandLine = strPathName;CString strPrintCommandLine = strPathName;CString strPrintToCommandLine = strPathName;CString strDefaultIconCommandLine = strPathName; if (bCompat){CString strIconIndex;HICON hIcon = ::ExtractIcon(AfxGetInstanceHandle(), strPathName, nTemplateIndex);if (hIcon != NULL){strIconIndex.Format(_afxIconIndexFmt, nTemplateIndex);DestroyIcon(hIcon);}else{strIconIndex.Format(_afxIconIndexFmt, DEFAULT_ICON_INDEX);}strDefaultIconCommandLine = strIconIndex;} CString strFilterExt, strFileTypeId, strFileTypeName;if (pTemplate->GetDocString(strFileTypeId,CDocTemplate::regFileTypeId) && !strFileTypeId.IsEmpty()){// enough info to register itif (!pTemplate->GetDocString(strFileTypeName,CDocTemplate::regFileTypeName))strFileTypeName = strFileTypeId; // use id name ASSERT(strFileTypeId.Find(' ') == -1); // no spaces allowed // first register the type ID of our serverif (!_AfxSetRegKey(strFileTypeId, strFileTypeName))continue; // just skip it if (bCompat){// path\DefaultIcon = path,1strTemp.Format(_afxDefaultIconFmt, (LPCTSTR)strFileTypeId);if (!_AfxSetRegKey(strTemp, strDefaultIconCommandLine))continue; // just skip it} // If MDI Applicationif (!pTemplate->GetDocString(strTemp, CDocTemplate::windowTitle) ||strTemp.IsEmpty()){// path\shell\open\ddeexec = [open("%1")]strTemp.Format(_afxShellOpenFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxDDEExec);if (!_AfxSetRegKey(strTemp, _afxDDEOpen))continue; // just skip it if (bCompat){// path\shell\print\ddeexec = [print("%1")]strTemp.Format(_afxShellPrintFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxDDEExec);if (!_AfxSetRegKey(strTemp, _afxDDEPrint))continue; // just skip it // path\shell\printto\ddeexec = [printto("%1","%2","%3","%4")]strTemp.Format(_afxShellPrintToFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxDDEExec);if (!_AfxSetRegKey(strTemp, _afxDDEPrintTo))continue; // just skip it // path\shell\open\command = path /dde// path\shell\print\command = path /dde// path\shell\printto\command = path /ddestrOpenCommandLine = _afxDDEArg;strPrintCommandLine = _afxDDEArg;strPrintToCommandLine = _afxDDEArg;}else{strOpenCommandLine = _afxOpenArg;}}else{// path\shell\open\command = path filename// path\shell\print\command = path /p filename// path\shell\printto\command = path /pt filename printer driver portstrOpenCommandLine = _afxOpenArg;if (bCompat){strPrintCommandLine = _afxPrintArg;strPrintToCommandLine = _afxPrintToArg;}} // path\shell\open\command = path filenamestrTemp.Format(_afxShellOpenFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxCommand);if (!_AfxSetRegKey(strTemp, strOpenCommandLine))continue; // just skip it if (bCompat){// path\shell\print\command = path /p filenamestrTemp.Format(_afxShellPrintFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxCommand);if (!_AfxSetRegKey(strTemp, strPrintCommandLine))continue; // just skip it // path\shell\printto\command = path /pt filename printer driver portstrTemp.Format(_afxShellPrintToFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxCommand);if (!_AfxSetRegKey(strTemp, strPrintToCommandLine))continue; // just skip it} pTemplate->GetDocString(strFilterExt, CDocTemplate::filterExt);if (!strFilterExt.IsEmpty()){ASSERT(strFilterExt[0] == '.'); LONG lSize = _MAX_PATH * 2;LONG lResult = ::RegQueryValue(HKEY_CLASSES_ROOT, strFilterExt,strTemp.GetBuffer(lSize), &lSize);strTemp.ReleaseBuffer(); if (lResult != ERROR_SUCCESS || strTemp.IsEmpty() ||strTemp == strFileTypeId){// no association for that suffixif (!_AfxSetRegKey(strFilterExt, strFileTypeId))continue; if (bCompat){strTemp.Format(_afxShellNewFmt, (LPCTSTR)strFilterExt);(void)_AfxSetRegKey(strTemp, _afxShellNewValue, _afxShellNewValueName);}}}}}}   附二:CWinApp::ProcessShellCommand BOOL CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo){BOOL bResult = TRUE;switch (rCmdInfo.m_nShellCommand){case CCommandLineInfo::FileNew:if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))OnFileNew();if (m_pMainWnd == NULL)bResult = FALSE;break; // If we've been asked to open a file, call OpenDocumentFile() case CCommandLineInfo::FileOpen:if (!OpenDocumentFile(rCmdInfo.m_strFileName))bResult = FALSE;break; // If the user wanted to print, hide our main window and// fire a message to ourselves to start the printing case CCommandLineInfo::FilePrintTo:case CCommandLineInfo::FilePrint:m_nCmdShow = SW_HIDE;ASSERT(m_pCmdInfo == NULL);OpenDocumentFile(rCmdInfo.m_strFileName);m_pCmdInfo = &rCmdInfo;m_pMainWnd->SendMessage(WM_COMMAND, ID_FILE_PRINT_DIRECT);m_pCmdInfo = NULL;bResult = FALSE;break; // If we're doing DDE, hide ourselves case CCommandLineInfo::FileDDE:m_pCmdInfo = (CCommandLineInfo*)m_nCmdShow;m_nCmdShow = SW_HIDE;break; // If we've been asked to unregister, unregister and then terminatecase CCommandLineInfo::AppUnregister:{UnregisterShellFileTypes();BOOL bUnregistered = Unregister(); // if you specify /EMBEDDED, we won't make an success/failure box// this use of /EMBEDDED is not related to OLE if (!rCmdInfo.m_bRunEmbedded){if (bUnregistered)AfxMessageBox(AFX_IDP_UNREG_DONE);elseAfxMessageBox(AFX_IDP_UNREG_FAILURE);}bResult = FALSE; // that's all we do // If nobody is using it already, we can use it.// We'll flag that we're unregistering and not save our state// on the way out. This new object gets deleted by the// app object destructor. if (m_pCmdInfo == NULL){m_pCmdInfo = new CCommandLineInfo;m_pCmdInfo->m_nShellCommand = CCommandLineInfo::AppUnregister;}}break;}return bResult;}   文档视图结构中,应用程序类对象实例初始操作的分析 BOOL CSomeApp::InitInstance(){Enable3dControls();LoadStdProfileSettings();AddDocTemplate(...) ...... ShowWindow(...);m_pMainWnd->DragAcceptFiles();EnableShellOpen();RegisterShellFileTypes(TRUE);CCommandLineInfo cmdInfo;ParseCommandLine(cmdInfo);if (!ProcessShellCommand(cmdInfo))return FALSE;return TRUE;}   下面对InitInstance中的一些操作及其流程进行分析   1.常规设置   如:  SetDialogBkColor()  Enable3dControls()..  (如果设置了后者,则前者就不必要了)  SetRegistryKey(指定注册表键,替代INI文件)   2.LoadStdProfileSettings()   LoadStdProfileSettings 完成最近文件列表功能,在菜单中添加最近的文件作为菜单项过程:   建立一个CRecentFileList从注册表或INI文件中读入最近文件列表;   当菜单建立时,文件列表将添加到菜单中ID_FILE_MRU_FILE*位置;   3.m_pMainWnd->DragAcceptFiles()接收文件拖入   使主窗口能响应文件拖入消息WM_DROPFILES;   当有文件拖入时, 框架窗口的OnDropFiles将处理,以打开这些文件。 void CFrameWnd::OnDropFiles(HDROP hDropInfo){SetActiveWindow(); // activate us first !UINT nFiles = ::DragQueryFile(hDropInfo, (UINT)-1, NULL, 0); CWinApp* pApp = AfxGetApp();ASSERT(pApp != NULL);for (UINT iFile = 0; iFile < nFiles; iFile ){TCHAR szFileName[_MAX_PATH];::DragQueryFile(hDropInfo, iFile, szFileName, _MAX_PATH);//应用程序打开拖入文档pApp->OpenDocumentFile(szFileName);}::DragFinish(hDropInfo);}   4.EnableShellOpen();   为在Windows中使用外壳操作打开文件作准备 void CWinApp::EnableShellOpen(){ASSERT(m_atomApp == NULL && m_atomSystemTopic == NULL); // do once m_atomApp = ::GlobalAddAtom(m_pszExeName);m_atomSystemTopic = ::GlobalAddAtom(_T("system"));}   5.RegisterShellFileTypes   向系统注册文件类型,以使用外壳操作。   将调用m_pDocManager->RegisterShellFileTypes()   (CDocManager::RegisterShellFileTypes()源码附后)   要点:将所有文档模板的类型,外壳命令等写入注册表   包括type ID、shell\open\ddeexec = [open("%1")]、shell\print\ddeexec = [print("%1")]、shell\printto\ddeexec = [printto("%1","%2","%3","%4")]等等。   6.ProcessShellCommand   处理命令行、外壳命令等 CCommandLineInfo cmdInfo;ParseCommandLine(cmdInfo);if (!ProcessShellCommand(cmdInfo))return FALSE;   ①先建立CCommandLineInfo对象   ②再将命令行参数等分解到cmdInfo; void CWinApp::ParseCommandLine(CCommandLineInfo& rCmdInfo){for (int i = 1; i < __argc; i ){LPCTSTR pszParam = __targv[i];BOOL bFlag = FALSE;BOOL bLast = ((i 1) == __argc);if (pszParam[0] == '-' || pszParam[0] == '/'){// remove flag specifierbFlag = TRUE; pszParam;}rCmdInfo.ParseParam(pszParam, bFlag, bLast);}}   通过该操作,命令行被转化为cmdInfo;   命令行的意义  app (新建文件)  app filename(打开文件)   app /p filename(打印文件)  app /pt filename printer driver port (用指定的打印机打印)  app /dde (运行并接收DDE命令)  app /Automation (启动为自动化服务器)  app /Embedding (内嵌式运行)   ParseCommandLine后,操作类型(打开、新建、打印..)存放在m_nShellCommand; 文件名存放在m_strFileName......   ③处理命令   主要操作: switch (rCmdInfo.m_nShellCommand){case CCommandLineInfo::FileNew:OnFileNew()....break;case CCommandLineInfo::FileOpen:OpenDocumentFile(rCmdInfo.m_strFileName)....break;case CCommandLineInfo::FilePrint:case CCommandLineInfo::FilePrintTo:打开文件,发送ID_FILE_PRINT_DIRECT,返回FALSE值(导致立即程序退出)case CCommandLineInfo::FileDDE: m_nCmdShow = SW_HIDE;(程序被运行,但被隐藏,m_nCmdShow作为ShowWindow的参数)等等操作 }   附一:CDocManager::RegisterShellFileTypes void CDocManager::RegisterShellFileTypes(BOOL bCompat){ASSERT(!m_templateList.IsEmpty()); // must have some doc templates CString strPathName, strTemp; AfxGetModuleShortFileName(AfxGetInstanceHandle(), strPathName); POSITION pos = m_templateList.GetHeadPosition();//针对每种文档模板进行注册for (int nTemplateIndex = 1; pos != NULL; nTemplateIndex ){CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetNext(pos); CString strOpenCommandLine = strPathName;CString strPrintCommandLine = strPathName;CString strPrintToCommandLine = strPathName;CString strDefaultIconCommandLine = strPathName; if (bCompat){CString strIconIndex;HICON hIcon = ::ExtractIcon(AfxGetInstanceHandle(), strPathName, nTemplateIndex);if (hIcon != NULL){strIconIndex.Format(_afxIconIndexFmt, nTemplateIndex);DestroyIcon(hIcon);}else{strIconIndex.Format(_afxIconIndexFmt, DEFAULT_ICON_INDEX);}strDefaultIconCommandLine = strIconIndex;} CString strFilterExt, strFileTypeId, strFileTypeName;if (pTemplate->GetDocString(strFileTypeId,CDocTemplate::regFileTypeId) && !strFileTypeId.IsEmpty()){// enough info to register itif (!pTemplate->GetDocString(strFileTypeName,CDocTemplate::regFileTypeName))strFileTypeName = strFileTypeId; // use id name ASSERT(strFileTypeId.Find(' ') == -1); // no spaces allowed // first register the type ID of our serverif (!_AfxSetRegKey(strFileTypeId, strFileTypeName))continue; // just skip it if (bCompat){// path\DefaultIcon = path,1strTemp.Format(_afxDefaultIconFmt, (LPCTSTR)strFileTypeId);if (!_AfxSetRegKey(strTemp, strDefaultIconCommandLine))continue; // just skip it} // If MDI Applicationif (!pTemplate->GetDocString(strTemp, CDocTemplate::windowTitle) ||strTemp.IsEmpty()){// path\shell\open\ddeexec = [open("%1")]strTemp.Format(_afxShellOpenFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxDDEExec);if (!_AfxSetRegKey(strTemp, _afxDDEOpen))continue; // just skip it if (bCompat){// path\shell\print\ddeexec = [print("%1")]strTemp.Format(_afxShellPrintFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxDDEExec);if (!_AfxSetRegKey(strTemp, _afxDDEPrint))continue; // just skip it // path\shell\printto\ddeexec = [printto("%1","%2","%3","%4")]strTemp.Format(_afxShellPrintToFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxDDEExec);if (!_AfxSetRegKey(strTemp, _afxDDEPrintTo))continue; // just skip it // path\shell\open\command = path /dde// path\shell\print\command = path /dde// path\shell\printto\command = path /ddestrOpenCommandLine = _afxDDEArg;strPrintCommandLine = _afxDDEArg;strPrintToCommandLine = _afxDDEArg;}else{strOpenCommandLine = _afxOpenArg;}}else{// path\shell\open\command = path filename// path\shell\print\command = path /p filename// path\shell\printto\command = path /pt filename printer driver portstrOpenCommandLine = _afxOpenArg;if (bCompat){strPrintCommandLine = _afxPrintArg;strPrintToCommandLine = _afxPrintToArg;}} // path\shell\open\command = path filenamestrTemp.Format(_afxShellOpenFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxCommand);if (!_AfxSetRegKey(strTemp, strOpenCommandLine))continue; // just skip it if (bCompat){// path\shell\print\command = path /p filenamestrTemp.Format(_afxShellPrintFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxCommand);if (!_AfxSetRegKey(strTemp, strPrintCommandLine))continue; // just skip it // path\shell\printto\command = path /pt filename printer driver portstrTemp.Format(_afxShellPrintToFmt, (LPCTSTR)strFileTypeId,(LPCTSTR)_afxCommand);if (!_AfxSetRegKey(strTemp, strPrintToCommandLine))continue; // just skip it} pTemplate->GetDocString(strFilterExt, CDocTemplate::filterExt);if (!strFilterExt.IsEmpty()){ASSERT(strFilterExt[0] == '.'); LONG lSize = _MAX_PATH * 2;LONG lResult = ::RegQueryValue(HKEY_CLASSES_ROOT, strFilterExt,strTemp.GetBuffer(lSize), &lSize);strTemp.ReleaseBuffer(); if (lResult != ERROR_SUCCESS || strTemp.IsEmpty() ||strTemp == strFileTypeId){// no association for that suffixif (!_AfxSetRegKey(strFilterExt, strFileTypeId))continue; if (bCompat){strTemp.Format(_afxShellNewFmt, (LPCTSTR)strFilterExt);(void)_AfxSetRegKey(strTemp, _afxShellNewValue, _afxShellNewValueName);}}}}}}   附二:CWinApp::ProcessShellCommand BOOL CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo){BOOL bResult = TRUE;switch (rCmdInfo.m_nShellCommand){case CCommandLineInfo::FileNew:if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))OnFileNew();if (m_pMainWnd == NULL)bResult = FALSE;break; // If we've been asked to open a file, call OpenDocumentFile() case CCommandLineInfo::FileOpen:if (!OpenDocumentFile(rCmdInfo.m_strFileName))bResult = FALSE;break; // If the user wanted to print, hide our main window and// fire a message to ourselves to start the printing case CCommandLineInfo::FilePrintTo:case CCommandLineInfo::FilePrint:m_nCmdShow = SW_HIDE;ASSERT(m_pCmdInfo == NULL);OpenDocumentFile(rCmdInfo.m_strFileName);m_pCmdInfo = &rCmdInfo;m_pMainWnd->SendMessage(WM_COMMAND, ID_FILE_PRINT_DIRECT);m_pCmdInfo = NULL;bResult = FALSE;break; // If we're doing DDE, hide ourselves case CCommandLineInfo::FileDDE:m_pCmdInfo = (CCommandLineInfo*)m_nCmdShow;m_nCmdShow = SW_HIDE;break; // If we've been asked to unregister, unregister and then terminatecase CCommandLineInfo::AppUnregister:{UnregisterShellFileTypes();BOOL bUnregistered = Unregister(); // if you specify /EMBEDDED, we won't make an success/failure box// this use of /EMBEDDED is not related to OLE if (!rCmdInfo.m_bRunEmbedded){if (bUnregistered)AfxMessageBox(AFX_IDP_UNREG_DONE);elseAfxMessageBox(AFX_IDP_UNREG_FAILURE);}bResult = FALSE; // that's all we do // If nobody is using it already, we can use it.// We'll flag that we're unregistering and not save our state// on the way out. This new object gets deleted by the// app object destructor. if (m_pCmdInfo == NULL){m_pCmdInfo = new CCommandLineInfo;m_pCmdInfo->m_nShellCommand = CCommandLineInfo::AppUnregister;}}break;}return bResult;} 下载本文示例代码


应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析应用程序类对象实例初始操作的分析
阅读(170) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~