Shoutbox

Browse For File - Printable Version

-Shoutbox (https://shoutbox.menthix.net)
+-- Forum: MsgHelp Archive (/forumdisplay.php?fid=58)
+--- Forum: Messenger Plus! for Live Messenger (/forumdisplay.php?fid=4)
+---- Forum: Scripting (/forumdisplay.php?fid=39)
+----- Thread: Browse For File (/showthread.php?tid=60949)

Browse For File by Volv on 06-24-2006 at 02:04 AM

Heya,
How would I go about making a 'Browse for file' dialog which allows the user to navigate to a file of their choice on their PC and returns the path to the file.

Thanks in advance
- Volv


RE: Browse For File by matty on 06-24-2006 at 02:19 AM

This is taken from Choli's Translator, give all credits too him.

js code:
    var OFN_ENABLESIZING = 0x800000;
    var OFN_EXPLORER = 0x80000;
    var OFN_FILEMUSTEXIST = 0x1000;
    var OFN_HIDEREADONLY = 0x4;
    var OFN_LONGNAMES = 0x200000;
    var OFN_PATHMUSTEXIST = 0x800;
   
function OnEvent_Initialize(MessengerStart)
{
    var OpenFileName = Interop.Allocate(88);
    var filter;
    var s_filter;
    var file;
    var s_file;
    var title;
    var s_title;
    var ret;
    var tdata;

     with (OpenFileName) {
        WriteDWORD(0, Size); // lStructSize
        WriteDWORD(4, 0); // hwndOwner
        WriteDWORD(8, 0); // hInstance
        filter = "Translation files (Lng_*.ini)|Lng_*.ini|All files (*.*)|*.*||";
        s_filter = Interop.Allocate(2 * (filter.length + 1));
        WriteMultiStringW(s_filter, filter);
        WriteDWORD(12, s_filter.DataPtr); // lpstrFilter
        WriteDWORD(16, 0); // lpstrCustomFilter
        WriteDWORD(20, 0); // nMaxCustomFilter
        WriteDWORD(24, 1); // nFilterIndex
        file = "Lng_*.ini" + Space(256);
        s_file = Interop.Allocate(2 * (file.length + 1));
        s_file.WriteString(0, file);
        WriteDWORD(28, s_file.DataPtr); // lpstrFile
        WriteDWORD(32, file.length); // nMaxFile
        WriteDWORD(36, 0); // lpstrFileTitle
        WriteDWORD(40, 0); // nMaxFileTitle
        WriteDWORD(44, 0); // lpstrInitialDir
        title = "Select original file";
        s_title = Interop.Allocate(2 * (title.length + 1));
        s_title.WriteString(0, title);
        WriteDWORD(48, s_title.DataPtr); // lpstrTitle
        WriteDWORD(52, OFN_ENABLESIZING | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_LONGNAMES | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST); // flags
        WriteWORD(56, 0); // nFileOffset
        WriteWORD(58, 0); // nFileExtension
        WriteDWORD(60, 0); // lpstrDefExt
        WriteDWORD(64, 0); // lCustData
        WriteDWORD(68, 0); // lpfnHook
        WriteDWORD(72, 0); // lpTemplateName
        WriteDWORD(76, 0); // pvReserved
        WriteDWORD(80, 0); // dwReserved
        WriteDWORD(84, 0); // FlagsEx
    } with OpenFileName
    ret = Interop.Call("comdlg32.dll", "GetOpenFileNameW", OpenFileName);
    if (ret == 0) {
        return;
    }
}
function WriteMultiStringW (datablock, string) {
    var pos = 0;
    datablock.WriteString(0, string);
    pos = string.indexOf("|", pos);
    while (pos != -1) {
        datablock.WriteWORD(2 * pos, 0);
        pos = string.indexOf("|", pos + 1);
    }
}
function Space (number) {
    var i;
    var s = "";
    for (i = 0; i < number; i++) {
        s += " ";
    }
    return s;
}

RE: Browse For File by Volv on 06-24-2006 at 05:16 AM

Thanks a lot Matty & Choli, works great :D


RE: Browse For File by Choli on 06-24-2006 at 09:32 AM

yeah, that's it :P

you can change the filter and put, for example:

"Text files|*.txt|All files|*.*||"

For more help, read this: GetOpenFileName and OPENFILENAME.


RE: Browse For File by CookieRevised on 04-11-2011 at 07:46 AM

Since this recently came up again, I'll post my own variation/fix of the above code here.
Note the word 'variation', as this kind of code is very very generic and can be found all over the net already in various forms. It is simply a matter of filling a structure with the needed stuff and calling the proper Windows API, thus not realy something special.

Some small notes first:

  • Choli's variation has some things in it which are specific for his Translator tool and should not be copied as-is. Nevertheless I have seen his code cropping up in various places and scripts without those needed changes. Aka, those copies will not always function properly.

  • Matti has made a better variation in the post: Matti's reply to CommonDialog help
    However, that too still contains bugs and one or two things which could be done better.

So here is my variation, based upon Matti's variation of Choli's snippet. It is of course different, but not that different to be completely 'new'. As such, I've also tried to keep most of the structure and variable names used in the previous variations. However, due to the change of order of the parameters and the change in format of the returned value of the function, you can't simply replace your old BrowseForFile function with this one though. You will need to change the order of the parameters in your code and change how you handle the return value in your code.


The code itself:
js code:
/* FUNCION: BrowseForFile (CookieRevised variation)

    Original code by Choli from the Messenger Plus! Translator script
    Modified by Matti to be more versatile (eg: including "Save as" dialog function)
    Modified by CookieRevised to optimize it a bit and fix some bugs

    Description:
        Opens a "Browse for file" dialog with the given settings

    Parameters (all parameters are now optional):
        save        (boolean) / true is "Save as" dialog, false is "Open" dialog
        title       (string)  / Dialog title
        dir         (string)  / Start directory
        file        (string)  / File to display as default
        filter      (string)  / Filter message. Example: "JavaScript Files (*.JS)|*.JS|Text Files (*.TXT;*.LOG)|*.TXT;*.LOG|"
        ext         (string)  / Default extension to append to the filename when no extension is specified
        multiselect (boolean) / true to allow multiple file selection in an "Open" dialog
        hwnd_owner  (number)  / Handle of the owner window, used to copy window icon etc.



    Return value:
        >>>A (string) array of full filepath names.<<<
        Note that this will also be an array if just 1 file was selected (in that case an array with just 1 element).
        If no files were selected or the dialog was canceled, the array will be empty.
        I think this is more consistant and versatile across multiple implementations of the function.
*/
>>>function BrowseForFile(save, title, dir, file, filter, ext, multiselect, hwnd_owner) {<<<
    // see http://msdn.microsoft.com/en-us/library/ms646839(v=vs.85).aspx
    var OFN_OVERWRITEPROMPT =    0x2;
    var OFN_HIDEREADONLY =        0x4;
    >>>var OFN_NOCHANGEDIR =        0x8;<<<
    var OFN_ALLOWMULTISELECT =    0x200;
    var OFN_PATHMUSTEXIST =        0x800;
    var OFN_FILEMUSTEXIST =        0x1000;
    >>>var OFN_NONETWORKBUTTON =    0x20000;<<<
    var OFN_EXPLORER =            0x80000;
    var OFN_LONGNAMES =            0x200000;
    var OFN_ENABLESIZING =        0x800000;
    >>>var OFN_DONTADDTORECENT =    0x2000000;<<<

    var OpenFileName = Interop.Allocate(88);
    with (OpenFileName) {

        save = save == true;
        >>>multiselect = (multiselect == true) && !save;<<<
        >>>hwnd_owner = hwnd_owner ? 1 * hwnd_owner : 0;<<<
       
        >>>if (typeof filter !== "string") filter = "All Files (*.*)|*.*|";<<<
        >>>var s_filter = Interop.Allocate(2 * (filter.length + 1) + 4);<<<
        >>>s_filter.WriteBSTR(0, filter.replace(/\|/g, '\0'));<<<

        if (typeof file !== "string") file = "";
        >>>var s_file = Interop.Allocate(2 * (Math.max(file.length, 16383) + 1));<<<
        s_file.WriteString(0, file);

        if (typeof dir === "string") {
            var s_dir = Interop.Allocate(2 * (dir.length + 1));
            s_dir.WriteString(0, dir);
            WriteDWORD(44, s_dir.DataPtr);    // lpstrInitialDir
        }
        if (typeof title === "string") {
            var s_title = Interop.Allocate(2 * (title.length + 1));
            s_title.WriteString(0, title);
            WriteDWORD(48, s_title.DataPtr);    // lpstrTitle
        }
        if (typeof ext === "string") {
            var s_ext = Interop.Allocate(2 * (ext.length + 1));
            s_ext.WriteString(0, ext);
            WriteDWORD(60, s_ext.DataPtr);    // lpstrDefExt
        }
        WriteDWORD(0, Size);                // lStructSize
        WriteDWORD(4, hwnd_owner);            // hwndOwner
        >>>WriteDWORD(12, s_filter.DataPtr + 4);    // lpstrFilter<<<
        WriteDWORD(24, 1);                    // nFilterIndex
        WriteDWORD(28, s_file.DataPtr);        // lpstrFile
        >>>WriteDWORD(32, s_file.Size / 2);        // nMaxFile<<<
        >>>WriteDWORD(52, OFN_DONTADDTORECENT | OFN_ENABLESIZING | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_LONGNAMES | OFN_PATHMUSTEXIST | (save ? OFN_OVERWRITEPROMPT : OFN_FILEMUSTEXIST) | (multiselect ? OFN_ALLOWMULTISELECT : 0)); // Flags<<<
    }

    var result = Interop.Call("COMDLG32.DLL", (save ? "GetSaveFileNameW" : "GetOpenFileNameW"), OpenFileName);
    if (result) {
        var p_path = s_file.ReadString(0);
        var p_files = new Array();
        if (multiselect) {
            var p_file = "";
            var pos = 2 * (p_path.length + 1);
            while (true) {
                p_file = s_file.ReadString(pos);
                if (p_file.length > 0) {
                    >>>p_files.push(p_path + (p_path.slice(-1) !== "\\" ? "\\" : "") + p_file);<<<
                    pos += 2 * (p_file.length + 1);
                } else break;
            }
        }
        >>>return p_files.length ? p_files : [p_path];<<<
    } else {
        >>>return [];<<<
    }
}
Major changes highlighted


Examples:
js code:
// Save File Dialog
// Opens up in C:\, file filters are 'Text Files' (*.txt and *.log) and 'All Files' (*.*).
var aFile = BrowseForFile(true, "Save file as", "C:\\", "test.txt", "Text Files (*.TXT;*.LOG)|*.TXT;*.LOG|All Files (*.*)|*.*|");
if (aFile.length) {
    // User specified a valid save location. Do something useful here.
    Debug.Trace(aFile[0])
} else {
    // User didn't specify a file to save to or pressed Cancel.
}
js code:
// Open File Dialog
// Opens up in C:\Windows, multiple selections are allowed, file filters are 'PNG Files' (*.png) and 'All Files' (*.*).
var aFile = BrowseForFile(false, "Select PNG", "C:\\Windows", null, "PNG Files (*.PNG)|*.PNG|All Files (*.*)|*.*|", null, true);
if (aFile.length) {
    // User selected some files. Do something useful here.
    for (var i in aFile) {
        Debug.Trace(aFile[i])
    }
} else {
    // User didn't selected any files to open or pressed Cancel.
}


Stuff changed compared to Matti's and Choli's (also see highlights in the code):
  • Made order of parameters a bit more logical, also in regards to optional paramater usage.
  • Changed the format of the returned value of the function. Now it always return an array with the full filepath names of the selected file(s). I think this is much more versatile and consistant in regards to multiple file selections, looping, etc. It is also very easy now to check if a file is returned or not, no matter the amount of files (just check the length of the array being 0 or not).

    thus beware, you can't simply replace your old BrowseForFile function with this one. See examples above.

  • Fixed the behaviour of the parameters so that they are now all optional. They weren't before in the sense that the code would fail if the file or dir parameter were not specified (was a big bug in Matti's variation). This was solved by removing the Datablock.Size = 0 lines at the end of the function. They are not needed as datablocks will be removed automatically by the garbage collector of Plus! when the function ends anyways.
  • Made hwnd_owner parameter also behave properly when not specified (was a potential bug). All parameters are now truely optional; you don't need to specify them.
  • Added the optional multiselect parameter (boolean).
  • Added some extra constants.
  • Replaced Choli's WriteMultiStringW function with Datablock::WriteBSTR() function and thus highly optimized the parsing of the filter parameter.
  • Removed Choli's Space() function.
  • Removed all unneeded Datablock::WriteDWORD(x, 0); lines. They are not needed because a Datablock will always be zero-filled anyways when you allocate it.
  • Added proper initial filepath name length check before writing to the filepath buffer. (was a potential bug)
  • Increased the filepath buffer drastically. 256 like in Choli's code is way too small. Even 1024 characters is small-ish, especially considering multiple file selections. There is no reason not to have a massive buffer for this.
  • Fixed nMaxFile calculation (size should include the null-character).
  • Added OFN_DONTADDTORECENT flag. This prevents files from being added to Windows' Recent Documents list, which is otherwise highly annoying in many cases. If you explicitly have a reason to add the opened/saved files in that list, simply remove that flag again in the code (on the line starting with WriteDWORD(52, ...).
  • Maybe some other tid bits not worth to mention.


RE: Browse For File by Choli on 04-11-2011 at 06:41 PM

:mipdodgy: Cookie... this thread is 5 years old...

quote:
Originally posted by CookieRevised
256 like in Choli's code is way too small.
Sure, but if I remember well, I think that old versions of Windows (maybe 98, or 2000) don't allow more than that, do they?. Or something like that :P
RE: Browse For File by CookieRevised on 04-11-2011 at 08:13 PM

quote:
Originally posted by Choli
:mipdodgy: Cookie... this thread is 5 years old...
Yup, the '90s are coming back :D
Nah, but joking aside, as I said, the request to have a 'BrowseForFile' dialog pops up now and then and will continue to pop up in the future no doubt. And apparently people keep on referring to this thread and/or use code from this thread (which is main reason why I posted this here and not in that other thread). I found many links pointing to Matty's post here, even in very recent posts. So, nothing wrong with (finaly) updating it a bit in that case imho.

quote:
Originally posted by Choli
quote:
Originally posted by CookieRevised
256 like in Choli's code is way too small.
Sure, but if I remember well, I think that old versions of Windows (maybe 98, or 2000) don't allow more than that, do they?. Or something like that :P
Not exactly; +-256 (or rather 260, which is MAX_PATH) is the absolute required minimum (eg: when you are sure you don't return multiple selections and with short file names). It's the minimum required size to hold exactly one complete path and file name (in short name notation.... because in long name notation the name itself can even be MAX_PATH characters). So, even back in the old days, the buffer could be (and better should be) much much bigger to hold both the returned path and filename.

For your Translator tool this was never a real problem. But since people copy that code without changing anything in it and using it as-is, problems do arise though.
RE: RE: Browse For File by Amec on 04-14-2011 at 06:54 PM

quote:
Originally posted by CookieRevised

js code:
with (OpenFileName) {

Every time you use a with statement, a teddy bear cries.

http://www.yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/
RE: Browse For File by matty on 04-14-2011 at 07:06 PM

quote:
Originally posted by Amec
quote:
Originally posted by CookieRevised

js code:
with (OpenFileName) {

Every time you use a with statement, a teddy bear cries.

http://www.yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/
[Image: attachment.php?pid=1013025&update2]
RE: Browse For File by CookieRevised on 04-14-2011 at 07:40 PM

quote:
Originally posted by Amec
quote:
Originally posted by CookieRevised

js code:
with (OpenFileName) {

Every time you use a with statement, a teddy bear cries.

http://www.yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/
Yeah, well, you're 5 years to late....

Although I agree for 50%, it is mostly also a matter of schematics. There is essentially nothing wrong with using with() if you know what the code will do. In the code snippet it is also used for the exact purpose that with() statement was invented and there are enough readDWORD() statements to justify the use of with() imo.

May I also quote that very same page you linked to:
quote:
If you can’t read a program and be confident that you know what it is going to do, you can’t have confidence that it is going to work correctly. For this reason, the with statement should be avoided.
I can read the program and I am 100% confident in knowing what it is going to do and that it is going to work correctly.

Lastly, it is not my code:
quote:
Originally posted by CookieRevised
So here is my variation, based upon Matti's variation of Choli's snippet. It is of course different, but not that different to be completely 'new'. As such, I've also tried to keep most of the structure and variable names used in the previous variations.
Note that there are also other parts in the code which could be made shorter/smarter/cleaner/etc. But that was not the purpose of showing or updating that code.
RE: RE: Browse For File by Amec on 04-14-2011 at 07:53 PM

quote:
Originally posted by CookieRevised
I can read the program and I am 100% confident in knowing what it is going to do and that it is going to work correctly.
You and I might know that it's going to work correctly, but what happens when someone new to ECMAScript copy+pastes your code? They're not going to know all the caveats that go with using with(). What if they want to edit it?


Edit: I just realised that the script is with'ing on a DataBloc interface, which always has the same members, and hence there's no ambiguity. lol
RE: Browse For File by CookieRevised on 04-14-2011 at 07:58 PM

quote:
Originally posted by Amec
quote:
Originally posted by CookieRevised
I can read the program and I am 100% confident in knowing what it is going to do and that it is going to work correctly.
You and I might know that it's going to work correctly, but what happens when someone new to ECMAScript copy+pastes your code? They're not going to know all the caveats that go with using with(). What if they want to edit it?
Sure, but that's the exact reason why I posted that code, it doesn't need to be edited anymore (in most cases). The previous code had quite a few things people needed to edit in order to let it work properly in their scripts and it even contained potential bugs.

Either way, you can say the same thing for like 75% of code in any snippet. If you're new to scripting you shouldn't start editing without knowing what you're doing. That goes for with() usage but that goes for most other statements and methods too from the simpliest to the hardest to understand.

EDIT:
quote:
Originally posted by Amec
Edit: I just realised that the script is with'ing on a DataBloc interface, which always has the same members, and hence there's no ambiguity. lol
still... you do have a small point when it wasn't though. But so do I, I like to believe (one shouldn't use statements nor edit stuff you don't know... and of course... testing is key).