Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / ATL
Article

Wallpaper for Visual Studio .NET

Rate me:
Please Sign up or sign in to vote.
4.33/5 (28 votes)
30 Mar 20045 min read 218.8K   3.2K   49   74
How to add an image to the background of the Visual Studio .NET edit window.

Introduction

This project is the result of some research that I did for ClearJump. ClearJump was kind enough to give me permission to publish the source code.

For details about the versions, see the History section at the end of the article.

In Visual Studio .NET, you can change the background color of the text editor but you cannot put an image in it as a background wallpaper. So, I decided to look at whether a Visual Studio Add-In could be made to provide support for a wallpaper bitmap. Except for a problem with flashing during scrolling, it actually worked as you can see below.

Sample Image - VSWallpaper.png

I'll discuss the flashing problem in detail later. If you find a solution to fix it, please let me know. Now, on with the wallpaper.

Even if you don't use this Add-In for adding a bitmap to your Visual Studio interface, the project may still be of interest for its use of a little known window subclassing technique. The project also features some image processing classes. For example, the Image class supports pixel-level access to the bitmap and can be used for adding alpha channels.

This project was created using the .NET Add-In wizard and has been tested on Visual Studio version 7.1.

Installation

  • Run the the VSWallpaperSetup.msi file.
  • Restart Visual Studio.
  • The Wallpaper menu should now appear in the Tools menu.

Enjoy!

Implementation

I was not able to find any .NET automation interfaces that support custom backgrounds. The best you can do programmatically is change the background color properties which is the same limited control that you have from the Tools/Options menu command. So I decided to subclass the text editor window and put my background code into the WM_ERASEBKGND handler. Unfortunately, .NET automation doesn't expose the window handles (HWND) of text editor windows. However, it is possible to get the main window handle.

MC++
HWND hwnd;
CComPtr<EnvDTE::Window> main;
m_pDTE->get_MainWindow((EnvDTE::Window**)&main) );
main->get_HWnd((long*)&hwnd) );

After some poking around, I retrieved the undocumented .NET window class names. Fortunately, .NET automation generates events when an editor text window is created or destroyed. The wallpaper Add-In processes these events. Using the window caption that is reported by these events and the window class names, the Add-In finds the appropriate window handle. You can see this by looking at the code for the EditorInstance class. This class uses the main window handle, editor window caption and editor window class name to find the editor window handle. The process is very straightforward and consists of calling the FindWindowEx() and EnumChildWindows() Win32 functions.

Next, I tried to use the SetWindowLong() API to subclass the window but that didn't work. The SetWindowLong() function should return a pointer to the previous window procedure in the call chain. Well in this case, it returned some number that is not a valid pointer.

It turns out that .NET uses the little known SetWindowSubclass() API. This API is supported by the comctl32.dll version 5.8 or later. So, you need to include the commctrl.h header and add comctl32.lib to your project. After that, the subclassing code is easy.

MC++
#include "StdAfx.h"
#include <commctrl.h>
#include ".\subclassedwindow.h"

#define SUBCLASS_ID (0xab01265)

LRESULT CALLBACK g_subclass( HWND hWnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam,
    UINT_PTR uIdSubclass,
    DWORD_PTR dwRefData
);


SubclassedWindow::SubclassedWindow(HWND h) : m_hwnd(h)
{
    if( !m_hwnd ) return;
    ::SetWindowSubclass( m_hwnd, g_subclass, SUBCLASS_ID, (DWORD_PTR)this );
}

SubclassedWindow::~SubclassedWindow(void)
{
    if( !m_hwnd ) return;
    ::RemoveWindowSubclass( m_hwnd, g_subclass, SUBCLASS_ID );
    m_hwnd = NULL;
}


LRESULT SubclassedWindow::winproc( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    //WM_NCDESTROY is the last message, 
    //detach the object from the window handle
    if( uMsg == WM_NCDESTROY )
    {
        HWND hsave = m_hwnd;
        ::RemoveWindowSubclass( m_hwnd, g_subclass, SUBCLASS_ID );
        m_hwnd = NULL;
        return ::DefSubclassProc( hsave, uMsg, wParam, lParam );
    }

    return dispatch( uMsg, wParam, lParam );
}

LRESULT SubclassedWindow::dispatch( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    //call the next window proc in the chain
    return ::DefSubclassProc( m_hwnd, uMsg, wParam, lParam );
}

LRESULT CALLBACK g_subclass( HWND hWnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam,
    UINT_PTR uIdSubclass,
    DWORD_PTR dwRefData
)
{
    //call the SubclassedWindow object that is attached to
    //this window
    SubclassedWindow *pw = (SubclassedWindow *)dwRefData;
    if( pw ) return pw->winproc(uMsg, wParam, lParam);
    return 0;
}

Now we can process the WM_ERASEBKGND message. But wait. Not so fast! Changing the background in the message handler actually doesn't work. Believe it or not, whatever you paint in the WM_ERASEBKGND handler will be repainted by VS.NET. Interestingly, VS.NET paints the background in the WM_PAINT message along with the text. So, I came up with the following workaround.

First, prepare the background image.

  • Read the image file (BMP, GIF or JPEG).
  • Alpha-blend the image file with the background color that is specified by the user in Tools/Options.

Now we have a ready-to-use background image.

In the WM_PAINT message handler, we do the following:

  • Call the .NET painting routine.
  • From the editor window, extract and save the new image.
  • BitBlt our custom image.
  • TransparentBlt the saved image. The transparent color is the user's selected background color. This step paints the text on top of the custom background image that we painted in the previous step.

To give you an idea of what this looks like, below is the code that does the painting.

MC++
LRESULT EditorWindow::winproc( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    if( uMsg == WM_PAINT  )
    {
        //save the update rectangle
        RECT rc;
        GetUpdateRect( m_hwnd, &rc, FALSE );

        HDC hdc;
        hdc = ::GetDC( m_hwnd );

        //preapare an empty bitmap
        HBITMAP img = CreateCompatibleBitmap( hdc, 
                  rc.right - rc.left, rc.bottom - rc.top );
        HDC hmem = ::CreateCompatibleDC( hdc );
        HBITMAP hold = (HBITMAP)::SelectObject(hmem, (HGDIOBJ)img);

        //call the .NET handler
        //we now have the updated editor window
        LRESULT lr = 0;
        lr = SubclassedWindow::winproc( uMsg, wParam, lParam );

        //the update rectangle is not empty
        if( !::IsRectEmpty(&rc)  )
        {
            //hide the caret temporarily
            ::HideCaret( m_hwnd );

            //extract the image from the editor
            BitBlt(hmem, 0,0,
                    rc.right - rc.left, rc.bottom - rc.top,
                    hdc,
                    rc.left, rc.top,
                    SRCCOPY);


            //draw our background image
            m_connect->m_background.draw( hdc, 
                   m_connect->m_bkg_color, 
                   m_connect->m_config.m_transparency, rc );

            //blt the saved editor image with transparent background color
            TransparentBlt( hdc, 
                   rc.left, 
                   rc.top, 
                   rc.right - rc.left, 
                   rc.bottom - rc.top, 
                   hmem, 0, 0, 
                   rc.right - rc.left, 
                   rc.bottom - rc.top, 
                   m_connect->m_bkg_color );

            //restore caret
            ::ShowCaret( m_hwnd );
        }


        //clean up
        ::SelectObject(hmem, hold );
        ::DeleteObject( (HGDIOBJ)img );
        ::DeleteDC(hmem);

        ::ReleaseDC( m_hwnd, hdc );

        return lr;
    }
    return SubclassedWindow::winproc( uMsg, wParam, lParam );
}

You can see from the code why the edit window flashes during scrolling, especially when the update region is large. The flashing occurs when the .NET painting routine is called and then we repaint it with our background image. Ugly. Again, if you figure out how to fix or work around this problem, please let me know.

For alpha-blending, we use the ImageLib::Image class that's found in the image.cpp and image.h files. This class is useful for accessing bitmap data directly. With it, you can initialize an Image object with your bitmap and then access individual pixels. The createAlphaBitmap() function can be used for adding an alpha channel to the image. This function returns HBITMAP that can be used directly in the AlphaBlend() function.

MC++
//Image class
//===========
class Image
{
public:
    Image();
    virtual ~Image();

    // allocate space for an image of the specified size
    Result allocate(size_t rows, size_t cols);
    // destroy the image
    void destroy(void);

    // initializes the Image object with the data from the bitmap
    Result load(HBITMAP hnd);

    // create bitmap from the image data
    HBITMAP createBitmap(HDC hdc) const;
    //add the alpha channel to the 'c' color and create bitmap
    //that can be used with AlphaBlend()
    HBITMAP createAlphaBitmap(HDC hdc, 
           COLORREF c, AlphaComponent alpha ) const;
    //create DIB from the image data
    Result  createDIB( Dib& dib ) const;  //creates 24bits DIB

    //pixel-level access
    inline Pixel*        getData() { return m_data; }
    inline const Pixel*  getData() const { return m_data; }
    inline size_t        getRows() const { return m_rows; }
    inline size_t        getCols() const { return m_cols; }
    // set the pixel color
    inline void setPixel(size_t row, size_t col, COLORREF cr);
    inline void setPixel( size_t row, size_t col,
                        PixelComponent r,
                        PixelComponent g,
                        PixelComponent b );
     inline Image& operator=( const Image& in )
     {
         if( this == &in ) return *this;
         if( getRows() != in.getRows() || getCols() != in.getCols() )
         {
             Result rc = allocate( in.getRows(), in.getCols() );
             assert( rc == rc_ok );
         }
         memcpy( m_data, in.m_data, m_rows*m_cols*sizeof(Pixel) );
         return *this;
     }
};

History

v1.1 (XP only)

I think that we are one step closer to solving the flashing problem on XP at least. It's almost gone now. Could someone please test it on Win2k? Thanks!

The key is in using the little know function, PrintWindow(). This function allows you to send a memory device context to the WM_PAINT handler. So that BeginPaint() will get the memory DC instead of the normal window DC.

The painting code look like this now.

MC++
LRESULT EditorWindow::winproc( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    if( uMsg == WM_PAINT && m_connect && m_connect->m_config.m_enabled
        && !m_reentry )
    {
        //save the update rectangle
        RECT rc, cr;
        ::GetUpdateRect( m_hwnd, &rc, FALSE );
        
        //the update rectangle is not empty
        if( !::IsRectEmpty(&rc)  )
        {
            ::GetClientRect( m_hwnd, &cr );
        
            HDC hdc;
            hdc = ::GetDC( m_hwnd );
            
            //preapare an empty bitmap
            HBITMAP img = CreateCompatibleBitmap( hdc, cr.right - cr.left, 
                                                  cr.bottom - cr.top );
            HDC hmem = ::CreateCompatibleDC( hdc );
            HBITMAP hold = (HBITMAP)::SelectObject(hmem, (HGDIOBJ)img);
            
            //hide the caret temporarily
            ::HideCaret( m_hwnd );
            
            //call the .NET handler
            //we now have the updated editor window
            m_reentry = true;
            ::PrintWindow( m_hwnd, hmem, PW_CLIENTONLY );
            m_reentry = false;
                
            //draw our background image             
            m_connect->m_background.draw( hdc, m_connect->m_bkg_color, 
                                          m_connect->m_config.m_transparency,
                                          rc );
            
            //blt the saved editor image with transparent background color
            TransparentBlt( hdc, rc.left, rc.top, rc.right - rc.left, 
                            rc.bottom - rc.top, hmem, rc.left, rc.top, 
                            rc.right - rc.left, rc.bottom - rc.top, 
                            m_connect->m_bkg_color );

            //clean up  and validate        
            ::SelectObject(hmem, hold );
            ::DeleteObject( (HGDIOBJ)img );
            ::DeleteDC(hmem);
            
            ::ReleaseDC( m_hwnd, hdc ); 
            
            ::ValidateRect( m_hwnd, &rc );  
        
            ::ShowCaret( m_hwnd );
            return 0;
        }
    }
    
    return SubclassedWindow::winproc( uMsg, wParam, lParam );
}

v1.2 (XP only)

Fixed visibility problems when the background doesn't cover all the text window.

The scrolling with scrollbar should be perfect now.

When scrolling with keyboard, the background still jumps up and down. This is caused by an internal call to ScrollWindow() or ScrollDC() in the keystrokes message handler. I am not sure how to deal with that yet.

Conclusion

Let me know if you have any cool wallpapers.

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 might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Didn't work on VS.net 7.0 Pin
Kandjar8-Mar-04 22:10
Kandjar8-Mar-04 22:10 
GeneralRe: Didn't work on VS.net 7.0 Pin
Paul Horstink9-Mar-04 20:26
Paul Horstink9-Mar-04 20:26 
GeneralRe: Didn't work on VS.net 7.0 Pin
rudy_g9-Mar-04 20:54
rudy_g9-Mar-04 20:54 
GeneralRe: Didn't work on VS.net 7.0 Pin
Kandjar9-Mar-04 22:29
Kandjar9-Mar-04 22:29 
GeneralRe: Didn't work on VS.net 7.0 Pin
joeyespo10-Mar-04 21:04
joeyespo10-Mar-04 21:04 
GeneralRe: Didn't work on VS.net 7.0 Pin
rudy_g10-Mar-04 21:22
rudy_g10-Mar-04 21:22 
GeneralRe: Didn't work on VS.net 7.0 Pin
Kandjar10-Mar-04 22:59
Kandjar10-Mar-04 22:59 
Questionwhy? Pin
Nathan Ridley7-Mar-04 18:12
Nathan Ridley7-Mar-04 18:12 
That sure seems like a lot of work just to put a background behind your code... I really for the life of me can't figure out why you'd want to.

NATHAN RIDLEY
Web Application Developer
email: nathan @ netlab.com.au
[remove the spaces before and after the @ symbol]
AnswerRe: why? Pin
rudy_g7-Mar-04 18:39
rudy_g7-Mar-04 18:39 
GeneralRe: why? Pin
Nathan Ridley7-Mar-04 21:17
Nathan Ridley7-Mar-04 21:17 
AnswerRe: why? Pin
Jerry Hammond8-Mar-04 16:15
Jerry Hammond8-Mar-04 16:15 
GeneralRe: why? Pin
Nathan Ridley8-Mar-04 16:24
Nathan Ridley8-Mar-04 16:24 
GeneralRe: why? Pin
theill9-Mar-04 7:04
theill9-Mar-04 7:04 
GeneralRe: why? Pin
Jerry Hammond9-Mar-04 8:03
Jerry Hammond9-Mar-04 8:03 
GeneralRe: why? Pin
Anna-Jayne Metcalfe30-Mar-04 21:41
Anna-Jayne Metcalfe30-Mar-04 21:41 
GeneralRe: why? Pin
x0n16-Mar-04 17:05
x0n16-Mar-04 17:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.