I'm having problems with changing a window's transparency.
What I'm trying to accomplish
I develop a WPF application, which has some interaction with another application (a game developed in Unity3D). My goal is to "integrate" the game into my WPF app to make it look like the game is a natural part of the WPF app. So when I move my main window, I use PInvoke to move the game's window, too, I use PInvoke to hide the window borders of the game window etc. This all works decently.
However, a problem arises when I try to hide the game without closing it. My first approach was to use PInvoke to set the game window as hidden:
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hwnd, int show);
const int SW_HIDE = 0;
const int SW_Show = 5;
ShowWindow(Handle, SW_HIDE);
Using this function call the window disappeares, but as a consequence, the CPU usage of the game process jumps from 1% to 20%. So far, I have no clue why that is. Maybe it's related to Unity, maybe not. (CPU goes back to normal when I show the window again, though).
My second approach was not to hide the window using the ShowWindow-call but to set the window's opacity to 0 (as suggested in this thread). On my setup, this works just fine (without the CPU going crazy), but on other setups, the window will stay visible.
Updating the graphics driver solves the issue sometimes, but not always. I have an old Vista machine where everything works, and I also tried it on newer Dell Laptops with Windows 10 installed, where it does not work. I don't have the issue on my Mac with Windows 10 and Boot Camp. So I have no clue what causes this problem.
Did anyone experience similiar issues? Is there another way to hide a window or does anyone know why CPU goes insane when using the ShowWindow()-method?
Any hint is much appreciated.
EDIT
Would be nice to know why this post is downvoted so much, but okay. I think especially the CPU usage thing is an interesting issue and I am terribly sorry if someone thinks it's obvious and I am a moron.
For those who are also interested, here is a minimal WPF reconstruction of my problem:
MainWindow.xaml
<Window x:Class="SampleWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="1024" Width="1280"
KeyDown="MainWindow_OnKeyDown">
<Grid>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
const int SW_HIDE = 0;
const int SW_SHOW = 5;
private Process gameProcess;
private IntPtr gameWindowHandle;
private bool windowHidden = false;
public MainWindow()
{
InitializeComponent();
this.SizeChanged += OnSizeChanged;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
StartGame();
}
private void MainWindow_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
if (windowHidden)
{
NativeMethods.ShowWindow(gameWindowHandle, SW_HIDE);
windowHidden = false;
}
else
{
NativeMethods.ShowWindow(gameWindowHandle, SW_SHOW);
windowHidden = true;
}
}
}
private void StartGame()
{
var mainWindowHandle = new WindowInteropHelper(this).Handle;
var processStartInfo = new ProcessStartInfo(#"Path\To\SampleUnityApp.exe");
processStartInfo.Arguments += "-parentHWND " + mainWindowHandle.ToInt32() + " " + Environment.CommandLine + " ";
processStartInfo.CreateNoWindow = true;
processStartInfo.UseShellExecute = false;
gameProcess = Process.Start(processStartInfo);
gameProcess.WaitForInputIdle();
NativeMethods.EnumChildWindows(mainWindowHandle,
(hwnd, lparam) =>
{
// Set the window handle of the game
gameWindowHandle = hwnd;
return 0;
}, IntPtr.Zero);
}
static class NativeMethods
{
[DllImport("user32.dll")]
internal static extern bool ShowWindow(IntPtr hwnd, int show);
[DllImport("user32.dll")]
internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
}
}
The SampleUnityApp I used is just a blank Unity project with a main camera. No other game object. No code I wrote. It's created with Unity 5.3.4f1 Personal.
Screenshot of TaskManager with window visible
Screenshot of TaskManager with window hidden
Related
I encountered a problem in WPF (C#), I need to position a popup on a certain point within the parent window.
I get the parent position with Application.Current.MainWindow.Left and Application.Current.MainWindow.Top, it works as long as I don't move the window from one monitor to the other with the Windows shortcut Windows + Shift + ◄/►. If I use the shortcut the properties stay the same as they were before moving the window.
The window has to be WindowState.Maximized, it works if it is not maximized.
I also tried using Window.GetWindow(this) instead of Application.Current.MainWindow, result is the same.
It seems as if the positionchanged event doesn't occur for the Application.Current.MainWindow and it doesn't update the Left and Top properties.
I didn't find anything on this on SO or Google.
A workaround, hint or solution are greatly appreciated.
Try to use this:
WindowInteropHelper windowInteropHelper = new WindowInteropHelper(Application.Current.MainWindow);
Screen screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
The Screen Bounds property provides the coordinates of the whole window and the WorkingArea the boundaries of the area without titlebar and docked windows.
You should use the win32 API to retrieve the information you want as the events exposed by .NET aren't enough to detect this scenario.
There are two things you have to do:
Listen to the WndProc message that corresponds to a window movement (full list here). The one we want is WM_MOVE and is equal to 0x0003. See this thread for details.
Be able to determine the real location of the Window even when it's in Maximized state, which we can do by using the GetWindowRect method. See this thread for details.
Here would be the assembled code that prints the top-left location of your Window when it is moved, including using the shortcut you describe.
public partial class MainWindow : Window {
HwndSource source;
const short WM_MOVE = 0x0003;
public MainWindow() {
InitializeComponent();
// Loaded event needed to make sure the window handle has been created.
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e) {
// Subscribe to win32 level events
source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
if (msg == WM_MOVE) {
Console.WriteLine("Window has moved!");
GetWindowRect(new HandleRef(this, new WindowInteropHelper(this).Handle), out RECT rect);
Console.WriteLine("New location is " + (rect.Left, rect.Top));
}
return IntPtr.Zero;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
}
I am currently trying the recreate the Docking Panel/forms of visual studio. I want to apply the same system to a different project to handle different window views, but first I want to recreate it.
So far I've been switching the MDiParent property between the MainForm and null, but haven't been able to recreate the smooth transition seen in Visual Studio. It's always janky and half the time I find out the MouseUP event I tie the switch to rarely works (I'm currently using the Location change event).
To move the form, I've been using this handy code block I found;
#region Form Movement
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
private void MoveForm()
{
ReleaseCapture();
SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
#endregion
And to Handle the Dock/UnDock, I'm using this code;
private void DockToMain()
{
Point f = Form1.Main.PointToClient(this.Location);
Point newP = new Point(f.X - 25, f.Y - 51);
try { this.MdiParent = Form1.Main; } catch { }
this.Location = newP;
}
private void UnDockFromMain()
{
Point f = Form1.Main.PointToScreen(this.Location);
Point newP = new Point(f.X + 25, f.Y + 51);
try { this.MdiParent = null; } catch { }
this.Location = newP;
}
Is there a way to recreate the Dock Panel/Form in Winforms? Am I even on the right track here, or is there a better way I'm not seeing?
Just to Answer this, Will is Completely Right. I Somehow got it to kind of work, but it's far too glitchy to be of any use. Just Switch to WPF (it isn't too difficult to make the jump from WinForms) and use the Avalon dock control from xceedsoftware's " Extended WPF Toolkit"
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.
I used small window as a tooltip which shows "now playing" info in my application. Sure I set Topmost = "True" in window declaration and when new song starts to play I do tooltip.Show(). The problem is when I have fullscreen application in focus (game for example), .Show() call makes fullscreen window to became non-fullscreen, windowed, with border and top bar (as if I press Alt + Enter) and it looks like it loses focus also. So to restore fullscreen I need to click in window to focus it and press Alt-Enter manually.
ShowActivated set to "False" already.
So, I see three solutions:
1) Somehow make Topmost option to not cause to steal focus on .Show()
2) Use some workaround to make tooltip window "always on top" without Topmost option (I don't need to popup tooltip over fullscreen application since it can be (and probably will be) drawed via D3D or OpenGL)
3) Detect if system has fullscreen window in focus now and don't try to show tooltip.
I have no any clue how to fix behavior with any of this options, or maybe there is something more elegant?
So... in case anyone interested, here is my "research".
Solution 1) Failed. I found a few discussion about this behavior and some says it's a bug and the others it's a feature, but any of them assumes that it cannot be solved in managed code. I also tried attempt from Sheridan's answer with no luck.
Solution 2) Failed. I have some experience with P/Invoke but I generally fail if something goes wrong. In this particular case I tried to use
this.Handle = new WindowInteropHelper(this).Handle;
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
as it suggested across web. This code does not show window. So I tried to show it with
ShowWindow(this.Handle, 4); // I also tried SHOW_NORMAL and few other flags
but window still was not visible
It becames visible if I do this.Visibility = /*visible*/ or this.Show(), and I really see that window is topmost, but it does not solve the issue and fullscreen escapes. If anyone knows how .Show() works internally and how I can show WPF window with P/Invoke, please let me know.
Solution 3) succeed. I found working code with a little googling here and slightly shortened it:
internal class FullscreenCheck
{
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowRect(IntPtr hwnd, out RECT rc);
// I hope this handles never changes
private static IntPtr hndlDesktop = GetDesktopWindow();
private static IntPtr hndlShell = GetShellWindow();
public static bool IsFullscreen()
{
var hndlForeground = GetForegroundWindow();
if (hndlForeground == null || hndlForeground == IntPtr.Zero || hndlForeground == hndlDesktop || hndlForeground == hndlShell)
{
return false;
}
RECT appBounds;
GetWindowRect(hndlForeground, out appBounds);
var screenBounds = System.Windows.Forms.Screen.FromHandle(hndlForeground).Bounds;
return ((appBounds.Bottom - appBounds.Top) == screenBounds.Height && (appBounds.Right - appBounds.Left) == screenBounds.Width);
}
So the last step is just not call .Show() if IsFullscreen() == true
I'm not totally sure about this, but how about trying this?:
tooltip.Owner = referenceToParentWindow;
If you launch this from MainWindow.xaml.cs, then you would use this:
tooltip.Owner = this;
If you launch this from outside the MainWindow.xaml.cs file, then you could (maybe) use this:
tooltip.Owner = Application.Current.MainWindow;
I got this code to mute/unmute the master volume
private const int APPCOMMAND_VOLUME_MUTE = 0x80000;
private const int WM_APPCOMMAND = 0x319;
[DllImport("user32.dll")]
public static extern IntPtr SendMessageW(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
SendMessageW(this.Handle, WM_APPCOMMAND, this.Handle, (IntPtr)APPCOMMAND_VOLUME_MUTE);
I would to know how can I get the master volume level/precent because I want to know if the sound is already muted or not.
Edit:
or else I would like to split the mute/unmute sound so I will have two functions - one for mute and one for unmute.
thanks
Have a look at this project http://www.codeproject.com/KB/vista/CoreAudio.aspx
They created an own mixer control, that also reports the current volumne and the mute/unmute state:
defaultDevice.AudioEndpointVolume.OnVolumeNotification += new AudioEndpointVolumeNotificationDelegate(
AudioEndpointVolume_OnVolumeNotification);
// .. snip ..
void AudioEndpointVolume_OnVolumeNotification(AudioVolumeNotificationData data)
{
Console.WriteLine("New Volume {0}", data.MasterVolume);
Console.WriteLine("Muted {0}", data.Muted);
}
Does this help you?
EDIT: With this code and the class from the project you should be able to set and unset mute directly (without toggle):
MMDeviceEnumerator devEnum = new MMDeviceEnumerator();
MMDevice defaultDevice = devEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
defaultDevice.AudioEndpointVolume.Mute = true; // or false
I could not do it for all Windows versions (xp, vista & 7).
Though, I achieved it by used external programs, such as NirCmd, and sent the command I needed.
not so good solution but it did solve my problem.
This thread shows how to control the master volume from C#.
You might also be interested in the responses to this question: Get Master Sound Volume in c#
Especially the NAudio managed wrapper.