Consider the following unit test for WinApi functionality:
public class WinApiTest
{
[TestMethod]
public void WinApiFindFormTest_SimpleNesting()
{
var form = new Form();
form.Text = #"My form";
var button = new Button();
button.Text = #"My button";
form.Controls.Add(button);
//with below line commented out, the test fails
form.Show();
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
IntPtr expectedParent = form.Handle;
//below 2 lines were added for debugging purposes, they are not part of test
//and they don't affect test results
Debug.WriteLine("Actual: " + WinApi.GetWindowTitle(actualParent));
Debug.WriteLine("Expected: " + WinApi.GetWindowTitle(expectedParent));
Assert.AreEqual(actualParent, expectedParent);
}
//this is a method being tested
//please assume it's located in another class
//I'm not trying to test winapi
public static IntPtr FindParent(IntPtr child)
{
while (true)
{
IntPtr parent = WinApi.GetParent(child);
if (parent == IntPtr.Zero)
{
return child;
}
child = parent;
}
}
}
Problem is that to make it work, I have to show the form, i.e. do form.Show(), otherwise, it fails with this output:
Actual: WindowsFormsParkingWindow
Expected: My form
Exception thrown: 'Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException' in Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
I read about this mysterious WindowsFormsParkingWindow, and it seems to be relevant only if there was no parent specified. So all controls without a parent live under this window. In my case, however, button was clearly specified to be part of form's controls.
Question: Is there a proper way to make this test pass? I'm trying to test FindParent method. In true spirit of unit tests, nothing should suddenly pop up in front of the user. It's possible to do Show and Hide sequence, but I think it's a rather hack-ish approach to solve the problem.
Code for WinApi class is provided below - it does not add much value to the question, but if you absolutely must see it, here it goes (major portion comes from this answer on SO):
public class WinApi
{
/// <summary>
/// Get window title for a given IntPtr handle.
/// </summary>
/// <param name="handle">Input handle.</param>
/// <remarks>
/// Major portition of code for below class was used from here:
/// https://stackoverflow.com/questions/4604023/unable-to-read-another-applications-caption
/// </remarks>
public static string GetWindowTitle(IntPtr handle)
{
if (handle == IntPtr.Zero)
{
throw new ArgumentNullException(nameof(handle));
}
int length = WinApi.SendMessageGetTextLength(handle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (length > 0 && length < int.MaxValue)
{
length++; // room for EOS terminator
StringBuilder windowTitle = new StringBuilder(length);
WinApi.SendMessageGetText(handle, WM_GETTEXT, (IntPtr)windowTitle.Capacity, windowTitle);
return windowTitle.ToString();
}
return String.Empty;
}
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, [Out] StringBuilder lParam);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);
}
When you access the Handle property, the window needs to be created. Child windows need to have a parent window, and if the parent window has not yet been created, the child window is created with the parking window as its parent. Only when the parent window is created, does the child window get re-parented.
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
IntPtr expectedParent = form.Handle;
When you access button.Handle, the button's window is created, but since the form's window is not yet created, the parking window is the parent. The simplest way for you to handle this is to ensure that the form's window is created before the buttons's window. Make sure that you refer to form.Handle, before you call GetParent on the button's handle, for example in your test you could reverse the order of assignment:
IntPtr expectedParent = form.Handle;
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
Obviously you'd want to comment this code so that a future reader knew that the order of assignment was critical.
I do wonder however why you feel the need to make a test like this however. I cannot imagine this sort of testing revealing a bug in your code.
Related
I have create a Window use WPF, I want to my Window always display on the top, So I just create a thread for it:
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
for (;;)
{
System.Threading.Thread.Sleep(3000);
this.Dispatcher.BeginInvoke(new Action(() =>
{
this.Activate();
this.Topmost = true;
}));
}
});
}
This will make sure my window go to front in every 3 seconds.
And when I open it under Visual Studio 2015, all fine, even when I open the Start Menu, it will close start menu and bring the window on top.But when I'm not use Visual studio open the application(just double click open the application), when I open start menu, the Window just flickering, not display on the top. What I miss? and how do I let it work as like open the application under Visual Studio 2015(I'm tested on Win10)?
EDIT: I somehow missed the point covered in the title. The point I make later of "Do not do it" still holds true though. It could cause problems for users of your application.
If you really need to though, this answer may be the one you are looking for. It discusses how to keep a window in front of everything. It is still a work around (just like most answers to your question).
Old Answer
I understand your problem as: you want your window to stay on top of all other windows. Similar functionality can be found in Ubuntu and The Google Play Music desktop application for Window's (see below).
To accomplish this, all you need to do is add Topmost="True" to your Window as demonstrated below (look at the last property).
<Window 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"
Topmost="True">
</Window>
Unless your actual problem "is what happens when two windows have that property set?" Then I would suggest reading this article (the same article that was referenced in the comments). It states the following:
"How do I create a window that is never covered by any other windows, not even other topmost windows?"
Imagine if this were possible and imagine if two programs did this.
Program A creates a window that is "super-topmost" and so does Program
B. Now the user drags the two windows so that they overlap. What
happens? You've created yourself a logical impossibility. One of those
two windows must be above the other, contradicting the imaginary
"super-topmost" feature.
If that functionality is really what you are after, I would suggest: do not do it. All other solutions are a workaround and could cause problems for consumers of your application.
I would have used interop to do this.
public class Interop
{
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hwind, int cmd);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
public static IntPtr GetWindowHandle(Window window)
{
return new WindowInteropHelper(window).Handle;
}
}
then use a timer:
private void Tick(object state)
{
this.Dispatcher.Invoke(() =>
{
IntPtr window = Interop.GetWindowHandle(this);
IntPtr focused = Interop.GetForegroundWindow();
if (window != focused)
{
Interop.SetForegroundWindow(window);
// Command 5 for show
Interop.ShowWindow(window, 5);
}
});
}
Code from
And for the part of your problem regarding the startmenu, just add a group policy.
Or you can make a interop to ´FindWindowEx´ to find the startbutton and disable it.
I agree with Jonas's answer but would modify it to use an event instead of a timer.
/// Get the topmost window handle
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
/// Trigger event when topmost window changed
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, TopmostWindowChangedDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
/// Set the topmost window
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
/// Show a window
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hwind, int cmd);
// Constant variables for topmost window changed event
private const uint WINEVENT_OUTOFCONTEXT = 0;
private const uint EVENT_SYSTEM_FOREGROUND = 3;
/// Keep track of the last topmost window with a name
private static IntPtr topWinHandle { get; set; }
/// Implementation of topmost window changed delegate
private TopmostWindowChangedDelegate TopmostWindowChanged { get; set; }
Then set up the handlers in one of your startup methods
// Set topmost window changed event handler
TopmostWindowChanged = new TopmostWindowChangedDelegate(WinEventProc);
// Set event hook for topmost window changed
IntPtr hook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, TopmostWindowChanged, 0, 0, WINEVENT_OUTOFCONTEXT);
And then move your window to topmost when the other topmost changes
/// Make sure this window stays on top
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
// Get current window name from handle
IntPtr handle = GetForegroundWindow();
if(handle != YOUR_WINDOW'S_HANDLE)
{
// Move your window back to the top
SetForegroundWindow(YOUR_WINDOW'S_HANDLE);
ShowWindow(YOUR_WINDOW'S_HANDLE, 5);
}
}
After spending 5 days of my life, I'm about to give up on this, but am consulting the experts once before that.
I have a WebBrowser control that loads a webpage and I programmatically scrape its contents. Clicking a particular menu item in the page brings up File Open dialog when done in IE (or any other browser). But clicking the same button in WebBrowser control using InvokeMember() apparently doesn't do anything no matter what. I've gone through several SO questions such as Setting Browser Features to make sure my control behaves exactly like IE, but that hasn't succeeded.
I went as far as inspecting the actual javascript function that the button is executing behind the scene and calling it manually using HtmlDocument.InvokeScript() but couldn't do that because the underlying function takes an argument of MouseEvent type (the click event actually) and I'm not sure how can I create that object in C#.
Another approach was to set focus to that particular button and then try SendKeys, but that won't work because the WebBrowser control is not visible. It is just an in-memory instance. To be more specific, the WebBrowser
EDIT
On a reader's request, here's the simple code that I'm using to find the element:
var MyButton = WB.Document.GetElementById("processfilelink");
processfilelink is an anchor tag (<a href='#' ... >) and I have confirmed that this element actually exists in the body of the document. The webpage uses jQuery's delegate feature to bind this anchor's click event to the target function. After locating the button, I simply call InvokeMember() like this:
MyButton.InvokeMember("click");
Note: I also see bindings for mousedown, mouseup and focus events in the page code. I expect all these events to automatically fire when one invokes click, but just to be sure I added InvokeMember calls for these events too. Results are no better.
From the comments:
... load this page in full IE browser, use F12 Tools to debug it and
execute button.click() in JavaScript console. Does it work as expected
this way?
So, you've tried that, and the result is:
... now that's interesting. It doesn't work! But clicking on the item
by hand does work flawlessly. What's going on here?
I suspected that as MyButton.InvokeMember("click") doesn't work. Apparently, the page handles this click by other means than via onclick event. Most likely, it uses onmousedown or onmouseup events. Study the page's scripting logic to verify if that's the case, use F12 debugger and put some break points.
Updated, if it turns out the page indeed uses onmousedown/onmouseup, you'd need to make your WebBrowser visible and automate it by posting WM_LBUTTONDOWN:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication_22979038
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
this.webBrowser.DocumentText = "<a id='goLink' href='javascript:alert(\"Hello!\"),undefined'>Go</a><script></script>";
this.webBrowser.DocumentCompleted += webBrowser_DocumentCompleted;
}
void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var element = this.webBrowser.Document.GetElementById("goLink");
element.Focus();
var hwnd = GetFocus();
if (!IsChild(this.webBrowser.Handle, hwnd))
throw new ApplicationException("Unexpected focused window.");
var rect = GetElementRect(element);
IntPtr wParam = (IntPtr)MK_LBUTTON;
IntPtr lParam = (IntPtr)(rect.Left | rect.Top << 16);
PostMessage(hwnd, WM_LBUTTONDOWN, wParam, lParam);
PostMessage(hwnd, WM_LBUTTONUP, wParam, lParam);
}
// get the element rect in window client area coordinates
static Rectangle GetElementRect(HtmlElement element)
{
var rect = element.OffsetRectangle;
int left = 0, top = 0;
var parent = element;
while (true)
{
parent = parent.OffsetParent;
if (parent == null)
return new Rectangle(rect.X + left, rect.Y + top, rect.Width, rect.Height);
var parentRect = parent.OffsetRectangle;
left += parentRect.Left;
top += parentRect.Top;
}
}
// interop
const int MK_LBUTTON = 0x0001;
const int WM_LBUTTONDOWN = 0x0201;
const int WM_LBUTTONUP = 0x0202;
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
[DllImport("User32.dll", CharSet = CharSet.Auto)]
static extern int PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
static extern IntPtr GetFocus();
[DllImport("User32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd);
}
}
I want to get the text which is in textbox of other application. It may be a textbox of gtalk client or a soap UI screen.
Based on my research most of the forums suggested winapi is the concept that I've to use to achieve this. I didn't get any good examples to implement this.
Here is an example of how to grab all the text from a window by it's window title.
Please see the comments for an explanation on how this works.
public class GetWindowTextExample
{
// Example usage.
public static void Main()
{
var allText = GetAllTextFromWindowByTitle("Untitled - Notepad");
Console.WriteLine(allText);
Console.ReadLine();
}
// Delegate we use to call methods when enumerating child windows.
private delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
private static extern IntPtr FindWindowByCaption(IntPtr zeroOnly, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, [Out] StringBuilder lParam);
// Callback method used to collect a list of child windows we need to capture text from.
private static bool EnumChildWindowsCallback(IntPtr handle, IntPtr pointer)
{
// Creates a managed GCHandle object from the pointer representing a handle to the list created in GetChildWindows.
var gcHandle = GCHandle.FromIntPtr(pointer);
// Casts the handle back back to a List<IntPtr>
var list = gcHandle.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
// Adds the handle to the list.
list.Add(handle);
return true;
}
// Returns an IEnumerable<IntPtr> containing the handles of all child windows of the parent window.
private static IEnumerable<IntPtr> GetChildWindows(IntPtr parent)
{
// Create list to store child window handles.
var result = new List<IntPtr>();
// Allocate list handle to pass to EnumChildWindows.
var listHandle = GCHandle.Alloc(result);
try
{
// Enumerates though all the child windows of the parent represented by IntPtr parent, executing EnumChildWindowsCallback for each.
EnumChildWindows(parent, EnumChildWindowsCallback, GCHandle.ToIntPtr(listHandle));
}
finally
{
// Free the list handle.
if (listHandle.IsAllocated)
listHandle.Free();
}
// Return the list of child window handles.
return result;
}
// Gets text text from a control by it's handle.
private static string GetText(IntPtr handle)
{
const uint WM_GETTEXTLENGTH = 0x000E;
const uint WM_GETTEXT = 0x000D;
// Gets the text length.
var length = (int)SendMessage(handle, WM_GETTEXTLENGTH, IntPtr.Zero, null);
// Init the string builder to hold the text.
var sb = new StringBuilder(length + 1);
// Writes the text from the handle into the StringBuilder
SendMessage(handle, WM_GETTEXT, (IntPtr)sb.Capacity, sb);
// Return the text as a string.
return sb.ToString();
}
// Wraps everything together. Will accept a window title and return all text in the window that matches that window title.
private static string GetAllTextFromWindowByTitle(string windowTitle)
{
var sb = new StringBuilder();
try
{
// Find the main window's handle by the title.
var windowHWnd = FindWindowByCaption(IntPtr.Zero, windowTitle);
// Loop though the child windows, and execute the EnumChildWindowsCallback method
var childWindows = GetChildWindows(windowHWnd);
// For each child handle, run GetText
foreach (var childWindowText in childWindows.Select(GetText))
{
// Append the text to the string builder.
sb.Append(childWindowText);
}
// Return the windows full text.
return sb.ToString();
}
catch (Exception e)
{
Console.Write(e.Message);
}
return string.Empty;
}
}
You are correct. You will need to use the Windows API. For example:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
But first, you'll probably need to use FindWindow or FindWindowEx(?) recursively from the desktop down to where the text box is in the window hieracrchy to get the right window handle.
It looks like http://www.pinvoke.net/ has a good database of Win API's.
Hope that helps.
One option would be to use TestStack.White, a UI automation framework. It's based on project white, and the original documentation is here.
I am new to WINAPI and have figured out how to send a message to another program. The program I am using however I would like to be able to have it click on a specific button. From what I have learned by viewing Spy++ windows handles change for the programs every time they are reloaded and so do the handles for their controls. The control ID stays the same. After two days of trying to figure it out I am here.
under SendMesssageA if I specify the current handle as viewable by Spy++ and use that and run the code it works fine and clicks the button on my external application. I am attempting to use GetDlgItem as I have read that I can get the handle for the control (child window) using it. I am doing something wrong however since no matter what I do it returns 0 or 'null'.
How can I get GetDlgItem to return the child control handle so that I may use it to sendmessage to click that control in the external application?
Thanks for your help an input ahead of time.
[DllImport("User32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
Process[] myProcess = Process.GetProcessesByName("program name here");
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessageA(IntPtr hwnd, int wMsg, int wParam, uint lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetDlgItem(int hwnd, int childID);
public const int WM_LBUTTONDOWN = 0x0201;
public const int WM_LBUTTONUP = 0x0202;
public void SendClick()
{
IntPtr hwnd = myProcess[0].MainWindowHandle;
SetForegroundWindow(hwnd);
int intCID = 1389;
IntPtr ptrTest = GetDlgItem(hwnd, intCID);
SendKeys.SendWait(" ");
Thread.Sleep(1000);
SendKeys.SendWait("various text to be sent here");
Thread.Sleep(1000);
SendKeys.SendWait("{ENTER}");
Thread.Sleep(1000);
SendMessageA(ptrTest, WM_LBUTTONDOWN, WM_LBUTTONDOWN, 0);
}
I think you have to use the Win32 API to find the "receiving" application window, and then find a child window of that handle.
This is something I found googling Win32 API FindWindow
http://www.c-sharpcorner.com/UploadFile/shrijeetnair/win32api12062005005528AM/win32api.aspx
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