How to prompt for a subdirectory from inside AFL?

Hi

From inside AFL I am trying to open the windows explorer window, select a directory and then return to AFL and store it to a variable.

Openning the EXPLORER.EXE is the easy part:
ShellExecute( "explorer.exe","C:\\", "" );

But how do I return the selected directory to AFL and store it in a variable?

Hi @bobptz

I just let you know one way that some one can use to achive this in two easy steps

  1. First select the directory or file, and then mouse Right-Click and Copy as Path.
  2. Second use ClipboardGet() in your afl

please read
Show “Copy as Path” Always in Right-Click Menu Without Shift Key

ClipboardGet() https://www.amibroker.com/guide/afl/clipboardget.html

Me too....I would like in this thread to see other options, if any.

Interesting solution.

Not user friendly though.

@bobptz, you can try some OLE automation using the Shell.Application object.

Here is an example that activate the browse for folders dialog from a trigger:

// Example to invoke the a Windows Shell dialog to select a folder
selected_folder = StaticVarGetText("selectedfolder");
// set a default folder (as needed)
if (selected_folder == "") {
	StaticVarSetText("selectedfolder", "C:\\");
	selected_folder = StaticVarGetText("selectedfolder");
}
if ( ParamTrigger( "Select a folder", "BROWSE FOR FOLDERS" ) )
{  
	// Modified an example code from: 
	// https://docs.microsoft.com/en-us/windows/win32/shell/shell-browseforfolder
	objShell = CreateObject("Shell.Application");
	objFolder = objShell.BrowseForFolder(0, "Select a folder", 0); 
	
	// There is an optional 4th parameter (a variant) that in scritps accepts some folder constants. See: 
	// https://docs.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants
    // but in COM automation seems to work only with strings.
	// If you pass it a string, it will open where you want. The problem is that this becomes the TOP LEVEL so 
	// the user cannot browse higher in the tree than this folder. 
	// If this value is not specified, the root folder used in the dialog box is the desktop.
	// Uncomment next line (and comment out the previous BrowseFroFolders call) to 
    // try opening a specific folder (change the string 4th parameter as needed):
   	// objFolder = objShell.BrowseForFolder(0, "Select a folder", 0, "C:\\"); 

    if (objFolder)
	{
		objFolder_self = objFolder.Self;
		selected_folder = objFolder_self.Path;
		// For debugging purposes show an info dialog
		PopupWindow("Your selected folder:\n" + selected_folder , "Selected folder", 3);
		StaticVarSetText("selectedfolder", selected_folder);
	}
}	
selected_folder = StaticVarGetText("selectedfolder");
// Do something with the folder info....
Title = selected_folder;
Plot(C, "Close", colorDefault);
6 Likes

@beppe

Thank you!! Thank you!! Thank you!! Thank you!!

Not only you gave me the exact solution that I was looking for. You showed me a whole new way of doing things. I knew OLE theoretically only. Now I see it in action, and how to use the windows documentation. Not easy but doable.

Thank you again.

1 Like

@beppe @bobptz It's possible to overcome the default path problem (default = root) using a powershell script invoking standard windows dialogs. More complex for minimal benefits, but the idea can be useful in other contexts.

Here is an example where a VBScript creates a PowerShell script on-the-fly, runs it, and returns the result/string. As the script is created everytime, it's unnecessary to install it somewhere. I'm not proficient in VBS, but found pieces here & there on the web. The main reference for FolderBrowserDialog is
https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.folderbrowserdialog?redirectedfrom=MSDN&view=netframework-4.8

Note that the root folder can't be arbitrary though, it must be a special folder.

EnableScript("vbscript");
<%

Function SelectFolder(root, dir)
	
	poshscript = "Function Get-Folder($rootFolder, $initialPath) { " & vbCrLf & _
		"   [System.Reflection.Assembly]::LoadWithPartialName(""System.Windows.Forms"") | Out-Null " & vbCrLf & _
		"   $FolderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog " & vbCrLf & _
		"   $FolderBrowserDialog.SelectedPath = $initialPath " & vbCrLf & _
		"   $FolderBrowserDialog.RootFolder = $rootFolder " & vbCrLf & _
		"   $FolderBrowserDialog.ShowNewFolderButton = 0 " & vbCrLf & _
		"	$FolderBrowserDialog.ShowDialog() | Out-Null " & vbCrLf & _
		"   $FolderBrowserDialog.SelectedPath " & vbCrLf & _
		"} #end function Get-Folder" & vbCrLf & _
		"# *** Entry Point to Script ***" & vbCrLf & _
		"Get-Folder -rootFolder """ & root & """ " & " -initialPath """ & dir & """ " & vbCrLf 
		
	Dim fso
	Set fso = CreateObject("Scripting.FileSystemObject")

	Dim tfolder, tname, tfile
	Const TemporaryFolder = 2
	Set tfolder = fso.GetSpecialFolder(TemporaryFolder)
	tname = fso.GetTempName & ".ps1"
	tpath = fso.BuildPath(fso.GetAbsolutePathName(tfolder), tname)
	Set tfile = fso.CreateTextFile(tpath, True)
	tfile.WriteLine poshscript
	tfile.WriteLine " "
	tfile.Close 	
	
	Set shell = CreateObject("Wscript.shell")
	cmd = "powershell -nologo -executionpolicy bypass -noninteractive -windowstyle hidden -file """ & tpath & """"
	
	Set executor = shell.Exec(cmd)
	executor.Stdin.Close
	
	SelectFolder = executor.StdOut.ReadAll 		rem ReadLine rem depends (-noexit)	
	
End Function 
%>
Dialog  = GetScriptObject();

last = "LastSelectedFolder"+GetChartID();

procedure TestSelectFileGUI() {
	local idtest1;
	
	idtest1 = 1;
	GuiButton("SELECT FOLDER",idTest1, 1, 30, 120, 30, notifyClicked);
	//
	for(n = 0; (id = GuiGetEvent(n, 0)) != 0; n++) {
		code = GuiGetEvent(n, 1);
		switch(id) {
			case idTest1: 
				if (code == notifyClicked) {
					filename = ""+Dialog.SelectFolder("", "C:\\Users\\");
					StaticVarSetText(last, filename);
					Title = "Last Selected Folder = " + filename;
				}
				break;
			default: break;
		}
	}
}

TestSelectFileGUI();

Title = "Last Selected Folder = " + StaticVarGetText(last);

1 Like

This works great! Is it possible to Select file Name as well as folder path?

It is strongly advised NOT to display blocking modal dialog boxes from within AFL.

AFL runs in worker threads. Worker threads should NOT TALK to GUI. Failure to obey to this principle may result in infinite loops because modal dialog boxes run embedded message pump that may result in re-entering the same code over and over again.

AFL's Gui* functions are exception from the rule because they are threading aware and use thread isolation and don't talk to GUI directly but via special thread safety non-blocking layer.

It is also advised NOT to use code that implicitly changes application working directory (like BrowseForFolder). Changing current working directory will result in catastrophic consequences because AmIBroker uses RELATIVE paths. Once you change CWD the files will be searched in invalid locations.

Ok I will not do that.

Is there a way to create a list of files in a folder that match a certain criteria?

So, Filenames are
p1234.txt
c3456.txt
c1234.txt
p8976.txt

create string "c3456.txt,c1234.txt"

Sure there is a fdir function AFL Function Reference - FDIR