Introduction

Writing software has become an art. I have always felt that programmers who write software that targets a "windowing" environment have one of two choices:

Write the software; give it a workable UI and leave it.

Write the software; spend time and effort making it look good and satisfy the senses of the customer.

In my experience, I have found that if the software looks good or exciting or just generally appealing to the end user, the process of adoption of the software in the user's life becomes less painful. Developers who take pride in presenting a "beautiful" user interface to their technology that the user never sees can consider themselves artists in their own right.

In an effort to make my software more "beautiful", I've created the CKCSideBannerWnd class. The class brings to life the concept of banners, which can be used to give the user context as to what the purpose is of the current window that they are staring at.

Background

Every time I use software, I always take notes of what I liked about the UI, what I didn't like, what was a good idea, what wasn't and, more importantly, why. One of the concepts that I've always enjoyed is that of a banner (like those found in InstallShield/Wise installation wizards, CPropertyEx derived wizards, etc).

Personally, I feel that those kinds of "well placed" banners give the software UI a more appealing, more professional look. So I took it upon myself to go and whip up a class that can pretty much do what the mentioned banners can do, but a little bit more (like attaching the banner to any edge of a window).

I also felt that the programmer should not have to worry about making allowances for the banner, and that it should be a quick exercise to attach a banner to any window. I have to admit, 2 of my own software packages have already been "upgraded" with the banner control :)

Using the Code

First off, to use the CKCSideBannerWnd class, you will need to include the following files in your project:

KCSideBannerWnd.h

KCSideBannerWnd.cpp

You will also need to copy the WndUtil.h file into the same directory as the KCSideBannerWnd.* files. Once you have the class at your disposal, using it is simple. To attach a banner to a dialog, add a member to your dialog class like this:

CKCSideBannerWnd can be attached to any other window, as well. Simply follow the above mentioned steps for attaching the banner to views, etc.

How It Works

The basic working of the control is as follows:

When Attach() is called, get hold of the parent window (passed in as first parameter).

Make space for the banner by growing the parent window in the appropriate direction.

Enumerate all of the parent's child windows (excluding the banner) and offset them accordingly.

Create the banner control and attach it.

The beauty of this technique is that the programmer does not specifically have to make space for the banner on any of his/her already defined dialogs or windows. Just a simple attach and the control will make room for itself.

Points of Interest

This article would have been published a lot sooner had it not been for an interesting nuance that I ran into with Windows and its combobox handling. I had been testing this control on a number of different dialogs and everything was working great, until I used it on a dialog box which contained 2 combobox controls.

When the banner was attached, the combobox controls "lost" their ability to display selected text or text that had been entered by the user on the keyboard. In fact, it seemed as if the edit control of the combobox had just stopped working. (The listbox control worked just fine :))

I posted to the forums, but no one seemed to be able to give me an answer as to why this was happening. So off I went on a bug(??) hunt to see what I was doing wrong to cause combobox controls to partially stop working.

What I found eventually was that when EnumChildWindows() is called, and it in turn calls the user supplier handler with each child HWND, one of the children that it found was in fact the edit control of the combobox. The problem came in with me moving the actual edit control, as well as the combobox control.

The solution to this problem was to add a check in the enumeration function which called GetParent() against the so-called child HWND and checked that the parent was in fact that parent as I knew it (the dialog or owning window). The edit control of the combobox control did in fact return the combobox's HWND as its parent and, since I was no longer moving both combobox and its contained edit control, everything worked great.

The uiID is the ID that will be used by the parent to refer to this control. It is defaulted to 0xFFF0.

void SetSize(int nSize)

Adjusts the size of the banner. This is relative in terms of adjusting either the height or the width of the banner and is dependent on where the banner is positioned. In other words, if the banner is on the the left or right, the width will be adjusted. If the banner is on the top or bottom, the height will be adjusted.

int GetSize()

Returns the current size of the banner.

void UpdateSize()

Call this when the size of the parent window has changed.

void SetPosFlag(unsignedint uFlags)

Call this to reposition the banner. The same flags apply as discussed in Attach().

unsignedint GetPosFlag()

Returns the current position flag.

void SetFillFlag(unsignedint uFlag)

Call this to set the fill type of the banner. Possible values are:

KCSB_FILL_FLAT:

Fills the banner with the color set with SetColBkg().

KCSB_FILL_GRADIENT:

Fills the banner with a gradient starting at the color set with SetColBkg() and moving to the color set with SetColBkg2().

KCSB_FILL_TEXTURE:

Fills the background of the banner with a bitmapped texture that can be set with SetTexture().

unsignedint GetFillFlag()

Returns the current fill flag.

void SetTitle(constchar* lpszTitle)

Sets the title (main string) of the banner.

CString GetTitle()

Returns the current title of the banner.

void SetCaption(constchar* lpszTitle)

Sets the caption (secondary string) of the banner.

CString GetCaption()

Returns the current caption of the banner.

void SetColBkg(COLORREF col)

Sets the primary background color. When a gradient fill is active, this will be the color that the gradient starts at.

COLORREF GetColBkg()

Returns the primary background color.

void SetColBkg2(COLORREF col)

Sets the secondary background color. When a gradient fill is active, this will be the color that the gradient will move towards. In flat fill mode, this color serves no purpose.

COLORREF GetColBkg2()

Returns the secondary background color.

void SetColEdge(COLORREF col)

Sets the color of the edge.

COLORREF GetColEdge()

Returns the color of the edge.

COLORREF SetColTxtTitle(COLORREF col)

Sets the color of the title text.

COLORREF GetColTxtTitle()

Returns the color of the title text.

COLORREF SetColTxtCaption(COLORREF col)

Sets the color of the caption text.

COLORREF SetColTxtCaption()

Returns the color of the caption text.

void SetEdgeOffset(CSize szOffset)

Sets the XY offset of the title text from the edge.

CSize GetEdgeOffset()

Returns the XY offset of the title text from the edge.

void SetCaptionOffset(CSize szOffset)

Sets the XY offset of the caption from the start of the title. In other words, if the edge offset is (5, 5) and the caption offset is also (5, 5), then the caption will be offset 5 pixels away from the title.

CSize GetCaptionOffset()

Returns the XY offset of the caption text relative to the title.

void SetTitleFont(CFont* pFont)

Sets the font used to draw the title text.

void GetTitleFont(LOGFONT* pFont)

Returns the font used to draw the title text in the LOGFONT structure passed into the function.

void SetCaptionFont(CFont* pFont)

Sets the font used to draw the caption text.

void GetCaptionFont(LOGFONT* pFont)

Returns the font used to draw the caption text in the LOGFONT structure passed into the function.

Sets the ICON that will be drawn in the banner. bSelfDelete indicates to the control whether you will be deleting the HICON resource (bSelfDelete = false) or whether the control can delete it when it's no longer needed (bSelfDelete = true).

uiIconPos can be one or more of the following values:

KCSB_ICON_LEFT:

Draws the icon on the left of the banner. If the banner is attached to the left of the window, the icon will be drawn at the bottom. If the banner is attached to the right of the window, the icon will be drawn at the top.

Note: This flag cannot be combined with the KCSB_ICON_RIGHT flag.

KCSB_ICON_RIGHT:

Draws the icon on the right of the banner. If the banner is attached to the left of the window, the icon will be drawn at the top. If the banner is attached to the right of the window, the icon will be drawn at the bottom.

Note: This flag cannot be combined with the KCSB_ICON_LEFT flag.

KCSB_ICON_TOP:

Draws the icon at the top of the banner. If the banner is attached to the left of the window, the icon will be drawn on the left. If the banner is attached to the right of the window, the icon will be drawn on the right.

Note: This flag cannot be combined with the KCSB_ICON_VCENTER or KCSB_ICON_BOTTOM flags.

KCSB_ICON_VCENTER:

Draws the icon vertically centered in the banner.

Note: This flag cannot be combined with the KCSB_ICON_TOP or KCSB_ICON_BOTTOM flags

KCSB_ICON_BOTTOM:

Draws the icon at the bottom of the banner. If the banner is attached to the left of the window, the icon will be drawn on the right. If the banner is attached to the right of the window, the icon will be drawn on the left.

Note: This flag cannot be combined with the KCSB_ICON_VCENTER or KCSB_ICON_TOP flags

void SetIconPos(UINT uiIconPos)

Set the icon's position in the banner. Use the flags described in SetIcon().

UINT GetIconPos()

Returns the icon positional flags (refer to SetIcon()).

void SetTexture(HBITMAP hBitmap, bool bSelfDelete = true)

Sets the bitmapped texture that will be used to draw the background when the control's fill flag is set to KCSB_FILL_TEXTURE (See SetFillFlag()). The bSelfDelete flag indicates whether you will delete the HBITMAP resource (bSelfDelete = false) or whether the control can delete it when it no longer needs it (bSelfDelete = true).

HBITMAP GetTexture()

Returns the HBITMAP of the texture that is used to draw the textured background.

Things I'd Like to Try When I Have More Time...

Personally, I have never written a control that "makes space" for itself, and the concept has grasped my imagination somewhat. I have been contemplating the idea of developing a background app/service that monitors all HWND creations and attaches a banner to all windows of say, type DIALOG. This would merely be for purposes of fun and educational value... but still a cool idea, I think.

History

2003-10-22

First public release.

2003-10-23

Added #pragma comment(lib, "MSIMG32.LIB") to the KCSIDEBANNERWND.H file to alleviate the necessity of changing the project settings - Thanks to Warren Stevens.

Added a release build of the project to the *.zip file download and removed all unnecessary files (e.g.: *.NCB files) - Thanks again to Warren :).

Fixed a bug with Cyrillic (and hopefully other fonts that may have suffered).

Added ability to provide a bitmap for the background (see SetTexture()) .

Added ability to indicate whether the control can clean up the HICON and HBITMAP resources or whether the controlling program will clean up after itself.

2003-10-29

Removed the total dependence on MSIMG32.LIB. The control now checks if it can dynamically load MSIMG32.DLL and, if not, it will use its own Gradient function (thanks to John A. Johnson). This idea was given to me by Dominik Reichl and he got the idea from Irek Zielinski's CStaticGradient control.

Im trying to use latest Visual Studio 2008 + MFC Feature Pack. This allow to make Office 2007 like ribbon tool bar control or Outlook like side bars.Meet problem with integration of your banner in wizard generated code.Effect controls partly overdrawn by banner, not shifted to side.In some cases like BOTTOM banner havent appeared.

Following Alexander Bischofberger's remark, if one of the characters is outside the range 0 to 255 (and "à" is) - most asian characters would be as well - there is a problem... Hence, I have slightly updated your code so it would cope with real "Unicode" characters.

I've tested your class into a CPropertySheet/CPropertyPage, but I've found some trouble.

I've used IsKindOf() to test for CPropertyPage and in this case I've resized the parent (CPropertySheet), but the internal painted area remain the same and the problem is also (in case of _TOP) to move button (easy) and the bottom line (I've found no solution). Probably also the object into CPropertySheet must be enumerated and moved.

Pretty strange to use pack(1) for entire application, at least in common situations. You can always use 'push' for saving old state, applying your 1 byte alignment and restore it only for this library. Then, returning to your 1 byte alignment of course.

Hello peter,Firstly, congratulation for your award !About the "points of interest" in your article, I fall down the same problem in one of my software. I had write a CDialog derived class that "center" or "adjust" all the control in the runtime size of the dialog. The idea is to compute the size of the dialog box in proportion of the screen size, and to center all the control from their orignal position, or to adjust them (change their size in proportion of the original dialog size and the runtime screen size).

I found the same problem for combo, and the same solution (perhaps I will take more time to read and write the MFC forums...) !

The center function work well, but the adjust not, because of the size of the combo doesn't change. As you have work on a similar problem, have you encountered something like that, and found a solution ?

Thanks. Believe me, it was a surprise. I didn't think that this control would be as popular as it has become...

TRECHE wrote:The center function work well, but the adjust not, because of the size of the combo doesn't change. As you have work on a similar problem, have you encountered something like that, and found a solution ?

What exactly were you doing in the Adjust() function that was different from the Center() functionality? If you have a look here, PJ Arends explained what actually happened with the Combobox control and why I had been experiencing the problem. Maybe this will help

Thank's for your help. I will read it very soon.For both function I use MoveWindow(x,y,width,height)For center, width and height are the same as original.For adjust they are...adjusted in proportion. For all the control it works except for combo for which the size doesn't change.

if you change m_strCaption to "LiXiaoQing"(Chinese Spell), You can see BugI change to following:////////////////////////////////////////////////////////////////////////////// 2006.04.16 17:02 Modify by LiXiaoQing to Display GB2312 char pDC->SetTextColor(m_colTxtCaption); nFont.CreateFontIndirect(&m_lfCaption); pOldFont = pDC->SelectObject(&nFont);

Yes, I am aware of the problem when attaching the banner to a CFrameWnd derived object that contains CControlBar type controls. The problem comes in when the control requests the available area for itself to attach into. The CFrameWnd class does not calculate the area to take the CControlBar controls into account...

I will have a look at how to get around this, however one possible solution would be to create your own CControlBar derivative that merely contains the CKCSideBannerWnd control, and attach that to the CMainFrame.

Using the control in dialogs requires a lot of initialisation e.g. setting texts and icon. To reduce the code needed for each dialog I wrote the following function to set up the control - perhaps it helps you using this great control:

I just wanted to put it to my project (which has also a German UI) and figured out some problems with the German characters ÄÖÜäöüß and even € (EUR). It seems that CDC::GetCharWidth() return bad values for these characters. My only solution is to replace the arrWidths[] entries for äö... with the corresponding values of ao...

if i was to make such a banner i would make a class that attaches to a certain hwnd (a dialog or whatever) and then subclasses it. next i would intercept WM_NCCALCSIZE, remember currently calculated rect (after caption and borders) and modify it to make some room for the banner. another interesting message would be WM_NCPAINT where i can use the rect from WM_NCCALCSIZE and the attached hwnd to get a window dc and paint directly.

benefits: no hacks with moving controls around -- suppose the developer actually does move some controls at fixed locations. in your case he/she will need to fix the positioning code.

in the proposed case only the hwnd client size will be affect and control positions on the hwnd basicly remain the same (although space is still somewhat reduced).

I've added the UNICODE fix that was mentioned in the thread below this one. Sorry about that! Atleast its done now. This will probably be the last update unless someone (or I) have a great idea of how to improve this control some more...

hi,I tried to compile the demo projet and I have execution error "can't find MSGIM32.dll". I scanned my disk, I found MSGIM32.lib. I use VC++ 6.0 and NT 4.0.I'd like to link statically the MSGIM32. Can you help me ?

Ok, the first problem is that you are running NT 4.0. According to MSDN, they say the dll/lib is available on:

Windows NT/2000/XP: Included in Windows 2000 and later.Windows 95/98/Me: Included in Windows 98 and later.

secondly, I know you've probably made a typo, but you made it twice (twece), so its actually MSIMG32. What you can do is get hold of a 98, or 2K machine, find the MSIMG32.DLL file and copy that over to your NT4 machines system(32) directory.

That should resolve the issue

Regarding the static linking question, I'm not sure of the lib is available as a static link, or if merely acts as a stub. I have a feeling the lib is merely a stub (can't be statically linked)

A few minor changes are necessary to make your control UNICODE compatible:1) enclose the rest of the string literals with _T("") including the definition of KCSB_CLASSNAME2) change the paramters to the SetTitle and SetCaption functions from const char* to LPCTSTR3) change the variable on line 541 of the cpp file from BYTE ch; to TCHAR ch;

A little issue though, can we change color of title and caption texts (without modifying your source code)? Texts became unreadable when I tried to use a dark backgroud. Also, since we are at it, will you consider bitmap backgroud?

And out of curious, why don't you just integrate WndUtil.h into KCSideBannerWnd.h so we only need to maintain 2 files instead of 3? Of course, if you are reusing WndUtil.h in multiple applications then forget about it.

Hi. Thanks for reminding me to add those obvious text colour functions. The files and article have been updated.I have also started a TO DO list... and you'll notice that I'm going to be adding the ability to supply your own bitmap as the background.

Regarding WndUtil.h, yes, I use it in a lot of my control nowadays. This is merely the first control that I've published in which I use it. I'll be publishing a few more controls in the upcoming weeks that make use to the WndUtil.h ... and I'll also be extending the header itself to do some other stuff...

From your article:Personally, I have never written a control that "makes space" for itself, and the concept has grasped my imagination somewhat. I have been contemplating the idea of developing a background app/service that monitors all HWND creations and attaches a banner to all windows of say, type DIALOG. This would merely be for purposes of fun and educational value... but still a cool idea I think.

Well ... not shure about how and if it can be useful, however, an idea can be to hook the WH_CALLWNDPROC and trap the WM_INITDIALOG message.This article can give some ideas. It is not necessary to use exactly that code. But the principle could be right.

Of course, in that article I was thinking to applications, not to "services" that do the trick to ll the windows every other would have created. But the idea is essentially the same.Let me know. By now, get my 5 !

Thanks for that. Yes, I was also thinking of hooking ... but was thinking more along the lines of hooking the CBT hook. I think either will work fine. But as I said, attaching the banner to EACH and every window would merely be an exercise of fun and trying to see "if I can do it".

The edit control is actually a child of the combobox (ID = 1001). The reason it is returned in your ChildEnumProc is that EnumChildWindows will enumerate all the child windows of the given window, and all the children of the child windows, for as many generations as it takes to enumerate them all. The reason it does not find the list portion of the combobox is because the list box is actually a child of the desktop.