Windows has a really useful right click -> print function for most files, but I can't figure out how to arbitrarily call this function. I've found out how to do a direct print by calling
rundll32 shimgvw.dll,ImageView_PrintTo "Absolute\Path\To\File" "Printer Name"
and I can open the photo viewer by calling
rundll32 shimgvw.dll,ImageView_Fullscreen Absolute\Path\To\File
However, the intermediate print setup GUI I can't figure out.
Found out how to do it! Here's an example C# program:
Few things to note:
The program owns the dialog, so it needs a while loop to stay alive
If you're going to use this, you might need this post too
using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace PrintGUI // Note: actual namespace depends on the project name.
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Printing file:");
Console.WriteLine(args[0]);
OpenPrintPictures(args[0]);
Console.WriteLine("Done");
while (true)
{
}
}
[ComImport]
[Guid("00000122-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IDropTarget
{
int DragEnter(
[In] System.Runtime.InteropServices.ComTypes.IDataObject pDataObj,
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
int DragOver(
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
int DragLeave();
int Drop(
[In] System.Runtime.InteropServices.ComTypes.IDataObject pDataObj,
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
}
/// <summary>
/// Open Print Pictures dialog
/// </summary>
/// <param name="filename">File to print</param>
public static void OpenPrintPictures(string filename)
{
var dataObj = new DataObject(DataFormats.FileDrop, new string[] { filename });
var memoryStream = new MemoryStream(4);
var buffer = new byte[] { 5, 0, 0, 0 };
memoryStream.Write(buffer, 0, buffer.Length);
dataObj.SetData("Preferred DropEffect", memoryStream);
var CLSID_PrintPhotosDropTarget = new Guid("60fd46de-f830-4894-a628-6fa81bc0190d");
var dropTargetType = Type.GetTypeFromCLSID(CLSID_PrintPhotosDropTarget, true);
var dropTarget = (IDropTarget)Activator.CreateInstance(dropTargetType);
dropTarget.Drop(dataObj, 0, new Point(), 0);
}
}
}
Related
I am creating a clipboard manager in C# and from time to time I experience that the clipboard is being set empty by some application.
This happens in e.g. Excel when deselecting what is just copied, so I need to figure out if the clipboard is empty but how can I get the application name that updates the clipboard?
I hope that I somehow can get the HWnd handle of the application that updates the clipboard so I can then lookup the process behind it with this code:
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
...
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_CLIPBOARDUPDATE:
// How to get the "handle" HWnd?
IntPtr handle = ??? <============= HOW TO GET THIS ONE ???
// Get the process ID from the HWnd
uint processId = 0;
GetWindowThreadProcessId(handle, out processId);
// Get the process name from the process ID
string processName = Process.GetProcessById((int)processId).ProcessName;
Console.WriteLine("Clipboard UPDATE event from [" + processName + "]");
break;
}
default:
base.WndProc(ref m);
break;
}
}
I would have hoped that I could use the HWnd from the Message object, but this seems to be my own application - probably to notify the application with this process ID:
If I can get it any other way then this would of course be fully okay also but I would appreciate any insights on this :-)
Solution
Based on #Jimi's answer then this is very simple. I can just add the below 3 lines to my original code:
// Import the "GetClipboardOwner" function from the User32 library
[DllImport("user32.dll")]
public static extern IntPtr GetClipboardOwner();
...
// Replace the original line with "HOW TO GET THIS ONE" with this line below - this will give the HWnd handle for the application that has changed the clipboard:
IntPtr handle = GetClipboardOwner();
You can call GetClipboardOwner() to get the handle of the Window that last set or cleared the Clipboard (the operation that triggered the notification).
[...] In general, the clipboard owner is the window that last placed data in the Clipboard.
The EmptyClipboard function assigns Clipboard ownership.
There are special cases when a Process passes a null handle to OpenClipboard(): read the Remarks section of this function and the EmptyClipboard function.
Before calling EmptyClipboard, an application must open the Clipboard
by using the OpenClipboard function. If the application specifies a
NULL window handle when opening the clipboard, EmptyClipboard succeeds
but sets the clipboard owner to NULL. Note that this causes
SetClipboardData to fail.
▶ Here I'm using a NativeWindow derived class to setup a Clipboard listener. The Window that process the Clipboard update messages is created initializing a CreateParams object and passing this parameter to the NativeWindow.CreateHandle(CreateParams) method, to create an invisible Window.
Then override WndProc of the initialized NativeWindow, to receive WM_CLIPBOARDUPDATE notifications.
The AddClipboardFormatListener function is used to place the Window in the system Clipboard listeners chain.
▶ The ClipboardUpdateMonitor class generates an event when a Clipboard notification is received. The custom ClipboardChangedEventArgs object passed in the event contains the Handle of the Clipboard Owner, as returned by GetClipboardOwner(), the ThreadId and ProcessId returned by GetWindowThreadProcessId() and the Process name, identified by Process.GetProcessById().
You can setup a ClipboardUpdateMonitor object like this:
This class can also be initialized in Program.cs
private ClipboardUpdateMonitor clipboardMonitor = null;
// [...]
clipboardMonitor = new ClipboardUpdateMonitor();
clipboardMonitor.ClipboardChangedNotify += this.ClipboardChanged;
// [...]
private void ClipboardChanged(object sender, ClipboardChangedEventArgs e)
{
Console.WriteLine(e.ProcessId);
Console.WriteLine(e.ProcessName);
Console.WriteLine(e.ThreadId);
}
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Forms;
public sealed class ClipboardUpdateMonitor : IDisposable
{
private bool isDisposed = false;
private static ClipboardWindow window = null;
public event EventHandler<ClipboardChangedEventArgs> ClipboardChangedNotify;
public ClipboardUpdateMonitor()
{
window = new ClipboardWindow();
if (!NativeMethods.AddClipboardFormatListener(window.Handle)) {
throw new TypeInitializationException(nameof(ClipboardWindow),
new Exception("ClipboardFormatListener could not be initialized"));
}
window.ClipboardChanged += ClipboardChangedEvent;
}
private void ClipboardChangedEvent(object sender, ClipboardChangedEventArgs e)
=> ClipboardChangedNotify?.Invoke(this, e);
public void Dispose()
{
if (!isDisposed) {
// Cannot allow to throw exceptions here: add more checks to verify that
// the NativeWindow still exists and its handle is a valid handle
NativeMethods.RemoveClipboardFormatListener(window.Handle);
window?.DestroyHandle();
isDisposed = true;
}
}
~ClipboardUpdateMonitor() => Dispose();
private class ClipboardWindow : NativeWindow
{
public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged;
public ClipboardWindow() {
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
var cp = new CreateParams();
cp.Caption = "ClipboardWindow";
cp.Height = 100;
cp.Width = 100;
cp.Parent = IntPtr.Zero;
cp.Style = NativeMethods.WS_CLIPCHILDREN;
cp.ExStyle = NativeMethods.WS_EX_CONTROLPARENT | NativeMethods.WS_EX_TOOLWINDOW;
this.CreateHandle(cp);
}
protected override void WndProc(ref Message m)
{
switch (m.Msg) {
case NativeMethods.WM_CLIPBOARDUPDATE:
IntPtr owner = NativeMethods.GetClipboardOwner();
var threadId = NativeMethods.GetWindowThreadProcessId(owner, out uint processId);
string processName = string.Empty;
if (processId != 0) {
using (var proc = Process.GetProcessById((int)processId)) {
processName = proc?.ProcessName;
}
}
ClipboardChanged?.Invoke(null, new ClipboardChangedEventArgs(processId, processName, threadId));
m.Result = IntPtr.Zero;
break;
default:
base.WndProc(ref m);
break;
}
}
}
}
Custom EventArgs object used to carry the information collected about the Clipboard Owner:
public class ClipboardChangedEventArgs : EventArgs
{
public ClipboardChangedEventArgs(uint processId, string processName, uint threadId)
{
this.ProcessId = processId;
this.ProcessName = processName;
this.ThreadId = threadId;
}
public uint ProcessId { get; }
public string ProcessName { get; }
public uint ThreadId { get; }
}
NativeMethods class:
internal static class NativeMethods
{
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll")]
internal static extern IntPtr GetClipboardOwner();
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
internal const int WM_CLIPBOARDUPDATE = 0x031D;
internal const int WS_CLIPCHILDREN = 0x02000000;
internal const int WS_EX_TOOLWINDOW = 0x00000080;
internal const int WS_EX_CONTROLPARENT = 0x00010000;
}
I am running a powershell script to change the background to a certain set of colors. I would like to do this without rebooting, but unfortunately cannot get the changes to take effect immediately on a windows 7/8 platform. I've found many solutions online but I cannot find one that works for me. I think it may have something to do with setting the SystemParametersInfo, but I don't know for sure. I've seen a few solutions and tried them out for myself, but I can't get them to work either. The registry keys update just find but the changes don't take effect until after I reboot. Below is what I have so far, if anyone seeing anything I could do different I would appreciate the help!
backgroundtest.ps1
Add-Type #"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace Background
{
public class Setter {
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo(int uAction, int uParm, string lpvParam, int fuWinIni);
public const int UpdateIniFile = 0x01;
public const int SendWinIniChange = 0x02;
public const int SetDesktopBackground = 20; <# following examples online to set parameters #>
public static void SetBackground() {
SystemParametersInfo(SetDesktopBackground, 0, "", UpdateIniFile | SendWinIniChange);
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true);
key.SetValue(#"WallPaper", 0); <#remove wallpaper#>
RegistryKey key2 = Registry.CurrentUser.OpenSubKey("Control Panel\\Colors", true);
key2.SetValue(#"Background", "0 118 163"); <#set background to new color>
key.Close();
key2.Close();
}
}
}
"#
[Background.Setter]::SetBackground()
The documented way to change the system colours is the SetSysColors function.
This sends the WM_SYSCOLORCHANGE message to all top-level windows to notify them of the change.
I've updated your class to clear the background and set the colour to purple. It would need copying into your PowerShell stuff. Note that the way I've declared SetSysColors you can only change one colour at a time.
public class Setter {
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo(int uAction, int uParm, string lpvParam, int fuWinIni);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SetSysColors(int count, [In] ref int index, [In] ref int colour);
public const int UpdateIniFile = 0x01;
public const int SendWinIniChange = 0x02;
public const int SetDesktopBackground = 20;
public const int COLOR_BACKGROUND = 1;
public static void SetBackground() {
SystemParametersInfo(SetDesktopBackground, 0, "", UpdateIniFile | SendWinIniChange);
int index = COLOR_BACKGROUND;
int colour = 0xFF00FF;
SetSysColors(1, ref index, ref colour);
}
}
Yesterday was my first Powershell experience and I was pretty lost with what I needed to do. In order to change the desktop background to a solid color, you first need to remove the wall paper and then you can use the SetSysColors function to immediately change the desktop background. This link helped me out tremendously. http://gallery.technet.microsoft.com/scriptcenter/Change-window-borderdesktop-609a6fb2
Hopefully this helps someone the same way it helped me.
Updated Code
$code = #'
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace Background
{
public class Setter {
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo(int uAction, int uParm, string lpvParam, int fuWinIni);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError =true)]
private static extern int SetSysColors(int cElements, int[] lpaElements, int[] lpRgbValues);
public const int UpdateIniFile = 0x01;
public const int SendWinIniChange = 0x02;
public const int SetDesktopBackground = 0x0014;
public const int COLOR_DESKTOP = 1;
public int[] first = {COLOR_DESKTOP};
public static void RemoveWallPaper() {
SystemParametersInfo( SetDesktopBackground, 0, "", SendWinIniChange | UpdateIniFile );
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true);
key.SetValue(#"WallPaper", 0);
key.Close();
}
public static void SetBackground(byte r, byte g, byte b) {
RemoveWallPaper();
System.Drawing.Color color= System.Drawing.Color.FromArgb(r,g,b);
int[] elements = {COLOR_DESKTOP};
int[] colors = { System.Drawing.ColorTranslator.ToWin32(color) };
SetSysColors(elements.Length, elements, colors);
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Colors", true);
key.SetValue(#"Background", string.Format("{0} {1} {2}", color.R, color.G, color.B));
key.Close();
}
}
}
'#
Add-Type -TypeDefinition $code -ReferencedAssemblies System.Drawing.dll -PassThru
Function Set-OSCDesktopColor
{
<# Powershell function to remove desktop background and set background to colors we want #>
Process
{
[Background.Setter]::SetBackground(0,118,163)
return
}
}
Set-OSCDesktopColor
I grabbed Wheatfairies code, updated it and published in (with Attribution) to the PowerShell Gallery at:
https://www.powershellgallery.com/packages/Set-DesktopBackGround/1.0.0.0/DisplayScript
So now everyone can just type:
Install-Script -Name Set-DesktopBackGround
to get it.
Cheers!
I saw this code for shadows around borderless windows but here i my problem. using System.Windows.Interop; is underlined and i cant find it in references. Also in public static void DropShadowToWindow(Window window) this Window is underlined so i guess its linked to Interop...
using System.Drawing.Printing;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
class DwmDropShadow
{
[DllImport("dwmapi.dll", PreserveSig = true)]
private static extern int DwmSetWindowAttribute(
IntPtr hwnd, int attr, ref int attrValue, int attrSize
);
[DllImport("dwmapi.dll")]
private static extern int DwmExtendFrameIntoClientArea(
IntPtr hWnd, ref Margins pMarInset
);
/// <summary>
/// Drops a standard shadow to a WPF Window, even a borderess window.
/// Only works with DWM (Vista and Seven).
///
/// This is much more efficient than setting AllowsTransparency to true
/// and using the DropShadow effect, as AllowsTransparency will turn off
/// acceleration for all the windows. (This is a huge performance issue.)
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
public static void DropShadowToWindow(Window window)
{
if (!DropShadow(window))
{
window.SourceInitialized +=
new EventHandler(window_SourceInitialized);
}
}
private static void window_SourceInitialized(object sender, EventArgs e)
{
Window window = (Window)sender;
DropShadow(window);
window.SourceInitialized -= new EventHandler(window_SourceInitialized);
}
/// <summary>
/// The actual method that makes API calls to drop the shadow to the window
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
/// <returns>True if the method succeeded, false if not</returns>
private static bool DropShadow(Window window)
{
try
{
WindowInteropHelper helper = new WindowInteropHelper(window);
int val = 2;
int ret1 = DwmSetWindowAttribute(helper.Handle, 2, ref val, 4);
if (ret1 == 0)
{
Margins m = new Margins {
Bottom = 0, Left = 0, Right = 0, Top = 0
};
int ret2 = DwmExtendFrameIntoClientArea(helper.Handle, ref m);
return ret2 == 0;
}
else
{
return false;
}
}
catch (Exception ex)
{
// Probably dwmapi.dll not found (incompatible OS)
return false;
}
}
}
It's in WindowsBase.DLL, which was introduced in .NET Framework 3.0. It is located in c:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll
Just in case someone comes here to search for System.Windows.Interop.CompositionMode: it didn't make it to the final .Net 4.5 version.
See http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2644120-bring-back-the-hwndhost-isredirected-and-compositi?page=2&per_page=20
I have a file type that I want to associate with my program. I could make every file of that type have the same standard icon like how all HTML files look the same or all txt files, but what I want to do is have every file show a preview of itself for its thumbnail, more like how jpg, bmp, and png show a thumbnail of that particular image file.
I mostly work in C# but I know something like this may require a little (or a lot) of C++ to do what I want to do and I'm ok with that if need be. I don't know where to begin as I've never tried this before. A little googling says that a COM object will do it, but I need more to go on than that.
EDIT
Here's what I have so far:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices.ComTypes;
namespace APKIconHandler {
[ComImport()]
[Guid("000214fa-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
//http://msdn.microsoft.com/en-us/library/windows/desktop/bb761852(v=vs.85).aspx
interface IExtractIcon {
/// <summary>
/// Gets the location and index of an icon.
/// </summary>
/// <param name="uFlags">One or more of the following values. This parameter can also be NULL.use GIL_ Consts</param>
/// <param name="szIconFile">A pointer to a buffer that receives the icon location. The icon location is a null-terminated string that identifies the file that contains the icon.</param>
/// <param name="cchMax">The size of the buffer, in characters, pointed to by pszIconFile.</param>
/// <param name="piIndex">A pointer to an int that receives the index of the icon in the file pointed to by pszIconFile.</param>
/// <param name="pwFlags">A pointer to a UINT value that receives zero or a combination of the following value</param>
/// <returns></returns>
///
[PreserveSig]
int GetIconLocation(IExtractIconuFlags uFlags, [Out, MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 2)] StringBuilder szIconFile, int cchMax, out int piIndex, out IExtractIconpwFlags pwFlags);
/// <summary>
/// Extracts an icon image from the specified location.
/// </summary>
/// <param name="pszFile">A pointer to a null-terminated string that specifies the icon location.</param>
/// <param name="nIconIndex">The index of the icon in the file pointed to by pszFile.</param>
/// <param name="phiconLarge">A pointer to an HICON value that receives the handle to the large icon. This parameter may be NULL.</param>
/// <param name="phiconSmall">A pointer to an HICON value that receives the handle to the small icon. This parameter may be NULL.</param>
/// <param name="nIconSize">The desired size of the icon, in pixels. The low word contains the size of the large icon, and the high word contains the size of the small icon. The size specified can be the width or height. The width of an icon always equals its height.</param>
/// <returns>
/// Returns S_OK if the function extracted the icon, or S_FALSE if the calling application should extract the icon.
/// </returns>
[PreserveSig]
int Extract([MarshalAs(UnmanagedType.LPWStr)] string pszFile, uint nIconIndex, out IntPtr phiconLarge, out IntPtr phiconSmall, uint nIconSize);
}
[Flags()]
public enum IExtractIconuFlags:uint
{
GIL_ASYNC=0x0020,
GIL_DEFAULTICON =0x0040,
GIL_FORSHELL =0x0002,
GIL_FORSHORTCUT =0x0080,
GIL_OPENICON = 0x0001,
GIL_CHECKSHIELD = 0x0200
}
[Flags()]
public enum IExtractIconpwFlags : uint
{
GIL_DONTCACHE = 0x0010,
GIL_NOTFILENAME = 0x0008,
GIL_PERCLASS = 0x0004,
GIL_PERINSTANCE = 0x0002,
GIL_SIMULATEDOC = 0x0001,
GIL_SHIELD = 0x0200,//Windows Vista only
GIL_FORCENOSHIELD = 0x0400//Windows Vista only
}
[Flags]
public enum IconHandlerReturnFlags {
SimulateDoc = 0x1,
PerInstance = 0x2,
PerClass = 0x4,
NotFilename = 0x8,
DontCache = 0x10
}
public class APKHandler : IExtractIcon, IPersistFile {
private const int S_OK = 0;
private const int S_FALSE = 1;
[ComRegisterFunctionAttribute]
public void DllRegisterDll() { }
public void GetClassID(out Guid g) {
g = new Guid("405a310a-b439-49b9-894a-cc55ffc6e91d");
}
public void GetCurFile(out String str) {
str = "CurFile";
}
public int IsDirty() {
return S_OK;
}
public void Load(string pszFileName, int dwMode) {
File.AppendAllText(#"C:\ApkHandler.txt", "Load :" + pszFileName + " , " + dwMode.ToString() + Environment.NewLine);
}
public void Save(string pszFileName, bool save) {
File.AppendAllText(#"C:\ApkHandler.txt", "Save :" + pszFileName + " , " + save + Environment.NewLine);
}
public void SaveCompleted(string pszFileName) {
File.AppendAllText(#"C:\ApkHandler.txt", "SaveCompleted :" + pszFileName + Environment.NewLine);
}
public int GetIconLocation(IExtractIconuFlags uFlags, StringBuilder szIconFile, int cchMax, out int piIndex, out IExtractIconpwFlags pwFlags)//Using IExtractIcon and IPersistFile.Load
{
piIndex = 0;
pwFlags = 0;
try {
pwFlags = IExtractIconpwFlags.GIL_PERCLASS | IExtractIconpwFlags.GIL_DONTCACHE | IExtractIconpwFlags.GIL_NOTFILENAME;
File.AppendAllText(#"C:\ApkHandler.txt", "GetIconLocation...");
return S_OK;
} catch (Exception e) {
File.AppendAllText(#"C:\ApkHandler.txt", "GetIconLocation " + e.Message);
return S_FALSE;
}
}
public int Extract(string pszFile, uint nIconIndex, out IntPtr phiconLarge, out IntPtr phiconSmall, uint nIconSize)//Using IExtractIcon
{
File.AppendAllText(#"C:\ApkHandler.txt", "Extract...");
phiconSmall = phiconLarge = IntPtr.Zero;
return S_OK;
}
}
}
I have edited my registry as the bottom of this page instructs (thanks arx).
I've toyed and tweaked with this over and over and have yet to had any of my functions called (as indicated by ApkHandler.txt never appearing). I disabled UAC so I don't think there's any permissions issues with creating the file in the root, I do it all the time while debugging. I'm trying not to get frustrated but this is really getting under my skin.
On the Media Foundation SDK there is the GetPhysicalMonitorsFromHMONITOR function
that I am trying to implement using C# but with no luck ...
In the returned PHYSICAL_MONITOR[], the function returns the string description of the monitor but for some mysterious reasons, the hPhysicalMonitor handle remains at 0.
I have generated the signatures with P/Invoke Interop Assistant with minor modifications.
Does the PHYSICAL_MONITOR structure or anything else needs further tuning ?
Thank you.
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using WindowsFormsApplication1;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public enum MC_DISPLAY_TECHNOLOGY_TYPE
{
MC_SHADOW_MASK_CATHODE_RAY_TUBE,
MC_APERTURE_GRILL_CATHODE_RAY_TUBE,
MC_THIN_FILM_TRANSISTOR,
MC_LIQUID_CRYSTAL_ON_SILICON,
MC_PLASMA,
MC_ORGANIC_LIGHT_EMITTING_DIODE,
MC_ELECTROLUMINESCENT,
MC_MICROELECTROMECHANICAL,
MC_FIELD_EMISSION_DEVICE,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct PHYSICAL_MONITOR
{
public IntPtr hPhysicalMonitor;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string szPhysicalMonitorDescription;
}
#region Imports
[DllImport("user32.dll", EntryPoint = "MonitorFromWindow")]
public static extern IntPtr MonitorFromWindow(
[In] IntPtr hwnd, uint dwFlags);
[DllImport("dxva2.dll", EntryPoint = "GetMonitorTechnologyType")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorTechnologyType(
IntPtr hMonitor, ref MC_DISPLAY_TECHNOLOGY_TYPE pdtyDisplayTechnologyType);
[DllImport("dxva2.dll", EntryPoint = "GetMonitorCapabilities")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorCapabilities(
IntPtr hMonitor, ref uint pdwMonitorCapabilities, ref uint pdwSupportedColorTemperatures);
[DllImport("dxva2.dll", EntryPoint = "DestroyPhysicalMonitors")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyPhysicalMonitors(
uint dwPhysicalMonitorArraySize, ref PHYSICAL_MONITOR[] pPhysicalMonitorArray);
[DllImport("dxva2.dll", EntryPoint = "GetNumberOfPhysicalMonitorsFromHMONITOR")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor, ref uint pdwNumberOfPhysicalMonitors);
[DllImport("dxva2.dll", EntryPoint = "GetPhysicalMonitorsFromHMONITOR")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor, uint dwPhysicalMonitorArraySize, [Out] PHYSICAL_MONITOR[] pPhysicalMonitorArray);
#endregion
public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e)
{
// Get monitor handle.
uint dwFlags = 0u;
IntPtr ptr = MonitorFromWindow(Handle, dwFlags);
// Get number of physical monitors.
uint pdwNumberOfPhysicalMonitors = 0u;
bool b1 = GetNumberOfPhysicalMonitorsFromHMONITOR(ptr, ref pdwNumberOfPhysicalMonitors);
if (b1)
{
// Get physical monitors.
uint dwPhysicalMonitorArraySize = 0u;
dwPhysicalMonitorArraySize = pdwNumberOfPhysicalMonitors;
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
//NOTE : Handles remain null !
bool b2 = GetPhysicalMonitorsFromHMONITOR(ptr, dwPhysicalMonitorArraySize, pPhysicalMonitorArray);
if (pPhysicalMonitorArray[0].hPhysicalMonitor
== IntPtr.Zero)
{
throw new Exception("ERROR !");
}
// Monitor has capabilities to do that ?
if (b2)
{
uint pdwMonitorCapabilities = 0u;
uint pdwSupportedColorTemperatures = 0u;
bool b3 = GetMonitorCapabilities(
ptr, ref pdwMonitorCapabilities, ref pdwSupportedColorTemperatures);
// If yes, get technology type.
if (b3)
{
MC_DISPLAY_TECHNOLOGY_TYPE type = MC_DISPLAY_TECHNOLOGY_TYPE.MC_SHADOW_MASK_CATHODE_RAY_TUBE;
bool b4 = GetMonitorTechnologyType(ptr, ref type);
if (b4)
{
// Do work.
}
else
{
throw new Exception("Couldn't get monitor technology type.");
}
}
else
{
throw new Exception("Couldn't get monitor capabilities.");
}
}
else
{
throw new Exception("The monitor doesn't have the required capabilities.");
}
bool b5 = DestroyPhysicalMonitors(dwPhysicalMonitorArraySize, ref pPhysicalMonitorArray);
if (!b5)
{
throw new Exception("Couldn't destroy physical monitors.");
}
}
else
{
throw new Exception("Couldn't get number of physical monitors.");
}
}
}
}
Your statement:
The function returns the string description of the monitor but for some mysterious reasons, the hMonitor handle remains at 0.
is correct. If you look at the docs here, you'll see that hMonitor is clearly an [in] parameter and will not be changed.
Update following comment:
Sorry, didn't realize you meant the physical handle being returned in the structure. All the information I can find on that particular problem seems to indicate that your monitor probably isn't fully DDC/CI compatible (e.g., here).
All your structure definitions look fine to me, based on the docs on MSDN for that particular call. And indeed, it is populating the description for you.
What is the value for the number of physical monitors being returned from GetNumberOfPhysicalMonitorsFromHMONITOR (pdwNumberOfPhysicalMonitors)?
Also, what is the size of your PHYSICAL_MONITOR structure and are you running in 32 or 64 bits?
It is alright that the hPhysicalMonitor value is 0. However, in the question's code sample all calls after the GetPhysicalMonitorsFromHMONITOR should use the hPhysicalMonitor reference instead of the ptr reference. The updated Form_Load method should be the following:
private void Form1_Load(object sender, EventArgs e)
{
// Get monitor handle.
uint dwFlags = 0u;
IntPtr ptr = MonitorFromWindow(Handle, dwFlags);
// Get number of physical monitors.
uint pdwNumberOfPhysicalMonitors = 0u;
bool b1 = GetNumberOfPhysicalMonitorsFromHMONITOR(ptr, ref pdwNumberOfPhysicalMonitors);
if (b1)
{
// Get physical monitors.
uint dwPhysicalMonitorArraySize = 0u;
dwPhysicalMonitorArraySize = pdwNumberOfPhysicalMonitors;
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
//NOTE : Handles remain null !
bool b2 = GetPhysicalMonitorsFromHMONITOR(ptr, dwPhysicalMonitorArraySize, pPhysicalMonitorArray);
// Monitor has capabilities to do that ?
if (b2)
{
uint pdwMonitorCapabilities = 0u;
uint pdwSupportedColorTemperatures = 0u;
bool b3 = GetMonitorCapabilities(pPhysicalMonitorArray[0].hPhysicalMonitor, ref pdwMonitorCapabilities, ref pdwSupportedColorTemperatures);
// If yes, get technology type.
if (b3)
{
MC_DISPLAY_TECHNOLOGY_TYPE type = MC_DISPLAY_TECHNOLOGY_TYPE.MC_SHADOW_MASK_CATHODE_RAY_TUBE;
bool b4 = GetMonitorTechnologyType(pPhysicalMonitorArray[0].hPhysicalMonitor, ref type);
if (b4)
{
// Do work.
}
else
{
throw new Exception("Couldn't get monitor technology type.");
}
}
else
{
throw new Exception("Couldn't get monitor capabilities.");
}
}
else
{
throw new Exception("The monitor doesn't have the required capabilities.");
}
bool b5 = DestroyPhysicalMonitors(dwPhysicalMonitorArraySize, ref pPhysicalMonitorArray);
if (!b5)
{
throw new Exception("Couldn't destroy physical monitors.");
}
}
else
{
throw new Exception("Couldn't get number of physical monitors.");
}
}
The monitor supports this function because with software like softMCCS and WinI2C/DDC,
the properties are returned correctly.
The return pdwNumberOfPhysicalMonitors value is 1 which is correct.
As you can see, its size is pdwNumberOfPhysicalMonitors :
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
And I am running Vista 32.
It is somewhat strange because half of it works, that's now about 4 days I am over it but still no progress ...
Thank you.