Friday, January 11, 2008

CWaitingTreeCtrl

What is it & Why?
My real intention, when I started this project, was to develop a tree control to display network resources. Enumerating network resources could be a lengthy operation, so I decided to actually do enumeration only when the user tries to expand an item. I realized soon that this kind of behavior could be useful to many other purposes, so I put all the code needed to have this behavior in a base class for my tree control, that was ready to use with other classes. I also added support for visual feedback, while an item is being populated.

When the user clicks to expand an item, a new item is added which contains a wait message. If you implement some animation inside the tree control the message is visible, otherwise it remains hidden. The blue rectangle is the area in which you can draw: you can do everything you want, even completely overwrite the wait message, as long as you stay within that area. Timed animations are also supported.

In the meantime, you can populate the item being expanded with new child items and the tree control will redraw itself to reflect the new content only when you have finished: the user will see only your animation. You may also choose not to display anything while populating, this is very easy.

I also added the ability to repaint the control while populating. The main thread is busy until the population process completes, this is "by design", but I noticed that it still receives WM_ERASEBKGND messages, at least on Win2K. So now I take a snapshot of the control before the process takes place and use that bitmap to redraw the control's background until the expanded item gets populated completely. (Please, report if this works on other platforms)

If you want to see some examples of what you can do with this class take a look at these articles:

CNetworkTreeCtrl
CShellTreeCtrl
CProgressFX and CHourglassFX
Using the Class
This class provides a common interface for two types of derived classes: those providing animation effects and those providing the items for the tree control. Obviously you can implement a single class that provides both content and animation, but you may lose in code re-use.

If you keep the two implementations separated, you can:

focus your attention on the content provider class, which will handle the underlying tree control
choose a ready-to-use animation provider class to add visual feedback to your content provider class or create your own
leave the control without any animation (users won't see the wait message)
enable the wait message when you use your content provider class
Providing Tree Content
Just derive your class from CTreeCtrl and then replace each occurence of CTreeCtrl with CWaitingTreeCtrl in both your '.h' and '.cpp' files.

class CMyTreeCtrl : public CWaitingTreeCtrl
{
...
};

Tip: if you want to use Class Wizard to generate message handlers later, you may temporarily change the base class in the BEGIN_MESSAGE_MAP macro to CTreeCtrl, but remember to restore it to the correct one (CWaitingTreeCtrl) and to change every call to the base implementation in the generated message handlers.

BEGIN_MESSAGE_MAP(CMyTreeCtrl, CWaitingTreeCtrl)
//{{AFX_MSG_MAP(CMyTreeCtrl)
...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

To fill in the tree control with items you are required to override PopulateItem and to call PopulateRoot somewhere to insert items at the first level. You may also override WantsRefresh to automatically refresh some items whenever they are expanded by user.

CWaitingTreeCtrl::PopulateItem
virtual void PopulateItem(HTREEITEM hParent)

This function gets called in the main thread after an item is expanded and the wait message is displayed. The animation takes place during the execution of this function and it is stopped when the function returns.

Tipically, you use the hParent parameter to get information about the parent item to populate and then you start adding new items. No redraw will happen until this function has finished, this is by design.

Also, there's currently no mean to abort this function, because it blocks the main thread, but you may create the underlying tree control in another user interface thread and provide your own means.

When you implement this function you have to remember the following rules:

you are supposed to add only one level of child items and you should only work with the hParent item and its children
you can always suppose that the hParent item has no children when the function gets called
you need to handle the special case in which hParent is TVI_ROOT
you should set the cChildren field of the TVITEM structure to 1 if you want that child item to be expandable
you should notify your progress to the base class (see below)
Returning TRUE means that the base class will check if you added any items and if it doesn't find any you will not be able to expand the hParent item again (the plus button will be removed).

Returning FALSE means that the hParent item will be always expandable, even if it has no children now. Note that it doesn't mean that the hParent item will be refreshed every time it's expanded, but that it will be expandable until it gets some children. If you want to refresh the hParent item each time it's expanded you have to override WantsRefresh.

Note: if you associate some data with each item you will probably handle the TVN_DELETEITEM notification. If you do so, you should ignore items that have the lParam field equals to zero. They could be the items used to display the wait message and they have no associated data. However this should not be a problem, since a zero value is tipically a NULL pointer.

CWaitingTreeCtrl::WantsRefresh
virtual BOOL WantsRefresh(HTREEITEM hParent)

This function gets called just before PopulateItem, only if the hParent item already has children, to ask a derived class if it wants the item's children to be refreshed.

You have to override this function if you want an item to refresh its children whenever the user expand it. Remember that to refresh items that have no children, you have to tell the base class not to check for inserted items by returning FALSE in your PopulateItem override.

Return TRUE if you want to automatically refresh the item (in the sense described above), or FALSE if you don't want automatic refresh. The base class implementation simply returns FALSE.

CWaitingTreeCtrl::PopulateRoot
void PopulateRoot()

You have to call this function somewhere in your derived class, if you want to see some items. A good place could be in your PreSubclassWindow or OnCreate override, but you may decide to have a function which initializes some data associated with the root item and then simulates a root expansion, populating the first level of items (PopulateItem is called with TVI_ROOT as the parent item).

You may also populate the first or some deeper levels of the tree without requiring the user to expand any items. A parent item that already has children won't be passed to PopulateItem, unless WantsRefresh says it must be refreshed. So static items are perfectly legal and can be used in conjunction with dynamic items.

CWaitingTreeCtrl::SetPopulationCount
void SetPopulationCount(int iMaxSubItems, int iFirstSubItem = 0)

You should call this function in your PopulateItem override, before adding any item, to set the total count of items you plan to insert (iMaxSubItems) and the optional initial value (iFirstSubItem).

You should set the total count to zero if you don't know how many items you're going to insert.

CWaitingTreeCtrl::IncreasePopulation
void IncreasePopulation(int iSubItemsToAdd = 1)

You should call this function in your PopulateItem override, when you insert a new item or a group of items. The current count is increased of the iSubItemsToAdd parameter, which can be a negative value.

CWaitingTreeCtrl::IncreasePopulation
void UpdatePopulation(int iSubItems)

You should call this function in your PopulateItem override, when you insert a new item or a group of items. The current count is updated to the value of iSubItems.

Providing Animation
Create a generic class and derive it from CWaitingTreeCtrl. Later you may replace the base class with a template, so that you can use the class as a plug-in to add custom animations and visual effects to a generic CWaitingTreeCtrl-derived class. (I'm not familiar with templates, so there could be a better way to do it).

template
class CMyAnimationFX : public BASE_TYPE
{
...
};

There are two types of animations: those refreshing at specified time intervals and those getting updated only when new items are inserted by a content provider class.

To display an animation frame, you have to override DoAnimation. If you want to initialize some variables related to the message item, such as the position of the animation frames, you need to override PreAnimation and PostAnimation (only if you need to free some memory at the end).

In all these functions you should call the base class implementation, especially when not directly deriving from CWaitingTreeCtrl. This way, and using templates, you can add more than a single animation to your content provider class.

CWaitingTreeCtrl::PreAnimation
virtual void PreAnimation(HTREEITEM hItemMsg)

This function gets called in the main thread just before the animation starts. Its only argument is the handle of the tree item displaying the wait message. You may use it to calculate the position in which to show the animation, but you shouldn't store it for later use. You can't assume the item still exists during the animation.

The wait message item is not visible by default, if you want to use its attributes to display something near to it, such as an animated item's image, or if you change its visual aspect, you have to call ShowWaitMessage in the constructor (or somewhere before entering this function).

You usually implement this function to initialize additional variables you may need during the animation. Remember that the hItemMsg item is a temporary item: you can use it only from inside this function, and only if the wait message is visible.

If you use the hItemMsg to draw something, you need to make it visible. If the message item is visible, you may also change the visual aspect of the tree control, such as scrolling the client area to properly displaying the animation. If the wait message is hidden the tree control can't redraw itself. You may have this situation if you don't draw the animation inside the tree control, but instead you use another window or control.

CWaitingTreeCtrl::DoAnimation
virtual void DoAnimation(BOOL bTimerEvent, int iMaxSteps, int iStep)

This function gets called in a higher priority worker thread each time you need to update your animation. If the bTimerEvent argument is TRUE this function has been called when the timer interval elapsed, otherwise the item count has been updated. If you want to handle timer events you have to specify the timer period calling SetAnimationDelay before the animation starts, either in the constructor or in PreAnimation.

The other two arguments reflect the current progress of the enumeration process: iMaxSteps is the total number of steps of the process (zero means unknown), while iStep is the current step. You may choose to ignore these during a timer event. You tipically use this information to display a progress bar.

You need to implement this function to draw a new frame of your animation. If you draw within the tree control, you should only draw inside the rectangle obtained by calling GetItemRect(hItemMsg, lpRect, FALSE). Remember that you can't call this function here: you may use CTreeCtrl::GetItemRect or CWaitingTreeCtrl::GetItemImageRect only inside PreAnimation.

CWaitingTreeCtrl::PostAnimation
virtual void PostAnimation()

This function gets called in the main thread just after the animation ends.

You have to implement this function only if you need to clean up some additional variables.

CWaitingTreeCtrl::SetAnimationDelay
void SetAnimationDelay(UINT nMilliseconds)

You may call this function in the constructor, or in your PreAnimation override, to set the delay between timer events. A delay of zero means you don't need timer events and it is the initial value. If you want timer events you have to explicitly call this function.

Note that there is only one timer, so the last call to this function before the animation starts takes precedence (if you want to use multiple animation classes).

CWaitingTreeCtrl::GetItemImageRect
BOOL GetItemImageRect(HTREEITEM hItem, LPRECT pRect)

You may call this function if you need to draw over an item's image, usually beside the wait message.

The return value is TRUE if the function is successful, FALSE otherwise.

Public Functions
There are also a few public functions that your content provider class will inherit.

CWaitingTreeCtrl::SetWaitMessage
void SetWaitMessage(LPCTSTR pszText, HICON hIcon = NULL)

You may call this function to change the wait message's text and image. If the hIcon argument is NULL the item gets a blank icon.

CWaitingTreeCtrl::ShowWaitMessage
void ShowWaitMessage()

You need to call this function in your animation provider class, either in the constructor or in any place before PreAnimation gets called, if you want to draw within the tree control. You are supposed to draw only in the rectangular area occupied by the wait message, so it must be visible. If you draw your animation in another window or if you use another control to provide visual feedback, you are not required to make the wait message visible.

You may also call this function if you don't have any animations, but you want the static wait message. Never call this function in your content provider class, the choice to display the message is left to the final user of your tree control.

Remember that the animation provider class should always assume that the wait message is not visible, and so it must show it if it needs to draw inside the control.

CWaitingTreeCtrl::RefreshSubItems
void RefreshSubItems(HTREEITEM hParent)

You may call this function to manually refresh the hParent item's children. You can only refresh items that can be expanded, whether or not they actually have children. In fact you assign a button only to items that can be expanded by the user, and only those items can be refreshed.

Updates
17 Oct 2000
Initial public release.
27 Sep 2001
Fixed a bug with refreshing empty items and possibly some other things
Added background repainting while busy (at least on Win2k)
License changed to Artistic License
Conclusion
This class probably needs some adjustments, but it's enough for me now. I think you may better understand this class if you see some implementations, so take a look at the other articles (see top). I will appreciate any comment, suggestion or contribution. Any help to improve this (and the others) article is welcome, I know it's not easy for you to read and it has been difficult for me to write.

License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors may to use can be found here

Quick Print

This printing class is based on the article by Rob A. Fraydl, "Creating a Printing Class". In fact, it's just an extension of the original class. What exactly has been extended? Well, first of all, I renamed the class to QPrint (stands for quick print). Big achievement, eh? OK, OK, this feature is not an extension at all, but anyway it's a start.

The second extension was to add the ability to manage big strings that extend beyond the right border of the physical paper size. Now, the big strings are not clipped at the end of the right border, but instead they continue to the next line.

Last but not the least, the new printing class supports tables as well as page orientation.

Background
As already said in the beginning, this piece of code did not start from scratch. You can always refer to the original printing class named CPrivatePrint. Please feel free to use and improve this code. Other than the additions I have made, I also corrected some bugs. I am not sure in some points if they were bugs, especially the part with the logical units, using the macros CALCF(x), CALCY(y), and CALCX(x). I deleted CALCX(x) completely, and the CALCY(x) partially, from the source code. So, if you meet "strange" results, please let me know. The others were safe point bugs that got corrected successfully.

I tested this class using the JawsPDF Creator (to save paper, time, patience, and finally money!) and different ink jet printers. It worked flawlessly.

Using the code
I chose this printing class as a base to build QuickPrint, because it was compact and easy to use. So, the only thing that you have to do is to declare the CQPrint prt and start using it. The following example shows the basic steps that you have to do:

void TestDlg::OnPrintButton ()
{
CQPrint prt;
HPRIVATEFONT hFont;

// Step 1 : call the CPrintDialog
if (prt.Dialog() == -1)
return;

//Step 2 : Start the Print
prt.StartPrint();

//Step 3 : Create a printing font
hFont = prt.AddFontToEnvironment("Arial Greek",8,8);

//Step 4 : Start Page
prt.StartPage();

//Step 5 : The actual printing goes here
prt.Print(hFont,"Set Font Arial Size = 8",FORMAT_NORMAL);

//Step 6 : now end the page
prt.EndPage();

//Step 7 : close the print document
// and release it in the spooler
prt.EndPrint();
}

You can see a live example (like the picture in this tutorial) in the following code:

Collapse CQPrint prt;
HPRIVATEFONT hFont,hFont1,hFont2;
CSize dim;
CRect margins;



// initializing the class and printer selection
if (prt.Dialog() == -1)
return;

// create the print document in the spooler
prt.SetPageOrientation (DMORIENT_PORTRAIT);
prt.StartPrint();


// Adding the arial font to the class
hFont = prt.AddFontToEnvironment("Arial Greek",8,8);
hFont1 = prt.AddFontToEnvironment("Arial Greek",9,9);
hFont2 = prt.AddFontToEnvironment("Arial Greek",10,10);

prt.SetMargins(40,160,200,200);
prt.SetDistance(5);

prt.GetDrawDimension(dim);
prt.GetMargins(margins);
prt.SetHPos(dim.cy-margins.top);
// activate a header and footer line
prt.SetActiveFont (hFont);
prt.ActivateHF(HeaderFooter);



// set margins and line spacing

prt.StartPage();
// start printing the lines


prt.Print(hFont,"Set Font Arial Size = 8",FORMAT_NORMAL);
prt.Print(hFont,"My name is George Papaioannou",FORMAT_NORMAL);
prt.Line (PS_SOLID);


prt.Print(hFont1,"Set Font Arial Size = 9",FORMAT_NORMAL);
prt.Print(hFont1,"My name is George Papaioannou",FORMAT_NORMAL);
prt.Line (PS_SOLID);


prt.Print(hFont2,"Set Font Arial Size = 10",FORMAT_NORMAL);
prt.Print(hFont2,"My name is George Papaioannou",FORMAT_NORMAL);
prt.Line (PS_SOLID);



// ===================== TABLE 1 ==========================

prt.lf(hFont2);
prt.lf(hFont2);
prt.Print(hFont2,"TABLE 1",FORMAT_CENTER);
prt.lf(hFont2);
prt.lf(hFont2);


int size[5];
size[0]=800;
size[1]=400;
size[2]=400;
size[3]=400;


prt.SetTableColumns (3);
prt.SetTableColumnsSize(size);
prt.SetTableHorLine (TRUE);
prt.SetTableVerLine (TRUE);
prt.SetTableBorderSize (200);
prt.SetTableJustify(FORMAT_CENTER);
prt.SetTableBorderLine(5);
//set the headers
prt.SetFace (hFont,FACE_NORMALBOLD);
prt.AddTableRecord (hFont,"NAME|GRADE|CITY",FORMAT_CENTER);
//set the records
prt.SetFace (hFont,FACE_NORMAL);
prt.AddTableRecord (hFont,"George Papaioannou|Grade"
" A|Lives at Athens (Greece)",FORMAT_CENTER);
prt.AddTableRecord (hFont,"Kostas Papadimitriou|"
"Grade B|Lives at Larissa (Greece)",FORMAT_CENTER);

prt.lf(hFont2);
prt.lf(hFont2);

prt.Print(hFont2,"AppWizard has created this PrintJob"
" application for you. This application "
"not only demonstrates the basics"
" of using the Microsoft Foundation classes "
"but is also a starting point for"
" writing your application.",FORMAT_NORMAL);

// ===================== TABLE 2 ==========================
prt.lf(hFont2);
prt.lf(hFont2);
prt.Print(hFont2,"TABLE 2",FORMAT_CENTER);
prt.lf(hFont2);
prt.lf(hFont2);


size[0]=800;
size[1]=400;
size[2]=600;
size[3]=600;

prt.SetTableReset();
prt.SetTableColumns (4);
prt.SetTableColumnsSize(size);

//set the headers
prt.SetFace (hFont,FACE_NORMALBOLD);
prt.AddTableRecord (hFont,"NAME|GRADE|CITY|ADDRESS",
FORMAT_CENTER);
//set the records
prt.SetFace (hFont,FACE_NORMAL);
prt.AddTableRecord (hFont,"George Papaioannou|Grade A|"
"Athens (Greece)|23 Octobriou",FORMAT_CENTER);
prt.AddTableRecord (hFont,"Kostas Papadimitriou|Grade B|"
"Larissa (Greece)|Irron Polytechneiou",FORMAT_CENTER);


// =================== TABLE 3 ==============================
prt.lf(hFont2);
prt.lf(hFont2);
prt.Print(hFont2,"TABLE 3",FORMAT_CENTER);
prt.lf(hFont2);
prt.lf(hFont2);

size[0]=800;
size[1]=400;
size[2]=600;
size[3]=600;

prt.SetTableReset();
prt.SetTableColumns (4);
prt.SetTableColumnsSize(size);
prt.SetTableHorLine (TRUE);
prt.SetTableBorderSize (200);
prt.SetTableJustify(FORMAT_RIGHT);


//set the headers
prt.SetFace (hFont,FACE_NORMALBOLD);
prt.AddTableRecord (hFont,"NAME|GRADE|CITY|ADDRESS",FORMAT_CENTER);
//set the records
prt.SetFace (hFont,FACE_NORMAL);
prt.AddTableRecord (hFont,"George Papaioannou|Grade A|"
"Athens (Greece)|23 Octobriou",FORMAT_CENTER);
prt.AddTableRecord (hFont,"Kostas Papadimitriou|Grade B|"
"Larissa (Greece)|Irron Polytechneiou",FORMAT_CENTER);

// now end the page
prt.EndPage();

// close the print document and release it in the spooler
prt.EndPrint();

You can call the following table methods:

CQPrint::SetTableColumns(int Cols)
SetTableColumns sets the table columns.

prt.SetTableColumns(3);

CQPrint::SetTableBorderSize(int Border)
SetTableBorderSize sets the table border space. Default is 50.

prt.SetTableBorderSize(50);

CQPrint::SetTableColumnsSize(int Cols,int size[])
SetTableColumnsSize sets the table column size. The maximum number of columns permitted is 20.

int size[4];
size[0]=800;
size[1]=400;
size[2]=600;
size[3]=600;

prt.SetTableColumnsSize(4,size);

CQPrint::SetTableHorLine(BOOL HLine)
SetTableHorLine sets the horizontal lines. Default is OFF (no lines).

prt.SetTableHorLine(TRUE);

CQPrint::SetTableVerLine(BOOL HLine)
SetTableVerLine sets the vertical lines. Default is OFF (no lines).

prt.SetTableVerLine(TRUE);

CQPrint::SetTableBorderLine(int Border)
SetTableBorderLine sets the width of the border line. Default is 0 (thin line).

prt.SetTableBorderLine(10);

CQPrint::SetTableJustify(int Justification)
SetTableJustify justifies the table. Default is FORMAT_NORMAL. You can also use FORMAT_CENTER and FORMAT_RIGHT.

prt.SetTableJustify(FORMAT_CENTER);

CQPrint::SetTableReset()
SetTableReset resets all the table parameters to the default state. Use this if you don't want to change values when using consecutive tables with different visual parameters.

prt.SetTableReset();

CQPrint::AddTableRecord(HPRIVATEFONT font, CString strText, UINT Format)
AddTableRecord inserts values to the current table. Use the | symbol to change the column in strText.

prt.AddTableRecord (hFont,"Column 1|Column 2|"
"Column 3|Column 4",FORMAT_CENTER);

If you want to change the page orientation, use SetPageOrientation before StartPrint. Use the values DMORIENT_PORTRAIT or DMORIENT_LANDSCAPE.

prt.SetPageOrientation (DMORIENT_PORTRAIT);
prt.StartPrint();

You can always use the "original" methods of the class like SetMargins(), Line(), lf() etc. More details about these functions can be found in the demo. You have to experiment a little before you use it. Don't forget that this class also supports bitmap printing with the InsertBitmap() method. I was wondering if there is someone who can extend this class to support other image formats using GDI+. Maybe the next version of QPrint will support print preview as well as image printing (JPG, GIF, PCX etc.), but it's a little bit complicated, since my wife Natasa is already negative to this perspective. So, don't count on this.

Don't forget that you can always use the callback function ActivateHF() that is used to print headers and footers. I have also included this feature in my demo. Anyway, there are more methods in QPrint that you can find in this short tutorial, so start playing with the demo to get an idea of how this stuff works, and then try to discover the more advanced capabilities that this class supports.

Points of Interest
Please, if you see problems with this class, especially with the (LOGPIXELSY, LOGPIXELSX) width and height of the letters, feel free to contact with me.

History
There is no history for the Quick Print (QPrint) class. This is the first and the only version. The base and the original code for this class was from Rob A. Fraydl and is called CPrivatePrint.

License

Cell blink for DataGridView

After reading many articles on Code project, I realized that it's my time to contribute. Few months ago, I came across a requirement for cells in a DataGridView control to blink when the cell value changed. The code presented here can be applied to any other grid.

The blinking of the grid cell is achieved in the following manner. When we update the value of a cell, we also change the background color of that cell to a blink color. To restore the cell background color to its original value, we run a background thread that iterates through a list of cells that are blinking and resets them to their original non blinking state.

The code
The sample project has two functions. The first function DataInputThreadFunc() is used to generate random values to be filled / updated in the grid. The second function GridBlinkThreadFunc() is used to restore the cells to the non blink state.

Let's take a look at the first function DataInputThreadFunc():

Collapseprivate void DataInputThreadFunc()
{
Random rand = new Random();
while (true)
{
if (dataGridView1.IsDisposed)
break;

CellData data = new CellData();
data.Row = rand.Next(0, 7);
data.Col = rand.Next(0, 3);
data.Time = DateTime.Now;

int value = rand.Next(0, 101);

dataGridView1.Invoke((MethodInvoker)delegate()
{
dataGridView1.Rows[data.Row].Cells[data.Col].Value = value;
dataGridView1.Rows[data.Row].Cells[data.Col].Style
.BackColor = Color.Salmon;
});

lock (_blinkData)
{
_blinkData.Add(data);
}

Thread.Sleep(1000);
}
}


The function uses a while (true) loop as it's a background thread and will be shutdown automatically when the application is closed. if (dataGridView1.IsDisposed) check is done to make sure we do not call dataGridView1.Invoke() on a disposed object. This can happen when the user closes the application.

Next, we initialize an object of the class CellData to store the blink data.

class CellData
{
public int Row;
public int Col;
public DateTime Time;
}

This class is used to store the row number, column number and the time when the value changed.

Next we use dataGridView1.Invoke() to make a call to the user interface thread and set the grid properties. We save the blink data in a generic list to be used later by the blink thread function. Since the list is altered by more than one thread, we synchronize access by locking the list on each access.

Now let's take a look at the blink thread function.

Collapseprivate void GridBlinkThreadFunc()
{
while (true)
{
// Make a copy to avoid invalid operation exception
// while iterating through the map
List tempBlinkData;
lock (_blinkData)
{
tempBlinkData = new List(_blinkData);
}

foreach (CellData data in tempBlinkData)
{
TimeSpan elapsed = DateTime.Now - data.Time;
if (elapsed.TotalMilliseconds > 500) // 500 is the Blink delay
{
if (dataGridView1.IsDisposed)
return;

dataGridView1.BeginInvoke((MethodInvoker)delegate()
{
dataGridView1.Rows[data.Row].Cells[data.Col]
.Style.BackColor = dataGridView1.Columns[data.Col]
.DefaultCellStyle.BackColor;
});

lock (_blinkData)
{
_blinkData.Remove(data);
}
}
}

Thread.Sleep(250); // Blink frequency
}
}

At the very beginning, we make a copy of the _blinkData list. This helps us to modify the list while we iterate through the contents of the temporary copy. For each cell we find in the list, we check to make sure whether the blink time has elapsed or not. In this case the blink time is 500 milliseconds. Any cell that has elapsed the blink time gets its background color reset to the default cell style background color and is removed from the list.

Again we make sure that we set the grid property only in the user interface thread. In addition, we lock the _blinkData list before altering it. Thread.Sleep(250) is the frequency with which we go through the list to turn off the cells. Ideally, it should be half the value of blink delay.

Points of Interest
You will notice this code can be applied to any grid. This code can also be hidden in a class that extends a DataGridView control.

One thing I love about .NET 2.0 is this dataGridView1.Invoke((MethodInvoker)delegate(). This statement lets you get away from writing a function and declaring a delegate. That frees up tons of browsing time!

A good point was made by "Kristof Verbiest" about the use of BeginInvoke() instead of Invoke(). The GridBlinkThreadFunc() uses BeginInvoke() to avoid unnecessary context switch.

A Class to Save and Load Listbox Data

Introduction
This is my first attempt at writing a class and at writing a Code Project article so I hope it will be of some use. I noticed I was using a lot of listbox saving/restoring in my applications so I decided to write a class for this. The class does not contain error handling but it should work fine. To use it just add the files to your project and include ListBoxSafe.h. There are (only) two functions you can use (simplicity):

void LoadList(CListBox &list, CString FileName, int MaxItems, int ItemLen);

and

void SaveList(CListBox &ListToFill, CString FileName, int MaxItems, int MaxItemLen);

list/ListToFill is the ListBox that you need to save or fill.
FileName is the name of the file from which you want to load or to which you want to save to.
MaxItems is the maximum amount of items to be saved or loaded.
ItemLen/MaxItemLen is the maximum length of a listbox item (should be the same if you save and load).

You can give MaxItems the value 0, and it will be 999 by default.
You can give MaxItemLen/ItemLen the value 0, and it will be 30 by default.

Example
CListBoxSafe::SaveList(m_lMyList,"test.dat",0,0);
CListBoxSafe::LoadList(m_lDataList,"data.txt",0,0);

Using virtual lists

Introduction
Let's say you have a large database in your program and you want to show the database to the user. You use a CListCtrl with a couple of columns and fill it with a few thousand, maybe million elements. When you run it, you notice it's a bit (or very) slow. Wouldn't it be great if you didn't need to add all elements to the list, and let the list show them anyway? Does it sound stupid and ridiculous? That's how a virtual list works.

Virtual list
A virtual list is a list that has no data, it only knows how many data items it is supposed to have. But how does it now what data to show? The secret is that the list asks the parent for the information it needs. Assume you have a list with 100 elements, and elements 10-20 are visible. When the list is redrawing, it first asks the parent about element 10. When the parent has answered, the list redraws element 10, and then goes on to the next element.

A virtual list is sending three different messages to the parent. LVN_GETDISPINFO is sent when the list needs information. This is the most important message. The message LVN_ODFINDITEM is sent when the user tries to find an item by writing in the list. LVN_ODCACHEHINT is sent to give you a chance to cache data. You will probably don't care about this message at all.

OK, that's enough fuzzy theory, let's look on some code instead.

Creating a virtual list
Creating a virtual list isn't much harder than creating an ordinary CListCtrl. Add a list control in the resource editor as you usually do. Then check the style "Owner data", and then add a CListCtrl variable for this control. The only difference from an ordinary CListCtrl is the "Owner data" (LVS_OWNERDATA) style.

When you work with a virtual list, you use it mostly in the same way you do with a non-virtual list. Adding columns, selecting items, adding image list and much more works exactly the same way.

Add items to the list
Let's say m_list is the control variable for the list. Normally, you add data to the list like this:

m_list.InsertItem(0, _T("Hello world"));

But in a virtual list, this will not work. Instead, it is up to you to handle the data. Instead of adding, you change the number of elements the list is showing:

//"Add" 100 elements
m_list.SetItemCount(100);

If you set the item count to 100 or 1,000,000 doesn't matter, the time to run this command will still be practically zero. In a non-virtual list, adding a million elements could take hours.

Handling the LVN_GETDISPINFO message
As I said before, the list is asking the parent for information when it needs it. The list does this by sending a LVN_GETDISPINFO message. This is the most important message to handle when you are dealing with a virtual list. A typical function looks like this:

Collapsevoid CVirtualListDlg::OnGetdispinfoList(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

//Create a pointer to the item
LV_ITEM* pItem= &(pDispInfo)->item;

//Which item number?
int itemid = pItem->iItem;

//Do the list need text information?
if (pItem->mask & LVIF_TEXT)
{
CString text;

//Which column?
if(pItem->iSubItem == 0)
{
//Text is name
text = m_database[itemid].m_name;
}
else if (pItem->iSubItem == 1)
{
//Text is slogan
text = m_database[itemid].m_slogan;
}

//Copy the text to the LV_ITEM structure
//Maximum number of characters is in pItem->cchTextMax
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
}

//Do the list need image information?
if( pItem->mask & LVIF_IMAGE)
{
//Set which image to use
pItem->iImage=m_database[itemid].m_image;

//Show check box?
if(IsCheckBoxesVisible())
{
//To enable check box, we have to enable state mask...
pItem->mask |= LVIF_STATE;
pItem->stateMask = LVIS_STATEIMAGEMASK;

if(m_database[itemid].m_checked)
{
//Turn check box on
pItem->state = INDEXTOSTATEIMAGEMASK(2);
}
else
{
//Turn check box off
pItem->state = INDEXTOSTATEIMAGEMASK(1);
}
}
}

*pResult = 0;
}

First, we create a LV_DISPINFO pointer, and then a pointer to the item. In itemid, we save which item we should handle. Then we check the mask in pItem. The mask is telling us what kind of information the list needs. First, we check if text information is needed. If it is, we first figure out which column the text is. The first column is for names, the second for slogans (several columns will be shown in report view, in other views only first column will be used).

We also check if image information is needed. If it is, we save which image to use in pItem->iImage (this is the number to an image in the image list connected to the list).

If check boxes are visible, we should send information about that when image information is requested. First, we change pItem->mask to tell that we are sending state information. We also change pItem->stateMask to tell what kind of information we are sending. Then we write if check box is on or off.

The mask could also have the flags LVIF_INDENT, LVIF_NORECOMPUTE, LVIF_PARAM, and LVIF_DI_SETITEM. But I have never used any of them, so I guess they aren't important :-).

It isn't harder to handle the LVN_GETDISPINFO message than this. Check boxes are probably the hardest, but you will probably never use any more complicated code than I have done in this example. When you have written this function, your list will almost act like a normal list. However, it might be a good idea to implement the LVN_ODFINDITEM as well.

Handling the LVN_ODFINDITEM message
First, some basic education: start Explorer and go to a folder where you have a lot of files. Press A. What happens? If you have a file or folder that begins with an 'A', that file should now be selected. Press A again. If you have more than one file that begins with an 'A', the second file should be selected. Write "AB". If there is any file that begins with 'AB', that is now selected. This is how every normal list control behaves. Aren't list controls cool? :-).

Let's look on how a list control normally searches:

Name
Anders
Anna
Annica
Bob
Emma
Emmanuel

Anna is selected. When we are writing anything, the list will search down to find the best match. If it reaches the end, it restarts at the top and searches until it is back on the start item (Anna). If "A" is written, Annika should be selected. If "AND" is written, Anders should be selected. If "ANNK" is written, the selection should stay on Anna. If "E" is written, Emma should be selected.

Unfortunately, this doesn't work with virtual lists. A virtual list doesn't try to find any item at all, unless you handle the LVN_ODFINDITEM message. I usually implement the message like this:

Collapsevoid CVirtualListDlg::OnOdfinditemList(NMHDR* pNMHDR, LRESULT* pResult)
{
// pNMHDR has information about the item we should find
// In pResult we should save which item that should be selected
NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;

/* pFindInfo->iStart is from which item we should search.
We search to bottom, and then restart at top and will stop
at pFindInfo->iStart, unless we find an item that match
*/

// Set the default return value to -1
// That means we didn't find any match.
*pResult = -1;

//Is search NOT based on string?
if( (pFindInfo->lvfi.flags & LVFI_STRING) == 0 )
{
//This will probably never happend...
return;
}

//This is the string we search for
CString searchstr = pFindInfo->lvfi.psz;

int startPos = pFindInfo->iStart;
//Is startPos outside the list (happens if last item is selected)
if(startPos >= m_list.GetItemCount())
startPos = 0;

int currentPos=startPos;

//Let's search...
do
{
//Do this word begins with all characters in searchstr?
if( _tcsnicmp(m_database[currentPos].m_name,
searchstr, searchstr.GetLength()) == 0)
{
//Select this item and stop search.
*pResult = currentPos;
break;
}

//Go to next item
currentPos++;

//Need to restart at top?
if(currentPos >= m_list.GetItemCount())
currentPos = 0;

//Stop if back to start
}while(currentPos != startPos);
}

It may not be obvious how this works at a first look, but if you read it carefully, you will understand. Or, you simply skip this, and copy the code and just do the necessary changes - it's up to you :-). If the list is really large, maybe you need to make a faster version of this function, or don't implement it at all.

pFindInfo->lvfi has information on how you should search (like "Restart at top if bottom is reached" or in which direction). I have never cared about that, if you do, you should read in MSDN to get more information.

Handling the LVN_ODCACHEHINT message
LVN_ODCACHEHINT is sent to give you a chance to cache data. If you are working with a database that is in another computer in some network, maybe this is useful, but I haven't used this message in any of my programs. A function that handles this message will probably look something like this:

void CVirtualListDlg::OnOdcachehintList(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVCACHEHINT* pCacheHint = (NMLVCACHEHINT*)pNMHDR;

// ... Cache the data pCacheHint->iFrom to pCacheHint->iTo ...

*pResult = 0;
}

This is quite simple as you see. But as usual, simple things don't work :-). According to MSDN, you should override OnChildNotify and add the handler in this function. But I will not dig deeper in this, if you need more information about this, read in MSDN.

Changing an item
What should you do to change the data? This is really simple. You don't change the data in the list, but in the database instead. To redraw the list items, call CListCtrl::RedrawItems.

Check boxes
Check boxes are useful, but they are quite tricky to implement when you are working with a virtual list. In a normal non-virtual list, check boxes are toggled when you click on them or when you press space. But in a virtual list, nothing will happen. So you have to implement these events yourself. We start with a simple toggle-check-box-function:

void CVirtualListDlg::ToggleCheckBox(int item)
{
//Change check box
m_database[item].m_checked = !m_database[item].m_checked;

//And redraw
m_list.RedrawItems(item, item);
}

We will call this function when we want to change an item. The function toggles the check box value (in the database!) and forces the list to redraw the item. Quite simple. Toggling a check box when space is pressed is also quite simple. Add a message handler for the LVN_KEYDOWN message. The function should be something like this:

void CVirtualListDlg::OnKeydownList(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDown = (LV_KEYDOWN*)pNMHDR;

//If user press space, toggle flag on selected item
if( pLVKeyDown->wVKey == VK_SPACE )
{
//Toggle if some item is selected
if(m_list.GetSelectionMark() != -1)
ToggleCheckBox( m_list.GetSelectionMark() );
}

*pResult = 0;
}

We check if space is pressed and if any item is selected before we toggle the check box. To toggle check box when we click on it, we have to do a more complicated function. Add a message handler for the NM_CLICK message. My function looks like this:

void CVirtualListDlg::OnClickList(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

LVHITTESTINFO hitinfo;
//Copy click point
hitinfo.pt = pNMListView->ptAction;

//Make the hit test...
int item = m_list.HitTest(&hitinfo);

if(item != -1)
{
//We hit one item... did we hit state image (check box)?
//This test only works if we are in list or report mode.
if( (hitinfo.flags & LVHT_ONITEMSTATEICON) != 0)
{
ToggleCheckBox(item);
}
}

*pResult = 0;
}

We are using CListCtrl::HitTest to see if we clicked on an item. If we did, the function returns which item we clicked on. We then use hitinfo.flags to see where we clicked. If the flag LVHT_ONITEMSTATEICON is on, then we know that the user clicked on the check box (state image).

Unfortunately, CListCtrl::HitTest doesn't work as it should if the list view is in "Icon" or "Small icon" mode. In these views, hitinfo.flags & LVHT_ONITEMSTATEICON is always 0. I haven't found a solution to this, if you do, let me now. However, using check boxes in these modes is probably quite unusual, so this is not a big problem.

Notes
Unless you are making something very unusual, you will not need to know more information than there is in this article, to use virtual lists. However, there are some minor compatibility issues between a virtual and a non-virtual list. For example, a virtual list can't sort data. But that is quite obvious, isn't it :-)? You find more information about these issues in MSDN.

When should you use a virtual list, and when should you not? For large lists, I personally prefer a virtual list. But a non-virtual list is usually easier to program (message handling is never fun), so for smaller lists, I use the ordinary list.

A very neat thing about virtual lists is that they are very easy to keep synchronized with a database. You just change the database and redraw the list if necessary. So, if you work with a list where the user should change data in the database, a virtual list could be useful even if number of items are low.

Another nice thing is that you sometimes could generate data when necessary. If you want to show row number in one column, that is very easy to do, and it takes practically no memory at all. In a non-virtual list, you would have to add this data on all items.

History

Clipboard backup (Visual C++)

Introduction
This piece of code is to help you backup your clipboard data and restore it after you have finished other clipboard operations.

My experience: while I'm developing Word add-in programs, the icon of the new-added button can only be set through clipboard by "...->PasteFace()". This will empty the clipboard data which is going to be pasted in Word. So, I wrote this class, and it helped me a lot.

Usage:

// the constructor will do backup clipboard operation
CClipboardBackup cbbackup;

// any other clipboard operations
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(......);
::CloseClipboard();
....

// restore
cbbackup.Restore();

Advertisement

Copying a Bitmap of the Client Area

The following bit of code renders the client area using OnDraw() into a bitmap. The bitmap is placed on the clipboard as a CF_BITMAP that is recognizable by most applications that accept bitmaps. This code will work will all mapping modes provided that mapping mode is set in the OnPrepareDC() function.

Collapsevoid CXStitchView::OnEditCopy()
{
CRect rect;
CClientDC dc(this);
CDC memDC;
CBitmap bitmap;

GetClientRect(&rect);

// Create memDC
memDC.CreateCompatibleDC(&dc);
bitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());
CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);

// Fill in memDC
memDC.FillSolidRect(rect, dc.GetBkColor());
OnPrepareDC(&memDC);
OnDraw(&memDC);

// Copy contents of memDC to clipboard
OpenClipboard();
EmptyClipboard();
SetClipboardData(CF_BITMAP, bitmap.GetSafeHandle());
CloseClipboard();

// Clean up
memDC.SelectObject(pOldBitmap);
bitmap.Detach();
}

Copying a Table of Data to the Clipboard
Placing a table of data on the clipboard is easy. It is simply a string of text. Tabs separate columns, new lines separate rows. Here's some example source.

#include

void CClipExamView::OnEditCopy()
{
// Create a shared memory file
CSharedFile sf(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT);

// Place clipboard data in the shared memory file
RenderTableData(sf);

if (sf.GetLength() > 0) {
// Put the data on the clipboard
OpenClipboard();
EmptyClipboard();
SetClipboardData(CF_TEXT, sf.Detach());
CloseClipboard();
}
}

void CClipExamView::RenderTableData(CFile &file)
{
// Columns are separated by tabs ('\t')
// Rows are separated by new lines ('\n')
CString buffer = "1\t2\t3\n4\t5\t6\n";
file.Write(buffer, buffer.GetLength());
}

Copying Formatted Data to the Clipboard
Formatted text can support bold, italic or any other formatting that can be done in a word processor. Formatted text is usually placed on the clipboard as RTF (Rich Text Format).

The Rich Text Format is intended as an interchange format for Word-processing applications. Because of that, it is a rather large and feature rich file format. Fortunately, it is possible to describe a minimal RTF command set for creating simple formatted documents.

Basic RTF commands:
\par - Starts a new paragraph.
\tab - A tab.
\b - Enable Bold (scoped within a group)
\i - Enable Italics (scoped within a group)
\ul - Enable Underline (scoped within a group)

For example, the RTF string:

{\rtf1 {1 \tab 2 \tab 3 \par 4 \tab {\b\i 5} \tab 6}}

Source

#include

void CClipExamView::OnEditCopy()
{
// Create a shared memory file
CSharedFile sf(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT);

// Place clipboard data in the shared memory file
RenderFormattedData(sf);

if (sf.GetLength() > 0) {
// Put the data on the clipboard
OpenClipboard();
EmptyClipboard();
SetClipboardData(::RegisterClipboardFormat(CF_RTF), sf.Detach());
CloseClipboard();
}
}

void CClipExamView::RenderFormattedData(CFile &file)
{
// RTF (Rich Text Format) - Don't forget to escape
// the \ character in your C strings!
CString buffer = "{\\rtf1 {1 \\tab 2 \\tab 3"
" \\par 4 \\tab {\\b\\i 5} \\tab 6}}";
file.Write(buffer, buffer.GetLength());
}

Getting More Info on RTF