For these “effect” windows we have:
Window Styles: 96000000 | Extended Styles: 00080080 |
WS_POPUP | WS_EX_LEFT |
WS_VISIBLE | WS_EX_LTRREADING |
WS_CLIPSIBLINGS | WS_EX_RIGHTSCROLLBAR |
WS_CLIPCHILDREN | WS_EX_TOOLWINDOW |
WS_EX_LAYERED |
being WS_EX_LAYERED probably the most important one.
Among the others, layered windows can be used to leverage the alpha blend effects, something surely used in this case where the glow effect is obtained using a color rapidly shaded.
The implementation we give here to the GlowWindow uses hints found here and there in the Net (in particular StackOverflow, all developers’ saver): somehow none of them was fine to me, they all had something missing… so I’m putting here the solution I’ve adopted “cutting and pasting” the various ideas…
SideGlow window
Our implementation will follow the same pattern: we are going to create a “glow effect” that is a layered window with no caption, no border and so on: a “plain” transparent rectangle; we’ll use one of these windows for each side of the final GlowWindow.
In the SideEffect window we’ll properly paint the glow effect blending the color with the background – to do this the layered window perfectly fits our needs: we are going to use a lot of native Win32 functions, so some import files will contain structures and functions from user32.dll and gdi32.dll.
The SideGlow window is created with these base styles:
const UInt32 extendedStyle = (UInt32)(WindowExStyles.WS_EX_LEFT | WindowExStyles.WS_EX_LTRREADING | WindowExStyles.WS_EX_RIGHTSCROLLBAR | WindowExStyles.WS_EX_TOOLWINDOW); const UInt32 style = (UInt32)(WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_CLIPCHILDREN | WindowStyles.WS_POPUP);
once done that, the SideGlow windows have to be updated explicitly, something we’ll do only when really needed:
User32.UpdateLayeredWindow(_handle, screenDc, ref newLocation, ref newSize, memDc, ref _ptZero, 0, ref _blend, 0x02);
“only when really needed” means -for example- when the Main Window is resized: in that case, Width and/or Height will change and thus also the size of the SideGlow has to change (depending on where it is positioned…).
When the Main Window is instead just moved around, we’ll have to reposition the four SideGlow children that must follow the window they are decorating.
There are other operations we’ll have to listen and react to: changes in z-order, show/hide, minimize/maximize and so on: whenever one of these operation is performed by the Main Window, the SideGlow will have to do something accordingly.
The paint operation is a bit tricky: we use a color and we blend it with the foreground using the alpha channel – this is where the layered window comes in handy; we need to re-paint the window only when the size of the Main window is changed because we’ll have to change the size of the SideGlow as well. We are not going to recreate exactly the same glow effect of VS or Office: the part that could be made better is how the shade is decreased around the corners but the final result is more than acceptable for our purpose:
GlowDecorator class
We’ll connect the SideGlows to a (redefined) Main Window through a decorator class: the decorator has an Attach() function that receives the window it has to attach to as a parameter; it will generate four SideGlow and will also hook its main loop. The messages needed from the Main Window are WM_WINDOWPOSCHANGED, WM_SETFOCUS, WM_KILLFOCUS and WM_SIZE.
Also, since the SideGlow can detect the mouse down, we’ll let them start the Main Window resize: this is coordinated by the GlowDecorator; when a mouse down event is generated by a SideGlow, then a proper message is sent to the decorated Main Window that can start a standard resize operation:
private void HandleSideMouseDown(object sender, SideGlowResizeArgs e) { if (e.Mode == HitTest.HTNOWHERE || e.Mode == HitTest.HTCAPTION) { return; } User32.SendMessage(_parentWindowHndl, (uint)WindowsMessages.WM_SYSCOMMAND, (IntPtr)e.Mode.ToInt(), IntPtr.Zero); }
MainWindow
The Main Window is just a standard WPF Window which Non-Client Area has been stripped-off and the Client Area has been used to completely redefine the Window look and feel.
Due to this we are going to lose some interesting standard features, such as the Aero snap but well… maybe we’ll fix it one day or another.
We are creating the window “by code” because we wants the inheritor not to have problems when they derive their custom Window from the GlowWindow; also, we’ll have to do some dirty work by ourselves, such as use the (redefined) caption to move the window.
Here it is the final result:
Implementation
A full, non-optimal, probably buggy and for sure optimizable version is available on GitHub here.