I'm trying to make a tooltip for my video player. I use a windows media player embedded into my winform's app on c# (AxWMPLib.AxWindowsMediaPlayer) for playing video. And I have created a control that shows a current position of a media. This control is transparent. Control's code is below:
namespace player.Controls
{
public partial class TransparentToolTip : System.Windows.Forms.UserControl
{
public enum PointerLocation : byte { ... }
#region Private data
// ...
#endregion
Timer Wriggler = new Timer();
int iInterval = 100;
protected void TickHandler(object sender, EventArgs e)
{
this.InvalidateEx();
}
private void _SetStyle()
{
this.SetStyle((ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.CacheText | ControlStyles.ContainerControl), true);
this.SetStyle(ControlStyles.Selectable, false);
this.SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.Opaque, true);
this.UpdateStyles();
}
private void _SetTimer(int _Interval)
{
Wriggler.Tick += new EventHandler(TickHandler);
this.Wriggler.Interval = _Interval;
this.Wriggler.Enabled = true;
}
public TransparentToolTip()
{
InitializeComponent();
_SetStyle();
_SetTimer(iInterval);
}
public TransparentToolTip(System.ComponentModel.IContainer container)
{
container.Add(this);
InitializeComponent();
_SetStyle();
_SetTimer(iInterval);
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= (0x00000020 | 0x00000008); // WS_EX_TRANSPARENT = 0x00000020, WS_EX_TOPMOST = 0x00000008
return cp;
}
}
#region Extra Properties
// ...
#endregion
// Drawing
protected void InvalidateEx()
{
if (Parent == null)
return;
Rectangle rc = new Rectangle(this.Location, this.Size);
Parent.Invalidate(rc, true);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
pe.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
pe.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
pe.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
Rectangle rect = new Rectangle(this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);
SolidBrush brushBackground = new SolidBrush(ColorBackground);
SolidBrush brushBorder = new SolidBrush(ColorBorder);
using (GraphicsPath graphicsPath = ToolTipBody(...))
{
using (Pen p = new Pen(brushBorder, BorderSize))
{
pe.Graphics.FillPath(brushBackground, graphicsPath); // background
pe.Graphics.DrawPath(p, graphicsPath); // borders
}
}
TextFormatFlags flags = // some flags;
TextRenderer.DrawText(pe.Graphics, ToolTipText, Font, new Rectangle(this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width, this.ClientRectangle.Height - TriangleSizeSide), ToolTipColor, System.Drawing.Color.Transparent, flags);
brushBorder.Dispose();
brushBackground.Dispose();
base.OnPaint(pe);
}
// Form mapping tips
private GraphicsPath ToolTipBody(...)
{
// some code
return graphicsPath;
}
}
I try to show this tooltip over the axWindowsMediaPlayer object. But my control is overlapped by the media player. I've tried to use SetWindowPos but this does not work:
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
const int SWP_NOSIZE = 0x0001;
const int SWP_NOMOVE = 0x0002;
const int SWP_SHOWWINDOW = 0x0040;
//...
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int flags);
//...
SetWindowPos(transparentToolTip1.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
Note: If do not change CreateParams of a new control it draws over the media player. But it becomes opaque.
Is any ideas to do it correct using winforms?
My guess is that the transparent thing is happening because you're defining the control style as ControlStyles.Opaque. Then, setting the flag to WS_EX_TRANSPARENT you're setting it to transparent style again.
About the overlapped issue: If you designed the form using the Visual Studio Windows Forms designer, maybe your control is zOrdered down the AxWMPLib.AxWindowsMediaPlayer component. Try to bring to front or control the ChildrenIndex in your control collection before show it.
Hope it helps.
Related
I have a custom TextBox in which I draw some place holder text when it's empty.
It works pretty well, but it flickers when the mouse enters and leaves the TextBox. It seems related to the border becoming blue when the mouse hovers the control (I'm on Windows 8.1).
Any idea how I could fix this ?
I've tried various SetStyles flags without success.
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center
};
private Font mPlaceHolderFont;
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0F)
{
if (string.IsNullOrEmpty(Text) && !Focused)
{
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var rect = new RectangleF(2, 2, Width - 4, Height - 4);
g.FillRectangle(Brushes.White, rect);
g.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
}
}
}
}
I had other problems with overriding OnPaint. Here is the best solution I came up with :
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near
};
private Font mPlaceHolderFont;
private Brush mForegroundBrush;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var bounds = new Rectangle(-2, -2, Width, Height);
var rect = new RectangleF(1, 0, Width - 2, Height - 2);
e.Graphics.FillRectangle(Brushes.White, rect);
if (string.IsNullOrEmpty(Text) && !Focused)
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
if (mForegroundBrush == null)
mForegroundBrush = new SolidBrush(ForeColor);
e.Graphics.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
else
{
var flags = TextFormatFlags.Default | TextFormatFlags.TextBoxControl;
if (!Multiline)
flags |= TextFormatFlags.SingleLine | TextFormatFlags.NoPadding;
TextBoxRenderer.DrawTextBox(e.Graphics, bounds, Text, Font, flags, TextBoxState.Selected);
}
}
}
Is there a special reason for using WM_PAINT instead of OnPaint? In WM_PAINT you obtain a drawing context from the handle, which is always a direct access to the control. In OnPaint you already have a Graphics in the event args, which can be either a buffer or a direct context, depending on the styles.
You mentioned that you have tried a few styles with no success. Firstly I would say try these and move your paint logic into OnPaint:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
If it does not work (a focused control may behave strangely in Windows) and you must stick to WM_PAINT, then create a buffer manually. Your original code draws a white rectangle first, then some text, which causes flickering. You can avoid this by using a buffer:
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
// creating a buffered context
using (BufferedGraphicsContext context = new BufferedGraphicsContext())
{
// creating a buffer for the original Graphics
using (BufferedGraphics bg = context.Allocate(e.Graphics, ClientRectangle))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var gBuf = bg.Graphics;
var rect = ClientRectangle;
rect.Inflate(-1, -1);
gBuf.FillRectangle(Brushes.White, rect);
gBuf.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
// copying the buffer onto the original Graphics
bg.Render(e.Graphics);
}
}
}
What I am trying to do is just set my from, that has a picture box on it, to be the top most object on my computer and stay that way. I have tried to use f.topmost = true; but if when I click on something my form is no longer top most.
I am running my form from a dll (code of dll is below). I have also tried to add in a on load even to my form, which also did nothing.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace pic
{
public class Class1
{
[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;
Form f = new Form();
public void t(int LocalX, int LocalY, string PicLocal, byte transparency)
{
f.Load += new EventHandler(ProgramViwer_Load);
Bitmap bitmap;
// Form f = new Form();
f.BackColor = Color.White;
f.FormBorderStyle = FormBorderStyle.None;
f.Bounds = Screen.PrimaryScreen.Bounds;
bitmap = new Bitmap(PicLocal);
f.Size = new Size(bitmap.Size.Width, bitmap.Size.Height);
f.StartPosition = FormStartPosition.Manual;
f.SetDesktopLocation(LocalX, LocalY);
Application.EnableVisualStyles();
PictureBox PictureBox1 = new PictureBox();
PictureBox1.Location = new System.Drawing.Point(0, 0);
PictureBox1.Image = bitmap;
PictureBox1.Size = new System.Drawing.Size(bitmap.Size.Width, bitmap.Size.Height);
PictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
f.Controls.Add(PictureBox1);
f.AllowTransparency = true;
SetWindowLong(f.Handle, GWL_EXSTYLE,
(IntPtr)(GetWindowLong(f.Handle, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT));
// set transparency to 50% (128)
SetLayeredWindowAttributes(f.Handle, 0, transparency, LWA_ALPHA);
Color BackColor = Color.White;
f.TransparencyKey = BackColor;
f.Opacity = transparency / 255f;
Application.Run(f);
}
private void ProgramViwer_Load(object sender, EventArgs e)
{
f.TopMost = true;
}
}
}
Your problem is that other windows set their 'TopMost' property to true as well. It's not the cleanest solution but it should work.
new Thread(() =>
{
try
{
while (true)
{
this.TopMost = true;
Thread.Sleep(1);
}
}
catch (Exception ex) { }
}).Start();
I like to have my form borderless in C#. So I used this code:
FormBorderStyle = FormBorderStyle.None;
But it removes the aero effect of windows 8. The form opens suddenly like a blink.
How can i bring the aero effect back?
I am answering my own question using the C++ reference
I wrote a class inherited from Form Class. Each form in application should be inherited from this class instead of Form Class.
public class AeroForm : Form
{
int _w = 100, _h = 100;
bool aero = false;
[StructLayout(LayoutKind.Sequential)]
struct MARGINS { public int Left, Right, Top, Bottom; }
[DllImport("dwmapi.dll", PreserveSig = false)]
static extern void DwmExtendFrameIntoClientArea
(IntPtr hwnd, ref MARGINS margins);
[DllImport("dwmapi.dll", PreserveSig = false)]
static extern bool DwmIsCompositionEnabled();
public AeroForm()
{
aero = IsCompositionEnabled();
}
public AeroForm(int width, int height)
: this()
{
_w = width;
_h = height;
Size = new Size(width, height);
}
protected override void WndProc(ref Message m)
{
const int WM_NCCALCSIZE = 0x0083;
switch (m.Msg)
{
case WM_NCCALCSIZE:
if (aero)
return;
break;
}
base.WndProc(ref m);
}
//this is for checking the OS's functionality.
//Windows XP does not have dwmapi.dll
//also, This corrupts the designer...
//so i used the Release/Debug configuration
bool IsCompositionEnabled()
{
#if !DEBUG
return File.Exists(Environment.SystemDirectory + "\\dwmapi.dll")
&& DwmIsCompositionEnabled();
#else
return false;
#endif
}
//this one is used for a shadow when aero is not available
protected override CreateParams CreateParams
{
get
{
const int CS_DROPSHADOW = 0x00020000;
const int WS_MINIMIZEBOX = 0x20000;
const int CS_DBLCLKS = 0x8;
CreateParams cp = base.CreateParams;
if (!aero)
cp.ClassStyle |= CS_DROPSHADOW;
cp.Style |= WS_MINIMIZEBOX;
cp.ClassStyle |= CS_DBLCLKS;
return cp;
}
}
//this is for aero shadow and border configurations
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (aero)
{
FormBorderStyle = FormBorderStyle.FixedSingle;
ControlBox = false;
MinimizeBox = false;
MaximizeBox = false;
Size = new Size(_w, _h);
MARGINS _glassMargins = new MARGINS()
{
Top = 5,
Left = 5,
Bottom = 5,
Right = 5
};
DwmExtendFrameIntoClientArea(this.Handle, ref _glassMargins);
}
else
FormBorderStyle = FormBorderStyle.None;
}
//When you minimize and restore, the size will change.
//this override is for preventing this unwanted resize.
protected override void OnPaint(PaintEventArgs e)
{
if (aero)
Size = new Size(_w, _h);
base.OnPaint(e);
}
}
with Show() and Application.Run() it works great!
but it has some regressions when the form opens with ShowDialog(). Closing the form, you will see a border that closes after your client content hides.
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.
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;
}