using System;
using System.Net;
using System.IO;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;
using System.Text;
using System.Drawing;
using System.Web;
using System.Linq;
using System.Runtime.InteropServices;
// V1.1
// by: Christian Treffler
//
// The function allows to store a setup of open Explorer windows for a given parent window.
// Use hotkey 'Alt + Ctrl + Win + Y' while the parent window is focused.
// Both the paths and the positions of these windows are stored for the current monitor configuration.
// If the parent window is opened while the spacebar is pressed, the complete setup is restored.
// Individual setups can be stored for different monitor configurations. If a setup does not exist for
// the current monitor configuration, the script applies one of the existing ones and assures that
// no window is outside the visible area.
// A kind of recursion could occur, but is per default prevented in the code:
// If one of the windows which are opened for a parent folder is itself a parent folder for a project setup,
// it would cause the respective setup to be executed, too.
// If you want to allow this recursion, set 'PreventRecursion' below to 'false'.
//
// You need to assign:
// - 'Alt + Ctrl + Win + Y' as hotkey to run this scripted function
// - A trigger for:
// - 'Window created',
// - 'Process Filename': 'C:\Windows\explorer.exe',
// - 'Windows Class': 'CabinetWClass'
// - 'Run Function': 'Project Setup Opener'
//
// If you want to change the hotkey 'Alt + Ctrl + Win + Y', or want a different key than the spacebar to
// restore setups, make sure to adjust the strings 'KeyDownOpener', 'KeyDownOpener2' and 'KeyDownCreator' below to your choices
//
// History:
// V1.1:
// - z-Order of Windows (Which one is above others) is now taken into account when storing setups
// - Helpers for Debugging and Logging
// - Bugfix: Check, if item is null in ExplorerWindows.InitExplorerHandler
// - Bugfix: Wait 500ms before getting Explorer-Windows. Otherwise the path to the windows might be empty
// - Bugfix: Increase wait time from 200ms to 500ms between opening windows and moving them. Otherwise some won't move.
// - Bugfix: Build hash for stored setup from full path, not directory name. it can happen that different paths have the same directry name
// - InitExplorerHandler: Replaced Parallel.For loop with a single thread for loop. Suspicion of threads not beeing stopped.
// V1.2:
// - Sometimes some Windows still don't move to their place
// - Add a one time repeat of window moving after 'allpresent == true'
// - decrease wait time while iteration to speed it up
// V1.3:
// - Bugfix: The decrease did not work because of 'if (waittime > 500)'
// - Bugfix:
// GetHashCode does not work as expected anymore since DisplayFusion 10.0
// See https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/ for details and fix
// - Only run the script, if windowHandle belongs to an Explorer window
// - Recursion prevention very early in the script
// - Move parent window immediately, so that user has feedback and can release the space bar
// - Prevent invalid state
// - Having the class ExplorerWindows as static causes lots of problems. Renamed to ExplorerWindowsHandler
// and created an instance of the class in the main function instead
// - Prevent storage of empty logs
// - Improve logging
public static class DisplayFusionFunction
{
// Virtual Key Codes: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
// When this key is pressed while opening an explorer window,
// the function will check, if a project setup exists and apply it
private const string KeyDownOpener = "32"; // Spacebar
private const string KeyDownOpener2 = "18;32"; // Ctrl + Spacebar. This is for opening a folder with ctrl - doubleclick and space
// When this hotkey combination is pressed, the function will store the current project setup
// This string must be identical to the key combination used to start this function
private const string KeyDownCreator = "17;18;89;91"; // Alt + Ctrl + Win + Y
private const bool PreventRecursion = true; // Set to false, if recursion should be allowed
private const int BlockTime = 8000; // Block recursion for <BlockTime> ms.
private const int WaitTimeStart = 400; // Wait before final loading of currently open Explorer windows.
private const int WaitTimeMove = 300; // Wait before moving recently opened windows.
private const int MoveCountMax = 10; // Max number of trials to open windows
private const bool ErrorMessages = false; // Set to true for debugging Purposes
private const bool Logging = false; // Set to true for for additional Logging Messages
public static void Run(IntPtr windowHandle)
{
if(BFS.Window.GetClass(windowHandle) != "CabinetWClass") return; // V1.3 Only run the script, if windowHandle belongs to an Explorer window
LogFile LogToFile = new LogFile();
LogToFile.ErrorMessages = ErrorMessages;
LogToFile.Log = Logging;
try
{
ProjectSetups SavedSetups = null;
ProjectSetup LoadedSetup = null;
string currPath = "";
string currDirectory = "";
string ProfileName = "";
uint trialtime = WaitTimeStart;
ExplorerWindowsHandler ExplorerWindows = new ExplorerWindowsHandler();
bool opener = BFS.Input.IsKeyDown(KeyDownOpener)
|| BFS.Input.IsKeyDown(KeyDownOpener2); // Folder opened while Spacebar is pressed?
bool creator = BFS.Input.IsKeyDown(KeyDownCreator); // Started by Hotkey?
if (opener && creator) // V1.3: Prevent invalid state
{
opener = false;
creator = false;
LogToFile.AddMessage("Invalid");
}
if (opener && PreventRecursion)
{
int timeblocked = BFS.ScriptSettings.ReadValueInt("PSO_TimeBlocked");
int timenow = Environment.TickCount;
LogToFile.AddMessage("Prevent Recursion", true);
if (timeblocked > timenow) // System probably restarted, timeblocked is not valid
{
timeblocked = 0;
LogToFile.AddMessage("Probable Restart");
}
if (timeblocked + BlockTime > timenow)
{
opener = false;
LogToFile.AddMessage("Blocked: " + timeblocked.ToString() + "; " + timenow.ToString());
}
else
{
BFS.ScriptSettings.WriteValueInt("PSO_TimeBlocked", timenow);
LogToFile.AddMessage("PSO_TimeBlocked: " + timenow.ToString());
}
LogToFile.SetIndent(false);
}
ExplorerWindows.ErrorMessages = ErrorMessages;
// V1.3: Move parent window immediately, so that user has feedback and can release the space bar
if (opener || creator) // Standard initialization for all purposes
{
ExplorerWindows.LogToFile = LogToFile;
LogToFile.AddMessage("Initialization", true);
while ((trialtime > 100) && (currPath.Length == 0))
{
ExplorerWindows.InitExplorerHandler(windowHandle);
currPath = ExplorerWindows.GetPath(windowHandle, true); // Path to current folder which is the parent for the Project Setup
if(currPath.Length == 0)
{
trialtime = trialtime - 100;
BFS.General.ThreadWait(100);
}
}
LogToFile.SetIndent(false);
if(currPath.Length > 0)
{
try
{
LogToFile.AddMessage("Path: " + currPath);
LogToFile.AddMessage("Remaining Waitime: " + trialtime.ToString(), true);
currDirectory = Path.GetFileName(currPath); // Name of the current folder
LogToFile.AddMessage("Load Setup: " + currPath.GetDeterministicHashCode() + " - " + currDirectory + ".xml");
SavedSetups = LoadProjectSetups(currDirectory, currPath); // Check, if project setup exists (otherwise empty setup)
// Get Resolution of all Monitors and create profile name
Rectangle[] Monitors = BFS.Monitor.GetMonitorBounds();
ProfileName = buildProfileName(Monitors);
LogToFile.AddMessage("SavedSetups: " + SavedSetups.SetupName + "; " + SavedSetups.HasSetups().ToString());
// Move parent window
string path = ExplorerWindows.GetPath(windowHandle, false);
LoadedSetup = SavedSetups.GetSetup(ProfileName);
Rectangle rect = LoadedSetup.Prgrms[path];
BFS.Window.SetSizeAndLocation(windowHandle, rect.Left, rect.Top, rect.Width, rect.Height);
LogToFile.AddMessage("Parent Window: " + windowHandle.ToString() + ": " + rect.ToString());
}
catch (Exception e)
{
if (ErrorMessages) BFS.Dialog.ShowMessageError("Parent:\n" + e.ToString());
LogToFile.AddMessage("Exception in Parent: " + e.ToString());
}
}
else
{
opener = false;
creator = false;
LogToFile.AddMessage("Abort", false);
}
}
if (opener || creator)
{
// Get all open Explorer windows and set relevant variables
BFS.General.ThreadWait(trialtime); // Bugfix V1.1. Otherwise the paths to windows might be empty
ExplorerWindows.InitExplorerHandler(windowHandle);
LogToFile.SetIndent(false);
}
if (opener) // Apply Project Setup
{
LogToFile.AddMessage("Opener", true);
if ( opener
&& SavedSetups.HasSetups())
{
LogToFile.AddMessage("Load Setup", true);
LoadedSetup = SavedSetups.GetSetup(ProfileName);
// Open windows, if they are not already open
foreach (KeyValuePair<string, Rectangle> window in LoadedSetup.Prgrms)
{
LogToFile.AddMessage("Handle: " + HttpUtility.UrlDecode(window.Key, System.Text.Encoding.Default));
IntPtr handle = ExplorerWindows.GetHandle(window.Key, false);
if (handle == IntPtr.Zero)
{
uint appid = BFS.Application.Start(HttpUtility.UrlDecode(window.Key, System.Text.Encoding.Default));
LogToFile.AddMessage("Start Application");
}
}
bool allpresent = false;
bool onetimerepeat = false;
int counter = 0;
uint waittime = WaitTimeMove;
uint delta = WaitTimeMove / MoveCountMax;
List <IntPtr> allwindows = new List <IntPtr>();
// It seems that there is a considerable time between 'BFS.Application.Start' and the actual existence of the
// respective explorer window. In order to speed up, the delay time is cut short and we try several times until
// all windows are present
while ( ((allpresent == false) || (onetimerepeat == false)) && (counter < MoveCountMax) )
{
// Allow all windows to open
// V1.1: 200ms seemed to be too short in the past, 300ms did not work either. Try 500ms
// V1.2: Start with 'waittime = 500', then decrease every iteration
// V1.3: Bugfix: The decrease did not work because of 'if (waittime > 500)'
BFS.General.ThreadWait(waittime);
if (waittime > ((100 > delta) ? 100 : delta))
{
waittime = waittime - delta;
}
onetimerepeat = allpresent; // V1.2: If all windows are present now, repeat one more time
allpresent = true;
counter++;
LogToFile.AddMessage("While loop: " + allpresent.ToString() + "; " + onetimerepeat.ToString() + "; " + counter.ToString(), true);
ExplorerWindows.LogToFile = LogToFile;
ExplorerWindows.InitExplorerHandler(windowHandle);
// Move the windows to their positions
foreach (KeyValuePair<string, Rectangle> window in LoadedSetup.Prgrms)
{
IntPtr handle = ExplorerWindows.GetHandle(window.Key, true);
if (handle != IntPtr.Zero)
{
if (!allwindows.Contains(handle))
{
allwindows.Add(handle);
Rectangle rect = window.Value;
BFS.Window.SetSizeAndLocation(handle, rect.Left, rect.Top, rect.Width, rect.Height);
LogToFile.AddMessage(window.Key + "; " + handle.ToString() + ": " + rect.ToString());
}
}
else
{
allpresent = false;
LogToFile.AddMessage(window.Key + ": missing");
}
}
LogToFile.SetIndent(false);
}
BFS.General.ThreadWait(1000); // Allow all windows to find their place
// Find windows which are not on a visible screen
LogToFile.AddMessage("Check off screen", true);
foreach (IntPtr handle in allwindows)
{
Rectangle WinRect = BFS.Window.GetBounds(handle);
bool moveit = false;
uint monitorid = BFS.Monitor.GetMonitorIDByWindow(handle);
if(monitorid == 0) // Not on any monitor
{
moveit = true;
}
else
{
Rectangle IntersectSize = Rectangle.Intersect(WinRect, BFS.Monitor.GetMonitorBoundsByID(monitorid));
if (IntersectSize.Width < 3 || IntersectSize.Height < 3) // Have a monitor, but barely visible
{
moveit = true;
}
}
LogToFile.AddMessage(handle.ToString() + ", moveit: " + moveit.ToString());
if(moveit)
{
BFS.DisplayFusion.RunFunctionWithWindowHandle("Move Window to Current Monitor", handle);
BFS.Window.MoveToCentreMonitor(handle);
BFS.Window.SetSize(handle, WinRect.Width, WinRect.Height);
}
}
LogToFile.SetIndent(false);
LogToFile.SetIndent(false);
}
LogToFile.SetIndent(false);
}
else if (creator) // Save Project Setup
{
LogToFile.AddMessage("Creator", true);
ProjectSetup newsetup = new ProjectSetup();
IntPtr[] handles = BFS.Window.GetAllWindowHandles() ;
foreach (IntPtr handle in handles)
{
string path = ExplorerWindows.GetPath(handle, false);
if (path != "")
{
int order = ExplorerWindows.GetOrder(handle);
newsetup.AddProgram(path, handle, order);
LogToFile.AddMessage(handle.ToString() + ": " + order.ToString() + ", " + path);
}
}
newsetup.SortProgram();
SavedSetups.AddSetup(ProfileName, newsetup);
SavedSetups.ProjectPath = currPath;
string SetupName = SaveData(SavedSetups, currDirectory, currPath);
BFS.Dialog.ShowMessageInfo("Stored Setup: " + currDirectory);
LogToFile.AddMessage("Stored Setup: " + currDirectory);
LogToFile.AddMessage("Setup File: " + SetupName);
}
}
catch (Exception e)
{
if (ErrorMessages) BFS.Dialog.ShowMessageError("Run:\n" + e.ToString());
LogToFile.AddMessage("Exception in Run: " + e.ToString());
}
LogToFile.StoreLog();
}
// From https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/
static int GetDeterministicHashCode(this string str)
{
unchecked
{
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;
for (int i = 0; i < str.Length; i += 2)
{
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1)
break;
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}
return hash1 + (hash2 * 1566083941);
}
}
public static string SaveData(ProjectSetups Data, string setupName, string setupPath) // store all window settings in an xml file with the provided filename
{
XmlSerializer XmlSer = new XmlSerializer(typeof(ProjectSetups)); // Provides the methods for XML-Serialization
// will be stored in the users local DisplayFusion AppData folder:
string destination = Environment.ExpandEnvironmentVariables("%userprofile%\\AppData\\Local\\DisplayFusion");
string p = "";
string sname = "";
bool ready2copy = false; // Copy only if destination folder exists
try
{
if (Directory.Exists(destination)) // DisplyFusion user folder found?
{
// Creat a subfolder if it doesn't exist, yet
destination = destination + "\\ProjectSetups";
ready2copy = Directory.Exists(destination);
if (!ready2copy)
{
ready2copy = Directory.CreateDirectory(destination).Exists;
}
}
sname = setupPath.GetDeterministicHashCode() + " - " + setupName + ".xml";
p = destination + "\\" + sname; // Full Filename with path
}
catch (Exception e)
{
if (ErrorMessages) BFS.Dialog.ShowMessageError("SaveProgramSettings:\n" + e.ToString());
ready2copy = false;
sname = "";
}
if (ready2copy) // if destination folder exists
{
FileStream DStream = new FileStream(p, FileMode.Create); // Create the Filestream, overwrite mode
try
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); // Use this object
ns.Add("", ""); // to prevent that attributes are added in the files
XmlWriterSettings xws = new XmlWriterSettings(); // Use this object to
xws.Indent = true; // - add line feeds and indents
xws.CloseOutput = true; // - close stream after writing
xws.Encoding = Encoding.Default; // - set encoding
xws.IndentChars = " "; // - set indent depth
xws.NewLineHandling = NewLineHandling.None; // - handle line feeds properly
using (XmlWriter writer = XmlWriter.Create(DStream, xws)) // Now serialize
{
XmlSer.Serialize(writer, Data, ns);
writer.Close();
}
}
catch (Exception e)
{
if (ErrorMessages) BFS.Dialog.ShowMessageError("SaveProgramSettings_2:\n" + e.ToString());
sname = "";
}
finally
{
if (!(DStream == null)) DStream.Close(); // Close the stream
}
}
return sname;
}
public static ProjectSetups LoadProjectSetups(string setupName, string setupPath)
{
ProjectSetups r = new ProjectSetups();
XmlSerializer XmlSer = new XmlSerializer(typeof(ProjectSetups)); // Provides the methods for XML-Serialization
string destination = Environment.ExpandEnvironmentVariables("%userprofile%\\AppData\\Local\\DisplayFusion");
string p = "";
bool ready2copy = false; // Copy only if destination folder exists
bool success = false;
string sname = setupPath.GetDeterministicHashCode() + " - " + setupName + ".xml";
if (Directory.Exists(destination)) // DisplyFusion user folder found?
{
// Creat a subfolder, if it doesn't exist, yet
destination = destination + "\\ProjectSetups";
ready2copy = Directory.Exists(destination);
if (!ready2copy)
{
ready2copy = Directory.CreateDirectory(destination).Exists;
}
}
p = destination + "\\" + sname; // Full Filename with path
FileStream DStream = null;
try
{
if (File.Exists(p))
{
DStream = new FileStream(p, FileMode.Open); // Create the Filestream
r = (ProjectSetups)XmlSer.Deserialize(DStream); // and load the Data
success = true;
}
}
catch (Exception e)
{
if (ErrorMessages) BFS.Dialog.ShowMessageError("LoadProjectSetups:\n" + e.ToString());
success = false;
}
finally
{
if (!(DStream == null)) DStream.Close(); // Done
if (r == null)
{
r = new ProjectSetups();
}
else if (success == true)
{
r.SetupName = sname;
}
}
return r;
}
public static string buildProfileName(Rectangle[] screens)
{
// needs System.Text to have StringBuilder class
StringBuilder pname = new StringBuilder();
for (int i = 0; i < screens.Length; i++) // For each monitor
{
// Add monitors resolution in the format "[<Width>x<Height>, <Y>]"
pname.Append("[");
pname.Append(screens[i].Width);
pname.Append("x");
pname.Append(screens[i].Height);
if(screens[i].X!=0 || screens[i].Y!=0) // Primary screen has coordinates [0,0]
{
// Add the y-coordinate, if not primary screen
pname.Append(",");
pname.Append(screens[i].Y);
}
pname.Append("]");
}
return pname.ToString();
}
[Serializable]
public class ProjectSetups // list of ProjectSetups per Monitor Konfiguration
{
public ProjectSetups() { } // empty constructor is needed to make the class serializable
public string ProjectPath = "";
public string SetupName = "";
public XMLDictionary<string, ProjectSetup> Setups = new XMLDictionary<string, ProjectSetup>(); // list of Programs
public void AddSetup(string monconfig, ProjectSetup setup)
{
if(Setups.ContainsKey(monconfig))
{
Setups[monconfig] = setup;
}
else
{
Setups.Add(monconfig, setup);
}
}
public bool HasSetups()
{
if (Setups.Count > 0) return true;
return false;
}
public ProjectSetup GetSetup(string monconfig)
{
if(Setups.ContainsKey(monconfig))
{
return Setups[monconfig];
}
else
{
return Setups.First().Value;
}
}
}
[Serializable]
public class ProjectSetup // list of window settings
{
public ProjectSetup() { } // empty constructor is needed to make the class serializable
private SortedList<int, IntPtr> SortedPointer = new SortedList<int, IntPtr>();
private Dictionary<IntPtr, string> WindowNames = new Dictionary<IntPtr, string>();
public XMLDictionary<string, Rectangle> Prgrms = new XMLDictionary<string, Rectangle>(); // list of Programs
public void AddProgram(string name, IntPtr handle, int zorder)
{
SortedPointer.Add(zorder, handle);
WindowNames.Add(handle, name);
}
public void SortProgram()
{
foreach(KeyValuePair<int, IntPtr> kvp in SortedPointer)
{
IntPtr newhandle = kvp.Value;
string newname = WindowNames[newhandle];
if(Prgrms.ContainsKey(newname))
{
Prgrms[newname] = BFS.Window.GetBounds(newhandle);
}
else
{
Prgrms.Add(newname, BFS.Window.GetBounds(newhandle));
}
}
}
}
}
// ***** XMLDictionary *****
// A Module to provide a dictionary which is serializable.
// Purpose: e.g. ini-files
// Paul Welter 3/5/2006
//
// http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspx
public class XMLDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
TKey key = (TKey)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
TValue value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
this.Add(key, value);
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
// Addition by Christian Treffler, 6/9/2010:
XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); // Use this object
ns.Add("", ""); // to prevent that attributes are added in the files
// End of addition
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key, ns); // Added ns in this call
writer.WriteEndElement();
writer.WriteStartElement("value");
TValue value = this[key];
valueSerializer.Serialize(writer, value, ns); // Added ns in this call
writer.WriteEndElement();
writer.WriteEndElement();
}
}
}
public class LogFile // Logging for Debugging
{
public DateTime StartTime = DateTime.Now;
public List<string> Messages = new List<string>();
public bool Log = false;
public bool ErrorMessages = false;
private int IndentLevel = 0;
public void AddMessage(string message)
{
if (Log)
{
Messages.Add(new string(' ', IndentLevel * 3) + message);
}
}
public void AddMessage(string message, bool indent)
{
if (Log)
{
AddMessage(message);
SetIndent(indent);
}
}
public void SetIndent (bool indent)
{
if (Log)
{
if (indent)
{
IndentLevel++;
}
else
{
if (IndentLevel > 0)
{
IndentLevel--;
}
}
}
}
public void StoreLog()
{
// will be stored in the users local DisplayFusion AppData folder:
string destination = Environment.ExpandEnvironmentVariables("%userprofile%\\AppData\\Local\\DisplayFusion");
string p = "";
bool ready2copy = false; // Copy only if destination folder exists
if (Log && (Messages.Count > 0))
{
try
{
if (Directory.Exists(destination)) // DisplyFusion user folder found?
{
// Creat a subfolder if it doesn't exist, yet
destination = destination + "\\Logs";
ready2copy = Directory.Exists(destination);
if (!ready2copy)
{
ready2copy = Directory.CreateDirectory(destination).Exists;
}
}
p = destination + "\\" + StartTime.ToString("yyMMdd_HH_mm_ss_fffff") + " PSO.log"; // Full Filename with path
}
catch (Exception e)
{
ready2copy = false;
if (ErrorMessages) BFS.Dialog.ShowMessageError("StoreLog, destination:\n" + e.ToString());
}
if (ready2copy) // if destination folder exists
{
// from: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/file-system/how-to-write-to-a-text-file
StreamWriter file = new StreamWriter(p);
try
{
foreach (string line in Messages)
{
file.WriteLine(line);
}
}
catch (Exception e)
{
if (ErrorMessages) BFS.Dialog.ShowMessageError("StoreLog, stream:\n" + e.ToString());
}
finally
{
if (!(file == null)) file.Close(); // Close the stream
}
}
}
}
}
// The following code is adapted from this solution:
// https://stackoverflow.com/questions/36886355/retrieve-the-full-path-of-an-explorer-window-through-a-handle-in-c-sharp
// It builds a directory of all open Explorer windows
public class ExplorerWindowsHandler
{
static Guid CLSID_ShellApplication = new Guid("13709620-C279-11CE-A49E-444553540000");
Type shellApplicationType = Type.GetTypeFromCLSID(CLSID_ShellApplication, true);
object shellApplication = null;
object windows = null;
Type windowsType = null;
object count = null;
Dictionary<IntPtr, string> ExpWndws = null; // list of explorer windows
Dictionary<IntPtr, int> ExpWndwsOrder = null; // list of z-Order of the explorer windows
IntPtr ThisScriptHandle = IntPtr.Zero;
public bool ErrorMessages = false;
public LogFile LogToFile = new LogFile();
public void InitExplorerHandler(IntPtr thisscripthandle)
{
try
{
LogToFile.AddMessage("InitExplorerHandler: " + thisscripthandle.ToString(), true);
shellApplication = Activator.CreateInstance(shellApplicationType);
windows = shellApplicationType.InvokeMember("Windows", System.Reflection.BindingFlags.InvokeMethod, null, shellApplication, new object[] { });
windowsType = windows.GetType();
count = windowsType.InvokeMember("Count", System.Reflection.BindingFlags.GetProperty, null, windows, null);
ExpWndws = new Dictionary<IntPtr, string>();
ExpWndwsOrder = new Dictionary<IntPtr, int>();
ThisScriptHandle = thisscripthandle;
LogToFile.AddMessage(windowsType.ToString() + ": " + ((int)count).ToString());
LogToFile.AddMessage(ExpWndws.ToString());
LogToFile.AddMessage(ExpWndwsOrder.ToString());
for (int i = 0; i < (int)count; i++)
{
object item = null;
try
{
item = windowsType.InvokeMember("Item", System.Reflection.BindingFlags.InvokeMethod, null, windows, new object[] { i });
if (item != null) // Bugfix V1.1
{
Type itemType = item.GetType();
LogToFile.AddMessage(i.ToString() + ": " + itemType.ToString(), true);
string itemName = (string)itemType.InvokeMember("Name", System.Reflection.BindingFlags.GetProperty, null, item, null);
LogToFile.AddMessage(itemName);
if (itemName == "Windows Explorer" || itemName == "File Explorer" || itemName == "Explorer")
{
string itemHandle = itemType.InvokeMember("HWND", System.Reflection.BindingFlags.GetProperty, null, item, null).ToString();
// Cannot cast the member "HWND" to IntPtr, therefore need to find a different way get the handle of the window
LogToFile.AddMessage("itemHandle: " + itemHandle);
foreach (IntPtr handle in BFS.Window.GetAllWindowHandles())
{
if (handle.ToString() == itemHandle)
{
if(!ExpWndws.ContainsKey(handle))
{
string itemPath = (string)itemType.InvokeMember("LocationURL", System.Reflection.BindingFlags.GetProperty, null, item, null);
itemPath = itemPath.Replace(@"file:///", "");
ExpWndws.Add(handle, itemPath);
int z = GetWindowZOrder(handle);
while (ExpWndwsOrder.ContainsValue(z)) {z++;} // Just to be sure that no double entry occurs
ExpWndwsOrder.Add(handle, z);
LogToFile.AddMessage("Add: " + handle.ToString() + ", " + z.ToString() + ", " + itemPath);
}
break;
}
}
}
}
}
catch (Exception e)
{
if (ErrorMessages) BFS.Dialog.ShowMessageError("InitExplorerHandler Loop:\n" + e.ToString());
LogToFile.AddMessage("Exception in InitExplorerHandler Loop: ");
LogToFile.AddMessage(e.ToString());
}
finally
{
LogToFile.AddMessage("Loop End", false);
}
}
}
catch (Exception e)
{
if (ErrorMessages) BFS.Dialog.ShowMessageError("InitExplorerHandler:\n" + e.ToString());
LogToFile.AddMessage("Exception in InitExplorerHandler: ");
LogToFile.AddMessage(e.ToString());
count = 0;
}
finally
{
LogToFile.AddMessage("InitExplorerHandler End", false);
}
}
// From: https://stackoverflow.com/a/61243105/3506904
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
static int GetWindowZOrder(IntPtr hWnd)
{
var zOrder = -1;
while ((hWnd = GetWindow(hWnd, 2 /* GW_HWNDNEXT */)) != IntPtr.Zero) zOrder++;
return zOrder;
}
public string GetPath(IntPtr windowHandle, bool decode)
{
string returnPath = "";
if(ExpWndws.ContainsKey(windowHandle))
{
returnPath = ExpWndws[windowHandle];
if(decode)
{
returnPath = HttpUtility.UrlDecode(returnPath, System.Text.Encoding.Default);
}
}
return returnPath;
}
public int GetOrder(IntPtr windowHandle)
{
int returnOrder = -1;
if(ExpWndwsOrder.ContainsKey(windowHandle))
{
returnOrder = ExpWndwsOrder[windowHandle];
}
return returnOrder;
}
public IntPtr GetHandle(string path, bool close_duplicates)
{
IntPtr ret = IntPtr.Zero;
foreach (KeyValuePair<IntPtr, string> window in ExpWndws)
{
if(window.Value == path)
{
if (ret != IntPtr.Zero)
{
// Close the duplicate window. If it is the window which is the handle for this
// scripted function, close the other one.
IntPtr forClosure = window.Key;
if(forClosure == ThisScriptHandle)
{
forClosure = ret;
ret = window.Key;
}
try
{
BFS.Window.Close(forClosure);
}
catch {}
}
else
{
ret = window.Key;
}
if(!close_duplicates) break;
}
}
return ret;
}
}