Laden...

[Artikel] CustomDialogs

Erstellt von dr4g0n76 vor 16 Jahren Letzter Beitrag vor 14 Jahren 66.475 Views
dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 16 Jahren
[Artikel] CustomDialogs

Ausgangssituation

Ausgangssituation

Oft wünscht man sich den FileDialog oder andere Dialog anpassen zu können.
Dann versucht man einfach auf den FileDialog irgendetwas draufzuplatzieren.
Leider hat der FileDialog kein „Controls“ item. Events für Controls usw. fehlt auch fast alles.

Was mir schon oft gefehlt hat war eine einfache Vorschau Funktion.

Was jetzt?

Man probiert es mit SetWindowsHookEx oder SetWindowsHook und ärgert sich erst mal über massenweise APIs die benötigt werden und von denen man vielleicht auch noch vor allem nicht weiss, wie diese in C# aufzurufen sind.

Microsoft schlägt vor über die DialogTemplates zu gehen.

Ok, nächster Schritt.

Wir suchen im Internet, ob vielleicht schon jemand die ganze Arbeit gemacht hat.
Vielleicht finden wir auch die beiden Artikel bei Codeproject, bei denen das ganz gut funktioniert.
Aber ganz so komfortabel ist das ganze dann doch nicht.
Also müssen wir einen Mittelweg gehen.
Wie man Controls einfügen könnte in einen FileDialog habe ich ja schon einmal unter
CustomFileDialog
gepostet.

Allerdings gefiel mir diese Möglichkeit selbst nicht so besonders.

Ziel

Unser Ziel ist jetzt also von hier…


Abbildung 1

nach hier zu kommen:


Abbildung 2

Das tolle ist dabei, ob das jetzt eine PictureBox rechts neben dran ist oder die Webbrowser-Komponente oder was eigenes ist, ist uns dabei völlig egal.

Für den OpenFileDialog, brauchen wir die Win32 API GetOpenFileName.

Diesem müssen wir eine OPENFILENAME Struktur übergeben.

Wenn wir ein bischen suchen finden wir z.B. folgendes:

 
   [StructLayout(LayoutKind.Sequential)]
    internal class DlgTemplate
    {
        // DLGTEMPLATE
        public Int32 style = DlgStyle.Ds3dLook
                  | DlgStyle.DsControl
                  | DlgStyle.WsChild
                  | DlgStyle.WsClipSiblings
                  | DlgStyle.SsNotify;

        public Int32 extendedStyle = ExStyle.WsExControlParent;
        public Int16 numItems = 1;
        public Int16 x = 0;
        public Int16 y = 0;
        public Int16 cx = 0;
        public Int16 cy = 0;
        public Int16 reservedMenu = 0;
        public Int16 reservedClass = 0;
        public Int16 reservedTitle = 0;

        // DLGITEMTEMPLATE

        public Int32 itemStyle = DlgStyle.WsChild;
        public Int32 itemExtendedStyle = ExStyle.WsExNoParentNotify;
        public Int16 itemX = 0;
        public Int16 itemY = 0;
        public Int16 itemCx = 0;
        public Int16 itemCy = 0;
        public Int16 itemId = 0;
        public UInt16 itemClassHdr = 0xffff;
        public Int16 itemClass = 0x0082;
        public Int16 itemText = 0x0000;
        public Int16 itemData = 0x0000;
    };

Es gibt also eine Dialog-Template Struktur.

Das muss man doch erst mal wissen.
Ich finde Microsoft hat diese gut versteckt.

Der Hook
Ok, wir brauchen doch einen Hook. Aber dieser sollte uns einige Vorteile bringen.

Alle Nachrichten des Dialogs werden an die Hook-Prozedur weitergeleitet. Damite können wir uns dann auch die Möglichkeit schaffen Controls im Dialog zu Layouten wie wir es möchten.

Für die OPENFILENAME Struktur benötigen wir einen Hook der über einen Unmanaged Pointer in selbige eingebunden wird. OFNHookProc

Zum Glück , können wir ein Managed Delegate benutzen das uns erlaubt GetOpenFileName aufzurufen.

Hier das Delegate das wir bei uns benutzen:


internal delegate IntPtr OfnHookProc( IntPtr hWnd,UInt16 msg,Int32 wParam,
Int32 lParam );

Wir legen uns einen Button auf eine Form und beschriften diesen Mit

„Choose File“.

Dann adden wir die PictureBox auf den OpenDialog.

Ich zäume jetzt mal das Pferd von hinten auf, so wenig Code wie nachfolgend aufgeführt , reicht also um den OpenFileDialog mit einer Bild-Vorschau aufzurufen.
Auszug zum dazugehörigen Source-Code:


private PictureBox m_PictureBox;

/// <summary>
/// Creates and shows an instance of the extended OpenFileDialog, with a PictureBox control placed inside
/// </summary>
private void buttonChooseFile_Click(object sender, System.EventArgs e)
{
// Create panel for the "preview" part of the dialog
      Panel panel = new Panel();
      panel.BorderStyle = BorderStyle.Fixed3D;

      // Add a picture box to the "preview" panel

      m_PictureBox = new PictureBox();
      panel.Controls.Add( m_PictureBox );
      m_PictureBox.Dock = DockStyle.Fill;
      m_PictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
      m_PictureBox.Click += new EventHandler( picBox_Click );
      m_PictureBox.BackgroundImageLayout = ImageLayout.Zoom;

      // Create and show the OpenFile Dialog

CustomizableDialogs.OpenFileDialog openFileDialog = new CustomizableDialogs.OpenFileDialog( "jpg", "", "Picture files (*.JPG;*.GIF;*.PNG)\0*.jpg;*.gif;*.png\0\0", panel );

openFileDialog.SelectionChanged += new
CustomizableDialogs.OpenFileDialog.SelectionChangedHandler(ofd_SelectionChanged);

bool bShow = openFileDialog.Show();
string sSelectedPath = openFileDialog.SelectedPath;

}

Da wir jetzt ein SelectedChanged-Event haben, können wir auch das entsprechende ausgewählte Bitmap laden:

[csharp]
/// <summary>
/// Event handler that is called when the user clicks on a file or folder inside the dialog
/// </summary>
/// <param name="path">the full path of the currently-selected file</param>
private void ofd_SelectionChanged( string _sPath )
{
// Check the path points to something valid before trying to display the contents

      if( !File.Exists( _sPath ) ) return;
      if( !Path.HasExtension( _sPath ) ) return;
      string ext = Path.GetExtension( _sPath ).ToLower();
      if( ext == ".jpg" || ext == ".gif" || ext == ".png" )
      {
            m_PictureBox.BackgroundImage = new Bitmap( _sPath );
      }
}

Wie machen wir das ganze aber, woher kommt das Ergebnis?

Dazu ist jetzt ein ganzer Satz APIs nötig, der sich im angehängten Testprojekt in der Klasse NativeMethods befindet.
Jedes Mal wenn der FileDialog in der Größe verändert wird,
Müssen die darin liegenden Controls entsprechend mit angepaßt werden.

Jetzt haben wir folgendes Problem: Wie kommen wir an ein Fenster oder ChildFenster das wir nicht erstellt haben?

Wir gehen mit dem Spy++ auf "SHELLDLL_DefView", klicken dies mit rechts an und wählen Eigenschaften aus.


Abbildung 3

Uns interessiert die Nummer 0x0461 (Control ID).

Wenn wir diese Nummer jetzt mit GetDlgItem API benutzen, können wir uns darauf verlassen auf die richtige ID zuzugreifen.
Wenn wir andere Controls verändern wollen, machen wir das genau gleich.
Jetzt wissen wir also, wie wir an alle Handles herankommen können.

Die Hookprocedure sollte mindestens folgende Windows-Nachrichten abgreifen:

* WM_INITDIALOG. Diese Nachricht sagt uns, dass der Dialog jetzt real existiert. Zu diesem Zeitpunkt werden wir eigene Controls in den Dialog einfügen.  Dazu benutzen wir SetParent.  

* WM_SIZE. Diese Nachricht sagt uns, dass etwas an der Größe des Dialogs verändert wurde. Auch beim ersten Anzeigen des Dialogs erhalten wir eine WM_Size-Nachricht  
  Dies benutzen wir um eigens hinzugefügte Controls richtig anzuordnen, wenn ein Resize-Event auftritt.  

* WM_NOTIFY. Dies fast viele Nachrichten zusammen, auch alle der Nachrichten die sich auf Dialoge beziehen mit Namen CDN_xxx.  
  Wir benutzen hier nur CDN_SELCHANGE, da wir wissen wollen was der User ausgewählt hat, um unsere Bitmap anzuzeigen.  
  Wenn wir diese Nachricht empfangen, können wir den CommonDialog nach dem vollen Namen der Datei fragen, indem wir eine CDM_GETFILEPATH-Nachricht schicken.   

Das sieht dann so aus:


StringBuilder pathBuffer = new StringBuilder(_MAX_PATH);

NativeMethods.SendMessage(hWndParent,CommonDlgMessage.GetFilePath,
_MAX_PATH,pathBuffer);

string path = pathBuffer.ToString();

Wir schicken eine Nachricht mit dem aktuellen Parent-Handle, dem GetFilePath und _MAX_PATH. _MAX_PATH wird für einen Buffer von Maximal 260 Zeichen benötigt.


Marshalling Strings (P/Invoke)

Die OPENFILENAME Struktur übergibt 2 Memory Buffer an die GetOpenFileName API, einen für den kompletten im Pfad selektierten Dialog, und einen für den Dateinamen. Normalerweise, könnten wir einen StringBuilder übergeben und es würde funktionieren.
Ich benutze hier aber SendMessage. Da hier die Strings aber Teil einer größeren Struktur sind, funktioniert p/invoke in Kombination mit StringBuilder nicht;.

Wir müssen dazu die Strings auch unbedingt im unmanaged Memory reservieren.
Wie erreichen wir das mit Managed Code?
Wir benutzen System.Runtime.InteropServices.Marshal.AllocCoTaskMe:


_fileNameBuffer = Marshal.AllocCoTaskMem(2 * _MAX_PATH);

_fileTitleBuffer = Marshal.AllocCoTaskMem(2 * _MAX_PATH);

Wenn wir diese Buffer benutzen, ist es angebracht diese zu nullen.Das erreichen wir mit Marshal.Copy.

Jetzt müssen wir Unicode-Strings ein- und auslesen können.
Diese Strings können so in einen Buffer geschrieben werden:


UnicodeEncoding ue = new UnicodeEncoding();

byte[] pathBytes = ue.GetBytes(path);

Marshal.Copy(pathBytes, 0, _fileNameBuffer, pathBytes.Length);

Und so lesen wir das ganze wieder aus:

String fileName = Marshal.PtrToStringUni(_fileNameBuffer);

Der Konstruktor von OpenFileDialog braucht mehrere Parameter.

Zwei davon sollten hier etwas genauer erklärt werden:

* String filter. Dieser Parameter bestimmt im Common Dialog welche Dateien im Inhaltsfenster angezeigt werden sollen. Tatsächlich wird dies durch Stringlistenpaare repräsentiert, die durch einen NULL-char getrennt sind, wobei der erste string, der 2. beschreibt das Format der akzeptierten Dateien, entsprechend den angegebenen Wildcards. Das exakte Format dafür befindet sich in der OPENFILENAME-Struktur.  

* Control userControl. Hiermit pflanzen wir das übergebene Control in den OpenFileDialog.  
  Das geht mit jedem Control, aber ein Panel zu nehmen macht vielleicht sogar mehr Sinn: Denn jetzt können wir mittels Panel.Controls.Add gleich weitere Controls hinzufügen.   

Nachdem eine Instanz von OpenFileDialog erstellt wurde, muss einfach nur noch dessen Show-Methode aufgerufen werden.Wenn der User OK geklickt hat, erhalten wir true zurück ansonsten false, wenn der Dialog aus einem anderen Grund geschlossen wurde.
Wenn der Dialog geschlossen wurde bekommen wir den kompletten Dateipfad über SelectedPath.

Ok, es sind vielleicht nicht ganz so wenig APIs wie gedacht, aber bei weitem weniger als ich bei diesen Versuchen benötigt hatte, bis das ganze Ding lief.

Der Rest steht in diesem Fall im Source-Code, habs bisher auf Englisch dokumentiert...

Ausserdem habe ich das Beispielprojekt angehängt.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

Q
214 Beiträge seit 2006
vor 16 Jahren

Hallo,
echt nett dein Tool/Artikel, leider bot mir dein FileDialog zuwenig Einstellmöglichkeiten, z.B. Breite des Fensters, Titel etc.
Da ich aber viele Probleme mit dem Kompilieren deines Projektes hatte, z.B. in Test.cs wurde gemeldet, dass er DlgStyle nicht kennt, sowie weiter Probleme, hab ich mich weiter auf die Suche gemacht, und folgendes gefunden:
Customizing OpenFileDialog in .NET

Ich bin damit echt zufrieden, da man dort deutlich mehr an der Oberfläche ändern kann, ohne großartig sich mit Hooks und ähnlichem rumplagen zu müssen.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 16 Jahren

Bei mir lässt sich das Projekt ohne Problem übersetzen. Ausserdem bezieht sich mein Artikel auf Dialog im allgemeinen. Mit dieser Technik kannst Du z.B. auch den Font-Dialog verändern.

Mir war persönlich der FileDialog im Artikel den du bei Code-Projekt gefunden hast, zu unflexibel. Aber so hat jeder eine andere Sichtweise oder Einsatzgebiete.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 14 Jahren
CusomtizableDialogs: Update

Ab jetzt füge ich hier ein neues Projekt hinzu.

Dieses Projekt basiert zu 100% auf dem alten Code.

Änderungen:

  • Konvertiert für VS 2008* Alle Properties des normalen FileDialogs können jetzt gesetzt/ausgelesen werden
  • OpenFile() sollte den Stream des selektierten Files zurückgeben, bei MultiSelect das erste File

Anmerkungen:

Das Projekt ist (in der VS 2008-Version) noch vollständig ungetestet

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 14 Jahren

hier nochmals ein Update:

Der Dialog sollte jetzt alles unterstützen, was der normale FileDialog auch unterstützt.

Bitte schreibt hier rein, wenn euch Fehler/Abstürze auffallen. Danke.

Bei Fehlern am besten die komplette Exception hier reinposten.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

79 Beiträge seit 2007
vor 14 Jahren

Hallo dr4g0n76,

ich bin gerade am Testen Deines tollen Dialoges (TestTemplateFileDialog.exe).
Dabei fällt mir ein Bug auf:

Im Verzeichnis "Eigene Dateien" habe ich Links (*.lnk) auf häufig genutzte Verzeichnisse angelegt.
Beim Doppelklick "springt" der klassische Dialog in das verlinkte Verzeichnis.
Beim erweiterten Dialog schließt sich beim Doppelklick einfach das Fenster.

Gruß Beauty

79 Beiträge seit 2007
vor 14 Jahren

Das mit den Verweisen wollte ich jetzt näher untersuchen, aber ich habe Probleme beim Debuggen.
Habe das Projekt runtergeladen, entpackt und in Visual Studio 2008 compiliert+gestartet.

Der Dialog ist als Bibliothek eingebunden (scheint so)
und der TestTemplateFileDialog als Startprojekt.

Wenn ich aus dem VS heraus das "Testprogramm" starte und beim Öffnen-Dialog eine Verlinkung anklicke kommt folgende Meldung:
[Bild jetzt als Anhang] (klicke links unten auf Volle Bildgröße)

Es wird auf ein Verzeichnis verwiesen, das es bei mir nicht gibt und nicht die lokale Datei genommen. So kann ich nicht debuggen. Leider weiß ich nicht, wie ich das Problem behebe. Habe das Projekt neu kompiliert. Keine Ahnung, warum der Debugger nicht die lokale Dateien von CustomizableDialogs nimmt, sondern ganz woanders sucht. (vermutlich das ursprüngliche Verzeichnis vom Author)

Hat jemand eine Idee?

Update:
Ich sah gerade am Datei-Datum, daß die Datei ExtensibleDialogs.dll doch nicht neu erstellt wurde (über F6). Habe das jetzt nochmal nachgeholt durch Rechtsklick auf CustomizableDialogs (in der Projektmappe) und Neu erstellen.
Jetzt kann ich debuggen, aber es kommt dennoch die Fehlermeldung mit einem Verzeichnis des Nutzers "morlho" im Pfad.

79 Beiträge seit 2007
vor 14 Jahren

Eine Kleinigkeit fiel mir noch auf:
Im Bin-Verzeichnis vom TestClient ist die Datei _PostBuildEvent.bat.
Darin steht der Befehl

copy "C:\Program Files\Microsoft\ExtensibleDialogsSource\TestClient\\TestClient.exe.manifest" "C:\Program Files\Microsoft\ExtensibleDialogsSource\TestClient\bin\Debug\"

Auf fremden Computern wird das wohl nicht die gewünschte Wirkung haben.
Vielleicht sollte man besser relative Verzeichnis-Angaben machen (falls das bei Postbuild-Ereignissen klappt).
Außerdem wundert mich, daß bei einem Verzeichnis-Trenner ein doppeltes Backslash verwendet wird.

Hat die bat-Datei vermutlich nichts mit dem vorherigen Problem zu tun, aber ich wollte es mal erwähnen.

79 Beiträge seit 2007
vor 14 Jahren

Ich habe noch einen Bug entdeckt, der schon mehrfach zum Programm-Absturz geführt hat (im Test-Client).

Und zwar wenn man schnell den Dialog schließt und erzeut öffnet.
Wenn ich das mehrfach nacheinander mache (öffnen+schließen+öffnen+schließen...) kommt der Fehler.
Im Anhang die Fehlermeldung.
Auch hier weiß ich keinen Grund (kenne mich zu wenig aus), wollte es aber berichten.
Weil es ist ja wünschenswert, daß diese tolle "Erweiterung" stabil läuft und nicht die Programme zum Absturz bringt, die es verwenden.

79 Beiträge seit 2007
vor 14 Jahren

Nun fand ich wieder einen Bug:

Wenn man beim Dialog-Fenster die Größe ändert, dann gibt es unschöne Effekte.

Hier ein Beispiel:

79 Beiträge seit 2007
vor 14 Jahren

Wegen dem Verlinkungs-Problem habe ich die Ursache gefunden:

In Datei OpenFileDialog.cs
in Methode SetDialogFlags()
steht u.a. folgendes:

if (!this.DereferenceLinks)
    m_OpenFileName.Flags |= OpenFileNameFlags.NoDereferenceLinks;

this.DereferenceLinks steht standardmäßig auf false.
Wenn man es auf true setzt, klappt das mit den Verlinkungen (zu Dateien/Verzeichnissen), wenn man auf eine *.lnk-Datei klickt.

Hinweis:
this bezieht sich auf die Klasse OpenFileDialog in Datei OpenFileDialog.cs.

Die Eigenschaft DereferenceLinks liest den Wert aus m_bDereferenceLinks.

Dieses ist fest definiert in der Zeile

private bool m_bDereferenceLinks = false;

Beim standardmäßigen OpenFileDialog ist das Flag auf true gesetzt, was im Normalfall sinnvoll ist.
Beim erweiterten OpenFileDialog würde ich das auch standardmäßig auf true setzen.

Generell würde ich die selben Flags setzen, wie standardmäßig auch beim "normalen" Öffnen-Dialog sind. (Ich weiß nicht, ob die anderen Flags diesbezüglich "richtig" definier sind.)

79 Beiträge seit 2007
vor 14 Jahren

Ich habe mir heute nochmal das Projekt angesehen.

Wegen dem Verlinkungs-Problem habe ich nicht nur das default-Flag geändert, sondern eine kleine Methode geschrieben, die die default-Flags so setzt, wie sie beim "originalen" Öffnen-Dialog sind:

        private void SetDefaultFlags()
        {
            System.Windows.Forms.OpenFileDialog ofd = new System.Windows.Forms.OpenFileDialog();
            m_bCheckFileExists  = ofd.CheckFileExists;
            m_bMultiSelect      = ofd.Multiselect; ;
            m_bReadOnlyChecked  = ofd.ReadOnlyChecked;
            m_bCheckPathExists  = ofd.CheckPathExists;
            m_bDereferenceLinks = ofd.DereferenceLinks;
            m_bRestoreDirectory = ofd.RestoreDirectory;
		    m_bShowHelp         = ofd.ShowHelp;
            m_bShowReadOnly     = ofd.ShowReadOnly;
            m_bSupportMultiDottedExtensions = ofd.SupportMultiDottedExtensions;
            m_bValidateNames    = ofd.ValidateNames;
            ofd.Dispose();
        }

Diese wird am Anfang von InitFileDialog() aufgerufen.
So können wir sicher sein, daß die Flags dem Original entsprechen (auch falls sich das in Zukunft ändern sollte, was ich weniger denke).
Im Anhang ist die modifizierte Datei.

Wegen dem Problem mit der Größenänderung habe ich folgendes rausgefunden.
Die Größe der erweiterten Fläche (im Beispiel das skalierte Bild) wird durch die Methode FindAndResizePanels() berechnet.
Allerdings wird sie scheinbar nur aufgerufen, wenn man der Dialog geöffnet wird oder wenn man eine Datei (bzw.) Verzeichnis markiert.
Was fehlt, ist ein Aufruf dieser Methode sobald der Nutzer die Größe des Dialog-Fensters ändert.
Wo man den zusätzlichen Methoden-Aufruf einbauen muß, habe ich leider nicht rausgefunden. Möglicherweise in der Methode MyHookProc().

Beim experimentieren fiel mir noch ein möglicherweise unerwünschter Effekt auf.
Wenn man die Größe des Öffnen-Dialoges ändert (und anschließend eine Datei bzw. ein Verzeichnis markiert), dann ändert sich auch die Größe des eingebetteten Panels (erstellt durch Nutzer).
Die Größe des Panels wird immer so gesetzt, daß es halb so breit ist, wie das Dialog-Fenster.

Für eine Bild-Vorschau kann das praktisch sein (durch automatische Skalierung).
Aber wenn das Panel eine feste Größe haben (und behalten) soll, dann geht das nicht.
Sinnvoll wäre das für eine Vorschaubild-Anzeige mit fester Größe oder zum Anzeigen von anderen Informationen (z.B. Text, Icons).

Daher mein Vorschlag:
Den Öffnen-Dialog um Eigenschaften erweitern, mit denen man das gewünschte Verhalten einstellen kann.
z.B.

public Boolean OpenFileDialog.FixedPanelSize
// wenn true, dann bleibt Panel-Größe unverändert

public Byte OpenFileDialog.PercentualPanelWidth   
// z.B. 50 für halbe Fläche, 33 für ein Drittel der Fläche;
// gültiger Bereich: 1 .. 99

public Int32 OpenFileDialog.DialogSize
// die Größe des gesamten Dialog-Fensters vorgeben
// standardmäßig ist das sehr klein

public event OpenFileDialog.SizeChanged
// eine Möglichkeit, daß der Nutzer sein Panel entsprechend anpassen kann,
// wenn sich die Größe des Dialog-Fensters ändert

Sinnvoll fände ich auch eine Option, mit der man den Panel auch unter der Dateiauswahl-Box positionieren kann. (z.B. für Ausgabe langer Textzeilen)

public enum PanelPositions = { Bottom, Right }   // mögliche Werte (ev. auch oben/links)
public PanelPositions OpenFileDialog.PanelPosition   // Eigenschaft änderbar

Ich vermute, das verarbeiten dieser Einstellungen müßte in der Methode FindAndResizePanels() eingebaut werden.

Manchmal bekomme ich beim dem beigelegten Beispielprogramm eine AccessViolationException.
Gerade eben war das beim Markieren einer Datei.
Der Debugger zeigt die Ausnahme in der Zeile bool bShow = openFileDialog.Show();
Leider zeigt er nicht, so genau in der Dialog-Klasse.
Hier einige Details - eventuell helfen die bei der Fehlersuche.

Message:
"Es wurde versucht, im geschützten Speicher zu lesen oder zu schreiben. Dies ist häufig ein Hinweis darauf, dass anderer Speicher beschädigt ist."

Source:
"ExtensibleDialogs"

Trace:
   bei CustomizableDialogs.NativeMethods.GetOpenFileName(OpenFileName& ofn)
   bei CustomizableDialogs.OpenFileDialog.Show() in c:\OgreSDK\projekte\CustomizableDialogs (Datei öffnen mit Vorschau)\CustomizableDialogs\ExtensibleDialogs\OpenFileDialog.cs:Zeile 315.
   bei TestTemplateFileDialog.MainForm.buttonChooseFile_Click(Object sender, EventArgs e) in c:\OgreSDK\projekte\CustomizableDialogs (Datei öffnen mit Vorschau)\CustomizableDialogs\TestClient\MainForm.cs:Zeile 124.
   bei System.Windows.Forms.Control.OnClick(EventArgs e)
   bei System.Windows.Forms.Button.OnClick(EventArgs e)
   bei System.Windows.Forms.Button.WndProc(Message& m)
   bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   bei System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   bei System.Windows.Forms.UnsafeNativeMethods.SendMessage(HandleRef hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   bei System.Windows.Forms.Control.SendMessage(Int32 msg, IntPtr wparam, IntPtr lparam)
   bei System.Windows.Forms.Control.ReflectMessageInternal(IntPtr hWnd, Message& m)
   bei System.Windows.Forms.Control.WmCommand(Message& m)
   bei System.Windows.Forms.Control.WndProc(Message& m)
   bei System.Windows.Forms.ScrollableControl.WndProc(Message& m)
   bei System.Windows.Forms.ContainerControl.WndProc(Message& m)
   bei System.Windows.Forms.Form.WndProc(Message& m)
   bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   bei System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   bei System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   bei System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
   bei System.Windows.Forms.Control.DefWndProc(Message& m)
   bei System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   bei System.Windows.Forms.Control.WndProc(Message& m)
   bei System.Windows.Forms.ButtonBase.WndProc(Message& m)
   bei System.Windows.Forms.Button.WndProc(Message& m)
   bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   bei System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   bei System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
   bei System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
   bei System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   bei System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   bei System.Windows.Forms.Application.Run(Form mainForm)
   bei TestTemplateFileDialog.MainForm.Main() in c:\OgreSDK\projekte\CustomizableDialogs (Datei öffnen mit Vorschau)\CustomizableDialogs\TestClient\MainForm.cs:Zeile 97.
   bei System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   bei System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
   bei System.Runtime.Hosting.ManifestRunner.Run(Boolean checkAptModel)
   bei System.Runtime.Hosting.ManifestRunner.ExecuteAsAssembly()
   bei System.Runtime.Hosting.ApplicationActivator.CreateInstance(ActivationContext activationContext, String[] activationCustomData)
   bei System.Runtime.Hosting.ApplicationActivator.CreateInstance(ActivationContext activationContext)
   bei System.Activator.CreateInstance(ActivationContext activationContext)
   bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssemblyDebugInZone()
   bei System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   bei System.Threading.ThreadHelper.ThreadStart()

Die hier vorgestellte Klasse heißt OpenFileDialog, also genauso wie das "Original".
Um Verwechslungen zu vermeiden würde ich vorschlagen, die Klasse umzubenennen. Vielleicht zu ExtOpenFileDialog oder OpenFileDialogExtended

Natürlich kann man sich selber die Klasse so anpassen, wie man sie gerne hätte. Dennoch denke ich, daß es sinnvoll wäre, die genannten Optionen einzubauen. Dann ist sie flexibler einsetzbar und der Nutzer (Programmierer) muß nicht erst im Quellcode nach Stellen suchen, wo man das ändern kann.

Anmerkung: Ich möcht dem Autor nicht reinreden, sondern mache nur Vorschläge 😉

79 Beiträge seit 2007
vor 14 Jahren

Update: Dieses Problem ist gelöst.
Deshalb ist der Text jetzt in grauer Farbe.

Nun eine praktische Frage:

Ich habe ein Test-Programm erstellt, bei dem im Öffnen-Dialog ein Panel mit mehreren Elementen angezeigt werden soll (derzeit 16 Stück).

Da eine GUI-Gestaltung per Quellcode unbequem ist, habe ich in Visual Studio ein neues Formular erstellt und in diesem mein Panel grafisch per Designer gestaltet.
Die Einbindung funktioniert, indem ich das Formular erstelle und die Panel-Referenz an den Öffnen-Dialog übergebe:

PanelForm panForm = new PanelForm();
panForm.Show();
Panel panel = panForm.cvPanel;

String filter = "All files (*.*)|*.*";
CustomizableDialogs.OpenFileDialog openFileDialog = new CustomizableDialogs.OpenFileDialog("*", "", filter, panel);
openFileDialog.SelectionChanged += new CustomizableDialogs.OpenFileDialog.SelectionChangedHandler(ofd_SelectionChanged);
bool bShow = openFileDialog.Show();

panForm.Dispose();

Grundsätzlich klappt das. Das Problem ist aber, daß ich das "Hilfs-Formular" nicht nur erstellen, sondern auch anzeigen muß. Ansonsten enthält die Panel-Referenz keinen Inhalt.

Erfolglos war auch dieser Versuch (zeigt leeres Panel im Öffnen-Dialog):

PanelForm panForm = new PanelForm();
panForm.CreateControl();
panForm.cvPanel.CreateControl();

Wenn ich es mit einem versteckten Formular versuche, dann wird ein leeres Fenster angezeigt.

PanelForm panForm = new PanelForm();
panForm.Visible = false;
panForm.Show();

Hat jemand eine Idee dazu?

Anmerkung:
Eigentlich kann man das Panel mit dem Designer von Visual Studio grafisch erstellen und einfach den generierten Quellcode kopieren. Allerdings ist das nicht sehr nutzerfreundlich, wenn man später Änderungen am Layout machen möchte.

Anmerkung 2:
Meine Frage weicht ein kleines bischen vom eigentlichen Thema ab, aber ich habe bewußt keinen neues Thema aufgemacht. Es kann nämlich durchaus sein, daß jemand den erweiterten Öffnen-Dialog benutzt und das selbe Problem hat mit der Panel-Erstellung. Dann finde ich sinnvoll, wenn hier gleich die Lösung steht.

79 Beiträge seit 2007
vor 14 Jahren

So, nach einiger Suche fand ich jetzt doch die Lösung für mein Panel-Problem.
Für das Formular, in dem ich das Panel definiert habe, muß ich nur ein kleines Attribut ändern.
Und zwar .TopLevel = false.
Dann erscheint kein leeres Formular mehr.

Ein Panel, das man im "Designer" vom Visual Studio erstellt hat, kann man also so einbinden:


// Panel extrahieren, welches in einer (unsichtbaren) Form eingebettet ist
PanelForm panForm = new PanelForm();
panForm.TopLevel = false;
panForm.Show();
Panel panel = panForm.cvPanel;

// Öffnen-Dialog
String filter = "All files (*.*)|*.*";
CustomizableDialogs.OpenFileDialog openFileDialog = new CustomizableDialogs.OpenFileDialog("*", "", filter, panel);
openFileDialog.SelectionChanged += new CustomizableDialogs.OpenFileDialog.SelectionChangedHandler(ofd_SelectionChanged);
bool bShow = openFileDialog.Show();

// unsichtbare Form (ink. Panel) wieder löschen
panForm.Dispose();

Leider gab es beim Schließen des Dialog-Fensters wieder mehrfach eine Ausnahme an dieser Stelle in Datei OpenFileDialog.cs:

public new void Dispose( bool disposing )
{
	if( disposing )
	{
t		GC.SuppressFinalize( this );
	}

	Marshal.FreeCoTaskMem( this.m_ptrFileNameBuffer );  // HIER AUSNAHME AccessViolationException
	Marshal.FreeCoTaskMem( this.m_ptrFileTitleBuffer );
	Marshal.FreeCoTaskMem( this.m_ptrIpTemplate );
}

Und es gab eine Ausnahme hier:

public bool Show()
{
        SetDialogFlags();

        return NativeMethods.GetOpenFileName(ref this.m_OpenFileName);  // HIER AUSNAHME AccessViolationException
}

Schade, daß der Öffnen-Dialog so instabil zu sein scheint.
Als Notlösung könnte man einen try/catch Block um die betreffenden Stellen machen.
Besser wäre aber, die Ursache(n) zu finden und zu beheben. Allerdings habe ich bei diesen Ausnahmen keine Ahnung, woran das liegen kann.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 14 Jahren

@Beauty: Wenn ich heute noch dazu komme, veröffentliche ich hier eine neuere Version.
Hoffe, dass diese dann stabiler ist. Zuhause hab ich momentan leider keine Zugriffsmöglichkeit und in der Firma ist das immer etwas schwierig.

Gucke jetzt gleich nach.

Sorry, dass es so lange gedauert hat.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

79 Beiträge seit 2007
vor 14 Jahren

Nun habe ich ein weiteres Problem festgestellt.
Und zwar bezogen auf die Eigenschaft Filter.

Bei Dateityp steht nur der Filter-Ausdruck, der als String definiert ist.
In der weiteren Auswahl sind "komische" Zeichen zu sehen.

Beispiel:

filter = "All files (*.*)|*.*";

(Bild ist im Anhang)

Dabei fällt mir noch eine Kleinigkeit auf:
Beim "normalen" Dialog steht links unten Dateiname und Dateityp.
Im erweiterten Dialog steht statt dessen Objektname und Objekttyp.
Das ist zwar nichts schlimmes, aber ich wollte das mal erwähnen.

Ändern konnte ich das nicht, da die Strings Objektname und Objekttyp nirgendwo im Quelltext zu finden sind.
So wie ich verstanden habe, tut die Klasse OpenFileDialog nur den normalen Dialog erweitern. Warum dann die Strings anders heißen, ist mir unklar.
In den Screenshots vom ersten Beitrag ist der Fehler scheinbar nicht vorhanden.

79 Beiträge seit 2007
vor 14 Jahren

Mir ist wieder etwas aufgefallen. Zum Glück aber kein Fehler (-;
Beim Screenshot im ersten Beitrag belegt das eingefügte Panel die komplette rechte Seite. Bei der CustomDialogs-Klasse ist die Höhe reduziert, da sich das Panel(?) mit Dateiname und Dateityp bis ganz rechts streckt.
In Worten läßt sich das nicht so gut beschreiben. Deshalb habe ich ein Bild erzeugt und angehangen. Die roten und blauen Rahmen markieren dabei die Panel-Flächen.

Für meine Software fände ich gut, wenn ich die ganze rechte Hälfte zur Verfügung habe. Das ist vermutlich von Nutzer zu Nutzer verschieden.
Daher wäre eine Option dafür ganz praktisch (und nutzerfreundlich).
Beispielsweise durch folgende Eigenschaft:

public VerticalPanelScaleProp OpenFileDialog.VerticalPanelScale   // Eigenschaft änderbar
public enum VerticalPanelScaleProp = { MaxHeight, AboveFilenamePanel }  // mögliche Werte

Wie man das im Quelltext ändert, habe ich nicht so recht rausgefunden.
Vielleicht in Methode MyHookProc(...) an folgender Stelle? Wenn ja, wie?


case WindowMessage.InitDialog:
{
	IntPtr ptrHWndParent = NativeMethods.GetParent( ptrHWnd );
            NativeMethods.SetParent(this.m_UserControl.Handle, ptrHWndParent);
79 Beiträge seit 2007
vor 14 Jahren

Wegen dem Filter-Problem habe ich etwas eigenartiges festgestellt.

Bei dem Test-Programm funktioniert die Auswahl richtig.
Der Filter-String ist darin so definiert:

"Picture files (*.JPG;*.GIF;*.PNG)\0*.jpg;*.gif;*.png\0\0"

Laut MSDN-Doku muß man aber ein Pipe-Symol als Trennzeichen verwenden statt \0 wie bei Dir.
Was mich auch wundert ist, daß am Ende Deines Filters nochmal "\0\0" steht.

So steht es als Beispiel in der Doku:

filter = "All files (*.*)|*.*";

Im Code habe ich nachgesehen, was mit der Filter-Variable passiert.
Sie wird nur in einer Zeile verwendet (Datei NativeMethods.cs, Methode InitFileDialog(...)):

m_OpenFileName.lpstrFilter = Marshal.StringToCoTaskMemUni(_sFilter);

Das Ergebnis wird in m_OpenFileName.lpstrFilter gespeichert (Typ IntPtr), aber nicht mehr im Quelltext der Klasse benutzt.

Von Marshaling habe ich keine Ahnung.
Kann also selber nicht rausfinden, wo der Fehler liegt.

Was mir aber auffiel ist, daß im Stuct OpenFileName die ähnlich aussehende Variable lpstrCustomFilter definiert ist, aber nie verwendet wird. Ob das was damit zu tun hat, weiß ich nicht.

Eine schmutzige Lösung wäre, intern die Filter-Eingabe mit einem regulären Ausdruck zu modifizieren. Also Pipes zu "\0" umwandeln und ggf. "\0\0" anzuhängen.
Besser wäre es natürlich, die echte Ursache zu finden und zu beheben.

79 Beiträge seit 2007
vor 14 Jahren
bool bShow = openFileDialog.Show();

Was genau bedeutet der Rückgabewert?
Daß eine Datei (oder mehrere) gewählt wurden und dann OK gelickt wurde?
(entsprechend DialogResult.OK)

Aus dem Code werde ich nicht schlau.
Ein XML-Kommentar dazu wäre praktisch. (da kann ich dann einfügen)

Im "originalen" Dialog gibt es keine Methode namens Show().
Statt dessen wird es da so gemacht:

DialogResult result = ofDialog.ShowDialog();
// oder in dieser Weise:
if (ofd.ShowDialog() == DialogResult.OK)

Der Rückgabewert ist ein enum.

79 Beiträge seit 2007
vor 14 Jahren

Leider habe ich wieder ein Problem festgestellt.

Die Angabe der Eigenschaft InitialDirectory wird ignoriert.
Habe es mit einer relativen, aber auch mit einer globalen Pfad-Angabe versucht.
Diese sind auch gültig - habe das getestet.

ScenePath = "..\\..\\Test-Dateien"; // relativer Pfad
openFileDialog.InitialDirectory = ScenePath;
Boolean test = Directory.Exists(openFileDialog.InitialDirectory); // Verzeichnis vorhanden? --> ja

Im Quelltext habe ich gesehen, daß diese Angabe zwar in der Klasse gespeichert wird, aber ansonsten nirgendwo verwendet wird.

Statt dessen ist das Start-Verzeichnis des Dialoges immer "Eigene Bilder".
Ob das irgendwo im Quellcode definiert wurde, konnte ich nicht rausfinden. Wäre aber möglich.

Die Behebung dieses Problems halte ich für sehr wichtig.

4.931 Beiträge seit 2008
vor 14 Jahren

Soviel ich weiß, wird ab Windows XP (evtl. auch schon ab Windows 2000) das IntialDirectory ignoriert und stattdessen je nach Programm sich der letzte Pfad gemerkt (und beim ersten Aufruf dann "Eigene Dateien" benutzt). Diese Werte stehen dann in der Registry unter "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedMRU".

Und unter "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSaveMRU" stehen dann je nach Dateityp die zuletzt ausgewählten Dateien (in der Filename-Combobox).

Edit: die Forumsanzeige macht aus irgendeinem Grund leider einen Zeilenumbruch vor der 32. Warum auch immer?

79 Beiträge seit 2007
vor 14 Jahren

Ich habe nochmal in der Doku von Visual Studio 2008 nachgesehen.

Ruft das Ausgangsverzeichnis ab, das im Dateidialogfeld angezeigt wird, oder legt dieses fest.

Für die Eigenschaft FileDialog.InitialDirectory steht kein Hinweis darauf, daß die auf bestimmte Windows-Versionen beschränkt wäre. Im Beispielcode der Doku wird "C:\" vorgegeben.
Ich halte für sehr sinnvoll, daß man das Start-Verzeichnis angeben kann.
Beim "normalen" Dialog verwende ich das auch und es funktioniert.

Bei der Registrierung habe ich mal reingesehen.
Es ist interessant, was bei OpenSaveMRU alles für Verzeichnisse gespeichert sind. Teilweise uralte Pfade g

P.S.
Schön zu sehen, daß hier auch noch andere mitlesen und Anregungen geben 🙂

79 Beiträge seit 2007
vor 14 Jahren

In der Callback-Funktion OpenFileDialog.SelectionChanged kann der Nutzer eigenen Code einfügen (z.B. Vorschaubild aktualisieren).
Wenn in diesem Code eine unbehandelte Ausnahme entsteht, dann wird sie durch diesen Code in Datei OpenFileDialog.cs abgefangen:


public bool Show()
{
        SetDialogFlags();

        return ShowInternal();
}

private bool ShowInternal()
{
    bool bSuccess = false;
    try
    {
        bSuccess = NativeMethods.GetOpenFileName(ref this.m_OpenFileName);
        return bSuccess;
    }
    catch (Exception ex)  // HIER abgefangen
    {
        return bSuccess;
    }
}

Eigentlich eine gute Sache.
Mich hat das aber des öfteren irretiert.
Wenn ich bestimmte Dateien angeklickt habe, dann ist der Dialog einfach zugegangen und ich habe nicht verstanden warum.
Erst beim zeilenweise Debuggen habe ich die Ursache gefunden. (das catch() in Datei OpenFileDialog)

Bei mir war es u.a. ein Fehler beim XML parsen.
Das sollte ich natürlich selbst abfangen (per try/catch), aber bei meinen kleinen Dialog-Experimenten habe ich das nicht getan.
Gut wäre gewesen, wenn ich dann als Programmierer gesehen hätte, daß es eine Ausnahme gab. Am Besten an der Stelle, wo sie aufgetreten ist.

Das brachte mich auf folgende Ideen:

1.
Speicherung der Ausnahme (Variable ex) in einer Klassen-Variable, die man abfragen kann, wenn OpenFileDialog.Show() ein false zurückliefert.

Exception OpenFileDialog.ExceptionOfShow  // Eigenschaft

2.
Die Möglichkeit einzustellen, ob der oben zitierte try/catch-Block verwendet wird.
Etwa über die Eigenschaft

Boolean OpenFileDialog.CatchExceptionsOfShow

Die Eigenschaft wäre standardmäßig auf true.

Hier der erweiterte Code, wie ich das machen würde:

private bool ShowInternal()
{
    Boolean m_catchExceptionsOfShow = false;  // TEST
    bool bSuccess = false;

    if (m_catchExceptionsOfShow)  // check mode
    {
        try
        {
            bSuccess = NativeMethods.GetOpenFileName(ref this.m_OpenFileName);
            return bSuccess;
        }
        catch (Exception ex)
        {
            return bSuccess;
        }
    }
    else  // don't catch exceptions
    {
        return NativeMethods.GetOpenFileName(ref this.m_OpenFileName);
    }
}

Alternativ könnte man es auch die Möglichkeit einbauen, daß die Ausnahmen nur im Release-Modus abgefangen werden und bei Debug-Modus "durchlassen".
Einstellen könnte man das so:

enum ExceptionOfShowModes = { Catch, NoCatch, CatchInDebugMode }
ExceptionOfShowModes OpenFileDialog.CatchExceptionsOfShow

Irgendwo habe ich gelesen, daß man im Quellcode abfragen kann, ob das laufende Programm im Debug oder Release-Modus compiliert wurde.

Was haltet Ihr davon, wenn ich diese Erweiterungen in die Veröffentlichung einbaue?

79 Beiträge seit 2007
vor 14 Jahren

Aha, durch meine zuletzt genannte Erweiterung habe ich wieder ein Problem entdeckt.

Es tritt auf, wenn man auf eine Verlinkung doppelklickt (*.lnk-Datei) und die Dialog-Eigenschaft DereferenceLinks auf true steht.
Dann soll der Dialog in das Ziel-Verzeichnis springen und dessen Inhalt anzeigen.
Dies funktioniert grundsätzlich.
Das Problem tritt erst dann auf, wenn man zuvor eine andere Datei angeklickt hat und danach erst die Verlinkung doppelklickt.

Dabei wird nämlich die Callback-Funktion OpenFileDialog.SelectionChanged aufgerufen und ein ungütiger Pfad als Argument übergeben.
Dieser ist zusammengesetzt aus dem Zielpfad der Verlinkung UND dem Dateinamen der zuvor ausgewählten Datei.

Das tritt nur selten auf und läßt sich leicht beheben, aber wenn man das nicht weiß und beachtet, dann kann das zu einer Ausnahme führen.
Im Normalfall wird die Ausnahme abgefangen (siehe try/catch-Block meises letzten Beitrags).
Für den Nutzer ist es aber irretierend, weil sich der Dialog einfach schließt.

Am Anfang der Callback-Funktion OpenFileDialog.SelectionChanged ist es also ganz wichtig zu prüfen, ob der Pfad valide ist.


// Callback-Funktion für OpenFileDialog.SelectionChanged
private void ofd_SelectionChanged( string _sPath )  
{
    if (!File.Exists(_sPath) && !Directory.Exists(_sPath))
        // abort if no valide path 
        // (this happens in seldom special cases) 
        return;

    // ...
}

Leider ist (nach meinem Kenntnisstand) keine Möglichkeit vorhanden, die Pfad-Gültigkeit innerhalb der Klasse OpenFileDialog zu prüfen, bevor die Callback-Funktion aufgerufen wird. Die Callback-Funktion scheint nämlich direkt an einen externen Aufruf geknüpft zu sein.

[DllImport("ComDlg32.dll", CharSet = CharSet.Unicode)]
internal static extern bool GetOpenFileName( ref OpenFileName ofn );
79 Beiträge seit 2007
vor 14 Jahren

Jetzt habe ich eine reproduzierbare Ursache gefunden, eine AccessViolationException zur Folge hat.

Sie tritt auf, wenn man im Feld "..." des Dialoges eine Datei inklusive Pfad einfügt und das aktuelle Verzeichnis nicht dem Pfad entspricht, die in der Datei angegeben ist (und anschließend OK geklickt wird).

Beispiel:
aktuelles Verzeichnis = "C:\Eigene Bilder";
Datei mit Pfad = "++C:\pfad\woanders++datei.txt"

Wenn der Datei-Pfad auch dem aktuellen Pfad entspricht, gibt es keine Probleme.

Die Ausnahme tritt in der Datei OpenFileDialog.cs auf.


private bool ShowInternal()
{
    Boolean m_catchExceptionsOfShow = false;  // TEST
    bool bSuccess = false;

    if (m_catchExceptionsOfShow)
    {
        try
        {
            bSuccess = NativeMethods.GetOpenFileName(ref this.m_OpenFileName);
            return bSuccess;
        }
        catch (Exception ex)
        {
            return bSuccess;
        }
    }
    else  // don't catch exceptions
    {
        return NativeMethods.GetOpenFileName(ref this.m_OpenFileName);  // HIER AccessViolationException
    }
}

Wo die programmiertechnische Ursache nun liegt, weiß ich nicht. Doch es würde mich freuen, wenn das Problem behoben wird.

P.S.
Auch dieses Problem konnte ich nur finden, weil ich in der modifizierten Methode ShowInternal() keine Ausnahmen kommentarlos abfange. (So wie in der ursprünglichen Version)

Ergänzung:
Die Variable m_OpenFileName kann ich vor dem Aufruf von GetOpenFileName(...) nicht einfach manipulieren, um den Pfad zu korrigieren.
Sie ist ein Struct, das keinen String enthält. Statt dessen sind nur die Datentypen Int32, IntPtr und ein OfnHookProc enthalten.

79 Beiträge seit 2007
vor 14 Jahren

Ich verstehe das ganze nicht.

Jetzt nach dem Abendbrot habe ich wieder experimentiert.
Das zuletzt genannte Problem kann ich garnicht mehr reproduzieren.
Vorhin kam die Ausnahme ständig, wenn der Pfad der Datei (im Feld Objektname) anders war.

Meine Vermutung war, daß das vielleicht nur bei einem anderen Laufwerksbuchstaben auftritt. Aber auch da bekomme ich keine Ausnahme mehr. Es funktioniert korrekt.

Dennoch bekomme ich immer mal wieder eine AccessViolationException bei der im letzten Beitrag genannten Stelle.
Und zwar, wenn ich eine Datei gewählt habe und dann auf OK klicke.
Mal klappt es 2x problemlos, mal klappt es 10x problemlos. Dann kommt die Ausnahme.

An meinem eigenen Code liegt es scheinbar nicht (z.B. in Callback-Funktion).
Bei einem Versuch habe ich ihn auskommentiert, bei einem anderen Versuch habe ich das Originale Beispiel-Programm verwendet (mit der Modifikation, daß Ausnahmen nicht abgefangen werden).

Noch ein neues Problem fiel mir auf:

Wenn das Auswählen einer Datei erfolgreich war, dann kann man den Dateiname (inkl. Pfad) über die Eigenschaft OpenFileDialog.SelectedPath abfragen.

In seltenen Fällen liefert der Wert "Müll" zurück.

1)
Es kam vor, daß der Pfad abgeschnitten war
z.B. (G:\OgreS

Es kam vor, daß kryptische Zeichen enthalten waren.
Entweder nach einem abgeschnittenen Pfad (siehe Anhang) oder mitten drin.

Das war aber nicht abhängig von der gewählten Datei.
Denn die Dateien, bei der die Fehler auftraten, ließen sich bei mehreren Wiederholungen problemlos ermitteln. (SelectedPath lieferte dann den richtigen Wert)

Der Wert wird intern über diesen Code ermittelt (Datei OpenFileDialog.cs):


public string SelectedPath
{
	get { return Marshal.PtrToStringUni( m_ptrFileNameBuffer ); }
}
79 Beiträge seit 2007
vor 14 Jahren

Gibt es auch andere Leute, die dieses Projekt verwenden?

Wenn ja, wie sind die Erfahrungen? Gibt/Gab es da auch Probleme?

(Ausnahmen sind im Original-Code aber nicht zu erkennen, weil ein try/catch-Block alles abfängt. Bei einer Ausnahme schließt sich einfach das Dialog-Fenter.)

Langsam fange ich an zu verzweifeln ...

79 Beiträge seit 2007
vor 14 Jahren

Ich habe mir mal angesehen, was es auf codeproject.com für mögliche C#-Alternativen für diesen Dialog gibt.

Customizing OpenFileDialog in .NET
Das Projekt wurde auch schon oben genannt. Leider hat es der Autor nicht mehr aktualisiert.
Verbesserungen und Bugfixes sind irgendwo in den vielen Kommentaren versteckt. (z.B. Unterstützung für neuere Windows-Versionen)
Letztes Update: 2006

Extend OpenFileDialog and SaveFileDialog the easy way
Ähnliche Funktionalität. Laut Beschreibung basiert es auf dem obigen Projekt Customizing OpenFileDialog in .NET, wurde aber erweitert und aktualisiert. Läuft auch auf Vista und Win7.
Das ist meine Empfehlung (Details dazu unten im übernächsten Beitrag).
Letztes Update: 2009

Extend OpenFileDialog and SaveFileDialog Using WPF
Ähnliches für WPF-Anwendungen
Letztes Update 2009

Full implementation of IShellBrowser
So wie es aussieht, hat jemand den Dialog (komplett?) nachprogrammiert und man kann den Dialog völlig frei gestalten (inkl. der "Standard-Elemente").
Letztes Update: 2009

Remote Control of Microsoft FileDialog Class (OpenFileDialog)
Beschreibt, wie man den "originalen" Dialog beeinflussen kann, um per Quellcode eine bestimmte Spalte zu sortieren (z.B. nach "Datum").
Letztes Update: 2009

FileDialogExtender
Hier geht es auch um die Erweiterung des originalen Dialogs, aber ich konnte auf den ersten Blick nicht erkennen, in welchem Umfang.
Letztes Update: 2004

Das ist nicht als Konkurrenz gedacht, sondern nur zur Information.
Vielleicht ist es ja brauchbar für andere Leute, die auch Probleme mit dem Projekt aus diesem Thread haben.
Eventuell findet man dort auch Problem-Lösungen und Anregungen.

Ergänzung:

Der Autor dieses Projekters (dr4g0n76) hat den Datei-Öffnen-Dialog komplett nachprogrammiert.
Siehe Thema CustomFileDialog

Der MSDN-Artikel Extensibility of Common Dialogs beschreibt die Grundlagen (Hooking etc.), wie man den Öffnen-Dialog erweitern kann.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 14 Jahren

@Beauty:

Bitte sieh mal in deinen Posteingang bei Mycsharp. Danke.

Aus persönlichen - unglücklichen Umständen bzw. Gründen - kann ich leider momentan nichts nichts an der Situation ändern, aber so bald ich Zeit habe, werde ich den Artikel mit all Deinen Vorschlägen berücksichtigen.

Ich habe momentan fast keine Zeit mehr...

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

79 Beiträge seit 2007
vor 14 Jahren

So, ich habe jetzt das oben genannte codeproject Extend OpenFileDialog and SaveFileDialog the easy way ausprobiert und meine Erweiterung dort eingebaut.

Auch dort mußte ich 2 Bugs fixen, die Ausnahmen geworfen haben g

Aber nun funktioniert es prima =)

Auch die Handhabung finde ich gut.
Man fügt das Projekt als UserControl hinzu und kann dann die GUI mit dem Designer von Visual Studio bequem gestalten.
Am Anfang wußte ich nicht wie man das UserControl hinzufügt. Die deutsche Übersetzung heißt Benutzersteuerelement. Wenn man das nicht weiß, muß man erst einmal eine Weile suchen (so wie ich).

So geht es:
Datei FileDlgExtenders.dll ins eigene Projekt kopieren und Referenz darauf hinzufügen.
Jetzt erst einmal neu kompilieren.

Dan geht man in den Projekt-Explorer und rechtsklickt auf das Projekt.
Dann Hinzufügen und dann Neues Element...
Links die Kategorie Windows Forms auswählen und rechts Geerbtes Benutzersteuerelement auswählen.
Unten Dateiname angeben und Hinzufügen klicken.
(Dann kommen bei mir 2 Warnungen - können ignoriert werden)
Im nächten Fenster auf Durchsuchen... klicken.
Dann die Datei FileDlgExtenders.dll referenzieren (die man vorher ins eigene Projekt kopiert).
Es erscheint anschließend in einer Liste, man wählt sie aus (anklicken) und klickt auf OK
Jetzt sollte das UserControl erscheinen.

Diverse Einstellungen kann man über die Eigenschaften machen - so wie bei einer Form.

Auf Grund (für mich) notwendiger Bugfixes habe ich den ganzen Quellcode aus dem Verzeichnis FileDlgExtenders in mein Projekt kopiert (und die Referenz auf die dll-Datei anschließend gelöscht).
So ist es möglich, das Projekt bequem zu debuggen.

Ich schreibe das so ausführlich, weil es vielleicht jemand gebrauchen könnte.