|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis 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.
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 This project was created using the .NET Add-In wizard and has been tested on Visual Studio version 7.1. Installation
Enjoy! ImplementationI 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 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 Next, I tried to use the It turns out that .NET uses the little known #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 First, prepare the background image.
Now we have a ready-to-use background image. In the
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 //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;
}
};
Historyv1.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, 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 ConclusionLet me know if you have any cool wallpapers.
|
||||||||||||||||||||||