Jun 152011

Here is a handy function I use to convert an icon to a HBITMAP. It’s not something you need to do very often, but very useful on the odd time when you need this functionality.

HBITMAP CMyDialog::ConvertIconToBitmap(HICON hIcon, int nWidth, int nHeight, COLORREF clrBackground)
{
    if(hIcon == NULL)
        return NULL;   

    HDC hDC = NULL;
    HDC hMemDC = NULL;
    HBITMAP hBitmap = NULL;
    HBITMAP hOldBitmap = NULL;
    HBRUSH hBrush = NULL;
    HBRUSH hOldBrush = NULL;

    BOOL bGotBitmap = false;

    try {
        hDC = ::GetDC(NULL);
        if (hDC != NULL) {
            hBitmap = CreateCompatibleBitmap(hDC, nWidth, nHeight);
            if (hBitmap != NULL) {
                hMemDC = CreateCompatibleDC(hDC);
                if (hMemDC != NULL) {
                    // Select the bitmap into the memory device context
                    hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);

                    hBrush = CreateSolidBrush(clrBackground);
                    if (hBrush != NULL) {
                        // Fill the bitmap with the background color
                        hOldBrush = (HBRUSH)SelectObject(hMemDC, hBrush);
                        PatBlt(hMemDC, 0, 0, nWidth, nHeight, PATCOPY);

                        // Draw the icon
                        DrawIconEx(hMemDC, 0, 0, hIcon, nWidth, nHeight, 0, NULL, DI_NORMAL);
                        bGotBitmap = true;
                    }
                }
            }
        }

        // Cleanup
        if (hOldBrush != NULL)
            SelectObject(hMemDC, hOldBrush);
        if (hOldBitmap != NULL)
            SelectObject(hMemDC, hOldBitmap);
        if (hBrush != NULL)
            DeleteObject(hBrush);
        if (hMemDC != NULL)
            DeleteDC(hMemDC);
        if (hDC != NULL)
            ::ReleaseDC(NULL, hDC);

        if (!bGotBitmap && hBitmap != NULL) {
            DeleteObject(hBitmap);
            hBitmap = NULL;
        }
    }
    catch(...) {
    }
    return hBitmap;
}

To use this function simply pass in an icon.

HICON hIcon = ::LoadIcon(NULL, IDI_ERROR);
if(hIcon != NULL) {
    hBmp = ConvertIconToBitmap(hIcon, 48, 48, RGB(255,255,255));
    if(hBmp != NULL) {
        CBitmap *cBmp = CBitmap::FromHandle(hBmp);
        // Do whatever you need with the CBitmap
        // ...
        // Remember to clean up the hBmp
        DeleteObject(hBmp);
        // Note: in this case you don't have to destroy the icon since it's a system icon
    }
}

An example of a use for this function is converting an icon returned from a call to SHGetFileInfo to retrieve an icon for a file.

SHFILEINFO sfi;
SHGetFileInfo(
        (LPCTSTR)strFileName,
        FILE_ATTRIBUTE_NORMAL,
        &sfi,
        sizeof(SHFILEINFO),
        SHGFI_ICON | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES);
}

HBITMAP hBmp = ConvertIconToBitmap(sfi.hIcon, 48, 48, RGB(255,255,255));

Then you could use that bitmap to add to a CImageList just as if it were a bitmap you’d read from a file or resource:

if(hBmp != NULL) {
    CBitmap *cBmp = CBitmap::FromHandle(hBmp);
    if(cBmp != NULL)
        m_imageList.Replace(0,cBmp,NULL);
}
Apr 012011

Well, almost 10 minutes anyway.  While at the MVP Summit last month I got recorded with some of the other MVPs to appear on Microsoft Channel 9 web network.  We were a little apprehensive when they approached us since we didn’t know what kinds of questions they were going to ask.  As it turned out the questions weren’t too hard so the initial stress was all for nothing and it turned out to be kind of fun.  Of, course, during the interview they delivered the afternoon coffee and cookies so we all look like we’re more focused on trying to get back to the session before the good stuff was all gone.

Here’s the link:  Channel 9 Interview (Angel, PJ, Tom, Sheng)

Apr 012011

This function returns the local GMT offset. It’s just a handy wrapper so I don’t have to remember all this “stuff” when I need the info.

//
// Returns the current local GMT offset
//
int GetGMT()
{
  TIME_ZONE_INFORMATION tzinfo;
  GetTimeZoneInformation(&tzinfo);
  return -(tzinfo.Bias/15);
}
Mar 312011

One of the great pleasures of my year so far was the recent trip I took last month to Microsoft for the yearly MVP Summit. Microsoft does an incredible job of fostering community among users and, somehow, gets us to support each other when many other companies tend to just not get it.

The MVP Summet was great as usual, with the usual “stuff” we can’t talk about, but I wanted to mention some overall feelings I got this year that won’t take me outside of my Non-Disclosure Agreement:

  • I feel a renewed sense of hope for C++. Microsoft is putting a lot of effort working towards standards and functionality. In the latest version of Visual Studio (VS 2010) we got some great C++ improvements, but if what I heard turns out to be true we can look forward to lots of new C++ functionality in the not too distant future. I feel more than ever that C++ is still a first class Windows application development platform.
  • This doesn’t minimize the excitement around the .NET languages (including C++/CLI) for the near future. There are tons of new improvements being discussed for the next release. I wish I could give more details, but I can tell you the talks were very exciting.
  • I feel a renewed sense of hope in Microsoft as a development platform provider. I am constantly hearing about how great the open source tools are and how Microsoft may be lagging behind, and how MFC is dead, and all kinds of other myths. There are so many uses for native Windows programming skills and I don’t see that paradigm changing dramatically. I felt, however, that the marriage of paradigms may becoming more of a day to day reality. For example, I’m seeing more and more where people are using C#/.NET for the front end GUI and C++/MFC/ATL/CLI for back end parts that need to either get closer to the OS or hardware, or maybe need specific speed improvements. This is true for many technologies, for example, writing games using direct screen access.

Today’s programmer needs lots of tools in their arsenal so they will know the right tool to use for the job at hand. My trip to Microsoft gave me a renewed hope that Microsoft knows this and is planning on continuing making our jobs easier as every new version is released.

I met many great people at the Summit.  Some were from Microsoft and others from over, I think they said 77 countries.  It was an overwhelming experience.  I can imagine that this is a win-win for Microsoft.  They get our attention for a few days and we take their message, what we’re allowed to talk about, out to the rest of the world.  It reminded me that technology is way more than just an industry.  It’s a culture.  Microsoft seems to know that and has done a nice job of incorporating that into their core beliefs.

Feb 202011

Occasionally I want to yield a tight loop to allow messages from the GUI to be updated.  One way to do this is to put the loop processing in a thread, but if I don’t want to go to that trouble for something really simple, I sometimes just call this function at a safe place in the loop.

//
// Release main thread for background processing
//
void GiveTime()
{
	// Idle until the screen redraws itself, et. al.
	MSG msg;
	while (::PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) ) {
		if (!AfxGetThread()->PumpMessage( )) {
			::PostQuitMessage(0);
			break;
		}
	}
	// let MFC do its idle processing
	LONG lIdle = 0;
	while (AfxGetApp()->OnIdle(lIdle++ ))
		;
}

For example:

for(int i = 0; i < 1000000; ++i) {
    // Do something
    GiveTime();
}

You may also want to throttle how often this happens to help the loop run faster.

for(int i = 0; i < 1000000; ++i) {
    // Do something
    if((i % 1000) == 0)
        GiveTime();
}
Jan 102011

This is a small function that will format a number to string that contains commas.  It’s function is to make the numbers easier to read before displaying them.  One nice feature with this function is it creates the string with the formatting based on the current locale being used by the application.  Simple, but useful.

PTCHAR Comma(LONGLONG value,PTCHAR bufout, UINT nSize)
{
	TCHAR bufin[30]; // 30 digits is a really big number
	TCHAR dsep[5], tsep[5];
	NUMBERFMT fmt;
	fmt.NumDigits = 0;
	::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ILZERO,bufin,2);
	fmt.LeadingZero = _ttoi(bufin);
	fmt.Grouping = 3;
	::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SDECIMAL,dsep,4);
	fmt.lpDecimalSep = dsep;
	::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_STHOUSAND ,tsep,4);
	fmt.lpThousandSep = tsep;
	::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_INEGNUMBER ,bufin,2);
	fmt.NegativeOrder = _ttoi(bufin); 

	_stprintf_s(bufin, _T("%I64d"), value);
	::GetNumberFormat(LOCALE_USER_DEFAULT, 0, bufin, &fmt, bufout, nSize);
	return bufout;
}

For example, it could be used to fill in a subitem in a list control:

m_cList.SetItemText(nItem,2,Comma(nValue, buf, 29));
Jan 082011

This is a really simple function, but I use it all the time. It creates a path to a temporary file name that can be used, well, temporarily. The files are not cleaned up or anything, but they are not going to conflict with other files that may be in the same temp folders.

Enjoy.

CString GetTempFilePath(LPCTSTR strPattern)
{
    CString csPath;
    if(GetTempPath(_MAX_PATH,csPath.GetBuffer(_MAX_PATH+1)) != 0) {
        csPath.ReleaseBuffer();
        CString csTempFile;
        if(GetTempFileName(csPath,strPattern,0,csTempFile.GetBuffer(_MAX_PATH+1)) != 0) {
            csTempFile.ReleaseBuffer();
            return csTempFile;
        }
    }
    return CString();
}
Nov 202010

This function will create an entire path to a folder if any part of the path doesn’t already exist. It is useful for ensuring that a path exists before trying to open a new file in a subfolder. This uses some of the file functions I posted last month.

//
//	Create all folders needed to get to a path if they are not already there.
//
void CreateAllDirectories(CString strDir)
{
	if(strDir.IsEmpty())
		return;
        // remove ending / if exists
	RemoveSlash(strDir);

	 // base case . . .if directory exists
	if(FileExists(strDir)) 
		return;

 // recursive call, one fewer directories
 int nFound = strDir.ReverseFind(_T('\\'));
 CreateAllDirectories(strDir.Left(nFound)); 

 // actual work
 CreateDirectory(strDir,NULL); 
}
Oct 102010

I just started using Visual Studio 2010 and I’m really pleased with the updates. The biggest thing I’ve noticed are:

  • ClassWizard is back. Well, it’s not the complete ClassWizard we used to have in V6, but it’s pretty darn nice.
  • New standard C++0x items have been added (Lamdas, rvalues, auto keyword, etc.
  • I like the new look and feel.
  • The ribbon editor is really nice for those of you who want to use ribbon type toolbars.
  • You can now set some search folders specific to a project like include and lib.
  • New MFC features mostly for support of Windows 7.
  • Better integration of the MFC Feature Pack classes.
  • Way better Intellisense for C++.
  • I actually like the new WPF based editor with better color hi-lighting. This new interface opens the door in the future to do a lot more that WPF enables.
  • Navigate To dialog is really nice.
  • Etc., etc. etc.

You can get the whole scoop here: What’s New In Visual C++ 2010

There are lots of improvements to C# has well including .NET 4.0.  I think it’s worth the upgrade cost just for the productivity improvements alone.

Oct 042010

These are some handy functions I use that wrap the SHLWAPI functions from Win32 SDK. All of these could be done with the individual calls, these functions just make it a little more convenient.

//
//	Returns the file portion from a path
//
CString GetFileOnly(LPCTSTR Path)
{
//	Strip off the path and return just the filename part
	CString temp = (LPCTSTR) Path; // Force CString to make a copy
	::PathStripPath(temp.GetBuffer(0));
	temp.ReleaseBuffer(-1);
	return temp;
}
//
//	Returns the folder portion from a path
//
CString GetFolderOnly(LPCTSTR Path)
{
//	Strip off the file name so we can direct the file scanning dialog to go
//	back to the same directory as before.
	CString temp = (LPCTSTR) Path; // Force CString to make a copy
	::PathRemoveFileSpec(temp.GetBuffer(0));
	temp.ReleaseBuffer(-1);
	return temp;
}
//
//	Adds a backslash to the end of a path if it is needed
//
CString AddSlash(LPCTSTR Path)
{
	CString cs = Path;
	::PathAddBackslash(cs.GetBuffer(_MAX_PATH));
	cs.ReleaseBuffer(-1);
	if(cs.IsEmpty())
		cs = BACK_SLASH;
	return cs;
}
//
//	Removes a backslash from the end of a path if it is there
//
CString RemoveSlash(LPCTSTR Path)
{
	CString cs = Path;
	::PathRemoveBackslash(cs.GetBuffer(_MAX_PATH));
	cs.ReleaseBuffer(-1);
	return cs;
}
//
//	Adds a folder path and file together to make a file path
//
CString AddPathAndFile(LPCTSTR Folder, LPCTSTR File)
{
	CString cs = Folder;
	::PathAddBackslash(cs.GetBuffer(_MAX_PATH));
	::PathAppend(cs.GetBuffer(_MAX_PATH),File);
	cs.ReleaseBuffer(-1);
	return cs;
}
//
//	Adds a .ext to the end of a file path
//
CString AddExt(LPCTSTR Path, LPCTSTR Ext)
{
	CString cs = Path;
	::PathAddExtension(cs.GetBuffer(_MAX_PATH),Ext);
	cs.ReleaseBuffer(-1);
	return cs;
}
//
//	Returns true if file exists or false if file does not exist
//
BOOL FileExists(LPCTSTR Path)
{
	return (::PathFileExists(Path));
}
//
//	Returns true if file is read only
//
BOOL FileReadOnly(LPCTSTR Path)
{
	DWORD nAttributes;
	if((nAttributes = GetFileAttributes(Path)) != -1) {
		if(nAttributes & FILE_ATTRIBUTE_READONLY)
			return true;
	}
	return false;
}
//
// Returns just the .ext part of the file path
//
CString GetFileExt(LPCTSTR Path)
{
	CString cs;
	cs = ::PathFindExtension(Path);
	return cs;
}
CString GetFileName(LPCTSTR Path)
{
	CString csFile = GetFileOnly(Path);
	if(!GetFileExt(csFile).IsEmpty()) { // Is there an extension
		::PathRemoveExtension(csFile.GetBuffer(_MAX_PATH));
		csFile.ReleaseBuffer();
	}
	return csFile;
}
//
// Exchanges one file extension for another and returns the new fiel path
//
CString RenameFileExt(LPCTSTR Path, LPCTSTR Ext)
{
	CString cs = Path;
	::PathRenameExtension(cs.GetBuffer(_MAX_PATH), Ext);
	return cs;
}