Click here to Skip to main content
11,429,345 members (69,378 online)
Click here to Skip to main content

Wallpaper for Visual Studio .NET

, 30 Mar 2004
Rate this:
Please Sign up or sign in to vote.
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.

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.

#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.

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.

//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.

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

Share

About the Author

rudy_g

United States United States
No Biography provided

Comments and Discussions

 
GeneralCool! But you could also try... Pin
Bcoelho20009-Oct-08 8:06
memberBcoelho20009-Oct-08 8:06 
GeneralI wish if it's work on vs 2005 and 2008 Pin
unique198812-Nov-07 7:32
memberunique198812-Nov-07 7:32 
GeneralTransparency settings don't seem to work Pin
Sheikh Nabeel Moeen2-Jan-06 2:45
memberSheikh Nabeel Moeen2-Jan-06 2:45 
GeneralVS2005 Pin
kanalarbeiter11-Sep-05 22:30
memberkanalarbeiter11-Sep-05 22:30 
GeneralRe: VS2005 Pin
Sppawnkid4-Sep-07 16:45
memberSppawnkid4-Sep-07 16:45 
GeneralDoesn't always work Pin
Thomas Blind21-Jul-05 12:15
sussThomas Blind21-Jul-05 12:15 
GeneralDeeper Pin
|\/| /\ |Z ! /\ |\| []12-Aug-04 13:00
member|\/| /\ |Z ! /\ |\| []12-Aug-04 13:00 
Generaluninstall Pin
Anonymous8-May-04 10:18
sussAnonymous8-May-04 10:18 
GeneralRe: uninstall Pin
Anonymous8-May-04 19:51
sussAnonymous8-May-04 19:51 
Generalno wallpaper Pin
auxcom8-Apr-04 13:10
memberauxcom8-Apr-04 13:10 
GeneralRe: no wallpaper Pin
rudy_g8-Apr-04 20:29
memberrudy_g8-Apr-04 20:29 
GeneralRe: no wallpaper Pin
auxcom9-Apr-04 13:10
memberauxcom9-Apr-04 13:10 
GeneralRe: no wallpaper Pin
rudy_g10-Apr-04 8:18
memberrudy_g10-Apr-04 8:18 
GeneralWonderfull Idea !!!! Pin
Steve Cox31-Mar-04 6:39
memberSteve Cox31-Mar-04 6:39 
GeneralRe: Wonderfull Idea !!!! Pin
rudy_g31-Mar-04 18:14
memberrudy_g31-Mar-04 18:14 
GeneralRe: Wonderfull Idea !!!! Pin
Kandjar19-Jul-04 11:29
memberKandjar19-Jul-04 11:29 
GeneralRe: Wonderfull Idea !!!! Pin
rudy_g19-Jul-04 20:38
memberrudy_g19-Jul-04 20:38 
GeneralRe: Wonderfull Idea !!!! Pin
Kandjar21-Jul-04 15:49
memberKandjar21-Jul-04 15:49 
GeneralRe: Wonderfull Idea !!!! Pin
rudy_g28-Jul-04 18:30
memberrudy_g28-Jul-04 18:30 
GeneralExcellent Utility Pin
Steve Thresher31-Mar-04 1:24
memberSteve Thresher31-Mar-04 1:24 
GeneralRe: Excellent Utility Pin
rudy_g31-Mar-04 18:13
memberrudy_g31-Mar-04 18:13 
GeneralCrashed on Win2000 with VS.Net 2003 Pin
S. Lim29-Mar-04 20:28
memberS. Lim29-Mar-04 20:28 
GeneralRe: Crashed on Win2000 with VS.Net 2003 Pin
rudy_g29-Mar-04 20:51
memberrudy_g29-Mar-04 20:51 
GeneralRe: Crashed on Win2000 with VS.Net 2003 Pin
S. Lim29-Mar-04 21:04
memberS. Lim29-Mar-04 21:04 
Questiontransparent background? Pin
Rob Tomson26-Mar-04 21:54
memberRob Tomson26-Mar-04 21:54 
AnswerRe: transparent background? Pin
rudy_g27-Mar-04 10:53
memberrudy_g27-Mar-04 10:53 
GeneralRe: transparent background? Pin
Member 34784593-Sep-08 5:24
memberMember 34784593-Sep-08 5:24 
GeneralMaybe another way... Pin
Kandjar17-Mar-04 23:43
memberKandjar17-Mar-04 23:43 
GeneralRe: Maybe another way... Pin
rudy_g18-Mar-04 20:00
memberrudy_g18-Mar-04 20:00 
GeneralAnnoying Blink On Scrolling Pin
Shay Ben Sasson12-Mar-04 8:33
sussShay Ben Sasson12-Mar-04 8:33 
GeneralRe: Annoying Blink On Scrolling Pin
rudy_g12-Mar-04 16:26
memberrudy_g12-Mar-04 16:26 
GeneralRe: Annoying Blink On Scrolling Pin
derek.thornton15-Mar-04 11:26
memberderek.thornton15-Mar-04 11:26 
GeneralRe: Annoying Blink On Scrolling Pin
rudy_g15-Mar-04 17:30
memberrudy_g15-Mar-04 17:30 
GeneralRe: Annoying Blink On Scrolling Pin
Anonymous15-Mar-04 18:09
sussAnonymous15-Mar-04 18:09 
GeneralRe: Annoying Blink On Scrolling Pin
Kandjar16-Mar-04 5:41
memberKandjar16-Mar-04 5:41 
GeneralRe: Annoying Blink On Scrolling Pin
rudy_g16-Mar-04 16:28
memberrudy_g16-Mar-04 16:28 
GeneralRe: Annoying Blink On Scrolling Pin
Anonymous17-Mar-04 2:26
sussAnonymous17-Mar-04 2:26 
GeneralRe: Annoying Blink On Scrolling Pin
rudy_g17-Mar-04 20:57
memberrudy_g17-Mar-04 20:57 
GeneralRe: Annoying Blink On Scrolling Pin
Kandjar17-Mar-04 23:31
memberKandjar17-Mar-04 23:31 
GeneralRe: Annoying Blink On Scrolling Pin
Martin Plante31-Mar-04 3:38
memberMartin Plante31-Mar-04 3:38 
GeneralRe: Annoying Blink On Scrolling Pin
rudy_g31-Mar-04 18:12
memberrudy_g31-Mar-04 18:12 
GeneralCool Pin
Pete Davis12-Mar-04 2:49
memberPete Davis12-Mar-04 2:49 
GeneralRe: Cool Pin
rudy_g12-Mar-04 16:16
memberrudy_g12-Mar-04 16:16 
GeneralWM_PAINT handler Pin
Maximilian Hänel8-Mar-04 20:54
memberMaximilian Hänel8-Mar-04 20:54 
GeneralRe: WM_PAINT handler Pin
rudy_g8-Mar-04 21:13
memberrudy_g8-Mar-04 21:13 
GeneralRe: WM_PAINT handler Pin
Maximilian Hänel8-Mar-04 21:26
memberMaximilian Hänel8-Mar-04 21:26 
GeneralDidn't work on VS.net 7.0 Pin
Kandjar8-Mar-04 2:02
memberKandjar8-Mar-04 2:02 
GeneralRe: Didn't work on VS.net 7.0 Pin
rudy_g8-Mar-04 17:40
memberrudy_g8-Mar-04 17:40 
GeneralRe: Didn't work on VS.net 7.0 Pin
Kandjar8-Mar-04 23:10
memberKandjar8-Mar-04 23:10 
GeneralRe: Didn't work on VS.net 7.0 Pin
paulhorstink9-Mar-04 21:26
memberpaulhorstink9-Mar-04 21:26 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150428.2 | Last Updated 31 Mar 2004
Article Copyright 2004 by rudy_g
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid