I'm writing a tool for my own, that replaces the windows desktop.
The code, that puts my program to desktop level is the following (C#):
IntPtr hwndf = this.Handle;
IntPtr hwndParent = FindWindow("ProgMan", null);
SetParent(hwndf, hwndParent);
Cause of this I can't set the opacity of the form to anything else than 100% (otherwise the program won't start).
Since I didn't find a good way to make single controls semi transparent I want to ask if somebody has an idea either how to make the whole form or the controls (eg. GridViews and TextBoxes) opaque or make the background of all controls transparent.
Related
My C# project starts some other processes, which create windows of their own. Displaying all those windows is messy - is there a way to clip/mask these windows (which my project does not own) so only a small portion of them is displayed and able to be interacted with?
E.g. something one'd pass a wHnd together with a Rectangle.
Is this a thing that exists?
Very, very many thanks to #Michael Gunter, MDI indeed allows to make such constructions!
For anyone who stumbles into this question years from now, here's what works:
IntPtr finalhWnd; // the window to clip's handle
Form mdiP = new MDIParent(); // MDIParent is custom, just a simple form without a border
mdiP.Show();
SetParent(finalhWnd, mdiP.Handle); // set the target form as a child of our own
SetWindowPos(finalhWnd, (IntPtr)0, -30, -50, 100, 100, 0x0200); // clip a 100x100 block, starting at (30,50)
Application.Run();
Here's an example of using it to clip the borders off of a Chrome instance:
I am trying to positioning EO.WebBrowser.Wpf.WebControl using .NET 4.5. It is one of WPF webbrowsers from NuGet. For some reasons, I need the instance of that webbrowser to be bigger than its parent object. My expectation was, that it is possible to clip it with the parent by something like this:
WebControl Browser = new WebControl()
{
ClipToBounds = true
};
Unfortunately, it is not working. For illustration following pictures (sorry for the links, but I don't have rights to send images directly):
http://unsite.cz/StackOverflow/7XJrP.png
http://unsite.cz/StackOverflow/DHwud.png
On the picture, there is used red System.Windows.Controls.Border to highlight the problem. Left and top part is OK, but bottom and right not, beacause the browser overflows its bounds. The hierarchy used for illustration is Window > Border > ScrollViewer > WebControl.
I tried to place some panels between ScrollViewer and Webcontrol, but without results.
I also tried to manipulate the Z-Index with no influence on it.
My last try was to solve this problem by following extern function for clipping:
[DllImport("User32.dll", SetLastError = true)]
private static extern Int32 SetWindowRgn(IntPtr hWnd, IntPtr hRgn, Boolean bRedraw);
The function is OK when I clipping whole window, but I don't know (and I think that it is not possible) to clip with it just one specific WPF window descendant.
So here is my question: How to force EO.WebBrowser.Wpf.WebControl to autoclipping, or how to do it manually?
Thanks
P.S. Sorry for my english...
I want to draw directly on the desktop in C#. From searching a bit, I ended up using a Graphics object from the Desktop HDC (null). Then, I painted normally using this Graphics object.
The problem is that my shapes get lost when any part of the screen is redrawn. I tried a While loop, but it actually ends up drawing as fast as the application can, which is not the update rate of the desktop.
Normally, I would need to put my drawing code in a "OnPaint" event, but such thing does not exist for the desktop.
How would I do it?
Example code: https://stackoverflow.com/questions/1536141/how-to-draw-directly-on-the-windows-desktop-c
I posted two solutions for a similar requirement here
Basically you have two options.
1- Get a graphics object for the desktop and start drawing to it. The problem is if you need to start clearing what you have previously drawn etc.
Point pt = Cursor.Position; // Get the mouse cursor in screen coordinates
using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
{
g.DrawEllipse(Pens.Black, pt.X - 10, pt.Y - 10, 20, 20);
}
2- The second option that I provide in the link above is to create a transparent top-most window and do all your drawing in that window. This basically provides a transparent overlay for the desktop which you can draw on. One possible downside to this, as I mention in the original answer, is that other windows which are also top-most and are created after your app starts will obscure your top most window. This can be solved if it is a problem though.
For option 2, making the form transparent is as simple as using a transparency key, this allows mouse clicks etc. to fall through to the underlying desktop.
BackColor = Color.LightGreen;
TransparencyKey = Color.LightGreen;
When you draw to HDC(NULL) you draw to the screen, in an unmanaged way. As you've discovered, as soon as windows refreshes that part of the screen, your changes are overwritten.
There are a couple of options, depending upon what you want to achieve:
create a borderless, possibly
non-rectangular window. (Use
SetWindowRgn to make a window
non-rectangular.) You can make this a child of the desktop window.
subclass the desktop window. This is not straightforward, and involves
injecting a DLL into the
Explorer.exe process.
To get an OnPaint for the desktop you would need to subclass the desktop window and use your drawing logic when it receives a WM_PAINT/WM_ERASEBKGND message.
As the thread you linked to says, you can only intercept messages sent to a window of an external process using a hook (SetWindowsHookEx from a DLL).
As mentioned a transparent window is another way to do it, or (depending on the update frequency) copying, drawing and setting a temporary wallpaper (as bginfo does).
This is difficult to do correctly.
It will be far easier, and more reliable, to make your own borderless form instead.
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();
}
I have a form, which sets these styles in constructor:
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
And I draw some rectangles in Paint event. There are no controls on the form. Hovewer, when I resize the form, there are black strips at right and bottom of the form. Is there any way to get rid of them? I've tried everything, listening for WM_ERASEBKGND in WndProc, manually drawing the form on WM_PAINT, implementing custom double buffer, etc. Is there anything else I could try?
I've found this:
https://connect.microsoft.com/VisualStudio/feedback/details/522441/custom-resizing-of-system-windows-window-flickers
and it looks like it is a bug in DWM, but I just hope I can do some workaround.
Please note that I must use double buffering, since I want to draw pretty intense graphic presentation in the Paint event. I develop in C# .NET 2.0, Win7.
Status Update 1
I've managed to get rid of most of the black stripes by implementing the resize functionality by myself. Hovewer there are still some minor glitches. Is there any way to do resize and paint operation at once? Here is a pseudo-code of what I need to do:
IntPtr hDC;
var size = new Size(250, 200);
IntPtr handle = API.PaintAndResizeBegin(this.Handle /* Form.Handle */,
size.Width, size.Height, out hDC);
using (var g = Graphics.FromHdc(hDC)) {
this.backBuffer.Render(g, size);
}
API.PaintAndResizeCommit(handle);
Is there any way to implement the above code?
The second solution could be to back-buffer whole form, including non-client area. But how to do that? I don't want to paint the non-client area by myself, as I want to keep the nice aero effect on Vista/7. Any help will be deeply appreciated.
Status Update 2
It looks like this problem is unsolvable, since it is omnipresent on Windows, in every application. We can just hope that MS will take some inspiration in Mac OS X and will provide appropriate APIs in new Windows.
I've found the function which can paint and resize window at the same time - UpdateLayeredWindow.
So now it should be possible to create resizable windows, which do not have any strips while being resized. However, you need to paint the window content yourself, so it is a little inconvenient. But I think that using WPF and UpdateLayeredWindow, there shouldn't be any problem.
Update
Found problems. :-) When using UpdateLayeredWindow, you must paint the window's border yourself. So, if you want standard window painted using UpdateLayeredWindow with nice glass effect in win7, you are screwed.
On Microsft Connect is even a thread about this problem, where Microsoft says it is a bug by design, and if it ever gets fixed, then probably in Win8 or some newer system. So there isn't much we could do about this.
I found that it is best not to do any custom rendering directly on the Form surface. Instead, put a docked PictureBox on the form, create Bitmap object that will be displayed in the PictureBox, draw everything onto that using the System.Drawing.Graphics.FromImage(Image) method.
I used that method with a game loop to make a simple shooter game (Crimsonland-style) and got pretty good performance (with anti-aliased lines), above 100 FPS.