Transparent ListBox

Introduction

Any experienced Windows programmer can tell you that transparency is not a trivial task in Windows. A transparent listbox control is no exception. Actually, listboxes are bit harder to create than other controls. The reason is the way Listboxes do their scrolling. But, overall, it is a pretty simple concept to implement.

For example, to make a Static contorl transparent, all anyone has to do is to handle the WM_ERASEBKGND, and also to repaint the control when the user calls SetWindowText.

In the case of a listbox, let's say that you take over the WM_ERASEBKGND message and return TRUE (basically, the easiest way to achieve transparency). When the user presses the down button of the listbox's scrollbar, what Windows does is bitblt the item's top index + 1 to the last shown item one line up, and then simply draws the new item. What happens there is that the background gets copied up with the item. Clearly, not the results you are looking for.

How to Do It

So, how do you achieve transparency with a listbox? You start with an Owner draw listbox.

The first thing you have to do is copy the parent window's image before the first time the listbox is drawn; this will give you the background for the listbox. You can do this in the WM_ERASEBKGND message handler. The first time this message is received, the listbox has not yet been drawn, so it is a safe place to take a snapshot of the parent window.

BOOL CTransparentListBox::OnEraseBkgnd(CDC* pDC)
{
   if (!m_HasBackGround)
   {
      CWnd *pParent = GetParent();
      if (pParent)
      {
         CRect Rect;
         GetClientRect(&Rect);
         ClientToScreen(&Rect);
         pParent->ScreenToClient(&Rect);
         CDC *pDC = pParent->GetDC();
         m_Width = Rect.Width();
         m_Height = Rect.Height();
         CDC memdc;
         memdc.CreateCompatibleDC(pDC);
         CBitmap *oldbmp = memdc.SelectObject(&m_Bmp);
         memdc.BitBlt(0,0,Rect.Width(),Rect.Height(),pDC,Rect.left,
                      Rect.top,SRCCOPY);
         memdc.SelectObject(oldbmp);
         m_HasBackGround = TRUE;
         pParent->ReleaseDC(pDC);
      }
   }
   
   return TRUE;

}

The second thing that you have to handle is drawing each item on the screen. Because of the scrolling, you can't trust the listbox to do any of your drawing for you. So, you override the DrawItem method and do nothing. And, in turn, you place the code that would normally be placed there in a separate DrawItem method that you can call when you want to paint the listbox, which you will do in the OnPaint method. OnPaint simply draws the background snapshot on to a memory DC, draws the visible items onto the same memory DC, and then bitblt the entire thing on the listbox DC. Simple so far.

void CTransparentListBox::DrawItem( LPDRAWITEMSTRUCT
                                    lpDrawItemStruct )
{
   //do nothing when the listbox asks you to draw an item
}

void CTransparentListBox::DrawItem(CDC &Dc,int Index,CRect &Rect,
                                   BOOL Selected)
{
   if (Index == LB_ERR || Index >= GetCount())
      return;

   if (Rect.top < 0 || Rect.bottom > m_Height)
   {
      return;
   }
   CRect TheRect = Rect;
   Dc.SetBkMode(TRANSPARENT);

   CDC memdc;
   memdc.CreateCompatibleDC(&Dc);

   CFont *pFont = GetFont();
   CFont *oldFont = Dc.SelectObject(pFont);
   CBitmap *oldbmp = memdc.SelectObject(&m_Bmp);
   Dc.BitBlt(TheRect.left,TheRect.top,TheRect.Width(),
             TheRect.Height(),&memdc,TheRect.left,TheRect.top,
             SRCCOPY);
   CString Text;
   GetText(Index,Text);
   if (m_Shadow)
   {
      if (IsWindowEnabled())
      {
         Dc.SetTextColor(m_ShadowColor);
      }
      else
      {
         Dc.SetTextColor(RGB(255,255,255));
      }
      TheRect.OffsetRect(m_ShadowOffset,m_ShadowOffset);
      Dc.DrawText(Text,TheRect,DT_LEFT|DT_EXPANDTABS|DT_NOPREFIX);
      TheRect.OffsetRect(-m_ShadowOffset,-m_ShadowOffset);
   }

   if (IsWindowEnabled())
   {
      if (Selected)
      {
            Dc.SetTextColor(m_SelColor);
      }
      else
      {
            Dc.SetTextColor(m_Color);
      }
   }
   else
   {
      Dc.SetTextColor(RGB(140,140,140));
   }
   Dc.DrawText(Text,TheRect,DT_LEFT|DT_EXPANDTABS|DT_NOPREFIX);
   Dc.SelectObject(oldFont);
   memdc.SelectObject(oldbmp);
}

void CTransparentListBox::OnPaint()
{
   CPaintDC dc(this);    // device context for painting

   CRect Rect;
   GetClientRect(&Rect);

   int Width = Rect.Width();
   int Height = Rect.Height();

   //create memory DCs
   CDC MemDC;
   MemDC.CreateCompatibleDC(&dc);
   CBitmap MemBmp;
   MemBmp.CreateCompatibleBitmap(&dc,Width,Height);

   CBitmap *pOldMemBmp = MemDC.SelectObject(&MemBmp);

   //paint the background bitmap on the memory DC
   CBitmap *pOldbmp = dc.SelectObject(&m_Bmp);
   MemDC.BitBlt(0,0,Width,Height,&dc,0,0,SRCCOPY);
   dc.SelectObject(pOldbmp);


   Rect.top = 0;
   Rect.left = 0;
   Rect.bottom = Rect.top + GetItemHeight(0);
   Rect.right = Width;
   
   int size = GetCount();
   //draw each item on memory DC
   for (int i = GetTopIndex(); i < size && Rect.top <= Height;++i)
   {
      DrawItem(MemDC,i,Rect,GetSel(i));
      Rect.OffsetRect(0,GetItemHeight(i));
   }

   //draw the results onto the listbox dc
   dc.BitBlt(0,0,Width,Height,&MemDC,0,0,SRCCOPY);

   MemDC.SelectObject(pOldMemBmp);
}

The third and the tricky part is handling the scroll messages. To overcome the scroll problem, you intercept the WM_VSCROLL message and wrap the call to CListBox::OnVScroll with SetRedraw(FALSE), and SetRedraw(TRUE), followed by a call to RedrawWindow to refresh the content of the listbox. This will give you smooth and flicker free scrolling. Simple!

void CTransparentListBox::OnVScroll(UINT nSBCode, UINT nPos,
                                    CScrollBar* pScrollBar)
{
   SetRedraw(FALSE);   //prevent any drawing
   CListBox::OnVScroll(nSBCode,nPos,pScrollBar);
   SetRedraw(TRUE);    //restore drawing

   //draw the frame and window content in one shot
   RedrawWindow(0,0,RDW_FRAME|RDW_INVALIDATE|RDW_UPDATENOW);
}

The same type of approach has to happen when an item is selected. So, you intercept the LBN_SELCHANGE message and force a redraw.

BOOL CTransparentListBox::OnLbnSelchange()
   {
   RedrawWindow(0,0,RDW_FRAME|RDW_INVALIDATE|RDW_UPDATENOW);
   return FALSE;
}

Using the Code

To use this class, simply insert a listbox into your dialog box. Make sure you have set the Owner-draw and Has Strings flags. Attach a variable of type CTransparentListBox to the control and you are ready to go. My CTransparentListBox class also gives you the ability to specify different fonts, colors, and shadows. This will come in handy on backgrounds that are too busy or too dark for the standard colors and font size.

class CTestDialog : public CDialog
{
   ....
   CTransparentListBox m_ListBox;
};

void CTestDialog::DoDataExchange(CDataExchange* pDX)
{
   CDialog::DoDataExchange(pDX);
   DDX_Control(pDX, IDC_LIST1, m_ListBox);
}

BOOL CTransStaticDlg::OnInitDialog()
{
   CDialog::OnInitDialog();

   m_ListBox.SetFont(12,"Aria",RGB(255,255,255),
                     RGB(255,0,0));    //Optional

   m_ListBox.AddString(b.Testb.);
}

Have Fun!



About the Author

Ali Rafiee

Ali Rafiee has been developing windows applications for the past 14 years using Visual C++, and he hasn't looked back since. Ali has been a software development consultant for must of his career, but he has finally settled down and has been working for an educational software company for the past 7 years. While he is not working, he is either learning C#, flying airplanes, playing with his daughter, or answering peoples question on newsgroups, he finds that to be a great learning tool for him (He is always trying to learn something new).

Downloads

Comments

  • List selection background color

    Posted by saurabhsaini on 09/04/2012 01:27am

    Sir,i used the following program its really wonderfull code ,could u please suggest me how to put background color to the selected text in the transparent list box,like other list boxes provide default blue color,or user defined color...

    Reply
  • Found a bug

    Posted by GuimarX on 01/03/2009 04:05am

    You need to call the default Reset Content handler for The listBox or it does not erase stored string entries. void CTransparentListBox::ResetContent(BOOL bInvalidate) { Default(); CListBox::ResetContent(); if ( bInvalidate ) { Invalidate(); UpdateWindow(); } }

    Reply
  • excellent work & hard to me

    Posted by namsun on 11/10/2005 02:31am

    it's difficult for me, a few years prgrm experience, to understand folloing description. And is it possible by C#? The third and the tricky part is handling the scroll messages. To overcome the scroll problem, you intercept the WM_VSCROLL message and wrap the call to CListBox::OnVScroll with SetRedraw(FALSE), and SetRedraw(TRUE), followed by a call to RedrawWindow to refresh the content of the listbox. This will give you smooth and flicker free scrolling. Simple! The same type of approach has to happen when an item is selected. So, you intercept the LBN_SELCHANGE message and force a redraw.

    • Transparent ListBox C# .NET

      Posted by chalcol06 on 01/30/2008 02:46pm

      I know this post is too old.. stil... may be my reply might be helpful for some one.. To make the ListBox background Transparent override the CreateParams and set the bitwise extended window style as cp.ExStyle |= 0x20; Then to avoid drawing glitch on scroll handle the WndProc WM_VSCROLL and lock window update then SetRedraw to false then call the base.WndProc of Wm_VScroll and then Reset Redraw and force RedrawWindow. Below are the native methods call. //Lock Window [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] internal static extern bool LockWindowUpdate(IntPtr hWndLock); //SetRedraw false/true by SendMessage [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); //Set Redraw False NativeMethods.SendMessage(m.HWnd, NativeMethods.WM_SETREDRAW, 0, 0); //Set Redraw True NativeMethods.SendMessage(m.HWnd, NativeMethods.WM_SETREDRAW, 1, 0); //RedrawWindow [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] internal static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, uint flags); //Redraw Flag must be:- =NativeMethods.RDW_ERASE |NativeMethods.RDW_INVALIDATE |NativeMethods.RDW_ALLCHILDREN | NativeMethods.RDW_FRAME; Happy Coding!

      Reply
    • The Scroll Problem

      Posted by AliRafiee on 11/10/2005 11:08am

      Hi namsun, I am not sure about C# because I don't have that much experience with C#. But as far as the paragraph in question. Since listboxes do their scrolling by bitblt the unchanged part of the list up or down, it messes up the background image. So what I do to get around the problem is that when the listbox wants to scroll, I stop it from drawing itself by calling SetRedraw(FALSE) tell it to scroll and then call SetRedraw(TRUE) to enable it to redraw again. When that's done, the listbox hasn't been redrawn itself with regards to the new scroll position, so I call RedrawWindow so that my paint message will get called, and at the same time the system will also redraw the border and scrollbar to reflect the new scroll position. I hope this help. Let me know if I can help you out in anyother way. Ali

      Reply
    Reply
  • Excellent work, minor change requested

    Posted by aingram on 10/07/2005 08:09pm

    This is really top-notch work. Thanks for sharing it. One small request is to change line 296 to support list boxes whose items are not selectable: DrawItem(MemDC,i,Rect,(GetStyle() & LBS_NOSEL ? FALSE : GetSel(i))); Thanks!

    Reply
  • too good

    Posted by vinay pandey on 09/27/2005 11:14pm

    hi it is too good.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • With the average hard drive now averaging one terabyte in size, the fallout from the explosion of user-created data has become an overwhelming volume of potential evidence that law-enforcement and corporate investigators spend countless hours examining. Join Us and SANS' Rob Lee for our 45-minute webinar, A Triage and Collection Strategy for Time-Sensitive Investigations, will demonstrate how to: Identify the folders and files that often contain key insights Reduce the time spent sifting through content by …

Most Popular Programming Stories

More for Developers

RSS Feeds