Transparent, Click-Through Forms except custom Title bar - c#

i was picked some code, implement a Topmost, Transparent and Click-Though Form via code:
public enum GWL
{
ExStyle = -20
}
public enum WS_EX
{
Transparent = 0x20,
Layered = 0x80000
}
public enum LWA
{
ColorKey = 0x1,
Alpha = 0x2
}
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
public static extern int GetWindowLong(IntPtr hWnd, GWL nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
public static extern int SetWindowLong(IntPtr hWnd, GWL nIndex, int dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetLayeredWindowAttributes")]
public static extern bool SetLayeredWindowAttributes(IntPtr hWnd, int crKey, byte alpha, LWA dwFlags);
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
int wl = GetWindowLong(this.Handle, GWL.ExStyle);
wl = wl | 0x80000 | 0x20;
SetWindowLong(this.Handle, GWL.ExStyle, wl);
SetLayeredWindowAttributes(this.Handle, 0, 128, LWA.Alpha);
}
With this code, all of form are transparent and click-though absolutely (Text, image, control button...)
Current Form Border Style is none (no border, no title bar). Now I want to make a custom Title bar, allow user move Form to other position on screen (Like Title bar).
The problems here, all of form is cannot click on it (cause by click-though code), how to do the Click-though Form but except that custom Title Bar?
Custom title bar must be transparent as other element.
I don't want to keep original Title bar, it look bad for my application.

Using the WS_EX_LAYERED style means that you have to draw your form yourself with only the UpdateLayeredWindow function. You can still get all the messages from your controls, providing that you have already paint there with UpdateLayeredWindow.
UpdateLayeredWindow will draw an hdc: handle device context only, so if you want to draw an image:
IntPtr screenHdc = GetDC(IntPtr.Zero);
IntPtr bmpHdc = CreateCompatibleDC(screenHdc);
IntPtr hBitmap;
Bitmap bmp = .... //load your bitmap, a png one for transparency
hBitmap = bmp.GetHbitmap(Color.FromArgb(0)); //Zero: background is transparent
SelectObject(bmpHdc, hBitmap);
UpdateLayeredWindow(this.Handle, screenHdc, ..., ..., bmpHdc, ..., ..., ..., ...);
Take a look UpdateLayeredWindow function for more information

Related

Making Background Transparent

I am trying to make a makeshift onscreen keyboard for Windows 10 and need the background to be transparent, to make it more convenient for the user (the keys are already transparent). I, however, have no idea how to make the background transparent.
Any help would be greatly appreciated.
I believe that I am essentially looking for an updated version of the code in this thread show below:
using System;
using System.Runtime.InteropServices;
using UnityEngine;
public class TransparentWindow : MonoBehaviour
{
[SerializeField]
private Material m_Material;
private struct MARGINS
{
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
[DllImport("user32.dll")]
private static extern IntPtr GetActiveWindow();
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
[DllImport("user32.dll")]
static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", EntryPoint = "SetLayeredWindowAttributes")]
static extern int SetLayeredWindowAttributes(IntPtr hwnd, int crKey, byte bAlpha, int dwFlags);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
private static extern int SetWindowPos(IntPtr hwnd, int hwndInsertAfter, int x, int y, int cx, int cy, int uFlags);
[DllImport("Dwmapi.dll")]
private static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);
const int GWL_STYLE = -16;
const uint WS_POPUP = 0x80000000;
const uint WS_VISIBLE = 0x10000000;
const int HWND_TOPMOST = -1;
void Start()
{
// You really don't want to enable this in the editor, but it works there..
int fWidth = Screen.width;
int fHeight = Screen.height;
var margins = new MARGINS() { cxLeftWidth = -1 };
var hwnd = GetActiveWindow();
SetWindowLong(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);
// Transparent windows with click through
SetWindowLong(hwnd, -20, 524288 | 32);//GWL_EXSTYLE=-20; WS_EX_LAYERED=524288=&h80000, WS_EX_TRANSPARENT=32=0x00000020L
SetLayeredWindowAttributes(hwnd, 0, 255, 2);// Transparency=51=20%, LWA_ALPHA=2
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, fWidth, fHeight, 32 | 64); //SWP_FRAMECHANGED = 0x0020 (32); //SWP_SHOWWINDOW = 0x0040 (64)
DwmExtendFrameIntoClientArea(hwnd, ref margins);
}
void OnRenderImage(RenderTexture from, RenderTexture to)
{
Graphics.Blit(from, to, m_Material);
}
}
The code given did not work, so I assume that it is outdated. I have no idea how to update it myself, since it is a bit out of my skill set. When I upload the code to Unity, it just says that there are errors in the code and that it is not a valid script. When I open the script, however, no errors appear.
I expect to be able to have a relatively good view of whatever is behind my keyboard, like my desktop, but I actually just see a black plane.
Update:
So apparently the error message was caused by my script not having the same name as my class. I spent over 4 hours yesterday trying to fix that error message, and this naming incident was the cause :(. Thanks Ruzihm. Anyway, now that the error message is gone, when I run or build the program, my transparent window material just comes up: a dark pink. I then changed my Unity version back to 2018.2.16f1, with no success. Then I removed the #if !Unity Editor line to get the transparency to work perfectly in the editor, but not when I build it. Note, click through does work when I build it and when I run it in the editor.
As discovered in the comments, the problem was fixed when the camera's clear flags were set to solid color and the pink transparent window material was replaced a with white transparent material.

Can you get the position of a window that is inside of another application

So I found an example from an answer provided here
There was an answer that gave this example of code to move the Notepad window to the top left corner of the screen. I tried it and it worked fine. I then tried it on a small project I am working on and I couldn't move it.
NOTE: I did change the "Notepad" to the name at the top of the window I wanted to move.
using System;
using System.Runtime.InteropServices; // For the P/Invoke signatures.
public static class PositionWindowDemo
{
// P/Invoke declarations.
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
const uint SWP_NOSIZE = 0x0001;
const uint SWP_NOZORDER = 0x0004;
public static void Main()
{
// Find (the first-in-Z-order) Notepad window.
IntPtr hWnd = FindWindow("Notepad", null);
// If found, position it.
if (hWnd != IntPtr.Zero)
{
// Move the window to (0,0) without changing its size or position
// in the Z order.
SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
}
I will give an example. Consider Visual Studios and how it has the Solution Explorer Window or the Output window, and I can drag them with the mouse and move them or undock them. Would there be a way to have an application that has windows inside of it similar to Visual Studios and get the position of them in a program?
I have seen many answers on here about moving a window or finding the active window etc. However I am not sure if I will be able to access this subWindow that is inside of another application.
Thanks

Click-through form on condition

I have a form which has various buttons and panels. I have one button which when pressed runs a check against some values and if the check passes I need the mouse click to fall through the form and hit whatever it is beneath the application window.
What I'm currently doing is after the button is pressed and the check has passed, I set the form to transparent using:
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
private int oldWindowLong = 0;
public void SetFormTransparent(IntPtr Handle)
{
oldWindowLong = GetWindowLong(Handle, -20);
SetWindowLong(Handle, -20, Convert.ToInt32(oldWindowLong | 0x80000 | 0x20));
}
public void SetFormNormal(IntPtr Handle)
{
SetWindowLong(Handle, -20, Convert.ToInt32(oldWindowLong | 0x80000));
}
Then I create a 1 millisecond timer, I simulate the mouse click using:
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
And set the form back to normal. This results in a really inconsistent and sometimes slow/unresponsive behavior.
What other options do I have if I want to simulate a mouse click as soon as the button's check has passed?
The point is to use Color.Magenta as TransparencyKey and BackColor of your form.
Then make button invisible, and simulate a click event, then make the button visible again.
In this example, when you click on the button, it makes the form transparent and then simulates a click to pass through the form.
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
private const int MOUSEEVENTF_LEFTDOWN = 0x02;
private const int MOUSEEVENTF_LEFTUP = 0x04;
public void PerformClick()
{
uint X = (uint)Cursor.Position.X;
uint Y = (uint)Cursor.Position.Y;
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, X, Y, 0, 0);
}
private void button1_Click(object sender, EventArgs e)
{
//Just to keep the form on top
this.TopMost = true;
//Make form transparent and click through
this.TransparencyKey = Color.Magenta;
this.BackColor = Color.Magenta;
//Make the button invisible and perform a click
//The click reaches behind the button
//Then make button visible again to be able handle clicks again
this.button4.Visible = false;
PerformClick();
this.button4.Visible = true;
}
Notes
Make Transparent and Click Through
To make a form Transparent and make clicks pass through the form, you can simply Set the TransparencyKey property and BackColor property of your form to the same color Color.Magenta.
Pay attention that the key point is using Magenta as TransparencyKey and BackColor. For example, if you use Red, it makes the form transparent but doesn't make it click through.
If you have some controls on your form, they will remain visible and will receive clicks. If you need to make them invisible, you can simply set Visible property of them to false
Make Normal
To make that form normal, it's enough to set BackColor to another color different than TransparencyKey, for example SystemColors.Control

Handling AeroSnap message in WndProc

In my C# .NET 4 application, I use WndProc to process some messages mostly dealing with resizing the application to and from full screen.
Right now I am just handling SC_MAXIMIZE and WM_NCLBUTTONDBLCLK to determine if the window is being resized to or from a maximized state (I know I don't need WndProc to handle SC_MAXIMIZE, but Form_Resize didn't seem to fire for a WM_NCLBUTTONDBLCLK message when I double-click on the application's title bar.
Now I noticed that if I Aero Snap the window to the top of the screen to maximize it, neither of the above messages are posted so certain logic is not applied when the window is maximized via Aero Snap. I only want to handle the message if the window is snapped to the top of the screen rather than the right or left, or if a window is unsnappped from maximized position.
I couldn't find any of the window messages related to Aero Snap. Does anyone know of any references for those messages?
I'm guessing there aren't any special messages here; Aero is likely just using the plain Win32 APIs - ShowWindow(SW_MAXIMIZE) and similar.
The thing to uderstand with the SC_ messages are that those are requests from a menu asking the window to resize/restore/etc itself, but that is not the only mechanism for changing the windows's size. What's probably happening is that when a window gets SC_MAXIMIZE, the DefWndProc implements this by calling ShowWindow(SW_MAXIMIZE).
Your best best is to listen to the WM_SIZE message, which the window receives, regardless of what triggered the size change: system menu, API, or other means. In particular, the lParam will let you know if the window was maximized (SIZE_MAXIMIZED) or restored (SIZE_RESTORED).
Here is the code for handling WM_WINDOWPOSCHANGING message for Maximise instead of WM_SIZE message. Thanks to the 20 or more questions on SO that I had to read to find all the bits to put it together and get it working. This addresses the problems that I was having with multiple monitors using different resolutions.
//register the hook
public static void WindowInitialized(Window window)
{
IntPtr handle = (new WindowInteropHelper(window)).Handle;
var hwndSource = HwndSource.FromHwnd(handle);
if (hwndSource != null)
{
hwndSource.AddHook(WindowProc);
}
}
//the important bit
private static IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case 0x0046: //WINDOWPOSCHANGING
var winPos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
var monitorInfo = new MONITORINFO();
IntPtr monitorContainingApplication = MonitorFromWindow(hwnd, MonitorDefaultToNearest);
GetMonitorInfo(monitorContainingApplication, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
//check for a framechange - but ignore initial draw. x,y is top left of current monitor so must be a maximise
if (((winPos.flags & SWP_FRAMECHANGED) == SWP_FRAMECHANGED) && (winPos.flags & SWP_NOSIZE) != SWP_NOSIZE && winPos.x == rcWorkArea.left && winPos.y == rcWorkArea.top)
{
//set max size to the size of the *current* monitor
var width = Math.Abs(rcWorkArea.right - rcWorkArea.left);
var height = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
winPos.cx = width;
winPos.cy = height;
Marshal.StructureToPtr(winPos, lParam, true);
handled = true;
}
break;
}
return (IntPtr)0;
}
//all the helpers for dealing with this COM crap
[DllImport("user32")]
internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
[DllImport("user32")]
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
private const int MonitorDefaultToNearest = 0x00000002;
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MONITORINFO
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
public RECT rcMonitor;
public RECT rcWork;
public int dwFlags;
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}

Getting A Window's Region

I am trying to a get a window's height and width using this :
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr handle, out Rectangle rect);
private void timer1_Tick(object sender, EventArgs e)
{
Rectangle bonds = new Rectangle();
GetWindowRect(GetForegroundWindow(), out bonds);
Bitmap bmp = new Bitmap(bonds.Width, bonds.Height);
Graphics memoryGraphics = Graphics.FromImage(bmp);
IntPtr dc = memoryGraphics.GetHdc();
PrintWindow(GetForegroundWindow(), dc, 0x1);
memoryGraphics.ReleaseHdc(dc);
bmp.Save("C:\\Test.gif", ImageFormat.Gif);
}
but bonds.width and height are more than the real window.
Example : My window is 100x150, bond.height is like 400 and bonds.width is like 500. I get a picture with the size of 400x500 that consist of the window in the corner of the picture (which is good) and the rest is black since the picture is way bigger than the window (which is bad)
NOTE : I don't care that the window is aeroless, it's good for me.
So any suggestion, or maybe a better way of getting a region?
You need to use ScreenToClient and translate the coordinates.
GetWindowRect returns the window area relative to the top-left of the screen ( 0, 0 ).

Categories

Resources