Entwickler-Ecke

Windows API - SHSetFolderPath?


Christian213 - Do 13.09.12 08:38
Titel: SHSetFolderPath?
Hallo,

ich suche eine Möglichkeit, per Win-API den Pfad eine "Special Folders" setzen zu können.
Laut MSDN gibt es eine Funktion "SHSetFolderPath", leider habe ich keine Idee wie ich dies in Delphi nutzen kann?!
http://msdn.microsoft.com/en-us/library/windows/desktop/bb762247%28v=vs.85%29.aspx
Dieser Thread hier hilft mir leider nicht weiter, da der Code umvollständig ist: http://www.entwickler-ecke.de/viewtopic.php?t=62923&highlight=shsetfolderpath
Wer kann mir hier einen Tipp geben? Danke! :-)

Gruß,
Christian


Delete - Do 13.09.12 09:49

Guckst Du hier:

http://www.delphipraxis.net/1182079-post9.html
und hier:
http://www.delphipraxis.net/170313-vergleich-shgetspecialfolderlocation-shgetknownfolderpath.html#post1182608


Christian213 - Do 13.09.12 11:23

Hallo,

Danke für die Antwort.
Das hilft mir leider nicht viel weiter, denn ich will den Wert nicht auslesen sondern setzen.

Gruß,
Christian


vagtler - Do 13.09.12 15:37

Naja, da kann man ja schon sehen, wie das Lesen mittels Import einer WinAPI-Funktion realisiert wurde.

Das zu adaptieren müsste doch möglich sein, oder?


Delete - Do 13.09.12 16:45

@Christian213

Bevor man ein Directory erstellt, muss man doch erst mal fragen, ob es nicht schon existiert!!!
An Deiner Antwort sehe ich, dass Du nichts verstanden hast...und den Sourcecode nicht gelesen hast!


Christian213 - Sa 15.09.12 17:25

Hallo,

erstmal Danke für die Antworten!

user profile iconvagtler hat folgendes geschrieben Zum zitierten Posting springen:
Naja, da kann man ja schon sehen, wie das Lesen mittels Import einer WinAPI-Funktion realisiert wurde.

Das zu adaptieren müsste doch möglich sein, oder?

Die Routine zum Auslesen habe ich ja bereits, sie funktioniert ja auch.
Nur das Setzen will irgendwie nicht.

user profile iconhathor hat folgendes geschrieben Zum zitierten Posting springen:
@Christian213

Bevor man ein Directory erstellt, muss man doch erst mal fragen, ob es nicht schon existiert!!!
An Deiner Antwort sehe ich, dass Du nichts verstanden hast...und den Sourcecode nicht gelesen hast!

Ich wundere mich gerade, was Du alles aus meinen Worten deuten kannst ;-)
Aber ich habe das Gefühl, Du hast mein Problem nicht verstanden:
Ich will kein Directory erstellen, sondern eine Systemvariable auf einen anderen Pfad setzen.
Die API-Funktion heißt ja auch SHSetFolderPath und nicht SHCreateFolderPath.
Also das, was man in einer Commandshell mittels "set USERPROFILE=..." machen kann, nur halt per WinAPI aus einem Programm heraus.

Das Problem vor dem ich stehe, ist dass die Funktion SHSetFolderPath nicht direkt über den Namen zur Verfügung steht.
Ich hatte es mit einer Funktionsdefinition versucht:
function SHSetFolderPath(Clsid : dWord; hToken : THandle; dwFlags : dWord; pszPath : pChar) : boolean; stdcall; external 'shell32.dll' index 231;
Wenn ich die Funktion später mit Werten fütter, bekomme ich Access Violation... Hier stehe ich nun auf dem Schlauch.

Gruß,
Christian


Gruß,
Christian


Delete - Sa 15.09.12 22:18

Endlich habe ich verstanden, was Du willst!

Im Beispiel weist Button1 der Konstanten CSIDL_FAVORITES den Pfad C:\PROG-1 zu.
Da diese Zuweisung aber auch nach Programmende erhalten bleibt, muss man erst den ursprünglichen Pfad speichern, um den am Ende wieder zuweisen zu können - falls erwünscht...
Existiert der Pfad nicht, wird kein Fehler angezeigt - die vorhergehende Zuweisung wird gelöscht!
Man muss also vorher auf DirectoryExists() prüfen und evtl. das DIR erzeugen!

Button2 erstellt die gesamte Specialfolder-Liste, die aktuell auf dem PC vorhanden ist.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComObj,
ActiveX, // IMalloc
ShellAPI, // SHGetSpecialFolderLocation() und SHGetPathFromIDList()
ShlObj; // CSIDL_-Konstanten

type
  TForm1 = class(TForm)
  Button1: TButton;
  Button2: TButton;
  Memo1: TMemo;
  procedure Button1Click(Sender: TObject);
  procedure Button2Click(Sender: TObject);
private

public

end;

var
Form1: TForm1;

implementation

{$R *.dfm}

function GetSpecialFolder(hWindow: HWND; Folder: Integer): String;
var
pMalloc: IMalloc;
pidl: PItemIDList;
Path: PChar;
begin
if (SHGetMalloc(pMalloc) <> S_OK) then
  begin
    MessageBox(hWindow, 'Couldn''t get pointer to IMalloc interface.','SHGetMalloc(pMalloc)'16);
    Exit;
  end;
SHGetSpecialFolderLocation(hWindow, Folder, pidl);
GetMem(Path, MAX_PATH);
SHGetPathFromIDList(pidl, Path);
Result := Path;
FreeMem(Path);
pMalloc.Free(pidl);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
 FLibHandle: THandle;
 path:WideString;
 SHSetFolderPath: FUNCTION (csidl:integer;
 hToken:THANDLE;
 dwFlags: DWORD;
 pszPath: LPCTSTR):HRESULT;stdcall;
begin
  FLibHandle := LoadLibrary('Shell32.dll');
  SHSetFolderPath := GetProcAddress(FLibHandle, pchar(Ord(232)));
  path:='C:\PROG-1';  // DEIN PFAD -------------------------------------------------<<<

  if DirectoryExists(path) = false then mkdir(path);//------------------------------<<<

  if assigned(SHSetFolderPath)
  then try     //------------------- DEINE KONSTANTE--------------------------------<<<
    if not SUCCEEDED(SHSetFolderPath(CSIDL_FAVORITES, 00, pchar(path)))then 
     Windows.beep(1000,100);
    finally
  end;
  if FLibHandle <> 0 then FreeLibrary(FLibHandle);
end;

procedure TForm1.Button2Click(Sender: TObject);
var i : Integer;
begin
for i := 0 to 63 do Memo1.Lines.add(IntToStr(i)+' : '+ GetSpecialFolder(Form1.Handle,i));
end;

end.


Christian213 - So 16.09.12 11:59

Hallo hathor,

Perfekt! - Genau so etwas habe ich gesucht!
1000 Dank! :-)

Gruß,
Christian


glotzer - So 16.09.12 12:09

user profile iconhathor hat folgendes geschrieben Zum zitierten Posting springen:


Delphi-Quelltext
1:
  if DirectoryExists(path) = false then mkdir(path);//------------------------------<<<                    


Böse!
Das sollte if not DirectoryExists(path) then mkdir(path); sein.


Delete - So 16.09.12 20:02

Das ist doch scheissegal, Du Oberlehrer!

"function DirectoryExists ( const DirectoryName : string ) : Boolean;"


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
function DirectoryExists(const Directory: string): Boolean;
var
  Code: Integer;
begin
  Code := GetFileAttributes(PChar(Directory));
  Result := (Code <> -1and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
end

//Alternative - erweitert:

function DirectoryExists(const Directory: string; FollowLink: Boolean = True): Boolean;
var
  Code: Cardinal;
  Handle: THandle;
  LastError: Cardinal;
begin
  Result := False;
  Code := GetFileAttributes(PChar(Directory));

  if Code <> INVALID_FILE_ATTRIBUTES then
  begin
    if faSymLink and Code = 0 then
      Result := faDirectory and Code <> 0
    else
    begin
      if FollowLink then
      begin
        Handle := CreateFile(PChar(Directory), GENERIC_READ, FILE_SHARE_READ, nil,
          OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
        if Handle <> INVALID_HANDLE_VALUE then
        begin
          CloseHandle(Handle);
          Result := faDirectory and Code <> 0;
        end;
      end
      else if faDirectory and Code <> 0 then
        Result := True
      else
      begin
        Handle := CreateFile(PChar(Directory), GENERIC_READ, FILE_SHARE_READ, nil,
          OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
        if Handle <> INVALID_HANDLE_VALUE then
        begin
          CloseHandle(Handle);
          Result := False; //----------------------------------------------------------<<<
        end
        else
          Result := True;  //----------------------------------------------------------<<<
      end;
    end;
  end
  else
  begin
    LastError := GetLastError;
    Result := (LastError <> ERROR_FILE_NOT_FOUND) and
      (LastError <> ERROR_PATH_NOT_FOUND) and
      (LastError <> ERROR_INVALID_NAME) and
      (LastError <> ERROR_BAD_NETPATH);// <= Die entscheidende Zeile, die fehlte...
  end;
end;


glotzer - So 16.09.12 20:32

user profile iconhathor hat folgendes geschrieben Zum zitierten Posting springen:
Das ist doch scheissegal, Du Oberlehrer!


Oh nein ist es nicht.

http://www.delphi-treff.de/tutorials/objectpascal/programmierung-mit-boolean-werten/typische-anfaengerfehler/


Narses - Mo 17.09.12 15:43

Moin!

user profile iconhathor hat folgendes geschrieben Zum zitierten Posting springen:
Das ist doch scheissegal, Du Oberlehrer!
:|

Also ich bin schon etwas ... verwundert ... sowas von einem langjährigen Community-Mitglied lesen zu müssen. Ich hoffe, dass bleibt ein verbaler "Ausrutscher", dieser Tonfall ist in der EE nicht erwünscht!

Da user profile iconglotzer obendrein in der Sache Recht hat, halte ich eine Entschuldigung für angemessen.

cu
Narses


Christian213 - Mo 17.09.12 15:45

Hallo,

klappt leider doch irgendwie noch nicht. Da ich das Ganze als Funktion brauchte, habe ich es wie folgt umgesetzt:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
function SetFolderPath(Csidl : Integer; Path : AnsiString) : boolean;

type TSHSetFolderPath = function (Csidl : Integer; hToken : THandle; dwFlags : Dword; pszPath : LPctstr) : HResult; stdcall;

var FLibHandle      : THandle;
    SHSetFolderPath : TSHSetFolderPath;
begin
  Result := false;
  FLibHandle := LoadLibrary('shell32.dll');
  SHSetFolderPath := TSHSetFolderPath(GetProcAddress(FLibHandle, pChar(Ord(232))));
  if assigned(SHSetFolderPath) then
  try
    if not SUCCEEDED(SHSetFolderPath(Csidl, 00, pChar(Path))) then
      Result := false;
  finally
    Result := true;
  end;
  if (FLibHandle <> 0then
    FreeLibrary(FLibHandle);
end;

Das Kapseln mittels der Typedefinition brauchte ich, da ich es unter Lazarus kompilieren will.
Es fliegt zwar kein Fehler, aber der übergebene Pfad wird auch nicht gesetzt. Habe es mit verschienden CSIDL-Werten und Pfaden versucht.
Hat jemand eine Idee?

Gruß
Christian


Delete - Mo 17.09.12 16:25


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
function DirectoryExists2(const Directory: string): Boolean;
var
  Code: Cardinal;
begin
  Code := GetFileAttributes(PChar(Directory));
  Result := (Code <> INVALID_FILE_ATTRIBUTES) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
end;

function SetFolderPath(CSIDL : Integer; PATH : WideString) : boolean;
var
 FLibHandle: THandle;
 SHSetFolderPath: FUNCTION (csidl:integer; hToken:THANDLE; dwFlags: DWORD;
 pszPath: LPCTSTR):HRESULT;stdcall;
begin
  FLibHandle := LoadLibrary('Shell32.dll');
  SHSetFolderPath := GetProcAddress(FLibHandle, pchar(Ord(232)));
  if not DirectoryExists2(path) then mkdir(path);

  if assigned(SHSetFolderPath)
  then
  try
    if not SUCCEEDED(SHSetFolderPath(CSIDL, 00, pchar(PATH)))then
    BEGIN
          Result := false;
          Windows.beep(1000,100);
    END else
        begin
          Result := true; //---------------------------------------------<<<
        end;
  finally
  end;
  if FLibHandle <> 0 then FreeLibrary(FLibHandle);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
 SetFolderPath(CSIDL_PERSONAL, 'C:\HATHOR-5');
end;


Christian213 - Mo 17.09.12 19:00

Hallo nochmal,

Kommand zurück - es funktioniert doch!
Mir war nicht bewusst, dass manche "SpecialFolders" erfordern, dass man sich ab- und wieder anmeldet bevor die neuen Einstellungen greifen.
Oder kann das OS irgendwie dazu bewegen, die neuen Pfade sofort zu benutzen??
Wie auch immer, Problem gelöst - vielen Dank nochmal :-)

Gruß,
Christian


Gerd Kayser - Mo 17.09.12 22:56

user profile iconChristian213 hat folgendes geschrieben Zum zitierten Posting springen:
Oder kann das OS irgendwie dazu bewegen, die neuen Pfade sofort zu benutzen??


Werf mal einen Blick auf die Message WM_SETTINGCHANGE. Eventuell löst das Dein Problem. Ich kann es nur nicht testen, weil ich nach dem Einbau einer SSD-Platte noch einige Tage mit dem Aufsetzen meines Rechners beschäftigt sein werde.