Get/Set TShellListView Path/Folder as String (Not Using .Root) - delphi

I want to set the path for a TShellListView to display a directory of files using Delphi 2007. I can initially use TShellListView.Root to set the root path like this and it shows the directory I want:
View := TShellListView.Create(Self);
// ...
View.Root := 'C:\Windows';
But if the user navigates away from that directory using backspace and I try to set the .Root back to the original directory, the directory displayed does not change. It looks like .Root is meant to define the root of the shell namespace, not the current directory.
Also, if the user navigates around (using backspace, etc.) the .Root property does not update to reflect the currently displayed path. There is no .Path property like there is for TShellTreeView.
What I want is a way to get and set the current path as a string without being required to link the TShellListView to a TShellTreeView and set TShellTreeView.Path or hack ShellCtrls.pas since the relevant methods of TShellListView all look private. I find it hard to believe there isn't a straightforward way to get/set the path, so I assume I'm missing something simple here, but this component is not documented at all.

You can get the currently loaded path using
ShellListView1.RootFolder.PathName
Setting the Root property works, but it isn't updated when you change folders interactively. So you need to force it to think there's a change. This works if you're always resetting it to the same original path:
ShellListView1.Root := View.RootFolder.PathName; // Updates to current location
ShellListView1.Root := 'C:\Windows';
Alternatively, for arbitrary paths you could just add/remove the trailing \ in order to fool the SameText check in SetRoot:
if ShellListView1.Root[Length(ShellListView1.Root)] = '\' then
ShellListView1.Root := ExcludeTrailingPathDelimiter(ANewPath)
else
ShellListView1.Root := IncludeTrailingPathDelimiter(ANewPath);

To get the current folder as a string, you can access the RootFolder-property.
procedure TForm2.Button1Click(Sender: TObject);
begin
showmessage(ShellListView1.RootFolder.PathName);
end;
To set the current folder as a string, you use the root-property.
procedure TForm2.Button2Click(Sender: TObject);
begin
ShellListView1.Root := 'C:\windows';
end;

Related

Delphi Tinifile.ReadString and PromptDataSource

The first, click button1,can get the [section].[Key].Value
but if change the path via the second page's [...] of PromptDataSource()
Then the next time, click button1, can not get the [section].[Key].Value
Why???
Awe.dat
[Options]
DBConnection=Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\2000.mdb;
the Button1Click's code
procedure TForm1.Button1Click(Sender: TObject);
var
Filename:string;
DatFile:TiniFile;
str:WideString;
ConnectStr:WideString;
begin
DatFile:=Tinifile.Create('.\Awe.dat');
str:=DatFile.ReadString('Options','DBConnection','');
ShowMessage(str+'-----------------');
ConnectStr:=PromptDataSource(handle,str);
ShowMessage(str+'-----------------'+ConnectStr);
DatFile.Free;
end;
It seems that the problem is related to your use of a relative path. Your path is relative to the process working directory.
If the working directory is in your complete control then your code would be fine. But often the working directory is not in your complete control. The process may start with an unexpected working directory. File dialogs may change the working directory.
You are better being explicit and providing a full path to the file. It looks like your file is in the same directory as the executable. So you could use:
ExtractFilePath(Application.ExeName) + FileName
or similar.

How to get a dynamic path during runtime?

I have problems with getting the specific path of an image file that is uploaded to my application during runtime and I would love some help.
Basically I am trying to save a picture to a database, and I can partly achieve that. I am using the following:
procedure TForm12.Button7Click(Sender: TObject);
begin
ADOQuery4.SQL.Text := 'SELECT Profile_Picture, username FROM profile WHERE username='+QuotedStr(edit12.Text);
ADOQuery4.Open();
ADOQuery4.Edit();
TBlobField(ADOQuery4.FieldByName('Profile_Picture')).LoadFromFile('aaa.jpg');
ADOQuery4.Post();
end;
The code selects the correct place I want it to be places in the database, and correctly updates it with the file I have selected (the aaa.jpg file).
But this works only if I program it as such and it cannot be changed during runtime, in other words the user of the application cannot change the file throught the interface unless he opens the whole thing and change the code.
What I am asking is, is it possible to get the path of a file through a command during runtime?
For example, if I wanna save whatever picture is uploaded with an OpenPictureDialog component, how is it possible to do that? To select whatever specific file is selected by the OpenPictureDialog during runtime.
TOpenPictureDialog tells you the full path of the selected file, eg:
procedure TForm12.Button7Click(Sender: TObject);
begin
if OpenPictureDialog1.Execute then
begin
...
TBlobField(ADOQuery4.FieldByName('Profile_Picture')).LoadFromFile(OpenPictureDialog1.FileName);
...
end;
end;

How to get the file path of a dropped folder in delphi firemonkey2

Hi as the title says i want to know what code i should use and how i would go about setting up my program so that when i drag a file over a panel or the main form,
It gives me the file path of that folder.
Please Help.
It's as simple as this:
Put a TDropTarget component on the main form.
Set the Filter property. Put * instead of . if you want it to accept folders as well as files.
In the OnDropped event, the list of fully qualified file and or folder names is in Data.Files. You should be able to get everything you need from there.
Is this what you are trying to accomplish, or do you have something else in mind?
Update:
By request, added an example that shows the name of the first file that was dropped.
procedure TForm1.DropTarget1Dropped(Sender: TObject; const Data: TDragObject;
const Point: TPointF);
begin
ShowMessage(Data.Files[0]);
end;

How do I get a list of control-panel items and then execute one?

1. How can I get a list of control panels, including their names and icons, so I can create a menu like the one the Start menu shows?
2. When I click an entry, how do I execute the corresponding control panel?
By the way, what controls are used to do this kind of PopupMenu? But it has right click event.
update :
I use PItemIDList to get a Folder:
var:
PIDL, TempPIDL: PItemIDList;
Path: array[0..MAX_PATH] of Char;
FI: SHFILEINFOW;
begin
SHGetSpecialFolderLocation(0, CSIDL_FAVORITES, PIDL);
SHGetPathFromIDList(PIDL , Path);
Memo1.Lines.Add(Path);
SHGetFileInfo(LPCTSTR(PIDL), 0, FI, SizeOf(FI), SHGFI_PIDL or SHGFI_DISPLAYNAME or SHGFI_ICON);
Memo1.Lines.Add(FI.szDisplayName);
Image1.Picture.Icon.Handle := FI.hIcon;
it display normal , but when I change CSIDL_FAVORITE to CSIDL_CONTROLS , I always get error .
this is a wrong way to get controls panel items ?
I also use another method by use CPL
copy from here
But it can not display complete Items.
You can check the registry for all registered applets.
This describes how to register them: http://msdn.microsoft.com/en-us/library/windows/desktop/bb757044.aspx
Similarly you may scan registry to check already registered applets and their run methods.
However on 64-bit windows there would be 64-bit applets that your 32-bit application would not be able to load, so extracting icon might be a pain. I don't know if you can call LoadLibraryEx with AsResourceLibrary flag over 64-bit DLLs for mere icon extraction though.
Another approach would be using Windows explorer namespaces. Get some Shell component suite that provides opening virtual paths like My Computer and My Documents rather than c:\ and such. Control Panel has a special GUID (that i do not remember right of. But Microsoft TweakUI tool can create Control Panel in any folder using that GUID). You can probably use some Shell UI to open Control Panel special virtual folder into kind of ListView , then get then enumerate items and extract correspondent pictures and re-arrange them as menu. Then executing would be probably done as double-click over item in that shell listview.
Control panel applets are CPL files that are located in your system folder
EG : C:\Windows\system32
My suggestion is to list those files and then extract icons and get their file name
If you have trouble with the code post it here so that we can help
CPL files are just DLL files they can contain multiple applets
After a google search I found this tutorial :
http://delphi.about.com/od/kbwinshell/l/aa062403a.htm
In your help, I solved the problem! Special thanks to David Heffernan
1.Get control panel items I use Windows Shell to get control panel items , use CPL files not get complete items .
Code :
var
psfDeskTop: IShellFolder;
psfControl: IShellFolder;
pidControl: PITEMIDLIST;
pidChild: PITEMIDLIST;
pidAbsolute: PItemIdList;
pEnumList: IEnumIDList;
celtFetched: ULONG;
FileInfo: SHFILEINFOW;
begin
OleCheck(SHGetDesktopFolder(psfDeskTop));
OleCheck(SHGetSpecialFolderLocation(0, CSIDL_CONTROLS, pidControl));
OleCheck(psfDeskTop.BindToObject(pidControl, nil, IID_IShellFolder, psfControl));
OleCheck(psfControl.EnumObjects(0, SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN or SHCONTF_FOLDERS, pEnumList));
while pEnumList.Next(1, pidChild, celtFetched) = 0 do
begin
pidAbsolute := ILCombine(pidControl, pidChild);
SHGetFileInfo(LPCTSTR(pidAbsolute), 0, FileInfo, SizeOf(FileInfo), SHGFI_PIDL
or SHGFI_DISPLAYNAME);
// SHGetFileInfo can get name and icon
//Do something to save item name and icon
end;
2. Execute must have to use ShellExecuteEx to execute a PIDL item.
var
ShExeInfo : SHELLEXECUTEINFO;
begin
ZeroMemory(#ShExeInfo, SizeOf(ShExeInfo));
ShExeInfo.cbSize := SizeOf(ShExeInfo);
ShExeInfo.lpVerb := 'Open';
// control panel item's PIDL
ShExeInfo.lpIDList := pidAbsolute;
ShExeInfo.nShow := SW_SHOWNORMAL;
ShExeInfo.fMask := SEE_MASK_IDLIST;
end
and use
ShellExecuteEx(#ShExeInfo);
Finally thanks to David Heffernan again. help me a lot.
Following the suggestion of Arioch 'The
Reference: http://www.geoffchappell.com/studies/windows/shell/shell32/classes/controlpanel.htm
The other "two or three methods" I was thinking of are detailed there:
the [MMCPL] section of the CONTROL.INI file, nowadays mapped to the
registry key HKEY_CURRENT_USER\Control Panel\MMCPL;
the registry key
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Control Panel\CPLs;
Also, detailed there as well are the don't load lists:
A candidate CPL module is rejected if its filename appears as a value
in either of the following registry keys:
HKEY_CURRENT_USER\Control Panel\don't load
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Control Panel\don't load
Like was said, the challenge of this project is sweeping all the proper locations and getting the data in the right way to be able to act on it. This is because of how a few of the "newer" design control panel items (and shell folders that appear there, I'm not really sure I discovered how to access those yet) are presented. I don't have the data handy, but I can copy an example or two in if it would further the discussion.

Problem adding lots of strings to a TStringList

I have a problem adding strings to a TStringList. I've searched other posts but couldn't find an answer to this.
What I'm trying to do is to add a big amount of strings to a TStringList (more than 14000) but somewhere in the process I get an EAccessViolation. Here's the code I'm using:
procedure TForm1.FormCreate(Sender: TObject);
begin
List := TStringList.Create;
List.Duplicates := dupAccept;
end;
procedure TForm1.ButtonStartClick(Sender: TObject);
begin
List.Clear;
List.Add('125-AMPLE');
List.Add('TCUMSON');
List.Add('ATLV 4300');
List.Add('150T-15');
List.Add('TDL-08ZE');
List.Add('RT20L');
List.Add('SIN LINEA');
List.Add('TIARA');
List.Add('FL200ZK1');
List.Add('FL250ZK1');
List.Add('SIN LINEA');
List.Add('CENTAURO-70 S.P.');
List.Add('CORSADO');
{ This list continues to about 14000 strings...}
List.Add('VOSJOD 2');
List.Add('Z 125');
List.Add('ZUMY');
List.Add('NEW AGE 125');
List.Add('SIN LINEA');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil(List);
end;
¿What's wrong with this code? The list contains duplicate strings so I set the Duplicates property to dupAccept. I was able to load the list using LoadFromFile, but I don't want to have a text file outside my application.
I hope you can help me!!! Please tell me if you need any further information.
Thank you very much. I really appreciate your help.
The suggestions for using an external file are on the mark here. However, your post indicates your desire for not having an external file. I would then suggest you link the file to the executable as a resource. You can easily do this following these steps:
Place all the strings into a text file called stringdata.txt (or whatever name you choose). Then create a .rc file of whatever name you choose and put the following into it (STRING_DATA can be any identifier you choose):
STRING_DATA RCDATA "stringdata.txt"
Create a .res file from the .rc:
BRCC32 <name of rc>.rc
Now reference this file from the source code. Place the following someplace in the unit:
{$R <name of res>.res}
Instead of loading from a file stream, load from a resource stream:
StringData := TResourceStream.Create(HInstance, 'STRING_DATA', RT_RCDATA);
try
List.LoadFromStream(StringData);
finally
StringData.Free;
end;
If you do command-line automated builds, I would suggest you keep the .rc file under source control and build the .res during the build process. This way you can also keep the stringdata.txt file under source control and any edits are automatically caught on the next build without having to explicitly build the .res file each time the .txt file changes.
What Delphi version are you using? Some older versions had a bug in the memory manager that can cause an access violation when trying to reallocate an array to a size that's too large.
Try adding FastMM4 to your project to replace the old memory manager and see if that helps.
Also, you're probably better off keeping the list in an external file. Yes, it's another file, but it also means that you can change the list without having to recompile the entire program. This also makes creating (and distributing!) updates easier.
Mason is probably right for the cause of the AV; this is quite a large array to grow.
On a side note, when doing such a long processing on a StringList, it's recommended to surround it by BeginUpdate/EndUpdate to avoid firing any update event.
Even if you don't have any now, they might be added later and you'll get problems.
Set list.capacity to the number of items you plan to add, immediately after you create the list. Alternatively, place the list in an RC file (named other than with the name of your project) and add it to your project. This gets compiled into your application, but does not involve executable code to create the list.
I would also worry about compiler integrity with a 14,000 line procedure. People have found other cases where going beyond anything reasonable breaks the compiler in various ways.
You may also want to try THashedStringList, could see a speed boost (although not in this function), although I'm not sure if the add method is a whole lot different.
try using the following instead of your code to add the strings to the StringList
var
Str: string;
begin
Str := '125-AMPLE' + #13#10;
Str := Str + 'TCUMSON' + #13#10;
Str := Str + 'ATLV 4300' + #13#10;
Str := Str + '150T-15' + #13#10;
................
List.Text := Str;
end;

Resources