I'm trying to integrate a unity application (.exe) inside a WPF XAML application. I've managed to get the unity window to open inside the WPF window, but it's stuck in the top left corner of the window and does not resize when I resize the WPF window.
Here is my code, (the unity application is called unityWindow.exe):
MainWindow.xaml
<Window x:Class="UnityApplicationIntegration.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UnityApplicationIntegration"
mc:Ignorable="d"
Title="MainWindow" MinHeight="488" MinWidth="815">
<Grid x:Name="unityContent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Threading;
namespace UnityApplicationIntegration
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
[DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
private Process _process;
private IntPtr _unityHWND = IntPtr.Zero;
private const int WM_ACTIVATE = 0x0006;
private readonly IntPtr WA_ACTIVE = new IntPtr(1);
private readonly IntPtr WA_INACTIVE = new IntPtr(0);
//Frame p = MainWindow.Instance.floatingFrame;
Grid UnityContent;
bool initialized = false;
public MainWindow()
{
InitializeComponent();
UnityContent = unityContent;
//MainWindow.Instance.MainWindowClosing += Application_Exit;
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += attemptInit;
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
dispatcherTimer.Start();
}
void attemptInit(object sender, EventArgs e)
{
if (initialized)
return;
HwndSource source = (HwndSource)HwndSource.FromVisual(UnityContent);
Debug.WriteLine("attempting to get handle...");
if (source == null)
{
Debug.WriteLine("Failed to get handle source");
return;
}
IntPtr hWnd = source.Handle;
try
{
_process = new Process();
_process.StartInfo.FileName = "UnityWindow.exe";
_process.StartInfo.Arguments = "-parentHWND " + hWnd.ToInt32() + " " + Environment.CommandLine;
_process.StartInfo.UseShellExecute = true;
_process.StartInfo.CreateNoWindow = true;
_process.Start();
_process.WaitForInputIdle();
// Doesn't work for some reason ?!
//hWnd = _process.MainWindowHandle;
EnumChildWindows(hWnd, WindowEnum, IntPtr.Zero);
//unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
Debug.WriteLine("Unity HWND: 0x" + _unityHWND.ToString("X8"));
// TODO: rename. What are the Args?
UnityContentResize(this, EventArgs.Empty);
initialized = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to UnityGame.exe.");
}
}
private void ActivateUnityWindow()
{
SendMessage(_unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
}
private void DeactivateUnityWindow()
{
SendMessage(_unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
}
private int WindowEnum(IntPtr hwnd, IntPtr lparam)
{
_unityHWND = hwnd;
ActivateUnityWindow();
return 0;
}
private void UnityContentResize(object sender, EventArgs e)
{
MoveWindow(_unityHWND, 0, 0, (int)UnityContent.Width, (int)UnityContent.Height, true);
Debug.WriteLine("RESIZED UNITY WINDOW TO: " + (int)UnityContent.Width + "x" + (int)UnityContent.Height);
ActivateUnityWindow();
}
// Close Unity application
private void ApplicationExit(object sender, EventArgs e)
{
try
{
_process.CloseMainWindow();
Thread.Sleep(1000);
while (!_process.HasExited)
_process.Kill();
}
catch (Exception)
{
}
}
private void UnityContentActivate(object sender, EventArgs e)
{
ActivateUnityWindow();
}
private void UnityContentDeactivate(object sender, EventArgs e)
{
DeactivateUnityWindow();
}
}
}
I've tried placing the unity window inside a content presenter like so:
<ContentPresenter x:Name="unityContent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</ContentPresenter>
But resizing still didn't work.
I've read in many places to use WindowsFormsHost but since this is not a forms application, this does not work.
Could anyone tell me where I'm going wrong?
Thanks for your time.
EDIT:
I managed to get the grid with the unity window to resize by changing:
MoveWindow(_unityHWND, 0, 0, (int)UnityContent.Width, (int)UnityContent.Height, true);
to
MoveWindow(_unityHWND, 0, 0, (int)UnityContent.ActualWidth, (int)UnityContent.ActualHeight, true);
But now, after the first resize I get blank space on the sides of the unity window. I need it to fill all available space. Please see the image.
I fixed this issue by doing 2 things:
By changing MoveWindow(_unityHWND, 0, 0, (int)UnityContent.Width, (int)UnityContent.Height, true);
to
MoveWindow(_unityHWND, 0, 0, (int)UnityContent.ActualWidth, (int)UnityContent.ActualHeight, true);
The unity window was not the same scale as the WPF window, so I had to add the following to mainwindow.xaml.cs
double scaleX, scaleY;
if (s != null)
{
scaleX = s.CompositionTarget.TransformToDevice.M11;
scaleY = s.CompositionTarget.TransformToDevice.M22;
}
var actualHeight = (int)UnityContent.ActualHeight * scaleY;
var actualWidth = (int)UnityContent.ActualWidth * scaleX;
MoveWindow(_unityHWND, 0, 0, (int)actualWidth, (int)actualHeight, true);
Related
I am making a recorder that records a specific window the user chooses. While I am recording, the program forces the window to be active all the time. That means that I can not move the window at all or even to change the size of the window. I was not able to stop the recording. Is there a way to fix that?
Combobox code:
private void Handlebox_SelectedIndexChanged(object sender, EventArgs e)
{
if (Handlebox.SelectedIndex == -1) return;
handle_name = Handlebox.SelectedItem.ToString();
}
The way I add all the processes in the combobox:
foreach (Process process in processlist)
{
if (!String.IsNullOrEmpty(process.MainWindowTitle))
{
Handlebox.Items.Add(process.ProcessName);
}
}
The script that the whole recording takes place:
// Record video:
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hWnd);
private const int SW_RESTORE = 9;
[DllImport("user32.dll")]
private static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
public Bitmap CaptureApplication(string procName)
{
Process proc;
// Cater for cases when the process can't be located.
try
{
proc = Process.GetProcessesByName(procName)[0];
}
catch (IndexOutOfRangeException e)
{
return null;
}
// You need to focus on the application
SetForegroundWindow(proc.MainWindowHandle);
ShowWindow(proc.MainWindowHandle, SW_RESTORE);
// Delay
Thread.Sleep(1000);
Rect rect = new Rect();
IntPtr error = GetWindowRect(proc.MainWindowHandle, ref rect);
// sometimes it gives error.
while (error == (IntPtr)0)
{
error = GetWindowRect(proc.MainWindowHandle, ref rect);
}
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics.FromImage(bmp).CopyFromScreen(rect.left,rect.top,0,0,new Size(width, height),CopyPixelOperation.SourceCopy);
return bmp;
}
public void RecordVideo()
{
// Keep track of time:
watch.Start();
// Save screenshot:
string name = tempPath + "//screenshot-" + fileCount + ".png";
CaptureApplication(handle_name).Save(name, ImageFormat.Png);
inputImageSequence.Add(name);
fileCount++;
// Dispose of bitmap:
CaptureApplication(handle_name).Dispose();
}
I have a high dpi setting on my monitor as the monitor is a fairly small 3840 x 2160 monitor.
This is causing issues with one of the applications I am writing as I am hosting a control in my main application. I developed it based off a nice example in the WPF-Samples.
On my screen, when the example is run in it's default state, the output looks like
I was able to account for the list control being the incorrect size by using
//MainWindow
PresentationSource source = PresentationSource.FromVisual(this); //Account for any scaling on the screen7
_listControl = new ControlHost(ControlHostElement.ActualWidth, ControlHostElement.ActualHeight, source.CompositionTarget.TransformToDevice.M11, source.CompositionTarget.TransformToDevice.M22);
...
//ControlHost
public ControlHost(double height, double width, double dpXScale, double dpYScale)
{
_hostHeight = (int) (height * dpXScale);
_hostWidth = (int) (width * dpYScale);
}
However, even when it is the correct size, the hosted UI is not scaled as the rest of the program is.
How could the program scale the hosted UI based on the DPI of the user's screen?
There are three main files that make up the this example.
ControlHost.cs
// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
#region Using directives
using System;
using System.Runtime.InteropServices;
using System.Windows.Interop;
#endregion
namespace WPFHostingWin32Control
{
public class ControlHost : HwndHost
{
internal const int
WsChild = 0x40000000,
WsVisible = 0x10000000,
LbsNotify = 0x00000001,
HostId = 0x00000002,
ListboxId = 0x00000001,
WsVscroll = 0x00200000,
WsBorder = 0x00800000;
private readonly int _hostHeight;
private readonly int _hostWidth;
private IntPtr _hwndHost;
public ControlHost(double height, double width, double dpXScale, double dpYScale)
{
_hostHeight = (int) (height * dpXScale);
_hostWidth = (int) (width * dpYScale);
}
public IntPtr HwndListBox { get; private set; }
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
HwndListBox = IntPtr.Zero;
_hwndHost = IntPtr.Zero;
_hwndHost = CreateWindowEx(0, "static", "",
WsChild | WsVisible,
0, 0,
_hostHeight, _hostWidth,
hwndParent.Handle,
(IntPtr) HostId,
IntPtr.Zero,
0);
HwndListBox = CreateWindowEx(0, "listbox", "",
WsChild | WsVisible | LbsNotify
| WsVscroll | WsBorder,
0, 0,
_hostHeight, _hostWidth,
_hwndHost,
(IntPtr) ListboxId,
IntPtr.Zero,
0);
return new HandleRef(this, _hwndHost);
}
protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
handled = false;
return IntPtr.Zero;
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
DestroyWindow(hwnd.Handle);
}
//PInvoke declarations
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
IntPtr hwndParent,
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam);
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
}
}
MainWindow.xaml
<Window x:Class="WPFHostingWin32Control.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFHostingWin32Control"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Loaded="On_UIReady">
<DockPanel Background="LightGreen">
<Border Name="ControlHostElement"
Width="200"
Height="200"
HorizontalAlignment="Right"
VerticalAlignment="Top"
BorderBrush="LightGray"
BorderThickness="3"
DockPanel.Dock="Right"/>
<StackPanel>
<Label HorizontalAlignment="Center"
Margin="0,10,0,0"
FontSize="14"
FontWeight="Bold">Control the Control</Label>
<TextBlock Margin="10,10,10,10" >Selected Text: <TextBlock Name="selectedText"/></TextBlock>
<TextBlock Margin="10,10,10,10" >Number of Items: <TextBlock Name="numItems"/></TextBlock>
<Line X1="0" X2="200"
Stroke="LightYellow"
StrokeThickness="2"
HorizontalAlignment="Center"
Margin="0,20,0,0"/>
<Label HorizontalAlignment="Center"
Margin="10,10,10,10">Append an Item to the List</Label>
<StackPanel Orientation="Horizontal">
<Label HorizontalAlignment="Left"
Margin="10,10,10,10">Item Text</Label>
<TextBox HorizontalAlignment="Left"
Name="txtAppend"
Width="200"
Margin="10,10,10,10" />
</StackPanel>
<Button HorizontalAlignment="Left"
Click="AppendText"
Width="75"
Margin="10,10,10,10">Append</Button>
<Line X1="0" X2="200"
Stroke="LightYellow"
StrokeThickness="2"
HorizontalAlignment="Center"
Margin="0,20,0,0"/>
<Label HorizontalAlignment="Center"
Margin="10,10,10,10">Delete the Selected Item</Label>
<Button Click="DeleteText"
Width="125"
Margin="10,10,10,10"
HorizontalAlignment="Left">Delete</Button>
</StackPanel>
</DockPanel>
</Window>
MainWindow.cs
// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
namespace WPFHostingWin32Control
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
internal const int
LbnSelchange = 0x00000001,
WmCommand = 0x00000111,
LbGetcursel = 0x00000188,
LbGettextlen = 0x0000018A,
LbAddstring = 0x00000180,
LbGettext = 0x00000189,
LbDeletestring = 0x00000182,
LbGetcount = 0x0000018B;
private Application _app;
private IntPtr _hwndListBox;
private int _itemCount;
private ControlHost _listControl;
private Window _myWindow;
private int _selectedItem;
public MainWindow()
{
InitializeComponent();
}
private void On_UIReady(object sender, EventArgs e)
{
_app = Application.Current;
_myWindow = _app.MainWindow;
_myWindow.SizeToContent = SizeToContent.WidthAndHeight;
PresentationSource source = PresentationSource.FromVisual(this); //Account for any scaling on the screen7
_listControl = new ControlHost(ControlHostElement.ActualWidth, ControlHostElement.ActualHeight, source.CompositionTarget.TransformToDevice.M11, source.CompositionTarget.TransformToDevice.M22);
ControlHostElement.Child = _listControl;
_listControl.MessageHook += ControlMsgFilter;
_hwndListBox = _listControl.HwndListBox;
for (var i = 1; i <= 100; i++) //populate listbox
{
var itemText = "Item" + i;
SendMessage(_hwndListBox, LbAddstring, IntPtr.Zero, itemText);
}
_itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + _itemCount;
}
private void AppendText(object sender, EventArgs args)
{
if (txtAppend.Text != string.Empty)
{
SendMessage(_hwndListBox, LbAddstring, IntPtr.Zero, txtAppend.Text);
}
_itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + _itemCount;
}
private void DeleteText(object sender, EventArgs args)
{
_selectedItem = SendMessage(_listControl.HwndListBox, LbGetcursel, IntPtr.Zero, IntPtr.Zero);
if (_selectedItem != -1) //check for selected item
{
SendMessage(_hwndListBox, LbDeletestring, (IntPtr) _selectedItem, IntPtr.Zero);
}
_itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + _itemCount;
}
private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
int textLength;
handled = false;
if (msg == WmCommand)
{
switch ((uint) wParam.ToInt32() >> 16 & 0xFFFF) //extract the HIWORD
{
case LbnSelchange: //Get the item text and display it
_selectedItem = SendMessage(_listControl.HwndListBox, LbGetcursel, IntPtr.Zero, IntPtr.Zero);
textLength = SendMessage(_listControl.HwndListBox, LbGettextlen, IntPtr.Zero, IntPtr.Zero);
var itemText = new StringBuilder();
SendMessage(_hwndListBox, LbGettext, _selectedItem, itemText);
selectedText.Text = itemText.ToString();
handled = true;
break;
}
}
return IntPtr.Zero;
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern int SendMessage(IntPtr hwnd,
int msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern int SendMessage(IntPtr hwnd,
int msg,
int wParam,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hwnd,
int msg,
IntPtr wParam,
string lParam);
}
}
Winforms does not handle high-DPI settings very well by default. As stated in the comments, you can try some of the available manifest settings to get it to resize. If that won't work for your situation then you will need to scale the control yourself. Most Winforms controls have a Scale method you could call when you adjust the height and width:
_listControl.Scale(dpXScale, dpYScale);
of course, since your actual code uses a custom control your mileage may vary.
I am painting a control from a buffer. My code for painting is it:
protected override void OnPaint(PaintEventArgs e)
{
if (surface != null)
{
using (Bitmap Pintar = new Bitmap(e.ClipRectangle.Width, e.ClipRectangle.Height))
{
BitmapData bmd = Pintar.LockBits(new Rectangle(0, 0, e.ClipRectangle.Width, e.ClipRectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
e.Graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
int DtX = e.ClipRectangle.X;
int DtY = e.ClipRectangle.Y;
Console.WriteLine(DtX + " - " + bmd.Width + " || " + DtY + " - " + bmd.Height);
unsafe
{
byte* PunteroL = (byte*)bmd.Scan0;
byte* PunteroS = (byte*)(surface.Buffer + (DtX * 4 + surface.Width * DtY * 4));
for (int Y = 0; Y < bmd.Height; Y++)
{
byte* PunteroLT = (byte*)(PunteroL + (bmd.Width * Y * 4));
byte* PunteroST = (byte*)(PunteroS + (surface.Width * Y * 4));
for (int X = 0; X < bmd.Width; X++)
{
byte* PunteroLS = (byte*)(PunteroLT + (X * 4));
byte* PunteroSS = (byte*)(PunteroST + (X * 4));
PunteroLS[0] = PunteroSS[0];
PunteroLS[1] = PunteroSS[1];
PunteroLS[2] = PunteroSS[2];
}
}
}
Pintar.UnlockBits(bmd);
e.Graphics.DrawImage(Pintar, DtX, DtY, Pintar.Width, Pintar.Height);
}
}
}
The problem here is when I resize the window I got an error "Tryng access to protected memory" and this is because of pointers..
I wanna know if there is any way to leave (or block) the OnPaint event while the user is resizing the view..
Thank! =)
What worked best for me in a similar situation was the solution described in this SO thread.
In an nutshell, it is suggested to add to your control the following import:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
And the following methods:
public static void SuspendDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
public static void ResumeDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
Then all you have to do is call SuspendDrawing before the re-size takes place and ResumeDrawing after.
Update:
Personally I prefer using it as an extension method, thus making it available for all controls I use without duplicating code or inheriting from base class...
public static class MyExtensionClass
{
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
public static void SuspendDrawing(this Control ctrl )
{
var parent = ctrl.Parent;
if(parent != null)
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
}
public static void ResumeDrawing(this Control ctrl )
{
var parent = ctrl.Parent;
if(parent != null)
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
}
}
Try to implement a event handler on Form.ResizeBegin, in which you will remove your OnPaint handler from your control.
Then you would just have to restore it in Form.ResizeEnd.
You have to get the ResizeBegin and the ResizeEnd events.
The code in there should look like:
private void OnResizeEnd(object sender, EventArgs e)
{
Paint += OnPaint;
}
private void OnResizeBegin(object sender, EventArgs e)
{
Paint -= OnPaint;
}
I'm looking for an event that would happen when the screen saver become active or inactive. I want to be notified. I do not want to use any timer.
I do not want to poll for it. I don't want to use: SystemParametersInfo( SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0 );
I tried that without success, I never receive the SC_SCREENSAVE...
// ************************************************************************
public MainWindow()
{
InitializeComponent();
}
// ************************************************************************
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
if (mainWindowSrc != null)
{
mainWindowSrc.AddHook(WndProc);
}
//HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
//source.AddHook(WndProc);
}
// ************************************************************************
private const Int32 WM_SYSCOMMAND = 0x112;
private const int SC_SCREENSAVE = 0xF140;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND)
{
Debug.Print("SysCommand : " + wParam);
if (wParam.ToInt32() == SC_SCREENSAVE)
{
Debug.Print(DateTime.Now.ToString());
}
}
return IntPtr.Zero;
}
Anybody has any other idea or does know what is wrong with my code ???
if (wParam.ToInt32() == SC_SCREENSAVE)
That's not correct. It is an odd quirk in the WM_SYSCOMMAND message, surely dating to a long gone era where they had to squeeze a GUI operating system in 640 kilobytes. The low 4 bits in the command value are used for internal purposes. You'll have to mask them out before you compare. Fix:
if ((wParam.ToInt32() & 0xfff0) == SC_SCREENSAVE)
You'd probably had figured this out yourself by using the proper Debug statement formatting :)
Debug.Print("SysCommand : 0x{0:X}", wParam);
Hans answer seems to not work for me on Windows 7 when it is activated normally by the timer (at least when your account is part of a domain and a policy make screen saver parameters as readonly).
Using SystemParametersInfo with SPI_GETSCREENSAVERRUNNING work fine but it require a timer (polling) which is to my point of view: horrible poorly designed code... but up to now, it is the only way I found that works all the time... :-(
I left my code as reference. It include many of my tests.
There is a lot more but just remove what you don't need.
You could consult the HotKey excellent class there (I couldn't found my initial source but this one is exactly the same): https://github.com/shellscape/Lumen/blob/master/Application/HotkeyHandler.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using HQ.Util.General;
using HQ.Util.Unmanaged;
using HQ.Wpf.Util;
using Microsoft.Win32;
using SpreadsheetGear;
namespace MonitorMe
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}
delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
private HotKeyHandeler _hotKeyHandler = null;
private Timer _timerToMonitorScreenSaver = null;
// ************************************************************************
public MainWindow()
{
InitializeComponent();
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
Monitor.Instance.Add(SessionSwitchReason.SessionLogon);
_timerToMonitorScreenSaver = new Timer(TimerCallbackMonitorScreenSaver, this, 3000, 3000);
}
private void TimerCallbackMonitorScreenSaver(object state)
{
int active = 1;
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref active, 0);
if (active == 1)
{
Debug.Print("Timer detected Screen Saver activated on: " + DateTime.Now.ToString());
}
}
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr LoadLibrary(string lpFileName);
// Signatures for unmanaged calls
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SystemParametersInfo(
int uAction, int uParam, ref int lpvParam,
int flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SystemParametersInfo(
int uAction, int uParam, ref bool lpvParam,
int flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int PostMessage(IntPtr hWnd,
int wMsg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr OpenDesktop(
string hDesktop, int Flags, bool Inherit,
uint DesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseDesktop(
IntPtr hDesktop);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool EnumDesktopWindows(
IntPtr hDesktop, EnumDesktopWindowsProc callback,
IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool IsWindowVisible(
IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetForegroundWindow();
// Callbacks
private delegate bool EnumDesktopWindowsProc(
IntPtr hDesktop, IntPtr lParam);
// Constants
private const int SPI_GETSCREENSAVERACTIVE = 16;
private const int SPI_SETSCREENSAVERACTIVE = 17;
private const int SPI_GETSCREENSAVERTIMEOUT = 14;
private const int SPI_SETSCREENSAVERTIMEOUT = 15;
private const int SPI_GETSCREENSAVERRUNNING = 114;
private const int SPIF_SENDWININICHANGE = 2;
private const uint DESKTOP_WRITEOBJECTS = 0x0080;
private const uint DESKTOP_READOBJECTS = 0x0001;
private const int WM_CLOSE = 16;
[DllImport("User32.dll")]
public static extern int SendMessage
(IntPtr hWnd,
uint Msg,
uint wParam,
uint lParam);
public enum SpecialHandles
{
HWND_DESKTOP = 0x0,
HWND_BROADCAST = 0xFFFF
}
// Registers a hot key with Windows.
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
// Unregisters the hot key with Windows.
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private IntPtr _hHook;
// ************************************************************************
void _hotKeyHandler_HotKeyPressed(object sender, HotKeyEventArgs e)
{
ActivateScreenSaver();
}
// ************************************************************************
private IntPtr MessageHookProc(int code, IntPtr wParam, IntPtr lParam)
{
if (code == WM_SYSCOMMAND)
{
Debug.Print("SysCommand : " + wParam);
if ((wParam.ToInt32() & 0xfff0) == SC_SCREENSAVE)
{
Debug.Print("MessageHookProc" + DateTime.Now.ToString());
}
}
return CallNextHookEx(_hHook, code, wParam, lParam);
}
// ************************************************************************
private const Int32 WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND)
{
Debug.Print("SysCommand : " + wParam);
if ((wParam.ToInt32() & 0xfff0) == SC_SCREENSAVE)
{
// Works fine in almost all cases. I already did some code in the past which activate the screen saver
// but I never receive any WM_SYSCOMMAND for that code, I do not have source code for it anymore).
Debug.Print("WndProc" + DateTime.Now.ToString());
}
}
return IntPtr.Zero;
}
// ************************************************************************
void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
Monitor.Instance.Add(e.Reason);
}
// ************************************************************************
private void CmdExportToExcel_Click(object sender, RoutedEventArgs e)
{
ExportToExcel();
}
// ************************************************************************
private void ExportToExcel()
{
IWorkbook wb = Factory.GetWorkbook();
IWorksheet workSheet = wb.Worksheets[0];
int col = 1;
for (int dayIndex = -30; dayIndex <= 0; dayIndex++)
{
DateTime day = DateTime.Today + new TimeSpan(dayIndex, 0, 0, 0);
Debug.Print(day.ToString("yyyy-MM-dd"));
workSheet.Cells[0, col].Value = day.DayOfWeek.ToString();
workSheet.Cells[1, col].Value = day;
DayMonitor dayMonitor = Monitor.Instance.DayMonitors.FirstOrDefault(dm => dm.Day == day);
if (dayMonitor != null)
{
workSheet.Cells[2, col].Value = "Last - First";
workSheet.Cells[2, col + 1].Value = dayMonitor.TotalHours;
workSheet.Cells[3, col].Value = "Has estimated time (7h00 or 18h00)";
workSheet.Cells[3, col + 1].Value = dayMonitor.HasSomeEstimatedHours;
int row = 5;
foreach (var state in dayMonitor.SessionLockStateChanges)
{
workSheet.Cells[row, col].Value = state.SessionSwitchReason.ToString();
workSheet.Cells[row, col + 1].Value = state.DateTime;
row++;
}
}
workSheet.Cells[1, col].ColumnWidth = 18;
workSheet.Cells[1, col + 1].ColumnWidth = 18;
col += 2;
}
if (wb != null)
{
if (FileAssociation.IsFileAssociationExistsForExtensionWithDot(".xlsx"))
{
string path = TempFolderAutoClean.GetTempFileName("xlsx", "Export ");
wb.SaveAs(path, FileFormat.OpenXMLWorkbook);
Process p = new Process();
p.StartInfo.UseShellExecute = true;
p.StartInfo.FileName = path;
p.Start();
}
else
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.InitialDirectory = Environment.CurrentDirectory;
saveFileDialog.Filter = "Excel file (*.xlsx)|*.xlsx";
saveFileDialog.DefaultExt = ".xlsx";
if (saveFileDialog.ShowDialog(Application.Current.MainWindow) == true)
{
string path = saveFileDialog.FileName;
wb.SaveAs(path, FileFormat.OpenXMLWorkbook);
}
}
}
}
// ************************************************************************
private void PrintOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
ExportToExcel();
}
// ************************************************************************
private void ExitOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.Close();
}
// ************************************************************************
private void PrintOnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.ContinueRouting = false;
}
// ************************************************************************
private void ExitOnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.ContinueRouting = false;
}
// ************************************************************************
private void CmdLaunchScreenSaver_Click(object sender, RoutedEventArgs e)
{
ActivateScreenSaver();
}
// ************************************************************************
private void ActivateScreenSaver()
{
// Next won't work in a secured Screen Saver by Account Policy (ex: by a domain policy)
//int nullVar = 0;
//SystemParametersInfo(SPI_SETSCREENSAVERACTIVE, 1, ref nullVar, SPIF_SENDWININICHANGE);
// This works fine all the time... and also send appropriate message to every top level window
SendMessage(new IntPtr((int)SpecialHandles.HWND_BROADCAST), WM_SYSCOMMAND, SC_SCREENSAVE, 0);
}
// ************************************************************************
private void MainWindow_OnInitialized(object sender, EventArgs e)
{
int error;
IntPtr hMod = LoadLibrary("user32.dll"); // Hans Passant info in stackOverflow
_hHook = SetWindowsHookEx(HookType.WH_GETMESSAGE, MessageHookProc, IntPtr.Zero, (uint)Thread.CurrentThread.ManagedThreadId);
error = Marshal.GetLastWin32Error(); // Error 87
_hHook = SetWindowsHookEx(HookType.WH_GETMESSAGE, MessageHookProc, hMod, (uint)Thread.CurrentThread.ManagedThreadId);
error = Marshal.GetLastWin32Error(); // Error 87
_hHook = SetWindowsHookEx(HookType.WH_GETMESSAGE, MessageHookProc, hMod, 0);
error = Marshal.GetLastWin32Error(); // Work fine, got a hook, but never receive the WM_SYSCOMMAND:SC_SCREENSAVE
}
// ************************************************************************
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
}
// ************************************************************************
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// Method 1
//IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
//HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
//if (mainWindowSrc != null)
//{
// mainWindowSrc.AddHook(WndProc);
//}
// Method 2
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
_hotKeyHandler = new HotKeyHandeler(this);
_hotKeyHandler.HotKeyPressed += _hotKeyHandler_HotKeyPressed;
// _hotKeyHandler.RegisterHotKey(0, Key.NumPad0);
this.Visibility = Visibility.Hidden;
}
// ************************************************************************
}
}
Goal: write a C# app that runs in the background, listens for the key combination Win-V, and when that occurs, pastes the clipboard contents into the current active window (some arbitrary app). Essentially I'm trying to mimic PureText, but I'm not bothering to convert the text to plain text first.
Problem: pasting into the currently active windows is not working.
Details: To listen in the background for key presses I'm using the globalKeyboardHook class from A Simple C# Global Low Level Keyboard Hook. I'm able to catch Win-V events, but I'm not able to send the paste command properly. I can send the paste by using the functions SendKeys.Send or keybd_event. However, they send another "V" press down the pipeline which gets caught by the gkh_KeyDown event and causes multiple paste events to fire.
I'm expecting that I need to use SendMessage or PostMessage, but all my attempts to do that have failed so far. Below is the full code with the last function, SendCtrlV, being the one of interest. The comments explain everything I've tried so far. Can you see what I'm missing?
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Utilities;
namespace KeyHookTest
{
public partial class Form1 : Form
{
private bool LWin_down;
private bool V_down;
globalKeyboardHook gkh = new globalKeyboardHook();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hwnd, int msg, int wParam, int lParam);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
gkh.HookedKeys.Add(Keys.V);
gkh.HookedKeys.Add(Keys.LWin);
gkh.KeyDown += new KeyEventHandler(gkh_KeyDown);
gkh.KeyUp += new KeyEventHandler(gkh_KeyUp);
}
void gkh_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.LWin)
LWin_down = false;
else
V_down = false;
}
void gkh_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.LWin)
LWin_down = true;
else
V_down = true;
if (LWin_down && V_down)
{
LogDebug("Enter Win+V");
try
{
SendCtrlV();
}
catch { }
}
}
private void SendCtrlV()
{
uint KEYEVENTF_KEYUP = 2;
int KEYDOWN = 0x0100;
int KEYUP = 0x0101;
byte KEY_LCONTROL1 = 0x11;
IntPtr KEY_LCONTROL2 = new IntPtr(0x11);
byte KEY_V1 = 0x56;
IntPtr KEY_V2 = new IntPtr(0x56);
int WM_PASTE1 = 0x302;
uint WM_PASTE2 = 0x302;
IntPtr hWnd = GetForegroundWindow();
// Works, but causes multiple gkh_KeyDown to fire so it's slow and buggy
/*keybd_event(KEY_LCONTROL1, 0, 0, 0);
keybd_event(KEY_V1, 0, 0, 0);
keybd_event(KEY_V1, 0, KEYEVENTF_KEYUP, 0);
keybd_event(KEY_LCONTROL1, 0, KEYEVENTF_KEYUP, 0);*/
// Works, but causes multiple gkh_KeyDown to fire so it's slow and buggy
//SendKeys.Send("^v");
// Doesn't work, causes UAC prompt
//SendKeys.Send("{^}v");
// Doesn't work, nothing gets pasted to the foregroundwindow
//SendMessage(hWnd, WM_PASTE1, 0, 0);
// Doesn't work, nothing gets pasted to the foregroundwindow
//PostMessage(hWnd, WM_PASTE2, IntPtr.Zero, IntPtr.Zero);
// Doesn't work, nothing gets pasted to the foregroundwindow
/*SendMessage(hWnd, KEYDOWN, KEY_LCONTROL1, 0);
SendMessage(hWnd, KEYDOWN, KEY_V1, 0);
SendMessage(hWnd, KEYUP, KEY_V1, 0);
SendMessage(hWnd, KEYUP, KEY_LCONTROL1, 0);*/
// Doesn't work, nothing gets pasted to the foregroundwindow
/*PostMessage(hWnd, 0x0100, KEY_LCONTROL2, IntPtr.Zero);
PostMessage(hWnd, 0x0100, KEY_V2, IntPtr.Zero);
PostMessage(hWnd, 0x0101, KEY_V2, IntPtr.Zero);
PostMessage(hWnd, 0x0101, KEY_LCONTROL2, IntPtr.Zero);*/
}
private void LogDebug(string msg)
{
string logpath = Environment.GetEnvironmentVariable("USERPROFILE") + #"\Desktop\KeyHookTest.txt";
File.AppendAllText(logpath, DateTime.Now.ToString("HH:mm:ss:fff") + ": " + msg + "\r\n");
}
}
}
These additional links helped lead me to the answer:
How to get active child window
How can I find the active child window?
Here's what's working for me:
private void SendCtrlV()
{
IntPtr hWnd = GetFocusedHandle();
PostMessage(hWnd, WM_PASTE, IntPtr.Zero, IntPtr.Zero);
}
static IntPtr GetFocusedHandle()
{
var info = new GuiThreadInfo();
info.cbSize = Marshal.SizeOf(info);
if (!GetGUIThreadInfo(0, ref info))
throw new Win32Exception();
return info.hwndFocus;
}
It works, but you must use the TextBox's native window handle if you want it to be effective