MFC Controls
If you have Cookies enabled,
you can check to see if any files you have downloaded
have been updated since you downloaded them
by clicking here.
|
|
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...
|
Example Calendar Control
This class is as example of several principles for the creation of a quick and efficient control in MFC.
To create a simple control class just use ClassWizard [Add Class...][New...] and make the class derived from CButton.
Drag out a Button on the dialog, then set the Button's [OwnerDraw] Property to checked.
Now to draw the control we need the Virtual Function DrawItem (NOT OnDrawItem), so use ClassWizard to add it and add an OnLButtondown handler as well.
Create an instance of your class in your Dialog class (Header File).
In your dialog OnInitDlg() subclass the control to your class using:
Calendar.SubclassDlgItem(IDC_Calendar, this);
Derive a class of your own from CCalendar that implements OnChanging() and OnChanged() if you need to.
You can set the current Day, Month and Year in many ways.
The default Constructor sets the Calendar to Today.
Month and Day are 1 Based, Year is 1900 based and can go up to 137.
You can also set the Background Colour, Text Colour and Specify some extra text for any day in the month using:
Calendar.SetBgColor(RGB(255,255,0));
Calendar.SetTextColor(RGB(255,0,0), 17);
Calendar.SetText("This is the day!", 17);
After altering the Month or Year you may want to redraw the control, so use:
Calendar.RedrawWindow();
to restore the default colours and Text use:
Calendar.Clear();
If you change the Month or Year and don't want the Day to remain selected, set Day to -1 or 0xFF.
Its worth trying to understand the way the maths draws the buttons for any sized control.
Notice how the drawing isn't exactly optimised, but doesn't fill the background all the time (if it did the whole control would flash).
Dynamic Control Positions
CDragBar implements a vertical bar which may be dragged to reposition other Child Windows.
All the Child Windows are positioned by the owner.
In the parent window create an owner-draw button and subclass it.
For example, in a Dialog, use the Resource Editor to make a Button Control, set [Owner Draw] to checked and in the OnInitDialog have the line:
DragBar.SubclassDlgItem(IDC_DragBar,this);
You can now give the Bar a default position to go to when the user double-clicks it (0.0=Left, 0.5=Middle, 1.0=Right):
DragBar.SetDefault(0.3);
If your applications other child window redraw slowly you may wish to stop them redrawing during dragging with:
DragBar.SetLive(false);
Since you will be redrawing controls during OnSize, you need to know when OnInitDialog has created al its child win
dows with a bool:
Updated=true;
Now you can set the initial positions by calling Move with no arguements:
DragBar.Move();
The OnSize Handler would look like this:
void CDragBarTesterDlg::OnSize(UINT nType, int cx, int cy) {
CDialog::OnSize(nType, cx, cy);
if(!Updated) return; // Don't access windows before they've been created
DrawControls();
}
CDragBar passes a special nType to OnSize (SIZE_BAR_DRAGGED) which you can use to filter this message if you need to:
if(nType==SIZE_BAR_DRAGGED) DrawControls();
This would mean that you wouldn't need the "Updated" flag, and that the controls would only move when you tell them to.
Since I haven't found an event which signals the end of a resize drag, this only seems useful for non-resizing windows.
DrawControls() should use the GetClientRect and DragBar.GetPostion functions to move and redraw the controls:
CRect Rect;
GetClientRect(&Rect);
int L=DragBar.GetPosition(Rect.Width()); // Left of the DragBar
const int W=DragBar.Width+1;
...more sums...
GetDlgItem(IDC_EDIT1)->MoveWindow(x,y,cx,cy);
...more moves...
Occasionally you'll need to Redraw a control because MoveWindow doesn't make Listboxes redraw their backgrounds etc.
GetDlgItem(IDC_LIST1)->RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); // Don't need to erase background.
You can download an example project here:
List Box
Decimal Point Aligned Properties
This is a direct replacement for a CListBox which is being used to display property values.
The ListBox holds whatever strings you give it but renders them in a formatted way:
Name: Value
The first colon ':' in the string terminates the Name.
All Property Names are displayed, left-justified.
All columns are automatically sized to ensure all data is displayed.
If Value is not numeric, it is displayed left-justified.
If Value is numeric, it is displayed with decimal point alignment.
This uses two further columns - the integral part being right-justified, and the fractional part being left-justified.
The control also has a concept of Sections.
Sections of properties are separated with a blank line (See how the "Short Section" is separated in the following code example.
The animation shows resizing CPropertyViewer (Left window) and a standard CListBox (Right window).

You can clearly see the decimal alignment and Sections keeping themselves visible.
If SectionalWidths is set (which it is by default) and the window is too narrow to display the whole of the long section,
the Short Section will try to keep the values visible by moving them closer to the name while there is space to do so.
The closeness of the Values to the Names depends on the available Window Width.
This is similar to browser behaviour if each Section were a Table with Width set to 100%.
If AutoCopy is set, and a CEdit control has the focus, clicking on the ListBox will transfer Value to the CEdit.
Otherwise you can set up a Right-Click context menu with Copy and use the Copy functions to copy the Value or whole string to the Clipboard.
Usage:
Put a ListBox in your Dialog I'll assume its called IDC_LIST1
in the Properties for the ListBox set:
- OwnerDraw =Fixed
- HasStrings=Checked
Add the PropertyViewer.h and PropertyViewer.cpp Files to your Project.
In your Dialog Header File include this File:
#include "PropertyViewer.h"
and add a Member to your Dialog Class:
CPropertyViewer PropertyViewer;
In the OnInitDialog() put the following lines:
PropertyViewer.SubclassDlgItem(IDC_LIST1,this);
AddString("Very Long Title Text:");
AddString("");
AddString("Very Long Comment Text.");
AddString("Text Value: Description.");
AddString("Integer Value a: 0");
AddString("Integer Value a: -0");
AddString("Integer Value b: -123");
AddString("Decimal Value a: 3000.0");
AddString("Decimal Value b: 1.234");
AddString("Decimal Value c: 2.2");
AddString("Decimal Value c: -0.00");
AddString("");
AddString("Tool Directions");
AddString("Direction: CCW");
AddString("Inside/Outside: Inside");
AddString("Side: Left");
AddString("");
AddString("Short Section");
AddString("Text: Description.");
AddString("a: 123.4");
AddString("b: 12.34");
AddString("c: 1.234");
AddString("d: 1234");
List Controls
Report View Lists
Full Row Select
The CListCtrl has an overridable void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); function...
But you need to know what the original code is to make your changes!
I, and most other folk, don't have access to the source of CommCtrl.dll, and code I found on the internet was buggy and overcomplicated.
So I threw away all but the guts and have put the results of my efforts here.
This is intended for a CListCtrl or CListView with [Properties][Styles Tab][View][Report] and [Properties][More Styles Tab][Owner draw fixed] both checked.
You have to have a Subclassed CListCtrl class, then just paste the RowSelect.h code into it and make any other adjustments you need.
As an example of the changes you could make, I wanted blue lines between rows and to make the selection highlight colour look like a highlighter pen (when viewed with the standard Windows Appearance):

This required the following alterations to the code:
Change this:
pDC->SetBkColor (GetSysColor(Highlight ? COLOR_HIGHLIGHT : COLOR_WINDOW ));
pDC->SetTextColor(GetSysColor(Highlight ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
To this:
// This: 0xFFFFFF^ makes a highlighter yellow selection in standard windows Appearance. It works for everything except the "High Contrast White" Schemes.
pDC->SetBkColor (Highlight ? 0xFFFFFF^GetSysColor(COLOR_HIGHLIGHT ) : GetSysColor(COLOR_WINDOW ));
pDC->SetTextColor(Highlight ? 0xFFFFFF^GetSysColor(COLOR_HIGHLIGHTTEXT) : GetSysColor(COLOR_WINDOWTEXT));
for the highlighting, and change this:
CPen Pen(PS_SOLID, 1, GetSysColor(COLOR_INACTIVECAPTION)); // Row separating Lines (Windows Explorer appears to use this colour for the lines in the Tree View, so I'm copying that :-)
to this:
CPen Pen(PS_SOLID, 1, GetSysColor(COLOR_WINDOW)^RGB(0x33,0x33,0)); // Row separating Lines
for blue gridlines (makes the page look like standard (UK) lined paper).
Sorting by any column
Well, there are plenty of solutions to this and none of them are good!
Most use lots of code and lots of memory.
You can either write your own QuickSort function and swap Rows your self by swapping every item in every column...
...or you can use the CListCtrl's existing SortItems(...) function - but that only provides you with a pointer to the ItemData of the Rows it want's you to compare...
Most solutions I've seen involve duplicating all the data in the ListCtrl in structures pointed to by ItemData.
So one solution duplicates code thats already been written, and one duplicates all the data in the control!
I always want to minimise the amount of code (and, therefore, the potential for bugs), so I'm not going to use the DIY method (I hope that the SortItems(...) function swaps rows with a simple pointer swap, so it should be far faster).
So the best I can do is leave the ItemData alone until a Sort is required, then replace it with a pointer to a structure that holds the old ItemData value, and the value from the Column which is to be sorted.
After the Sort the Item Data can be restored to its original state and all the memory that was used for duplication can be freed.
The type of the column to sort will usually be the same for the whole column, so its up to the class to work out what type of sorting to use (String/Icon/Date/Number).
My List had one Date column, so I used the ItemData to store the CTime::GetDate() value so that date comparisons could be fast.
Since the data to sort by for that Date column is already in the ItemData, I can use the standard SortItems call:
SortItems(CompareDates, 0);
where CompareDates is:
static int CALLBACK CompareDates(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) {return (lParamSort ? lParam1-lParam2 : lParam2-lParam1);}
I used the lParamSort parameter to indicate whether the sort should be ascending or descending.
But for all the other columns I must first put the data to sort into ItemData without loosing ItemData.
Use classWizard to get an OnColumnclick handler for your CListCtrl-derived class, and copy and paste the code in Sort.h:
Of course, your list won't have the same columns as mine did, so you'll still have some coding to do before you see anything work, but hopefully its all easy to understand!
If anyone thinks its worth me maintaining or updating any of the above, please e-mail me.