Related
Basically I've written some code that listens for a "save as" dialog box to pop up inside an application, and when it does it presses "save", all through code. This works great, however I need to be able to set the file path to what I'd like before saving.
Here is my code so far:
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using HWND = System.IntPtr;
using System.Text;
/// <summary>Contains functionality to get all the open windows.</summary>
public static class OpenWindowGetter
{
private const int BN_CLICKED = 245;
/// <summary>Returns a dictionary that contains the handle and title of all the open windows.</summary>
/// <returns>A dictionary that contains the handle and title of all the open windows.</returns>
public static IDictionary<HWND, string> GetOpenWindows()
{
HWND shellWindow = GetShellWindow();
Dictionary<HWND, string> windows = new Dictionary<HWND, string>();
EnumWindows(delegate (HWND hWnd, int lParam)
{
if (hWnd == shellWindow) return true;
if (!IsWindowVisible(hWnd)) return true;
int length = GetWindowTextLength(hWnd);
if (length == 0) return true;
StringBuilder builder = new StringBuilder(length);
GetWindowText(hWnd, builder, length + 1);
if (builder.ToString() == "Export Selection") //Check for the export selection window
{
//Press the "save" button through code here
IntPtr hwndChild = FindWindowEx((IntPtr)hWnd, IntPtr.Zero, "Button", "&Save");
SendMessage((HWND)(int)hwndChild, BN_CLICKED, (HWND)0, IntPtr.Zero);
}
windows[hWnd] = builder.ToString();
return true;
}, 0);
return windows;
}
private delegate bool EnumWindowsProc(HWND hWnd, int lParam);
[DllImport("USER32.DLL")]
private static extern int SetWindowText(HWND hWnd, String lpString);
[DllImport("USER32.DLL")]
private static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);
[DllImport("USER32.DLL")]
private static extern int GetWindowText(HWND hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("USER32.DLL")]
private static extern int GetWindowTextLength(HWND hWnd);
[DllImport("USER32.DLL")]
private static extern bool IsWindowVisible(HWND hWnd);
[DllImport("USER32.DLL")]
private static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);
[DllImport("User32.dll")]
public static extern Int32 SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
}
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
while (true)
{
foreach (KeyValuePair<IntPtr, string> window in OpenWindowGetter.GetOpenWindows())
{
IntPtr handle = window.Key;
string title = window.Value;
//Console.WriteLine("{0}: {1}", handle, title);
}
}
}
}
}
I've been using FindWindowEx and SendMessage for controlling the window handles. This worked perfectly for the Save button, but now I'm trying to access the toolbar part of the window and set its text to the desired directory. I'm not even sure if this approach will work, but it seems the most simple. I've attached a screenshot for reference (the area circled in red is the handle I'm trying to access and change to a given file path before I "press" save)
As seen in the photo I've been using Spy++ to get info about the Window and its handles. If I attempt this for example to get a pointer to that handle:
IntPtr hwndChildToolbar= FindWindowEx((IntPtr)hWnd, IntPtr.Zero, "ToolbarWindow32", "Address: "+"C:\\Windows\\System32");
It doesn't work. The "Caption" value seen in the Spy++ app changes to whatever the current directory name is, so trying to access that doesn't seem to make sense.
This next line of code does give me a pointer in return, but it's not the correct address. Worth noting is there are multiple handles on this window that fall under the ToolbarWindow32 class:
IntPtr hwndChildToolbar= FindWindowEx((IntPtr)hWnd, IntPtr.Zero, "ToolbarWindow32", null);
If I can find a way to get the correct handle, then from here I just want to use SetWindowText if that's possible and set its value to a string that's my intended file path.
To sum it up I need some way to easily set the directory, and I'm not sure if this way is possible. My C# knowledge is limited so anything helps!
Here is a sample .NET Framework Console App that uses UI Automation, opens Notepad, type something and saves it as a file in the temp folder, using the Address bar on top of the Common Dialog.
See here .NET UI Automation Overview or here Native UI Automation for introduction and reference on UI Automation. It's usually better to use UI Automation than hacking windows handles. If you can't do it with UI Automation, there are chances you won't be able to do it with handles anyway. To discover the elements you can use and code against, you can use the Inspect tool from the Windows SDK.
Note I'm using here an interop version of the native UI Automation, as the .NET original wrappers that are provided with Windows have not been updated by Microsoft for many years for some reason.
// this code needs the "Interop.UIAutomationClient" Nuget package and "using Interop.UIAutomationClient"
class Program
{
private static readonly CUIAutomation8 _automation = new CUIAutomation8();
static void Main()
{
// track window open event
var processId = 0;
_automation.AddAutomationEventHandler(UIA_EventIds.UIA_Window_WindowOpenedEventId, _automation.GetRootElement(), TreeScope.TreeScope_Subtree, null,
new AutomationEventHandler((window, id) =>
{
// check the process id we opened
if (window.CurrentProcessId != processId)
return;
// get editor control
var editor = window.FindFirst(TreeScope.TreeScope_Children, _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_EditControlTypeId));
if (editor == null) // not the window we're looking for
return;
// get editor's value pattern & set some text value
var value = (IUIAutomationValuePattern)editor.GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId);
value.SetValue("hello world");
// get menu bar
var menuBar = window.FindFirst(TreeScope.TreeScope_Children, _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_MenuBarControlTypeId));
if (menuBar == null)
{
Console.WriteLine("Can't find menu bar.");
return;
}
// get "File" menu item (beware of localization) & invoke (open)
var file = menuBar.FindFirst(TreeScope.TreeScope_Children, _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, "File"));
if (file == null)
{
Console.WriteLine("Can't find 'File' menu item.");
return;
}
// expand "File" menu
var expand = (IUIAutomationExpandCollapsePattern)file.GetCurrentPattern(UIA_PatternIds.UIA_ExpandCollapsePatternId);
expand.Expand();
do
{
// get the "Save" item by name from the window subtree (as the menu that opens is a child of the window)
// do some retry to handle menu opening time
var save = window.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreatePropertyConditionEx(UIA_PropertyIds.UIA_NamePropertyId, "Save", PropertyConditionFlags.PropertyConditionFlags_MatchSubstring));
if (save != null)
{
((IUIAutomationInvokePattern)save.GetCurrentPattern(UIA_PatternIds.UIA_InvokePatternId)).Invoke();
break;
}
}
while (true);
// get the "Save As" dialog
// do some retry to handle dialog opening time
IUIAutomationElement dialog;
do
{
dialog = window.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_LocalizedControlTypePropertyId, "dialog"));
if (dialog != null)
break;
}
while (true);
// get the "Previous locations" to enable the Address edit box
var previous = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_ButtonControlTypeId),
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, "Previous Locations")));
if (previous == null)
{
Console.WriteLine("Can't find 'Previous Locations' button.");
return;
}
// push "Previous Locations" button
var previousButton = (IUIAutomationInvokePattern)previous.GetCurrentPattern(UIA_PatternIds.UIA_InvokePatternId);
previousButton.Invoke();
// enter the directory path
var address = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_EditControlTypeId),
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, "Address")));
if (address == null)
{
Console.WriteLine("Can't find 'Address' edit.");
return;
}
// sets the directory (here we use the temp directory)
var edit = (IUIAutomationValuePattern)address.GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId);
edit.SetValue(System.IO.Path.GetTempPath());
// push "Previous Locations" button again to "commit"
previousButton.Invoke();
// get the "File name:" edit
// do some retry to handle folder refresh
do
{
var fileName = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_EditControlTypeId),
_automation.CreatePropertyConditionEx(UIA_PropertyIds.UIA_NamePropertyId, "File name", PropertyConditionFlags.PropertyConditionFlags_MatchSubstring)));
if (fileName != null)
{
// sets the file name (some "random" name)
((IUIAutomationValuePattern)fileName.GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId)).SetValue(#"hello" + Environment.TickCount + ".txt");
break;
}
}
while (true);
// get the "Save" button
var dialogSave = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_ButtonControlTypeId),
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, "Save")));
if (dialogSave == null)
{
Console.WriteLine("Can't find 'Save' button.");
return;
}
// press the 'Save' button
((IUIAutomationInvokePattern)dialogSave.GetCurrentPattern(UIA_PatternIds.UIA_InvokePatternId)).Invoke();
}));
// start notepad
var process = Process.Start("notepad");
processId = process.Id;
Console.WriteLine("Press any key to quit...");
Console.ReadKey(false);
try
{
process.CloseMainWindow();
}
catch
{
// maybe closed by something else, do nothing
}
}
// helper class
class AutomationEventHandler : IUIAutomationEventHandler
{
public AutomationEventHandler(Action<IUIAutomationElement, int> action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
Action = action;
}
public Action<IUIAutomationElement, int> Action { get; }
public void HandleAutomationEvent(IUIAutomationElement sender, int eventId) => Action(sender, eventId);
}
}
I have a Win Forms app in which I am starting a process from a different .exe file, then setting it's Handle property to a panel in my Win Form app. This gives the effect of the other app running inside of the Win Forms app.
Here is how I accomplish that:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hwc, IntPtr hwp);
string exepath = "myProgram.exe";
ProcessStartInfo p = new ProcessStartInfo(exepath);
process = Process.Start(p);
Thread.Sleep(500); //sleep to allow program to start up properly
SetParent(process.MainWindowHandle, pictureBox1.Handle); //then set the handle to give the effect of being run inside the win forms app
Now I know I can call process.Kill() in a FormClosed event, which terminates the process when this form is closed, but how would I go about killing the process if my Win Form app is forcefully quit? Is that even possible?
Because the process's handle is set to a panel in the Win Form app, it doesn't appear on the taskbar but it will still continue to run if process.Kill() is not called, which happens when the Win Form is forcefully closed. This means each time I have to shut it off via task manager, which is a pain..
If this is not possible, I will not bother with setting the Handle to the panel, and I will just have it open in a new window.
Thanks
Try with this code, I was using it for a long time:
public partial class Launcher : Form
{
/// <summary>Collection of process. </summary>
private Dictionary<IntPtr, Process> _processCollection = new Dictionary<IntPtr, Process>();
#region DLL Import
[DllImport("user32.dll")]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
#endregion
/// <summary>Default constructor. </summary>
public Launcher()
{
InitializeComponent();
try {
FormClosing += Launcher_FormClosing;
StartInstances();
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
/// <summary>Starts the instances. </summary>
private void StartInstances()
{
var path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
var numberOfInstances = Int32.Parse(ConfigurationManager.AppSettings["NumerOfInstances"]);
for (int i = 0; i < numberOfInstances; i++) {
StartInstance(i, path);
}
}
/// <summary>Starts an instance. </summary>
private void StartInstance(int instanceId, string path)
{
Process proc = Process.Start(path + "\\foo.exe", instanceId.ToString());
IntPtr handlerDocked = IntPtr.Zero;
Panel panel = new Panel();
panel.Size = new Size(flwPanel.Width / 3, flwPanel.Height / 2);
flwPanel.Controls.Add(panel);
do {
try {
proc.WaitForInputIdle(1000); //wait for the window to be ready for input;
proc.Refresh(); //update process info
if (proc.HasExited) {
return; //abort if the process finished before we got a handle.
}
handlerDocked = proc.MainWindowHandle; //cache the window handle
}
catch {
Thread.Sleep(500);
}
} while (handlerDocked == IntPtr.Zero);
//hWndOriginalParent = SetParent(hWndDocked, panel1.Handle);
SetParent(handlerDocked, panel.Handle);
var docked = new DockedElement(handlerDocked, panel);
panel.SizeChanged += new EventHandler(Panel_Resize);
Panel_Resize(docked, new EventArgs());
_processCollection.Add(handlerDocked, proc);
}
private void Panel_Resize(object sender, EventArgs e)
{
var docked = (DockedElement)sender;
//Change the docked windows size to match its parent's size.
MoveWindow(docked.Handle, 0, 0, docked.Container.Width, docked.Container.Height, true);
}
/// <summary>Finallize instances. </summary>
public void FinallizeInstances()
{
foreach (var docked in _processCollection) {
docked.Value.Close();
}
_processCollection.Clear();
}
private void Launcher_FormClosing(object sender, FormClosingEventArgs e)
{
FinallizeInstances();
}
protected override void Dispose(bool disposing)
{
FinallizeInstances();
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
}
To be sure your child processes are properly wiped as soon as your application closes (gracefully or forcefully), you can use Job Objects.
A job object allows groups of processes to be managed as a unit. Job
objects are namable, securable, sharable objects that control
attributes of the processes associated with them. Operations performed
on a job object affect all processes associated with the job object.
Examples include enforcing limits such as working set size and process
priority or terminating all processes associated with a job.
There is a similar question on StackOverflow with a detailed implementation in C#: Working example of CreateJobObject/SetInformationJobObject pinvoke in .net?
I am working on a wpf application where instead of exiting the app when user closes the button I am minimizing it to the tray(similar to google talk).
void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
What I need is if user forgets that there is an instance of the app and tries to open a new instance I have to shut down the second instance and set my application as the foreground app. If the app is in minimized state (not hidden) I am able to do this. I am using the following code
protected override void OnStartup(StartupEventArgs e)
{
Process currentProcess = Process.GetCurrentProcess();
var runningProcess = (from process in Process.GetProcesses()
where
process.Id != currentProcess.Id &&
process.ProcessName.Equals(
currentProcess.ProcessName,
StringComparison.Ordinal)
select process).FirstOrDefault();
if (runningProcess != null)
{
Application.Current.Shutdown();
ShowWindow(runningProcess.MainWindowHandle, 5);
ShowWindow(runningProcess.MainWindowHandle, 3);
}
}
[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
When the app is minimized it has some unique value for MainWindowHandle. When I hide the app, the MainWindowHandle of runningProcess is showing as 0. I think this is why my application is not opening when it is in hidden state, but don't know how to fix it.
Tell me if I need to post more code or clarify anything. Thank you in advance.
When I hide the app, the MainWindowHandle of runningProcess is showing as 0
You're right. If a process doesn't have a graphical interface associated with it (hidden/ minimized) then the MainWindowHandle value is zero.
As workaround, you could try getting the HANDLE for the hidden window by enumerating all open windows using EnumDesktopWindows function and compare its process id with the hidden/ minimized windows's process id.
Update
The WPF's WIN32 window has a bit different behavior than the standard WIN32 window. It has the class name composed of the word HwndWrapper, the name of AppDomain it was created in, and a unique random Guid (which changes on every launch), e.g., HwndWrapper[WpfApp.exe;;4d426cdc-31cf-4e4c-88c7-ede846ab6d44].
Update 2
When WPF's window is hidden by using the Hide() method, it internally calls UpdateVisibilityProperty(Visibility.Hidden), which in turns set the internal visibility flag for UIElement to false. When we call the Show() method of WPF Window, UpdateVisibilityProperty(Visibility.Visible) is called, and the internal visibility flag for UIElement is toggled.
When we show the WPF window using the ShowWindow(), the UpdateVisibilityProperty() method is not trigerred, thus the internal visibility flag does not get reversed (which causes the window to be displayed with black backround).
By looking at the WPF Window internal implementation, the only way to toggle the internal visiblity flag, without calling the Show() or Hide() method, is by sending a WM_SHOWWINDOW message.
const int GWL_EXSTYLE = (-20);
const uint WS_EX_APPWINDOW = 0x40000;
const uint WM_SHOWWINDOW = 0x0018;
const int SW_PARENTOPENING = 3;
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsProc ewp, int lParam);
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[DllImport("user32.dll")]
private static extern uint GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern uint GetWindowText(IntPtr hWnd, StringBuilder lpString, uint nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
static bool IsApplicationWindow(IntPtr hWnd) {
return (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_APPWINDOW) != 0;
}
static IntPtr GetWindowHandle(int pid, string title) {
var result = IntPtr.Zero;
EnumWindowsProc enumerateHandle = delegate(IntPtr hWnd, int lParam)
{
int id;
GetWindowThreadProcessId(hWnd, out id);
if (pid == id) {
var clsName = new StringBuilder(256);
var hasClass = GetClassName(hWnd, clsName, 256);
if (hasClass) {
var maxLength = (int)GetWindowTextLength(hWnd);
var builder = new StringBuilder(maxLength + 1);
GetWindowText(hWnd, builder, (uint)builder.Capacity);
var text = builder.ToString();
var className = clsName.ToString();
// There could be multiple handle associated with our pid,
// so we return the first handle that satisfy:
// 1) the handle title/ caption matches our window title,
// 2) the window class name starts with HwndWrapper (WPF specific)
// 3) the window has WS_EX_APPWINDOW style
if (title == text && className.StartsWith("HwndWrapper") && IsApplicationWindow(hWnd))
{
result = hWnd;
return false;
}
}
}
return true;
};
EnumDesktopWindows(IntPtr.Zero, enumerateHandle, 0);
return result;
}
Usage Example
...
if (runningProcess.MainWindowHandle == IntPtr.Zero) {
var handle = GetWindowHandle(runningProcess.Id, runningProcess.MainWindowTitle);
if (handle != IntPtr.Zero) {
// show window
ShowWindow(handle, 5);
// send WM_SHOWWINDOW message to toggle the visibility flag
SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));
}
}
...
Thanks IronGeek, This is great. I'm just learning c# and struggled for a while trying to get this to work. Also I cant 'add comment' as insufficient reputation here so hence this post. I'm using WPF .Net 5.0.
I searched around to implement this, so for other newbies they will also need something like the following in their program to receive the message (sorry, not sure what page I copied this from, so many of them (individuals will need to make their own Mainwindow_Loaded event handler).
private void Mainwindow_Loaded_Event(object sender, RoutedEventArgs e)
{
hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
hwndSource.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SHOWWINDOW)
{
MessageBox.Show("I recieved WM_SHOWWINDOW");
handled = true;
}
return IntPtr.Zero;
}
The 'bring to front' tip you mentioned was also needed in my case, here is what is needed:
(from Bring Word to Front )
put this in the declarations section:
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
and put the following just after the 'SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));' statement:
SetForegroundWindow(handle);
Without this, the activated window just remains hidden behind other windows and it has to be found by manually fishing around in the taskbar.
So now I've finally got this going for a non-hidden window but now need to look at what is needed for a hidden one as that is my real goal.
Following on from my early post, and to quote an earlier comment from IronGeek the issue is ' If a process doesn't have a graphical interface associated with it (hidden/ minimized) then the MainWindowHandle value is zero'. Therefore any attempt to pass a hidden Window's handle is doomed as it doesn't exist.
So I have found a work-around, although it requires the target process to regularly check for the presence of a new message. Therefore this is still not ideal, but it works for me in 2021 (WPF, .Net 5.0) and doesn't need to import the user32.dll. Rather it carries out a makeshift type of Inter Process Communication (IPC) using the MainWindowTitle as a container to send a message passively.
The MainWindowTitle is settable at runtime and is viewable from other processes, therefore it can be used like an inter-process variable. This is my entire solution below, note it needs to be published to a local folder to see how it runs as the point is to run multiple instances.
<Window x:Class="TitleComsTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TitleComsTest"
mc:Ignorable="d"
Name="TitleComsTest" Title="TitleComsTest" Height="400" Width="600"
WindowStartupLocation = "CenterScreen" Closing="TitleComsTest_Closing" Visibility="Hidden">
<Grid>
<TextBox Name ="TextBox1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="230" Width="460"/>
<Button Name="QuitButton" Content=" Really Quit " HorizontalAlignment="Center" Margin="0,0,0,30" VerticalAlignment="Bottom" Click="QuitButton_Click"/>
</Grid>
The code behind:
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
namespace TitleComsTest
{
public partial class MainWindow : Window
{
//string for MainWindowTitle when first instance and visible:
const string my_Visible_exe_Name = "TitleComstest";
//string for MainWindowTitle when first instance and Hidden:
const string my_Hidden_exe_Name = "TitleComstest...";
//string for MainWindowTitle when 2nd instance :
const string my_exe_Name_Flag = "TitleComstest, Please-Wait";
bool reallyCloseThisProgram = false;
private DispatcherTimer timer1, timer2;
public MainWindow()
{
InitializeComponent();
//Get an array of processes with the chosen name
Process[] TitleComstests = Process.GetProcessesByName(my_Visible_exe_Name);
if (TitleComstests.Length > 1)
{ //Then this is not the first instance
for (int i = 0; i < TitleComstests.Length; i++)
{
if (TitleComstests[i].MainWindowTitle == my_Visible_exe_Name)
{ //The first instance is visible as the MainWindowTitle has been set to the visible name
Close(); //Quit - nothing to do but close the new instance
}
}
//The first instance is hidden, so set MainWindowTitle so the first instance can see it and react
this.Title = my_exe_Name_Flag;
this.WindowState = WindowState.Minimized; //Minimize the window to avoid having two windows shown at once
this.Visibility = Visibility.Visible; //The second instance needs to be visible (minimized is enough) to be seen
StartTimerQuit(4000); //arbitrary time, needs to be longer than 2000ms which is the checking period - see StartTimerLook(2000);
}
else
{
TextBox1.Text = "This is Multi-instance demo using the 'MainWindowTitle' to send messages\r\nto the first (hidden) instance to wake it up.";
TextBox1.Text += "\r\n\r\nThis demo requires the program be published to a local folder and \r\nnot run in the debugger.";
TextBox1.Text += "\r\n\r\nYou can type here to mark this instance: _____________ \r\n\r\nand then hide me by clicking top right close window 'X'";
TextBox1.Text += "\r\n\r\nOnce closed then start the program again to see the 1st instance pop up.";
TextBox1.Text += "\r\n\r\nFinally use the 'Really Quit' button to end this demo.";
this.Visibility = Visibility.Visible;
}
}
private void StartTimerQuit(Int32 interval) //Timer to Quit setup and start
{
timer1 = new DispatcherTimer(); timer1.Tick += timerQuit_Tick;
timer1.Interval = new TimeSpan(0, 0, 0, 0, interval); timer1.Start();
}
private void timerQuit_Tick(object sender, EventArgs e)
{
reallyCloseThisProgram = true; Close();
}
private void TitleComsTest_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (!reallyCloseThisProgram)
{
e.Cancel = true;
this.Title = my_Hidden_exe_Name; //Set the Title text to flag a hidden state
this.Visibility = Visibility.Hidden;
//Start checking every 2 secs at the process names - could be faster but this is a constant background process
StartTimerLook(2000);
}
}
private void StartTimerLook(Int32 interval) //Timer to look for new instances setup and start
{
timer2 = new DispatcherTimer(); timer2.Tick += timerLook_Tick;
timer2.Interval = new TimeSpan(0, 0, 0, 0, interval); timer2.Start();
}
private void timerLook_Tick(object sender, EventArgs e)
{ //Every timer interval check to see if a process is present with the Ttile name flag
Process[] myNameFlagProcesses = Process.GetProcessesByName(my_Visible_exe_Name);
for (int i = 0; i < myNameFlagProcesses.Length; i++)
{
if (myNameFlagProcesses[i].MainWindowTitle == my_exe_Name_Flag) //If name flag is seen ...
{ //... then wake up
TextBox1.Text += "\r\n Saw the other window";
this.Visibility = Visibility.Visible;
this.Title = my_Visible_exe_Name; //Set the Title text to flag a visible state
this.Show();
this.Activate();
timer2.Stop();
}
}
}
private void QuitButton_Click(object sender, RoutedEventArgs e)
{
reallyCloseThisProgram = true; Close();
}
}
}
Hi I have been searching but I can't find the answer. How do I know when the screen is going off or on. Not the SystemEvents.PowerModeChanged .
I dont know how to retrieve the display/screen EVENTS
private const int WM_POWERBROADCAST = 0x0218;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private const int SC_CLOSE = 0xF060; // dont know
private const int SC_MONITORPOWER = 0xF170;
private const int SC_MAXIMIZE = 0xF030; // dont know
private const int MONITORON = -1;
private const int MONITOROFF = 2;
private const int MONITORSTANBY = 1;
[DllImport("user32.dll")]
//static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private static extern int SendMessage(IntPtr hWnd, int hMsg, int wParam, int lParam);
public void Init(Visual visual)
{
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
HwndSource source = ((HwndSource)PresentationSource.FromVisual(visual));
source.AddHook(MessageProc);
Handle = source.Handle;
}
public void SwitchMonitorOff()
{ // works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOROFF);
}
public void SwitchMonitorOn()
{// works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITORON);
}
public void SwitchMonitorStandBy()
{// works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITORSTANBY);
}
private IntPtr MessageProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND) //Intercept System Command
{
// not finished yet
// notice the 0xFFF0 mask, it's because the system can use the 4 low order bits of the wParam
// value as stated in the MSDN library article about WM_SYSCOMMAND.
int intValue = wParam.ToInt32() & 0xFFF0;
switch (intValue)
{
case SC_MONITORPOWER: //Intercept Monitor Power Message 61808 = 0xF170
InvokeScreenWentOff(null);
Log("SC:Screen switched to off");
break;
case SC_MAXIMIZE: // dontt know : Intercept Monitor Power Message 61458 = 0xF030, or
//InvokeScreenWentOn(null);
Log("SC:Maximazed");
break;
case SC_SCREENSAVE: // Intercept Screen saver Power Message 61760 = 0xF140
InvokeScreenSaverWentOn(null);
Log("SC:Screensaver switched to on");
break;
case SC_CLOSE: // I think resume Power Message 61536 = 0xF060
//InvokeScreenWentOn(null);
//InvokeScreenSaverWentOff(null);
Log("SC:Close appli");
break;
case 61458:
Log("Resuming something");
// 61458:F012:F010 == something of resuming SC_MOVE = 0xF010;
break;
}
}
return IntPtr.Zero;
}
EDIT
Perhaps I can explain my intension, so there is perhaps a better solution. I have a Dual binding WCF service running on. It's running on an archos (portable tablet pc). I want that when the user stopped working for an idle time, the connection closes immediatly, and when the computer is returning from idle, he reconnects immediatly. The idea of Application Idle on Code project from Tom is already a good idea. The less power consumption , the better. The startup must be as fast as possible.
Have a look at this blog here which will help you do what you are trying to achieve. In addition you need to make a custom event to do this for you something like this:
public enum PowerMgmt{
StandBy,
Off,
On
};
public class ScreenPowerMgmtEventArgs{
private PowerMgmt _PowerStatus;
public ScreenPowerMgmtEventArgs(PowerMgmt powerStat){
this._PowerStatus = powerStat;
}
public PowerMgmt PowerStatus{
get{ return this._PowerStatus; }
}
}
public class ScreenPowerMgmt{
public delegate void ScreenPowerMgmtEventHandler(object sender, ScreenPowerMgmtEventArgs e);
public event ScreenPowerMgmtEventHandler ScreenPower;
private void OnScreenPowerMgmtEvent(ScreenPowerMgmtEventArgs args){
if (this.ScreenPower != null) this.ScreenPower(this, args);
}
public void SwitchMonitorOff(){
/* The code to switch off */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.Off));
}
public void SwitchMonitorOn(){
/* The code to switch on */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.On));
}
public void SwitchMonitorStandby(){
/* The code to switch standby */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.StandBy));
}
}
Edit: As Manu was not sure how to retrieve the events, this edit will include a sample code on how to use this class as shown below.
Using System;
Using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Interop;
using System.Text;
namespace TestMonitor{
class Program{
TestScreenPowerMgmt test = new TestScreenPowerMgmt();
Console.WriteLine("Press a key to continue...");
Console.ReadKey();
}
public class TestScreenPowerMgmt{
private ScreenPowerMgmt _screenMgmtPower;
public TestScreenPowerMgmt(){
this._screenMgmtPower = new ScreenPowerMgmt;
this._screenMgmtPower.ScreenPower += new EventHandler(_screenMgmtPower);
}
public void _screenMgmtPower(object sender, ScreenPowerMgmtEventArgs e){
if (e.PowerStatus == PowerMgmt.StandBy) Console.WriteLine("StandBy Event!");
if (e.PowerStatus == PowerMgmt.Off) Console.WriteLine("Off Event!");
if (e.PowerStatus == PowerMgmt.On) Console.WriteLine("On Event!");
}
}
}
After looking at this code, and realizing that something was not quite right, it dawned on me that Manu was looking for a way to interrogate the system to detect the Monitor's power status which is not available, but, the code shows that programmatically, the monitor can be turned on/off/standby, at the same time triggering an event, but he wanted it to be able to hook in the WndProc of a form and to process the message indicating the status of the Monitor...now, at this point, I am going to express my opinion on this.
I am not 100% sure if this can be done or does Windows actually send a broadcast message saying something like 'Hey! Monitor is going to sleep' or 'Hey! Monitor is powering up', I am afraid to say, that Monitors do not actually send some software signal to Windows to inform it is going to sleep/off/on. Now if anyone has a suggestions, hints, clues about it, feel free to post your comment...
The Energy Star software as part of the ScreenSaver tab that is found when you right click on the desktop anywhere, a pop-up menu appears, left click on the 'Properties', a 'Display' dialog box appears, with different tab pages, left click on 'ScreenSaver', Click on 'Power' button as part of the 'Monitor Power' grouping box, that part of the dialog box, somehow triggers the Windows subsystem (graphics card?/Energy Star driver?) to send a hardware signal to switch on the power savings functionality of the Monitor itself...(Monitors that are brand new do not have this enabled by default AFAIK...feel free to dismiss this notion...)
Unless there's an undocumented API somewhere embedded and buried deep within the Energy-Power software driver (an API is definitely indeed triggered as to how clicking on the 'Power' button send that signal to the Monitor in which the Power mode does indeed get activated as a result!) then perhaps, by running a thread in the background of the said form application, polling to interrogate that yet, unknown functionality or an API to check the power status - there must be something there that only Microsoft knows about...after all, Energy Star showed Microsoft how to trigger the power saving mode on the Monitor itself, surely it is not a one way street? or is it?
Sorry Manu if I could not help further .... :(
Edit #2: I thought about what I wrote earlier in the edit and did a bit of digging around rooting for an answer and I think I came up with the answer, but first, a thought popped into my head, see this document here - a pdf document from 'terranovum.com', the clue (or so I thought...) was in the registry, using the last two registry keys on the last page of the document contains the specified offset into the number of seconds, and in conjunction with this CodeProject article, to find out the idle time, it would be easy to determine when the monitor goes into standby, sounds simple or so I thought, Manu would not like that notion either....
Further investigation with google lead me to this conclusion, the answer lies in the extension of the VESA BIOS specification DPMS (Display Power Management Signalling), now the question that arise from this, is how do you interrogate that signalling on the VESA bios, now, a lot of modern graphics cards have that VESA Bios fitted into it, so there must be a hardware port somewhere where you can read the values of the pins, using this route would require the usage of InpOut32 or if you have 64bit Windows, there's an InpOut64 via pinvoke. Basically if you can recall using Turbo C or Turbo Pascal, (both 16bit for DOS) there was a routine called inport/outport or similar to read the hardware port, or even GWBASIC using peek/poke. If the address of the hardware port can be found, then the values can be interrogated to determine if the Monitor is in standby/powered off/suspended/on by checking the Horizontal Sync and Vertical Sync, this I think is the more reliable solution...
Apologies for the long answer but felt I had to write down my thoughts....
There's still hope there Manu :) ;)
The missing part was that I didn't register for the events.
Found that there's a power management example from Microsoft:
http://www.microsoft.com/en-us/download/details.aspx?id=4234
hMonitorOn = RegisterPowerSettingNotification(this.Handle,ref GUID_MONITOR_POWER_ON,DEVICE_NOTIFY_WINDOW_HANDLE);
[DllImport("User32", SetLastError = true,EntryPoint = "RegisterPowerSettingNotification",CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient,ref Guid PowerSettingGuid,Int32 Flags);
[DllImport("User32", EntryPoint = "UnregisterPowerSettingNotification",CallingConvention = CallingConvention.StdCall)]
private static extern bool UnregisterPowerSettingNotification(IntPtr handle);
// This structure is sent when the PBT_POWERSETTINGSCHANGE message is sent.
// It describes the power setting that has changed and contains data about the change
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public Int32 DataLength;
}
This works for me even MainWindow is hidden. The code is based on above post, and C++ code of https://www.codeproject.com/Articles/1193099/Determining-the-Monitors-On-Off-sleep-Status.
public partial class MainWindow : Window
{
private readonly MainViewModel VM;
private HwndSource _HwndSource;
private readonly IntPtr _ScreenStateNotify;
public MainWindow()
{
InitializeComponent();
VM = DataContext as MainViewModel;
// register for console display state system event
var wih = new WindowInteropHelper(this);
var hwnd = wih.EnsureHandle();
_ScreenStateNotify = NativeMethods.RegisterPowerSettingNotification(hwnd, ref NativeMethods.GUID_CONSOLE_DISPLAY_STATE, NativeMethods.DEVICE_NOTIFY_WINDOW_HANDLE);
_HwndSource = HwndSource.FromHwnd(hwnd);
_HwndSource.AddHook(HwndHook);
}
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// handler of console display state system event
if (msg == NativeMethods.WM_POWERBROADCAST)
{
if (wParam.ToInt32() == NativeMethods.PBT_POWERSETTINGCHANGE)
{
var s = (NativeMethods.POWERBROADCAST_SETTING) Marshal.PtrToStructure(lParam, typeof(NativeMethods.POWERBROADCAST_SETTING));
if (s.PowerSetting == NativeMethods.GUID_CONSOLE_DISPLAY_STATE)
{
VM?.ConsoleDisplayStateChanged(s.Data);
}
}
}
return IntPtr.Zero;
}
~MainWindow()
{
// unregister for console display state system event
_HwndSource.RemoveHook(HwndHook);
NativeMethods.UnregisterPowerSettingNotification(_ScreenStateNotify);
}
}
And Native methods here:
internal static class NativeMethods
{
public static Guid GUID_CONSOLE_DISPLAY_STATE = new Guid(0x6fe69556, 0x704a, 0x47a0, 0x8f, 0x24, 0xc2, 0x8d, 0x93, 0x6f, 0xda, 0x47);
public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
public const int WM_POWERBROADCAST = 0x0218;
public const int PBT_POWERSETTINGCHANGE = 0x8013;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public uint DataLength;
public byte Data;
}
[DllImport(#"User32", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);
[DllImport(#"User32", SetLastError = true, EntryPoint = "UnregisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
public static extern bool UnregisterPowerSettingNotification(IntPtr handle);
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private const int WM_POWERBROADCAST = 0x0218;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private const int SC_CLOSE = 0xF060; // dont know
private const int SC_MONITORPOWER = 0xF170;
private const int SC_MAXIMIZE = 0xF030; // dont know
private const int MONITORON = -1;
private const int MONITOROFF = 2;
private const int MONITORSTANBY = 1;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND) //Intercept System Command
{
int intValue = wParam.ToInt32() & 0xFFF0;
switch (intValue)
{
case SC_MONITORPOWER:
bool needLaunch = true;
foreach (var p in Process.GetProcesses())
{
if (p.ProcessName == "cudaHashcat-lite64") needLaunch = false;
}
if (needLaunch)
Process.Start(#"C:\Users\Dron\Desktop\hash.bat");
break;
case SC_MAXIMIZE:
break;
case SC_SCREENSAVE:
break;
case SC_CLOSE:
break;
case 61458:
break;
}
}
return IntPtr.Zero;
}
}
My (C#, .NET 3.5) application generates files and, in addition to raising events that can be caught and reacted to, I want to display the target folder to the user in a form. The file-list is being shown within the same form as other information.
I'm using an instance of the WebBrowser control (System.Windows.Forms.WebBrowser), then navigating to the folder. This shows some default view of the explorer window, with the file summary panel on the left and the files in the 'Tiles' (large icon and text) view.
For example,
wb.Navigate(#"c:\path\to\folder\");
I'd like to suppress the panel and to view the file list in the Details view. The user can get to this via a right-click, context menu, but I'd like it to come up automatically.
I'd rather not have to build my own TreeView, DataGridView or whatever; the WebBrowser control does all the updating and re-sorting and whatnot 'for free'.
Is there a better way? A different control to use or some additional arguments to pass to the control?
And if I could trap events (for example, files being selected/renamed/double-clicked, etc.) then all the better!
WARNING: Long post with lots of code.
When you navigate the web browser control to a file system folder the web browser control hosts a shell view window that in turn hosts the explorer list view. In fact this is exactly the same thing that the Explorer process does as well as the file dialogs and Internet Explorer. This shell window is not a control so there are no methods that can be called on it or events that can be subscribed to but it can receive windows messages and it can be sub-classed.
It turns out that the part of your question dealing with setting the view to Details automatically is actually quite easy. In your web browser control's Navigated event simply find the handle to the shell view window and send it a WM_COMMAND message with a particular shell constant (SHVIEW_REPORT). This is an undocumented command but it is supported on all Windows platforms up to and including Windows 2008 and almost certainly will be on Windows 7. Some code to add to your web browser's form demonstrates this:
private delegate int EnumChildProc(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern int EnumChildWindows(IntPtr hWndParent,
EnumChildProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName,
int nMaxCount);
private const int WM_COMMAND = 0x0111;
private const int SHVIEW_REPORT = 0x702C;
private const string SHELLVIEW_CLASS = "SHELLDLL_DefView";
private IntPtr m_ShellView;
void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
m_ShellView = IntPtr.Zero;
EnumChildWindows(webBrowser1.Handle, EnumChildren, IntPtr.Zero);
if (m_ShellView != IntPtr.Zero)
{
SendMessage(m_ShellView, WM_COMMAND, (IntPtr)SHVIEW_REPORT, (IntPtr)0);
}
}
private int EnumChildren(IntPtr hwnd, IntPtr lParam)
{
int retval = 1;
StringBuilder sb = new StringBuilder(SHELLVIEW_CLASS.Length + 1);
int numChars = GetClassName(hwnd, sb, sb.Capacity);
if (numChars == SHELLVIEW_CLASS.Length)
{
if (sb.ToString(0, numChars) == SHELLVIEW_CLASS)
{
m_ShellView = hwnd;
retval = 0;
}
}
return retval;
}
Every time the web browser navigates to a new window (including when a folder is opened from within the explorer view) a new shell view window is created so the message must be re-sent to the new window in every Navigated event.
For the second part of your question you would like to receive events from the explorer list view. This is quite a bit more difficult than the first part. To do this you would need to sub-class the list view window and then monitor the windows messages for ones that interest you (such as WM_LBUTTONDBLCLK). In order to sub-class a window you would need to create your own class derived from the NativeWindow class and assign it the handle of the window that you need to monitor. You can then override its Window procedure and handle the various messages as you wish. Below is an example of creating a double click event - it is relatively simple but to get extensive access to the explorer list view may involve a lot more work than you are willing to do.
Add this to your form:
private ExplorerListView m_Explorer;
void OnExplorerItemExecuted(object sender, ExecuteEventArgs e)
{
string msg = string.Format("Item to be executed: {0}{0}{1}",
Environment.NewLine, e.SelectedItem);
e.Cancel = (MessageBox.Show(msg, "", MessageBoxButtons.OKCancel)
== DialogResult.Cancel);
}
and these two lines to the Navigated event handler (right after the SendMessage):
m_Explorer = new ExplorerListView(m_ShellView);
m_Explorer.ItemExecuted += OnExplorerItemExecuted;
Then add the following classes:
class ExplorerListView : NativeWindow
{
public event EventHandler<ExecuteEventArgs> ItemExecuted;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindowEx(IntPtr hwndParent,
IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
private const int WM_LBUTTONDBLCLK = 0x0203;
private const int LVM_GETNEXTITEM = 0x100C;
private const int LVM_GETITEMTEXT = 0x1073;
private const int LVNI_SELECTED = 0x0002;
private const string EXPLORER_LISTVIEW_CLASS = "SysListView32";
public ExplorerListView(IntPtr shellViewHandle)
{
base.AssignHandle(FindWindowEx(shellViewHandle, IntPtr.Zero,
EXPLORER_LISTVIEW_CLASS, null));
if (base.Handle == IntPtr.Zero)
{
throw new ArgumentException("Window supplied does not encapsulate an explorer window.");
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_LBUTTONDBLCLK:
if (OnItemExecution() != 0) return;
break;
default:
break;
}
base.WndProc(ref m);
}
private int OnItemExecution()
{
int cancel = 0;
ExecuteEventArgs args = new ExecuteEventArgs(GetSelectedItem());
EventHandler<ExecuteEventArgs> temp = ItemExecuted;
if (temp != null)
{
temp(this, args);
if (args.Cancel) cancel = 1;
}
return cancel;
}
private string GetSelectedItem()
{
string item = null;
IntPtr pStringBuffer = Marshal.AllocHGlobal(2048);
IntPtr pItemBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LVITEM)));
int selectedItemIndex = SendMessage(base.Handle, LVM_GETNEXTITEM, (IntPtr)(-1), (IntPtr)LVNI_SELECTED).ToInt32();
if (selectedItemIndex > -1)
{
LVITEM lvi = new LVITEM();
lvi.cchTextMax = 1024;
lvi.pszText = pStringBuffer;
Marshal.StructureToPtr(lvi, pItemBuffer, false);
int numChars = SendMessage(base.Handle, LVM_GETITEMTEXT, (IntPtr)selectedItemIndex, pItemBuffer).ToInt32();
if (numChars > 0)
{
item = Marshal.PtrToStringUni(lvi.pszText, numChars);
}
}
Marshal.FreeHGlobal(pStringBuffer);
Marshal.FreeHGlobal(pItemBuffer);
return item;
}
struct LVITEM
{
public int mask;
public int iItem;
public int iSubItem;
public int state;
public int stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public IntPtr lParam;
public int iIndent;
public int iGroupId;
int cColumns; // tile view columns
public IntPtr puColumns;
public IntPtr piColFmt;
public int iGroup;
}
}
public class ExecuteEventArgs : EventArgs
{
public string SelectedItem { get; private set; }
public bool Cancel { get; set; }
internal ExecuteEventArgs(string selectedItem)
{
SelectedItem = selectedItem;
}
}
This should give you an idea of what you would need to do. If you want more than fairly simple events you may want to look for a alternative control, though from what I have seen in the free and low cost areas there are some pretty decent controls but they all have some quirks and will not give a seamless explorer experience.
Remember this code was put together fairly quickly without error handling or comments and ignoring several issues such as multiple selected items, so use it as a guideline and at your own risk.
In order to handle renaming, deleting and make other customization you need to write your own file explorer. WebBrowser control is not suitable for your needs. It's just a wrapper over ActiveX component.
You should check this codeproject article. It contains an implementation of file explorer. There are few more samples of file browser:
one
two
LogicNP Software has two controls (FileView and ShComboBox) that do what your looking for:
http://www.ssware.com/fldrview.htm
You can download a trial from their page, however it's ~130$ for the license.
I have written a library that might be able to help you. You can find it at: http://gong-shell.sourceforge.net/
The control you're looking for is the ShellView. There's tutorials there on how to create a simple Windows Explorer clone in only a few lines too.
Note for .NET 4.0 users: Gong-shell is currently broken for 4.0. The framework introduced changes in Interop and it will build just fine but cause different issues when interfacing with shell32 (notably the shellicon api, leading to an unmanaged null pointer dereference).
Check out this article here, it shows how to do this in .NET and WinForms. Doing it this way gives full-control over what the user sees.
I've used it in one of my applications and it works really well. You can show icon/details/list view and it stops the user moving to other directories (which is often the problem of showing the standard file/directory dialogs.
I use it to show the screen like the one below below http://img7.imageshack.us/img7/7647/screenshotbaf.png:
You may want to look at the ExplorerBrowser object.
See http://blogs.msdn.com/ieinternals/archive/2009/12/30/Windows-7-Web-Browser-Control-will-not-browse-file-system.aspx for more details.
If you are happy being Windows Vista only and wrapping a COM control, IExplorerBrowser might be acceptable for you needs.
This The Code Project article shows its use within an MFC program but at least one other person seems to have got it to work in C# after some effort.
The newer API exposes considerably more programmability than simply intercepting messages, but it is (obviously) useless for older platforms.
If you want to open a different window to display the target folder's content you can use System.Windows.Forms.OpenFileDialog, or SaveFileDialog, or inherit from FileDialog and extend it.
To allow the user to select a folder you can use FolderBrowserDialog, though as a user I don't like that control.
Does this help or you absolutely have to embed a control in your form?
Asaf