Is it possible to do the following with WinForms/C#?
Dynamically detect window size and position of a running program (for example Notepad.exe)?
Snap WinForm to specific position within Notepad.exe?
Minimize and maximize WinForm window with other process (so when Notepad is minimize, so is WinForm window)?
See for example (black shape would be WinForm window):
Essentially I need to create a toolbar for a program, and the toolbar should "snap" to that program in the same place regardless of position or size of window.
First find the handle of the notepad window:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
Just pass null for the first parameter and the caption ("Notepad"?) of the window as the second parameter.
An alternative would be to enumerate all windows and select the best match based on the caption:
using System.Runtime.InteropServices;
public delegate bool CallBackPtr(int hwnd, int lParam);
private CallBackPtr callBackPtr;
public class EnumReport
{
[DllImport("user32.dll")]
private static extern int EnumWindows(CallBackPtr callPtr, int lPar);
public static bool Report(int hwnd, int lParam)
{
Console.WriteLine("Window handle is "+hwnd);
return true;
}
}
static void Main()
{
// note in other situations, it is important to keep
// callBackPtr as a member variable so it doesnt GC while you're calling EnumWindows
callBackPtr = new CallBackPtr(EnumReport.Report);
EnumReport.EnumWindows(callBackPtr, 0);
}
Then attach a WndProc to it:
HwndSource src = HwndSource.FromHwnd(windowHandle);
src.AddHook(new HwndSourceHook(WndProc));
In the WndProc respond to the resizing and moving of the window.
I am not sure about setting the toolbar as a child of the notepad window; that might have unexpected effects when Notepad tries to manage it and order its z-depth.
At the same time I doubt this to be a good thing; the user will be able to type 'below' the overlay and lose his cursor/text.
Find Notepad's window (FindWindow).
Create your window without borders.
Set your window as a child of Notepad's window (SetParent).
Your window will be anchored to the top left corner of Notepad's window. Minimizing will be handled automatically, but you'll need to resize your window when Notepad's window is resized (or maximized). You may also want to move Notepad's edit control.
WinForms can be used, but you'll need some interop calls.
I have to warn that this is not a very good idea. Your controls may conflict with controls inside host process's window, host process may rearrange controls the way you don't like, draw over your controls. In general, be ready to fight with numerous issues without a good clean solution, and to accept that there may be glitches when resizing etc.
See also:
Attach form window to another window in C#.
Related
I require the ability to get the height of the on screen keyboard for Windows 8.1, "TabTip.exe." So far I've managed to open and close it at will, but now I also need to get it's height so I can compensate for it in my app. I have TextBox controls near the very bottom of the Window that get covered up by the keyboard.
All attempts to utilize the Win API call "GetWindowRect" have failed.
Code placed just inside the beginning of the class definition:
private const string OnScreenKeyboardName = "TabTip";
DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(HandleRef hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
code placed inside one of the event handlers in the Window:
RECT rct = new RECT();
if (Process.GetProcessesByName(OnScreenKeyboardName).Length > 0)
{
Process[] Processes = Process.GetProcessesByName(OnScreenKeyboardName);
foreach (Process pOnScreenKeyboard in Processes)
{
if (!GetWindowRect(new HandleRef(this, pOnScreenKeyboard.Handle), ref rct))
{
MessageBox.Show("ERROR");
return;
}
MessageBox.Show(rct.ToString());
}
}
Consistent with most examples, I had originally been using a call to "FindWindow" to get the handle for the TabTip.exe Window. That seemed to work great for a while, but it stopped working all of a sudden within the last few days, and so I switched to "Process.FindByName..."
My test case involves placing the code (second part) above into the Window's "MouseMove" event handler. Then I make sure the on screen keyboard is showing, and then I move the mouse. This causes the event to fire and then it always shows the "ERROR" MessageBox, which indicates that "GetWindowRect" returns false (or has some kind of error?
I've spent a lot of time on Google searches, p/invoke, etc. This is frustrating because there seem to be very few examples of how to properly do this. And it seems there is some new thing called a HandleRef (am I using that properly? - there are basically no examples for that either!!!)
I really need to get this working. Can someone please tell me where I'm going wrong? Thanks!
You are passing the process handle (pOnScreenKeyboard.Handle) to GetWindowRect where GetWindowRect expects a window handle (HWND). Finding the process isn't sufficient - a process can create many windows, so you need to find the onscreen keyboard's window handle. You can try using pOnScreenKeyboard.MainWindowHandle, but from this post, you might find that it's null. You said you had a previous solution using FindWindow. I would go back to that and figure out why it stopped working.
I am currently developing an iTunes plug-in and I need to have my WPF plug-in to stick beside the iTunes window when dragged, resized, etc. The goal is to have iTunes sticked to my wpf app side by side.
I am looking for a way to track the movement (resizing, moving) of another window (in my case iTunes). The iTunes COM for Windows SDK offers events on maximize and minimize, unfortunately there are no events for resizing and moving the window.
I have tried the win32 setParent function without no succes. I don't think it's the appropriate solution for my problem. I have searched thoroughly through out the web but didn't find nothing.
I think the WINDOWPOS structure is what you're looking for. Other window structures may come in handy.
Some google searching turned up another example, that doesn't use WINDOWPOS:
1, Invoke API FindWindow() to retrieve the window handle (Uuse SPY++
to get the two parameters ClassName & WindowName);
2, Invoke API
GetWindowRect() to retrieve the size & postion of the specified
window.
Code Snippet
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string className, string windowName);
[DllImport("user32.dll")]
private static extern int GetWindowRect(IntPtr hwnd, out Rectangle rect);
private void button2_Click(object sender, EventArgs e)
{
string className = "yourClassName";
string windowName = "yourWindowName";
Rectangle rect;
IntPtr hwnd = FindWindow(className, windowName);
GetWindowRect(hwnd, out rect);
}
In applications like Windows Explorer and Internet Explorer, one can grab the extended frame areas beneath the title bar and drag windows around.
For WinForms applications, forms and controls are as close to native Win32 APIs as they can get; one would simply override the WndProc() handler in their form, process the WM_NCHITTEST window message and trick the system into thinking a click on the frame area was really a click on the title bar by returning HTCAPTION. I've done that in my own WinForms apps to delightful effect.
In WPF, I can also implement a similar WndProc() method and hook it to my WPF window's handle while extending the window frame into the client area, like this:
// In MainWindow
// For use with window frame extensions
private IntPtr hwnd;
private HwndSource hsource;
private void Window_SourceInitialized(object sender, EventArgs e)
{
try
{
if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
{
throw new InvalidOperationException("Could not get window handle for the main window.");
}
hsource = HwndSource.FromHwnd(hwnd);
hsource.AddHook(WndProc);
AdjustWindowFrame();
}
catch (InvalidOperationException)
{
FallbackPaint();
}
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case DwmApiInterop.WM_NCHITTEST:
handled = true;
return new IntPtr(DwmApiInterop.HTCAPTION);
default:
return IntPtr.Zero;
}
}
The problem is that, since I'm blindly setting handled = true and returning HTCAPTION, clicking anywhere but the window icon or the control buttons causes the window to be dragged. That is, everything highlighted in red below causes dragging. This even includes the resize handles at the sides of the window (the non-client area). My WPF controls, namely the text boxes and the tab control, also stop receiving clicks as a result:
What I want is for only
the title bar, and
the regions of the client area...
... that aren't occupied by my controls
to be draggable. That is, I only want these red regions to be draggable (client area + title bar):
How do I modify my WndProc() method and the rest of my window's XAML/code-behind, to determine which areas should return HTCAPTION and which shouldn't? I'm thinking something along the lines of using Points to check the location of the click against the locations of my controls, but I'm not sure how to go about it in WPF land.
EDIT [4/24]: one simple way about it is to have an invisible control, or even the window itself, respond to MouseLeftButtonDown by invoking DragMove() on the window (see Ross's answer). The problem is that for some reason DragMove() doesn't work if the window is maximized, so it doesn't play nice with Windows 7 Aero Snap. Since I'm going for Windows 7 integration, it's not an acceptable solution in my case.
Sample code
Thanks to an email I got this morning, I was prompted to make a working sample app demonstrating this very functionality. I've done that now; you can find it on GitHub (or in the now-archived CodePlex). Just clone the repository or download and extract an archive, then open it in Visual Studio, and build and run it.
The complete application in its entirety is MIT-licensed, but you'll probably be taking it apart and putting bits of its code around your own rather than using the app code in full — not that the license stops you from doing that either. Also, while I know the design of the application's main window isn't anywhere near similar to the wireframes above, the idea is the same as posed in the question.
Hope this helps somebody!
Step-by-step solution
I finally solved it. Thanks to Jeffrey L Whitledge for pointing me in the right direction! His answer was accepted because if not for it I wouldn't have managed to work out a solution. EDIT [9/8]: this answer is now accepted as it's more complete; I'm giving Jeffrey a nice big bounty instead for his help.
For posterity's sake, here's how I did it (quoting Jeffrey's answer where relevant as I go):
Get the location of the mouse click (from the wParam, lParam maybe?), and use it to create a Point (possibly with some kind of coordinate transformation?).
This information can be obtained from the lParam of the WM_NCHITTEST message. The x-coordinate of the cursor is its low-order word and the y-coordinate of the cursor is its high-order word, as MSDN describes.
Since the coordinates are relative to the entire screen, I need to call Visual.PointFromScreen() on my window to convert the coordinates to be relative to the window space.
Then call the static method VisualTreeHelper.HitTest(Visual,Point) passing it this and the Point that you just made. The return value will indicate the control with the highest Z-Order.
I had to pass in the top-level Grid control instead of this as the visual to test against the point. Likewise I had to check whether the result was null instead of checking if it was the window. If it's null, the cursor didn't hit any of the grid's child controls — in other words, it hit the unoccupied window frame region. Anyway, the key was to use the VisualTreeHelper.HitTest() method.
Now, having said that, there are two caveats which may apply to you if you're following my steps:
If you don't cover the entire window, and instead only partially extend the window frame, you have to place a control over the rectangle that's not filled by window frame as a client area filler.
In my case, the content area of my tab control fits that rectangular area just fine, as shown in the diagrams. In your application, you may need to place a Rectangle shape or a Panel control and paint it the appropriate color. This way the control will be hit.
This issue about client area fillers leads to the next:
If your grid or other top-level control has a background texture or gradient over the extended window frame, the entire grid area will respond to the hit, even on any fully transparent regions of the background (see Hit Testing in the Visual Layer). In that case, you'll want to ignore hits against the grid itself, and only pay attention to the controls within it.
Hence:
// In MainWindow
private bool IsOnExtendedFrame(int lParam)
{
int x = lParam << 16 >> 16, y = lParam >> 16;
var point = PointFromScreen(new Point(x, y));
// In XAML: <Grid x:Name="windowGrid">...</Grid>
var result = VisualTreeHelper.HitTest(windowGrid, point);
if (result != null)
{
// A control was hit - it may be the grid if it has a background
// texture or gradient over the extended window frame
return result.VisualHit == windowGrid;
}
// Nothing was hit - assume that this area is covered by frame extensions anyway
return true;
}
The window is now movable by clicking and dragging only the unoccupied areas of the window.
But that's not all. Recall in the first illustration that the non-client area comprising the borders of the window was also affected by HTCAPTION so the window was no longer resizable.
To fix this I had to check whether the cursor was hitting the client area or the non-client area. In order to check this I needed to use the DefWindowProc() function and see if it returned HTCLIENT:
// In my managed DWM API wrapper class, DwmApiInterop
public static bool IsOnClientArea(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam)
{
if (uMsg == WM_NCHITTEST)
{
if (DefWindowProc(hWnd, uMsg, wParam, lParam).ToInt32() == HTCLIENT)
{
return true;
}
}
return false;
}
// In NativeMethods
[DllImport("user32.dll")]
private static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);
Finally, here's my final window procedure method:
// In MainWindow
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case DwmApiInterop.WM_NCHITTEST:
if (DwmApiInterop.IsOnClientArea(hwnd, msg, wParam, lParam)
&& IsOnExtendedFrame(lParam.ToInt32()))
{
handled = true;
return new IntPtr(DwmApiInterop.HTCAPTION);
}
return IntPtr.Zero;
default:
return IntPtr.Zero;
}
}
Here's something you could try:
Get the location of the mouse click (from the wParam, lParam maybe?), and use it to create a Point (possibly with some kind of coordinate transformation?).
Then call the static method VisualTreeHelper.HitTest(Visual,Point) passing it this and the Point that you just made. The return value will indicate the control with the highest Z-Order. If that's your window, then do your HTCAPTION voodoo. If it's some other control, then...don't.
Good luck!
Looking to do the same thing (make my extended Aero glass draggable in my WPF app), I just came across this post via Google. I read through your answer, but decided to keep searching to see if there was anything simpler.
I found a much less code-intensive solution.
Simply create a transparent item behind your controls, and give it a left mouse button down event handler which calls the window's DragMove() method.
Here's the section of my XAML which appears over my extended Aero glass:
<Grid DockPanel.Dock="Top">
<Border MouseLeftButtonDown="Border_MouseLeftButtonDown" Background="Transparent" />
<Grid><!-- My controls are in here --></Grid>
</Grid>
And the code-behind (this is within a Window class, and so DragMove() is available to call directly):
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
And that's it! For your solution, you would have to add more than one of these to achieve your non-rectangular draggable area.
simple way is
create stackpanel or every thing you want for your titlebar
XAML
<StackPanel Name="titleBar" Background="Gray" MouseLeftButtonDown="titleBar_MouseLeftButtonDown" Grid.ColumnSpan="2"></StackPanel>
code
private void titleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
How can I efficiently tell if the mouse is over a top-level window?
By "over", I mean that the mouse pointer is within the client rectangle of the top-level window and there is no other top-level window over my window at the location of the mouse pointer. In other words, if the user clicked the event would be sent to my top-level window (or one of its child windows).
I am writing in C# using Windows Forms, but I don't mind using p/invoke to make Win32 calls.
You could use the WinAPI function WindowFromPoint. Its C# signature is:
[DllImport("user32.dll")]
static extern IntPtr WindowFromPoint(POINT Point);
Note that POINT here is not the same as System.Drawing.Point, but PInvoke provides a declaration for POINT that includes an implicit conversion between the two.
If you don’t already know the mouse cursor position, GetCursorPos finds it:
[DllImport("user32.dll")]
static extern bool GetCursorPos(out POINT lpPoint);
However, the WinAPI calls lots of things “windows”: controls inside a window are also “windows”. Therefore, you might not get a top-level window in the intuitive sense (you might get a radio button, panel, or something else). You could iteratively apply the GetParent function to walk up the GUI hierarchy:
[DllImport("user32.dll", ExactSpelling=true, CharSet=CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);
Once you find a window with no parent, that window will be a top-level window. Since the point you originally passed in belongs to a control that is not covered by another window, the top-level window is necessarily the one the point belongs to.
After you obtain the window handle you can use Control.FromHandle() to get a reference to the control. Then check the relative mouse position to see if its it is the client area of the form or control. Like this:
private void timer1_Tick(object sender, EventArgs e) {
var hdl = WindowFromPoint(Control.MousePosition);
var ctl = Control.FromHandle(hdl);
if (ctl != null) {
var rel = ctl.PointToClient(Control.MousePosition);
if (ctl.ClientRectangle.Contains(rel)) {
Console.WriteLine("Found {0}", ctl.Name);
return;
}
}
Console.WriteLine("No match");
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point loc);
I've seen that some apps (maybe not .NET apps) that have an extra button on the left from the minimize button on the form's title bar? How can I achieve this in C#?
UPDATE: Added a solution that will work with Aero enabled for Windows Vista and Windows 7
***Non-Aero Solution***
The non-client area of a window interaction is managed by a series of non-client specfic messages. For example WM_NCPAINT message is sent to the window procedure to paint the non-client area.
I have never done this from .NET, but I suspect you can overide the WndProc and handle the WM_NC* messages to achieve what you want.
Update: Since I never tried this from .NET I got a few minutes and thought I would give it a quick try.
Trying this on Windows 7, I found that I needed to disable the Themes for the Window if I wanted to OS to do the base rendering of the non-client area. So here is a short test. I used GetWindowDC to get the DC of the entire window rather than GetDCEx, that was just because I could interop that from memory and did not have lookup all the flag constants for GetDcEx. And of course the code could do with more error checking.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1
{
public partial class CustomBorderForm : Form
{
const int WM_NCPAINT = 0x85;
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetWindowDC(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
[DllImport("user32.dll", SetLastError = true)]
public static extern void DisableProcessWindowsGhosting();
[DllImport("UxTheme.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr SetWindowTheme(IntPtr hwnd, string pszSubAppName, string pszSubIdList);
public CustomBorderForm()
{
// This could be called from main.
DisableProcessWindowsGhosting();
InitializeComponent();
}
protected override void OnHandleCreated(EventArgs e)
{
SetWindowTheme(this.Handle, "", "");
base.OnHandleCreated(e);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case WM_NCPAINT:
{
IntPtr hdc = GetWindowDC(m.HWnd);
using (Graphics g = Graphics.FromHdc(hdc))
{
g.FillEllipse(Brushes.Red, new Rectangle((Width-20)/2, 8, 20, 20));
}
ReleaseDC(m.HWnd, hdc);
}
break;
}
}
}
}
Btw. I called DisableProcessWindowsGhosting, this will stop the OS from drawing the non-client area if the application takes too long to respond to windows messages. If you do not do this, then in some situations the border will be renderd but your adornments will not be shown. So that depends on your requirements it that is right for you or not.
***Aero supported solution***
Prompted by the comment from #TheCodeKing, I thought I would take another look at this. It turns out this can be done in a fully documented way while supporting Aero. But it is not for the faint of heart. I will not provide a complete solution here, there are still some kinks to workout, but it does the basics.
This code/solution is based off the Win32 example which can be found at the following location
http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx
In principal what you need to do is the following.
Extend the client area of the window to cover the Frame. This is done by handling the WM_NCCALCSIZE message and returning 0. This gives the Non-Client area a size of 0 and therefore the client area now covers the entire window.
Extend the Frame into the client area using DwmExtendFrameIntoClientArea. This gets the OS to render the Frame over the client area.
The above steps will give you a windows with the standard glass frame excluding the system menu (Window Icon) and the title. The minimize, maximize and close buttons will still be drawn and will work. What you will not be able to do is drag or resize the window, this is because the frame is not really there, remember the client area covers the whole window, we have just asked the OS to draw the frame onto the client area.
Now you can draw on the window as normal, even on top of the frame. You can even put controls in the caption area.
Finally, allow the DWM to handle hit-testing for you, by calling DwmDefWindowProc from your WndProc (before you've processed it). It returns a boolean indicating whether the DWM handled the message for you.
Simple Solution:
Step 1: Create a Windows Form (this will be your custom title bar)
-Set Form Border Style to None
-Add whatever controls you would like to this
-I will name this custom form "TitleBarButtons"
Step 2. In the from that you want to use this custom control in add
titleBarBtn = new TitleBarButtons();
titleBarBtn.Location = new Point(this.Location.X + 100, this.Location.Y+5);
titleBarBtn.Show();
titleBarBtn.Owner = this;
To your constructor... you can mess with the offsets this just fit in a nice position for my app
Step 3. Add the move event to your main form
private void Form14_Move(object sender, EventArgs e)
{
titleBarBtn.Location = new Point(this.Location.X + 100, this.Location.Y+5);
}
Please let me know if you would like a better explanation of any of the above code.
I think a way to do this would be to handle WM_NCPAINT message (non-client paint) to draw the button, and to handle non-client mouse clicks to know someone clicked on the "button".