PictureBox. Zaimplementowanie stosu do obsługi Cofinj/Ponów

0

Witam,

napisałem klasę, której zadaniem jest przechowywanie informacji (dokładniej: bitmap) o historii działań na kontrolce PictureBox. Jednakże klasa nie funkcjonuje zupełnie poprawnie.

Klasa działa w ten sposób, że po narysowaniu figury zostaje przesłana kopia bitmapy na stos, po narysowaniu kolejnej figury, na stos zostaje wysłana kolejna bitmapa. I teraz, gdy klikniemy przycisk Cofnij na kontrolce zostaje wyświetlona poprzednia bitmapa, gdy klikniemy kolejny raz, druga w kolejności (od wierzchołka stosu) bitmapa zostanie przesłana na ekran.

  1. Prosiłbym o udzielenie rad, jak poprawić tę klasę. Konkretnie problemem jest to że przy pierwszym kliknięciu przycisku cofnij nic się nie dzieje (wiem nawet czemu - bitmapa na stosie jest tą którą aktualnie widzimy na ekranie), oraz kolejny problem to to że klasa gubi bitmapy podczas wielokrotnego cofanie / rysowania, po prostu jeśli coś narysujemy a już wcześniej cofaliśmy to odtworzy się zbyt daleka bitmapa.

  2. Gdzie należy wstawić funkcję która wysyła bitmapy na stos. Obecnie mam ją podpiętą do zdarzenia onMouseUp kontrolki PictureBox, jednak nie wiem czy to dobry pomysł.

  3. Czy można zaimplementować to w inny (lepszy, łatwiejszy) sposób ? Np. bez obsługi stosu.

Przedstawiam kawałki kodu:

    public class Historia             // klasa do obsługi operacji Cofnij/Ponów
    {
        private const int max = 5;        
        private Bitmap aktualnyObraz;
        private Stack stosCofania;
        private Stack stosPonawiania;
        private bool czyCofano;

        public Historia()
        {
            stosCofania = new Stack();
            stosPonawiania = new Stack();
            czyCofano = false;
        }

        public void Przechowaj(Bitmap obraz)
        {
            if (!czyCofano)
                stosCofania.Push(obraz);
        }

        public Bitmap Cofaj()
        {
            if (stosCofania.Count > 0)
            {
                czyCofano = true;
                aktualnyObraz = (Bitmap)stosCofania.Pop();
                stosPonawiania.Push(aktualnyObraz);
                czyCofano = false;
            }
            return aktualnyObraz;
        }

        public Bitmap Ponawiaj()
        {
            if (stosPonawiania.Count > 0)
            {
                aktualnyObraz = (Bitmap)stosPonawiania.Pop();
            }
            return aktualnyObraz;
        }

        public bool CzyMoznaCofac
        {
            get
            {
                return (stosCofania.Count > 0 ? true : false);
            }
        }

        public bool CzyMoznaPonawiac
        {
            get
            {
                return (stosPonawiania.Count > 0 ? true : false);
            }
        }
    }
}
        private void cofnijToolStripMenuItem_Click(object sender, EventArgs e)
        {
            zapisanyStanObrazu = historia.Cofaj();
            grafika = Graphics.FromImage(zapisanyStanObrazu);
            pictureBox.Image = (Image)zapisanyStanObrazu;
            pictureBox.Refresh();
            cofnijToolStripMenuItem.Enabled = historia.CzyMoznaCofac;
        }

        private void ponówToolStripMenuItem_Click(object sender, EventArgs e)
        {
            zapisanyStanObrazu = historia.Ponawiaj();
            grafika = Graphics.FromImage(zapisanyStanObrazu);
            pictureBox.Image = (Image)zapisanyStanObrazu;
            pictureBox.Refresh();
            ponówToolStripMenuItem.Enabled = historia.CzyMoznaPonawiac;
        }
        private void pictureBox_MouseUp(object sender, MouseEventArgs e)
        {
            mouseUpLocation = e.Location;

            if (rysuje)
            {
                grafika = Graphics.FromImage(zapisanyStanObrazu);
                grafika.SmoothingMode = SmoothingMode.AntiAlias;

                switch (paletaNarzedzi.dajNarzedzie())
                {
                     // ...kod
                }

                historia.Przechowaj((Bitmap)pictureBox.Image);        ///////  <- TUTAJ WYSYŁAM NA STOS

                grafika.Save();
        //        cofanie.Wyslij(zapisanyStanObrazu);
        //        pictureBox.Image = zapisanyStanObrazu;
                rysuje = false;
                cofnijToolStripMenuItem.Enabled = true;
            }
        }
0

Ostatnio pisałem mały edytor graficzny i też zrobiłem opcje cofania i ponawiania w podobny sposób, ale bez oddzielnej klasy.
Po prostu w klasie formatki mam obiekt obecnego obrazu i dwa stosy:

//aktualnie wyświetlany obraz (po wszystkich transformacjach)
private Bitmap currentImage;
//stos obrazków do cofania
Stack<Bitmap> undoImages = new Stack<Bitmap>();
//stos obrazków do ponawiania
Stack<Bitmap> redoImages = new Stack<Bitmap>();

Przed każdą operacją na obrazie (np. konwersją do skali szarości) umieszczam aktualny obraz w stosie cofania.
<code class="c#">
//najpierw wrzucenie na stos do historii
this.undoImages.Push(this.currentImage);

I zrobiłem także obsługę przycisków cofnij/ponów:

private void menuUndo_Click(object sender, EventArgs e)
{
//jeśli istnieje aktualny obraz i stos cofania nie jest pusty
if (this.currentImage != null && this.undoImages.Count != 0)
{
//zapisanie aktualnego obrazu w stosie ponawiania
this.redoImages.Push(this.currentImage);
//pobranie ostatniego obrazka ze stosu cofania i ustawienie go jako obecnego
this.currentImage = this.undoImages.Pop();
//i wyświetlenie go
this.pbxImage.Image = this.currentImage;
}
}

private void menuRedo_Click(object sender, EventArgs e)
{
//jeśli istnieje aktualny obraz i stos ponawiania nie jest pusty
if (this.currentImage != null && this.redoImages.Count != 0)
{
//zapisanie aktualnego obrazu w stosie cofania
this.undoImages.Push(this.currentImage);
//pobranie ostatniego obrazka ze stosu ponawiania i ustawienie go jako obecnego
this.currentImage = this.redoImages.Pop();
//i wyświetlenie go
this.pbxImage.Image = this.currentImage;
}
}

0

Mam małe pytanie do powyższego kodu.
W momencie kiedy odkładam obraz na stos jest on jakiegoś tam typu, przyjmijmy PixelFormat = 24bppRgb, natomiast kiedy ten sam niby obraz zdejmuje ze stosu zmienia on PixelFormat na 32bppArgb.
Jak tego uniknąć i zdejmować ze stosu obrazy w takim samym formacie w jakim się je odłożyło?

  if (this.currentImage != null && this.redoImages.Count != 0)
    {
        //tu currentImage jest typu 24bppRgb
        this.undoImages.Push(this.currentImage);
        //A już tutaj po zdjęciu ze stosu jest 32bppArgb
        this.currentImage = this.redoImages.Pop();
        this.pbxImage.Image = this.currentImage;
    }
0

Obrazki zdejmowane ze stosu będą miały naturalnie ten sam format co obiekty wkładane. Będą to de facto te same obiekty.

BądźCoBądź napisał(a)
  if (this.currentImage != null && this.redoImages.Count != 0)
    {
        //tu currentImage jest typu 24bppRgb
        this.undoImages.Push(this.currentImage); // undoImages
        //A już tutaj po zdjęciu ze stosu jest 32bppArgb
        this.currentImage = this.redoImages.Pop(); // redoImages
        this.pbxImage.Image = this.currentImage;
    }

W przykładzie wyżej operujesz na dwóch różnych stosach, więc zdejmujesz najprawdopodobniej inny obiekt. Strzelam na ślepo, że jest to nowo utworzony Bitmap() na wzór starego ale bez sprecyzowania formatu, stąd 32bpp. Ale to ślepy strzał.

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