Windows screensaver in C# using WPF crashing during preview mode - c#

I have just started to learn C# and am trying to write a screensaver. My App.xaml.cs contains the following code for when the /p argument is used:
else if (arg1 == "/p")
{
if (e.Args[1] == null)
{
System.Windows.Forms.MessageBox.Show("Invalid or missing window handle.");
System.Windows.Application.Current.Shutdown();
}
IntPtr previewHandle = new IntPtr(long.Parse(e.Args[1]));
System.Windows.Application.Current.Run(new MainWindow(previewHandle));
}
Then in my MainWindow.xaml.cs, this construct handles the preview call:
public MainWindow(IntPtr previewHandle)
{
App.Current.Properties["isPreviewMode"] = true;
App.Current.Properties["previewHandle"] = previewHandle;
InitializeComponent();
}
After this, it crashes. In my MainWindow.xaml I have Loaded="MainWindow_Loaded".
In MainWindows.xaml.cs this is MainWindow_Loaded:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if ((bool)App.Current.Properties["isPreviewMode"])
{
IntPtr myHandle = new WindowInteropHelper(this).Handle;
SetParent(myHandle, (IntPtr)App.Current.Properties["previewHandle"]);
SetWindowLong(myHandle, -16, new IntPtr(GetWindowLong(myHandle, -16) | 0x40000000));
Rectangle ParentRect;
GetClientRect((IntPtr)App.Current.Properties["previewHandle"], out ParentRect);
this.Top = ParentRect.X;
this.Left = ParentRect.Y;
this.Height = ParentRect.Height;
this.Width = ParentRect.Width;
}
ssimage.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/EmbeddedImage.PNG"));
}
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern bool GetClientRect(IntPtr hWnd, out Rectangle lpRect);
I have other code in the App.xaml.cs to handle changing the images on a timer and other code in MainWindow.xaml.cs to handle mouse movement, clicks and keypresses. Everything works fine when running the screensaver normally. It is just the preview that fails. What am I doing wrong?

To my knowledge, WPF does not allow you to retarget the window handle, so performing screensaver previews in WPF using your technique is impossible.
However, there is a workaround: if you configure WPF to render to a bitmap target (see RenderTargetBitmap), you could then blit that bitmap onto the desired destination window handle - but this would involve an unholy mix of GDI and WPF and probably have awful runtime performance; I doubt it would be worth the effort.

What I ended up doing was using the WPF windows to display when the screensaver runs normally in full screen with /s. I created a new regular windows form previewForm.cs with a picturebox to use for the preview. That works fine and there is no performance issues. I assume the picturebox is using GDI.
This is my modified App.xaml.cs for handling the /p argument:
else if (arg1 == "/p")
{
if (e.Args[1] == null)
{
System.Windows.Forms.MessageBox.Show("Invalid or missing window handle.");
System.Windows.Application.Current.Shutdown();
}
IntPtr previewHandle = new IntPtr(long.Parse(e.Args[1]));
pw = new previewForm(previewHandle);
GetImages();
pw.Show();
}
and my previewForm.cs construct to handle the preview:
public previewForm(IntPtr previewHandle)
{
InitializeComponent();
IntPtr myHandle = this.Handle;
SetParent(myHandle, previewHandle);
SetWindowLong(myHandle, -16, new IntPtr(GetWindowLong(myHandle, -16) | 0x40000000));
Rectangle ParentRect;
GetClientRect(previewHandle, out ParentRect);
this.Size = ParentRect.Size;
this.pictureBox1.Size = ParentRect.Size;
this.Location = new Point(0, 0);
}
So I used a mix of a WPF form and a regular windows form to accomplish this. And the performance is fine. Only uses like 14-18MB of RAM and practically no CPU.

Related

Screen Capture program copies more than selected area

I followed Huw Collingbourne program in creating a screen capture program with C#. However, I noticed a couple of weird items, and whether I use his created program or my modified program does the same. Specifically I created a program that opens a window that allows you to capture the area.
I think it has to do with sittings on my computer, but need to know how to anticipate and fix this if others are going to use my screen capture program!
If my display for windows 10 is set to 100% I get a little more than the selected window and if I set display to 125% text then I get a lot of the selected area. Leaving at default size I should be 555, 484 in size. but I capture much larger.
public partial class Form1 : Form
{
//https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowrect
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, ref Rectangle lpRect);
//ICON info
//https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getcursorinfo
[DllImport("user32.dll")]
private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);
[DllImport("user32.dll")]
private static extern bool GetCursorInfo(out CURSORINFO pci);
public struct POINT
{
public Int32 x;
public Int32 y;
}
public struct ICONINFO
{
public bool fIcon;
public Int32 xHotspot;
public Int32 yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
public struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public Point ptScreenPos;
}
GrabRegionForm grabForm;
public void GrabRect(Rectangle rect)
{
int rectWidth = rect.Width - rect.Left;
int rectHeight = rect.Height - rect.Top;
Bitmap bm = new Bitmap(rectWidth, rectHeight);
Graphics g = Graphics.FromImage(bm);
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, new Size(rectWidth, rectHeight));
DrawMousePointer(g, Cursor.Position.X - rect.Left, Cursor.Position.Y - rect.Top);
this.pb_screengrab.Image = bm;
Clipboard.SetImage(bm);
}
}
public partial class GrabRegionForm : Form
{
public Rectangle formRect;
private Form1 mainForm;
[DllImport("user32.dll")]
public static extern bool ReleaseCapture();
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private void buttonOK_Click(object sender, EventArgs e)
{
formRect = new Rectangle(this.Left, this.Top, this.Left + this.Width, this.Top + this.Height);
this.Hide();
mainForm.GrabRect(formRect);
Close();
}
}
ScreenGrab with Display at 100%
ScreenGrab with Display at 125%
Area showing capture window
Area Actually Captured
IF using earlier than 4.7 and not windows 10 follow Jimi's examples and ensure you sign out and back into windows.
From Jimi https://stackoverflow.com/users/7444103/jimi
How to configure an app to run correctly on a machine with a high DPI setting How to configure an app to run correctly on a machine with a high DPI setting (e.g. 150%)?
From Jimi https://stackoverflow.com/users/7444103/jimi
Using SetWindowPos with multiple monitors Using SetWindowPos with multiple monitors
If I target my app for windows 10 Only it is incredibly simple now.
Microsoft made it even easier to change DPI settings with using 4.7 if using Windows 10.
https://learn.microsoft.com/en-us/dotnet/framework/winforms/high-dpi-support-in-windows-forms
Declare compatibility with Windows 10.
Then add the following to the app.manifest file in the XML under the commend for Windows 10 compatibility.
supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"
Enable per-monitor DPI awareness in the app.config file.
Windows Forms introduces a new element to support new features and customizations added starting with the .NET Framework 4.7. To take advantage of the new features that support high DPI, add the following to your application configuration file.
Go to the XML line for System.Windows.Forms.ApplicationConfigurationSection
add key="DpiAwareness" value="PerMonitorV2"

windows forms Form.Font doesn't change Form.Text's font [duplicate]

how i change title bar font of form in windows application by C# ?
i found this code but not working and doesn't drawing titlebar:
how can i do this?
thanks all
protected override void WndProc(ref Message msg)
{
base.WndProc(ref msg);
const int WM_NCPAINT = 0x85;
if (msg.Msg == WM_NCPAINT)
{
this.Text = "";// remove the original title text
IntPtr hdc = GetWindowDC(msg.HWnd);
Graphics g = Graphics.FromHdc(hdc);
Font ft = new Font("Arial", 16);
g.DrawString("Hello World title", ft, Brushes.Red, new PointF(20.0f, 0.0f));
ReleaseDC(msg.HWnd, hdc);
}
}
[DllImport("User32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("User32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
On Vista and Windows 7 you will need to disable Aero for the code to work.
Take a look at the answer I provided to the following question
How to add an extra button to the window's title bar?
I don't know if it is possible since you can only edit the text but not the markup.
I think it just uses the font-size that windows uses...

Use Chrome or Firefox in Winforms application ?

Is there a way to use Firefox or Chrome (whichever is installed) in a .NET application? I am not thinking about putting gecko or webkit engine in my application but using the browser installed on the system instead (just like the WebBrowser control uses IE). I heard that it is possible via ActiveX controls but didn't find any more info about it.
Well you could use user32.dll to set the parent window of the specified child window (here firefox or chrome).
This is what the result look likes:
First of all I have two small problems and 1 bigger one:
As you can see firefox is not maximized, therefore some content is missing (still looking for a way to fix that [help appriciated])
Because you have to start the process first and then set the parent of the window, Firefox is running outside of your application for a small amount of time.
The biggest problem: The program your are trying to "bind" to your application mustn't run when running your application, because it cannot set the parent of firefox to your program
MSDN for explanation of the methods: http://msdn.microsoft.com/en-us/library/windows/desktop/ff468919(v=vs.85).aspx
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int nWidth, int nHeight, bool repaint);
//hwnd: A handle to the window | x: The new position of the left side of the window. | y: The new position of the top of the window.
//nWidth: The new width of the window. | nHeight: The new height of the window.
//repaint: Indicates whether the window is to be repainted. If this parameter is TRUE, the window receives a message. If the parameter is FALSE, no repainting of any kind occurs.
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
Process ffprocess = new Process();
private void openProgramAndSetParent()
{
string str = #"C:\Program Files\Mozilla Firefox\firefox.exe"; //Enter full path to Firefox or Chrome
ffprocess.StartInfo.FileName = str;
ffprocess.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
ffprocess.Start();
IntPtr ptr = IntPtr.Zero;
while ((ptr = ffprocess.MainWindowHandle) == IntPtr.Zero)
{
Application.DoEvents();
}
Thread.Sleep(1000);
SetParent(ffprocess.MainWindowHandle, panel1.Handle);
MoveWindow(ffprocess.MainWindowHandle, 0, 0, this.Width, this.Height -100, true);
}
private void Form1_Resize(object sender, EventArgs e)
{
try
{
MoveWindow(ffprocess.MainWindowHandle, 0, 0, this.Width, this.Height, true);
}
catch (Exception)
{}
}

Hosting external app in WPF window

We are developing a layout manager in WPF that has viewports which can be moved/resized/etc by a user. Viewports are normally filled with data (pictures/movies/etc) via providers that are under our control in the layout manager. My job is to examine if its also possible to host any external Windows app (i.e. notepad, calc, adobe reader, etc) in a viewport. I encounter a number of problems.
Most resources point to using the HwndHost class. I am experimenting with this walkthrough from Microsoft itself: http://msdn.microsoft.com/en-us/library/ms752055.aspx
I've adapted this so the list box is replaced with the windows handle from the external application. Can anybody help me out with these questions:
The walkthrough adds an extra static sub window in which the ListBox is placed. I don't think I need that for external apps. If I ommit it, I have to make the external app a child window (using Get/SetWindowLong from user32.dll to set GWL_STYLE as WS_CHILD). But if I do that, the menu bar of the app dissapears (because of the WS_CHILD style) and it no longer receives input.
If I do use the sub window, and make the external app a child of that things work reasonably, but sometimes the external app does not paint ok.
Also, I need the child window to resize to the viewport. Is this possible?
When the exernal app spawns a child window (i.e. Notepad->Help->About), this window is not hosted by the HwndHost (and thus can be moved outside the viewport). Is there any way I can prevent that?
Since I need no further interaction between the external application and the layout manager, am I right in assuming I do not need to catch and forward messages? (the walkthrough adds a HwndSourceHook to the sub window to catch selection changes in the listbox).
When you run the (unmodified) example VS2010 and close the window, VS2010 does not see that the program ended. If you break-all, you end up in assembly without source. Something smelly is going on, but I cannot find it.
The walkthrough itself seems to be very sloppy coded, but I have not found any better documentation on this subject. Any other examples?
Another approach is not to use HwndHost but WindowsFormHost as discussed here. It works (and is much simpler!) but I do not have control over the size of the application? Also, WinFormHost is not really meant for this?
Thanks for any pointers in the right direction.
Well... if the question had been posed like 20 years ago, one would have answer, "Sure, look at 'OLE'!", here is a link to what is "Object Linking and Embedding":
http://en.wikipedia.org/wiki/Object_Linking_and_Embedding
If you read this article, you will see the number of interfaces this spec defined, not because its author thought it was fun, but because it's technically difficult to achieve in the general cases
It's actually still supported by some apps (mostly Microsoft ones, as Microsoft was almost the only sponsor of OLE...)
You can embed these apps using something called DSOFramer (see links here on SO: MS KB311765 and DsoFramer are missing from MS site), a component that allows you to host OLE server (ie: external apps running as another process)
visually inside an application. It's some kind of a big hack Microsoft let out a few years ago, that is not supported anymore to the point that the binaries are quite difficult to find!
It (may) still works for simple OLE servers, but I think I read somewhere it does not even work for new Microsoft applications such as Word 2010.
So, you can use DSOFramer for application that support it. You can try it.
For others applications, well, today, in the modern world we live in, you don't host applications, ran in external process, you host components, and they are in general supposed to run inprocess.
That's why you will have great difficulties to do what you want to do in general. One problem you will face (and not the least with recent versions of Windows) is security: how can your process I don't trust can legitimately handle my windows and menus created by my process :-) ?
Still, you can do quite a lot application by application, using various Windows hack.
SetParent is basically the mother of all hacks :-)
Here is a piece of code that extends the sample you point, adding automatic resize, and the removal of the caption box.
It demonstrates how to implicitely remove the control box, the system menu, as an example:
public partial class Window1 : Window
{
private System.Windows.Forms.Panel _panel;
private Process _process;
public Window1()
{
InitializeComponent();
_panel = new System.Windows.Forms.Panel();
windowsFormsHost1.Child = _panel;
}
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32")]
private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent);
[DllImport("user32")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
private const int SWP_NOZORDER = 0x0004;
private const int SWP_NOACTIVATE = 0x0010;
private const int GWL_STYLE = -16;
private const int WS_CAPTION = 0x00C00000;
private const int WS_THICKFRAME = 0x00040000;
private void button1_Click(object sender, RoutedEventArgs e)
{
button1.Visibility = Visibility.Hidden;
ProcessStartInfo psi = new ProcessStartInfo("notepad.exe");
_process = Process.Start(psi);
_process.WaitForInputIdle();
SetParent(_process.MainWindowHandle, _panel.Handle);
// remove control box
int style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
style = style & ~WS_CAPTION & ~WS_THICKFRAME;
SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);
// resize embedded application & refresh
ResizeEmbeddedApp();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
if (_process != null)
{
_process.Refresh();
_process.Close();
}
}
private void ResizeEmbeddedApp()
{
if (_process == null)
return;
SetWindowPos(_process.MainWindowHandle, IntPtr.Zero, 0, 0, (int)_panel.ClientSize.Width, (int)_panel.ClientSize.Height, SWP_NOZORDER | SWP_NOACTIVATE);
}
protected override Size MeasureOverride(Size availableSize)
{
Size size = base.MeasureOverride(availableSize);
ResizeEmbeddedApp();
return size;
}
}
This is basically all Windows "traditional" hacks. You could also remove item menus you don't like, as explained here: http://support.microsoft.com/kb/110393/en-us (How to Remove Menu Items from a Form's Control-Menu Box).
You can also replace "notepad.exe" by "winword.exe" and it seems to work. But there are limitations to this (keyboard, mouse, focus, etc.).
Good luck!
Simon Mourier's answer is extremely well written. However, when I tried it with a winform app made by myself, it failed.
_process.WaitForInputIdle();
can be replaced by
while (_process.MainWindowHandle==IntPtr.Zero)
{
Thread.Sleep(1);
}
and everything goes smoothly.
Thank you for the great question and all of you for your answers.
After reading the answers in this thread and doing some trial and error myself I ended up with something that works pretty well, but of course some things will need your attention for special cases.
I used the HwndHostEx as base class for my host class, you can find it here: http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035
Example code:
public class NotepadHwndHost : HwndHostEx
{
private Process _process;
protected override HWND BuildWindowOverride(HWND hwndParent)
{
ProcessStartInfo psi = new ProcessStartInfo("notepad.exe");
_process = Process.Start(psi);
_process.WaitForInputIdle();
// The main window handle may be unavailable for a while, just wait for it
while (_process.MainWindowHandle == IntPtr.Zero)
{
Thread.Yield();
}
HWND hwnd = new HWND(_process.MainWindowHandle);
const int GWL_STYLE = -16;
const int BORDER = 0x00800000;
const int DLGFRAME = 0x00400000;
const int WS_CAPTION = BORDER | DLGFRAME;
const int WS_THICKFRAME = 0x00040000;
const int WS_CHILD = 0x40000000;
int style = GetWindowLong(notepadHandle, GWL_STYLE);
style = style & ~WS_CAPTION & ~WS_THICKFRAME; // Removes Caption bar and the sizing border
style |= WS_CHILD; // Must be a child window to be hosted
NativeMethods.SetWindowLong(hwnd, GWL.STYLE, style);
return hwnd;
}
protected override void DestroyWindowOverride(HWND hwnd)
{
_process.CloseMainWindow();
_process.WaitForExit(5000);
if (_process.HasExited == false)
{
_process.Kill();
}
_process.Close();
_process.Dispose();
_process = null;
hwnd.Dispose();
hwnd = null;
}
}
The HWND, NativeMethods and enums comes from the DwayneNeed library as well (Microsoft.DwayneNeed.User32).
Just add the NotepadHwndHost as a child in a WPF window and you should see the notepad window hosted there.
The solution is incredibly involved. Lots of code. Here's a few hints.
First, you are on the right track.
You do have to use the HwndHost and HwndSource thing. If you don't, you'll get visual artifacts. Like flicker. A warning, if you don't use the Host and Source, it will seem like it will work, but it won't in the end -- it will have random little stupid bugs.
Take a look at this for some hints. It's not complete, but it will help you go in the right direction.
http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346
You have to get into Win32 to control a lot of what you are asking about. You do need to catch and forward messages. You do need to control which windows "own" the child windows.
Use Spy++ alot.
I have this running in production and so far so good in a WPF application. Make sure you call SetNativeWindowInWPFWindowAsChild() from UI thread that owns window.
public static bool SetNativeWindowInWPFWindowAsChild(IntPtr hWndNative, Window window)
{
UInt32 dwSyleToRemove = WS_POPUP | WS_CAPTION | WS_THICKFRAME;
UInt32 dwExStyleToRemove = WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE;
UInt32 dwStyle = GetWindowLong(hWndNative, GWL_STYLE);
UInt32 dwExStyle = GetWindowLong(hWndNative, GWL_EXSTYLE);
dwStyle &= ~dwSyleToRemove;
dwExStyle &= ~dwExStyleToRemove;
SetWindowLong(hWndNative, GWL_STYLE, dwStyle | WS_CHILD);
SetWindowLong(hWndNative, GWL_EXSTYLE, dwExStyle);
IntPtr hWndOld = SetParent(hWndNative, new WindowInteropHelper(window).Handle);
if (hWndOld == IntPtr.Zero)
{
System.Diagnostics.Debug.WriteLine("SetParent() Failed -> LAST ERROR: " + Marshal.GetLastWin32Error() + "\n");
}
return hWndOld != IntPtr.Zero;
}
Here is the Native Win32 API I used. (There are extras in here because I size/focus the window after it's set)
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public Int32 left;
public Int32 top;
public Int32 right;
public Int32 bottom;
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
private static extern UInt32 SetWindowLong(IntPtr hWnd, int nIndex, UInt32 dwNewLong);
[DllImport("user32.dll")]
private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
private static int GWL_STYLE = -16;
private static int GWL_EXSTYLE = -20;
private static UInt32 WS_CHILD = 0x40000000;
private static UInt32 WS_POPUP = 0x80000000;
private static UInt32 WS_CAPTION = 0x00C00000;
private static UInt32 WS_THICKFRAME = 0x00040000;
private static UInt32 WS_EX_DLGMODALFRAME = 0x00000001;
private static UInt32 WS_EX_WINDOWEDGE = 0x00000100;
private static UInt32 WS_EX_CLIENTEDGE = 0x00000200;
private static UInt32 WS_EX_STATICEDGE = 0x00020000;
[Flags]
private enum SetWindowPosFlags : uint
{
SWP_ASYNCWINDOWPOS = 0x4000,
SWP_DEFERERASE = 0x2000,
SWP_DRAWFRAME = 0x0020,
SWP_FRAMECHANGED = 0x0020,
SWP_HIDEWINDOW = 0x0080,
SWP_NOACTIVATE = 0x0010,
SWP_NOCOPYBITS = 0x0100,
SWP_NOMOVE = 0x0002,
SWP_NOOWNERZORDER = 0x0200,
SWP_NOREDRAW = 0x0008,
SWP_NOREPOSITION = 0x0200,
SWP_NOSENDCHANGING = 0x0400,
SWP_NOSIZE = 0x0001,
SWP_NOZORDER = 0x0004,
SWP_SHOWWINDOW = 0x0040
}
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
private static readonly IntPtr HWND_TOP = new IntPtr(0);
private static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
Check out my answer to: How to run an application inside wpf application?
I managed to get the notepad example working without DwayneNeed jiggery. I just added SetParent() and boom... she works just like the DwayneNeed example.

Capture screenshot Including Semitransparent windows in .NET

I would like a relatively hack-free way to do this, any ideas? For example, the following takes a screenshot that doesn't include the semi-transparent window:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Shown
Text = "Opaque Window"
Dim win2 As New Form
win2.Opacity = 0.5
win2.Text = "Tranparent Window"
win2.Show()
win2.Top = Top + 50
win2.Left = Left() + 50
Dim bounds As Rectangle = System.Windows.Forms.Screen.GetBounds(Point.Empty)
Using bmp As Bitmap = New Bitmap(bounds.Width, bounds.Height)
Using g As Graphics = Graphics.FromImage(bmp)
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size)
End Using
bmp.Save("c:\temp\scn.gif")
End Using
Process.Start(New Diagnostics.ProcessStartInfo("c:\temp\scn.gif") With {.UseShellExecute = True})
End Sub
End Class
Either my google-fu really sucks or this is not as easy as it sounds. I'm pretty sure why this is happening because of the way the video driver would have to separate the memory to make this work, but I don't care why it doesn't work, I just want to do it without...
* print-screen key hacks
* 3rd party software
* SDK functions are OK but I'll upvote every object owned by the user that can show me it in pure framework (Just kidding but it would be nice).
If This is the only way to do it, how to I do that in VB?
1M thanks.
Forms that have the TransparencyKey or Opacity property set are so-called layered windows. They are shown using the "overlay" feature of the video adapter. Which make them being able to have their transparency effects.
Capturing them requires turning on the CopyPixelOperation.CaptureBlt option in the CopyFromScreen overload that accepts the CopyPixelOperation argument.
Unfortunately, this overload has a critical bug that prevents this from working. It doesn't validate the value properly. Still not fixed in .NET 4.0. There is no other good fix but fall back to using P/Invoke to make the screen shot. Here's an example:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
Size sz = Screen.PrimaryScreen.Bounds.Size;
IntPtr hDesk = GetDesktopWindow();
IntPtr hSrce = GetWindowDC(hDesk);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, sz.Width, sz.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, 0, 0, sz.Width, sz.Height, hSrce, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
Bitmap bmp = Bitmap.FromHbitmap(hBmp);
SelectObject(hDest, hOldBmp);
DeleteObject(hBmp);
DeleteDC(hDest);
ReleaseDC(hDesk, hSrce);
bmp.Save(#"c:\temp\test.png");
bmp.Dispose();
}
// P/Invoke declarations
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
}
}
Fwiw, a later Windows version provided a workaround for this bug. Not exactly sure which, I think it was Win7 SP1. The BitBlt() function will now do what you want if you pass only the CopyPixelOperation.CaptureBlt option. But of course that workaround wasn't applied retro-actively to earlier Windows versions so you can't really depend on it.

Categories