Laden...

Allgemeiner Fehler in der GDI+

Erstellt von theSoulT vor 5 Jahren Letzter Beitrag vor 5 Jahren 20.046 Views
T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 5 Jahren
Allgemeiner Fehler in der GDI+

Guten Abend zusammen,

ich habe leider mit meinem Code ein kleines Problem und komme nicht weiter.
Ich habe eine Userform mit mehreren Buttons. Dazu eine PictureBox
Das Bild wird wie folgend hochgeladen:

private void btnLoadPicture_Click(object sender, EventArgs e)
        {
            OpenFileDialog OF = new OpenFileDialog();
            OF.Title = "Bitte Bild wählen...";
            OF.Multiselect = false;
            OF.Filter = "Bilder|*.jpeg;*.jpg;*.png;*.bmp|JPG-Bilder|*.jpeg;*jpg|PNG-Bilder|*.png|BMP-Bilder|*.bmp";
            DialogResult DR = OF.ShowDialog();

            if (DR == DialogResult.OK)
            {
                iContactPicture = Image.FromFile(OF.FileName);
                pictureContact.Image = iContactPicture;
            }
            else
            {
                pictureContact.Image = global::Personaldaten.Properties.Resources.Who_is_it;
            }
        }

Drücke ich auf den Button Save wird das Bild in einen String umgewandelt und zusammen mit den Infos aus den Textboxen in einer Datei gespeichert:

 private string ImageToString(Image img, ImageFormat imgFormat)
        {
            string sImg;
            MemoryStream MS = new MemoryStream();
            img.Save(MS, imgFormat); //Hier tritt der Fehler auf!!!!
            sImg = Convert.ToBase64String(MS.ToArray());
            MS.Close();
            return sImg;
        }

Über einen Öffnen Button kann ich jetzt die Inhalte der Textboxen und der PictureBox wieder mit den Inhalten der Datei befüllen. Drücke ich jetzt direkt wieder auf Speichern (weil ich irgendwelche Textboxen geändert habe) bekomme ich den im Titel genannten Fehler. Wenn ich aber erneut ein Bild auswähle bekomme ich diesen Fehler nicht.

Ich hoffe ihr könnt mir weiterhelfen 😃

Vielen Dank schon mal.

16.807 Beiträge seit 2008
vor 5 Jahren

Alle nicht verwalteten Ressourcen in .NET müssen manuell disposed werden (siehe Basics).
Gleicher Fall wie im anderen Thread; man muss nur Lösung von dort auf Deinen Code anwenden. 😉

Es ist nicht mal im Ansatz erkenntlich, wie die Methode ImageToString verwendet wird. 🤔
Aber das Image objekt gehört zu den nicht-verwalteten Ressourcen (siehe riesiger, Monitor-großer Hinweis-Banner, den man quasi nicht übersehen kann (er ist drei Mal auf der Seite!), in der Dokumentation)

Sofern Du das Bild von einer Datei lädst, wäre zB die korrekte Implementierung


        private string ImageToString(string imagePath, ImageFormat imgFormat)
        {
            string sImg;
            using(MemoryStream memoryStream = new MemoryStream())
            using(Image img = Image.FromFile(imagePath))
            {
               img.Save(memoryStream, imgFormat);
               sImg = Convert.ToBase64String(memoryStream.ToArray());
            }
            return sImg;
        }

Alternativ direkt einfach mit ReadAllBytes ohne Stream-Handling die Inhalte lesen.

ps: [Artikel] C#: Richtlinien für die Namensvergabe.

pictureContact.Image = global::Personaldaten.Properties.Resources.Who_is_it;

Und wenn man "Global" liest, dann schaut das auch nicht so aus, dass hier noch mehr Potential im Code steckt 😉

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 5 Jahren

Es ist nicht mal im Ansatz erkenntlich, wie die Methode ImageToString verwendet wird.

Sorry hier der Code:

public void SetGeneralVars(string sSpitzname, string sTitel, string sBirthday, Image contactPicture)
        {
            this.sSpitzname = sSpitzname;
            this.sTitel = sTitel;
            this.sBirthday = sBirthday;
            this.sContactPicture = ImageToString(contactPicture, contactPicture.RawFormat);
        }

Die ImageToString Methode und SetGeneralVars sitzen beide in der 2. Klasse.
Das Bild wird in der Hauptklasse geöffnet und in eine Variable gespecihert und dann über SetGeneralVars an die zweite Klasse übergeben.

16.807 Beiträge seit 2008
vor 5 Jahren

Interessanter Code.... da scheint jemand (vermutlich ein Kollege von Dir..?) von einer anderen Programmiersprache zu kommen und versucht dortige Muster auf C# umzumünzen 😉

Wo auch immer das Image Objekt verwaltet wird; wird wohl nicht richtig verwaltet.
Was für Dich eine "Hauptklasse" ist weiß ich nicht; im Kontext einer Software Architektur gibt es den Bezeichner "Hauptklasse" nicht 😉

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 5 Jahren

Interessanter Code.... da scheint jemand (vermutlich ein Kollege von Dir..?) von einer anderen Programmiersprache zu kommen und versucht dortige Muster auf C# umzumünzen 😉
)

Videotutorial 🙂 Ich wollte den Code jetzt etwas anpassen aber komm hier nicht weiter 🙁

Was für Dich eine "Hauptklasse" ist weiß ich nicht

Ich meinte damit nur die Klasse meiner Hauptuserform 😉

Wo auch immer das Image Objekt verwaltet wird; wird wohl nicht richtig verwaltet.

Hmm und was mach ich da jetzt? ?(

16.807 Beiträge seit 2008
vor 5 Jahren

Ich vermute (mit meinem geringen Grafikwissen), dass irgendwas mit dem Image (oder dem Stream dahinter) passiert, das wir hier nicht sehen.

Dadurch befindet sich der Image Stream nicht in dem Zustand, der für das Save notwendig ist -> Save knallt mit einem GDI Error.

Meine Wissen ist es daher auch Best Practise, dass ein Bild in Form eines Byte-Arrays oder in einer Datei gespeichert wird; und nicht in einem Image Objekt.
Work around IIRC: Image duplizieren und mit einem zweiten Objekt arbeiten.

Der Code funktioniert bei mir:

        private string EncodeImageBase64(Image img, ImageFormat imgFormat)
        {
            byte[] imageContents;


            using (MemoryStream memoryStream = new MemoryStream())
            using (Image tempImage = new Bitmap(img))
            {
                tempImage.Save(memoryStream, imgFormat);
                imageContents = memoryStream.ToArray();
            }

            return Convert.ToBase64String(imageContents);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            textBox1.Text = EncodeImageBase64(pictureBox1.Image, ImageFormat.Jpeg);
        }

Getestet mit einer Form mit einer PictureBox, das ein Bild hält, einem Button, der das Encoding ausführt und einer Textbox, der den Inhalt des Base64 Strings zeigt.

Wenn es bei Dir nicht klappt, dann wohl weil der Code noch irgendwas anderes mit dem Bild anstellt.

49.485 Beiträge seit 2005
vor 5 Jahren

Hallo theSoulT,

hier der Code:

daran sieht man aber leider immer noch nicht, wo das Image herkommt, und auch nicht, wie es geladen wird. Das ist doch das entscheidende. Nicht der Save-Code ist wichtig, sondern dass das Image korrekt geladen wurde. Und korrekt bedeutet, dass der Stream, aus dem es geladen wurde, während der gesamten Lebensdauer des Images offen bleibt, also insbesondere auch beim Save noch offen ist.

Wenn es nicht möglich ist, den originalen Stream offen zu halten, muss man die Bildddaten in einen MemoryStream umkopieren, das Image-Objekt aus dem MemoryStream erzeugen und diesen während der gesamten Lebensdauer des Image-Objekts offen halten. Das sollte aber eigentlich schon aus "Allgemeiner Fehler in der GDI+" beim Speichern eines Bildes [==> weil der Stream geschlossen wurde] klar geworden sein.

herbivore

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 5 Jahren

daran sieht man aber leider immer noch nicht, wo das Image herkommt, und auch nicht, wie es geladen wird

Hier wird das Bild geladen:

        private void FrmOp_AcceptOpenFrame(string sFile)
        {
            tabControl1.Visible = true;
            btnPreview.Visible = true;
            lblChoice.Visible = false;
            btnSave.Visible = true;

            Kontakt k = new Kontakt();
            k = Kontakt.OpenContact(sFile,sKey,sIv);
            this.txtName.Text = k.sName;
            this.txtFirstName.Text = k.sFirstName;
            this.txtTitel.Text = k.sTitel;
            this.txtSpitzname.Text = k.sSpitzname;
            if (k.bIsFemale)
            {
                this.rbtnFemale.Checked = true;
            }
            else { this.rbtnMale.Checked = true; }
            this.iContactPicture = Kontakt.StringToImage(k.sContactPicture);
            this.pictureContact.Image = iContactPicture;
        public static Image StringToImage(string sImage)
        {
            MemoryStream MS = new MemoryStream(Convert.FromBase64String(sImage));
            Image img = Image.FromStream(MS);
            MS.Close();
            return img;
        }

Danke euch, hab den Fehler gefunden. Das MS.Close() ist hier nicht richtig. Das ist schuld an der Fehlermeldung 😃
Vielen Dank für eure Mühen!

16.807 Beiträge seit 2008
vor 5 Jahren

Du solltest Dir nochmal die Grundlagen von C# und die Grundlagen von OOP anschauen; da sind ein paar Dinge im Argen, die Dir früher der später ziemlich auf die Füße fallen werden.
Im Endeffekt hast Du mit sauberem Code auch mehr Spaß an der Sache 😉

  • OpenContact-Methode würde man sauber über eine Factory Method machen können
  • Geschlechter, sofern sie boolsch umgesetzt werden, sollte man hier mit Radio-Buttons lösen; nicht mit Checkboxen
  • Die Methode StringToImage ist vom Name her falsch; und auch von der Position; im Endeffekt sauberer durch eine Erweiterungsmethode (auf den Kontakt) umzusetzen

Ansonsten ganz klar noch das Thema Data Binding und [Artikel] Drei-Schichten-Architektur als Hinweis.
Die Gefahr, dass man bei so einem Code sehr schnell die Übersichtlichkeit verliert, ist riesig.

Und ganz arg wichtig: lass den Käse mit dem Typ-Präfix in Deinen Eigenschaften/Variablen.
Keiner außer Du, weiß, was Du damit machen willst. Unternehmen haben auch schon versucht eigene Regeln zu erfinden; sind klaglos gescheitert.
Benenn Deine Variablen lieber richtig, sodass man am Namen erkennt, was es ist inhaltlich ist.
sContactImage => zB Base64EncodedContactImage

Dass es nen String ist, das erkenn ich auch am Typ - das ist aber nicht die Aufgabe eines Namens.
Daher erneuten Hinweis auf [Artikel] C#: Richtlinien für die Namensvergabe