Outgoing Outlook Mail Message Minimized During Modal ShowDialog() Event (VSTO) - vb.net

We've inherited some code for a VSTO Outlook Add-In that pops up a modal dialog sort of as an acknowledgement to the end user before sending an email.
The dialog is fired on the Application_ItemSend event
Private Sub Application_ItemSend(ByVal Item As Object, ByRef Cancel As Boolean) Handles Application.ItemSend
The problem that we're seeing is that when we show the dialog:
objCheckDialog.ShowDialog()
The outgoing email window is minimized, when the dialog pops-up which is not desirable and using .Show() is also not desirable.
During our research we've seen some issues where it was suggested to investigate parent properties of our dialog object, however we don't see any parent properties available which would allow us to maximize the parent:
Another suggestion was to pass the ShowDialog() a reference to the Add-In to specify an owner of the dialog box, IE:
objCheckDialog.ShowDialog(Me)
Since that property is also Nothing, but thought that this might populate Parent:
However, that throws the following exception:
{"Unable to cast object of type 'XYZ.ThisAddIn' to type 'System.Windows.Forms.IWin32Window'."}
Any idea of what we're doing wrong?
Thanks.

You would need to use the NativeWindow class.
objCheckDialog.ShowInTaskbar = false;
IntPtr wnd = ParentWindow();
if (wnd != IntPtr.Zero)
{
NativeWindow nativeWindow = new NativeWindow();
nativeWindow.AssignHandle(wnd);
return objCheckDialog.ShowDialog(nativeWindow);
}
else
{
return form.ShowDialog();
}
Parent windows handle can be retrieved from Explorer or Inspector object using IOleWindow interface:
[ComImport]
[Guid("00000114-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleWindow
{
void GetWindow(out IntPtr phwnd);
void ContextSensitiveHelp([In, MarshalAs(UnmanagedType.Bool)] bool fEnterMode);
}
public IntPtr ParentWindow()
{
IntPtr wnd = new IntPtr(0);
object window = _application.ActiveWindow();
if (window != null)
{
IOleWindow oleWindow = window as IOleWindow;
if (oleWindow != null)
{
oleWindow.GetWindow(out wnd);
}
}
return wnd;
}

Related

Outlook VSTO Addin - NormalEmail.dotm not saved

I'm trying to register a handler for the WindowSelectionChanged event. It works, but when quitting Outlook, it will discard any changes to NormalEmail.dotm file.
For example, any changes to the Quick Part Gallery (Insert->Text->QuickParts) will only be visible during the current session; closing Outlook and starting it again will show the previous list of elements, even if they were deleted, or new ones were added).
Deleting the file :
C:\Users[USER]\AppData\Roaming\Microsoft\Templates\ NormalEmail.dotm
should prompt Outlook to create a new one, but this won't happen when the Addin is enabled.
This can be reproduced by registering even an empty method to the WindowSelectionChange event:
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.Inspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(RegisterChangeHandler);
}
void RegisterChangeHandler(Outlook.Inspector inspector)
{
Microsoft.Office.Interop.Word.Application app = Application.ActiveExplorer().ActiveInlineResponseWordEditor?.Application;
--> app.WindowSelectionChange += WinSelectionChange; <-- this line causes the bug
}
private void WinSelectionChange(Selection Sel)
{
/* nothing */
}
...
}
This causes no exceptions; the result is the same when putting try/catch blocks in place, or when the callback is unregistered in the Application.Quit event.
Question: How can Outlook be made to commit the NormalEmail.dotm file, while also being able to register a callback for WindowSelectionChange event ?
(the project type is "2013 and 2016 VSTO Addin")

Best way "select all" on a *form*? [duplicate]

I'm looking for a best way to implement common Windows keyboard shortcuts (for example Ctrl+F, Ctrl+N) in my Windows Forms application in C#.
The application has a main form which hosts many child forms (one at a time). When a user hits Ctrl+F, I'd like to show a custom search form. The search form would depend on the current open child form in the application.
I was thinking of using something like this in the ChildForm_KeyDown event:
if (e.KeyCode == Keys.F && Control.ModifierKeys == Keys.Control)
// Show search form
But this doesn't work. The event doesn't even fire when you press a key. What is the solution?
You probably forgot to set the form's KeyPreview property to True. Overriding the ProcessCmdKey() method is the generic solution:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
if (keyData == (Keys.Control | Keys.F)) {
MessageBox.Show("What the Ctrl+F?");
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
On your Main form
Set KeyPreview to True
Add KeyDown event handler with the following code
private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.N)
{
SearchForm searchForm = new SearchForm();
searchForm.Show();
}
}
The best way is to use menu mnemonics, i.e. to have menu entries in your main form that get assigned the keyboard shortcut you want. Then everything else is handled internally and all you have to do is to implement the appropriate action that gets executed in the Click event handler of that menu entry.
You can even try this example:
public class MDIParent : System.Windows.Forms.Form
{
public bool NextTab()
{
// some code
}
public bool PreviousTab()
{
// some code
}
protected override bool ProcessCmdKey(ref Message message, Keys keys)
{
switch (keys)
{
case Keys.Control | Keys.Tab:
{
NextTab();
return true;
}
case Keys.Control | Keys.Shift | Keys.Tab:
{
PreviousTab();
return true;
}
}
return base.ProcessCmdKey(ref message, keys);
}
}
public class mySecondForm : System.Windows.Forms.Form
{
// some code...
}
If you have a menu then changing ShortcutKeys property of the ToolStripMenuItem should do the trick.
If not, you could create one and set its visible property to false.
From the main Form, you have to:
Be sure you set KeyPreview to true( TRUE by default)
Add MainForm_KeyDown(..) - by which you can set here any shortcuts you want.
Additionally,I have found this on google and I wanted to share this to those who are still searching for answers. (for global)
I think you have to be using user32.dll
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0312)
{
/* Note that the three lines below are not needed if you only want to register one hotkey.
* The below lines are useful in case you want to register multiple keys, which you can use a switch with the id as argument, or if you want to know which key/modifier was pressed for some particular reason. */
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF); // The key of the hotkey that was pressed.
KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF); // The modifier of the hotkey that was pressed.
int id = m.WParam.ToInt32(); // The id of the hotkey that was pressed.
MessageBox.Show("Hotkey has been pressed!");
// do something
}
}
Further read this http://www.fluxbytes.com/csharp/how-to-register-a-global-hotkey-for-your-application-in-c/
Hans's answer could be made a little easier for someone new to this, so here is my version.
You do not need to fool with KeyPreview, leave it set to false. To use the code below, just paste it below your form1_load and run with F5 to see it work:
protected override void OnKeyPress(KeyPressEventArgs ex)
{
string xo = ex.KeyChar.ToString();
if (xo == "q") //You pressed "q" key on the keyboard
{
Form2 f2 = new Form2();
f2.Show();
}
}
In WinForm, we can always get the Control Key status by:
bool IsCtrlPressed = (Control.ModifierKeys & Keys.Control) != 0;
The VB.NET version of Hans' answer.
(There's a ProcessCmdKey function template in Visual Studio.)
Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
If (keyData = (Keys.Control Or Keys.F)) Then
' call your sub here, like
SearchDialog()
Return True
End If
Return MyBase.ProcessCmdKey(msg, keyData)
End Function
End Class

Set FolderBrowserDialog to show directly the set filepath folder [duplicate]

As show in this screen shot, the selected folder is not in the view. It needs to be scrolled down to view the selected folder.
Same dialog shows selected folder visible on different computer
I ran it on two computers both having windows 7. It works correctly on one but does not on 2nd. It looks something with windows environment instead some code issue? Can anyone suggest any fix?
There is no change in code. I used longer paths from different drives but results are same.
private void TestDialog_Click ( object sender, EventArgs e )
{
//Last path store the selected path, to show the same directory as selected on next application launch.
//Properties.Settings.Default.LastPath
FolderBrowserDialog dlgFolder = new FolderBrowserDialog ();
dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;
dlgFolder.SelectedPath = Properties.Settings.Default.LastPath;
if (dlgFolder.ShowDialog () == System.Windows.Forms.DialogResult.OK)
{
Properties.Settings.Default.LastPath = dlgFolder.SelectedPath;
Properties.Settings.Default.Save ();
}
}
The fundamental problem is a poor design decision in the FolderBrowserDialog. First, we need to realize that the FolderBrowserDialog is not a .NET control, but is rather the Common Dialog and is part of Windows. The designer of this dialog elected not to send the TreeView control a TVM_ENSUREVISIBLE message after the dialog is displayed and an initial folder is selected. This message causes a TreeView control to scroll so that the currently selected item is visible in the window.
So, all we need to do to fix this is to send the TreeView that is part of the FolderBrowserDialog the TVM_ENSUREVISIBLE message and everything will be great. Right? Well, not so fast. This is indeed the answer, but there some things standing in our way.
First, because the FolderBrowserDialog is not really a .NET control, it does not have an internal Controls collection. This means that we can't just find and access the TreeView child control from .NET.
Second, the designers of the .NET FolderBrowserDialog class decided to seal this class. This unfortunate decision prevents us from deriving from it and overriding the window message handler. Had we been able to do this, we might have tried to post the TVM_ENSUREVISIBLE message when we got the WM_SHOWWINDOW message in the message handler.
The third issue is that we can’t send the TVM_ENSUREVISIBLE message until the Tree View control actually exists as a real window, and it does not exist until we call the ShowDialog method. However, this method blocks, so we won’t have the opportunity to post our message once this method is called.
To get around these issues, I created a static helper class with a single method that can be used to show a FolderBrowserDialog, and will cause it to scroll to the selected folder. I manage this by starting a short Timer just prior to calling the dialogue's ShowDialog method, and then tracking down the handle of the TreeView control in the Timer handler (i.e., after the dialogue is displayed) and sending our TVM_ENSUREVISIBLE message.
This solution is not perfect because it depends on some prior knowledge about the FolderBrowserDialog. Specifically, I find the dialogue using its window title. This will break with non-English installations. I track down the child controls in the dialogue using their dialogue Item IDs, rather than title text or class name, because I felt this would be more reliable over time.
This code has been tested on Windows 7 (64 bit), and Windows XP.
Here is the code:
(You may need: using System.Runtime.InteropServices;)
public static class FolderBrowserLauncher
{
/// <summary>
/// Using title text to look for the top level dialog window is fragile.
/// In particular, this will fail in non-English applications.
/// </summary>
const string _topLevelSearchString = "Browse For Folder";
/// <summary>
/// These should be more robust. We find the correct child controls in the dialog
/// by using the GetDlgItem method, rather than the FindWindow(Ex) method,
/// because the dialog item IDs should be constant.
/// </summary>
const int _dlgItemBrowseControl = 0;
const int _dlgItemTreeView = 100;
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Some of the messages that the Tree View control will respond to
/// </summary>
private const int TV_FIRST = 0x1100;
private const int TVM_SELECTITEM = (TV_FIRST + 11);
private const int TVM_GETNEXTITEM = (TV_FIRST + 10);
private const int TVM_GETITEM = (TV_FIRST + 12);
private const int TVM_ENSUREVISIBLE = (TV_FIRST + 20);
/// <summary>
/// Constants used to identity specific items in the Tree View control
/// </summary>
private const int TVGN_ROOT = 0x0;
private const int TVGN_NEXT = 0x1;
private const int TVGN_CHILD = 0x4;
private const int TVGN_FIRSTVISIBLE = 0x5;
private const int TVGN_NEXTVISIBLE = 0x6;
private const int TVGN_CARET = 0x9;
/// <summary>
/// Calling this method is identical to calling the ShowDialog method of the provided
/// FolderBrowserDialog, except that an attempt will be made to scroll the Tree View
/// to make the currently selected folder visible in the dialog window.
/// </summary>
/// <param name="dlg"></param>
/// <param name="parent"></param>
/// <returns></returns>
public static DialogResult ShowFolderBrowser( FolderBrowserDialog dlg, IWin32Window parent = null )
{
DialogResult result = DialogResult.Cancel;
int retries = 10;
using (Timer t = new Timer())
{
t.Tick += (s, a) =>
{
if (retries > 0)
{
--retries;
IntPtr hwndDlg = FindWindow((string)null, _topLevelSearchString);
if (hwndDlg != IntPtr.Zero)
{
IntPtr hwndFolderCtrl = GetDlgItem(hwndDlg, _dlgItemBrowseControl);
if (hwndFolderCtrl != IntPtr.Zero)
{
IntPtr hwndTV = GetDlgItem(hwndFolderCtrl, _dlgItemTreeView);
if (hwndTV != IntPtr.Zero)
{
IntPtr item = SendMessage(hwndTV, (uint)TVM_GETNEXTITEM, new IntPtr(TVGN_CARET), IntPtr.Zero);
if (item != IntPtr.Zero)
{
SendMessage(hwndTV, TVM_ENSUREVISIBLE, IntPtr.Zero, item);
retries = 0;
t.Stop();
}
}
}
}
}
else
{
//
// We failed to find the Tree View control.
//
// As a fall back (and this is an UberUgly hack), we will send
// some fake keystrokes to the application in an attempt to force
// the Tree View to scroll to the selected item.
//
t.Stop();
SendKeys.Send("{TAB}{TAB}{DOWN}{DOWN}{UP}{UP}");
}
};
t.Interval = 10;
t.Start();
result = dlg.ShowDialog( parent );
}
return result;
}
}
I know this thread is WAY old, but with extension methods, this can be added to the FolderBrowserDialog.ShowDialog method, and then used repeatedly where needed.
The sample (below) is just using the easy SendKeys method (which I hate doing, but in this case, it works well). When using the SendKeys method to jump to the selected folder in the dialog, if you are debugging this in Visual Studio, then the SendKeys call applies to the current window, which would be the active VS window. To be more foolproof and to avoid the wrong window from getting the SendKeys message, then the extension method would contain the external method calls to send messages to the specific window similar to what Marc F posted, but translated to C#.
internal static class FolderBrowserDialogExtension
{
public static DialogResult ShowDialog(this FolderBrowserDialog dialog, bool scrollIntoView)
{
return ShowDialog(dialog, null, scrollIntoView);
}
public static DialogResult ShowDialog(this FolderBrowserDialog dialog, IWin32Window owner, bool scrollIntoView)
{
if (scrollIntoView)
{
SendKeys.Send("{TAB}{TAB}{RIGHT}");
}
return dialog.ShowDialog(owner);
}
}
I have used a workaround from https://www.daniweb.com/software-development/csharp/threads/300578/folderbrowserdialog-expanding-the-selected-directory-
FolderBrowserDialog^ oFBD = gcnew FolderBrowserDialog;
oFBD->RootFolder = Environment::SpecialFolder::MyComputer;
oFBD->SelectedPath = i_sPathImport;
oFBD->ShowNewFolderButton = false; // use if appropriate in your application
SendKeys::Send ("{TAB}{TAB}{RIGHT}"); // <<-- Workaround
::DialogResult oResult = oFBD->ShowDialog ();
It's not the nicest way, but it works for me.
Without the RootFolder it does NOT work on the first call, but on the 2nd and following. With it, it works always.
As others have observed that this failure is dependent on the operating system:
I am using Win 7 Pro x64 SP1
on VB.Net code, just put this line of code right before showing the dialog.
SendKeys.Send ("{TAB}{TAB}{RIGHT}")
this works for me
folderBrowserDialog1.Reset();
folderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyComputer;
folderBrowserDialog1.SelectedPath = WorkingFolder;
but only after the second use of the dialog
I read at different forums that it could be due to RootFolder because SelectedPath and RootFolder are are mutually exclusive, that means both cannot co-exists but with default RootFolder(.Desktop), It allows ,at least, climbing the Tree(navigate the drive/folders).
However, if RootFolder is changed to other than Desktop, you would not be able to navigate to UNC paths.
Answer to Hans Passant:
I tried this Dialog Extension, which has TextBox, but no luck.
Customising the browse for folder dialog to show the path
I have found that:
If .SelectedPath ends with "\", the Dialog will scroll down to make the path visible.
If .SelectedPath does not end with "\", the path is still selected, but not ensured visible.
I computed something in VB.NET, so it would be easy to transform it into C#.
I'm French, and I'm beginner in VB.
Anyway, you can try my solution.
My idea is to launch an asynchronous task just before showing the folderBrowserDialog.
I found this myself, but I was inspired by Brad post.
Here's my code:
Imports System.Threading.Tasks
Imports Microsoft.VisualBasic.FileIO.FileSystem
Public Enum GW
HWNDFIRST = 0
HWNDLAST = 1
HWNDNEXT = 2
HWNDPREV = 3
OWNER = 4
CHILD = 5
ENABLEDPOPUP = 6
End Enum
Public Declare Function SendMessageW Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal msg As UInteger, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As IntPtr
Public Declare Function FindWindowExW Lib "user32.dll" (ByVal hWndParent As IntPtr, ByVal hWndChildAfter As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszClass As String, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszWindow As String) As IntPtr
Public Declare Function GetWindow Lib "user32" (ByVal hwnd As IntPtr, ByVal wCmd As Long) As Long
Public Declare Function GetDesktopWindow Lib "user32" () As IntPtr
Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
Private Sub FolderBrowserDialog_EnsureVisible(FB As FolderBrowserDialog, _Owner As IntPtr)
Dim hwnd As IntPtr
Dim sClassname As New System.Text.StringBuilder(256)
Thread.Sleep(50) 'necessary to let FolderBrowserDialog construct its window
hwnd = GetDesktopWindow() 'Desktop window handle.
hwnd = GetWindow(hwnd, GW.CHILD) 'We will find all children.
Do Until hwnd = 0
If GetWindow(hwnd, GW.OWNER) = _Owner Then 'If one window is owned by our main window...
GetClassName(hwnd, sClassname, 255)
If sClassname.ToString = "#32770" Then 'Check if the class is FolderBrowserDialog.
Exit Do 'Then we found it.
End If
End If
hwnd = GetWindow(hwnd, GW.HWNDNEXT) 'Next window.
Loop 'If no found then exit.
If hwnd = 0 Then Exit Sub
Dim hChild As IntPtr = 0
Dim hTreeView As IntPtr = 0
Dim i As Integer = 0
Do
i += 1
If i > 1000 Then Exit Sub 'Security to avoid infinite loop.
hChild = FindWindowExW(hwnd, hChild, Nothing, Nothing) 'Look for children windows of FolderBrowserDialog.
hTreeView = FindWindowExW(hChild, 0, "SysTreeView32", Nothing) 'Look for treeview of FolderBrowserDialog.
Thread.Sleep(5) 'delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
Loop While hTreeView = 0
If SendMessageW(hwnd, &H46A, 1, FB.SelectedPath) = 0 Then 'Send message BFFM_SETEXPANDED to FolderBrowserDialog.
SendMessageW(hTreeView, &H7, 0, Nothing) 'Send message WM_SETFOCUS to the treeeview.
End If
End Sub
Dim My_save_dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\My-Saves"
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim FolderBrowserDialog1 As New FolderBrowserDialog
FolderBrowserDialog1.Description = "Choose your save files path."
If Directory.Exists(My_save_dir) Then
FolderBrowserDialog1.SelectedPath = My_save_dir
Else
FolderBrowserDialog1.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
End If
Dim Me_handle = Me.Handle 'Store the main handle to compare after with each windows owner.
Task.Run(Sub() FolderBrowserDialog_EnsureVisible(FolderBrowserDialog1, Me_handle)) 'Here's the trick, run an asynchronous task to modify the folderdialog.
If FolderBrowserDialog1.ShowDialog(Me) = System.Windows.Forms.DialogResult.OK Then
My_save_dir = FolderBrowserDialog1.SelectedPath
End If
End Sub
I'm waiting for your suggestions.
And someone can translate it into C# because I don't know C#.
I run into same problem in c++ /mfc. It worked for me to use ::PostMessage rather than ::SendMessage in the BFFM_INITIALIZED callback to place the TVM_ENSUREVISIBLE msg
case BFFM_INITIALIZED:
{
// select something
::SendMessage(m_hDialogBox, BFFM_SETSELECTION, TRUE, (LPARAM) pszSelection);
// find tree control
m_hTreeCtrl = 0;
HWND hchild = GetWindow(hWnd, GW_CHILD) ;
while (hchild != NULL)
{
VS_TChar classname[200] ;
GetClassName(hchild, classname, 200) ;
if (VS_strcmp(classname, _T("SHBrowseForFolder ShellNameSpace Control")) == 0)
{
HWND hlistctrl = GetWindow(hchild, GW_CHILD) ;
do
{
GetClassName(hlistctrl, classname, 200) ;
if (lstrcmp(classname, _T("SysTreeView32")) == 0)
{
m_hTreeCtrl = hlistctrl;
break ;
}
hlistctrl = GetWindow(hlistctrl, GW_HWNDNEXT) ;
} while (hlistctrl != NULL);
}
if (m_hTreeCtrl)
break;
hchild = GetWindow(hchild, GW_HWNDNEXT);
}
if (m_hTreeCtrl)
{
int item = ::SendMessage(m_hTreeCtrl, TVM_GETNEXTITEM, TVGN_CARET, 0);
if (item != 0)
::PostMessage(m_hTreeCtrl, TVM_ENSUREVISIBLE,0,item);
}
break;
}
I have read the above discussion and solutions. Particularly Brat Oestreicher put me in the right direction. In essence, we must first find the TreeView control in the SHBrowseForFolder dialog, and send that window the TVM_ENSUREVISIBLE message. The following does this in C.
#include <windows.h>
#include <objbase.h>
#include <objidl.h>
#include <Shlobj.h>
#include <Dsclient.h>
#include <wchar.h>
//
// EnumCallback - Callback function for EnumWindows
//
static BOOL CALLBACK EnumCallback(HWND hWndChild, LPARAM lParam)
{
char szClass[MAX_PATH];
HTREEITEM hNode;
if (GetClassName(hWndChild, szClass, sizeof(szClass))
&& strcmp(szClass,"SysTreeView32")==0) {
hNode = TreeView_GetSelection(hWndChild); // found the tree view window
TreeView_EnsureVisible (hWndChild, hNode); // ensure its selection is visible
return(FALSE); // done; stop enumerating
}
return(TRUE); // continue enumerating
}
//
// BrowseCallbackProc - Callback function for SHBrowseForFolder
//
static INT CALLBACK BrowseCallbackProc (HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
switch (uMsg)
{
case BFFM_INITIALIZED:
SendMessage (hWnd, BFFM_SETEXPANDED, TRUE, lpData); // expand the tree view
SendMessage (hWnd, BFFM_SETSELECTION, TRUE, lpData); // select the item
break;
case BFFM_SELCHANGED:
EnumChildWindows(hWnd, EnumCallback,0);
break;
}
return 0;
}
//
// SelectDirectory - User callable entry point
//
int SelectDirectory (HWND hWndParent, char *path, int pathSize)
{
BROWSEINFO bi = {0};
LPITEMIDLIST pidl = NULL;
wchar_t ws[MAX_PATH];
CoInitialize(0);
if (pathSize < MAX_PATH) return(FALSE);
swprintf(ws, MAX_PATH, L"%hs", path);
bi.hwndOwner = hWndParent;
bi.lpszTitle = "Select Directory";
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM) ws;
pidl = SHBrowseForFolder (&bi);
if (pidl != NULL)
{
LPMALLOC pMalloc = NULL;
SHGetPathFromIDList (pidl, path);
path[pathSize-1]= '\0';
SHGetMalloc(&pMalloc);
pMalloc->lpVtbl->Free(pMalloc,pidl); // deallocate item
pMalloc->lpVtbl->Release(pMalloc);
return (TRUE);
}
return (FALSE);
}
Many thanks to Gary Beene.
dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;
is not the same as
dlgFolder.RootFolder = Environment.SpecialFolder.Desktop;
What's the difference between SpecialFolder.Desktop and SpecialFolder.DesktopDirectory?
The thread linked indicates that as a path, they do get the same result. But they are not the same, as one is a logical path and the other is a physical path.
I have found when either one is assigned to the RootFolder of the open folder dialog, the resulting behavior can be different.
As a .RootFolder assignment, some versions of windows, like win7, treat either one as "Desktop". That is, you can see the "Computer" sub-entry, and open that to see the individual drive letters. The .SelectedPath gets selected either way, but the selected path is only made visible when the logical path of the desktop is assigned to the .RootFolder.
Worse, when using the browse folder dialog in win10 pre-release, it appears that "DesktopDirectory" as just that, the contents of the Desktop Directory only, with no link whatsoever to the logical desktop directory. And not listing any sub-items under it. Very frustrating if an app written for win7 is trying to be used with win10.
I think the problem the OP is having is that they employed the physical desktop as the root, when they should have employed the logical desktop.
I don't have an explanation for why the OP's two different machines respond differently. I would speculate that they have two different versions of the .NET framework installed.
The fact that win10 prerelease has the "Stuck on Desktop" issue with the browse folder dialog may be due to the more recent .NET framework shipped with win10 prerelease. Unfortunately, I remain ignorant of all the facts in this (win10) case, as I have not updated yet.
P.S. I found that win8 also experiences the "Stuck on Desktop" symptom:
https://superuser.com/questions/869928/windows-8-1-folder-selection-dialog-missing-my-computer-and-sub-items
The workaround there was to select the alternate GUI in win8. Perhaps something similar can be done in win10 prerelease.
In response to Marc F's post - I've converted the VB.Net to C#
public enum GW
{
HWNDFIRST = 0,
HWNDLAST = 1,
HWNDNEXT = 2,
HWNDPREV = 3,
OWNER = 4,
CHILD = 5,
ENABLEDPOPUP = 6
}
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessageW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr SendMessageW(IntPtr hWnd, uint msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "FindWindowExW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndChildAfter, [MarshalAs(UnmanagedType.LPWStr)] string lpszClass, [MarshalAs(UnmanagedType.LPWStr)] string lpszWindow);
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern UInt32 GetWindow(IntPtr hwnd, UInt32 wCmd);
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetDesktopWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetDesktopWindow();
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetClassNameA", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern int GetClassName(IntPtr hwnd, System.Text.StringBuilder lpClassName, int nMaxCount);
private void FolderBrowserDialog_EnsureVisible(FolderBrowserDialog FB, IntPtr _Owner)
{
IntPtr hwnd = System.IntPtr.Zero;
System.Text.StringBuilder sClassname = new System.Text.StringBuilder(256);
Thread.Sleep(50); //necessary to let FolderBrowserDialog construct its window
hwnd = GetDesktopWindow(); //Desktop window handle.
hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.CHILD); //We will find all children.
while (!(hwnd == (System.IntPtr)0))
{
if (GetWindow(hwnd, (UInt32)GW.OWNER) == (UInt32)_Owner) //If one window is owned by our main window...
{
GetClassName(hwnd, sClassname, 255);
if (sClassname.ToString() == "#32770") //Check if the class is FolderBrowserDialog.
{
break; //Then we found it.
}
}
hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.HWNDNEXT); //Next window.
} //If no found then exit.
if (hwnd == (System.IntPtr)0)
{
return;
}
IntPtr hChild = (System.IntPtr)0;
IntPtr hTreeView = (System.IntPtr)0;
int i = 0;
do
{
i += 1;
if (i > 1000) //Security to avoid infinite loop.
{
return;
}
hChild = FindWindowExW(hwnd, hChild, null, null); //Look for children windows of FolderBrowserDialog.
hTreeView = FindWindowExW(hChild, (System.IntPtr)0, "SysTreeView32", null); //Look for treeview of FolderBrowserDialog.
Thread.Sleep(5); //delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
} while (hTreeView == (System.IntPtr)0);
if (SendMessageW(hwnd, 0x46A, 1, FB.SelectedPath) == (System.IntPtr)0) //Send message BFFM_SETEXPANDED to FolderBrowserDialog.
{
SendMessageW(hTreeView, 0x7, 0, null); //Send message WM_SETFOCUS to the treeeview.
}
}
Tested this and it works fine. Make sure you reference System.Runtime.InteropServices, System.Threading, an System.Threading.Tasks
This link has a simple answer that worked for me fine (I have windows 8.1)
FolderBrowserDialog: Expanding the selected directory
I tried the code SendKeys.Send("{TAB}{TAB}{RIGHT}");, but the same code sometimes just didn't work.
At the end the best solution which I went with, where I can't get it not working, was:
public static class FolderBrowserDialogExt
{
public static void ScrollSelectedPathIntoView(this FolderBrowserDialog fbd)
{
System.Threading.Tasks.Task.Run(() =>
{
SendKeys.SendWait("{TAB}");
SendKeys.SendWait("{TAB}");
SendKeys.SendWait("{RIGHT}");
});
}
}
void SomeMeth(string selPath)
{
using (var fbd = new FolderBrowserDialog())
{
fbd.SelectedPath = selPath;
fbd.ScrollSelectedPathIntoView();
fbd.ShowDialog();//fbd.ShowDialog(owner);
}
}
This is how I use the folder browser dialog. This code solves the selected folder issue, and also selects the folder from the clipboard or the registry (if any), and if the folder is deleted it goes up throw parents until selecting an existing folder. This makes using the dialog very comfortable:
Dim FldrBr As New FolderBrowserDialog With {
.RootFolder = Environment.SpecialFolder.Desktop,
.Description = "Chose a flder",
.ShowNewFolderButton = False
}
Dim x = Clipboard.GetText()
Dim lastDir = GetSetting("Mp4Joiner", "SrcFolder", "Path", "")
Try
If x = "" Then
x = lastDir
ElseIf File.Exists(x) Then
x = Path.GetDirectoryName(x)
ElseIf Not Directory.Exists(x) Then
x = lastDir
End If
Catch
x = lastDir
End Try
Do
If x = "" OrElse Directory.GetDirectoryRoot(x) = x OrElse Directory.Exists(x) Then
Exit Do
End If
x = Path.GetDirectoryName(x)
Loop
FldrBr.SelectedPath = x
Dim th As New Threading.Thread(
Sub()
Threading.Thread.Sleep(300)
SendKeys.SendWait("{TAB}{TAB}{RIGHT}")
End Sub)
th.Start()
If FldrBr.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
SaveSetting("Mp4Joiner", "SrcFolder", "Path", FldrBr.SelectedPath)
' ........
End If
The best approach, at least the most reliable is to make your own browser class dialog box. The tree scrolling issue has been a pain for many years - it will never get fixed!
If you know how to render in paint there is not much you can't do.. fast in paint well that is another story.
The first place I would look is at the open source .Net source code on GitHub, in your .Net version of choice, for the dialog class you're interested in improving. You may be surprised what you can achieve with a little effort and follow through. Just duplicate the control and debug to the point where the error occurs and patch - that'a what Microsoft does, so too can you!
Since this is an old thread and posting samples may never get read. It would make more since to post if asked.
Yet for someone looking to solve such an issue as with tree scrolling to the "expected" directory, here is some solid advise. If an issue exists with a control or library that has no immediate solution, create your own version, when possible extend the original and patch the problem. I've revamped everything from the Windows.Form.Control class to Win32 libraries for the sole purpose of getting predictable and accurate results.
The good news is that with C# there is a lot of low level control available to achieve almost any reasonable objective and the is C too.
In the past I have spent way too many hours searching for a solution to a problem where had I just recreated what was not working a lot of time would have been saved.

Unsubscribe from IObservableElementEnumerable.EnumerableChanged doesn't work?

Parts of our UI uses IObservableElementEnumerable.EnumerableChanged in order to update if the user e.g. deletes a domain object from a folder.
When the UI is disposed, we unsubscribe from the event... or so we thought. It turns out that the unsubscribe doesn't have any effect, and our event handler is still called. This caused a number of odd bugs, but also leads to memory leaks.
The only time unsubscription works, is if we store the IObservableElementEnumerable reference instead of calling IObservableElementEnumerableFactory.GetEnumerable(obj) again. But this, in turn, is likely to keep a live reference to the folder object, which will break if the folder itself is deleted by the user.
This is particularly puzzling as the GetEnumerable() documentation clearly states: "It is expected that subsequent calls with the same domain object will yield the same instance of IObservableElementEnumerable." Is this not to be interpreted as a guarantee?
Should there be any reason for unsubscription not working?
The following code replicates the issue on Petrel 2011 (add to a simple plugin with a menu extension, or get the full solution here (DropBox)):
using System;
using System.Linq;
using System.Windows.Forms;
using Slb.Ocean.Core;
using Slb.Ocean.Petrel;
using Slb.Ocean.Petrel.Basics;
using Slb.Ocean.Petrel.UI;
namespace ObservableElementEnumerable
{
public class OEEForm : Form
{
private Droid _droid;
private bool _disposed;
public OEEForm()
{
IInput input = PetrelProject.Inputs;
IIdentifiable selected = input.GetSelected<object>().FirstOrDefault() as IIdentifiable;
if (selected == null)
{
PetrelLogger.InfoOutputWindow("Select a folder first");
return;
}
_droid = selected.Droid;
GetEnumerable().EnumerableChanged += enumerable_EnumerableChanged;
PetrelLogger.InfoOutputWindow("Enumerable subscribed");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing && !_disposed)
{
GetEnumerable().EnumerableChanged -= enumerable_EnumerableChanged;
PetrelLogger.InfoOutputWindow("Enumerable unsubscribed (?)");
_droid = null;
_disposed = true;
}
}
IObservableElementEnumerable GetEnumerable()
{
if (_disposed)
throw new ObjectDisposedException("OEEForm");
object obj = DataManager.Resolve(_droid);
IObservableElementEnumerableFactory factory = CoreSystem.GetService<IObservableElementEnumerableFactory>(obj);
IObservableElementEnumerable enumerable = factory.GetEnumerable(obj);
return enumerable;
}
void enumerable_EnumerableChanged(object sender, ElementEnumerableChangeEventArgs e)
{
PetrelLogger.InfoOutputWindow("Enumerable changed");
if (_disposed)
PetrelLogger.InfoOutputWindow("... but I am disposed and unsubscribed!");
}
}
public static class Menu1
{
public static void OEEBegin1_ToolClick(object sender, System.EventArgs e)
{
OEEForm f = new OEEForm();
f.Show();
}
}
}
To replicate:
Run Petrel with the plugin
Load a project with a folder with objects
Select the folder
Activate the plugin menu item
With the popup open, delete an object in the folder
Close the Form popping up
Delete an object in the folder
The message log should clearly show that the event handler is still called after the form is disposed.
You already keep a reference to the underlying enumerable by connecting the event. Events are references as well. Just keep a reference to the enumerable and unsubscribe from the same instance as the one you subscribe to.
To deal with the issue of objects that are deleted by the user you need to listen to the delete event.

How to get HWND of a windowless ATL control?

I created a ATL windows less control and the class definition is like this:
class ATL_NO_VTABLE CRSPClient :
public IObjectSafetyImpl<CRSPClient, INTERFACESAFE_FOR_UNTRUSTED_CALLER|INTERFACESAFE_FOR_UNTRUSTED_DATA>,
public CComObjectRootEx<CComSingleThreadModel>,
public IDispatchImpl<IRSPClient, &IID_IRSPClient, &LIBID_axBanckleRSPClientLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IPersistStreamInitImpl<CRSPClient>,
public IOleControlImpl<CRSPClient>,
public IOleObjectImpl<CRSPClient>,
public IOleInPlaceActiveObjectImpl<CRSPClient>,
public IQuickActivateImpl<CRSPClient>,
public IViewObjectExImpl<CRSPClient>,
public IOleInPlaceObjectWindowlessImpl<CRSPClient>,
#ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly
public IObjectSafetyImpl<CRSPClient, INTERFACESAFE_FOR_UNTRUSTED_CALLER>,
#endif
public CComCoClass<CRSPClient, &CLSID_RSPClient>,
public CComControl<CRSPClient>
Then for some purpose I need to post message to the window. I tried to get the handle of the window in quite many ways and ALL of them failed:
HWND CRSPClient::GetHwnd()
{
HWND hwndRet = NULL;
// hwndRet = m_hWnd;
//IOleInPlaceActiveObjectImpl<CRSPClient>::GetWindow(&hwndRet);
//IOleWindow<CRSPClient>::GetWindow(&hwndRet);
//this->m_spInPlaceSite->GetWindow(&hwndRet);
//CComQIPtr<IOleInPlaceSite> spSite = this->m_spClientSite;
//spSite->GetWindow(&hwndRet);
//hwndRet = ::WindowFromDC(GetDC());
return hwndRet;
}
Anybody know be there any way to get the HWND?
OMG I'm totally frustrated by microsoft's great ATL framework!
The whole point of the windowless control is that it works without a window handle. If you wanted to use window handle in case it, by any chance, exists and the control falled back into windowed mode, then it's easy: m_hWndCD.
If otherwise you have to have a window, then you have m_bWindowOnly to flag in constructor and indicate that you will need a HWND:
Flag indicating the control should be windowed, even if the container supports windowless controls.
If you still want it windowless and need a window just sometimes, under certain condition that come up on runtime, you always have an option to create a private message only window and dispatch messages through it.
Here is some code taken from Microsoft's Direct3D ATL sample. I haven't tested it yet.
// Get the window we need to use. This will either be the window that has already
// been created if we are window. If we are windowless the HWND of the client
// will be retrieved from the HDC.
void GetOurWindow()
{
// If we're windowless we still need an HWND for Direct3d
if (m_bWndLess)
{
HDC hDC;
// Get the HDC from the client
m_spInPlaceSite->GetDC(NULL, OLEDC_NODRAW, &hDC);
m_hOurWnd = WindowFromDC(hDC);
#if 1
// Code to make VB5 paint properly now it has clipped it's own hDC
RECT rect;
::GetClientRect(m_hOurWnd,&rect);
HRGN hRegion = CreateRectRgn(rect.left,rect.top,rect.right,rect.bottom);
SelectClipRgn(hDC,hRegion);
#endif
m_spInPlaceSite->ReleaseDC(hDC);
}
else
m_hOurWnd = m_hWnd;
}