Is there a way to mimic ctrl+alt+tab functionality with WPF? - c#

I'm looking for a way to add a preview of currently active windows (using their window handler) with images and live updates.
Similar to Windows ctrl+alt+tab which basically streams all the active windows or shows a cached version of the non-active windows.
Is there a way to achieve that using WPF? should I use another UI framework for such a requirement?
At the moment I found a way to screenshot an active non-focused window using user32 api -
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr handle, ref Rectangle rect);
var _clientHandler = Process.GetProcessesByName(PROCESSNAME)[0].MainWindowHandle;
CaptureWindow(_clientHandler);
void CaptureWindow(IntPtr handle)
{
// Get the size of the window to capture
Rectangle rect = new Rectangle();
GetWindowRect(handle, ref rect);
// GetWindowRect returns Top/Left and Bottom/Right, so fix it
rect.Width = rect.Width - rect.X;
rect.Height = rect.Height - rect.Y;
// Create a bitmap to draw the capture into
using (Bitmap bitmap = new Bitmap(rect.Width, rect.Height))
{
// Use PrintWindow to draw the window into our bitmap
using (Graphics g = Graphics.FromImage(bitmap))
{
IntPtr hdc = g.GetHdc();
if (!PrintWindow(handle, hdc, 0))
{
int error = Marshal.GetLastWin32Error();
var exception = new System.ComponentModel.Win32Exception(error);
Debug.WriteLine("ERROR: " + error + ": " + exception.Message);
// TODO: Throw the exception?
}
g.ReleaseHdc(hdc);
}
// Save it as a .png just to demo this
bitmap.Save("Example.png");
}
}
I guess I can repeat that process for each window 30 times a second (or with w/e fps I'll want).
Would that be the correct approach? is there a better way to achieve that?
I'll appreciate any guidance, ty.

Related

How to get the correct position of a Windows Form relative to the display?

I have searched for hours and tried many things.
I have a transparent Windows Form and I am trying to capture the screen within the bounds of that form.
For whatever reason, I am unable to get the correct form area...
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr ptr);
[DllImport("user32.dll")]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
private void LocationChanged(object sender, EventArgs e)
{
var position = form.PointToScreen(Point.Empty);
bounds = new Rectangle(position, new Size(50, 50));
DrawRectangle(bounds);
}
void DrawRectangle(Rectangle rect)
{
IntPtr desktopPtr = GetDC(IntPtr.Zero);
Graphics g = Graphics.FromHdc(desktopPtr);
SolidBrush b = new SolidBrush(Color.Green);
g.FillRectangle(b, rect);
g.Dispose();
ReleaseDC(IntPtr.Zero, desktopPtr);
}
This is the result:
The green square should completely cover the form with the red border on the left.
I've tried different monitors, I've made sure scaling is 100% (what to do if it is not?), I'e tried passing the form.Location to form.PointToScreen
What am I doing wrong?
You can try setting the property form.StartPosition = FormPosition.Manual
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.form.startposition?view=netframework-4.7.2

WPF - How to create a tile map editor

I'm trying to build a map editor like this one: http://blog.rpgmakerweb.com/wp-content/uploads/2012/04/T2EditorLabelled.png in WPF.
How should it work:
By selecting a specific tile on the "tile list" - B section - is possible to draw that tile on the canvas - section A.
In the end the final result is a full game level drawn on the canvas.
First approach:
In my first approach each tile is drawn by creating a new image control and adding it to the canvas (a WPF canvas control).
Steps:
Select the tile from the tileset
Catch the click event on the canvas
Create a new image, cropping the tile from the tileset
Adding the image in the right spot on the canvas (as a child)
This method is quite naive and it imply two big issues:
All the pixels are already buffered on the tileset that contains all the tiles, but each time I draw a tile on the canvas a new image is create and by doing so I am obliged to replicate part of the tileset pixel data as a source for the new
Too many controls on the canvas:
A map game can reach the size of 1000 tiles x 1000 tiles, WPF start to have sensible decay of performance on a 100x100 tile map.
So create a control image for each tile is an unfeasible solution.
Second approach:
My second approach contemplate the use of a single big WriteableBitmap as canvas background.
As in the previous approach the tile is selected on the tileset and the draw event is a click on the canvas.
In this case though no new image is created ex-novo but the background WriteableBitmap is modified accordingly.
So in the number of controls is reduced drastically since all the draw mechanism is performed on the WriteableBitmap.
The main problem with this approach is that if I want to create a large map with 1k x 1k tiles with a tile of 32x32, the size of the background image would be astronomical.
I wonder if there is a way to develop a good solution of this problem in WPF.
How would you address this development problem?
There a variety of different ways you could approach this problem to increase performance.
In terms of the rendering of images, WPF by default isn't amazing, so you could;
Use GDI's BitBlt to quickly render images to a WinForms control which can be hosted. The benefit of this is that GDI is software and hence does not require a graphics card or anything. (WPF fast method to draw image in UI)
You can use D3DImage as an image source. This would mean that you can use the D3DImage as the canvas to which to draw. Doing this would mean you would have to render all the tiles to the D3DImage image source using Direct3D, which would be much faster as it is hardware accelerated. (https://www.codeproject.com/Articles/28526/Introduction-to-D3DImage)
You may be able to host XNA through a WinForms control and rendering using that, I have no experience with this, so I test to any performance. (WPF vs XNA to render thousands of sprites)
Personally, for rendering, I would use the GDI method as it is software based, relatively easy to set up and I have had experience with it, and seen it's performance.
Additionally, when rendering the tiles to a control, you can use the scrollbar positions and control size to determine the area of your map which is actually visible. From this, you can simply select those few tiles and only render them, saving a lot of time.
Furthermore, when you manage it yourself, you can simply load the different sprites into memory and then use that same memory to draw it in different locations onto a buffer image. This would decrease that memory issue you mentioned.
Below is my example code for the GDI method, I render 2500 32x32 pixel sprites (all of which are the same green color, however you'd set this memory to an actual sprite - src memory). The sprites are bitblit to a buffer image (srcb memory) which is then bitblit to the window, in your case, you'd bitblit the buffer image to a winforms canvas or something. With this, I get between 30 and 40 fps on my base model Surface Pro 3. This should be enough performance for a level editor's rendering. Please note this code is very crude and just roughly outlines the process, it can almost certainly be improved upon.
//
// GDI DLL IMPORT
//
[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteDC(IntPtr hDC);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern bool BitBlt(IntPtr hDC, int x, int y, int width, int height, IntPtr hDCSource, int sourceX, int sourceY, uint type);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern bool FillRgn(IntPtr hdc, IntPtr hrgn, IntPtr hbr);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr CreateSolidBrush(uint crColor);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
public const uint SRCCOPY = 0x00CC0020; // dest = source
public const uint SRCPAINT = 0x00EE0086; // dest = source OR dest
public const uint SRCAND = 0x008800C6; // dest = source AND dest
public const uint SRCINVERT = 0x00660046; // dest = source XOR dest
public const uint SRCERASE = 0x00440328; // dest = source AND (NOT dest )
public const uint NOTSRCCOPY = 0x00330008; // dest = (NOT source)
public const uint NOTSRCERASE = 0x001100A6; // dest = (NOT src) AND (NOT dest)
public const uint MERGECOPY = 0x00C000CA; // dest = (source AND pattern)
public const uint MERGEPAINT = 0x00BB0226; // dest = (NOT source) OR dest
public const uint PATCOPY = 0x00F00021; // dest = pattern
public const uint PATPAINT = 0x00FB0A09; // dest = DPSnoo
public const uint PATINVERT = 0x005A0049; // dest = pattern XOR dest
public const uint DSTINVERT = 0x00550009; // dest = (NOT dest)
public const uint BLACKNESS = 0x00000042; // dest = BLACK
public const uint WHITENESS = 0x00FF0062; // dest = WHITE
//
// END DLL IMPORT
//
//GDI Graphics
private Graphics g;
//Colors
private const int BACKGROUND_COLOR = 0xffffff;
private const int GRAPH_COLOR_ONE = 0x00FF00;
//Pointers
IntPtr hdc;
IntPtr srcb;
IntPtr dchb;
IntPtr origb;
IntPtr src;
IntPtr dch;
IntPtr orig;
//Brushes
IntPtr brush_one;
IntPtr brush_back;
public Form1()
{
InitializeComponent();
//Create a graphics engine from the window
g = Graphics.FromHwnd(this.Handle);
//Get the handle of the Window's graphics and then create a compatible source handle
hdc = g.GetHdc();
srcb = CreateCompatibleDC(hdc);
src = CreateCompatibleDC(hdc);
//Get the handle of a new compatible bitmap object and map it using the source handle to produce a handle to the actual source
dchb = CreateCompatibleBitmap(hdc, ClientRectangle.Width, ClientRectangle.Height);
origb = SelectObject(srcb, dchb);
//Get the handle of a new compatible bitmap object and map it using the source handle to produce a handle to the actual source
dch = CreateCompatibleBitmap(hdc, 32, 32);
orig = SelectObject(src, dch);
//Create the burshes
brush_one = CreateSolidBrush(GRAPH_COLOR_ONE);
brush_back = CreateSolidBrush(BACKGROUND_COLOR);
//Create Image
FillRectangle(brush_one, src, 0, 0, 32, 32);
//Fill Background
FillRectangle(brush_back, hdc, 0, 0, ClientRectangle.Width, ClientRectangle.Height);
this.Show();
Render();
}
private void Render()
{
Stopwatch s = new Stopwatch();
s.Start();
int frames = 0;
while(frames <= 30)
{
frames++;
FillRectangle(brush_back, srcb, 0, 0, ClientRectangle.Width, ClientRectangle.Height);
for (int i = 0; i < 50; i++)
for (int j = 0; j < 50; j++)
BlitBitmap(i * 5, j * 5, 32, 32, srcb, src);
BlitBitmap(0, 0, ClientRectangle.Width, ClientRectangle.Height, hdc, srcb);
}
s.Stop();
float fps = (float)frames / ((float)s.ElapsedMilliseconds / 1000.0f);
MessageBox.Show(Math.Round(fps, 2).ToString(), "FPS");
}
private void FillRectangle(IntPtr b, IntPtr hdc, int x, int y, int w, int h)
{
//Create the region
IntPtr r = CreateRectRgn(x, y, x + w, y + h);
//Fill the region using the specified brush
FillRgn(hdc, r, b);
//Delete the region object
DeleteObject(r);
}
private void BlitBitmap(int x, int y, int w, int h, IntPtr to, IntPtr from)
{
//Blit the bits of the actual source object to the window, using its handle
BitBlt(to, x, y, w, h, from, 0, 0, SRCCOPY);
}

C# : Application in which monitor?

There are two monitors and an application (e.g. IE) is currently active/displayed in the 2nd monitor. How do I detect if an application is in 1st or 2nd monitor.
I need to know this - to show a userform just on top of the application irrespective of the monitor in which it is. I know what the WindowText (Title) would be (if that helps).
Right now I just show my form near the system tray but would like to show it on top of the application.
// FORM POSITION
this.StartPosition = FormStartPosition.Manual;
Rectangle workArea = Screen.PrimaryScreen.WorkingArea;
int left = workArea.Width - this.Width;
int top = workArea.Height - this.Height;
this.Location = new Point(left, top);
This was converted from VB but should work.
The Test() method shows how you would use it.
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);
public static RECT GetWindowLocationByName(string name)
{
IntPtr handle = FindWindow(default(string), name);
RECT result = default(RECT);
GetWindowRect(handle, ref result);
return result;
}
public static void Test()
{
dynamic location = GetWindowLocationByName("Untitled - Notepad");
Screen result = null;
foreach (Screen s in Screen.AllScreens) {
if (s.WorkingArea.IntersectsWith(new Rectangle(location.Left, location.Top, location.Right - location.Left, location.Bottom - location.Top))) {
result = s;
}
}
}
Edit: More Info
Step 1: Get the window handle
Step 2: Get the window rect (location/size)
Step 3: Determin which monitor the window resides on
If your looking at overlyaying one window on top of another you don't actually need to know which monitor it is on just the position and size of the window relative to the desktop. In both windows forms and WPF when you set the window location X / Left is the distance in pixels from the left side of the left most monitor. E.g if you have two montior 1024 pixels wide setting X / Left to 2000 will put the window 86 pixels into right hand monitor.
To get the windows position of another process you could use the library mentioned here
How to get and set the window position of another application in C#
You should then be able to check on which screen the position of the rectangle is
private Screen IsVisibleOnScreen(Rectangle rect)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.WorkingArea.IntersectsWith(rect))
{
return Screen;
}
}
return null;
}

How to perform smooth image zoom and pan?

I am writing a program that displays a map, and on top of it another layer where position of cameras and their viewing direction is shown. The map itself can be zoomed and panned. The problem is that the map files are of significant size and zooming does not go smoothly.
I created class ZoomablePictureBox : PictureBox to add zooming and panning ability. I tried different methods, from this and other forums, for zooming and panning and ended up with the following, firing on the OnPaint event of ZoomablePictureBox:
private void DrawImgZoomed(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
if (imgZoomed != null)
e.Graphics.DrawImage(imgZoomed, new Rectangle(-ShiftX, -ShiftY, imgZoomed.Width, imgZoomed.Height), 0, 0, imgZoomed.Width, imgZoomed.Height, GraphicsUnit.Pixel);
}
Where ShiftX and ShiftY provide proper map panning (calculation irrelevant for this problem).
imgZoomed is zoomed version of original map calculated in BackgroundWorker everytime the zoom changes:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
Bitmap workerImage = e.Argument as Bitmap;
Bitmap result;
result = new Bitmap(workerImage, new Size((int)(workerImage.Width * Zoom), (int)(workerImage.Height * Zoom)));
e.Result = result;
}
So current approach is, that everytime user scrolls mousewheel, the new imgZoomed is calculated based on current Zoom. With map size of ~30 MB this can take up to 0,5 second which is annoying, but panning runs smoothly.
I realize that this may not be the best idea. In previous approach i did not create zoomed image copy everytime mouse is scrolled but did this instead:
e.Graphics.DrawImage(Image, new Rectangle(-ShiftX, -ShiftY, (int)(this._image.Width * Zoom), (int)(this._image.Height * Zoom)), 0, 0, Image.Width, Image.Height, GraphicsUnit.Pixel);
Zoom was much smoother, because from what i understand, it just stretched original image. On the other hand panning was skipping heavily.
I wast thinking of:
creating copies of orignial map for each zoom in memory/on hard drive - it will take up too much memory/hdd space
creating copies of orignial map for next/actual/previous zooms so i
have more time to calculate next step - it will not help if user
scrolls more than one step at a time
I also tried matrix transformations - no real performance gain, and calculating pan was a real pain in the arse.
I'm running in circles here and don't know how to do it. If i open the map in default Windows picture viewer zooming and panning is smooth. How do they do that?
In what way do I achieve smooth zooming and panning at the same time?
Sorry this is so long, I left in only the essential parts.
Most of the p/invoke stuff is taken from pinvoke.net
My solution for smooth panning across large images involves using the StretchBlt gdi method instead of Graphics.DrawImage. I've created a static class that groups all the native blitting operations.
Another thing that helps greatly is caching the HBitmap and Graphics objects of the Bitmap.
public class ZoomPanWindow
{
private Bitmap map;
private Graphics bmpGfx;
private IntPtr hBitmap;
public Bitmap Map
{
get { return map; }
set
{
if (map != value)
{
map = value;
//dispose/delete any previous caches
if (bmpGfx != null) bmpGfx.Dispose();
if (hBitmap != null) StretchBltHelper.DeleteObject(hBitmap);
if (value == null) return;
//cache the new HBitmap and Graphics.
bmpGfx = Graphics.FromImage(map);
hBitmap = map.GetHbitmap();
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (map == null) return;
//finally, the actual painting!
Rectangle mapRect = //whatever zoom/pan logic you implemented.
Rectangle thisRect = new Rectangle(0, 0, this.Width, this.Height);
StretchBltHelper.DrawStretch(
hBitmap,
bmpGfx,
e.Graphics,
mapRect,
thisRect);
}
}
public static class StretchBltHelper
{
public static void DrawStretch(IntPtr hBitmap, Graphics srcGfx, Graphics destGfx,
Rectangle srcRect, Rectangle destRect)
{
IntPtr pTarget = destGfx.GetHdc();
IntPtr pSource = CreateCompatibleDC(pTarget);
IntPtr pOrig = SelectObject(pSource, hBitmap);
if (!StretchBlt(pTarget, destRect.X, destRect.Y, destRect.Width, destRect.Height,
pSource, srcRect.X, srcRect.Y, srcRect.Width, srcRect.Height,
TernaryRasterOperations.SRCCOPY))
throw new Win32Exception(Marshal.GetLastWin32Error());
IntPtr pNew = SelectObject(pSource, pOrig);
DeleteDC(pSource);
destGfx.ReleaseHdc(pTarget);
}
[DllImport("gdi32.dll", EntryPoint = "SelectObject")]
public static extern System.IntPtr SelectObject(
[In()] System.IntPtr hdc,
[In()] System.IntPtr h);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteObject(
[In()] System.IntPtr ho);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern bool StretchBlt(IntPtr hdcDest, int nXOriginDest, int nYOriginDest,
int nWidthDest, int nHeightDest,
IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc,
TernaryRasterOperations dwRop);
public enum TernaryRasterOperations : uint
{
SRCCOPY = 0x00CC0020
//there are many others but we don't need them for this purpose, omitted for brevity
}
}

GetWindowRect returning same value for left & right

I'm trying to capture browser screenshot and one of my Win32 api method is GetWindowRect. This is returning same left & right value. This is only happen when I'm running my application in a remote machine having Win7 as a OS.
Also my PrintWindow method failing in this machine. If anyone have faced this issue before please let me know.
Those above two methods works fine with Vista and XP as OS in remote machine.
Adding few of the methods of my application.
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
private Image Capture(IntPtr hwnd)
{
Rectangle windowSize = this.GetWindowPosition(hwnd);
Bitmap bm = new Bitmap(windowSize.Width, windowSize.Height);
using (Graphics g = Graphics.FromImage(bm))
{
IntPtr hdc = g.GetHdc();
if (PrintWindow(hwnd, hdc, 0) == false)
{
throw new Exception("PrintWindow call failed");
}
g.ReleaseHdc(hdc);
g.Flush();
}
return bm;
}
private Rectangle GetWindowPosition(IntPtr hwnd)
{
Rect r = new Rect();
GetWindowRect(hwnd, ref r);
return new Rectangle(r.Left, r.Top, r.Width, r.Height);
}
You aren't checking your Win32 return codes. My guess is that GetWindowRect fails for some reason and so doesn't assign any values to the rect. Thus its values remain uninitialised.
Check the return value and if the call fails use Marshal.GetLastWin32Error() to find out why. You'll need to update your P/Invokes too:
[DllImport("user32.dll", SetLastError=true)]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll", SetLastError=true)]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
...
if (!GetWindowRect(hwnd, ref r))
int ErrorCode = Marshal.GetLastWin32Error();

Categories

Resources