Is it possible to enable a second monitor programatically and extend the Windows Desktop onto it in C#? It needs to do the equivalent of turning on the checkbox in the image below.
MSDN Device Context Functions
What you basically need to do:
Use the EnumDisplayDevices() API call
to enumerate the display devices on
the system and look for those that
don't have the
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP
flag set (this will include any
mirroring devices so not all will be
physical displays.) Once you've found
the display device you'll need to get
a valid display mode to change it to,
you can find this by calling the
EnumDisplaySettingsEx() API call -
Generally you'd display all the
available modes and allow the user to
choose however in your case it sounds
like this may be possible to hard-code
and save you an additional step. For
the sake of future-proofing your
application though I'd suggest having
this easily changeable without having
to dig through the source every time,
a registry key would be the obvious
choice. Once you've got that sorted
out populate a DevMode display
structure with the information about
the display positioning (set the
PelsWidth/Height, Position,
DisplayFrequency and BitsPerPel
properties) then set these flags in
the fields member. Finally call
ChangeDisplaySettingsEx() with this
settings structure and be sure to send
the reset and update registry flags.
That should be all you need, hope this
helps,
DISPLAY_DEVICE structure import using PInvoke
EnumDisplayDevices function import
EnumDisplaySettingsEx function import
etc. the rest of them functions can be found with a simple search by name.
If you have windows 7, then just start a process:
private static Process DisplayChanger = new Process
{
StartInfo =
{
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "DisplaySwitch.exe",
Arguments = "/extend"
}
};
then DisplayChanger.Start();
I don't have the full answer here but I am almost sure that you will have to call out of .Net to do this. You will have to use Pinvoke to call an unmanaged dll. A great resource for this is pinvoke.net.
I did a quick search and found http://www.pinvoke.net/default.aspx/user32/ChangeDisplaySettings.html which probably isn't exactly what you want but you will probably find it somewhere on pinvoke.net
I am looking for the same solution. I have written the following code to call ChangeDisplaySettingsEx with PInvoke:
DEVMODE dm = new DEVMODE();
dm.dmSize = (short)Marshal.SizeOf(dm);
dm.dmPelsWidth = 1680;
dm.dmPelsHeight = 1050;
dm.dmBitsPerPel = 32;
dm.dmDisplayFrequency = 60;
dm.dmFields = DevModeFields.DM_BITSPERPEL | DevModeFields.DM_PELSWIDTH |
DevModeFields.DM_PELSHEIGHT | DevModeFields.DM_DISPLAYFREQUENCY;
int res = ChangeDisplaySettingsEx(#"\\.\DISPLAY2", ref dm, IntPtr.Zero, CDS_RESET | CDS_UPDATEREGISTRY, IntPtr.Zero);
Console.WriteLine("result = " + res.ToString());
If the monitor is already enabled, this changes the resolution successfully. But if the monitor isn't attached to the desktop already, this won't activate it. So does anyone have a code example that works?
To enable a monitor, set its position to something other than 0,0, like as shown:
POINTL enabledPosition = new POINTL();
enabledPosition.x = -1280;
enabledPosition.y = 0;
dm.dmPosition = enabledPosition;
dm.dmFields = DM.Position;
res = ChangeDisplaySettingsEx(d.DeviceName, ref dm, IntPtr.Zero, (uint) DeviceFlags.CDS_UPDATEREGISTRY, IntPtr.Zero);
Related
I am trying to create an application in C# using which I need to change the BackColor/Forecolor of some specific controls in another process/application. For example I have few application running in my machine. When I run the new C# application (which does not have any UI), the backcolor/forecolor of few controls in other applications should be changed to the new colors which I set. And when I close the application, the colors should be reset to the original colors. I tried the SetTextColor, SetBkColor windows apis from gdi32 but they dont work. Could you suggest me how can I achieve this?
Note: The coordinates of the controls for which the colors need to be changed are already noted and I am using the WindowFromPoint api to get the handle of the control.
This is the code which I tried.
point = new Point(1340, 144);
IntPtr hWnd = DllImports.WindowFromPoint(point);//this is a button control in one of the applications
if (hWnd != IntPtr.Zero)
{
IntPtr wDC = DllImports.GetDC(hWnd);
int result = DllImports.SetBkMode(wDC, TRANSPARENT);
int i = DllImports.SetTextColor(wDC, ColorTranslator.ToWin32(Color.Red));
i = DllImports.SetBkColor(wDC, ColorTranslator.ToWin32(Color.Yellow));
result = DllImports.SetBkMode(wDC, OPAQUE);
}
Thanks in advance
since some time now I try to figure out how to correctly setup this new UWF (Unified Write Filter). Unfortunately it seems there is only documentation for Win 8.1 industry (here), not for Win 10. I hope there were no relevant changes since.
I also asked this on the WindowsDevCenter but got no response so far.
Here is my problem:
With the WMI providers I got UWF enabled by now (UWF_Filter.Enable()), but I cannot protect any volume.
Also the volume list looks very strange: There are 4 entrys, everyone is with CurrentSession=True.
The first is for an volume with no drive letter, only a volume id.
The second is for C:
and then there are 2 identical for D: .
Should'nt there normally be 2 entrys per volume, one where CurrentSession is true and one where its false, meaning its the setting applied after reboot?
If I try to execute Protect on the ManagementObject with DriveLetter=C: I get an Access denied exception, I assume because its the object for the current session.
Also if I try uwfmgr.exe Volume Protect C: on the console it simply hangs: no reaction, no error, only a forever blinking cursor. EDIT: it turned out this was a problem caused by another installed software. See also below.
Do I have to enable or disable or do anything else before I can protect volumes?
Thanks in advance,
Sebastian
My system:
Windows 10 IOT Enterprise 2016 LTSB x64
1 SSD 250GB with Boot, C: and D:
Edit:
Here I asked a follow up question with some other details and a workaround. If I use uwfmgr.exe volume protect c: for example, it works and UWF_Volume now suddenly has (the correct) 2 entries for C:, one for the current and one for the next session.
However I want to avoid this, because IMHO it should be solveable by WMI only.
Edit 2: #sommmen
The partition layout is as following: One disk with 4 partitions.
Boot, 500MB
C:/ , 45GB
unknown, 500MB (Boot-Backup I think)
D:/ , ~200GB
PS:
Please could anyone create the tags uwf and uwfmgr? Would be nice :-)
Missing UWF_Volume instances often appeared after reboot in my tests. But if not, you can create them directly using ManagementClass.CreateInstance().
The problem here is that the official docs are not exactly correct. The description of the UWF_Volume.VolumeName property is:
The unique identifier of the volume on the current system. The
VolumeName is the same as the DeviceID property of the Win32_Volume
class for the volume.
from: https://learn.microsoft.com/en-us/windows-hardware/customize/enterprise/uwf-volume#properties
In fact, the DeviceID needs a slight modification, before using it as value for UWF_Volume.VolumeName:
DeviceID.Substring(4).TrimEnd('\\')
So, after removing prefix \\?\ and removing any trailing slashes you can create instances with CurrentSession=false for the specified device.
This also works in Windows 10 Pro without any uwfmgr.exe. Though, officially not recommended/supported.
Also, I was not able to delete instances, yet. So be sure to add only correct values.
Full Example:
// example value
var DeviceId_From_Win32_Volume = #"\\?\Volume{c2eac053-27e3-4f94-b28c-c2c53d5f4fe1}\";
// example value
var myDriveLetter = "C:";
var myDeviceId = DeviceId_From_Win32_Volume.Substring(4).TrimEnd('\\');
var wmiNamespace = "root\\standardcimv2\\embedded";
var className = "UWF_Volume";
var mgmtScope = new ManagementScope {Path = {NamespacePath = wmiNamespace}};
var mgmtPath = new ManagementPath(className);
var mgmtClass = new ManagementClass(mgmtScope, mgmtPath, null);
// prepare the new object
var newObj = mgmtClass.CreateInstance();
newObj.SetPropertyValue("DriveLetter", myDriveLetter);
newObj.SetPropertyValue("VolumeName", myDeviceId);
newObj.SetPropertyValue("CurrentSession", false);
newObj.SetPropertyValue("CommitPending", false);
newObj.SetPropertyValue("BindByDriveLetter", false);
// create the WMI instance
newObj.Put(new PutOptions {Type = PutType.CreateOnly});
I experience the similar issue in that I could not query the UWF_Volume with CurrentSession=False. However, there's one thing I did that seems to "generate" the UWF_Volume management object with CurrentSession=False. I ran "uwfmgr volume protect c:". Unfortunately, in your case running this causes it to hang.
Could you try running uwfmgr in cmd in admin? Also, if you run "uwfmgr get-config", would you be able to get the current setting of the write filter?
Another thing from your description: you said there are two identical volumes for D:, but if you looks closely at the properties, one would be CurrentSession=True, and the other one is CurrentSession=False. According to the documentation, if you want to make change, you must select the management object (UWF_Volume) with CurrentSession=False.
https://learn.microsoft.com/en-us/windows-hardware/customize/enterprise/uwf-volume
(scroll down to powershell script code sample section)
First of all a volume may have several partitions. They will show up as having the same drive label.
e.g.
C:/ //?/{some guid here}
C:/ //?/{some other guid here}
Now this is common for the %systemDrive% because this has the boot partition.
You can use the commands
mountvol
and
Diskpart
List volume
To figure out the right guid for your need (or you can protect both the boot partition and the system partition). Also using wmi you can look at Win32_volume under namespace cimv2 to get some more insight.
The command line util UWFmgr seems to create an UWF_VOLUME wmi instance once you run the protect command. The docs also hint that you need to create an object yourself.
function Set-ProtectVolume($driveLetter, [bool] $enabled) {
# Each volume has two entries in UWF_Volume, one for the current session and one for the next session after a restart
# You can only change the protection status of a drive for the next session
$nextConfig = Get-WMIObject -class UWF_Volume #CommonParams |
where {
$_.DriveLetter -eq "$driveLetter" -and $_.CurrentSession -eq $false
};
# If a volume entry is found for the drive letter, enable or disable protection based on the $enabled parameter
if ($nextConfig) {
Write-Host "Setting drive protection on $driveLetter to $enabled"
if ($Enabled -eq $true) {
$nextConfig.Protect() | Out-Null;
} else {
$nextConfig.Unprotect() | Out-Null;
}
}
=======> (!) im talking about this comment
# If the drive letter does not match a volume, create a new UWF_volume instance
else {
Write-Host "Error: Could not find $driveLetter. Protection is not enabled."
}
}
The docs however do not provide a method of doing this. For now it seems we need to use the command line util till someone has an example using the WMI provider.
To answer my own question: So far I have only a workaround but no real solution.
It is to check if there is an entry with CurrentSession=False and if not invoke the command directly:
ManagementObjectSearcher ms = new ManagementObjectSearcher(_Scope, new ObjectQuery("select * from UWF_Volume where VolumeName = \"" + volId + "\" AND CurrentSession=\"False\""));
ManagementObjectCollection c = ms.Get();
UInt32 res = 1;
foreach (ManagementObject mo in c)
{
// entry found: do it with WMI
res = (UInt32)mo.InvokeMethod(newState ? "Protect" : "Unprotect", new object[] { });
}
if (c.Count == 1 && res == 0)
// message: success
if (c.Count == 0)
{
// no entry found: invoke cmd
ProcessStartInfo info = new ProcessStartInfo("uwfmgr.exe", "volume " + (newState ? "Protect" : "Unprotect") + #" \\?\" + volId);
Process process = new Process();
info.Verb = "runas"; //needs admin
process.StartInfo = info;
process.Start();
process.WaitForExit();
}
This has the side effect that for a split second a command line window will pop up, but nevertheless it works well.
I have seen numerous posts on this subject here, but none seem to answer this issue directly. I want to control two instances of Powerpoint running on a second monitor.
The ideal solution looks like this:
PowerPoint.Application PPTViewer1 = new PowerPoint.Application();
PowerPoint.Application PPTViewer2 = new PowerPoint.Application();
I can do this manually, simply by starting two instances of PowerPoint, loading the presentation, and starting the slide show from each instance. I can toggle back and forth between the two slide shows manually, with each being brought to the front as expected.
So... how do I do this programatically using VSTO and C#?? Like others before me, I see that the Interop.PowerPoint interface will create only the single instance. If that were not the case, then I could achieve the results I am looking for easily enough.
Additionally, I am not looking for a third party component for this task.
Any help is appreciated.
Thanks in advance.
It may appear that you're running multiple instances of Powerpoint, but you're not. It only allows one instance of itself. If you see two instances of Powerpnt.exe in the task list, as sometimes happens, it means that something's gone wrong and left a zombie in memory.
May not be totally ideal but here is a reference that suggested to start an instance as a different user (Note that this site is for PowerPoint 2007).
runas /user:username "C:\Program Files\Microsoft Office\Office12\POWER PNT.EXE"
Each instance of the Powerpoint COM object shares the same fullscreen display window. I know of no method to switch which presentation has that window
The solution is to host the Powerpoint display in your own window
This therefore allows you to scale the window and show multiple presentations on one monitor, or move it from one monitor to another?
e.g.
var display1 = new FullScreenDisplay(); // A form with BorderStyle = None
display1.Show();
application1 = new PowerPoint.Application();
presentation1 = application1.Presentations.Open2007(....);
var slideShowSettings1 = presentation1.SlideShowSettings;
slideShowSettings1.ShowType = PowerPoint.PpSlideShowType.ppShowTypeSpeaker;
var slideShowWindow1 = slideShowSettings1.Run();
IntPtr hwnd1 = (IntPtr)slideShowWindow1.HWND;
SetParent(hwnd1, display1.Handle);
var display2 = new FullScreenDisplay();
display2.Show();
application2 = new PowerPoint.Application();
presentation2 = application2.Presentations.Open2007(....);
var slideShowSettings2 = presentation2.SlideShowSettings;
slideShowSettings2.ShowType = PowerPoint.PpSlideShowType.ppShowTypeSpeaker;
var slideShowWindow2 = slideShowSettings2.Run();
IntPtr hwnd2 = (IntPtr)slideShowWindow2.HWND;
SetParent(hwnd2, display2.Handle);
display1.BringToFront(); // to show slideshow 1
// or
display2.BringToFront(); // to show slideshow 2
// To advance a slide
presentation1.SlideShowWindow.View.Next();
// or
presentation2.SlideShowWindow.View.Next();
// To exit, note order!
presentation2.SlideShowWindow.View.Exit();
presentation1.SlideShowWindow.View.Exit();
Application.Exit();
This is a hack, and may not work in future versions of Powerpoint?
You also need this import
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
I am using the ManagedWindows API in a C# environment:
http://mwinapi.sourceforge.net/
In the past I have successfully scraped the contents of listbox-like parts of other running programs using the code below, where I iterate through key/value pairs to find the list items. For this particular list of items, however, I can get an accurate number of items, but the value is always null!
Using this:
TargetMidWindow.Content.ComponentType
I have discovered that the list I am having issues with is a 'listview' whereas the other windows I have had success with are 'detailslistview' in case it matters. Below is the code I have for finding the data I want, which is almost identical to my other successful code with the exception of altering the search terms I used. Also, in case its relevant, the program I'm trying to pull data out of is MetaTrader4, and I've been able to scrape data off other parts of the program successfully.
// Find the main window
SystemWindow[] TopLevel = SystemWindow.AllToplevelWindows;
SystemWindow TargetTopWindow = SystemWindow.ForegroundWindow;
foreach (SystemWindow SearchWindow in TopLevel)
{
string Title = SearchWindow.Title;
if (Title.Contains("MetaTrader"))
{
TargetTopWindow = SearchWindow;
break;
}
}
// Find the section where positions are contained
SystemWindow[] MidLevel = TargetTopWindow.AllDescendantWindows;
SystemWindow TargetMidWindow = SystemWindow.ForegroundWindow;
foreach (SystemWindow SearchWindow in MidLevel)
{
string ClassName = SearchWindow.ClassName;
if (ClassName.Contains("SysListView32"))
{
SystemWindow ParentWindow = SearchWindow.Parent;
if ((ParentWindow.Title.Contains("Terminal")))
{
TargetMidWindow = SearchWindow;
}
}
}
// Get the positions
Dictionary<string, string> RawValues = new Dictionary<string, string>();
foreach (KeyValuePair<string, string> KVP in TargetMidWindow.Content.PropertyList)
{
string key = KVP.Key;
string value = KVP.Value;
}
Is there something special I need to do so that I do not get 'null' values for each list item?
Thanks!
Bill
Hmya, wrapping Windows messages with a friendly API isn't that difficult. Windows Forms would be a good example. But that has a knack for running into a very solid wall once you start doing this with another process.
The specific message you need in order to read ListView items is LVM_GETITEM. That's one of those solid wall messages. The LPARAM argument you pass to SendMessage() needs to be a pointer to an LVITEM structure. The control fills in the fields in that structure. Problem is, the pointer you pass is only valid in your process, not the process who owns that window.
Fixing this takes a great deal of hackery. You have to allocate memory that's valid inside that process. That takes VirtualAllocEx() and ReadProcessMemory(). Plus all the glue calls you need to make these work. I assume that this library you are using is not taking care of this. Easy to find out, grep the source code files for these API function names.
If you want to find the correct handle to a particular SysListView32 window, you need to start with the right window hierarchy. From the code snippet, it doesn't appear that you're actually finding the correct handle to retrieve a quote from the SysListView32 window. This is why you're receiving null values back. You would do well to run spy++ and determine the correct windows structure of the Metatrader terminal for your specific broker and build. I've found that the classes are different between builds for some of the windows, and also between some brokers, though to a lesser extent.
You're looking for the specific quote window hierarchy like this:
Metatrader -> Market Watch -> Market Watch -> SysListView32
By contrast, currently you're looking here in your code:
Metatrader -> Terminal -> (many sub-windows with SysListView32 class)
Where each level to the right is a child window of the window to the left.
Find the parent "Metatrader" window then chain down looking for the child window until you get to SysListView32. If you use spy++ you can read the class for the SysListView32 parent window (market watch), and use that to enumerate the windows to find the correct SysListView32 window. FYI the correct Market Watch class name for build 419 is:
Afx:00400000:b:00010003:00000000:00000000
Once you find the correct window, you may be able to extract its contents using your current component. I haven't tried that and am looking to port some code from VB6 from a ListView module that does in fact involve epic hackery. ;) I may take a look at the .NET Managed Windows API to see if this can help make the process simpler.
But in the mean time, if you do have to go low-level, the following VB6 source should help you get an idea of what is involved. This is fairly advanced material so good luck!
Public Function GetListviewItem(ByVal hWindow As Long, ByVal pColumn As Long, ByVal pRow As Long) As String
Dim result As Long
Dim myItem As LV_ITEMA
Dim pHandle As Long
Dim pStrBufferMemory As Long
Dim pMyItemMemory As Long
Dim strBuffer() As Byte
Dim index As Long
Dim tmpString As String
Dim strLength As Long
Dim ProcessID As Long, ThreadID As Long
ThreadID = GetWindowThreadProcessId(hWindow, ProcessID)
'**********************
'init the string buffer
'**********************
ReDim strBuffer(MAX_LVMSTRING)
'***********************************************************
'open a handle to the process and allocate the string buffer
'***********************************************************
pHandle = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, ProcessID)
pStrBufferMemory = VirtualAllocEx(pHandle, 0, MAX_LVMSTRING, MEM_COMMIT, PAGE_READWRITE)
'************************************************************************************
'initialize the local LV_ITEM structure
'The myItem.iSubItem member is set to the index of the column that is being retrieved
'************************************************************************************
myItem.mask = LVIF_TEXT
myItem.iSubItem = pColumn
myItem.pszText = pStrBufferMemory
myItem.cchTextMax = MAX_LVMSTRING
'**********************************************************
'write the structure into the remote process's memory space
'**********************************************************
pMyItemMemory = VirtualAllocEx(pHandle, 0, Len(myItem), MEM_COMMIT, PAGE_READWRITE)
result = WriteProcessMemory(pHandle, pMyItemMemory, myItem, Len(myItem), 0)
'*************************************************************
'send the get the item message and write back the memory space
'*************************************************************
result = SendMessage(hWindow, LVM_GETITEMTEXT, pRow, ByVal pMyItemMemory)
result = ReadProcessMemory(pHandle, pStrBufferMemory, strBuffer(0), MAX_LVMSTRING, 0)
result = ReadProcessMemory(pHandle, pMyItemMemory, myItem, Len(myItem), 0)
'**************************************************
'turn the byte array into a string and send it back
'**************************************************
For index = LBound(strBuffer) To UBound(strBuffer)
If Chr(strBuffer(index)) = vbNullChar Then Exit For
tmpString = tmpString & Chr(strBuffer(index))
Next index
tmpString = Trim(tmpString)
'**************************************************
'deallocate the memory and close the process handle
'**************************************************
result = VirtualFreeEx(pHandle, pStrBufferMemory, 0, MEM_RELEASE)
result = VirtualFreeEx(pHandle, pMyItemMemory, 0, MEM_RELEASE)
result = CloseHandle(pHandle)
If Len(tmpString) > 0 Then GetListviewItem = tmpString
End Function
I am trying to use the mixerGetLineInfo and mixerGetLineControls functions to get access to the the volume control for the default recording device opened with waveIn. I have written C# interop code that can successfully enumerate through all the sources destinations and controls in the system, but working out which is the control associated with the default waveIn device has so far eluded me. Does anyone have some sample code that does this?
You could use:
int mixerId = -1;
int inputID = MmeMixerApi.WAVE_MAPPER; // = -1
int result = MmeMixerApi.mixerGetID(inputId, ref mixerId, MIXER_OBJECTFLAG.WAVEIN);
The default input and output devices can be accessed through the wave mapper which has an ID of -1. mixerGetID will return the mixer ID associated with that input. You can then use the mixer ID to iterate over the controls. You would still need to find the correct source line (e.g. microphone, line-in etc.). For this you may want to look for a source line with a particular dwComponentType such as MIXERLINE_COMPONENTTYPE.SRC_MICROPHONE or MIXERLINE_COMPONENTTYPE.SRC_LINE.