For any custom dialog (form) in a WinForm application I can set its size and position before I display it with:
form.StartPosition = FormStartPosition.Manual;
form.DesktopBounds = MyWindowPosition;
This is particularly important when dealing with multiple monitors. Without such code, when you open a dialog from an application that you have dragged to a second monitor, the dialog appears on the primary monitor. This presents a poor user experience.
I am wondering if there are any hooks to set the position for the standard .NET OpenFileDialog and SaveFileDialog (which do not have a StartPosition property).
I suspect that the best you can do is make sure you use the overload of ShowDialog that accepts an IWin32Window to use as the parent. This might help it choose an appropriate location; most commonly:
using(var dlg = new OpenFileDialog()) {
.... setup
if(dlg.ShowDialog(this) == DialogResult.OK) {
.... use
}
}
Check out this article on CodeProject. Excerpt:
Here is when the handy .NET
NativeWindow comes into the picture, a
NativeWindow is a window wrapper where
it processes the messages sent by the
handle associated to it. It creates a
NativeWindow and associates the
OpenFileWindow handle to it. From this
point, every message sent to
OpenFileWindow will be redirected to
our own WndProc method in the
NativeWindow instead, and we can
cancel, modify, or let them pass
through.
In our WndProc, we process the message
WM_WINDOWPOSCHANGING. If the open
dialog is opening, then we will change
the original horizontal or vertical
size depending of the StartLocation
set by the user. It will increment the
size of the window to be created. This
happens only once when the control is
opened.
Also, we will process the message
WM_SHOWWINDOW. Here, all controls
inside the original OpenFileDialog are
created, and we are going to append
our control to the open file dialog.
This is done by calling a Win32 API
SetParent. This API lets you change
the parent window. Then, basically
what it does is attach our control
to the original OpenFileDialog in the
location it set, depending on the
value of the StartLocation property.
The advantage of it is that we still
have complete control over the
controls attached to the
OpenFileDialog window. This means we
can receive events, call methods, and
do whatever we want with those
controls.
OpenFileDialog and SaveFileDialog position themselves in the upper-left corner of
the client area of the most recently displayed window. So just create a new invisible window positioned where you want the the dialog to appear before creating and showing that dialog.
Window dialogPositioningWindow = new Window();
dialogPositioningWindow.Left = MainWindow.Left + <left position within main window>;
dialogPositioningWindow.Top = MainWindow.Top + <top position within main window>;
dialogPositioningWindow.Width = 0;
dialogPositioningWindow.Height = 0;
dialogPositioningWindow.WindowStyle = WindowStyle.None;
dialogPositioningWindow.ResizeMode = ResizeMode.NoResize;
dialogPositioningWindow.Show();// OpenFileDialog is positioned in the upper-left corner
// of the last shown window (dialogPositioningWindow)
Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
...
if ((bool)dialog.ShowDialog()){
...
}
dialogPositioningWindow.Close();
I had this problem for most of yesterday. BobB's answer was the one that helped me out the most (Thanks BobB).
You can even go as far as to make a private method that creates a window and closes it before the dialog.ShowDialog() method call and it will still centre the OpenFileDialog.
private void openFileDialogWindow()
{
Window openFileDialogWindow = new Window();
openFileDialogWindow.Left = this.Left;
openFileDialogWindow.Top = this.Top;
openFileDialogWindow.Width = 0;
openFileDialogWindow.Height = 0;
openFileDialogWindow.WindowStyle = WindowStyle.None;
openFileDialogWindow.ResizeMode = ResizeMode.NoResize;
openFileDialogWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
openFileDialogWindow.Show();
openFileDialogWindow.Close();
openFileDialogWindow = null;
}
Then call it in any method before the ShowDialog() method.
public string SelectWebFolder()
{
string WebFoldersDestPath = null;
CommonOpenFileDialog filePickerDialog = new CommonOpenFileDialog();
// OpenFileDialog Parameters..
openFileDialogWindow();
if (filePickerDialog.ShowDialog() == CommonFileDialogResult.Ok)
{
WebFoldersDestPath = filePickerDialog.FileName + "\\";
}
filePickerDialog = null;
return WebFoldersDestPath;
}
Here's how I did it:
The point where I want to display the OpenFileDialog:
Thread posThread = new Thread(positionOpenDialog);
posThread.Start();
DialogResult dr = ofd.ShowDialog();
The repositioning code:
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
/// <summary>
/// Find the OpenFileDialog window when it appears, and position it so
/// that we can see both dialogs at once. There is no easier way to
/// do this (&^%$! Microsoft!).
/// </summary>
private void positionOpenDialog ()
{
int count = 0;
IntPtr zero = (IntPtr)0;
const int SWP_NOSIZE = 0x0001;
IntPtr wind;
while ((wind = FindWindowByCaption(zero, "Open")) == (IntPtr)0)
if (++count > 100)
return; // Find window failed.
else
Thread.Sleep(5);
SetWindowPos(wind, 0, Right, Top, 0, 0, SWP_NOSIZE);
}
I start a thread that looks for a window with the "Open" title. (Typically found in 3 iterations or 15 milliseconds.) Then I set its position with the obtained handle. (See SetWindowPos documentation for the position/size parameters.)
Kludgy.
There is quite an old example of one approach on MSDN.
http://msdn.microsoft.com/en-us/library/ms996463.aspx
It includes all the code needed to implement your own OpenFileDialog class that allows extensibility.
Very grateful for BobB's reply on this one. There are a few more "gotchas". You have to pass the handle of PositionForm when calling OpenFileDialog1.ShowDialog(PositionForm) otherwise BobB's technique is not reliable in all cases. Also, now that W8.1 launches a new fileopen control with SkyDrive in it, the Documents folder location in the W8.1 fileopen control is now screwed. So I frig fileopen to use the old W7 control by setting ShowHelp = True.
Here is the VB.NET code I ended up using, my contribution to the community in case it helps.
Private Function Get_FileName() As String
' Gets an Input File Name from the user, works with multi-monitors
Dim OpenFileDialog1 As New OpenFileDialog
Dim PositionForm As New Form
Dim MyInputFile As String
' The FileDialog() opens in the last Form that was created. It's buggy! To ensure it appears in the
' area of the current Form, we create a new hidden PositionForm and then delete it afterwards.
PositionForm.StartPosition = FormStartPosition.Manual
PositionForm.Left = Me.Left + CInt(Me.Width / 2)
PositionForm.Top = Me.Top + CInt(Me.Height / 2)
PositionForm.Width = 0
PositionForm.Height = 0
PositionForm.FormBorderStyle = Forms.FormBorderStyle.None
PositionForm.Visible = False
PositionForm.Show()
' Added the statement "ShowHelp = True" to workaround a problem on W8.1 machines with SkyDrive installed.
' It causes the "old" W7 control to be used that does not point to SkyDrive in error.
OpenFileDialog1.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
OpenFileDialog1.Filter = "Excel files (*.xls*)|*.xls*|CSV Files (*.csv)|*.csv"
OpenFileDialog1.FilterIndex = 1
OpenFileDialog1.RestoreDirectory = True
OpenFileDialog1.AutoUpgradeEnabled = False
OpenFileDialog1.ShowHelp = True
OpenFileDialog1.FileName = ""
OpenFileDialog1.SupportMultiDottedExtensions = False
OpenFileDialog1.Title = "Select an Excel or .csv file containing patent data or list of Publication Numbers for your project."
If OpenFileDialog1.ShowDialog(PositionForm) <> System.Windows.Forms.DialogResult.OK Then
Console.WriteLine("No file was selected. Please try again!")
PositionForm.Close()
PositionForm.Dispose()
OpenFileDialog1.Dispose()
Return ""
End If
PositionForm.Close()
PositionForm.Dispose()
MyInputFile = OpenFileDialog1.FileName
OpenFileDialog1.Dispose()
Return MyInputFile
End Function
Using Rob Sherrit's response on Jan 22 '14 as inspiration, I created a new module and called it CKRFileDialog (call it what you want) which contains the following code:
Public Function Show(fd As Object, CoveredForm As Form, Optional bShowHelp As Boolean = False) As DialogResult
Dim oDR As DialogResult
'The .Net FileDialogs open in the last Form that was created.
'To ensure they appear in the area of the current Form, we create a new HIDDEN PositionForm and then
'delete it afterwards.
Dim PositionForm As New Form With {
.StartPosition = FormStartPosition.Manual,
.Left = CoveredForm.Left + CInt(CoveredForm.Width / 8), 'adjust as required
.Top = CoveredForm.Top + CInt(CoveredForm.Height / 8), 'adjust as required
.Width = 0,
.Height = 0,
.FormBorderStyle = Windows.Forms.FormBorderStyle.None,
.Visible = False
}
PositionForm.Show()
'If you use SkyDrive you need to ensure that "bShowHelp" is set to True in the passed parameters.
'This is a workaround for a problem on W8.1 machines with SkyDrive installed.
'Setting it to "true" causes the "old" W7 control to be used which avoids a pointing to SkyDrive error.
'If you do not use SkyDrive then simply do not pass the last parameter (defaults to "False")
fd.ShowHelp = bShowHelp
'store whether the form calling this routine is set as "topmost"
Dim oldTopMost As Integer = CoveredForm.TopMost
'set the calling form's topmost setting to "False" (else the dialogue will be "buried"
CoveredForm.TopMost = False
oDR = fd.ShowDialog(PositionForm)
'set the "topmost" setting of the calling form back to what it was.
CoveredForm.TopMost = oldTopMost
PositionForm.Close()
PositionForm.Dispose()
Return oDR
End Function
I then call this code in my various modules as follows:
If performing a "FileOpen" ensure that there is a FileOpenDialog component added to your form or code and adjust the properties of the component if you wish
(e.g. InitDirectory,Multiselect,etc.)
Do the same when using FileSaveDialog components (Different properties to the FileOpenDialog component may apply).
To "show" the dialog component use a line of code as follows, passing two parameters, the first the FileDialog you are using ("Open" or "Save") and the second parameter the Form upon which you wish to overlay the dialogue.
CKRFileDialog.Show(saveFileDialog1, CoveredForm)
or
CKRFileDialog.Show(openFileDialog1, CoveredForm)
Remember, if you are using SkyDrive you must pass "True" as a third parameter:
CKRFileDialog.Show(saveFileDialog1, CoveredForm, True)
or
CKRFileDialog.Show(openFileDialog1, CoveredForm, True)
I set the "offset" of the dialogue to be 1/8 the way across and down on the form
"CoveredForm", but you can set that back to 1/2 (as in Rob Sherret's code) or whatever value you wish.
This seemed the easiest approach
Thanks Rob! :-)
Related
I am trying to finish my static Prompt class to be able to call it from anywhere. But the problem is couldn't make the dialog show. I am already using [STAThread] and here is my code.
public static string ShowFileDialog()
{
string selectedPath = "";
var t = new Thread((ThreadStart)(() =>
{
FolderBrowserDialog fbd = new FolderBrowserDialog();
fbd.RootFolder = System.Environment.SpecialFolder.MyComputer;
fbd.ShowNewFolderButton = true;
if (fbd.ShowDialog() == DialogResult.OK)
{
selectedPath = fbd.SelectedPath;
}
}));
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
return selectedPath;
}
public static class Prompt is my Prompt Class. I am calling it from public partial class Dashboard : Form class
Thank you for your helps.
It surely works just fine when you don't get an exception. But yes, pretty decent odds that you don't see the dialog. Pretty ugly problem, you don't have a taskbar button either. Only way to find it back is by minimizing the other windows on the desktop.
A dialog, any dialog, must have an owner window. You are supposed to pass that owner to the ShowDialog(owner) method overload. If you don't specify one that it goes looking for an owner by itself. Underlying call is GetActiveWindow(). To come up with nothing, the desktop window now becomes the owner. That isn't good enough to ensure that the dialog window is in front.
At a minimum you must create that owner window, you'll now at least have the taskbar button. Like this:
using (var owner = new Form() { Width = 0, Height = 0,
StartPosition = FormStartPosition.CenterScreen,
Text = "Browse for Folder"}) {
owner.Show();
owner.BringToFront();
FolderBrowserDialog fbd = new FolderBrowserDialog();
fbd.RootFolder = System.Environment.SpecialFolder.MyComputer;
fbd.ShowNewFolderButton = true;
if (fbd.ShowDialog(owner) == DialogResult.OK) {
selectedPath = fbd.SelectedPath;
}
}
Still doesn't guarantee that the dialog is visible, you cannot push a window into the user's face when he's interacting with another window. But at least there's a taskbar button.
I'll very hesitantly show the hack around that, don't use it:
owner.Show();
var pid = System.Diagnostics.Process.GetCurrentProcess().Id;
Microsoft.VisualBasic.Interaction.AppActivate(pid);
The proper way to draw the user's attention and get him to interact with your UI is NotifyIcon.ShowBalloonTip().
I cant find any information for this...
Basically I have a TopMost WinForm in C# that is an overlay for another application.
The overlay has buttons that I need to be able to press without stealing focus from the other application.
Is this possible, as I can't find any relative information.
You could store the mouse position:
Point point = Cursor.Position;
and then use an area with no controls in it to change the focus back to the DirectX9 window,
moving the cursor back to the original position before clicking again? That might work.
The only issue is that the button would still be there so you would need some way of getting it to click to the window rather than the button.
e.g.
Point p = Cursor.Position;
Cursor.Position = new point(x,y);
mouse(leftClick);
Cursor.Position = p;
mouse(leftClick);
the mouse(leftclick) method is here.
The other way to do this would be to track cursor position separately and then on each click, check if the click is within any controls and if so then run that method;
(Please tell me in the comments if there is a way to do this more efficiently as it would actually be quite useful to know)
Here is a solution that is working for me:
It identifies the target program by its title or parts of it and after each Button click it sets the target to be the foreground window again.
I can type into notepad, click a Button and type on..
I start by making the Form click-through.
You may want to do a lot more styling, maybe maximize, remove the control/min/max boxes and the title or even grab the target's position and size and overlay just the target window. This way you can make completely unobtrusive adornments to the target.
(I did that once because the target didn't treat touch screens right; so I made an overlay with holes in its region where the non-touch-defunct controls were. Nobody can even notice that the overlay is there!)
I write a few status info into to the output console..
Instead of the MainWindowTitle you could also use the ProcessName to identify the target. You can also incorporate code to re-establish the process after an error, like the user closing and restarting the target app..
using System.Runtime.InteropServices;
using System.Diagnostics;
...
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
string targetTitle = "Notepad2";
Process theProcess = null;
public Form1()
{
InitializeComponent();
// make the form clickthrough..
TransparencyKey = Color.Fuchsia;
BackColor = TransparencyKey;
// find and keep a reference to the target process
theProcess = findProcess(targetTitle);
// our overlay should stay on top
TopMost = true;
}
Process findProcess(string processTitle)
{
Process[] processList = Process.GetProcesses();
return processList.FirstOrDefault(
pr => pr.MainWindowTitle.ToLower().Contains(targetTitle.ToLower()));
}
void setActive(Process theProcess)
{
if (theProcess == null)
{ Console.WriteLine( "Process " + targetTitle + " not found";) return; }
bool ok = SetForegroundWindow(theProcess.MainWindowHandle);
Console.Write("Process " + theProcess.ProcessName + " (" +
theProcess.MainWindowTitle + + ok ? " OK." : " not OK!" );
}
private void button1_Click(object sender, EventArgs e)
{
// do your stuff..
Console.WriteLine("Testing Button 1");
// done: bring the target process back:
setActive(theProcess);
}
}
I create a global hot key to show a window by PInvoking RegisterHotKey(). But to do this I need that window's HWND, which doesn't exist until the window is loaded, that means shown for the first time. But I don't want to show the window before I can set the hot key. Is there a way to create a HWND for that window that is invisible to the user?
If you are targeting .NET 4.0 you can make use of the new EnsureHandle method available on the WindowInteropHelper:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(thanks to Thomas Levesque for pointing this out.)
If you are targeting an older version of the .NET Framework, the easiest way is to show the window to get to the HWND while setting a few properties to make sure that the window is invisible and doesn't steal focus:
var window = new Window() //make sure the window is invisible
{
Width = 0,
Height = 0,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false
};
window.Show();
Once you want to show the actual window you can then set the Content, the size and change the style back to a normal window.
You can also change the window into a so called message-only window. As this window type does not support graphical elements it will never be shown. Basically it comes down to calling:
SetParent(hwnd, (IntPtr)HWND_MESSAGE);
Either create a dedicated message window which will always be hidden, or use the real GUI window and change it back to a normal window when you want to display it. See the code below for a more complete example.
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hwnd, IntPtr hwndNewParent);
private const int HWND_MESSAGE = -3;
private IntPtr hwnd;
private IntPtr oldParent;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
if (hwndSource != null)
{
hwnd = hwndSource.Handle;
oldParent = SetParent(hwnd, (IntPtr)HWND_MESSAGE);
Visibility = Visibility.Hidden;
}
}
private void OpenWindowMenuItem_Click(object sender, RoutedEventArgs e)
{
SetParent(hwnd, oldParent);
Show();
Activate();
}
For me the solution of setting the width, height to zero and style to none didn't work out, as it still showed a tiny window, with an annoying shadow of what seems to be the border around a 0x0 window (tested on Windows 7). Therefore I'm providing this alternative option.
This is a dirty hack, but it should work, and doesn't have the downsides of changing the opacity :
set the WindowStartupLocation to Manual
set the Top and Left properties to somewhere outside the screen
set ShowInTaskbar to false so that the user doesn't realize there is a new window
Show and Hide the window
You should now be able to retrieve the HWND
EDIT: another option, probably better : set ShowInTaskBar to false and WindowState to Minimized, then show it : it won't be visible at all
I had already posted an answer to that question, but I just found a better solution.
If you just need to make sure that the HWND is created, without actually showing the window, you can do this:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(actually the EnsureHandle method wasn't available when the question was posted, it was introduced in .NET 4.0)
I've never tried to do what you are doing, but if you need to show the Window to get the HWND, but don't want to show it, set the Window Opacity to 0. This will also prevent any hit testing from occurring. Then you could have a public method on the Window to change the Opacity to 100 when you want to make it visible.
I know absolutely nothing about WPF, but could you create a message only window using other means (PInvoke for example) to receive the WM_HOTKEY message? If yes, then once you receive the WM_HOTKEY, you could launch the WPF window from there.
I've noticed that the last thing that happens when the window is being initialized, is the change of WindowState, if it differs from normal. So, you can actually make use of it:
public void InitializeWindow(Window window) {
window.Top = Int32.MinValue;
window.Left = Int32.MinValue;
window.Width = 0;
window.Height = 0;
window.ShowActivated = false;
window.ShowInTaskbar = false;
window.Opacity = 0;
window.StateChanged += OnBackgroundStateChanged;
window.WindowStyle = WindowStyle.None;
}
public void ShowWindow(Window window) {
window.Show();
window.WindowState = WindowState.Maximized;
}
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.Top = 300;
window.Left = 200;
window.Width = 760;
window.Height = 400;
window.WindowState = WindowState.Normal;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
That works fair enough for me. And it does not require working with any handles and stuff, and, more importantly, does not require to have a custom class for a window. Which is great for dynamically loaded XAML. And it is also a great way if you are making a fullscreen app. You do not even need to change its state back to normal or set proper width and height. Just go with
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
And you're done.
And even if I am wrong in my assumption that change of state is last thing done when window is being loaded, you can still change to any other event, it does not really matter.
The WindowInteropHelper class should allow you to get the HWND for the WPF window.
MyWindow win = new MyWindow();
WindowInteropHelper helper = new WindowInteropHelper(win);
IntPtr hwnd = helper.Handle;
MSDN Documentation
Another option in a similar vein to setting the opacity to 0, is to set the size to 0 and set the position to be off the screen. This won't require the AllowsTransparency = True.
Also remember that once you have shown it once you can then hide it and still get the hwnd.
Make the size of the window 0 x 0 px, put ShowInTaskBar to false, show it, then resize it when needed.
I've created extension method for showing invisible window, next Show calls will behave OK.
public static class WindowHelper
{
public static void ShowInvisible(this Window window)
{
// saving original settings
bool needToShowInTaskbar = window.ShowInTaskbar;
WindowState initialWindowState = window.WindowState;
// making window invisible
window.ShowInTaskbar = false;
window.WindowState = WindowState.Minimized;
// showing and hiding window
window.Show();
window.Hide();
// restoring original settings
window.ShowInTaskbar = needToShowInTaskbar;
window.WindowState = initialWindowState;
}
}
Start Wpf Window in Hidden mode:
WpfWindow w = new WpfWindow() { Visibility = Visibility.Hidden };
Start Wpf Window in Visible mode:
WpfWindow w = new WpfWindow();
w.Show();
drag-n-drop is a pretty discussed topic in a lot sites (this too) and i found nice questions too, but no answer to this case.
I have a listView with some elements and i need them to be droppable on the Windows Explorer. When dropped i need only the file path where those are dropped, i don't need to copy anything, just need the path.
Similar question (and why they don't work for me):
Drag and drop to Desktop / Explorer (this works only if you already have the file to copy and doesn't give path)
http://blogs.msdn.com/b/delay/archive/2009/11/16/creating-something-from-nothing-and-knowing-it-developer-friendly-virtual-file-implementation-for-net-refined.aspx (this looks good but it still create a virtual file and copy it to the location, so i have no path and still have to copy something in my destination)
The only solution i found:
http://www.codeproject.com/Articles/23207/Drag-and-Drop-to-Windows-Folder-C
This works but in a very "unpratical" way, it creates a file watcher, create a dummy file, let the DragDrop function copy it, watch where it was created and finally delete it. Testing it in my Windows8.1 results in a incorrect Explorer refresh and i can still see the file until i refresh my screen (F5).
Is this the only way? I still can't believe i can't achieve this in a simpler way
Think about it for a minute... if you know about drag and drop, then you'll know that the drag source worries about packaging up the data into the correct format and the drag target worries about retrieving the data in the correct format. Your problem is that your drag target is not in your WPF application and so there is very little that you can do as the data is dropped.
A much better solution would be to implement your own basic file browser and then as part of your application, it would be far simpler to access the file path with the drag and drop operation. Either way, you've got a lot of work to do.
Create an empty FileDrop item as soon as you start dragging any item from your ListView.
As one of your mouse buttons is always down while dragging, start a timer which fires an event as soon as you release the pressed mouse button.
When the button is released, get the window handle of the window where the mouse is located. Match that handle against any opened Windows Explorer window.
If a matched window was found, get the location URL of that Windows Explorer Window and manipulate the available URL of that Windows Explorer window to get the (UNC) Windows path.
Create a Windows Form in design mode and add a ListView to it with name lvFiles. Set its AllowDrop property to True.
Then add a timer to the form and name it dropTimer. Set the interval to 50. Set Enabled to False.
In the events of dropTimer, double click, so the event will be dropTimer_Tick.
Go to code behind and paste code below.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace test
{
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern int GetForegroundWindow();
[DllImport("user32.dll")]
static extern short GetKeyState(VirtualKeyStates nVirtKey);
enum VirtualKeyStates : int
{
VK_LBUTTON = 0x01,
VK_RBUTTON = 0x02,
}
bool IsKeyPressed(VirtualKeyStates testKey)
{
bool keyPressed = false;
short result = GetKeyState(testKey);
switch (result)
{
case 0:
keyPressed = false;
break;
case 1:
keyPressed = false;
break;
default:
keyPressed = true;
break;
}
return keyPressed;
}
int GetActiveWindowHandle()
{
const int nChars = 256;
int handle = 0;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if (GetWindowText(handle, Buff, nChars) > 0)
return handle;
else
return 0;
}
private string GetWindowsExplorerPathFromWindowHandle(int handle)
{
// Add a project COM reference to Microsoft Internet Controls 1.1
SHDocVw.ShellWindows shellWindows = new SHDocVw.ShellWindowsClass();
string fileName;
string path = "";
foreach ( SHDocVw.InternetExplorer ie in shellWindows )
{
fileName = Path.GetFileNameWithoutExtension(ie.FullName).ToLower();
if (fileName.Equals("explorer") && ie.HWND == handle)
{
path = ie.LocationURL;
path = path.ToLower();
path = path.Replace("file://", "");
if (path.StartsWith("/"))
path = path.Substring(1);
path = path.Replace("/", "\\");
if (!path.Contains(":")) // unc paths
path = "\\\\" + path;
break;
}
}
return path;
}
// Replace the created event from the designer with this event:
//
private void lvFiles_ItemDrag(object sender, ItemDragEventArgs e)
{
// fake drag and drop effect (start)
string dataFormat = DataFormats.FileDrop;
string[] data = new string[1];
data[0] = "";
DataObject dataObject = new DataObject(dataFormat, data);
// catch mouse events
if (IsKeyPressed(VirtualKeyStates.VK_LBUTTON))
MouseButtonPressed = MouseButtons.Left;
else if (IsKeyPressed(VirtualKeyStates.VK_RBUTTON))
MouseButtonPressed = MouseButtons.Right;
else
MouseButtonPressed = MouseButtons.None;
if (MouseButtonPressed == MouseButtons.Left || MouseButtonPressed == MouseButtons.Right)
this.dropTimer.Enabled = true;
// fake drag and drop effect (launch)
DoDragDrop(dataObject, DragDropEffects.Copy);
}
private void dropTimer_Tick(object sender, EventArgs e)
{
bool mouseButtonsReleased = false;
if (MouseButtonPressed == MouseButtons.Left && !IsKeyPressed(VirtualKeyStates.VK_LBUTTON))
mouseButtonsReleased = true;
else if (MouseButtonPressed == MouseButtons.Right && !IsKeyPressed(VirtualKeyStates.VK_RBUTTON))
mouseButtonsReleased = true;
if (mouseButtonsReleased)
{
dropTimer.Enabled = false;
int handle = GetActiveWindowHandle();
string dropPath = GetWindowsExplorerPathFromWindowHandle(handle);
MessageBox.Show(dropPath); // Here is where the Windows Explorer path is shown
}
}
}
}
Fill your ListView some way and drag any of the ListView item(s) to a Windows Explorer window; The drop path will show up.
I am currently attempting to get some text from a child window using SendMessage via C# pinvoke. However, my previous attempts to hardcore the window handle failed as the value changes upon the startup of the application. Is there a way to reliably get the window handle of this child window? Winspector spy shows that the class name of this window is RichEdit20W. My current code is as follows :
IntPtr hWnd= (IntPtr) 0xA0E88; // Hardcode window handle
int txtlen = SendMessage(hWnd, WM_GETTEXTLENGTH, 20, null);
StringBuilder text = new StringBuilder(txtlen);
int RetVal = SendMessage(hWnd, WM_GETTEXT, text.Capacity, text);
If you can get the top-level window (the one with the title bar), you can use FindWindowEx to recurse through the children. This lets you specify the text of the window (use null since you don't know it), and/or the class (which you know).
http://www.pinvoke.net/default.aspx/user32.findwindowex
I ended up using the Managed Windows API to enumerate all descendant windows of the window.
var descendantwindows = childWindows[0].AllDescendantWindows; // Get all descendant windows of CMainWindow
for (int i = 0; i<descendantwindows.Length; i++)
{
if (descendantwindows[i].ClassName == "RichEdit20W")
childHandle = descendantwindows[i].HWnd;
}
You can PInvoke the API [FindWindow][1] in order to have the top level window, or maybe better, if you know the process name, by using:
Process[] processes = Process.GetProcessesByName("yourprocessname");
foreach (Process p in processes)
{
IntPtr pFoundWindow = p.MainWindowHandle;
// Do something with the handle...
//
}
Notice there is more entries because there can be potentially more process instance running at the same time. Then you need some strategy to lookup the top level children to point exactly the window you are looking for.