force SHBrowseForFolder() to show desired directory - c++

I've been searching online and fighting this thing for over an hour and still can't seem to get it to work. Most people seem to be satisfied when they get it this far on forums etc. but mine still doesn't work.
I'm trying to force the SHBrowseForFolder() function to start in a folder of my choosing.
char current[MAX_PATH];
strcpy(current,"C:\\Users");
char outbuf[MAX_PATH];
BROWSEINFO bis;
bis.hwndOwner = NULL;
bis.pidlRoot = NULL;
bis.pszDisplayName = outbuf;
bis.lpszTitle = (LPCSTR)"HERE";
bis.ulFlags = BIF_NEWDIALOGSTYLE|BIF_RETURNONLYFSDIRS;
bis.lpfn = NULL;
bis.lParam = (LPARAM)current;
SHBrowseForFolder(
&bis
);
It seems like this should be a relatively simple task. :/
At the moment, the above code is still showing the default: the Desktop folder.
Beyond starting in a specific folder, if possible, I'd also like it to ONLY show that folder and below, with no access to parent directories.
What am I missing here?

You can also send a BFFM_SETSELECTION message from your BrowseCallbackProc, like:
int FAR PASCAL BrowseNotify(HWND hWnd, UINT iMessage, long wParam, LPARAM lParam)
{ if (iMessage == BFFM_INITIALIZED)
{ SendMessage(hWnd, BFFM_SETSELECTION, 1, (LPARAM) szInitialPathName); // Set initial folder
return 1;
}
return 0;
}

Set the BFFCALLBACK (lpfn) to a BrowseCallbackProc. From there you can call SendMessage with BFFM_SETEXPANDED to specify the path of a folder to expand in the Browse dialog box.
See:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773205(v=vs.85).aspx and
http://msdn.microsoft.com/en-us/library/windows/desktop/bb762598(v=vs.85).aspx
From my experience, that folder dialog is a bit flaky - it often scrolls the desired directory out of view and looks suboptimal. Just one of the joys of Windows...
Also, there is no way I have discovered to get it to show only that directory and its subs. The parent directories always seem to be there.

Set BIF.PidlRoot to the PIDL you don't want the user to browse below, select and expand the folder you want initially focused and selected - do as above - and it should work.
Jens. :)

Related

How to create a notification balloon on Windows?

I want to create a simple command line tool to post quick notifications like this.
I want the tool to be as simple and small as possible. So I choose to code in CPP, and use Win32 API directly.
I found this guide very useful. But it seems this Shell_NotifyIcon API requires a valid hWnd handler, which means I will have to create a hidden/invisible window in my command line tool, which I'd rather not.
Any better idea on how to create a notification on Windows?
The shell notification API requires that you supply a window handle. So create a message only window and use that as the owner of the notification icon and balloons.
That you would prefer not to create a window in your console app is understandable but the API is what it is. You don't get to re-write system APIs for your convenience. You just have to go along with them.
You can do that easily with no need to use ATL or MFC, just pure Win32 API, which can be a Console application as well.
First you need the icon image. Store it in your resource.h file
#define IDI_BATTERY_IMAGE 101
add an .rc file to your project with the following line
IDI_BATTERY_IMAGE ICON "Battery.ico"
Bonus tip: you can find free images to download, like this one. Just make sure to call it "Battery.ico" and place it in the path of your other source files.
Best if you create a separate pair of .cpp and .h files for the notification balloon functionality.
In your .cpp file place the following code:
// tray icon data
NOTIFYICONDATA m_NID;
BOOL CreateTrayIcon()
{
memset(&m_NID, 0, sizeof(m_NID));
m_NID.cbSize = sizeof(m_NID);
m_NID.uID = IDI_BATTERY_IMAGE;
// set handle to the window that receives tray icon notifications
m_NID.hWnd = GetForegroundWindow();
// fields that are being set when adding tray icon
m_NID.uFlags = NIF_MESSAGE | NIF_ICON;
// set image
m_NID.hIcon = LoadIcon(NULL,MAKEINTRESOURCE(IDI_BATTERY_IMAGE));
if (!m_NID.hIcon)
return FALSE;
m_NID.uVersion = NOTIFYICON_VERSION_4;
if (!Shell_NotifyIcon(NIM_ADD, &m_NID))
return FALSE;
return Shell_NotifyIcon(NIM_SETVERSION, &m_NID);
}
BOOL ShowTrayIconBalloon(LPCTSTR pszTitle, LPCTSTR pszText, UINT unTimeout, DWORD dwInfoFlags)
{
m_NID.uFlags |= NIF_INFO;
m_NID.uTimeout = unTimeout;
m_NID.dwInfoFlags = dwInfoFlags;
if (StringCchCopy(m_NID.szInfoTitle, sizeof(m_NID.szInfoTitle), pszTitle) != S_OK)
return FALSE;
if (StringCchCopy(m_NID.szInfo, sizeof(m_NID.szInfo), pszText) != S_OK)
return FALSE;
return Shell_NotifyIcon(NIM_MODIFY, &m_NID);
}
In your header file place the following:
BOOL CreateTrayIcon();
BOOL ShowTrayIconBalloon(LPCTSTR pszTitle, LPCTSTR pszText, UINT unTimeout, DWORD dwInfoFlags);
In your main function add the following code:
CreateTrayIcon();
ShowTrayIconBalloon(L"27 percent remaining", L"Your battary has such and such percentage...", 1000, NULL);
The following code was tested with a simple Console app.

How to get names of the icons on desktop

guys.
I want to obtain names of icons on desktop in c++. And I know how to get their handle:
HWND hwnd = FindWindow("Progman","Program Manager");
HWND hwndSHELLDLL_DefView = ::FindWindowEx( hwnd, NULL, "SHELLDLL_DefView", NULL );
HWND hwndSysListView32 = ::FindWindowEx( hwndSHELLDLL_DefView, NULL, "SysListView32", "FolderView" );
What's next?
First, you need to get the location of the desktop folder using SHGetFolderLocation.. Next, you enumerate the contents of this folder using IShellFolder::EnumObjects
From here, the sky is the limit. Tons of information on interacting with the windows shell here.
Have fun!
Update:
A quick google search turns up this sample which seems to do exactly what you want.

GetOpenFileName() does not refresh when changing filter

I use GetOpenFilename() to let the user select a file. Here is the code:
wchar_t buffer[MAX_PATH] = { 0 };
OPENFILENAMEW open_filename = { sizeof (OPENFILENAMEW) };
open_filename.hwndOwner = handle_;
open_filename.lpstrFilter = L"Video Files\0*.avi;*.mpg;*.wmv;*.asf\0"
L"All Files\0*.*\0";
open_filename.lpstrFile = buffer;
open_filename.nMaxFile = MAX_PATH;
open_filename.lpstrTitle = L"Open media file...";
open_filename.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
::GetOpenFileNameW(&open_filename);
The file dialog shows up, but when I
change the Filter or
click on "My Computer"
the file list turns empty. Pressing [F5] does not help, but if I switch to the parent folder and return to the original folder (in the case of the Filter change) the filtering works fine and files show up in the list.
EDIT: My system is Windows XP (SP3) 32-bit - nothing special. It happens on other machines - with the same config - as well.
One thing you haven't done that might be causing problems is fully initialize the OPENFILENAMEW structure, especially the lStructSize element. I've seen this causing strange effects before. I'd suggest having something like
OPENFILENAMEW open_filename = { sizeof (OPENFILENAMEW) };
ZeroMemory(&open_filename, sizeof (OPENFILENAMEW));
open_filename.lStructSize = sizeof (OPENFILENAMEW);
Okay, I have figured out the problem, or at least, I have a solution that is working for me.
Earlier in the code, I had the following call to initialize COM...
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
Well, changing this to...
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
...solves the problem for me! Now the file dialog is filtering again.
I searched the web for this and it seems that a very few people faces the same problem, but no one published the aforementioned solution. Can anyone verify my findings?
Thank you, beef2k. It works.
But my problem has a little difference.
Everything worked fine until I added the SHBrowseForFolder call. I had got the same effect since that moment. But adding CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); solved the problem.

How to send a link to an application, like Spotify does

When we save a level in our editor, we create a log file of any errors it contains. These consist basically of an error message and a path that allows the user to find the erronous item in a tree view.
What I want is to make that path a link, something like
< a href="editor://path/to/gameobject" > Click to see object in editor< /a >
The SO questions I've seen regarding this seems to point to this msdn page:
http://msdn.microsoft.com/en-us/library/aa767914.aspx
But from what I can tell, it will spawn a new instance of the application. What I want to do is to simply "call" our editor somehow. One way to do it, I guess, is to spawn it, and in the beginning check if there's already an instance running, and if so, send the commandline to it.
Is that the best way to do it? If so, any ideas on how to do it best? What are otherwise some ways that this could be done?
Also: does the msdn solution work across browsers? Our editor runs in Windows only, but people use IE, Fx, GC and Opera.
If you need the link to work in any viewer, yes, registering a protocol handler is the best way.
As for launching the editor, you could implement it as an out-of-process COM server, but if you've already got command line parsing sorted, you might as well use a window message or named pipe to pass that to the editor. If you're sending a window message, you could use FindWindow (with a unique class name) to check for a running instance.
Sounds like you solved it already, by checking for a previous instance.
I would be surprised if the OS takes upon it to somehow "stamp" the association with data telling it to separate programs that should run multiple times from programs that should not.
Here's how I solved it. Basically, there are two parts. Or three.
First, the app needs to register itself in the registry, like this. It took some googling to find out how to use the windows register functions, but they were pretty straightforward. By adding this to the registry, your application will launch when a link with your custom url protocol is clicked.
Secondly, the app needs to detect that it's been started from a browser. Obviously quite trivial, just check the command line for "/uri" or however you chose to customize it.
Third, you don't actually want to start your application - it should already be running! Instead, when you've detected that you got started from a hyperlink, you need to detect if another instance of the application is already running. After that, you need to pass the command line to it. Here's how I did it:
bool ShouldContinueStartEditor( const std::string& command_line )
{
// Check if this instance was spawned from a web browser
if ( command_line.find( "/uri" ) != std::string::npos )
{
// Try to find other instance of JustEdit
HWND wnd = FindWindow( "AV_MainFrame", NULL );
if ( wnd )
{
COPYDATASTRUCT cds;
NEditorCopyData::SCommandLine data_to_copy;
strncpy( data_to_copy.m_CommandLine, command_line.c_str(), sizeof(data_to_copy.m_CommandLine) - 2 );
cds.dwData = NEditorCopyData::ECommandLine; // function identifier
cds.cbData = sizeof( data_to_copy ); // size of data
cds.lpData = &data_to_copy; // data structure
SendMessage( wnd, WM_COPYDATA, NULL, (LPARAM) (LPVOID) &cds );
}
return false;
}
return true;
}
"AV_Mainframe" is the name of the hwnd. If you happen to be using WTL, you can declare it like this.
DECLARE_FRAME_WND_CLASS("AV_MainFrame", IDR_MAINFRAME)
Now, in your window class, you need to handle the WM_COPYDATA message like this:
MESSAGE_HANDLER(WM_COPYDATA, OnCopyData);
LRESULT OnCopyData(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT CMainFrame::OnCopyData(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
PCOPYDATASTRUCT cds = (PCOPYDATASTRUCT) lParam;
if ( cds->dwData == NEditorCopyData::ECommandLine )
{
NEditorCopyData::SCommandLine* command_line = static_cast( cds->lpData );
const char* internal_path = strstr( command_line->m_CommandLine, "/uri" );
if ( internal_path != NULL )
{
// Do your thang
}
}
return 0;
}
And that's pretty much all there's to it. Oh, this is what the copy data namespace looks like:
namespace NEditorCopyData
{
enum ECopyDataMessages
{
ECommandLine = 0
};
struct SCommandLine
{
char m_CommandLine[512];
};
}

Reading from a text field in another application's window

Is there a way for a Windows application to access another applications data, more specifically a text input field in the GUI, and grab the text there for processing in our own application?
If it is possible, is there a way to "shield" your application to prevent it?
EDIT: The three first answers seem to be about getting the another applications window title, not a specific text input field in that window.
I'm no Windows API expert, so could you be more exact how do I find a certain text field in that window, what are the prerequisites for it (seems like knowing a window handle something is required, does it require knowing the text field handle as well? How do I get that? etc...)
Code snippets in C++ really would be really appreciated. MSDN help is hard to browse since Win32-API has such horrible naming conventions.
Completed! See my answer below for a how-to in C++.
For reading text content from another application's text box you will need to get that text box control's window handle somehow. Depending on how your application UI is designed (if it has a UI that is) there are a couple of different ways that you can use to get this handle. You might use "FindWindow"/"FindWindowEx" to locate your control or use "WindowFromPoint" if that makes sense. Either way, once you have the handle to the text control you can send a "WM_GETTEXT" message to it to retrieve its contents (assuming it is a standard text box control). Here's a concocted sample (sans error checks):
HWND hwnd = (HWND)0x00310E3A;
char szBuf[2048];
LONG lResult;
lResult = SendMessage( hwnd, WM_GETTEXT, sizeof( szBuf ) / sizeof( szBuf[0] ), (LPARAM)szBuf );
printf( "Copied %d characters. Contents: %s\n", lResult, szBuf );
I used "Spy++" to get the handle to a text box window that happened to be lying around.
As for protecting your own text boxes from being inspected like this, you could always sub-class your text box (see "SetWindowLong" with "GWL_WNDPROC" for the "nIndex" parameter) and do some special processing of the "WM_GETTEXT" message to ensure that only requests from the same process are serviced.
OK, I have somewhat figured this out.
The starting point is now knowing the window handle exactly, we only know partial window title, so first thing to do is find that main window:
...
EnumWindows((WNDENUMPROC)on_enumwindow_cb, 0);
...
which enumerates through all the windows on desktop. It makes a callback with each of these window handles:
BOOL CALLBACK on_enumwindow_cb(HWND hwndWindow, LPARAM lParam) {
TCHAR wsTitle[2048];
LRESULT result;
result = SendMessage(hwndWindow, WM_GETTEXT, (WPARAM) 2048, (LPARAM) wsTitle);
...
and by using the wsTitle and little regex magic, we can find the window we want.
By using the before mentioned Spy++ I could figure out the text edit field class name and use it to find wanted field in the hwndWindow:
hwndEdit = FindWindowEx(hwndWindow, NULL, L"RichEdit20W", NULL);
and then we can read the text from that field:
result = SendMessage(hwndEdit, WM_GETTEXT, (WPARAM) 4096, (LPARAM) wsText);
I hope this helps anyone fighting with the same problem!
Look at AutoHotkey. If you need an API for your application, look at their sources.
To prevent it, use a custom widget instead of WinForms, MFC or Win32 API. That is not foolproof, but helps.
Yes it is possible in many ways (one way is to use WINAPI GetWindow and GetWindowText).
First, get a handle to the textbox you want to retrieve text from (using FindWindow, EnumChildWindows and other APIs), then:
Old VB6-codeexample, declaration of API:
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long
Code to extract text:
Dim MyStr As String
MyStr = String(GetWindowTextLength(TextBoxHandle) + 1, Chr$(0))
GetWindowText TextBoxHandle, MyStr, Len(MyStr)
MsgBox MyStr
About how to shield the application to prevent it, you could do many things.
One way would be to have a own control to handle text input that build up the text from lets say a couple of labels placed where the text would be, or that draws the text graphically.
You can also get text from a richedit control with EM_GETTEXTRANGE message, but it works only in the same process in which the control was created.