Skip to content

Commit ddfb8a4

Browse files
committed
Select file in correct file explorer window
Closes #9
1 parent 5eb3cf9 commit ddfb8a4

File tree

2 files changed

+101
-31
lines changed

2 files changed

+101
-31
lines changed

PasteIntoFile/Dialog.cs

+33-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Drawing;
44
using System.IO;
55
using System.Linq;
6+
using System.Threading.Tasks;
67
using System.Windows.Forms;
78
using PasteIntoFile.Properties;
89
using WK.Libraries.BetterFolderBrowserNS;
@@ -87,31 +88,47 @@ public Dialog(string location = null, bool forceShowDialog = false)
8788
Show();
8889
BringToFront();
8990
WindowState = FormWindowState.Normal;
90-
}
91-
else {
91+
92+
// register clipboard monitor
93+
clipMonitor.ClipboardChanged += ClipboardChanged;
94+
95+
} else {
96+
// directly save without showing a dialog
97+
Opacity = 0; // prevent dialog from showing up for a fraction of a second
98+
9299
var file = clipRead ? save() : null;
93-
if (file != null)
94-
{
95-
if (!saveIntoSubdir)
96-
ExplorerUtil.RequestFilenameEdit(file);
100+
if (file != null) {
97101

98-
Program.ShowBalloon(Resources.str_autosave_balloontitle,
99-
new []{file, Resources.str_autosave_balloontext}, 10);
102+
if (!saveIntoSubdir) {
103+
// select file in explorer for rename and exit afterwards
104+
ExplorerUtil.FilenameEditComplete += (sender, args) => {
105+
Program.ShowBalloon(Resources.str_autosave_balloontitle,
106+
new []{file, Resources.str_autosave_balloontext}, 10);
107+
Environment.ExitCode = 0;
108+
Close();
109+
};
110+
111+
ExplorerUtil.AsyncRequestFilenameEdit(file);
112+
113+
// Timeout in case filename edit fails
114+
Task.Delay(new TimeSpan(0, 0, 0, 10)).ContinueWith(o => Close());
115+
116+
} else {
117+
// exit immediately
118+
Program.ShowBalloon(Resources.str_autosave_balloontitle,
119+
new []{file, Resources.str_autosave_balloontext}, 10);
120+
Environment.ExitCode = 0;
121+
Close();
122+
}
100123

101-
Environment.ExitCode = 0;
102-
Close();
103-
}
104-
else
105-
{
124+
} else {
125+
// save failed, exit with error code
106126
Environment.ExitCode = 1;
107127
Close();
108128
}
109129

110130
}
111131

112-
// register clipboard monitor
113-
clipMonitor.ClipboardChanged += ClipboardChanged;
114-
115132
}
116133

117134
public string formatFilenameTemplate(string template)

PasteIntoFile/ExplorerUtil.cs

+68-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using System.Runtime.InteropServices;
44
using System.Runtime.InteropServices.ComTypes;
5+
using Shell32;
56

67
namespace PasteIntoFile
78
{
@@ -50,37 +51,82 @@ private static SHDocVw.InternetExplorer GetActiveExplorer()
5051
}
5152
}
5253
return null;
53-
}
54+
}
5455

56+
public static event EventHandler FilenameEditComplete;
5557

58+
/// <summary>
59+
/// Searches the file with given path in the given shell window and selects it if found
60+
/// </summary>
61+
/// <param name="window">The shell window</param>
62+
/// <param name="path">The path of the file to select</param>
63+
/// <param name="edit">Select in edit mode if true, otherwise just select</param>
64+
private static void SelectFileInWindow(SHDocVw.InternetExplorer window, string path, bool edit = true) {
65+
if (!(window?.Document is IShellFolderViewDual view)) return;
66+
window.DocumentComplete += (object disp, ref object url) => {
67+
foreach (FolderItem folderItem in view.Folder.Items()) {
68+
if (folderItem.Path == path){
69+
SetForegroundWindow((IntPtr)window.HWND);
70+
// https://docs.microsoft.com/en-us/windows/win32/shell/shellfolderview-selectitem
71+
view.SelectItem(folderItem, 16 /* focus it, */ + 8 /* ensure it's visible, */
72+
+ 4 /* deselect all other and */
73+
+ (edit ? 3 : 1) /* select or edit */);
74+
break;
75+
}
76+
}
77+
FilenameEditComplete?.Invoke(null, EventArgs.Empty);
78+
};
79+
window.Refresh();
80+
}
5681

5782
/// <summary>
5883
/// Request file name edit by user in active explorer path
5984
/// </summary>
6085
/// <param name="filePath">Path of file to select/edit</param>
6186
/// <param name="edit">can be set to false to select only (without entering edit mode)</param>
62-
public static void RequestFilenameEdit(string filePath, bool edit = true)
63-
{
87+
public static void AsyncRequestFilenameEdit(string filePath, bool edit = true) {
6488
filePath = Path.GetFullPath(filePath);
6589
var dirPath = Path.GetDirectoryName(filePath);
66-
67-
// code below adopted from https://stackoverflow.com/a/8682999/13324744
68-
IntPtr folder = PathToPidl(dirPath);
69-
IntPtr file = PathToPidl(filePath);
70-
try
71-
{
72-
SHOpenFolderAndSelectItems(folder, 1, new[] { file }, edit ? 1 : 0);
90+
91+
// check current shell window first
92+
var focussedWindow = GetActiveExplorer();
93+
if (GetExplorerPath(focussedWindow) == dirPath) {
94+
SelectFileInWindow(focussedWindow, filePath, edit);
95+
return;
7396
}
74-
finally
75-
{
76-
ILFree(folder);
97+
98+
// then check other open shell windows
99+
var shellWindows = new SHDocVw.ShellWindows();
100+
foreach (SHDocVw.InternetExplorer window in shellWindows) {
101+
if (GetExplorerPath(window) == dirPath) {
102+
SelectFileInWindow(window, filePath, edit);
103+
return;
104+
}
105+
}
106+
107+
// or open a new shell window
108+
IntPtr file;
109+
SHParseDisplayName(filePath, IntPtr.Zero, out file, 0, out _);
110+
try {
111+
SHOpenFolderAndSelectItems(file, 0, null, edit ? 1 : 0);
112+
FilenameEditComplete?.Invoke(null, EventArgs.Empty);
113+
} finally {
77114
ILFree(file);
78115
}
79116
}
117+
80118

81-
private static IntPtr PathToPidl(string path)
119+
private static IntPtr PathToPidl(string path, bool special = false)
82120
{
83-
// documentation: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishellfolder-parsedisplayname
121+
if (special) {
122+
// Will use special paths, e.g. "::{374DE290-123F-4565-9164-39C4925E467B}" for Downloads
123+
IntPtr fileSpecial;
124+
SHParseDisplayName(path, IntPtr.Zero, out fileSpecial, 0, out _);
125+
return fileSpecial;
126+
}
127+
128+
// Will use full paths, e.g. "C:\Users\User\Downloads" for Downloads
129+
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishellfolder-parsedisplayname
84130
SHGetDesktopFolder(out IShellFolder desktopFolder);
85131
desktopFolder.ParseDisplayName(IntPtr.Zero, null, path, out var pchEaten, out var ppidl, 0);
86132
return ppidl;
@@ -90,6 +136,13 @@ private static IntPtr PathToPidl(string path)
90136
[DllImport("user32.dll")]
91137
private static extern IntPtr GetForegroundWindow();
92138

139+
[DllImport("user32.dll")]
140+
static extern bool SetForegroundWindow(IntPtr hWnd);
141+
142+
[DllImport("shell32.dll", SetLastError = true)]
143+
public static extern void SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr bindingContext,
144+
[Out] out IntPtr pidl, uint sfgaoIn, [Out] out uint psfgaoOut);
145+
93146
[DllImport("shell32.dll")]
94147
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, IntPtr[] apidl, int dwFlags);
95148

0 commit comments

Comments
 (0)