Microsoft Foundation Classes|
My intention is to provide simple, free access to useable solutions to common but difficult programming problems. If you know any better solutions then please e-mail me so that everyone can share your wisdom! Be aware that I use a non-standard "Coding Style"... and its infectious... |
CString _fastcall itoa(int i) {
char s[20];
itoa(i, s, 10);//s.Format("%i", i);
return s;
}
CString _fastcall itoa(long i) {
char s[20]; // CString S;
ltoa(i, s, 10);// S.Format("%l", i);
return s; // return S;
}
CString _fastcall itoa(double i) {
char s[20]; // CString S;
ltoa((long)(i+0.5), s, 10);// S.Format("%l", i+0.5);
return s; // return S;
}
#define Black RGB( 0, 0, 0)
#define DkGray RGB(128,128,128)
#define LtGray RGB(192,192,192)
#define White RGB(255,255,255)
#define Red RGB(255, 0, 0)
#define Green RGB( 0,255, 0)
#define Blue RGB( 0, 0,255)
#define Yellow RGB(255,255, 0)
#define Magenta RGB(255, 0,255)
#define Cyan RGB( 0,255,255)
void ShowLastError() {
LPVOID lpMsgBuf; // Default language:
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
MessageBox(NULL, (LPTSTR)lpMsgBuf, "Error Information", MB_OK|MB_ICONINFORMATION);
LocalFree(lpMsgBuf);
}
#define Redraw(nID) {GetDlgItem(nID)->RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);}
void SizeComboDrop(CComboBox* Combo) {
CRect Rect;
Combo->GetDroppedControlRect(Rect);
Combo->ShowWindow(SW_HIDE); //The SetWindowPos insists on opening and closing the popup...
Combo->SetWindowPos(NULL,0,0,Rect.Width(), min(::GetSystemMetrics(SM_CYFULLSCREEN), (Combo->GetCount()+1)*Combo->GetItemHeight(0)+Combo->GetItemHeight(-1)), SWP_NOZORDER|SWP_NOMOVE|SWP_NOREDRAW|SWP_NOACTIVATE);
Combo->ShowWindow(SW_SHOW);
}
class CSerializingTree : public CTreeCtrl {
public:
CSerializingTree() {}
virtual ~CSerializingTree() {}
void Serialize(CArchive& ar);
};
Then you create an instance of it to use (in your Dialog class header file):CSerializingTree SerializingTree;Now put a Tree Control on your dialog box (I'll assume you've left it called IDC_TREE1 here).
SerializingTree.SubclassDlgItem(IDC_TREE1,this);This is so easy to use once you know it, but knowing about it seems to be very rare!
void CMyButton::OnMouseMove(UINT nFlags, CPoint point) {
CRect rect; // Get the bounds of the control (just the client area)
GetClientRect(rect);
static bool WasIn=false; // Check the mouse is inside the control
if(rect.PtInRect(point)) {
if(!WasIn) { // This is the OnMouseEnter area
SetWindowText("Over");
SetCapture();
}else SetWindowText("Moved Again"); // This else wouldn't normally be used.
WasIn=true;
}else{ //This is the OnMouseLeave area:
SetWindowText("Out");
ReleaseCapture();
WasIn=false;
}
CButton::OnMouseMove(nFlags, point);
}
There are three ways to Create control instances:CMyButton MyButton;to your Dialog Header file,
MyButton.SubclassDlgItem(IDC_BUTTON1,this);to your Dialog Class OnInitDialog() Method.
CMyButton MyButton;to your Dialog Header file,
MyButton.CreateEx(...);to your Dialog Class OnInitDialog() Method.
CString Path(*__argv);Folk who don't know that you can directly access argv and argc by prefixing them with two underscores may use GetCommandLine() or this method:
char Buffer[2*MAX_PATH]; // Allow space for Path AND FileName here. GetModuleFileName(GetModuleHandle(NULL), Buffer, sizeof(Buffer));If you just want the path then use this:
CString Path(*__argv);
int i=Path.ReverseFind('\\')+1;
if(i) Path=Path.Left(i);
BOOL CMyApp::InitInstance() {
CreateMutex(0, FALSE, "MyApp-SingleInstance");
if(GetLastError()==ERROR_ALREADY_EXISTS || GetLastError()==ERROR_ACCESS_DENIED) return FALSE;
...
All you need to know about CreateMutex is that Windows will only let one instance of your Application create a Mutex of a given name.
const UINT UWM_RU12=RegisterWindowMessage("MyApp-SingleInstance-UWM_RU12");
struct SingleInstance {
SingleInstance() {
CreateMutex(0, FALSE, "MyApp-SingleInstance");
//The system closes the handle automatically when the process terminates
//The call fails with ERROR_ACCESS_DENIED if the Mutex was created by another Instance because we gave no SECURITY_ATTRIBUTES
if(GetLastError()==ERROR_ALREADY_EXISTS || GetLastError()==ERROR_ACCESS_DENIED) {
HWND hOther=0;
EnumWindows(Searcher, (LPARAM)&hOther);
if(hOther) { // Try to activate the older Instance
SetForegroundWindow(hOther);
ShowWindow(hOther, IsZoomed(hOther) ? SW_MAXIMIZE : SW_RESTORE); //Needed for windows that have been maximized, then minimized, then hidden.
}
exit(0); // Terminates this Instance
} }
static BOOL CALLBACK Searcher(HWND hWnd, LPARAM lParam) {
DWORD Result;
if(!SendMessageTimeout(hWnd, UWM_RU12, 0,0, SMTO_BLOCK | SMTO_ABORTIFHUNG, 500, &Result)
|| Result!=UWM_RU12) return TRUE; // Continue searching
*((HWND*)lParam)=hWnd; // Found it!
return FALSE; // Stop searching
}
};
class CMyApp : public CWinApp {
...
In your Application's .cpp file all you need to do is create an instance of this structure before your CWinApp instance.SingleInstance Instance; CPostEApp theApp;Now we just have to make the older Instance reply to our Searcher Message (UWM_RU12) and tell us its Main Window's Handle so we can activate it...
//}}AFX_MSG_MAP
afx_msg LRESULT RU12(WPARAM, LPARAM) {return UWM_RU12;}
DECLARE_MESSAGE_MAP()
And in the Main Window's .cpp File, find the END_MESSAGE_MAP() line and immediately above insert the RU12 line:
//}}AFX_MSG_MAP ON_REGISTERED_MESSAGE(UWM_RU12, RU12) END_MESSAGE_MAP()You can test what you have done by:
class CFManClientDlg : public CDialog, public CThread {
public:
CFManClientDlg(CWnd* pParent = NULL) {Start();} // <--This starts the new Thread running the Main() function;
void Main() {
...
}
...
};
This example makes a Window with a counting TitleBar:
class CMyThread : public CThread {
CWnd* Owner;
public:
CMyThread(CWnd* pWnd) : Owner(pWnd) {}
void Main() {
CString S, Old;
Owner->GetWindowText(Old);
int i=0;
while(!Abort) { // we must exit quickly and safely when Abort is true.
S.Format("%i", ++i);
Owner->SetWindowText(S);
Sleep(200);
}
// for(;;); // hang the thread to test TerminateThread. If you uncomment this and click [Start] or [Stop] you'll get Memory leaks (being the CStrings [S] and [Old]) because the Thread was Terminated.
if(::IsWindow(Owner->m_hWnd)) Owner->SetWindowText(Old);
}
};
To use CMythread, create a Dialog Application (called ThreadTester in this example) with a couple of buttons [Start] and [Stop].
virtual ~CThreadTesterDlg() {delete Thread;}
CMyThread* Thread;
In the .cpp file add these lines to OnInitDialog():
Thread=new CMyThread(this); Thread->Start();Then use ClassWizard to add button Handlers for the Start and Stop Buttons and make them look like this:
void CThreadTesterDlg::OnStart() {if(!Thread->Start()) Thread->Main();} // If we can't do it in a separate Thread, do it in this one.
void CThreadTesterDlg::OnStop () {if(!Thread->Stop ()) SetWindowText(Thread->IsRunning() ? "Runaway Thread!" : "Thread had hung!");}
When you click [Start] the Dialog's Title Bar will start counting.
#include <vector>
template <class T>
class CLockVector : public std::vector<T>, public CLock {
public:
CLockVector() : vector<T>() {if(!Lock.Lockable() Panic();}
virtual ~CLockVector() {}
void Insert(T& obj) {
CLockedSection LockedSection(Lock);
vector<T>::push_back(obj);
}
};
#include "DTree.h"
main() {
CDTree DTree;
if(DTree.GetList("V:\\Programming\\C++\\TestApps\\DirectoryTester","*.cpp")) {
CString S;
while(DTree.GetNext(S)) {
bool Directory=DTree.IsDirectory();
//Deal with it here...
} } }
The order of the search is as you would see the results in a fully expanded tree representation, top to bottom.| OnDIR | is called when a Directory is found. At this point GetCurrentDirectoryName(), GetPath() and GetDepth() are of the Directory where you are, and GetName() is of the Directory you're going into. |
| OnFile | is called when a File is found. At this point GetName() is of the File, and GetPath(), GetCurrentDirectoryName() and GetDepth() are of the Directory that the file is in. |
| OnUp | is called when the search goes up a level. At this point GetPath() is where you are, and GetCurrentDirectoryName(), GetName() and GetDepth() are of the Directory you've just finished scanning. |
+-dir1
| +-dir11
| | \-fName11
| \-dir12
| +-fName.txt
| \-fName12
\-dir2
+-dir21
| +-fName.txt
| \-fName21
+-dir22
| +-Copy of fName22
| +-fName.txt
| \-fName22
+-fName.txt
\-fName2
#include "DTree.h"
#include "ioStream.h" // For cout
#include <direct.h> // For mkdir
#include <conio.h> // For getch
class CDTreeViewer: public CDTreeBehaviour {
bool Done[MAX_PATH];
public:
CDTreeViewer(CString Path) {
CString S;
DWORD Length=GetFullPathName(Path,0,0,0); //Make relative paths absolute:
GetFullPathName(Path, Length, S.GetBufferSetLength(Length), 0);
S.ReleaseBuffer();
cout << (const char*)(S) << endl;
memset(Done, false, MAX_PATH);
CDTree(Path, *this);
}
private:
void OnUp (CDTreeBase& DTreeBase) {Done[DTreeBase.GetDepth()]=false;}
void OnDIR (CDTreeBase& DTreeBase) {OnFile(DTreeBase);}
void OnFile(CDTreeBase& DTreeBase) {
CString S(DTreeBase.GetPath()+'\\'+DTreeBase.GetName());
for(int i=0; i<DTreeBase.GetDepth(); ++i) cout << (Done[i] ? " " : "| ");
cout << (DTreeBase.IsLast() ? '\\' : '+') << '-' << (const char*)(DTreeBase.GetName()) << endl;
if(DTreeBase.IsLast()) Done[DTreeBase.GetDepth()]=true;
}
};
main() {
mkdir("Test" );
mkdir("Test\\dir1" );
mkdir("Test\\dir1\\dir11" );
CFile("Test\\dir1\\fName11" ,CFile::modeCreate);
mkdir("Test\\dir1\\dir12" );
CFile("Test\\dir1\\dir12\\fName.txt" ,CFile::modeCreate);
CFile("Test\\dir1\\dir12\\fName12" ,CFile::modeCreate);
mkdir("Test\\dir2" );
mkdir("Test\\dir2\\dir21" );
CFile("Test\\dir2\\dir21\\fName.txt" ,CFile::modeCreate);
CFile("Test\\dir2\\dir21\\fName21" ,CFile::modeCreate);
mkdir("Test\\dir2\\dir22" );
CFile("Test\\dir2\\dir22\\Copy of fName22",CFile::modeCreate);
CFile("Test\\dir2\\dir22\\fName.txt" ,CFile::modeCreate);
CFile("Test\\dir2\\dir22\\fName22" ,CFile::modeCreate);
CFile("Test\\dir2\\fName.txt" ,CFile::modeCreate);
CFile("Test\\dir2\\fName22" ,CFile::modeCreate);
CDTreeViewer(".");
getch();
}
The following code saves a directory tree to a text file in a compact manner:
class CDTreeSaver: public CDTreeBehaviour {
public:
CDTreeCat(CString Path) : pFile(0), pAr(0) {
pFile=new CFile;
if(!pFile->Open("C:\\S.TXT",CFile::modeCreate|CFile::modeWrite)) return;
pAr=new CArchive(pFile,CArchive::store);
pAr->WriteString(Path+"\r\n");
CDTree(Path, *this);
}
~CDTreeCat() {delete pAr; delete pFile;}
private:
CFile* pFile;
CArchive* pAr;
void OnFile(CDTreeBase& DTreeBase) {pAr->WriteString( DTreeBase.GetName()+"\r\n");}
void OnDIR (CDTreeBase& DTreeBase) {pAr->WriteString('.'+DTreeBase.GetName()+"\r\n");}
void OnUp (CDTreeBase& DTreeBase) {pAr->WriteString("..\r\n");}
};
The following projects in the Freeware section of this site demonstrate the use of DTree.h:
CFileIO::Delete("Test");
If you had a list of Directory Trees to delete, use a string of strings (double null terminator):
CFileIO::Delete(CString("Test")+'\0'+"Test2"+'\0');
If you give the full path, CFileIO will delete to the Recycle Bin (unless you say:
CFileIO::Delete("Test\0Test2\0",false);
The following code gets the full path for a file:CString Path; DWORD Length=GetFullPathName(Path,0,0,0); //Make relative paths absolute: GetFullPathName(Path, Length, Path.GetBufferSetLength(Length), 0); Path.ReleaseBuffer();it will also Move and Copy lists of files giving the standard Windows Move/Copy Progress Bar Dialog.
char Buf[6]; CResFile ResFile(nID); ResFile.Read(Buf,6); ResFile.Rewind();or
CResFile ResFile;
if(ResFile.Open(nID)) {
ResFile.Read(Buf,6);
ResFile.Close();
}
void CMyDlg::OnButton1() {
char Dest[MAX_PATH];
PickDir("Hello", Dest);
}
bool CMyDlg::PickDir(const char *Prompt, char * Dest) {
// Dest is assumed to be _MAX_PATH characters in length
BROWSEINFO bi;
ITEMIDLIST* pItemIDList;
char Folder[_MAX_PATH];
memset(&bi, 0, sizeof(bi));
bi.hwndOwner=m_hWnd;
bi.pszDisplayName=Folder;
bi.lpszTitle=Prompt;
if((pItemIDList=SHBrowseForFolder(&bi))!=NULL) {
SHGetPathFromIDList(pItemIDList, Dest);
return(true);
}else return false;
}
There is also Callback functionality available - but most people find that confusing, so I've encapsulated it in a class (CBrowseForFolder) that should help and provided a couple of useful example classes.
CImageList ImageList;In the dialog class OnInitDialog() method add the following two lines.
ImageList.Create(IDB_ImageList, 16, 1, RGB(255,0,255)); MyTreeControl.SetImageList(&ImageList,TVSIL_NORMAL);If its not a tree control you'll have to look up the appropriate SetImageList Parameters:
MyListControl.SetImageList(&ImageList,LVSIL_SMALL);So how do you get a control to put a File Item's Icon in a control? Well, there may be a better way, but the following is as good as I have found so far:
void SetImage(CString& Path) {
SHFILEINFO FileInfo;
HIMAGELIST HImageList=(HIMAGELIST)SHGetFileInfo(Path, FILE_ATTRIBUTE_NORMAL, &FileInfo, sizeof(FileInfo), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
CImageList* pImageList=CImageList::FromHandle(HImageList);
if(pImageList) MyTreeControl.SetImageList(pImageList,TVSIL_NORMAL);
MyTreeControl.SetImage(FileInfo.iIcon);
}
void SetFolderImage() {
SHFILEINFO FileInfo;
HIMAGELIST HImageList=(HIMAGELIST)SHGetFileInfo("", FILE_ATTRIBUTE_DIRECTORY, &FileInfo, sizeof(FileInfo), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
CImageList* pImageList=CImageList::FromHandle(HImageList);
if(pImageList) MyTreeControl.SetImageList(pImageList,TVSIL_NORMAL);
MyTreeControl.SetImage(FileInfo.iIcon);
}
class CModelessDialog : public CDialog {
public:
CModelessDialog( CWnd *pParent=NULL) {}
CModelessDialog(UINT nIDTemplate, CWnd *pParent=NULL) {Create(nIDTemplate , pParent);}
CModelessDialog(LPCSTR lpszTemplateName, CWnd *pParent=NULL) {Create(lpszTemplateName, pParent);}
protected:
virtual void OnCancel() {DestroyWindow();}
virtual void OnOK() {if(UpdateData(TRUE)) DestroyWindow();}
virtual void PostNcDestroy() {delete this;}
};
To use this, create a normal Dialog class.
//In the Header file:
static CMyDlg* Active;
//In the .cpp file:
CMyDlg* CTipDlg::Active=NULL;
//At the beginning of the constructor code MyDlg::MyDlg(:
if(Active==NULL) Active=this;
else {
Active->SetActiveWindow();
OnCancel();
return;
}
ShowWindow(SW_SHOW);
SetActiveWindow();
//At the beginning of the Destructor code MyDlg::~MyDlg():
if(Active==this) Active=NULL;
BOOL CMyDlgOrControl::OnEraseBkgnd(CDC* pDC) {return TRUE;}
If you're filling it black or white use:
PatBlt(Rect.left, Rect.top, Rect.Width(), Rect.Height(), BLACKNESS);or:
PatBlt(Rect.left, Rect.top, Rect.Width(), Rect.Height(), WHITENESS);to fill with the current dialog background colour use:
dc.FillSolidRect(&Rect, GetSysColor(COLOR_3DFACE)); //Draw in dialogs background colourIf you are looking to draw real-time graphics, look at the Graphics section of this site.
NONCLIENTMETRICS ncm; ncm.cbSize=sizeof(NONCLIENTMETRICS); SystemParametersInfo(SPI_GETNONCLIENTMETRICS,0,&ncm,0);This provides a LOGFONT structure at ncm.lfStatusFont which you can modify (say by adding underline:
ncm.lfStatusFont.lfUnderline=TRUE;) Then just use
CreateFontIndirect(&ncm.lfStatusFont);to have the font ready for selecting into the device context.
CGdiObject* OldFont=dc.SelectObject(GetParent()->GetFont());then use
dc.DrawText(...);and finish with
dc.SelectObject(OldFont);
CGdiObject* OldFont=dc.SelectStockObject(ANSI_VAR_FONT);then use
dc.DrawText(...);and finish with
dc.SelectObject(&OldFont);It is best to use a class to encapsulate the clean-up:
class CUseDialogFont {
CGdiObject* OldFont;
CDC* pDC;
public:
CUseDialogFont(CDC* _pDC) {pDC=_pDC; OldFont=pDC->SelectStockObject(ANSI_VAR_FONT);}
~CUseDialogFont() {pDC->SelectObject(&OldFont);}
};
Then in your code just use:
CUseDialogFont Font(&dc);That way you can 'return' at any time safe in the knowledge that the original font will be selected.
CBitmap Bitmap;In the .cpp file:
#include "MemDC.h"and then call the following just once:
Bitmap.CreateCompatibleBitmap(pDC, Rect.Width(), Rect.Height());If you don't have pDC use GetDC() which returns the DC of the screen.
CPaintDC PaintDC(this); // device context for painting CMemDC dc(PaintDC, &Bitmap);Then draw everything to dc - and thats it!
CPaintDC PaintDC(this); // device context for painting CMemDC dc(PaintDC);it'll be slower for complex animations, but fine for most Controls. The same applies to OwnerDraw Buttons, create the MemDC object as follows:
void CMemDCTesterDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) {
CMemDC dc(lpDrawItemStruct, &Bitmap);
Theres also a constructor for Views:
void CMyView::OnDraw(CDC* pDC) {
CMemDC dc(pDC, &Bitmap);
If you are looking to draw real-time graphics, look at the Graphics section of this site.CStatus Status;to your Dialog's Header file, then add:
Status.SubclassDlgItem(IDC_Status,this);
to your Dialog's OnInitDialog() method.
if(!File.Open("Debug\\StatusTest.pch", CFile::modeRead)) return;
Hopefully you'll find lots of interesting optimisations in the code!
The colour change has been made to vary from red to green, but without fading. The overall effect for fast changes is strangely more aesthetic than fading, and the redraw speed is faster.![]() |
A useful metaphor, when doing processes like file copying, is a clock: the big hand spinning once round for each file copied, and the small hand once round for all files copied. If a square space is all that is available, this could replace two progress bars. |
[autorun] OPEN=Start.exe ICON=Start.exe,0 shell\Start=&View PSL CD... shell\Start\command=Start.exeThats wonderfully easy, until you want to give out a CD which contains a web site or at least starts from a web page...
#include "stdafx.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HKEY Key;
if(RegOpenKeyEx(HKEY_CLASSES_ROOT,".htm",0,KEY_READ,&Key)==ERROR_SUCCESS) {
DWORD dwSize=2*MAX_PATH;
char Browser[2*MAX_PATH];
if(RegQueryValueEx(Key,"",NULL,NULL,(BYTE*)&Browser,&dwSize)==ERROR_SUCCESS) {
RegCloseKey(Key);
strcat(Browser, "\\shell\\open\\command");
if(RegOpenKeyEx(HKEY_CLASSES_ROOT,Browser,0,KEY_READ,&Key)==ERROR_SUCCESS) {
dwSize=2*MAX_PATH;
if(RegQueryValueEx(Key,"",NULL,NULL,(BYTE*)&Browser,&dwSize)==ERROR_SUCCESS) {
strcat(Browser," \"file://");
char* ptr=Browser+dwSize+9-1; //Find the end of the string
GetCurrentDirectory(MAX_PATH, ptr--);
while(char c=*++ptr) if(c=='\\') *ptr='/';
if(*(ptr-1)!='/') {
*ptr++='/';
*ptr=0;
}
strcat(Browser, "index.htm\"");
WinExec(Browser, SW_SHOWNORMAL);
} } }
RegCloseKey(Key);
}
return 0;
}
void CMyDlg::OnButton1() {
HKEY Key;
if(RegOpenKeyEx(HKEY_CLASSES_ROOT,".htm",0,KEY_READ,&Key)==ERROR_SUCCESS) {
DWORD dwSize=2*MAX_PATH;
char Browser[2*MAX_PATH];
if(RegQueryValueEx(Key,"",NULL,NULL,(BYTE*)&Browser,&dwSize)==ERROR_SUCCESS) {
RegCloseKey(Key);
strcat(Browser, "\\shell\\open\\command");
if(RegOpenKeyEx(HKEY_CLASSES_ROOT,Browser,0,KEY_READ,&Key)==ERROR_SUCCESS) {
dwSize=2*MAX_PATH;
if(RegQueryValueEx(Key,"",NULL,NULL,(BYTE*)&Browser,&dwSize)==ERROR_SUCCESS) {
strcat(Browser," \"http://rossm.net/Electronics/Computers/Software/C++/\"");
WinExec(Browser, SW_SHOWNORMAL);
} } }
RegCloseKey(Key);
} }
CToolTipCtrl Tips;If you don't have OnInitDialog(), use Class Wizard to add WM_INITDIALOG.
Tips.Create(this); Tips.AddTool(GetDlgItem(IDC_THE_CONTROL),"Click to start transferring e-mails"); Tips.Activate(TRUE);To make the Tip Control work use Class Wizard to add PreTranslateMessage.
Tips.RelayEvent(pMsg);and thats it!
If you want to use a hand cursor like a web browsers use when the mouse is over a link you either have to have Windows 2000 or above, or use a resource.
#include "MsgWnd.h"to the top and add
CMsgWnd MsgWnd;To the private or protected section.
MsgWnd.SetOwner(this); //The MsgWnd will now send Notify Messages to 'this' CWnd. If you don't need to know if the user clicked the MsgWnd, then don't have this line! MsgWnd.SetAVI(IDR_eMailAnimation); //This isn't necessary, but it was for my application, so its here!Then use ClassWizard to add an OnNotify handler and make it look like this:
BOOL CMsgWndDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) {
if(MsgWnd.Clicked(lParam)) {
SetFocus();
MsgWnd.Show("You clicked?"); //This line is just for debug feedback
return TRUE;
}
return CDialog::OnNotify(wParam, lParam, pResult);
}
There is also:
MsgWnd.ShowDelayed("Message", 1000);
which starts showing the window after the specified number of milliseconds (showDelayed defaults to 100ms).bool LButtonDown; // Keeps track of the mouse for Click Messages. bool MouseOver; // true if the mouse is over the window.In the control's constructor put:
LButtonDown=false; MouseOver=false;Now use ClassWizard to add OnMouseMove, OnLButtonDown, OnLButtonUp and make them look like this:
void CMsgWnd::OnMouseMove(UINT nFlags, CPoint point) {
if(!MouseOver) {
MouseOver=true;
SetCapture();
}else{
CRect Rect;
GetClientRect(Rect);
if(Rect.PtInRect(point)) {
MouseOver=false;
ReleaseCapture();
}else{
CWnd::OnMouseMove(nFlags, point);
return;
} }
RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
CWnd::OnMouseMove(nFlags, point);
}
void CMsgWnd::OnLButtonDown(UINT nFlags, CPoint point) {
if(MouseOver) LButtonDown=true;
else CWnd::OnLButtonDown(nFlags, point);
}
void CLinkButton::OnLButtonUp(UINT nFlags, CPoint point) {
if(MouseOver && LButtonDown) {
GetOwner()->PostMessage(WM_COMMAND, GetDlgCtrlID());
MouseOver=false;
RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}else CButton::OnLButtonUp(nFlags, point);
LButtonDown=false;
}
The dialog that owns this control can now have an ON_BN_CLICKED event handler which will be fired when the Button is clicked.
CMyDlg dlg;
m_pMainWnd=&dlg;
CTrayIcon TI("My ToolTip Text", AfxGetApp()->LoadIcon(IDR_MAINFRAME));
dlg.DoModal();
If you want your Main Window's Task Bar Button to be hidden when the window is minimized to the System Tray you will need to add the following function:
void CMyDlg::OnSysCommand(UINT nID, LPARAM lParam) {
switch(nID & 0xFFF0) {
case SC_MINIMIZE: if(lParam) ShowWindow(SW_HIDE); else SetForegroundWindow(); return;
case IDM_ABOUTBOX: CAboutDlg().DoModal(); return; //This line is only for a Dialog Application with an About Box.
default: CDialog::OnSysCommand(nID, lParam); return;
} }
CMyDlg dlg;
m_pMainWnd=&dlg;
CMenuTrayIcon MTI("My ToolTip Text", AfxGetApp()->LoadIcon(IDR_MAINFRAME), IDR_PopUps, ID_ShowMe);
dlg.DoModal();
You will also have to use ClassWizard to create handlers for each of these menu items (COMMAND and, optionally, UPDATE_COMMAND_UI (be aware that Dialog Applications don't support UPDATE_COMMAND_UI: you need CFrameWnd derived windows for that)).
CMyDlg dlg;
m_pMainWnd=&dlg;
CAniTrayIcon ATI("My ToolTip Text", AfxGetApp()->LoadIcon(IDR_MAINFRAME));
static const UINT Icons[]={IDI_ICON1,IDI_ICON2,IDI_ICON3, ... ,0};
AMTI.SetIcons(Icons);
ATI.Animate(100); //Show one frame every 100 milliseconds.
dlg.DoModal();
Well, that'll show you how it works, but to allow your Application to access the CAniTrayIcon::Animate and CAniTrayIcon::StopAnimating functions,((CMyApp*)AfxGetApp())->ATI.StopAnimating();
CWndAnimator WndAnimator();Then if you are creating a Dialog Application, in OnInitDialog()
WndAnimator.SetWnd(this);and call each animation function appropriately (this example is for a Dialog Application):
void CMyDlg::OnCancel() {WndAnimator.Close(); CDialog::OnCancel();}
void CMyDlg::OnShow () {WndAnimator.Restore();}
void CMyDlg::OnSysCommand(UINT nID, LPARAM lParam) {
switch(nID & 0xFFF0) {
case IDM_ABOUTBOX: CAboutDlg().DoModal (); return;
case SC_MINIMIZE: WndAnimator.Minimize(); return;
case SC_RESTORE: if(!IsZoomed()) {WndAnimator.Restore(); return;} //Only use our animation for restoring from the TaskBar
default: CDialog::OnSysCommand(nID, lParam); return;
} }
CToney Toney;and when you want to play the sound use:
Toney.Play("Trim Phone:d=16,o=5,b=350:a,b,a,b,a,b,a,4p,a,b,a,b,a,b,a,b,a.");
When the CToney is destroyed, the thread playing the sound is stopped.CToney Toney;In your OnInit() function put:
Toney.LoadCombo((CComboBox*)GetDlgItem(IDC_ToneList), "RingTones.RTTTL");this will fill a Combo Box with Ringtone Names from a file of RTTTL RingTones, one RingTone Per line.
Dream:d=8,o=4,b=220:c3,4p.,c,4p,d#3,p,d#,d#3,p,d#,p,d#3,p,f3,4p.,f,4p,g3,p,g,g3,p,a#3,p,c,p,c3,p,f5,p,c,p,c5,d#3,f5,d#,d#3,p,d#,f5,d#3,g5,f3,p,f5,p,f,p,f5,g3,g5,g,g3,p,a#3,p,c,p,c3,g5,f5,p,c,g5,c5,d#3,f5,d#,d#3,p,d#,f5,d#3,g5,f3,g,f5,p,f,g5,f5,g3,g5,g,g3,p,a#3,d#5,c,p,c3,g5,f5,c5,c,g5,c5,d#3,f5,d#,d#3,g5,d#,f5,d#3,g5,f3,g,f5,d#5,f,g5,f5,g3,g5,g,g3,f5,a#3,d#5,c,c5,c3,g5,f5,p,c,g5,c5,d#3,f5,d#,d#3,p,d#,f5,d#3,g5,f3,g,f5,p,f,g5,f5,g3,g5,g,g3,p,a#3,d#5,c,p,c3,p,f5,p,c,p,c5,d#3,f5,d#,d#3,p,d#,f5,d#3,g5,f3,p,f5,p,f,p,f5,g3,g5,g,g3,p,a#3,p,c,p,c3,4p.,c,4p,d#3,p,d#,d#3,p,d#,p,d#3,p,f3,4p.,f,4p,g3,p,g,g3,p,a#3,p,c Ring High:d=16,o=6,b=350:b5,d,b5,d,b5,d,b5,d,d,f,d,f,d,f,d,f,f,a,f,a,f,a,f,a. Ring Low:d=16,o=5,b=355:b4,d,b4,d,b4,d,b4,d,d,f,d,f,d,f,d,f,f,a,f,a,f,a,f,a Scale:d=32,o=5,b=160:c,d,e,f,g,a,b,c6,b,a,g,f,e,d,c Trim Phone:d=16,o=5,b=350:a,b,a,b,a,b,a,4p,a,b,a,b,a,b,a,b,a. Wolf Whistle:d=16,o=5,b=900:8a4,a#4,b4,c,c#,d,d#,e,f,f#,g,g#,a,a#,b,c6,8c#6,d6,d#6,e6,f6,4p,4p,a4,a#4,b4,c,c#,d,d#,e,f,f#,g,g#,a,a#,b,a#,a,g#,g,f#,f,e,d#,d,c#,c,b4,a#4,a4 Van Halen-Eruption:d=32,o=5,b=120:c#6,16c#,e,g#,16c#,e,g#,16c#,e,g#,16c#,e,c#6,16c#,e,g#,16c#,e,g#,16c#,e,g#,16c#,e,a,16c#,e,a,16c#,e,a,16c#,e,a,16c#,e,a,16c#,e,a,16c#,e,a,16c#,e,a,16c#,d#,a,16d#,f#,a,16d#,f#,a,16d#,f#,a,16d#,f#,a,16d#,f#,a,16d#,f#,b,16d#,f#,b,16d#,f#,b,16e,g#,b,16e,g#,b,16e,g#,b,16e,g#,b,16e,g#,b,16e,g#,b,16e,g#,b,16e,g#,c6,16e,g,b,16e,g,b,16e,g,b,16e,g,b,16e,g,b,16e,g,d6,16e,g,d6,16e,f#,d6,16f#,a,d6,16f#,a,d6,16f#,a,d6,16f#,a,d6,16f#,a,d6,16f#,a,e6,16f#,a,e6,16f#,a,e6,16b,g#,e6,16b,g#,e6,16b,g#,e6,16b,g#,e6,16b,g#,e6,b,16g#,e6,b,16g#,e6,b,16g#,e6,16g#.,e6,g#,16b,e6,16b.,e6,b,16d6,e6,a#,16c#6,e6,a#,16c#6,e6,a,16c6,e6,a,16c6,e6,g#,16b,e6,g#,b,d6,e6,b,16d6,e6,b,16d6,e6,a#,16c#6,e6,a#,16c#6,e6,a,16c6,e6,a,16c6,e6,g#,16b,e6,g#,16b,d6,a,16c6,d6,a,16c6,d6,g#,16b,d6,g#,16b,d6,g,16a#,d6,g,16a#,d6,f#,a,16d6,f#,a,16c6,g,16a#,c6,g,16a#,c6,f#,16a,c6,f#,16a,c6,f,16g#,c6,f,16g#,c6,e,16g,c6,e,16g,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,e,16g,b,e,16g,b,e,16g,b,e,16g,b,e,16g,b,e,16g,b,e,16g,b,e,16g,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,d#,16f#,b,e,16g,b,d#,16f#,b,e,16g,b,d#,16f#,b,e,16g,b,d#,16f#,b,e,16g,b,d#,16f#,b,e,16g,b,d#,16f#,b,e,16g,b,d#,16f#,b,e,16g,b,d#,16f#,b,e,16g,b,d#,16f#,b,4e,4a#4,4e.3 Zorba (Give it time!):d=16,o=5,b=125:16c#6,2d6,2p,c#6,2d6,2p,32e6,32d6,32c#6,2d6,2p,c#6,2d6,2p,b,2c6,2p,32d6,32c6,32b,2c6,2p,a#,2b,4p,8p,32c6,32b,32a,32g,32b,2a,2p,32a,32g,32f#,32a,1g,1p,8c#6,8d6,8d6,8d6,8d6,8d6,8d6,8d6,8c#6,8d6,8d6,8d6,8d6,8d6,e6,d6,c#6,e6,8c#6,8d6,8d6,8d6,8d6,8d6,8d6,8d6,8c#6,8d6,8d6,8d6,8d6,8d6,e6,d6,c#6,e6,8b,8c6,8c6,8c6,8c6,8c6,8c6,8c6,8b,8c6,8c6,8c6,8c6,8c6,d6,c6,b5,d6,8b,8c6,8c6,8c6,8c6,8c6,8c6,8c6,8b,8c6,8c6,8c6,8c6,8c6,8c6,8c6,c#6.,d6.,d6.,d6.,d6.,d6.,d6.,d6.,c#6.,d6.,d6.,d6.,d6.,d6.,32e6.,32d6.,32c#6.,32e6.,c#6.,d6.,d6.,d6.,d6.,d6.,d6.,d6.,c#6.,d6.,d6.,d6.,d6.,d6.,32e6.,32d6.,32c#6.,32e6.,b.,c6.,c6.,c6.,c6.,c6.,c6.,c6.,b.,c6.,c6.,c6.,c6.,c6.,32d6.,32c6.,32b5.,32d6.,b.,c6.,c6.,c6.,c6.,c6.,c6.,c6.,b.,c6.,c6.,c6.,c6.,c6.,c6.,c6.,To play any of the tunes use the following code in the event handlers for the buttons:
void CMyDlg::OnStop() {Toney.Stop();}
void CMyDlg::OnPlay() {
CString S;
CComboBox* List=(CComboBox*)GetDlgItem(nID);
List->GetLBText(List->GetCurSel(), S);
Toney.Play(S, "RingTones.RTTTL");
}
The playing is done in a separate Thread using CThread (OnPlay doesn't need to call OnStop first, because that is done by CThread automatically).