"OpenDialog1->Files->Count" auf 830 Dateien beschränkt?
-
Ich verwende den OpenDialog um mehrere Dateien zu laden. Dazu habe ich unter Options Multiselect auf true gesetzt.
Wenn ich nun über 830 Dateien lade erhalte ich einen EFOpenError. Schau ich mir die OpenDialog1->Files->Count-Eigenschaft an ist klar warum. Dort steht der Wert 830 drin obwohl ich mehr Dateien ausgewählt habe.
Gibt es für das Problem eine Lösung?
-
Eigenartig, das habe ich noch nicht gesehen.
Bevor ich es zu reproduzieren versuche: welche C++Builder-Version verwendest du?
-
ich arbeite mit dem BCB5
-
Den hab ich nicht. Mit dem C++Builder 6 habe ich aber gerade mal alle Dateien in %WINDIR%\system32 eingelesen, das waren über 2000. Keine Probleme.
void __fastcall TForm1::Button1Click(TObject *Sender) { if (OpenDialog1->Execute ()) { for (int i = 0, c = OpenDialog1->Files->Count; i < c; ++i) ListBox1->Items->Add (OpenDialog1->Files->Strings[i]); Label1->Caption = String (OpenDialog1->Files->Count); } }
-
Ich kann es auch mit dem BCB5 nicht reproduzieren (auf Windows XP).
Wenn ich den Inhalt von Windows\System32 öffne, steht in OpenDialog1->Files->Count irgendwas über 2300.
-
Ok hab das mit der System32 auch mal probiert. Und siehe da. Auch bei mir werden 2300 Dateien geladen. Dann bin ich draufgekommen. Die Dateinamen, die ich lade, haben zu viele Zeichen. Hier eine Beispieldatei: 120710_1051_45_RR2_555_ABTZ_2344.txt
Wenn man nun die Eigenschaft unter Options PathMustExist auf true setzt erscheint zumindest eine Fehlermeldung wenn man den OK Button im Dialog betätigt.
Die Dateinamen kann ich leider nicht kürzen. Gibt es eine Lösung für das Problem?
-
rudpower schrieb:
Gibt es eine Lösung für das Problem?
Einen eigenen Dialog programmieren ?
Ist denn die Auswahl so vieler Dateien in dieser Art sinnvoll ?
Die haben doch sicher Gemeinsamkeiten im Namen ? Da macht wahrscheinlich eine andere Art von Benutzerinterface Sinn.
-
Auf die Dateinamenbenennung habe ich keinen Einfluss. Mein Programm ist eine Art Einleseroutine für ein anderes Programm.
Einen eigenen Dialog hatte ich schon. Ich habe alle Dateien eines ausgewählten Verzeichnisses in eine ListBox geladen. Problem dabei ist, dass die Sortierung dort nicht stimmt. In meinem Dateinamen sind einstellige Ziffern die falsch sortiert werden. Beispiel:
120711_BI1
120711_BI2
...
120711_BI10
120711_BI11
usw.Die ListBox sortiert so:
120711_BI1
120711_BI10
usw.Bei dem OpenDialog ist dies nicht so.
-
Dann vielleicht die Namen selber sortieren und dann in eine Listbox einfügen ?
-
rudpower schrieb:
Ok hab das mit der System32 auch mal probiert. Und siehe da. Auch bei mir werden 2300 Dateien geladen. Dann bin ich draufgekommen. Die Dateinamen, die ich lade, haben zu viele Zeichen. Hier eine Beispieldatei: 120710_1051_45_RR2_555_ABTZ_2344.txt
Wenn man nun die Eigenschaft unter Options PathMustExist auf true setzt erscheint zumindest eine Fehlermeldung wenn man den OK Button im Dialog betätigt.
Die Dateinamen kann ich leider nicht kürzen. Gibt es eine Lösung für das Problem?
Aha, das erklärt das Problem. TOpenDialog ruft die Windows-Funktion GetOpenFileName() auf, und deren Schnittstelle ist, sagen wir mal, suboptimal: man muß eine OPENFILENAME-Struktur übergeben und darin einen Zeiger auf einen Speicherblock mitgeben, in den der Dateiname geschrieben wird. Das setzt natürlich voraus, daß man raten kann, wie viel Speicherplatz benötigt wird. In dialogs.pas steht folgendes:
function TOpenDialog.DoExecute(Func: Pointer): Bool; const MultiSelectBufferSize = High(Word) - 16; ... begin ... with OpenFilename do begin ... if ofAllowMultiSelect in FOptions then nMaxFile := MultiSelectBufferSize else nMaxFile := MAX_PATH;
Das heißt, wenn die Menge aller Dateinamen 64 KB überschreitet, wird hinten abgeschnitten oder der Dialog schlägt fehl, je nachdem.
Du kannst das Problem auf einem der folgenden Wege beheben:
- einen eigenen Dialog von TOpenDialog ableiten, die Execute()-Methode überschreiben und mehr Speicherplatz übergeben (vielleicht ein paar MB). Das verlagert das Problem zwar nur, aber es funktioniert wahrscheinlich für die meisten Fälle.
- C++Builder >= 2007 und Windows >= Vista benutzen. Da gibt es neue Dateidialoge mit gescheitem Interface, das diese Probleme nicht hat, und wenn man es nicht explizit abstellt, benutzen VCL-Anwendungen automatisch die neuen Dialoge
-
vielen Dank für die Antwort.
Du kannst das Problem auf einem der folgenden Wege beheben:
- einen eigenen Dialog von TOpenDialog ableiten, die Execute()-Methode überschreiben und mehr Speicherplatz übergeben (vielleicht ein paar MB). Das verlagert das Problem zwar nur, aber es funktioniert wahrscheinlich für die meisten Fälle.
- C++Builder >= 2007 und Windows >= Vista benutzen. Da gibt es neue Dateidialoge mit gescheitem Interface, das diese Probleme nicht hat, und wenn man es nicht explizit abstellt, benutzen VCL-Anwendungen automatisch die neuen DialogeMöglichkeit 2 scheidet bei mir aus da ich mit Windows XP und dem BCB 5 arbeite.
Leider hab ich mit Vererbung bisher nicht gearbeitet bzw. hab es nicht gebraucht.
Das müsst ja so ungefähr funktionieren:
class MyTOpenDialog : public TOpenDialog { };
Kann ich die Klasse in private der Form deklarieren? Wie überschreibe ich die Execute Methode bzw ändere die maximale Größe der Dateinamenmenge?
-
rudpower schrieb:
Du kannst das Problem auf einem der folgenden Wege beheben:
- einen eigenen Dialog von TOpenDialog ableiten, die Execute()-Methode überschreiben und mehr Speicherplatz übergeben (vielleicht ein paar MB). Das verlagert das Problem zwar nur, aber es funktioniert wahrscheinlich für die meisten Fälle.
- C++Builder >= 2007 und Windows >= Vista benutzen. Da gibt es neue Dateidialoge mit gescheitem Interface, das diese Probleme nicht hat, und wenn man es nicht explizit abstellt, benutzen VCL-Anwendungen automatisch die neuen DialogeMöglichkeit 2 scheidet bei mir aus da ich mit Windows XP und dem BCB 5 arbeite.
War auch eher der obligatorische Wink mit dem Zaunpfahl. Mit alter Software machst du dir nur das Leben schwer.
rudpower schrieb:
Leider hab ich mit Vererbung bisher nicht gearbeitet bzw. hab es nicht gebraucht.
Aha, du machst Learning by Doing?
Ich rate dir, nimm dir mal die Zeit für ein paar richtige Bücher zum Thema. Es lohnt sich.
rudpower schrieb:
Das müsst ja so ungefähr funktionieren:
class MyTOpenDialog : public TOpenDialog { };
Kann ich die Klasse in private der Form deklarieren? Wie überschreibe ich die Execute Methode bzw ändere die maximale Größe der Dateinamenmenge?
Zunächst wirst du feststellen, daß TOpenDialog dir die Änderung nicht einfach machen wird (alle Codebeispiele sind aus C++Builder 6, weil ich C++Builder 5 nicht besitze):
// dialogs.pas, l. 643ff function TOpenDialog.DoExecute(Func: Pointer): Bool; const MultiSelectBufferSize = High(Word) - 16; // <-- hier wird die Puffergröße definiert ... var TempFilter, TempFilename, TempExt: string; begin ... if ofAllowMultiSelect in FOptions then // <-- hier wird die Puffergröße gesetzt nMaxFile := MultiSelectBufferSize else nMaxFile := MAX_PATH; SetLength(TempFilename, nMaxFile + 2); // <-- hier wird der Puffer angelegt ... end; // l. 868ff function TOpenDialog.Execute: Boolean; begin Result := DoExecute(@GetOpenFileName); end;
All das verbirgt sich nicht etwa in einer kleinen, dem spezifischen Zweck der Pufferallokation zugedachten virtuellen Funktion, sondern in der monolithischen, nichtvirtuellen Funktion DoExecute(). TOpenDialog.Execute(), dankenswerterweise überschreibbar, ruft die Funktion in recht einfacher Weise auf. Du wirst also von TOpenDialog ableiten, die Execute()-Funktion überschreiben und die komplette Implementation von DoExecute() wiederholen müssen.
Damit du nicht den schönen Tag damit verbringen mußt, Delphi nach C++ zu übersetzen, entscheidest du sodann, dein Derivat, nennen wir es zweckgemäß TOpenDialogForManyFiles, in Delphi zu implementieren. Du erstellst also in C++Builder ein neues Package, sagen wir "MyFixesForAncientBCB5VCL.bpl", und speicherst es irgendwo. In demselben Ordner erstellst du eine .pas-Datei folgenden Inhalts:
unit OpenDialogForManyFiles; interface uses Types, Dialogs; type TOpenDialogForManyFiles = class (TOpenDialog) private function DoExecute(Func: Pointer): Bool; public function Execute: Boolean; override; end; procedure Register; implementation uses SysUtils, Classes, Controls, Forms, CommDlg, Windows; function TOpenDialogForManyFiles.DoExecute(Func: Pointer): Bool; const MultiSelectBufferSize = 1024 * 1024 * 4; // <-- neue Puffergröße: 4 MB OFN_ENABLESIZING = $00800000; // auch ganz praktisch // den Rest übernimmst du unverändert aus TOpenDialog.DoExecute() in dialogs.pas! end; function TOpenDialogForManyFiles.Execute: Boolean; begin Result := DoExecute(@GetSaveFileName); end; procedure Register; begin RegisterComponents ('Dialoge', [TOpenDialogForManyFiles]); end; end.
Die fügst du dann dem Package-Projekt hinzu und kompilierst es. Du wirst einen Haufen Fehlermeldungen bekommen, weil DoExecute() auf diverse private Felddeklarationen von TOpenDialog zugreift ("undefinierter Bezeichner FFiles" oder so). Die Fehler kannst du allesamt umgehen, indem du den "F"-Präfix wegnimmst, so daß deine Funktion auf die korrespondierenden (öffentlichen) Eigenschaften zugreift - mit folgenden Ausnahmen:
- diese Zeile:
FCurrentFilterIndex := FFilterIndex;
für FCurrentFilterIndex gibt es keine korrespondierende Eigenschaft; kommentiere die Zeile einfach aus.
- das hier:
if (FFlags and OFN_EXTENSIONDIFFERENT) <> 0 then Include(FOptions, ofExtensionDifferent) else Exclude(FOptions, ofExtensionDifferent); if (FFlags and OFN_READONLY) <> 0 then Include(FOptions, ofReadOnly) else Exclude(FOptions, ofReadOnly);
Dieser Zugriff ist nur für L-Values gestattet, und das sind Properties nur in Zuweisungen; ändere es ab in
if (Flags and OFN_EXTENSIONDIFFERENT) <> 0 then Options := Options + [ofExtensionDifferent] else Options := Options - [ofExtensionDifferent]; if (Flags and OFN_READONLY) <> 0 then Options := Options + [ofReadOnly] else Options := Options - [ofReadOnly];
- Außerdem wirst du noch über das hier stolpern, was auf private Methoden aus Dialogs.pas zugreift:
if (ofOldStyleDialog in FOptions) or not NewStyleControls then lpfnHook := DialogHook else lpfnHook := ExplorerHook;
Du kannst die vier Zeilen auskommentieren.
Sodann installierst du das Package (im Projektmanager: Rechtsklick|Installieren) und hast in der Tool-Palette deine neue Komponente zur Verfügung.
(Bonusaufgabe für Sharkbyte, falls du hier mitliest:
Dir ist sicher aufgefallen, daß ich oben eine Definition fürOFN_ENABLESIZING
angegeben habe, die im Original nicht drinsteht. Du solltest in der Lage sein, damit dein Problem hier zu lösen und deinen Dialog resizable zu bekommen.)