Related
I've made this little .ps1 script as it allows me to run C# without using a compiler (directly at least). I'd like to move the "Accessibility On-Screen Keyboard" that opens with cmd /c osk.exe as I can't really use TabTip - the panned touchscreen keyboard on Win8+.
As the On-Screen Keyboard isn't really that pretty like the panned keyboard, I'd like to move the keyboard to a desired location and resize it. I noticed the OSK has a child window (OSKMainClass → DirectUIHWND), so I went even for that, but no luck. On the other hand, the same code for a single window works for notepad and correctly places and resizes it.
I put Process.Start() into the if, so that it gave back some feedback, therefore I see it found the child window - that's nice. BUT, it didn't move it.
An interesting thing appeared when I pressed Alt+Tab and held the Alt - the OSK window appeared like a grey fullscreen one (metro-like style). I'm not sure if that's an intended behavior for a parent window or not.
Also, I thought it'd be the window styles' thingy, but no, the styles are almost the same (except two unrelated styles), so I'm without any clue how to continue. Any ideas?
Code:
$CSsource = #"
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Win {
public static class API {
[DllImport("user32.dll")]
static extern IntPtr FindWindow(
string lpClassName,
string lpWindowName
);
[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(
IntPtr parentHwnd,
IntPtr childAfter,
string className,
string windowTitle
);
[DllImport("user32.dll")]
static extern bool ShowWindow(
IntPtr hWnd,
int nCmdShow
);
[DllImport("user32.dll")]
static extern bool MoveWindow(
IntPtr hWnd,
int X, int Y,
int Width, int Height,
bool Repaint
);
public static void Move(
string wClass, string wName,
string childClass,
int top, int left,
int width, int height
) {
IntPtr hwnd = FindWindow(wClass, wName);
if ((int) hwnd > 0) {
IntPtr subHwnd;
if (childClass != String.Empty) {
subHwnd = FindWindowEx(hwnd, IntPtr.Zero, childClass, null);
} else {
subHwnd = IntPtr.Zero;
}
if ((int) subHwnd > 0) {
MoveWindow(subHwnd, left, top, width, height + 50, true);
Process.Start("cmd"); //feedback from loop, heh
} else {
MoveWindow(hwnd, left, top, width, height + 50, true);
}
}
}
}
}
"#
add-type -TypeDefinition $CSsource
#[Win.API]::Move('OSKMainClass', 'On-Screen Keyboard', 'DirectUIHWND', 50, 50, 200, 100)
#[Win.API]::Move('OSKMainClass', 'Accessibility On-Screen Keyboard', 'DirectUIHWND', 50, 50, 200, 100)
[Win.API]::Move('OSKMainClass', 'Accessibility On-Screen Keyboard', '', 50, 50, 200, 100)
[Win.API]::Move('Notepad', 'Untitled - Notepad', '', 50, 50, 200, 100)
OSK window styles:
WS_CAPTION
WS_VISIBLE
WS_CLIPSIBLINGS
WS_CLIPCHILDREN
WS_SYSMENU
WS_THICKFRAME
WS_OVERLAPPED
WS_MINIMIZEBOX
WS_EX_LEFT
WS_EX_LTRREADING
WS_EX_TOPMOST
WS_EX_WINDOWEDGE
WS_EX_APPWINDOW
WS_EX_LAYERED
WS_EX_NOACTIVATE
Notepad window styles:
above +
WS_RIGHTSCROLLBAR
WS_ACCEPTFILES
OSK has UIAccess="true" in its manifest so it runs at a higher integrity level (slightly above medium).
To interact with it you need to:
Run your app elevated
or
Put UIAccess="true" in your manifest
Sign the .exe (This blog post indicates that you can self sign during testing)
Put the .exe somewhere inside the Program Files folder
You can also try to disable UAC to verify that your lack of UIAccess is the problem.
I use the below code to pop-up a form on the screen on top of everything but it doesn't steal focus.
This works fine, but I now need to close the form, the form itself doesnt show up in Application.OpenForms
How do I go about doing this?
Setup and open the form
frmClientCall frm = new frmClientCall {StartPosition = FormStartPosition.Manual, Text = "Phone Call"};
frm.Location = new System.Drawing.Point(
Screen.PrimaryScreen.WorkingArea.Width - frm.Width,
Screen.PrimaryScreen.WorkingArea.Height - frm.Height - 202
);
frm.lblClient.Text = URI;
frm.ShowInactiveTopmost();
Code to prevent focus on the form
private const int SW_SHOWNOACTIVATE = 4;
private const int HWND_TOPMOST = -1;
private const uint SWP_NOACTIVATE = 0x0010;
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
static extern bool SetWindowPos(
int hWnd, // Window handle
int hWndInsertAfter, // Placement-order handle
int X, // Horizontal position
int Y, // Vertical position
int cx, // Width
int cy, // Height
uint uFlags); // Window positioning flags
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
public void ShowInactiveTopmost()
{
ShowWindow(Handle, SW_SHOWNOACTIVATE);
SetWindowPos(Handle.ToInt32(), HWND_TOPMOST, Left, Top, Width, Height, SWP_NOACTIVATE);
}
Yes, this is not the only mishap. You for example can also see that the form's Load event never fires. Basic issue is that you are bypassing the normal logic, which is a pretty big deal in Winforms since it creates the native window lazily. In your case it happens when you use the Handle property. I think the underlying issue is that the Visible property was never set to true, that's the one that truly gets the ball rolling.
Well, don't do it this way, Winforms already supports showing a window without activating it. Paste this code into the form you want to display without activation:
protected override bool ShowWithoutActivation {
get { return true; }
}
The SetWindowPos() pinvoke to make it topmost is not necessary either, paste this code:
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
cp.ExStyle |= 8; // Turn on WS_EX_TOPMOST
return cp;
}
}
I am working on a C# WPF application that uses two screens. In the application the user is able to clone or extend the screen depending on what the user want to do. This is done in windows 7 and is using the following code:
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
private static extern long SetDisplayConfig(uint numPathArrayElements, IntPtr pathArray, uint numModeArrayElements, IntPtr modeArray, uint flags);
UInt32 SDC_TOPOLOGY_INTERNAL = 0x00000001;
UInt32 SDC_TOPOLOGY_CLONE = 0x00000002;
UInt32 SDC_TOPOLOGY_EXTEND = 0x00000004;
UInt32 SDC_TOPOLOGY_EXTERNAL = 0x00000008;
UInt32 SDC_APPLY = 0x00000080;
public void CloneDisplays()
{
SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, (SDC_APPLY | SDC_TOPOLOGY_CLONE));
}
public void ExtendDisplays()
{
SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, (SDC_APPLY | SDC_TOPOLOGY_EXTEND));
}
Now to my problem. When using the above code I manage to clone/extend the screen. However, after this is done the taskbar at the bottom of the screen is in front of the full screen application which should not be the case. How do i put the application window back at the top?
Additional information:
When I start the application it starts in fullscreen with the taskbar behind the application. This is done by setting the following:
WindowState="Maximized"
WindowStyle="None"
And this is what I want after the clone/extend has been done.
Thanks
Edit:
I have noticed that after I clone/extend the screen and sleep for say 5 seconds everything works as it should. However, as soon as the 5 seconds is over and the function exits the taskbar gets on top. Therefore it seems that I can not change something right after the clone/extend because the taskbar will always get on top in the end. So somehow I have to figure out how to stop the taskbar to behave like this, instead of changing the property of the window.
Try setting the width and height of the WPF window as follows, You could set this within window constructor.
Width = System.Windows.SystemParameters.PrimaryScreenWidth;
Height = System.Windows.SystemParameters.PrimaryScreenWidth;
To hide the taskbar try setting,
Width = System.Windows.SystemParameters.FullPrimaryScreenWidth;
Height = System.Windows.SystemParameters.FullPrimaryScreenHeight;
I'm already doing full-screen mode within my winforms applications, but i think you can do it more or less the same within WPF:
(this has to be different but similar in WPF):
form.WindowState = FormWindowState.Normal;
form.FormBorderStyle = FormBorderStyle.None;
form.Bounds = Screen.GetBounds(form);
Then the next step is to hide the task-bar if your application is on the primary screen:
if (Screen.PrimaryScreen.Equals(Screen.FromRectangle(Screen.GetBounds(form))))
{
ShowWindowsToolbar(false);
}
And the method ShowWindowsToolbar() is implemented as follows:
[DllImport("user32.dll")]
private static extern int FindWindow(string lpszClassName, string lpszWindowName);
[DllImport("user32.dll")]
private static extern int ShowWindow(int hWnd, int nCmdShow);
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
public void WindowsToolbar(bool visible)
{
int hWnd = FindWindow("Shell_TrayWnd", "");
ShowWindow(hWnd, visible ? SW_SHOW : SW_HIDE);
}
That's the way, how most of the tools out there support this kind of stuff. Also note, that this mode can mostly entered/leaved by pressing F11. So it would be good, if you also support this keystroke.
Turns out all I have to do is update the dispatcher queue and force it to do the update right after the clone/extend has been done. Then I can update the window properties.
public void ExtendDisplays()
{
SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, (SDC_APPLY | SDC_TOPOLOGY_EXTEND));
this.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })); //Force update
current_window.hide();
current_window.show();
}
To give some background, I am developing a piece of software that assists players with the game Star Wars: The old republic. The game has very limited user interface capabilities, so i am developing an external application that will parse the log in real time, and output visual clues to help the user maximize their in-game performance. For example, if a character gets a certain "buff" the combat-log will show it, and i want to place a visual clue on the screen (so the user doesn't need to pay attention to small icons at the perimeter of the screen).
Before i begin, i wanted to create a few "proof of concept" scripts for myself to figure out how i am going to hand the major parts. The one i am stuck on is where i have my question:
I need to be able to show a graphic, likely a PNG file with transparency, on the screen over the game. The user needs to be able to click through that image so they can continue to interact with the game. I am a bit lost on how to go about that. The requirements would be:
Show an image (or multiple images, for that matter)
Have that image stick over-top the other application, even without application "focus"
Have that image be non-interactive (or click-through-able).
I am developing the application in C#
Any guidance on where to begin would be very much appreciated!
I have started looking at doing some similar things, so this might be able to give you a start.
For a first version, you might start by looking at the "AllowTransparency", and "TransparencyKey" , and "TopMost" properties of Form.
(I have found that the TransparencyKey doesn't work with White (255,255,255), but that specific non-white colors work fine...not sure why).
This would work as a click-throughable form that would stand above other forms...but since it is transparent, you can't display images in the transparent part. But if all you need is a hud that fits around the target application, this might be the easiest way.
If this top level form doesn't end up in front of the game...you might try putting the game in Windowed mode.
When running in full-screen mode, games generally draw to the screen directly through ActiveX, Direct3D, OpenGL, DirectDraw, etc.
Drawing on top of these would require injecting code into the DirectX, OpenGL, orother engine's draw/update/refresh function (basically tell DirectX3D to draw your stuff at the end of each draw cycle). There are some existing software that does this: for example, Steam Overlay, fraps, xfire.
A quick google search found "Game Overlay" which although I haven't downloaded or tried, says that it can overlay form applications on top of games for you.
(Seems that that program is under a company that was just dissolved, and I couldn't seem to get it to work for me anyway...)
It is possible to create a form that is not completely transparent but is click throughable by making native Windows calls..I'll see if I can create an example over the next few days.
I found an old test project and cleaned it up a bit.
Basically when run it will draw 500 random red lines to the front of the screen that are clickthrough-able. Then it draws 1000 random white lines (i.e. erases). Then repeats.
In writing the code I wanted to get a proof of concept for a couple of things: How to be able to draw on the full surface of a form, How to programmatically make the form become full size over multiple screens, How to make use of Background Workers, and How this proof of concept might work as a transparent overlay.
Instructions:
Create a new Windows Forms Project named TranparentOverlay_simpleExample
In design view, set the following properties on Form1:
BackColor: White
FormBorderStyle: None
Location: -1280, 0 (i.e. the top left corner of your screens, with one screen probably just 0,0)
TopMost: True
TransparencyKey: White
WindowState: Maximized
Now enter the code view for Form1 and replace it with the following:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
namespace TransparentOverlay_simpleExample
{
public partial class Form1 : Form
{
BackgroundWorker bw = new BackgroundWorker();
Random rand = new Random(DateTime.Now.Millisecond);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, uint wMsg, UIntPtr wParam, IntPtr lParam); //used for maximizing the screen
const int WM_SYSCOMMAND = 0x0112; //used for maximizing the screen.
const int myWParam = 0xf120; //used for maximizing the screen.
const int myLparam = 0x5073d; //used for maximizing the screen.
int oldWindowLong;
[Flags]
enum WindowStyles : uint
{
WS_OVERLAPPED = 0x00000000,
WS_POPUP = 0x80000000,
WS_CHILD = 0x40000000,
WS_MINIMIZE = 0x20000000,
WS_VISIBLE = 0x10000000,
WS_DISABLED = 0x08000000,
WS_CLIPSIBLINGS = 0x04000000,
WS_CLIPCHILDREN = 0x02000000,
WS_MAXIMIZE = 0x01000000,
WS_BORDER = 0x00800000,
WS_DLGFRAME = 0x00400000,
WS_VSCROLL = 0x00200000,
WS_HSCROLL = 0x00100000,
WS_SYSMENU = 0x00080000,
WS_THICKFRAME = 0x00040000,
WS_GROUP = 0x00020000,
WS_TABSTOP = 0x00010000,
WS_MINIMIZEBOX = 0x00020000,
WS_MAXIMIZEBOX = 0x00010000,
WS_CAPTION = WS_BORDER | WS_DLGFRAME,
WS_TILED = WS_OVERLAPPED,
WS_ICONIC = WS_MINIMIZE,
WS_SIZEBOX = WS_THICKFRAME,
WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW,
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
WS_CHILDWINDOW = WS_CHILD,
//Extended Window Styles
WS_EX_DLGMODALFRAME = 0x00000001,
WS_EX_NOPARENTNOTIFY = 0x00000004,
WS_EX_TOPMOST = 0x00000008,
WS_EX_ACCEPTFILES = 0x00000010,
WS_EX_TRANSPARENT = 0x00000020,
//#if(WINVER >= 0x0400)
WS_EX_MDICHILD = 0x00000040,
WS_EX_TOOLWINDOW = 0x00000080,
WS_EX_WINDOWEDGE = 0x00000100,
WS_EX_CLIENTEDGE = 0x00000200,
WS_EX_CONTEXTHELP = 0x00000400,
WS_EX_RIGHT = 0x00001000,
WS_EX_LEFT = 0x00000000,
WS_EX_RTLREADING = 0x00002000,
WS_EX_LTRREADING = 0x00000000,
WS_EX_LEFTSCROLLBAR = 0x00004000,
WS_EX_RIGHTSCROLLBAR = 0x00000000,
WS_EX_CONTROLPARENT = 0x00010000,
WS_EX_STATICEDGE = 0x00020000,
WS_EX_APPWINDOW = 0x00040000,
WS_EX_OVERLAPPEDWINDOW = (WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE),
WS_EX_PALETTEWINDOW = (WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST),
//#endif /* WINVER >= 0x0400 */
//#if(WIN32WINNT >= 0x0500)
WS_EX_LAYERED = 0x00080000,
//#endif /* WIN32WINNT >= 0x0500 */
//#if(WINVER >= 0x0500)
WS_EX_NOINHERITLAYOUT = 0x00100000, // Disable inheritence of mirroring by children
WS_EX_LAYOUTRTL = 0x00400000, // Right to left mirroring
//#endif /* WINVER >= 0x0500 */
//#if(WIN32WINNT >= 0x0500)
WS_EX_COMPOSITED = 0x02000000,
WS_EX_NOACTIVATE = 0x08000000
//#endif /* WIN32WINNT >= 0x0500 */
}
public enum GetWindowLongConst
{
GWL_WNDPROC = (-4),
GWL_HINSTANCE = (-6),
GWL_HWNDPARENT = (-8),
GWL_STYLE = (-16),
GWL_EXSTYLE = (-20),
GWL_USERDATA = (-21),
GWL_ID = (-12)
}
public enum LWA
{
ColorKey = 0x1,
Alpha = 0x2,
}
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
/// <summary>
/// Make the form (specified by its handle) a window that supports transparency.
/// </summary>
/// <param name="Handle">The window to make transparency supporting</param>
public void SetFormTransparent(IntPtr Handle)
{
oldWindowLong = GetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE);
SetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE, Convert.ToInt32( oldWindowLong | (uint)WindowStyles.WS_EX_LAYERED | (uint)WindowStyles.WS_EX_TRANSPARENT));
}
/// <summary>
/// Make the form (specified by its handle) a normal type of window (doesn't support transparency).
/// </summary>
/// <param name="Handle">The Window to make normal</param>
public void SetFormNormal(IntPtr Handle)
{
SetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE, Convert.ToInt32(oldWindowLong | (uint)WindowStyles.WS_EX_LAYERED));
}
/// <summary>
/// Makes the form change White to Transparent and clickthrough-able
/// Can be modified to make the form translucent (with different opacities) and change the Transparency Color.
/// </summary>
public void SetTheLayeredWindowAttribute()
{
uint transparentColor = 0xffffffff;
SetLayeredWindowAttributes(this.Handle, transparentColor, 125, 0x2);
this.TransparencyKey = Color.White;
}
/// <summary>
/// Finds the Size of all computer screens combined (assumes screens are left to right, not above and below).
/// </summary>
/// <returns>The width and height of all screens combined</returns>
public static Size getFullScreensSize()
{
int height = int.MinValue;
int width = 0;
foreach (Screen screen in System.Windows.Forms.Screen.AllScreens)
{
//take largest height
height = Math.Max(screen.WorkingArea.Height, height);
width += screen.Bounds.Width;
}
return new Size(width, height);
}
/// <summary>
/// Finds the top left pixel position (with multiple screens this is often not 0,0)
/// </summary>
/// <returns>Position of top left pixel</returns>
public static Point getTopLeft()
{
int minX = int.MaxValue;
int minY = int.MaxValue;
foreach (Screen screen in System.Windows.Forms.Screen.AllScreens)
{
minX = Math.Min(screen.WorkingArea.Left, minX);
minY = Math.Min(screen.WorkingArea.Top, minY);
}
return new Point( minX, minY );
}
public Form1()
{
InitializeComponent();
MaximizeEverything();
SetFormTransparent(this.Handle);
SetTheLayeredWindowAttribute();
BackgroundWorker tmpBw = new BackgroundWorker();
tmpBw.DoWork += new DoWorkEventHandler(bw_DoWork);
this.bw = tmpBw;
this.bw.RunWorkerAsync();
}
private void MaximizeEverything()
{
this.Location = getTopLeft();
this.Size = getFullScreensSize();
SendMessage(this.Handle, WM_SYSCOMMAND, (UIntPtr)myWParam, (IntPtr)myLparam);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
int numRedLines = 500;
int numWhiteLines = 1000;
Size fullSize = getFullScreensSize();
Point topLeft = getTopLeft();
using (Pen redPen = new Pen(Color.Red, 10f), whitePen = new Pen(Color.White, 10f)) {
using (Graphics formGraphics = this.CreateGraphics()) {
while (true) {
bool makeRedLines = true;
for (int i = 0; i < numRedLines + numWhiteLines; i++)
{
if (i > numRedLines)
{
makeRedLines = false;
}
//Choose points for random lines...but don't draw over the top 100 px of the screen so you can
//still find the stop run button.
int pX = rand.Next(0, (-1 * topLeft.X) + fullSize.Width);
int pY = rand.Next(100, (-1 * topLeft.Y) + fullSize.Height);
int qX = rand.Next(0, (-1 * topLeft.X) + fullSize.Width);
int qY = rand.Next(100, (-1 * topLeft.Y) + fullSize.Height);
if (makeRedLines)
{
formGraphics.DrawLine(redPen, pX, pY, qX, qY);
}
else
{
formGraphics.DrawLine(whitePen, pX, pY, qX, qY);
}
Thread.Sleep(10);
}
}
}
}
}
}
}
The lists of Enums are values used in native windows calls and converting RGB colors like White into uints makes dealing with native Windows a bit of a pain.
But, at last, we now have an invisible canvas that covers all screens, and we can draw to it just as with any other graphics object (so it is just about as easy to draw text or pictures as lines).
(I think that if you draw a translucent picture to the graphics object, that you could make yourself a translucent overlay rather than fully opaque/transparent overlays).
This example can't place overlays over fullscreen 3d games, but works fine for those same games run in Windowed mode.
(P.S. I just tested this in Team Fortress 2, it draws over it in Windowed mode, but not fullscreen, so I'm guessing The Old Republic will be similar).
The following links might be useful to anyone trying to hook into the drawing routine for Direct3D versions 9, 10, and 11.
http://spazzarama.com/2011/03/14/c-screen-capture-and-overlays-for-direct3d-9-10-and-11-using-api-hooks/
https://github.com/spazzarama/Direct3DHook
It doesn't provide a full featured overlay, but the example project above successfully writes the frames per second on top of Team Fortress 2 for me. It has good instructions on how to start using it. It should guide you through the process of setting up SlimDX Runtime and EasyHook.
The way that I have done this in the past is to get the handle for the main window, often the only way to do it is to traverse the entire window list, looking for the one with the title that you want. That is problematic if there are two instances of the game open at the same time with the same title.
Once you have that window handle, you can add visual elements over the top of what is already there by specifying how many pixels over, how many pixels down and how many layers "out" (the z-index) you want it to be relative to the upper left pixel of the window.
One approach to the multiple window is to start your c# program that looks to see if there is an instance of the game already being played, issuing a message and terminating if there is, if not, then starting an instance of the game your self as a child process. I think you can get back the hwin at that time, but if not you can search the window list for the target title after the game is running.
Some background
One of my current clients runs a chain of Internet points where customers an access the net through PC:s set up as "kiosks" (a custom-built application "locks" the computer until a user has signed in, and the running account is heavily restricted through the Windows group policy). Currently, each computer is running Windows XP and uses Active Desktop to display advertisements in the background. However, since my client has got problems with Active Desktop crashing on a daily basis (in addition to generally slowing down the computer) I have been asked to develop an application that replaces it.
The problem
I am trying to investigate whether it is possible to build a Windows forms application (using C#) that always stays in the background. The application should lie above the desktop (so that it covers any icons, files etc) but always behind all other running applications. I guess I'm really looking for a BottomMost property of the Form class (which doesn't exist, of course).
Any tips or pointers on how to achieve this would be highly appreciated.
This isn't directly supported by the .NET Form class, so you have two options:
1) Use the Win32 API SetWindowPos function.
pinvoke.net shows how to declare this for use in C#:
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
So in your code, call:
SetWindowPos(Handle, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
As you commented, this moves the form to the bottom of the z-order but doesn't keep it there. The only workaround I can see for this is to call SetWindowPos from the Form_Load and Form_Activate events. If your application is maximized and the user is unable to move or minimise the form then you might get away with this approach, but it's still something of a hack. Also the user might see a slight "flicker" if the form gets brought to the front of the z-order before the SetWindowPos call gets made.
2) subclass the form, override the WndProc function and intercept the WM_WINDOWPOSCHANGING Windows message, setting the SWP_NOZORDER flag (taken from this page).
I think the best way to do so is using the activated event handler and SendToBack method, like so:
private void Form1_Activated(object sender, EventArgs e)
{
this.SendToBack();
}
Set your window to be a child window of the desktop (the "Program Manager" or "progman" process). I've succeeded with this method in Windows XP (x86) and Windows Vista (x64).
I stumbled on this method while searching for a way to make a screensaver display as if it were wallpaper. It turns out, this is sort of built in to the system's .scr handler. You use screensaver.scr /p PID, where PID is the process id of another program to attach to. So write a program to find progman's handle, then invoke the .scr with that as the /p argument, and you have screensaver wallpaper!
The project I'm playing with now is desktop status display (shows the time, some tasks, mounted disks, etc), and it's built on Strawberry Perl and plain Win32 APIS (mainly the Win32::GUI and Win32::API modules), so the code is easy to port to or understand any dynamic language with similar Win32 API bindings or access to Windows' Scripting Host (eg, ActivePerl, Python, JScript, VBScript). Here's a relevant portion of the class that produces the window:
do { Win32::API->Import(#$_) or die "Win32::API can't import #$_ ($^E)" } for
[user32 => 'HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)'],
[user32 => 'HWND SetParent(HWND hWndChild, HWND hWndNewParent)'],
sub __screen_x {
Win32::GUI::GetSystemMetrics(SM_CXSCREEN)
}
sub __screen_y {
Win32::GUI::GetSystemMetrics(SM_CYSCREEN)
}
sub _create_window { # create window that covers desktop
my $self = shift;
my $wnd = $$self{_wnd} = Win32::GUI::Window->new(
-width => __screen_x(), -left => 0,
-height => __screen_y(), -top => 0,
) or die "can't create window ($^E)";
$wnd->SetWindowLong(GWL_STYLE,
WS_VISIBLE
| WS_POPUP # popup: no caption or border
);
$wnd->SetWindowLong(GWL_EXSTYLE,
WS_EX_NOACTIVATE # noactivate: doesn't activate when clicked
| WS_EX_NOPARENTNOTIFY # noparentnotify: doesn't notify parent window when created or destroyed
| WS_EX_TOOLWINDOW # toolwindow: hide from taskbar
);
SetParent($$wnd{-handle}, # pin window to desktop (bottommost)
(FindWindow('Progman', 'Program Manager') or die "can't find desktop window ($^E)")
) or die "can't pin to desktop ($^E)";
Win32::GUI::DoEvents; # allow sizing and styling to take effect (otherwise DC bitmaps are the wrong size)
}
This program buffers output to prevent flickering, which you'll probably want to do as well. I create a DC (device context) and PaintDesktop to it (you could use any bitmap with only a couple more lines -- CreateCompatibleBitmap, read in a file, and select the bitmap's handle as a brush), then create a holding buffer to keep a clean copy of that background and a working buffer to assemble the pieces -- on each loop, copy in background, then draw lines and brush bitmaps and use TextOut -- which is then copied to the original DC, at which time it appears on screen.
Yes, function SetWindowPos with flag HWND_BOTTOM should help you. But, from my experience: even after calling SetWindowPos as result of some user operations your window may bring to front.
subclass the form, override the WndProc function and intercept the Windows message(s) that are responsible for moving it up the z-order when it gets activated.
Create a Panel that cover your form, but what ever you want on that Panel, then in the Panel's Click-Event write this.sendback .
I've managed to get rid of the flickering when using setwindowpos...
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_NOZORDER = 0x0004;
const int WM_ACTIVATEAPP = 0x001C;
const int WM_ACTIVATE = 0x0006;
const int WM_SETFOCUS = 0x0007;
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
const int WM_WINDOWPOSCHANGING = 0x0046;
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr DeferWindowPos(IntPtr hWinPosInfo, IntPtr hWnd,
IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr BeginDeferWindowPos(int nNumWindows);
[DllImport("user32.dll")]
static extern bool EndDeferWindowPos(IntPtr hWinPosInfo);
private void Window_Loaded(object sender, RoutedEventArgs e)
{
IntPtr hWnd = new WindowInteropHelper(this).Handle;
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource src = HwndSource.FromHwnd(windowHandle);
src.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SETFOCUS)
{
IntPtr hWnd = new WindowInteropHelper(this).Handle;
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
handled = true;
}
return IntPtr.Zero;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource src = HwndSource.FromHwnd(windowHandle);
src.RemoveHook(new HwndSourceHook(this.WndProc));
}