積累的VC編程小技巧之框架窗口及其他
1.修改主窗口風格
AppWizard生成的應用程序框架的主窗口具有缺省的窗口風格,比如在窗口標題條中自動添加文檔名、窗口是疊加型的、可改變窗口大小等。要修改窗口的缺省風格,需要重載CWnd::PreCreateWindow(CREATESTRUCT& cs)函數,并在其中修改CREATESTRUCT型參數cs。
CWnd::PreCreateWindow 函數先于窗口創建函數執行。如果該函數被重載,則窗口創建函數將使用CWnd::PreCreateWindow 函數返回的CREATESTRUCT cs參數所定義的窗口風格來創建窗口;否則使用預定義的窗口風格。
CREATESTRUCT結構定義了創建函數創建窗口所用的初始參數,其定義如下:
typedef struct tagCREATESTRUCT {
LPVOID lpCreateParams; // 創建窗口的基本參數
HANDLE hInstance; // 擁有將創建的窗口的模塊實例句柄
HMENU hMenu; // 新窗口的菜單句柄
HWND hwndParent; // 新窗口的父窗口句柄
int cy; // 新窗口的高度
int cx; // 新窗口的寬度
int y; // 新窗口的左上角Y坐標
int x; // 新窗口的左上角X坐標
LONG style; // 新窗口的風格
LPCSTR lpszName; // 新窗口的名稱
LPCSTR lpszClass; // 新窗口的窗口類名
DWORD dwExStyle; // 新窗口的擴展參數
} CREATESTRUCT;
CREATESTRUCT結構的style域定義了窗口的風格。比如,缺省的MDI主窗口的風格中就包括FWS_ADDTOTITLE(在標題條中顯示當前的工作文檔名)、FWS_PREFIXTITLE(把文檔名放在程序標題的前面)、WS_THICKFRAME(窗口具有可縮放的邊框)等風格。由于多種風格參數由邏輯或(“|”)組合在一起的,因此添加某種風格,就只需用“|”把對應的參數加到CREATESTRUCT結構的style域中;刪除已有的風格,則需用“&”連接CREATESTRUCT結構的style域與該風格的邏輯非值。
CREATESTRUCT結構的x、y、cx、cy域分別定義了窗口的初始位置和大小,因此,在CWnd::PreCreateWindow 函數中給它們賦值,將能定義窗口的初始顯示位置和大小。
下例中的代碼將主框窗口的大小將固定為1/4屏幕,標題條中僅顯示窗口名,不顯示文檔名。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
// 修改主窗風格
cs.style &= ~FWS_ADDTOTITLE; //去除標題條中的文檔名
cs.style &= ~WS_THICKFRAME; //去除可改變大小的邊框
cs.style |= WS_DLGFRAME; //增加不能改變大小的邊框
// 確定主窗的大小和初始位置
int cxScreen = ::GetSystemMetrics(SM_CXSCREEN);//獲得屏幕寬
int cyScreen = ::GetSystemMetrics(SM_CYSCREEN); //獲得屏幕高
cs.x = 0; // 主窗位于左上角
cs.y = 0;
cs.cx = cxScreen/2; // 主窗寬為1/2屏幕寬
cs.cy = cxScreen/2; // 主窗高為1/2屏幕高
return CMDIFrameWnd::PreCreateWindow(cs);
}
2.窗口的分割與停靠
一、新建一個類CMySplitter,基類為CSplitterWnd
二、重載該類的OnMouseMove函數:
void CMySplitter::OnMouseMove(UINT nFlags, CPoint point)
{
// 限制切分條的運動范圍。
if(point.x<228||point.x>600)
{
CWnd::OnMouseMove(nFlags, point);
}
else
{
CSplitterWnd::OnMouseMove(nFlags, point);
}
}
三、 然后就可以跟一般的窗口分割那樣去做了,if(point.x<228||point.x>600)這里的范圍可以隨你去設置了 ^_^,夠簡單吧。
四、切分窗口
在MaiFram.h建立切分條對象:
protected:
CMySplitter m_wndSplitter; //切分窗口對象
//在MaiFram.cpp中實現窗口切分:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,CCreateContext* pContext)
{
// 創建拆分器窗口
if (!m_wndSplitter.CreateStatic(this, 1, 2))
return FALSE;
if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CLeftView),CSize(228,100), pContext) ||!m_wndSplitter.CreateView(0,1, RUNTIME_CLASS(CDataEditView), CSize(100, 100), pContext))
{
m_wndSplitter.DestroyWindow();
return FALSE;
}
return TRUE;
}
3.如何使我的程序在啟動時不創建一個新文檔
[問題]
如何使我的程序在啟動時不創建一個新文檔?
[解答]
在程序的InitInstance中的ProcessShellCommand函數之前加入: cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing
4.初始化應用程序的大小
如果想使應用程序界面(文檔)在開始運行是按你的尺寸展現在屏幕上,
添加代碼如下:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
int xsize=::GetSystemMetrics(SM_CXSCREEN);
int ysize=::GetSystemMetrics(SM_CYSCREEN);
cs.cx=xsize*5/10;
cs.cy=ysize*5/10;
cs.x=(xsize-cs.cx)/2;
cs.y=(ysize-cs.cy)/2;
}
其中的5/10是你的初始界面占屏幕的百分比,可以自己修改。如果想使應用程序大小固定添加cs.style&=~WS_THICKFRAME;
5.如何全屏顯示(沒有標題,沒有菜單,沒有工具條)
[解決方法]
重載CMainFrame的ActivateFrame函數:
void CMainFrame::ActivateFrame(int nCmdShow)
{
CRect cRectdesktop;
WINDOWPLACEMENT windowplacement;
::GetWindowRect(::GetDesktopWindow(),&cRectdesktop);
::AdjustWindowRectEx(&cRectdesktop,GetStyle(),TRUE,GetExStyle());
windowplacement.rcNormalPosition=cRectdesktop;
windowplacement.showCmd=SW_SHOWNORMAL;
SetWindowPlacement(&windowplacement);
CFrameWnd::ActivateFrame(nCmdShow);
}
6.如何限制mdi子框架最大化時的大小
用ptMaxTrackSize代替prMaxSize,如下所示:
void CChildFrame::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
// TOD Add your message handler code here and/or call default
CChildFrame::OnGetMinMaxInfo(lpMMI);
lpMMI->ptMaxTrackSize.x = 300;
lpMMI->ptMaxTrackSize.y = 400;
}
7.程序如何刪除自己
/////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE h, HINSTANCE b, LPSTR psz, int n) {
// Is this the Original EXE or the clone EXE?
// If the command-line 1 argument, this is the Original EXE
// If the command-line >1 argument, this is the clone EXE
if (__argc == 1) {
// Original EXE: Spawn clone EXE to delete this EXE
// Copy this EXEcutable image into the user''s temp directory
TCHAR szPathOrig[_MAX_PATH], szPathClone[_MAX_PATH];
GetModuleFileName(NULL, szPathOrig, _MAX_PATH);
GetTempPath(_MAX_PATH, szPathClone);
GetTempFileName(szPathClone, __TEXT("Del"), 0, szPathClone);
CopyFile(szPathOrig, szPathClone, FALSE);
//***注意了***:
// Open the clone EXE using FILE_FLAG_DELETE_ON_CLOSE
HANDLE hfile = CreateFile(szPathClone, 0, FILE_SHARE_READ, NULL, OPEN_EXISTI
NG, FILE_FLAG_DELETE_ON_CLOSE, NULL);
// Spawn the clone EXE passing it our EXE''s process handle
// and the full path name to the Original EXE file.
TCHAR szCmdLine[512];
HANDLE hProcessOrig = OpenProcess(SYNCHRONIZE, TRUE, GetCurrentProcessId());
wsprintf(szCmdLine, __TEXT("%s %d \"%s\""), szPathClone, hProcessOrig, szPat
hOrig);
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
CreateProcess(NULL, szCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
CloseHandle(hProcessOrig);
CloseHandle(hfile);
// This original process can now terminate.
} else {
// Clone EXE: When original EXE terminates, delete it
HANDLE hProcessOrig = (HANDLE) _ttoi(__targv[1]);
WaitForSingleObject(hProcessOrig, INFINITE);
CloseHandle(hProcessOrig);
DeleteFile(__targv[2]);
// Insert code here to remove the subdirectory too (if desired).
// The system will delete the clone EXE automatically
// because it was opened with FILE_FLAG_DELETE_ON_CLOSE
}
return(0);
}
這一段程序思路很簡單:不是不能在運行時直接刪除本身嗎?好,那么程序先復制(CLONE)一個自己,用復制品起動另一個進程,然后自己結束運行,則原來的EXE文件不被系統保護.這時由新進程作為殺手刪除原來的EXE文件,并且繼續完成程序其他的功能。
新進程在運行結束后,復制品被自動刪除。這又是值得介紹的一個把戲了,注意:
// Open the clone EXE using FILE_FLAG_DELETE_ON_CLOSE
HANDLE hfile = CreateFile(szPathClone, 0, FILE_SHARE_READ, NULL,OPEN_EXISTIN
G, FILE_FLAG_DELETE_ON_CLOSE, NULL);
這里面的FILE_FLAG_DELETE_ON_CLOSE標志,這個標志是告訴操作系統,當和這個文件相關的所有句柄都被關閉之后(包括上面這個CREATEFILE創建的句炳),就把這個文件刪除。幾乎所有的臨時文件在創建時,都指明了這個標志。另外要注意的是:在復制品進程對原始程序操刀之前,應該等待原進程退出.在這里用的是進程同步技術.用HANDLE hProcessOrig = OpenProcess(SYNCHRONIZE, TRUE,GetCurrentProcessId());得到原進程句柄.SYNCHRONICE標志在NT下有效,作用是使OpenProcess得到的句柄可以做為同步對象.復制品進程用WaitForSingleObject函數進行同步,然后一個DeleteFile,以及進行其它銷毀證據(比如刪目錄)的工作,一切就完事了。
程序是基于CONSOLE的,通過傳入的參數確定是原始的進程還是復制品新進程,并且得到需要操作的目標文件的信息(主要是路徑),復制品放在系統的TEMP目錄(GetTempPath得到),你也可以隨便找個你認為安全的地方(比如:WINDOWS\SYSTEM32等等)。這里面沒有甚么深的技術.再看其他的一些實現刪除自己的例子,比如說在進程退出前,用fwrite等方法輸出一個.BAT文件,在里面寫幾句DEL,然后WINEXEC一下這個BAT文件即可.玩兒過DOS的蟲蟲大多都會。
8.如何實現SDI與MDI的轉換
我想將一個編好的SDI應用程序轉換為MDI,很明顯要有多處的改變。
你可以這樣做:建立一個繼承于CMDIChidWnd的類,不防設為CChldFrm.在CWinApp中作如下變化。
InitInstance()
{
. ...
//instead of adding CSingleDocTemplate
// Add CMultiDocTemplate.
pDocTemplate = new CMultiDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CSDIDoc),
RUNTIME_CLASS(CChldFrm),
// For Main MDI Frame change this frame window from
// CFrameWnd derivative ( i.e. CMainFrame )
// to your CMDIChildWnd derived CChldFrm.
RUNTIME_CLASS(CSDIView));
/// After this it is required to create the main frame window
// which will contain all the child windows. Now this window is
// what was initially frame window for SDI.
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
.....
}
在從CMDIFrameWnd中繼承的類CMainFrame代替CFramWnd后,所有的類都將從CMDIFrame繼承,而不是CFrameWnd,編譯運行后你就會發現程序已經從SDI變換到MDI。
注意:在CMainFram中必須將構造函數從private改為public.否則會出錯。
9.想在程序一啟動時就自動關閉窗口,不在任務欄里顯示
用CTRL+W打開ClassWizard;
點擊Class Info頁,類名是工程名Dlg,
再在左下方的"Filter"中選擇"Windows";
回到Message Maps頁,就可以看到消息中有WM_WINDOWPOSCHANGING,
加入代碼,如上所示.
這樣運行*.EXE,不但看不到主界面,任務欄也沒有,就是任務管理器中的"應用程序"中也不列出,那該如何關閉它?
在任務管理器的"進程"中可以找到它,這是黑客程序常用的方法.
如果需要的話,連"進程"中也看不到.這樣要終止它就是問題了
10.如何修改frame窗口的背景顏色
MDI窗口的客戶區是由frame窗口擁有的另一個窗口覆蓋的。為了改變frame窗口背景的顏色,只需要這個客戶區的背景顏色就可以了。你必須自己處理WM_ERASEBKND消息。下面是工作步驟:
創建一個從CWnd類繼承的類,就叫它CMDIClient吧;
在CMDIFrameWnd中加入CMDIClient變量;(具體情況看下面的代碼)
#include "MDIClient.h"
class CMainFrame : public CMDIFrameWnd
{
...
protected:
CMDIClient m_wndMDIClient;
}
重載CMDIFrameWnd::OnCreateClient,下面是這段代碼,請注意其中的SubclassWindow();
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
if ( CMDIFrameWnd::OnCreateClient(lpcs, pContext) )
{
m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
return TRUE;
}
else
return FALSE;
}
最后要在CMDIClient中加入處理WM_ERASEBKGND的函數。
11.在MFC下實現圖像放大鏡
一、 引言
當我們想仔細觀察某個細微的東西時,一般都會使用放大鏡。而要看清顯示在計算機屏幕上的圖片或文字時通常也
可以借助于Windows操作系統附帶的放大程序來實現。但該程序只能以固定的放大倍數去進行觀看,有時并不能滿足我們的需要。本文就通過MFC基本類庫提供的StretchBlt函數來實現對屏幕圖象的局部放大,并且可以隨意放大、縮小,選取到合適的放大倍數來對圖像的細節進行觀察。
二、 設計與實現
本程序主要用來對圖像的局部進行可調倍數的放大,應當具有以下主要功能:
1. 移動MOUSE放大顯示圖像的不同部位
2. 左擊增加放大倍率、右擊減少放大倍率。
從光學角度來看,對物體的放大成像是通過把較小的真實物體顯示成尺寸較大的虛像來實現的。因此我們可以用類
似的原理,把圖像中待放大的區間從較小的顯示范圍拉伸到一個比較大的顯示范圍即可達到圖像放大的效果,兩個區間的比值也就是圖像的放大倍率。可以通過縮小源區間的范圍或擴大放大區間的范圍來實現放大倍率的調整。在MFC基本類庫中提供有CDC類的StretchBlt函數可以將一幅位圖從一個源矩形以一定的光柵操作拷貝到另外一個不同大小的目標矩形中去,因此可以用此函數來實現圖象放大的功能,其函數原形聲明如下:
BOOL StretchBlt( int x, int y, //目標矩形的坐標原點
int nWidth, int nHeight, //目標矩形的長度和寬度
CDC* pSrcDC, //源設備環境句柄
int xSrc, int ySrc, //源矩形的坐標原點
int nSrcWidth, int nSrcHeight, //源矩形的長度和寬度
DWORD dwRop ); //光柵操作標志
當指定的源和目標矩形的寬度或高度不一樣時,StretchBlt函數將創建一個位圖的鏡像。如果是寬度有變化,就沿x軸創建鏡像;如果是高度上有變化就沿y軸創建鏡像。而且該函數可以在內存中對源圖象做拉伸或壓縮處理后再拷貝到目標矩形中去。
要放大圖像首先要把圖像顯示出來,一般可以從文件動態裝載或者直接從資源中用LoadBitMap讀取位圖資源。下面的代碼放在視類的OnDraw函數中,用以在第一次調用時將位圖裝載并顯示出來,以后再被調用只是負責重畫:
……
static bool load;
if (!load)
{
BITMAP bm;
load = !load;
//裝載位圖到 m_pBitmap
m_pBitmap->LoadBitmap(IDB_BITMAP1);
//創建相關的設備環境
m_pdcMem->CreateCompatibleDC(pDC);
//將位圖從m_ pBitmap中裝載到m_pdcMem中
m_pdcMem->SelectObject(m_pBitmap);
m_pBitmap->GetObject(sizeof(bm),&bm);
m_sizeSource.cx = bm.bmWidth;
m_sizeSource.cy = bm.bmHeight;
m_sizeDest = m_sizeSource;
//把位圖從m_pdcMem中裝載到當前正在使用的設備環境中
pDC->StretchBlt(0,0,m_sizeSource.cx,m_sizeSource.cy,m_pdcMem,0,0,m_sizeSource.cx,m_sizeSource.cy,mana);
}
else
{
//重畫圖像
pDC->StretchBlt(0,0,m_sizeSource.cx,m_sizeSource.cy,m_pdcMem,0,0,m_sizeSource.cx,m_sizeSource.cy,mana);
SetCursor(NULL);//隱藏鼠標
}
要實現前面提到的第一個功能:移動MOUSE放大顯示圖像的不同部位,顯然首先要在WM_MOUSEMOVE消息的響應函數里編寫代碼。以整形變量s和d來分別表示所選取的源和目標區域的大小,再通過消息響應函數OnMouseMove的入口參數point來確定當前的鼠標位置就可以計算出我們要選取的源和目標區域在圖像的位置。放大的工作只需通過StretchBlt函數將源區域中所在的圖像拉伸到目標矩形那么大,并拷貝給目標區域即可實現所選區域的放大效果,下面是部分主要代碼:
……
//確定目標區域、源區域的坐標位置
CRect srect,drect,mrect;
srect.left = point.x - s;
srect.top = point.y - s;
srect.right = point.x + s;
srect.bottom = point.y + s;
drect.left = point.x - d;
drect.top = point.y - d;
drect.right = point.x + d;
drect.bottom = point.y + d;
mrect.left = oldx - d;
mrect.top = oldy - d;
mrect.right = oldx + d;
mrect.bottom = oldy + d;
dd = 2*d;
//獲取可用設備環境句柄
CDC * pDC = GetDC();
OnPrepareDC(pDC);
if (recover)
{
pDC->BitBlt(mrect.left,mrect.top,dd,dd,m_pdcMem,mrect.left,mrect.top,mana);
}
//隱藏鼠標
SetCursor(NULL);
//拉伸放大
pDC->StretchBlt(drect.left,drect.top,drect.Width(),drect.Height(),m_pdcMem,srect.left,srect.top,srect.Width(),srect.Height(),SRCCOPY);
//保存當前鼠標位置備用
oldx = point.x; oldy = point.y;
//釋放設備環境句柄
ReleaseDC(pDC);
recover = true;
……
為了實現第二個功能:左擊增加放大倍率、右擊減少放大倍率,可以分別在消息WM_LBUTTONDOWN和消息WM_RBUTTONDOWN中添加改變選取區域大小的代碼來實現。如果選取源矩形不變而改變目標矩形的大小會隨著放大倍數的增大,顯示區域也不斷增大,當放大到一定程度的時候會另人無法忍受,因此選取通過縮放源矩形大小來控制放大倍數的方案:
void CZoomInView::OnRButtonDown(UINT nFlags, CPoint point)
{
if (s < 60)
{
SetCursor(NULL);
s+=3;
OnMouseMove(nFlags, point);
}
CView::OnRButtonDown(nFlags, point);
}
……
void CZoomInView::OnLButtonDown(UINT nFlags, CPoint point)
{
if(s>5)
{
s-=3;
SetCursor(NULL);
OnMouseMove(nFlags, point);
}
CView::OnLButtonDown(nFlags, point);
}
(完)
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成