In case multiple screens are connected to a computer, I'd like to show an application on a touchscreen. By iterating over System.Windows.Forms.Screen.AllScreens I can get the WorkingArea in order to move the window. However, Screen doesn't provide a IsTouchscreen method.
On the other hand by iterating over all System.Windows.Input.Tablet.TabletDevices I am unable to find the corresponding Screen, because Screen.DeviceName doesn't match TabletDevice.Name.
So is there a way to somehow match a Screenwith a TabletDevice or is there another workaround I could use?
This information is available, the low-level COM interfaces that WPF uses are documented in this MSDN article. A disclaimer is however appropriate, Microsoft doesn't like you to use them. The interface documentation warns "Developers should not use this interface", otherwise without any obvious reason why that's good advice. If Microsoft really wants to prevent us from using it then it would be much simpler to just not document them.
And there's something funky going on with the ITablet2::GetMatchingScreenRect() function, the one you are looking for, the documentation for it missing. In itself a possible reason that this info is not exposed by WPF. So caution is necessary, you do need to test it thoroughly on the hardware you want to use it on. I don't have any to verify.
I wrote some code that uses these interfaces. Add a new class to your project and paste the code shown below. You need to add a reference to System.Drawing.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Text;
public enum TouchDeviceKind { Mouse, Pen, Touch }
public class TouchTabletCollection {
public TouchTabletCollection() {
Guid CLSID_TabletManager = new Guid("A5B020FD-E04B-4e67-B65A-E7DEED25B2CF");
var manager = (ITabletManager)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_TabletManager));
int count = 0;
manager.GetTabletCount(out count);
Count = count;
tablets = new List<TouchTablet>(count);
for (int index = 0; index < count; index++) {
tablets.Add(new TouchTablet(manager, index));
}
}
public int Count { get; private set; }
public TouchTablet this[int index] {
get { return tablets[index]; }
}
private List<TouchTablet> tablets;
}
public class TouchTablet {
internal TouchTablet(ITabletManager mgr, int index) {
ITablet itf;
mgr.GetTablet(index, out itf);
device1 = itf;
device2 = (ITablet2)itf;
device3 = (ITablet3)itf;
}
public bool IsMultiTouch {
get {
bool multi;
device3.IsMultiTouch(out multi);
return multi;
}
}
public TouchDeviceKind Kind {
get {
TouchDeviceKind kind;
device2.GetDeviceKind(out kind);
return kind;
}
}
public string Name {
get {
IntPtr pname;
device1.GetName(out pname);
return Marshal.PtrToStringUni(pname);
}
}
public Rectangle InputRectangle {
get {
RECT rc;
device1.GetMaxInputRect(out rc);
return Rectangle.FromLTRB(rc.Left, rc.Top, rc.Right, rc.Bottom);
}
}
public Rectangle ScreenRectangle {
get {
RECT rc;
device2.GetMatchingScreenRect(out rc);
return Rectangle.FromLTRB(rc.Left, rc.Top, rc.Right, rc.Bottom);
}
}
private ITablet device1;
private ITablet2 device2;
private ITablet3 device3;
}
// COM declarations
[ComImport, Guid("764DE8AA-1867-47C1-8F6A-122445ABD89A")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ITabletManager {
void GetDefaultTablet(out ITablet table);
void GetTabletCount(out int count);
void GetTablet(int index, out ITablet tablet);
// rest omitted...
}
[ComImport, Guid("1CB2EFC3-ABC7-4172-8FCB-3BC9CB93E29F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ITablet {
void Dummy1();
void Dummy2();
void GetName(out IntPtr pname);
void GetMaxInputRect(out RECT inputRect);
void GetHardwareCaps(out uint caps);
// rest omitted
}
[ComImport, Guid("C247F616-BBEB-406A-AED3-F75E656599AE")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ITablet2 {
void GetDeviceKind(out TouchDeviceKind kind);
void GetMatchingScreenRect(out RECT rect);
}
[ComImport, Guid("AC0E3951-0A2F-448E-88D0-49DA0C453460")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ITablet3 {
void IsMultiTouch(out bool multi);
void GetMaximumCursors(out int cursors);
}
internal struct RECT { public int Left, Top, Right, Bottom; }
A sample program that uses it:
using System;
class Program {
static void Main(string[] args) {
var tablets = new TouchTabletCollection();
for (int ix = 0; ix < tablets.Count; ++ix) {
Console.WriteLine("Found tablet {0} named {1}", ix + 1, tablets[ix].Name);
Console.WriteLine(" Type = {0}, Multi-touch supported = {1}", tablets[ix].Kind, tablets[ix].IsMultiTouch);
Console.WriteLine(" Input rectangle = {0}", tablets[ix].InputRectangle);
Console.WriteLine(" Screen rectangle = {0}", tablets[ix].ScreenRectangle);
}
Console.ReadLine();
}
}
Note that Windows 7 or higher is required. The output on my touch-ignorant laptop:
Found tablet 1 named \\.\DISPLAY1
Type = Mouse, Multi-touch supported = False
Input rectangle = {X=0,Y=0,Width=1440,Height=900}
Screen rectangle = {X=0,Y=0,Width=1440,Height=900}
have you tried.
public bool HasTouchInput()
{
foreach (TabletDevice tabletDevice in Tablet.TabletDevices)
{
//Only detect if it is a touch Screen
if(tabletDevice.Type == TabletDeviceType.Touch)
return true;
}
return false;
}
Try this Link
Or try This
var isTouchDevice = Tablet.TabletDevices.Cast<TabletDevice>().Any(dev => dev.Type == TabletDeviceType.Touch);
this is might also help
Related
I'm trying to cut down on how much duplication I have on my code, so I decided to make one of my classes a static class since I decided that its data should really be shared with everyone. Here's the static method below:
// A static class, that holds all object's coordinates, and methods to return & update their values.
internal static class Coordinate
{
private static int[,] PlayerCoordinate { get; set; }
public static int[,] GateCoordinate { get; }
public static int[,] FountainCoordinate { get; }
static Coordinate() // FIRST VALUE IS X (column), SECOND VALUE IS Y (row).
{
PlayerCoordinate = new int[,] { { 0, 0 } };
GateCoordinate = PlayerCoordinate; // Just starts off in the same place as player.
FountainCoordinate = new int[,] { { 2, 0 } };
}
// A static method, that sends the data of all object coordinates, deconstructed into seperate ints.
public static int PlayerColumn() { return PlayerCoordinate[0, 0]; }
public static int PlayerRow() { return PlayerCoordinate[0, 1]; }
public static int GateColumn() { return GateCoordinate[0, 0]; }
public static int GateRow() { return GateCoordinate[0, 1]; }
public static int FountainColumn() { return FountainCoordinate[0, 0]; }
public static int FountainRow() { return FountainCoordinate[0, 1]; }
// Updates the coordinates of the player.
public static void UpdatePlayerCoordinate(int column, int row) { PlayerCoordinate = new int[,] { { column, row } }; }
}
The main issue comes in from my GameManager class. On the console, the beginning section should print out "You are the room at (Column=0, Row=0), but it prints this instead:
Here is the code for my GameManager class:
internal class GameManager
{
private bool IsGameOver;
private Player Player;
private Updater Updater;
// Don't need to call Fountain or Coordinate since they're static
public GameManager()
{
IsGameOver = false;
Player = new();
Updater = new();
}
public void RunGame()
{
while (!IsGameOver)
{
Console.WriteLine("----------------------------------------------------------");
Updater.DisplayPlayerPosition(); // This is the main line that I'm having issues with as of right now. All other functions past this are another problem.
Updater.DisplayPlayerSenses();
string playerInput = Player.GetInput();
Updater.MovePlayer(playerInput);
IsGameOver = Updater.CheckForWin();
}
}
}
And just to make sure, here is the code from my updater class, with the specific method that I'm having issues with:
internal class Updater
{
// No fields
// Default constructor
// Gets the text to show the player his current position.
public void DisplayPlayerPosition() // This is the method that I'm having issues with.
{
Console.WriteLine($"You are in the room at (Column={Coordinate.PlayerColumn}, Row={Coordinate.PlayerRow})");
}
...
I'm fairly new to the static keyword so I believe that I may be missing smth. I personally believe that it's because the class itself hasn't been initialized (like I haven't called the constructor for the Coordinate class, and apparently you can't call a static constructor anyways), but that's just me. If I could get any help, I'd greatly appreciate it!
PlayerColumn() and PlayerRow() are methods, but you are accesing them in the WriteLine statement as if they are properties.
Update your WriteLine to:
Console.WriteLine($"You are in the room at (Column={Coordinate.PlayerColumn()}, Row={Coordinate.PlayerRow()})");
I'm learning inheritance and I understand the code below.
namespace InheritanceApplication {
class Shape {
public void setWidth(int w) {
width = w;
}
public void setHeight(int h) {
height = h;
}
protected int width;
protected int height;
}
// Base class PaintCost
public interface PaintCost {
int getCost(int area);
}
// Derived class
class Rectangle : Shape, PaintCost {
public int getArea() {
return (width * height);
}
public int getCost(int area) {
return area * 70;
}
}
class RectangleTester {
static void Main(string[] args) {
Rectangle Rect = new Rectangle();
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// Print the area of the object.
Console.WriteLine("Total area: {0}", Rect.getArea());
Console.WriteLine("Total paint cost: ${0}" , Rect.getCost(area));
Console.ReadKey();
}
}
}
However, why have they created the set height and set width functions. Would it not be better practice to simply just do this:
public int width {get;set;}
public int height {get;set;}
and then in the main class just do something like below:
rect.width = 5;
rect.height = 7;
Many thanks,
Amir
I'm sure others will provide different points, but here are my main 2 reasons for using gets/sets. If these don't apply for a given property, chances are I won't use getters/setters.
1 - Debugging
It makes it significantly easier to debug data propagation (how data gets passed around) if you can debug a setter that you're concerned about. You can easily throw in a Debug.Print call and debug the value being set if you're concerned it's being passed the wrong value. Or you could place break points and actually debug through the stack trace. For example:
class Shape {
public void setWidth(int w) {
if(w < 0)
Debug.Print("width is less than 0!");
width = w;
}
public void setHeight(int h) {
height = h;
}
protected int width;
protected int height;
}
2 - Value Change Actions
There may be better ways to achieve this, but I like being able to add simple logic to setters to ensure that any logic that needs to run when a value changes does so. For instance I may use the following:
public void SetWindowHeight(int newHeight)
{
if(WindowHeight == newHeight)
return;
WindowHeight = newHeight;
UpdateWindowDisplay();
}
public int GetWindowHeight()
{
return WindowHeight;
}
private int WindowHeight;
public void UpdateWindowDisplay()
{
Window.UpdateHeight(WindowHeight);
// Other window display logic
}
Although personally I prefer to use property gets/sets, but that's just my preference.
public int WindowHeight
{
get
{
return windowHeight;
}
set
{
if(windowHeight == value)
return;
windowHeight = value;
UpdateWindowDisplay();
}
}
private int windowHeight;
public void UpdateWindowDisplay()
{
Window.UpdateHeight(WindowHeight);
// Other window display logic
}
When i do display.init() I get these white lines and a few other different pixels. The next thing that happens is they disappear one line at a time and it's preventing my VGA from booting.
I'll post my kernel code and display driver.
DISPLAY DRIVER C#
using Cosmos.HAL;
using Sys = Cosmos.System;
namespace Display
{
public class DisplayDriver
{
protected VGAScreen screen;
private int width, height;
public DisplayDriver()
{
screen = new VGAScreen();
}
public void init()
{
screen.SetGraphicsMode(VGAScreen.ScreenSize.Size320x200, VGAScreen.ColorDepth.BitDepth8);
screen.Clear(0);
width = screen.PixelWidth;
height = screen.PixelHeight;
}
public virtual void setPixel(int x, int y, int c)
{
if (screen.GetPixel320x200x8((uint)x, (uint)y) != (uint)c)
setPixelRaw(x, y, c);
}
public virtual byte getPixel(int x, int y)
{
return (byte)screen.GetPixel320x200x8((uint)x, (uint)y);
}
public virtual void clear()
{
clear(0);
}
public virtual void clear(int c)
{
screen.Clear(c);
}
public virtual void step() { }
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
public void setPixelRaw(int x, int y, int c)
{
screen.SetPixel320x200x8((uint)x, (uint)y, (uint)c);
}
}
}
KERNEL:
using System;
using Sys = Cosmos.System;
using Display;
using Cosmos.Core;
using Cosmos.HAL;
using Cosmos.Common;
using Cosmos.Debug;
using Cosmos.IL2CPU;
namespace CosmosKernel3
{
public class Kernel : Sys.Kernel
{
protected override void BeforeRun()
{
Console.WriteLine("Booting VGADriver.");
try
{
var display = new DisplayDriver();
Console.WriteLine("ATTEMPTING");
display.init(); //init display
display.clear();
display.setPixel((int)40, 50, 60);
}
catch (Exception)
{
Console.WriteLine("Booting VGA failed. Booting into DOS mode.");
dosemergency();
}
}
protected override void Run()
{
boot();
while (true) ;
}
public static void boot()
{
}
public static void dosemergency()
{
Console.WriteLine("XENA DOS EMERGENCY MODE.");
Console.WriteLine("COMMANDS:");
Console.WriteLine("graphics -r (Graphics retry)");
String meow = Console.ReadLine();
if (meow == "graphics -r") ;
Console.WriteLine("Booting VGADriver.");
try
{
var display = new DisplayDriver();
display.init(); //init display
boot(); //boot
}
catch (Exception)
{
Console.WriteLine("Booting VGA failed. Booting into DOS mode.");
dosemergency();
}
}
}
}
SCREENSHOT:
Anyway, this has been happening for a while now and I cant seem to figure out why its doing this. Help!
I don't know if it's not too late, however this issue is popular in Cosmos, so future readers might find this answer.
Your code is correct. After talking with one of the devs he told me that they are aware of the issue and it happens because
current code [of Cosmos] uses delegates for pixelputting
If you wait 1-2 minutes, the screen will finally load.
edit:
I found a way to speed up this process a lot:
never call
Screen.clear()
function
Instead create the following function:
public void DrawFilledRectangle(uint x0, uint y0, int Width, int Height, int color)
{
for (uint i = 0; i < Width; i++)
{
for (uint h = 0; h < Height; h++)
{
setPixel((int)(x0 + i), (int)(y0 + h), color);
}
}
}
and replace
public virtual void clear(int c)
{
screen.Clear(c);
}
function
by:
public virtual void clear(int c)
{
//screen.Clear(c);
DrawFilledRectangle(0, 0, width, height, c);
}
Also replace init function by:
public void init()
{
screen.SetGraphicsMode(VGAScreen.ScreenSize.Size320x200, VGAScreen.ColorDepth.BitDepth8);
width = screen.PixelWidth;
height = screen.PixelHeight;
clear(0);
}
Which is much faster. Good luck in creating your OS :)
I'm building an application where DMX output is generated. The application than gives this data via an USB interface to the hardware. Since DMX uses the RS485 protocol, almost any RS485 USB-to-Serial converter can be used. I want my application to be extendable by user written plugins, which inherit from my base class which than handles the communication between the application and the interface.
So here is my problem:
I can't communicate with the interface when the interface is plugged in after the app was started. Im using WqlEventQuery's and the ManagementEventWatcher classes to detect usb event's (plugin & out) and load & unload my classes accordingly. While debugging I noticed that the driver class indeed see's the device, but can't read from it (eg the serial number). Do I need to reset something before the port get's usable?
status get's set to FT_INVALID_HANDLE after FT_Open which means that a communication is never accomplished.
The FTD2xx class is working as it should be except when the interface is plugged in after starting the app.
EDIT
I got it working. However to make the serial usable in my application I have to open, close and dispose it once from the C# SerialPort class once. Why is that?
var ports = SerialPort.GetPortNames();
SerialPort port = new SerialPort(ports[0]);
port.Open();
port.Close();
port.Dispose();
-> Following code works
Code:
Bases driver class
public abstract class Driver
{
public abstract string FullName { get; set; }
public virtual string Description { get; set; }
public abstract string[] HardwareIDs { get; set; }
public virtual int FrameRate { get; set; }
public Driver(int framerate)
{
this.FrameRate = framerate;
}
public abstract void Start();
public abstract void Stop();
public abstract void SetDMXValue(int channel, byte value);
}
FTD2xx driver class
public class FTD2XX : Driver
{
public override string FullName
{
get { return "FTD2XX Usb-Interface"; }
set {}
}
public override string Description
{
get { return "Driver for the FTD2xx usb-to-dmx interface"; }
set {}
}
public override string[] HardwareIDs
{
get { return new string[] { #"USB\VID_0403&PID_6001" }; }
set {}
}
public byte[] buffer { get; private set; }
public uint handle;
public bool done = false;
public int bytesWritten = 0;
public FT_STATUS status;
public const byte BITS_8 = 8;
public const byte STOP_BITS_2 = 2;
public const byte PARITY_NONE = 0;
public const UInt16 FLOW_NONE = 0;
public const byte PURGE_RX = 1;
public const byte PURGE_TX = 2;
Thread workThread;
// DLL IMPORT'S LEFT OUT FOR SIZE //
public FTD2XX(int frameRate) : base(frameRate)
{
this.buffer = new byte[255];
}
public override void Start()
{
handle = 0;
status = FT_Open(0, ref handle);
workThread = new Thread(new ThreadStart(writeData));
workThread.Start();
}
public override void SetDMXValue(int channel, byte value)
{
if (buffer != null)
{
buffer[channel] = value;
}
}
public void writeData()
{
while (!done)
{
InitOpenDMX();
FT_SetBreakOn(handle);
FT_SetBreakOff(handle);
bytesWritten = write(handle, buffer, buffer.Length);
Thread.Sleep(FrameRate);
}
}
public int write(uint handle, byte[] data, int length)
{
IntPtr ptr = Marshal.AllocHGlobal((int)length);
Marshal.Copy(data, 0, ptr, (int)length);
uint bytesWritten = 0;
status = FT_Write(handle, ptr, (uint)length, ref bytesWritten);
return (int)bytesWritten;
}
public void InitOpenDMX()
{
status = FT_ResetDevice(handle);
status = FT_SetDivisor(handle, (char)12); // set baud rate
status = FT_SetDataCharacteristics(handle, BITS_8, STOP_BITS_2, PARITY_NONE);
status = FT_SetFlowControl(handle, (char)FLOW_NONE, 0, 0);
status = FT_ClrRts(handle);
status = FT_Purge(handle, PURGE_TX);
status = FT_Purge(handle, PURGE_RX);
}
public override void Stop()
{
this.done = true;
this.workThread.Abort();
this.workThread = null;
status = FT_ResetDevice(handle);
status = FT_Close(handle);
}
/// <summary>
/// Enumaration containing the varios return status for the DLL functions.
/// </summary>
public enum FT_STATUS
{
FT_OK = 0,
FT_INVALID_HANDLE,
FT_DEVICE_NOT_FOUND,
FT_DEVICE_NOT_OPENED,
FT_IO_ERROR,
FT_INSUFFICIENT_RESOURCES,
FT_INVALID_PARAMETER,
FT_INVALID_BAUD_RATE,
FT_DEVICE_NOT_OPENED_FOR_ERASE,
FT_DEVICE_NOT_OPENED_FOR_WRITE,
FT_FAILED_TO_WRITE_DEVICE,
FT_EEPROM_READ_FAILED,
FT_EEPROM_WRITE_FAILED,
FT_EEPROM_ERASE_FAILED,
FT_EEPROM_NOT_PRESENT,
FT_EEPROM_NOT_PROGRAMMED,
FT_INVALID_ARGS,
FT_OTHER_ERROR
};
}
}
USB event handler
private void DeviceInsertedEvent(object sender, EventArrivedEventArgs e)
{
ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
foreach(Driver driver in this.drivers)
{
if (driver.HardwareIDs.Any(hID => (instance.Properties["DeviceID"].Value as string).StartsWith(hID)))
{
driver.Start();
if (this.OnDriverLoaded != null)
this.OnDriverLoaded(this, new PluginLoadUnloadEventArgs(driver));
break;
}
}
}
Unplug is basiclly the same, but .Stop() and OnDriverUnload() used
Detecting any compatible usb devices after startup (Code works as desired if this is the case)
ManagementObjectCollection collection;
using (var searcher = new ManagementObjectSearcher(#"Select * From Win32_USBHub"))
collection = searcher.Get();
foreach (var device in collection)
{
foreach (Driver driver in this.drivers)
{
if (driver.HardwareIDs.Any(hID => (device.GetPropertyValue("DeviceID") as string).StartsWith(hID)))
{
driver.Start();
if (this.OnDriverLoaded != null)
this.OnDriverLoaded(this, new PluginLoadUnloadEventArgs(driver));
goto End;
}
}
}
End:
return;
I love that Windows 10 now has support for virtual desktops built in, but I have some features that I'd like to add/modify (e.g., force a window to appear on all desktops, launch the task view with a hotkey, have per-monitor desktops, etc.)
I have searched for applications and developer references to help me customize my desktops, but I have had no luck.
Where should I start? I am looking for Windows API functions (ideally, that are callable from a C# application) that will give me programmatic access to manipulate virtual desktops and the windows therein.
The Windows SDK Support Team Blog posted a C# demo to switch Desktops via IVirtualDesktopManager:
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
[System.Security.SuppressUnmanagedCodeSecurity]
public interface IVirtualDesktopManager
{
[PreserveSig]
int IsWindowOnCurrentVirtualDesktop(
[In] IntPtr TopLevelWindow,
[Out] out int OnCurrentDesktop
);
[PreserveSig]
int GetWindowDesktopId(
[In] IntPtr TopLevelWindow,
[Out] out Guid CurrentDesktop
);
[PreserveSig]
int MoveWindowToDesktop(
[In] IntPtr TopLevelWindow,
[MarshalAs(UnmanagedType.LPStruct)]
[In]Guid CurrentDesktop
);
}
[ComImport, Guid("aa509086-5ca9-4c25-8f95-589d3c07b48a")]
public class CVirtualDesktopManager
{
}
public class VirtualDesktopManager
{
public VirtualDesktopManager()
{
cmanager = new CVirtualDesktopManager();
manager = (IVirtualDesktopManager)cmanager;
}
~VirtualDesktopManager()
{
manager = null;
cmanager = null;
}
private CVirtualDesktopManager cmanager = null;
private IVirtualDesktopManager manager;
public bool IsWindowOnCurrentVirtualDesktop(IntPtr TopLevelWindow)
{
int result;
int hr;
if ((hr = manager.IsWindowOnCurrentVirtualDesktop(TopLevelWindow, out result)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
return result != 0;
}
public Guid GetWindowDesktopId(IntPtr TopLevelWindow)
{
Guid result;
int hr;
if ((hr = manager.GetWindowDesktopId(TopLevelWindow, out result)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
return result;
}
public void MoveWindowToDesktop(IntPtr TopLevelWindow, Guid CurrentDesktop)
{
int hr;
if ((hr = manager.MoveWindowToDesktop(TopLevelWindow, CurrentDesktop)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
}
}
it includes the API to detect on which desktop the Window is shown and it can switch and move a Windows the a Desktop.
Programmatic access to the virtual desktop feature is very limited, as Microsoft has only exposed the IVirtualDesktopManager COM interface. It does provide two key functions:
IVirtualDesktopManager::GetWindowDesktopId allows you to retrieve the ID of a virtual desktop, based on a window that is already assigned to that desktop.
IVirtualDesktopManager::MoveWindowToDesktop allows you to move a window to a specific virtual desktop.
Unfortunately, this is not nearly enough to accomplish anything useful. I've written some C# code based on the reverse-engineering work done by NickoTin. I can't read much of the Russian in his blog post, but his C++ code was pretty accurate.
I do need to emphasize that this code is not something you want to commit to in a product. Microsoft always feels free to change undocumented APIs whenever they feel like it. And there is a runtime risk as well: this code does not necessarily interact well when the user is tinkering with the virtual desktops. Always keep in mind that a virtual desktop can appear and disappear at any time, completely out of sync with your code.
To use the code, create a new C# class library project. I'll first post ComInterop.cs, it contains the COM interface declarations that match NickoTin's C++ declarations:
using System;
using System.Runtime.InteropServices;
namespace Windows10Interop {
internal static class Guids {
public static readonly Guid CLSID_ImmersiveShell =
new Guid(0xC2F03A33, 0x21F5, 0x47FA, 0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39);
public static readonly Guid CLSID_VirtualDesktopManagerInternal =
new Guid(0xC5E0CDCA, 0x7B6E, 0x41B2, 0x9F, 0xC4, 0xD9, 0x39, 0x75, 0xCC, 0x46, 0x7B);
public static readonly Guid CLSID_VirtualDesktopManager =
new Guid("AA509086-5CA9-4C25-8F95-589D3C07B48A");
public static readonly Guid IID_IVirtualDesktopManagerInternal =
new Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5");
public static readonly Guid IID_IVirtualDesktop =
new Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4");
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4")]
internal interface IVirtualDesktop {
void notimpl1(); // void IsViewVisible(IApplicationView view, out int visible);
Guid GetId();
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5")]
internal interface IVirtualDesktopManagerInternal {
int GetCount();
void notimpl1(); // void MoveViewToDesktop(IApplicationView view, IVirtualDesktop desktop);
void notimpl2(); // void CanViewMoveDesktops(IApplicationView view, out int itcan);
IVirtualDesktop GetCurrentDesktop();
void GetDesktops(out IObjectArray desktops);
[PreserveSig]
int GetAdjacentDesktop(IVirtualDesktop from, int direction, out IVirtualDesktop desktop);
void SwitchDesktop(IVirtualDesktop desktop);
IVirtualDesktop CreateDesktop();
void RemoveDesktop(IVirtualDesktop desktop, IVirtualDesktop fallback);
IVirtualDesktop FindDesktop(ref Guid desktopid);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
internal interface IVirtualDesktopManager {
int IsWindowOnCurrentVirtualDesktop(IntPtr topLevelWindow);
Guid GetWindowDesktopId(IntPtr topLevelWindow);
void MoveWindowToDesktop(IntPtr topLevelWindow, ref Guid desktopId);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("92CA9DCD-5622-4bba-A805-5E9F541BD8C9")]
internal interface IObjectArray {
void GetCount(out int count);
void GetAt(int index, ref Guid iid, [MarshalAs(UnmanagedType.Interface)]out object obj);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
internal interface IServiceProvider10 {
[return: MarshalAs(UnmanagedType.IUnknown)]
object QueryService(ref Guid service, ref Guid riid);
}
}
Next is Desktop.cs. It contains the friendly C# classes that you can use in your code:
using System;
using System.Runtime.InteropServices;
namespace Windows10Interop
{
public class Desktop {
public static int Count {
// Returns the number of desktops
get { return DesktopManager.Manager.GetCount(); }
}
public static Desktop Current {
// Returns current desktop
get { return new Desktop(DesktopManager.Manager.GetCurrentDesktop()); }
}
public static Desktop FromIndex(int index) {
// Create desktop object from index 0..Count-1
return new Desktop(DesktopManager.GetDesktop(index));
}
public static Desktop FromWindow(IntPtr hWnd) {
// Creates desktop object on which window <hWnd> is displayed
Guid id = DesktopManager.WManager.GetWindowDesktopId(hWnd);
return new Desktop(DesktopManager.Manager.FindDesktop(ref id));
}
public static Desktop Create() {
// Create a new desktop
return new Desktop(DesktopManager.Manager.CreateDesktop());
}
public void Remove(Desktop fallback = null) {
// Destroy desktop and switch to <fallback>
var back = fallback == null ? DesktopManager.GetDesktop(0) : fallback.itf;
DesktopManager.Manager.RemoveDesktop(itf, back);
}
public bool IsVisible {
// Returns <true> if this desktop is the current displayed one
get { return object.ReferenceEquals(itf, DesktopManager.Manager.GetCurrentDesktop()); }
}
public void MakeVisible() {
// Make this desktop visible
DesktopManager.Manager.SwitchDesktop(itf);
}
public Desktop Left {
// Returns desktop at the left of this one, null if none
get {
IVirtualDesktop desktop;
int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 3, out desktop);
if (hr == 0) return new Desktop(desktop);
else return null;
}
}
public Desktop Right {
// Returns desktop at the right of this one, null if none
get {
IVirtualDesktop desktop;
int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 4, out desktop);
if (hr == 0) return new Desktop(desktop);
else return null;
}
}
public void MoveWindow(IntPtr handle) {
// Move window <handle> to this desktop
DesktopManager.WManager.MoveWindowToDesktop(handle, itf.GetId());
}
public bool HasWindow(IntPtr handle) {
// Returns true if window <handle> is on this desktop
return itf.GetId() == DesktopManager.WManager.GetWindowDesktopId(handle);
}
public override int GetHashCode() {
return itf.GetHashCode();
}
public override bool Equals(object obj) {
var desk = obj as Desktop;
return desk != null && object.ReferenceEquals(this.itf, desk.itf);
}
private IVirtualDesktop itf;
private Desktop(IVirtualDesktop itf) { this.itf = itf; }
}
internal static class DesktopManager {
static DesktopManager() {
var shell = (IServiceProvider10)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_ImmersiveShell));
Manager = (IVirtualDesktopManagerInternal)shell.QueryService(Guids.CLSID_VirtualDesktopManagerInternal, Guids.IID_IVirtualDesktopManagerInternal);
WManager = (IVirtualDesktopManager)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_VirtualDesktopManager));
}
internal static IVirtualDesktop GetDesktop(int index) {
int count = Manager.GetCount();
if (index < 0 || index >= count) throw new ArgumentOutOfRangeException("index");
IObjectArray desktops;
Manager.GetDesktops(out desktops);
object objdesk;
desktops.GetAt(index, Guids.IID_IVirtualDesktop, out objdesk);
Marshal.ReleaseComObject(desktops);
return (IVirtualDesktop)objdesk;
}
internal static IVirtualDesktopManagerInternal Manager;
internal static IVirtualDesktopManager WManager;
}
}
And finally a little test WinForms project that I used to test the code. Just drop 4 buttons on a form and name them buttonLeft/Right/Create/Destroy:
using Windows10Interop;
using System.Diagnostics;
...
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonRight_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
Debug.Assert(curr.Equals(Desktop.Current));
var right = curr.Right;
if (right == null) right = Desktop.FromIndex(0);
if (right != null) {
right.MoveWindow(this.Handle);
right.MakeVisible();
this.BringToFront();
Debug.Assert(right.IsVisible);
}
}
private void buttonLeft_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
Debug.Assert(curr.Equals(Desktop.Current));
var left = curr.Left;
if (left == null) left = Desktop.FromIndex(Desktop.Count - 1);
if (left != null) {
left.MoveWindow(this.Handle);
left.MakeVisible();
this.BringToFront();
Debug.Assert(left.IsVisible);
}
}
private void buttonCreate_Click(object sender, EventArgs e) {
var desk = Desktop.Create();
desk.MoveWindow(this.Handle);
desk.MakeVisible();
Debug.Assert(desk.IsVisible);
Debug.Assert(desk.Equals(Desktop.Current));
}
private void buttonDestroy_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
var next = curr.Left;
if (next == null) next = curr.Right;
if (next != null && next != curr) {
next.MoveWindow(this.Handle);
curr.Remove(next);
Debug.Assert(next.IsVisible);
}
}
}
The only real quirk I noticed while testing this is that moving a window from one desktop to another can move it to the bottom of the Z-order when you first switch the desktop, then move the window. No such problem if you do it the other way around.
There is this guy that made a application to map keyboard shorcut to move a window between virtual desktop.
https://github.com/Grabacr07/SylphyHorn
(I use it every day )
He has a blog where he explain what he did
http://grabacr.net/archives/5701 ( you can use google translate it is in japanese)
He in fact used the same api mantionned in the Alberto Tostado response.
http://www.cyberforum.ru/blogs/105416/blog3671.html
and the api can be found on his github https://github.com/Grabacr07/VirtualDesktop
The api is really simple to use BUT it seems impossible to move a window from another process.
public static bool MoveToDesktop(IntPtr hWnd, VirtualDesktop virtualDesktop)
{
ThrowIfNotSupported();
int processId;
NativeMethods.GetWindowThreadProcessId(hWnd, out processId);
if (Process.GetCurrentProcess().Id == processId) // THAT LINE
{
var guid = virtualDesktop.Id;
VirtualDesktop.ComManager.MoveWindowToDesktop(hWnd, ref guid);
return true;
}
return false;
}
To workaround this problem they made another implementation that they use alongside the one in the russian blog
if (VirtualDesktopHelper.MoveToDesktop(hWnd, right) //<- the one in the russian blog
|| this.helper.MoveWindowToDesktop(hWnd, right.Id)) <- the second implementation
The second implementation can be found here: https://github.com/tmyt/VDMHelper
This one can move a window from another process to another desktop. BUT it is buggy right now. For exemple when i try to move some window like google chrome it crash.
So this is the result of my research. I m rigth now trying to make a StickyWindow feature with these api.
I fear that all about "Virtual desktops" in Windows 10 is undocumented, but in a Russian page I've seen documented the interfaces. I don't speak Russian but seems that they have used reversed engineering. Anyway, the code is very clear (Thanks to them!).
Keep an eye here:
http://www.cyberforum.ru/blogs/105416/blog3671.html
I've been trying to see if the old API's CreateDesktop, OpenDesktop, etc... is linked to the new Virtual-Desktops, but no way...
The interfaces work with the final production release of Windows 10 (2015-05-08), but you shouldn't use them in a real wide distributed application until Microsoft documents them. Too much risk.
Regards.