i am making an app that overlays other d3d games,
the app is working perfectly except it has a huge performance impact on the cpu
taking 21.4 % of the cpu when rendering only a single line !
i am using slimdx library on c# and here is my full code
OverLay.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using SlimDX.Direct3D11;
using SlimDX.Windows;
using System.Runtime.InteropServices;
using System.Security;
using SlimDX;
using SlimDX.DXGI;
using Device = SlimDX.Direct3D9.Device;
using Resource = SlimDX.Direct3D9.Resource;
using System.Threading;
using D3D = SlimDX.Direct3D9;
namespace OverlayForm
{
public partial class OverLay : RenderForm
{
RenderForm form;
Device device;
// D3D.Sprite sprite;
public OverLay()
{
InitializeComponent();
Paint += OverLay_Paint;
FormBorderStyle = FormBorderStyle.None;
ShowIcon = false;
ShowInTaskbar = false;
TopMost = true;
WindowState = FormWindowState.Maximized;
//Make the window's border completely transparant
//SetWindowLong(Handle , GWL_EXSTYLE , (IntPtr)(GetWindowLong(Handle , GWL_EXSTYLE) ^ WS_EX_LAYERED ^ WS_EX_TRANSPARENT));
SetWindowLong(Handle , GWL_EXSTYLE , (IntPtr)(GetWindowLong(Handle , GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT));
//Set the Alpha on the Whole Window to 255 (solid)
SetLayeredWindowAttributes(Handle , 0 , 255 , LWA_ALPHA);
form = this;
form.FormClosing += Form_FormClosing;
//Init DirectX
//This initializes the DirectX device. It needs to be done once.
//The alpha channel in the backbuffer is critical.
D3D.PresentParameters presentParameters = new D3D.PresentParameters();
presentParameters.Windowed = true;
presentParameters.SwapEffect = D3D.SwapEffect.Discard;
presentParameters.BackBufferFormat = D3D.Format.A8R8G8B8;
device = new Device(new D3D.Direct3D() , 0 , D3D.DeviceType.Hardware , Handle ,
D3D.CreateFlags.HardwareVertexProcessing , presentParameters);
//sprite = new D3D.Sprite(device);
font = new D3D.Font(device , new Font("Arial" , 9 , FontStyle.Regular));
line = new D3D.Line(this.device);
MessagePump.Run(form , new MainLoop(dxThread));
}
private void Form_FormClosing(object sender , FormClosingEventArgs e)
{
device.Dispose();
}
int centerx = Screen.PrimaryScreen.WorkingArea.Width / 2;
int centery = Screen.PrimaryScreen.WorkingArea.Height / 2;
private void OverLay_Paint(object sender , PaintEventArgs e)
{
//Create a margin (the whole form)
marg.Left = 0;
marg.Top = 0;
marg.Right = Width;
marg.Bottom = Height;
//Expand the Aero Glass Effect Border to the WHOLE form.
// since we have already had the border invisible we now
// have a completely invisible window - apart from the DirectX
// renders NOT in black.
DwmExtendFrameIntoClientArea(Handle , ref marg);
}
private static D3D.Font font;
private static D3D.Line line;
private void dxThread()
{
form.TopMost = true;
device.SetRenderState(D3D.RenderState.ZEnable , false);
device.SetRenderState(D3D.RenderState.Lighting , false);
device.SetRenderState(D3D.RenderState.CullMode , D3D.Cull.None);
device.SetTransform(D3D.TransformState.Projection , Matrix.OrthoOffCenterLH(0 , Width , Height , 0 , 0 , 1));
device.BeginScene();
//DrawFilledBox(0 , 0 , 100 , 100 , Color.White);
//font.DrawString( null, "Swag" , 10, 10 , new Color4(Color.White));
//DrawBox(0 , 0 , 10 , 10 , 1 , Color.Green);
DrawLine(0 , 0 , Screen.PrimaryScreen.Bounds.Width , Screen.PrimaryScreen.Bounds.Height , 2 , Color.Pink);
device.EndScene();
device.Present();
}
public static void DrawFilledBox(float x , float y , float w , float h , Color Color)
{
Vector2[] vLine = new Vector2[2];
line.GLLines = true;
line.Antialias = false;
line.Width = w;
vLine[0].X = x + w / 2;
vLine[0].Y = y;
vLine[1].X = x + w / 2;
vLine[1].Y = y + h;
line.Begin();
line.Draw(vLine , new Color4(Color));
line.End();
}
public static void DrawLine(float x1 , float y1 , float x2 , float y2 , float w , Color Color)
{
Vector2[] vLine = new Vector2[2] { new Vector2(x1 , y1) , new Vector2(x2 , y2) };
line.GLLines = true;
line.Antialias = false;
line.Width = w;
line.Begin();
line.Draw(vLine , new Color4(Color));
line.End();
}
public static void DrawBox(float x , float y , float w , float h , float px , System.Drawing.Color Color)
{
DrawFilledBox(x , y + h , w , px , Color);
DrawFilledBox(x - px , y , px , h , Color);
DrawFilledBox(x , y - px , w , px , Color);
DrawFilledBox(x + w , y , px , h , Color);
}
#region Extras
private Margins marg;
//this is used to specify the boundaries of the transparent area
internal struct Margins
{
public int Left, Right, Top, Bottom;
}
[DllImport("user32.dll" , SetLastError = true)]
private static extern UInt32 GetWindowLong(IntPtr hWnd , int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd , int nIndex , IntPtr dwNewLong);
[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd , uint crKey , byte bAlpha , uint dwFlags);
public const int GWL_EXSTYLE = -20;
public const int WS_EX_LAYERED = 0x80000;
public const int WS_EX_TRANSPARENT = 0x20;
public const int LWA_ALPHA = 0x2;
public const int LWA_COLORKEY = 0x1;
[DllImport("dwmapi.dll")]
static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd , ref Margins pMargins);
#endregion
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace OverlayForm
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
using (OverLay x = new OverLay())
{
}
}
}
}
Please note :
i already saw this : Very high CPU usage directx 9
but i am using MessagePump.Run and don't know how to apply the answer.
The reason for the high CPU is that SlimDX is using PeekMessage rather than the more usual GetMessage (that the majority of Windows apps use). The former does not wait for a message to appear in the message pump unlike the latter. In other words, GetMessage() will block the current thread possibly reducing the CPU load which is what you want a well-behaved Windows desktop application to do.
MSDN:
Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval.
Unlike GetMessage, the PeekMessage function does not wait for a message to be posted before returning More...
Now a typical graceful Windows message pump looks like this:
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
...however SlimDX uses what some people refer to as an action game loop:
static bool AppStillIdle
{
get
{
Message msg;
return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
}
public void MainLoop()
{
// hook the application's idle event
Application.Idle += new EventHandler(OnApplicationIdle);
Application.Run(form);
}
void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
// Render a frame during idle time (no messages are waiting)
RenderFrame();
}
}
With nothing to draw you will experience a very tight loop of PeekMessage with no waiting in between!
My suggestion is that you either use one of the MessagePump.Run overloads for idle, or add a sleep as per below:
Change this:
void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
// Render a frame during idle time (no messages are waiting)
RenderFrame();
}
}
...to this:
void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
// Render a frame during idle time (no messages are waiting)
RenderFrame();
Thread.Sleep(0); // <------------- be graceful
}
}
Note the use of Thread.Sleep(0). This pauses for the minimal amount of time whilst still allowing the thread to be relinquished to the OS.
MSDN:
The number of milliseconds for which the thread is suspended. If the value of the millisecondsTimeout argument is zero, the thread relinquishes the remainder of its time slice to any thread of equal priority that is ready to run. If there are no other threads of equal priority that are ready to run, execution of the current thread is not suspended.
I see in your answer you already had a Thread.Sleep(50) but now it is good to know why SlimDX requires a Sleep in the first place and that 50 is perhaps too high a value.
GetMessage
OP:
i won't need a very active rendering mechanism, because i will only use this to show overlay from my music player about current song playing , next song , etc
Considering this is the case, the most CPU-efficient means is to replace your action loop with a turn-based game loop using GetMessage() instead of PeekMessage(). Then put your rendering in your application's OnIdle() callback.
As Nick Dandoulakis says in Windows Game Loop 50% CPU on Dual Core:
Nick:
That's a standard game loop for action games, where you must update objects positions / game world.
If you are making a board game GetMessage would be a better choice.
It really depends on what game you are making. More...
No Sleep() required.
the problem was that i was using full power of cpu even when not rendering, so adding
Thread.Sleep(50);
at the end of the dxThread method lowered it to only
Related
I have an application that plays a .wav file using the soundplayer, I looked it up and couldn't find a way to change the volume it plays in. What I'm looking for is either to change the volume of the file independently through the program or have a slider to change the volume of the window itself in windows volume mixer. Thanks!
public void loadSound()
{
sp.Load();
sp.Play();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (BarTimer.Value < BarTimer.Maximum)
{
BarTimer.Value = BarTimer.Value + 1;
}
if(BarTimer.Value==BarTimer.Maximum)
{
loadSound();
timer1.Stop();
BarTimer.Value = BarTimer.Minimum;
}
}
I only found this on MSDN: Attenuating SoundPlayer Volume.
It uses waveOutGetVolume and waveOutSetVolume functiuons.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace VolumeControl
{
public partial class Form1 : Form
{
[DllImport("winmm.dll")]
public static extern int waveOutGetVolume(IntPtr hwo, out uint dwVolume);
[DllImport("winmm.dll")]
public static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);
public Form1()
{
InitializeComponent();
// By the default set the volume to 0
uint CurrVol = 0;
// At this point, CurrVol gets assigned the volume
waveOutGetVolume(IntPtr.Zero, out CurrVol);
// Calculate the volume
ushort CalcVol = (ushort)(CurrVol & 0x0000ffff);
// Get the volume on a scale of 1 to 10 (to fit the trackbar)
trackWave.Value = CalcVol / (ushort.MaxValue / 10);
}
private void trackWave_Scroll(object sender, EventArgs e)
{
// Calculate the volume that's being set. BTW: this is a trackbar!
int NewVolume = ((ushort.MaxValue / 10) * trackWave.Value);
// Set the same volume for both the left and the right channels
uint NewVolumeAllChannels = (((uint)NewVolume & 0x0000ffff) | ((uint)NewVolume << 16));
// Set the volume
waveOutSetVolume(IntPtr.Zero, NewVolumeAllChannels);
}
}
}
Hope it helped.
I am working on creating a simple notebook application. I have been asked to make the input area look like a sheet of notebook paper, with the text sitting on light blue lines. I am trying to make this work, but it seems to be failing miserably.
So far, I have created a transparent RichTextBox that sits on top of a panel. The Text Box is:
using System;
using System.Windows.Forms;
public class TransparentTextBox : RichTextBox
{
public TransparentTextBox()
{
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get
{
CreateParams parms = base.CreateParams;
parms.ExStyle |= 0x20; // Turn on WS_EX_TRANSPARENT
return parms;
}
}
}
The paint code for the panel:
private void paper_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
g.DrawLine(new Pen(Brushes.LightPink, 2), 20, 0, 20, paper.Height);
int h = TextRenderer.MeasureText("Testj", txtBody.Font).Height;
for (int x = 2 + h; x < paper.Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, paper.Width, x);
}
}
The lines are static, and they will grow to fit any font size/family that is chosen. The problem is when the text box is scrolled. The lines won't move with the text. I have tried to link the handle of the scroll bar to the lines, but they don't seem to be linking properly.
The code to get the current scroll position:
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLINFO
{
public int cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
public enum ScrollBarDirection
{
SB_HORZ = 0,
SB_VERT = 1,
SB_CTL = 2,
SB_BOTH = 3
}
public enum ScrollInfoMask
{
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS
}
...
public partial class Form1 : Form
{
[DllImport("User32.dll", EntryPoint = "GetScrollInfo")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetScrollInfo([In]IntPtr hwnd, [In]int fnBar, [In, Out]ref SCROLLINFO lpsi);
...
private void txtBody_VScroll(object sender, EventArgs e)
{
inf.cbSize = Marshal.SizeOf(inf);
inf.fMask = (int)ScrollInfoMask.SIF_ALL;
GetScrollInfo(txtBody.Handle, 1, ref inf);
Console.WriteLine(inf.nTrackPos + ":" + inf.nPos + ":" + TextRenderer.MeasureText("Testj", txtBody.Font).Height);
paper.Invalidate();
}
Then the paint above was modified to use this:
for (int x = inf.nPos % h; x < paper.Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, paper.Width, x);
}
I also tried to use nTrackPos, but neither seemed to follow the text like I want it to. I'm not too familiar with C#, so I wanted to know what I am missing/could do better. I am using Visual Studio 2008, with Visual C# 2008. .Net framework 3.5 SP1
So, here is what I came up with after some intensive googling. I decided to follow more into Gusman's comment on my question and look into drawing on the textbox again. After some playing, I realized I was improperly calculating the position of the start line. So, I reconfigured my custom RichTextBox to look like:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Journal
{
class CustomRichTextBox : RichTextBox
{
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x20A;
private const int WM_PAINT = 0x00F;
private const int EM_GETSCROLLPOS = 0x4DD;
public int lineOffset = 0;
[DllImport("user32.dll")]
public static extern int SendMessage(
IntPtr hWnd,
int Msg,
IntPtr wParam,
ref Point lParam
);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
using (Graphics g = base.CreateGraphics())
{
Point p = new Point();
//get the position of the scrollbar to calculate the offset
SendMessage(this.Handle, EM_GETSCROLLPOS, IntPtr.Zero, ref p);
//draw the pink line on the side
g.DrawLine(new Pen(Brushes.LightPink, 2), 0, 0, 0, this.Height);
//determine how tall the text will be per line
int h = TextRenderer.MeasureText("Testj", this.Font).Height;
//calculate where the lines need to start
lineOffset = h - (p.Y % h);
//draw lines until there is no more box
for (int x = lineOffset; x < Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, Width, x);
}
//force the panel under us to draw itself.
Parent.Invalidate();
}
}
}
public CustomRichTextBox()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}
}
I then set this box inside of a panel to get the padding I want. The panel is forced to redraw itself with the text box.
My WPF application is exhibiting strange behavior on my two monitor laptop development system. The second monitor has a resolution of 1920 x 1080; the laptop's resolution is 1366 x 768. The laptop is running Windows 8.1 and both displays have their DPI settings set to 100%. When it is plugged in, the second monitor is the primary display. Obviously, when the second monitor is not plugged in, the laptop's display is the primary display.
The application window is always maximized but can be minimized. It cannot be dragged The problem has to do with how the window is displayed when it is moved from one monitor to the other when you plug the second monitor in or unplug it.
When the program is started with the second monitor plugged in, it moves to the laptop's display when it is unplugged. The WPF code handles this change correctly, too. That is, it detects that the original size can't fit on the new monitor so it redraws it to fit. When the second monitor is plugged back in, it moves back to the second monitor and redraws itself at the proper size for that monitor. This is exactly what I want in this scenario. The problem is when the program is started in the other configuration.
When the program is started without the second monitor plugged in, it's drawn at the proper size for the laptop's display. When the second monitor is plugged in with the program running, the window moves to the second monitor, but it is drawn wrong. Since the program is maximized, it has a huge black border surrounding it on three sides with the content displayed in an area the same size it was on the laptop's display.
Edit:
I've just finished some testing and WPF does not seem to handle resolution changes from smaller resolution to higher resolution properly. The window's behavior is identical to what I'm getting when I start the program on the laptop's display & then plug in the second monitor. At least it's consistent.
I've found that I can get notification of when the second monitor is plugged in, or of screen resolution changes, by handling the SystemEvents.DisplaySettingsChanged event. In my testing, I've found that the when the window moves from the smaller display to the larger one, that the Width, Height, ActualWidth, and ActualHeight are unchanged when the window moves to the larger window. The best I've been able to do is to get the Height & Width properties to values that match the working area of the monitor, but the ActualWidth and ActualHeight properties won't change.
How do I force the window to treat my problem case as though it were just a resolution change? Or, how do I force the window to change its ActualWidth and ActualHeight properties to the correct values?
The window descends from a class I wrote called DpiAwareWindow:
public class DpiAwareWindow : Window {
private const int LOGPIXELSX = 88;
private const int LOGPIXELSY = 90;
private const int MONITOR_DEFAULTTONEAREST = 0x00000002;
protected enum MonitorDpiType {
MDT_Effective_DPI = 0,
MDT_Angular_DPI = 1,
MDT_Raw_DPI = 2,
MDT_Default = MDT_Effective_DPI
}
public Point CurrentDpi { get; private set; }
public bool IsPerMonitorEnabled;
public Point ScaleFactor { get; private set; }
protected HwndSource source;
protected Point systemDpi;
protected Point WpfDpi { get; set; }
public DpiAwareWindow()
: base() {
// Watch for SystemEvent notifications
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
// Set up the SourceInitialized event handler
SourceInitialized += DpiAwareWindow_SourceInitialized;
}
~DpiAwareWindow() {
// Deregister our SystemEvents handler
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
}
private void DpiAwareWindow_SourceInitialized( object sender, EventArgs e ) {
source = (HwndSource) HwndSource.FromVisual( this );
source.AddHook( WindowProcedureHook );
// Determine if this application is Per Monitor DPI Aware.
IsPerMonitorEnabled = GetPerMonitorDPIAware() == ProcessDpiAwareness.Process_Per_Monitor_DPI_Aware;
// Is the window in per-monitor DPI mode?
if ( IsPerMonitorEnabled ) {
// It is. Calculate the DPI used by the System.
systemDpi = GetSystemDPI();
// Calculate the DPI used by WPF.
WpfDpi = new Point {
X = 96.0 * source.CompositionTarget.TransformToDevice.M11,
Y = 96.0 * source.CompositionTarget.TransformToDevice.M22
};
// Get the Current DPI of the monitor of the window.
CurrentDpi = GetDpiForHwnd( source.Handle );
// Calculate the scale factor used to modify window size, graphics and text.
ScaleFactor = new Point {
X = CurrentDpi.X / WpfDpi.X,
Y = CurrentDpi.Y / WpfDpi.Y
};
// Update Width and Height based on the on the current DPI of the monitor
Width = Width * ScaleFactor.X;
Height = Height * ScaleFactor.Y;
// Update graphics and text based on the current DPI of the monitor.
UpdateLayoutTransform( ScaleFactor );
}
}
protected Point GetDpiForHwnd( IntPtr hwnd ) {
IntPtr monitor = MonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST );
uint newDpiX = 96;
uint newDpiY = 96;
if ( GetDpiForMonitor( monitor, (int) MonitorDpiType.MDT_Effective_DPI, ref newDpiX, ref newDpiY ) != 0 ) {
return new Point {
X = 96.0,
Y = 96.0
};
}
return new Point {
X = (double) newDpiX,
Y = (double) newDpiY
};
}
public static ProcessDpiAwareness GetPerMonitorDPIAware() {
ProcessDpiAwareness awareness = ProcessDpiAwareness.Process_DPI_Unaware;
try {
Process curProcess = Process.GetCurrentProcess();
int result = GetProcessDpiAwareness( curProcess.Handle, ref awareness );
if ( result != 0 ) {
throw new Exception( "Unable to read process DPI level" );
}
} catch ( DllNotFoundException ) {
try {
// We're running on either Vista, Windows 7 or Windows 8. Return the correct ProcessDpiAwareness value.
awareness = IsProcessDpiAware() ? ProcessDpiAwareness.Process_System_DPI_Aware : ProcessDpiAwareness.Process_DPI_Unaware;
} catch ( EntryPointNotFoundException ) { }
} catch ( EntryPointNotFoundException ) {
try {
// We're running on either Vista, Windows 7 or Windows 8. Return the correct ProcessDpiAwareness value.
awareness = IsProcessDpiAware() ? ProcessDpiAwareness.Process_System_DPI_Aware : ProcessDpiAwareness.Process_DPI_Unaware;
} catch ( EntryPointNotFoundException ) { }
}
// Return the value in awareness.
return awareness;
}
public static Point GetSystemDPI() {
IntPtr hDC = GetDC( IntPtr.Zero );
int newDpiX = GetDeviceCaps( hDC, LOGPIXELSX );
int newDpiY = GetDeviceCaps( hDC, LOGPIXELSY );
ReleaseDC( IntPtr.Zero, hDC );
return new Point {
X = (double) newDpiX,
Y = (double) newDpiY
};
}
public void OnDPIChanged() {
ScaleFactor = new Point {
X = CurrentDpi.X / WpfDpi.X,
Y = CurrentDpi.Y / WpfDpi.Y
};
UpdateLayoutTransform( ScaleFactor );
}
public virtual void SystemEvents_DisplaySettingsChanged( object sender, EventArgs e ) {
// Get the handle for this window. Need to worry about a window that has been created by not yet displayed.
IntPtr handle = source == null ? new HwndSource( new HwndSourceParameters() ).Handle : source.Handle;
// Get the current DPI for the window we're on.
CurrentDpi = GetDpiForHwnd( handle );
// Adjust the scale factor.
ScaleFactor = new Point {
X = CurrentDpi.X / WpfDpi.X,
Y = CurrentDpi.Y / WpfDpi.Y
};
// Update the layout transform
UpdateLayoutTransform( ScaleFactor );
}
private void UpdateLayoutTransform( Point scaleFactor ) {
if ( IsPerMonitorEnabled ) {
if ( ScaleFactor.X != 1.0 || ScaleFactor.Y != 1.0 ) {
LayoutTransform = new ScaleTransform( scaleFactor.X, scaleFactor.Y );
} else {
LayoutTransform = null;
}
}
}
public virtual IntPtr WindowProcedureHook( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled ) {
// Determine which Monitor is displaying the Window
IntPtr monitor = MonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST );
// Switch on the message.
switch ( (WinMessages) msg ) {
case WinMessages.WM_DPICHANGED:
// Marshal the value in the lParam into a Rect.
RECT newDisplayRect = (RECT) Marshal.PtrToStructure( lParam, typeof( RECT ) );
// Set the Window's position & size.
Vector ul = source.CompositionTarget.TransformFromDevice.Transform( new Vector( newDisplayRect.left, newDisplayRect.top ) );
Vector hw = source.CompositionTarget.TransformFromDevice.Transform( new Vector( newDisplayRect.right = newDisplayRect.left, newDisplayRect.bottom - newDisplayRect.top ) );
Left = ul.X;
Top = ul.Y;
Width = hw.X;
Height = hw.Y;
// Remember the current DPI settings.
Point oldDpi = CurrentDpi;
// Get the new DPI settings from wParam
CurrentDpi = new Point {
X = (double) ( wParam.ToInt32() >> 16 ),
Y = (double) ( wParam.ToInt32() & 0x0000FFFF )
};
if ( oldDpi.X != CurrentDpi.X || oldDpi.Y != CurrentDpi.Y ) {
OnDPIChanged();
}
handled = true;
return IntPtr.Zero;
case WinMessages.WM_GETMINMAXINFO:
// lParam has a pointer to the MINMAXINFO structure. Marshal it into managed memory.
MINMAXINFO mmi = (MINMAXINFO) Marshal.PtrToStructure( lParam, typeof( MINMAXINFO ) );
if ( monitor != IntPtr.Zero ) {
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo( monitor, monitorInfo );
// Get the Monitor's working area
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
// Adjust the maximized size and position to fit the work area of the current monitor
mmi.ptMaxPosition.x = Math.Abs( rcWorkArea.left - rcMonitorArea.left );
mmi.ptMaxPosition.y = Math.Abs( rcWorkArea.top - rcMonitorArea.top );
mmi.ptMaxSize .x = Math.Abs( rcWorkArea.right - rcWorkArea.left );
mmi.ptMaxSize .y = Math.Abs( rcWorkArea.bottom - rcWorkArea.top );
}
// Copy our changes to the mmi object back to the original
Marshal.StructureToPtr( mmi, lParam, true );
handled = true;
return IntPtr.Zero;
default:
// Let the WPF code handle all other messages. Return 0.
return IntPtr.Zero;
}
}
[DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )]
protected static extern IntPtr GetDC( IntPtr hWnd );
[DllImport( "gdi32.dll", CallingConvention = CallingConvention.StdCall )]
protected static extern int GetDeviceCaps( IntPtr hDC, int nIndex );
[DllImport( "shcore.dll", CallingConvention = CallingConvention.StdCall )]
protected static extern int GetDpiForMonitor( IntPtr hMonitor, int dpiType, ref uint xDpi, ref uint yDpi );
[DllImport( "user32" )]
protected static extern bool GetMonitorInfo( IntPtr hMonitor, MONITORINFO lpmi );
[DllImport( "shcore.dll", CallingConvention = CallingConvention.StdCall )]
protected static extern int GetProcessDpiAwareness( IntPtr handle, ref ProcessDpiAwareness awareness );
[DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )]
protected static extern bool IsProcessDpiAware();
[DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )]
protected static extern IntPtr MonitorFromWindow( IntPtr hwnd, int flag );
[DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )]
protected static extern void ReleaseDC( IntPtr hWnd, IntPtr hDC );
}
public enum SizeMessages {
SIZE_RESTORED = 0,
SIZE_MINIMIZED = 1,
SIZE_MAXIMIZED = 2,
SIZE_MAXSHOW = 3,
SIZE_MAXHIDE = 4
}
public enum WinMessages : int {
WM_DPICHANGED = 0x02E0,
WM_GETMINMAXINFO = 0x0024,
WM_SIZE = 0x0005,
WM_WINDOWPOSCHANGING = 0x0046,
WM_WINDOWPOSCHANGED = 0x0047,
}
public enum ProcessDpiAwareness {
Process_DPI_Unaware = 0,
Process_System_DPI_Aware = 1,
Process_Per_Monitor_DPI_Aware = 2
}
I don't think that the problem is in this code; I think it's in the WPF Window class. I need to find a way to work around this problem. However, I could be wrong.
EDIT:
I have a test program which contains a normal window that descends from my DpiAwareWindow class. It is exhibiting similar behavior when the screen resolution changes. But, as a test, I changed the code so the window descended from the Window class and I did not see the behavior. So there is something in the DpiAwareWindow code that doesn't work.
If it's not too much to ask, could someone with VS 2013 download this WPF Per Monitor DPI Aware sample program, build it & see if it behaves properly when started with a lower screen resolution and then the screen resolution is increased?
Edit 2
I've just did some testing and I've found that the problem does not happen if I comment out the entire WinMessages.WM_GETMINMAXINFO case in the WindowProcedureHook method's switch statement. The purpose of this code is to limit the size of a maximized window so it does not obscure the Task Bar.
This code was added to keep a maximized window from obscuring the task bar. There seems to be some kind of interaction between what it returns and whatever logic is running in WPF when the screen resolution changes.
I've finally resolved this problem. It turns out that what I needed to do is change one line in the switch statement in the WindowProcedureHook method:
case WinMessages.WM_GETMINMAXINFO:
// lParam has a pointer to the MINMAXINFO structure. Marshal it into managed memory.
MINMAXINFO mmi = (MINMAXINFO) Marshal.PtrToStructure( lParam, typeof( MINMAXINFO ) );
if ( monitor != IntPtr.Zero ) {
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo( monitor, monitorInfo );
// Get the Monitor's working area
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
// Adjust the maximized size and position to fit the work area of the current monitor
mmi.ptMaxPosition.x = Math.Abs( rcWorkArea.left - rcMonitorArea.left );
mmi.ptMaxPosition.y = Math.Abs( rcWorkArea.top - rcMonitorArea.top );
mmi.ptMaxSize .x = Math.Abs( rcWorkArea.right - rcWorkArea.left );
mmi.ptMaxSize .y = Math.Abs( rcWorkArea.bottom - rcWorkArea.top );
}
// Copy our changes to the mmi object back to the original
Marshal.StructureToPtr( mmi, lParam, true );
handled = false; // This line used to set handled to true
return IntPtr.Zero;
With this change, the code that's normally executed in WPF when the WM_GETMINMAXINFO message is received still runs, but it uses the change to the MINMAXINFO object made by the code in order to do its work. With this change, the WPF window handles the resolution changes properly.
EDIT
And it turns out that the code no longer needs to look specifically for a screen resolution or installed monitor change. That is, the SystemEvent.DisplaySettingsChanged event handler is no longer needed.
Turns out its not a complicated fix.
The MinTrackSize point (bounds) needs to be set to the working area dimensions of the secondary monitor.
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
MINMAXINFO mmi = (MINMAXINFO)System.Runtime.InteropServices.Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
/* 0x0001 // center rect to monitor
0x0000 // clip rect to monitor
0x0002 // use monitor work area
0x0000 // use monitor entire area */
int MONITOR_DEFAULTTONEAREST = 0x00000002;
System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor != System.IntPtr.Zero)
{
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo(monitor, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
// set the maximize size of the application
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
// reset the bounds of the application to the monitor working dimensions
mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x;
mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y;
}
System.Runtime.InteropServices.Marshal.StructureToPtr(mmi, lParam, true);
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
This is some code that I picked up which I tried to implement. Its purpose is to create a form layer which is transparent, full screen, borderless, clickthrough, and always on top of other windows. It then lets you draw using directx over the top of it remaining otherwise transparent.
The parts that don't work are the click-through part, and the directx render. When I run it I basically have an invisible force field in front of all other windows and have to alt-tab around to visual studio to quickly press ALT F5 and end the debug (so at least the always on top and transparency works). I have been trying to figure out why those parts don't work, but my newbie c# skills fail me. hopefully someone can spot why and provide a modification.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Globalization;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Threading;
namespace MinimapSpy
{
public partial class Form1 : Form
{
private Margins marg;
//this is used to specify the boundaries of the transparent area
internal struct Margins
{
public int Left, Right, Top, Bottom;
}
[DllImport("user32.dll", SetLastError = true)]
private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
public const int GWL_EXSTYLE = -20;
public const int WS_EX_LAYERED = 0x80000;
public const int WS_EX_TRANSPARENT = 0x20;
public const int LWA_ALPHA = 0x2;
public const int LWA_COLORKEY = 0x1;
[DllImport("dwmapi.dll")]
static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMargins);
private Device device = null;
public Form1()
{
//Make the window's border completely transparant
SetWindowLong(this.Handle, GWL_EXSTYLE,
(IntPtr)(GetWindowLong(this.Handle, GWL_EXSTYLE) ^ WS_EX_LAYERED ^ WS_EX_TRANSPARENT));
//Set the Alpha on the Whole Window to 255 (solid)
SetLayeredWindowAttributes(this.Handle, 0, 255, LWA_ALPHA);
//Init DirectX
//This initializes the DirectX device. It needs to be done once.
//The alpha channel in the backbuffer is critical.
PresentParameters presentParameters = new PresentParameters();
presentParameters.Windowed = true;
presentParameters.SwapEffect = SwapEffect.Discard;
presentParameters.BackBufferFormat = Format.A8R8G8B8;
this.device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParameters);
Thread dx = new Thread(new ThreadStart(this.dxThread));
dx.IsBackground = true;
dx.Start();
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
//Create a margin (the whole form)
marg.Left = 0;
marg.Top = 0;
marg.Right = this.Width;
marg.Bottom = this.Height;
//Expand the Aero Glass Effect Border to the WHOLE form.
// since we have already had the border invisible we now
// have a completely invisible window - apart from the DirectX
// renders NOT in black.
DwmExtendFrameIntoClientArea(this.Handle, ref marg);
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void dxThread()
{
while (true)
{
//Place your update logic here
device.Clear(ClearFlags.Target, Color.FromArgb(0, 0, 0, 0), 1.0f, 0);
device.RenderState.ZBufferEnable = false;
device.RenderState.Lighting = false;
device.RenderState.CullMode = Cull.None;
device.Transform.Projection = Matrix.OrthoOffCenterLH(0, this.Width, this.Height, 0, 0, 1);
device.BeginScene();
//Place your rendering logic here
device.EndScene();
//device.Present();
}
this.device.Dispose();
Application.Exit();
}
}
Here's a refined full sample code for making a window topmost - click through - transparent (= alpha blended). The sample makes a rotating color wheel which is rendered with DirectX, or actually with XNA 4.0, because I believe Microsoft has discontinued developing the managed directx and favours XNA today.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework.Graphics;
namespace ClickThroughXNA
{
public partial class Form1 : Form
{
// Directx graphics device
GraphicsDevice dev = null;
BasicEffect effect = null;
// Wheel vertexes
VertexPositionColor[] v = new VertexPositionColor[100];
// Wheel rotation
float rot = 0;
public Form1()
{
InitializeComponent();
StartPosition = FormStartPosition.CenterScreen;
Size = new System.Drawing.Size(500, 500);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; // no borders
TopMost = true; // make the form always on top
Visible = true; // Important! if this isn't set, then the form is not shown at all
// Set the form click-through
int initialStyle = GetWindowLong(this.Handle, -20);
SetWindowLong(this.Handle, -20, initialStyle | 0x80000 | 0x20);
// Create device presentation parameters
PresentationParameters p = new PresentationParameters();
p.IsFullScreen = false;
p.DeviceWindowHandle = this.Handle;
p.BackBufferFormat = SurfaceFormat.Vector4;
p.PresentationInterval = PresentInterval.One;
// Create XNA graphics device
dev = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, p);
// Init basic effect
effect = new BasicEffect(dev);
// Extend aero glass style on form init
OnResize(null);
}
protected override void OnResize(EventArgs e)
{
int[] margins = new int[] { 0, 0, Width, Height };
// Extend aero glass style to whole form
DwmExtendFrameIntoClientArea(this.Handle, ref margins);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// do nothing here to stop window normal background painting
}
protected override void OnPaint(PaintEventArgs e)
{
// Clear device with fully transparent black
dev.Clear(new Microsoft.Xna.Framework.Color(0, 0, 0, 0.0f));
// Rotate wheel a bit
rot+=0.1f;
// Make the wheel vertexes and colors for vertexes
for (int i = 0; i < v.Length; i++)
{
if (i % 3 == 1)
v[i].Position = new Microsoft.Xna.Framework.Vector3((float)Math.Sin((i + rot) * (Math.PI * 2f / (float)v.Length)), (float)Math.Cos((i + rot) * (Math.PI * 2f / (float)v.Length)), 0);
else if (i % 3 == 2)
v[i].Position = new Microsoft.Xna.Framework.Vector3((float)Math.Sin((i + 2 + rot) * (Math.PI * 2f / (float)v.Length)), (float)Math.Cos((i + 2 + rot) * (Math.PI * 2f / (float)v.Length)), 0);
v[i].Color = new Microsoft.Xna.Framework.Color(1 - (i / (float)v.Length), i / (float)v.Length, 0, i / (float)v.Length);
}
// Enable position colored vertex rendering
effect.VertexColorEnabled = true;
foreach (EffectPass pass in effect.CurrentTechnique.Passes) pass.Apply();
// Draw the primitives (the wheel)
dev.DrawUserPrimitives(PrimitiveType.TriangleList, v, 0, v.Length / 3, VertexPositionColor.VertexDeclaration);
// Present the device contents into form
dev.Present();
// Redraw immediatily
Invalidate();
}
[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);
[DllImport("dwmapi.dll")]
static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref int[] pMargins);
}
}
A little extension/modification to Jaska's code, which the form is transparent
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.TopMost = true; // make the form always on top
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; // hidden border
this.WindowState = FormWindowState.Maximized; // maximized
this.MinimizeBox = this.MaximizeBox = false; // not allowed to be minimized
this.MinimumSize = this.MaximumSize = this.Size; // not allowed to be resized
this.TransparencyKey = this.BackColor = Color.Red; // the color key to transparent, choose a color that you don't use
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
// Set the form click-through
cp.ExStyle |= 0x80000 /* WS_EX_LAYERED */ | 0x20 /* WS_EX_TRANSPARENT */;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// draw what you want
e.Graphics.FillEllipse(Brushes.Blue, 30, 30, 100, 100);
}
}
Change your extended window style to only WS_EX_LAYERED, window style to only WS_POPUP (NO WS_SIZEBOX) and make sure to use DwmExtendFrameIntoClientArea with all -1's and this will produce transparent windows with layered support: downside is you need to bltbit with GDI from an offscreen directx rendering. Not optimal but it works. This gives mouse click throughs + directx rendering + transparency. Downside is you'll need to inform GDI anytime, pull the directx buffer (all of it or just the damaged portions) and write them to the screem with bltbit.
Setting the extended window style to WS_EX_COMPOSITED and DwmExtendedFrameIntoClientArea with all -1's (similar as above, WS_POPUP on the regular window style). This you can run directx from but no mouse clickthroughs. You can at this point define irregular paths for the hit mask and pass it to windows, its not perfect but if you know a general (non regular) area that can pass-through it'll work.
Still trying to find a true way of using opengl/directx on mac or windows platforms that can pass through mouse clicks with out having to do a bitblt to a legacy rendering system.
I have a simple way use TransparentKey property and a 1x1 pixel label with the color of Form TransparentKey.
On Form and all control MouseMouse event. Set label position to Mouse location.
private void MoveHole()
{
var newLocation = PointToClient(MousePosition);
lblHole.Location = newLocation;
}
I have a Panel In my C# form and I have a button. When I click on the Button the invisible Panel Shows. Instead of that I want the Panel to move in or slide in.
For example when you click on a combobox the dropdown list doesnt just pop in. I want my Panel to appear like that. How can I do that ?
Window animation is a built-in feature for Windows. Here's a class that uses it:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public static class Util {
public enum Effect { Roll, Slide, Center, Blend }
public static void Animate(Control ctl, Effect effect, int msec, int angle) {
int flags = effmap[(int)effect];
if (ctl.Visible) { flags |= 0x10000; angle += 180; }
else {
if (ctl.TopLevelControl == ctl) flags |= 0x20000;
else if (effect == Effect.Blend) throw new ArgumentException();
}
flags |= dirmap[(angle % 360) / 45];
bool ok = AnimateWindow(ctl.Handle, msec, flags);
if (!ok) throw new Exception("Animation failed");
ctl.Visible = !ctl.Visible;
}
private static int[] dirmap = { 1, 5, 4, 6, 2, 10, 8, 9 };
private static int[] effmap = { 0, 0x40000, 0x10, 0x80000 };
[DllImport("user32.dll")]
private static extern bool AnimateWindow(IntPtr handle, int msec, int flags);
}
Sample usage:
private void button2_Click(object sender, EventArgs e) {
Util.Animate(button1, Util.Effect.Slide, 150, 180);
}
If you are using .NET 4 (if not replace Task with Thread), a function similar to this could be a start:
private void slideToDestination(Control destination, Control control, int delay, Action onFinish)
{
new Task(() =>
{
int directionX = destination.Left > control.Left ? 1 : -1;
int directionY = destination.Bottom > control.Top ? 1 : -1;
while (control.Left != destination.Left || control.Top != destination.Bottom)
{
try
{
if (control.Left != destination.Left)
{
this.Invoke((Action)delegate()
{
control.Left += directionX;
});
}
if (control.Top != destination.Bottom)
{
this.Invoke((Action)delegate()
{
control.Top += directionY;
});
}
Thread.Sleep(delay);
}
catch
{
// form could be disposed
break;
}
}
if (onFinish != null) onFinish();
}).Start();
}
Usage:
slideToDestination(sender as Control, panel1, 10, () => MessageBox.Show("Done!"));
slideToDestination(sender as Control, panel1, 0, null);
As action you would send some boolean variable to set to true so that you know that the animation has finished or some code to run after it. Beware of deadlocks when calling with a null action. You could run two animations on the same control in two different directions with the same speed, and it will stay where it was forever and of course two animations simultaneusly can make the control go infinitely in some direction because the while will never finish :)
Check out the library I wrote last year:
WinForm Animation Library [.Net3.5+]
A simple library for animating controls/values in .Net WinForm (.Net
3.5 and later). Key frame (Path) based and fully customizable.
https://falahati.github.io/WinFormAnimation/
new Animator2D(
new Path2D(new Float2D(-100, -100), c_control.Location.ToFloat2D(), 500))
.Play(c_control, Animator2D.KnownProperties.Location);
This moves the c_control control from -100, -100 to the location it was in first place in 500 ms.
For WinForms, you could start with the Panel location being off screen.
Employ a Timer, and in the Tick event, shift the Panel's location slowly into view until it is at your predefined coordinates.
Lots of ways to skin a cat, but this is how I'd do it.