Wyświetlanie elementów jako UNICODE w TShellTreeView pod Delphi 7.

0

Cześc.

Jak wiecie staram się sobie na ogół radzić sam, ale tutaj pomimo kombinowania nie za bardzo znowu mi wychodzi. Tym razem problem nie z WinAPI, a z UNICODE. Do tej pory pod Delphi 7 potrzebowałem wsparcia i wyświetlania takich znaków jak ta dziwna, ruska Cyrylica w jednym z projektów. I to głownie za namową @Marooned'a, który jako aktywny użytkownik mojego programu do pobierania zdjęć znajomych z FaceBooka ma decydujące zdanie :) Ale do rzeczy. Chodzi o kod: http://4programmers.net/Pastebin/2280 - załączyłęm go w module ashellctrls.pas wraz z testowym programem w Delphi 7 i exekiem w pliku unicode_stv.rar. Przycisk Button1 tworzy nam katalog na dysku D:\ poprzedzony !_ i tym ruskim tekstem. Nie wiem nawet co to znaczy - nie sprawdzałęm. Zostało to skopiowane na szybko z pliku pomocy do nowego FAR Managera. Chodziło tylko o szybki test. Kiedy wejdziemy do tego katalogu, jak widać przycisk Button7 pokazuje nam prawidłową śieżkę w komponencie typu TTntLabel. Jakby ktoś nie miał TNT w tej wersji ze źródłem większości modułów (ale bez tntnitfiles, który sobie wygooglowałem w jakiejś wersji i tam dodałem), ktorej użyłem to dodałem ją w archiwum tnt.rar. Najlepiej wrzucić do $(Delphi)\LIB\TNT\ i zapisać cały projekt i przebudować, bo Search Path jest dodane, tylko w pliku *.cfg może się nie odświeżyć. Ok, problem jest oczywisty. Jak sprawić aby katalog również wyświetlił się w Cyrylicy? Pozmieniałem możliwe typy ze string na WideString. Funkcje WinAPI jakie znalazłem z tych bez sufixu (czyli pod Delphi 7 z "A", takie jak FindFirstFileA) na te z W. czyli z przykładowo FindFirstFileA na FindFirstFileW. To samo z typami. Ale mogłem coś przeoczyć. Samego miejsca dodawania dokładnie nie namierzyłem. Dlatego prośba do wszystkich. Pomóżcie sprawić aby ten komponent pokazał mi folder z prawidłowymi znakami cyrlicy zamiast znaków zapytania. I nie chcę instalować 32 bitowego Lazarusa i pod nim modzić. Wolę mieć projekt nadal w Delphi 7 i spróbować jakoś z Waszą pomocą ogarnąć temat żeby w programie poza użytymi już kontrolkami TNT - mieć pełne wsparcie dla nietypowych tekstów z UNICODE. Dodam, że oryginalny kod miałem już kiedyś w podkatalogu LIB i jest on jako oryginał chyba prawidłowy. Proszę o przykłady rozwiązań. Z góry dziękuję i przepraszam za rozpisanie się :)
unicode_stv.jpg

0

Nie mam za bardzo jak tego sprawdzić, ale kontrolka shell używa chyba ustawień z windowsa? Czy windowsowy explorer wyświetla cyrylice poprawnie? Może trzeba w opcjach językowych panelu sterowania dograć "języki wschodnioazjatyckie" albo przestawić lokalizacje.
Jeżeli nie tu leży problem, to pozostaje zajrzeć do kodu komponentu (czy nie używa string zamiast widestring)

0

@ergo: dziękuję za opdowiedź. ale oczywiście to sprawdziłem. Poza najnowszym publicznie dostępnym, stabilnym Total Commanderem, również Eksplorator Windows pod Windows 7 Ultimate 64 bit PL wyświetla nazwę jak nalezy. A właśnie z namierzeniem konkretnego miejsca z dodawaniem itemów mam problem. Być może trzeba by było też poza zmianami jakie dokonałem zrobić klasę dziedziczącą po TNode i wszystkie pola rypu string też podmienić na WideString. Jak zresztą widzisz w kodzie, praktycznie już nie ma odwołań do string, poprawiłem też to co pozostało z PChar na PWideChar i funkcję SHGetFileInfo na SHGetFileInfoW. Ale wiadomo ta używana jest raczej tylko do pobrania ikony dla danego elementu. Dlatego problem nadal pozostaje i czekam na rady od Was, najlepiej z przykładami. Pewnie rozwiązanie jest proste, tylko ja go nie mogę niestety sam ogarnąć :/

0

Macie rację, patrząc na kod ashellctrls.pas, to wygląda na to, że kodowanie traci się gdzieś po drodze. Żmudna robota, żeby to przeszukiwać.
Nazwa folderu zwracana jest przez metodę IShellFolder::GetDisplayNameOf w postaci wskaźnika do stringa więc tu należałoby zacząć:

 
function GetDisplayName(Parentfolder : IShellFolder; PIDL : PItemIDList;
  Flags : DWORD) : WideString;
var
  StrRet : TStrRet;
begin
  Result := '';
  if ParentFolder = nil then
  begin
    Result := 'parentfolder = nil'; { Do not localize }
    exit;
  end;
  FillChar(StrRet, SizeOf(StrRet), 0);
  ParentFolder.GetDisplayNameOf(PIDL, Flags, StrRet);  
  Result := StrRetToString(PIDL, StrRet);              
  { TODO 2 -oMGD -cShell Controls : Remove this hack (on Win2k, GUIDs are returned for the
  PathName of standard folders)}
  if (Pos('::{', Result) = 1) then
    Result := GetDisplayName(ParentFolder, PIDL, SHGDN_NORMAL);
end;

function StrRetToString(PIDL : PItemIDList; StrRet : TStrRet; Flag : WideString = '') : WideString;
var
  P : PChar;
begin
  case StrRet.uType of
    STRRET_CSTR :
      Result := StrRet.cStr;
    STRRET_OFFSET :
      begin
        P := PChar(@PIDL.mkid.abID[StrRet.uOffset - SizeOf(PIDL.mkid.cb)]);
        SetString(Result, P, PIDL.mkid.cb - StrRet.uOffset);
      end;
    STRRET_WSTR :
      if Assigned(StrRet.pOleStr) then
        Result := StrRet.pOleStr
      else
        Result := '';
  end;
  { This is a hack bug fix to get around Windows Shell Controls returning
    spurious "?"s in date/time detail fields }
  if (Length(Result) > 1) and (Result[1] = '?')
    {{and (PWIdeChar(Result[2]) in ['0'..'9'])}then
    Result := Tnt_WideStringReplace(Result, '?', '', [rfReplaceAll]);
end;

Widać, że nazwa jest w rekordzie StrRet, potem przekształcana na widestring funkcją StrRetToString. Tyle, że funkcja używa PChar, a nie PWideChar.

0

Funkcja StrRetToString używa PChar, bo musi. StrRet to taki dziwoląg, który może zawierać tekst jako ansi albo jako unicode, stąd ten case StrRet.uType of.
Ta funkcja jest dobra. Choć przydałoby się wyświetlić, albo sprawdzić pod debuggerem, który case się wykonuje.

0

Ech ten nasz pastebin. Jak coś źle nie sparsuje to się utnie. Wrzucam kod w załącznikach. W archiwum v1.rar jest wersja gdzie poprawiłem co się dało, ale bez efektów. W arhiwum v2.rar jest inna wersja gdzie poprawiłem wszystkie TTreeNode na TTntTreeNode i samą klasę, po której dziedziczy ten komponent, czyli teraz jest TTntCustomTreeView. Ale efekt jest taki, jak widziecie. Dodał się tylko element "Pulpit" i nic więcej. Mogłem coś namieszać, ale nie wiem za bardzo konretnie co. Zobaczcie też exek oraz kod z archiwum test.rar. Dodaje się tekst z TntLabel1 do TTntTreeView, później można pokazać go przez WideShowMessage i zgadza się z tym w Labelu, ale jak widać na liście są same kreski. Mimo, że wcześniej dla pewności ustawiam taki sam font jak dla tego TntLabel. Kiedy w poniższym fragmencie kodu damy WideShowMessage by pokazało nam zawartość zmiennej DisplayName` to jest widoczna cyrylica, ale już na etapie dodawania, ponieważ własnośc TTreeNode jest typu string, zostaje "przekłamana". I pokazuje się to co widać na screenie w moim pierwszym postcie tego wątku. Dlatego, bardzo bym prosił o kolejne podpowiedzi, jak można rozwiązać mój problem.

//...

procedure TCustomShellTreeView.InitNode(NewNode : TTreeNode; ID : PItemIDList; ParentNode : TTreeNode);
var
  CanAdd : Boolean;
  NewFolder : IShellFolder;
  AFolder : TShellFolder;
begin
  AFolder := TShellFolder(ParentNode.Data);
  NewFolder := GetIShellFolder(AFolder.ShellFolder, ID);
  NewNode.Data := TShellFolder.Create(AFolder, ID, NewFolder);
  with TShellFolder(NewNode.Data) do
  begin
    NewNode.Text := DisplayName;
    if FUseShellImages and not Assigned(Images) then
    begin
      NewNode.ImageIndex := GetShellImage(AbsoluteID, False, False);
      NewNode.SelectedIndex := GetShellImage(AbsoluteID, False, True);
    end;
    if NewNode.SelectedIndex = 0 then
      NewNode.SelectedIndex := NewNode.ImageIndex;
    NewNode.HasChildren := SubFolders;
    if fpShared in Properties then
      NewNode.OverlayIndex := 0;
    if (otNonFolders in ObjectTypes) and (ShellFolder <> nil) then
      NewNode.HasChildren := GetHasSubItems(ShellFolder, ObjectFlags(FObjectTypes));
  end;

  CanAdd := True;
  if Assigned(FOnAddFolder) then
    FOnAddFolder(Self, TShellFolder(NewNode.Data), CanAdd);
  if not CanAdd then
    NewNode.Delete;
end;
0

zdajesz sobie sprawę, że MS Sans Serif nie jest unicodowy - ustaw treeview Microsoft Sans Serif i zagada to jeśli chodzi o tntlabel. Jeśli chodzi o shelltreeview to po prostu nie da rady tak tego zrobić bo jest ono oparte o customtreeview, które w d7 nie pokaże Ci nic w unicode. Tam każdy item ma właściwość Text typu string i koniec. Jeśli chciałbyś aby to działało to musisz napisać własną kontrolkę dziedziczącą po ttnttreeview

0

A dziękuję. To rozwiązuje problem przy użyciu TTntTreeView, ale wolał bym nie pisać od nowa własnego WideShellTreeView dziedziczącego po TTntTreeView, a mieć zamiast tego wsparcie dla unicode na bazie tamtego komponentu. Dodam, że oczywiście zmiana Font.Name := 'Microsoft Sans Serif' nie pomaga.

0

Sorry @Azarien, dopiero teraz zauważyłem Twoją odpowiedź. Ten potwortek wygląda tak:

//..
  PSTRRet = ^TStrRet;
  {$EXTERNALSYM _STRRET}
  _STRRET = record
     uType: UINT;              { One of the STRRET_* values }
     case Integer of
       0: (pOleStr: LPWSTR);                    { must be freed by caller of GetDisplayNameOf }
       1: (pStr: LPSTR);                        { NOT USED }
       2: (uOffset: UINT);                      { Offset into SHITEMID (ANSI) }
       3: (cStr: array[0..MAX_PATH-1] of Char); { Buffer to fill in }
    end;
  TStrRet = _STRRET;
  {$EXTERNALSYM STRRET}
  STRRET = _STRRET;
//...

I nie przeskocze jednak go, ponieważ korzysta z GetDisplayNameOf, które wymaga takiego typu jak powyżej. Nie ma też odpowiednika dla komponentów TnT.

Coż @abrakadaber masz rację, będę musiał chyba spróbowac napisać własną kontrolkę dziedziczącą po tych z Tnt. Dla mnie najprościej, mającego doświadczenie z TListView było by zrobić własne ListView do przechodzenia po katalogach i ich wyboru, tak jak jest to pod TotalComamnderem. Chociaż wolał bym widok jak w TTreeView i dodawanie takich elementów do rozwinięcia i wybrania jak Pulpit, Mój komputer i wszystkie dyski. Jednak nie mam doświadczenia z typami PItemIDList oraz żadnym TTreeView. Natomiast nie chcę używac do wyboru katalogu w akurat tym programie SelectDirectoryEx, ponieważ jest małych rozmiarów i do tego mniej wygodniejsze w nawigacji. A może ktoś z Was bawił się w zrobienie takiego ShellTreeView opartego na Unicode pod Delphi 7. Lub może coś podpowiedzieć jeszcze jak się tutaj do tego zabrać. Samodzielnie widzę tylko fragmentów kodu do zaadoptowania u siebie w docelowym rozwiązaniu.

0

Nie wiem czy sprawdziłeś VirtualTreeView tam nawet w demo jest coś takiego zrobione, sprawdź jak działa ten z demo (wg. tego co piszą) powinien radzić sobie z Unicode . Jest to na jednej z zakładek w demo Advanced (tam są dodane kolumny ale żaden problem ich nie dodać byle tylko wyświetlał poprawnie).

EDIT// Jednak jest tak samo przynajmniej u mnie ale to może z powodu czcionki nie wiem nie wnikałem :/

0

@kAzek: a mogę Ciebie prosić o przykład jak z TVirtualStringTree (chyba, że należy z innego komponentu z tej paczki) zrobić ShellTreeView. Ponieważ zainstsalowałem najnowszą paczkę z sourceforge, z czwartego kwietnia. I ogarnąłem demo Minimal. Jeżeli tam zmienię nazwę czionki dla TVirtualStringTree na "Microsoft Sans Serif" to również wyświetla prawidłowo nazwę z TntCaption Cyrylicą. Poza tym Caption dla typu PMyRec, jest tam typu WideString, co sprawia, że wyświetla cyrylicę. Próbowałem też komponentów z pakietu "ShellShock". Jednak tam rónież TStShellTreeView pomimo zmiany czcionki wyświetla nazwę folderów bez Cyrylicy.

0

Z wszystkimi funkcjami jak ShellTreeView to praktycznie od nowa pisanie dość zaawansowanego komponentu, natomiast jeżeli chodzi tylko o wyświetlanie drzewa folderów to jest w demo Advanced tylko oparte na TVirtualDrawTree bo tam jest dodatkowo zrobione wyświetlanie miniatur plików graficznych ale z przerobieniem tego na tylko wyświetlanie folderów opartym na "zwykłym" TVirtualStringTree nie powinno być żadnego problemu.

0

Uzyskałem taki efekt jak na screenie poniżej. Teraz czeka mnie mozolne pozbycie się kolumn i doapsowanie wyglądu. A nie będzie to łatwe, bo w kodzie *.dfm widzę własnośc Columns, a w Object Insepctorze dla TVirtualDrawTree ich nie widzę. Ogólnie zamotany jak dla mnie tne VirtualTreeView, bo nie miałem z nim styczności. Chciałbym jednak móc uzyskać taki efekt jak w ShellTreeView. I już wiem dlaczego w załączniku v2.rar widać tylko element "Pulpit" I raczej tego się nie przeskoczy, ponieważ TntVrtualTreeView nie wykonuje kodu z procedury InitNode, ale nie wiem czy wynika to tylko z dziedziczenia po innej klasie bazowej.
virtual_tv.jpg

0

Sorry za post pod postem, ale będzie czytelniej jak dla mnie ;)

Po komentarzu @Azarien'a wnoszę, że będą z tym za pewne jakieś problemy. Jednak @Marooned nie posiada chyba znajomych Chinczyków na FaceBook'u ;) A przynajmniej nie zgłaszal takich nazw albumów w testach mojego programu. Za pewne aby kodowanie azjatyckie chodziło jak należy musimy mieć system w tym języku. Chociaż Total Commander ma ich obsługę ok i to co opiszę poniżej również.

Dziękuję również @kAzek'owi za porady. Ostatecznie jednak postanowiłem nie wyważać otwarych drzwi i skorzystać z rozwiązań stricto systemowych. Z moich testów wynika, że i nazwa Chinskiej Republiki Ludowej wklejona z Wikipedii wyświetla się również ok. Do tego posta dołaczam archiwum z projektem, który testowałem na wirtualnej maszynie z Windows 98. I tam mamy obslugę ShellDialogu z ANSI.

Całośc jest w module browse_folder_dialogs.pas. Jest to WinAPI "compatibile", jak to czasami mam w zwyczaju ;) Dołaczony exek pozwala przetestować całość bez kompilowania pod Delphi 7. A i bug opisany w pytaniu, w wątku pod tym adresem: http://stackoverflow.com/questions/5975745/tbrowseforfolder-selected-row-out-of-focus również u mnie występuje (może to wina faktycznie 64 bitowego systemu). Także rozwiązałem ją dla pewności 100 milisekundowym Sleepem i ponowieniem komunikatu. Sleep jest na tyle krótki, że nie powinno to być uciążliwe, a innego działającego rozwiązania nie znam. Bez Sleep'a bug miał miejsce.

Ten post zatwierdzam jako rozwiązania. Jednak przyszłościowo, jeżeli ktoś wyodrębni TShellTreeView do współpracy z Tnt to dajcie proszę tutaj znać. Wiem, że nie powinno się spoczywać na laurach, ale uznałem, że skoro pod nowszymi Windowsami to systemowe okno wyboru katalogu pokazuje się jak należy i nie jest aż tak małe jak mi się wydawało, to jestem w stanie to "przeboleć" :) Dziękuję wszystkim za zapoznanie się z tym wątkiem. Mam nadzieję, że może w przyszłosci moje rozwiązanie się komuś przyda do szybkiego użycia w swojej aplikacji bez klepania tego "od zera".

1 użytkowników online, w tym zalogowanych: 0, gości: 1