DataGridView Zapisywanie List do pliku Serializacja

0

Bardzo proszę o pomoc. Nie wiem jak poradzić sobie z pewnym problemem. Chodzi mi o zapisywanie do pliku dat czyli serializacje. Zamieszczam poniżej kod który czasami działa poprawnie(tak mi się wydaje) a czasami nie. Urachamia się i wczytuje dane do listy podane w kodzie i wyśietla. Teraz w datagridview edytuje dodaje usuwam itd. i zapisuje do pliku. I teraz gdy wczytuje dane z pliku do datagridview to czasami wczytuja się wszystkie dane poprawnie a czasmi po wczytaniu wyswietlaja się tylko dane te z kodu. Spedziłem dwa dni na poszukiwanie rozwiązania i niestety NIC!!

public partial class kontrahenci : Form
    {
[Serializable]
    public class Kontrahent
    {
        public int Id
        {
            get
            {
             
                return id;
            }
            set
            {
                id = value;
            }
        }
        private int id;
        public string FirstName { get; set; }
        public string LastName { get; set; }
       

        List<Kontrahent> kontra = new List<Kontrahent>();

        public kontrahenci()
        {
            InitializeComponent();



            kontra.Add(new Kontrahent() { Id = 1, FirstName = "Michał", LastName = "Nowak});
            kontra.Add(new Kontrahent() { Id = 2, FirstName = "Monika", LastName = "Hanc" });
            kontra.Add(new Kontrahent() { Id = 3, FirstName = "Jan", LastName = "Kowalski" });
            kontra.Add(new Kontrahent() { Id = 4, FirstName = "Franek", LastName = "Zielichowski" });

        }

        private void kontrahenci_Load(object sender, EventArgs e)
        {

           
            bindingSource1.DataSource = kontra;
            dataGridView1.DataSource = bindingSource1;
            bindingNavigator1.BindingSource = bindingSource1;
           

        }
     
        private void button2_Click(object sender, EventArgs e)
        {
            //---------------Zapisywanie--------------
           
            if (File.Exists(@"c:\fakturka\kontrahenci.dat"))
            {
                File.Delete(@"c:\fakturka\kontrahenci.dat");
            }
            string path = @"c:\fakturka\kontrahenci.dat";
            using (Stream output = File.Create(path))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(output, kontra);
                MessageBox.Show("Lista kontrahentów zostala zapisana na dysku");

            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            //---------------Odczytywanie--------------
         
            string path = @"c:\fakturka\kontrahenci.dat";

            using (Stream input = File.OpenRead(path))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                List<Kontrahent> kontra = (List<Kontrahent>)formatter.Deserialize(input);

                bindingSource1.DataSource = kontra;
                dataGridView1.DataSource = bindingSource1;
                bindingNavigator1.BindingSource = bindingSource1;

                MessageBox.Show("Dane zostaly wczytane!");
            }
        }
0

Niepotrzebnie komplikujesz sobie sprawę. Takie rzeczy można spokojnie zapisać do XML'a:

// Serializacja
var list = new List<Kontrahent>();
var serializer = new XmlSerializer(typeof(List<Kontrahent>));
var writer = new StreamWriter("test.xml");
serializer.Serialize(writer, list);
writer.Close();

// Desertializacja
var deserializer = new XmlSerializer(typeof(List<Kontrahent>));
var reader = new StreamReader("test.xml");
var newList = deserializer.Deserialize(reader) as List<Kontrahent>;
reader.Close();

Oczywiście zapnij to też w jakiś konkretny blok try catch.

0

Niestety żadne dane nie zapisały się do pliku XML.
Zawartość pliku po zapisaniu to:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfKontrahent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
0

Nic nie zapisało, bo gwarantuje, że lista jest pusta.
Sprawdź to debugerem.

1

Jak nie działa jak działa:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml.Serialization;

namespace Program {
    public class Person{
        public string Name { get; set; }
        public string Surname { get; set; }
    }

    public class PersonSerializer {
        public void Serialize(List<Person> list, string path) {
            var serializer = new XmlSerializer(typeof(List<Person>));
            var writer = new StreamWriter(path);
            serializer.Serialize(writer, list);
            writer.Close();
        }

        public List<Person> Deserialize(string path) {
            var deserializer = new XmlSerializer(typeof(List<Person>));
            var reader = new StreamReader(path);
            var newList = deserializer.Deserialize(reader) as List<Person>;
            reader.Close();
            return newList;
        }
    }

    class Program {
        static void Main(string[] args) {
            var serializer = new PersonSerializer();
            var list = serializer.Deserialize("test.xml");

            var form = new Form();
            form.FormClosed += (sender, e) => { serializer.Serialize(list, "test.xml"); };

            var grid = new DataGridView();
            form.Controls.Add(grid);

            var source = new BindingSource();
            source.DataSource = list;
            grid.DataSource = source;

            form.ShowDialog();
        }
    }
}

Przy każdym otwarciu formy program odczytuje zawartość z pliku test.xml. Teraz kiedy wpiszesz coś do grida i zamkniesz formę to zostanie to zapisane do pliku i przy ponownym uruchomieniu programu odczytane i dodane do girda.

PS: Tylko przed pierwszym uruchomieniem programu dodaj do pliku test.xml takiego stringa...

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>

... bo inaczej program wywali Ci wyjątek, który pominąłem dla czytelności kodu.

Musi działać.

0

W liście są dane. Sprawdziłem debugerem count=4. Przecież w kodzie deklaruje 4 rekordy.

0

Tak Oczywiście działa!! Odpisałem ani zauważyłem nowego kodu!! Przepraszam. Wielki DZIĘKI!! Muszę ten kod dostosować do siebie. Dziękuję!

0

Jeszcze jedno szybkie pytanie. Czy kod przycisku zapisującego dane mógłby tak wyglądać (nie zapisuje mi).

private void zapiszdane_Click(object sender, EventArgs e)
        {
            var serializer = new PersonSerializer();
            var list = serializer.Deserialize("c:\\fakturka\\test.xml");


             serializer.Serialize(list, "c:\\fakturka\\test.xml"); 

            }
0

Zrób sobie obiekt klasy serializującej jako pole w swojej klasie i stamtąd używaj metod do zapisu i odczytu. To oczywiście nie jest rozwiązanie problemu. Coś znów pomieszaleś. BTW: jednak nie jesteś anonimem, zaznaczaj poprawne posty :)

EDIT: W ogóle jeżeli kolekcja odczytana przez Deserialize jest pusta co już wiesz co ma zapiać Serialize :P

0

Po przerobieniu mój kod wygląda tak:
klasa:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Xml.Serialization;
using System.Xml;

namespace Invoice
{
    public class KontrahentName
    {
        public int Id
        {
            get
            {

                return ++id;
            }
            set
            {

                id = value;
            }
        }
        private int id;
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
   

    //------------Serializacja XML--------------
    class Serializacja
    {

       
        public static void XMLSerialize(List<KontrahentName> kontrahent)
        {
            
            StreamWriter sw = new StreamWriter(@"c:\fakturka\kontrahent.xml");
            try
            {
                XmlSerializer s = new XmlSerializer(typeof(List<KontrahentName>));
                s.Serialize(sw, kontrahent);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                sw.Close();
            }
        }
    }
    ///------------------Deserializacja XML-------------------
    class DeSerializacja
    {

         public static List<KontrahentName> XMLDeserialize(string xmlFile)
        {
            List<KontrahentName> obj;
           XmlReader xr =  XmlReader.Create(xmlFile);
            try
            {
                XmlSerializer s = new XmlSerializer(typeof(List<KontrahentName>));
               obj =(List<KontrahentName>)s.Deserialize(xr);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                obj = null;
            }
            finally
            {
                xr.Close();
            }
            return obj;
        }

    }

}


a teraz forma

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Invoice
{
    public partial class Form1 : Form
    {
        
        List<KontrahentName> kontrahent;
        public Form1()
        {
            InitializeComponent();
            
        }

        private void button1_Click(object sender, EventArgs e)
        {
           ////-------------- Zapisywanie po nacisnieciu przycisku
          
           
         
           
            bindingSource1.DataSource = kontrahent;
            dataGridView1.DataSource = bindingSource1;
            bindingNavigator1.BindingSource = bindingSource1;

            Serializacja.XMLSerialize(kontrahent);
           
        }

        private void button2_Click(object sender, EventArgs e)
        {
            /////////// -----Deserializacja Wczytywanie po nacisnieciu przycisku
          
        //    Member m2 = XMLDeserialize(@"c:\fakturka\members.xml");
        List<KontrahentName> kontrahent = DeSerializacja.XMLDeserialize(@"c:\fakturka\kontrahent.xml");

            bindingSource1.DataSource = kontrahent;
            dataGridView1.DataSource = bindingSource1;
            
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            bindingNavigator1.BindingSource = bindingSource1;
            List<KontrahentName> kontrahent = DeSerializacja.XMLDeserialize(@"c:\fakturka\kontrahent.xml");

            bindingSource1.DataSource = kontrahent;
            dataGridView1.DataSource = bindingSource1;
        }
    }
}

"Nic nie zapisało, bo gwarantuje, że lista jest pusta. " Miałeś racje lista jest pusta. Zapisywały się rekordy który były w kodzie. Ale po naciśnięciu zapisz. Zapisuje mi się pusty plik. Rozumowałem że skoro załadowałem dane do List z pliku to one są w datagridview(i w pamięci) i mogę je teraz zapisać. A niestety tak się nie dzieje!

1

Niepotrzebnie w zapisywaniu robisz drugi raz databinding.

Wywal to:

bindingSource1.DataSource = kontrahent;
dataGridView1.DataSource = bindingSource1;
bindingNavigator1.BindingSource = bindingSource1;

przez to wszystko co wprowadzasz do grida idzie w kosmos, bo bindujesz DataGridView do pustej listy.

Aha:

  • Stosuj var. Zamiast pisać np: StreamReader sr = new StreamReader(); piszesz: var sr = new StreamReader();
  • Przy deserializacji nie musisz używać XmlReadera vide kod, który Ci wcześniej pokazalem.
0

Bardzo Dziękuję Ci za wszystkie odpowiedzi!! Ale niestety po zastosowaniu kolejnej porady przez Ciebie nadal przy zpisywaniu zapisuje pustą liste. Jestem zrozpaczony od 3 dni siedzę cały czas nad tym i nie mogę tego rozwiązać!!

1

Wrzuć cały projekt na jakiegoś sendspace'a i pokaż.

0

Mogę ci wysłać projekt email, skype lub gg??

1

Ale masz tam miszmasz. :D
Ale główny problem z zapisem miałeś w klasie form1. Wczytywania już nie poprawiałem.

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Invoice
{
   public partial class Form1 : Form
   {
      private List<KontrahentName> kontrahent;

      public Form1()
      {
         InitializeComponent();
         kontrahent = DeSerializacja.XMLDeserialize(@"c:\fakturka\kontrahent.xml");
      }

      private void button1_Click(object sender, EventArgs e)
      {
         Serializacja.XMLSerialize(kontrahent);
      }

      private void button2_Click(object sender, EventArgs e)
      {
         bindingSource1.DataSource = kontrahent;
         dataGridView1.DataSource = bindingSource1;
      }

      private void Form1_Load(object sender, EventArgs e)
      {
         bindingNavigator1.BindingSource = bindingSource1;
         bindingSource1.DataSource = kontrahent;
         dataGridView1.DataSource = bindingSource1;
      }
   }
}
0

Dziękuję bardzo !! Teraz działa!! Ale ze mnie gapa!! Teraz to wydaje się proste!! Zamykam wątek.

WIELKIE DZIĘKI!!!

0

Wszystko działa. Ale dlaczego przy dodaniu konstruktora podanego poniżej do klasy Kontrahent.

Program nie wczytuje danych z pliku i przy próbie dodania nowej osoby wysypuje się.
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll

Additional information: No row can be added to a DataGridView control that does not have columns. Columns must be added first.
Nie rozumiem dlaczego dodanie konstruktora to powoduje???

  

private static int NextId = 0;

    

        public Kontrahent(string firstName, string lastName)
        {
            NextId++;
            id = NextId;

            FirstName = firstName;
            LastName = lastName;

        }
0

Jeżeli chcesz dodawać do kolekcji rzeczy w taki sposób:

// Fragment Twojego kodu:
kontra.Add(new Kontrahent() { Id = 1, FirstName = "Michał", LastName = "Nowak});
kontra.Add(new Kontrahent() { Id = 2, FirstName = "Monika", LastName = "Hanc" });
kontra.Add(new Kontrahent() { Id = 3, FirstName = "Jan", LastName = "Kowalski" });
kontra.Add(new Kontrahent() { Id = 4, FirstName = "Franek", LastName = "Zielichowski" });

to klasa Kontrahent musi mieć domyślny konstruktor.
To samo dotyczy klasy, która bierze udział w serializacji / deserializacji danych.

Dlatego masz błąd przy odczycie pliku.

0

Rozumiem to. Ale jak to Twoim zdaniem zmienić?

0

Po prostu dopisz do klasy konstruktor domyślny.

0

OK. dopisałem konstrukor domyślny

public Kontrahent()
        {

        } 

i teraz prawie działa dobrze. Bo gdy dane z kodu są wyśietlane w datagridview to Id jest numerowany po kolei (o jeden większy)

 
 kontrahent.Add(new Kontrahent("Mirek","Nowak") );
            kontrahent.Add(new Kontrahent("Anna", "Panna"));

Natomiast gdy dane z pliku Id jest zawsze zero.

0

Co muszę zmienić żeby wywoływał mi się ten konstrukor

public Kontrahent(string firstName, string lastName)
        {
            NextId++;
            id = NextId;

            FirstName = firstName;
            LastName = lastName;


        }
 
1

Komplikujesz sobie niepotrzebnie życie.

Skoro już tak upierasz się żeby używać sparametryzowanego konstruktora to numerowanie musisz zrobić tak...

kontrahent[kontrahent.Count - 1].Id = kontrahent.Count;

...dla każdego nowego obiektu.

Nie możesz po prostu zrobić np tak:

public void AddSomeData(string name) {
     collection.Add(new Class { Id = collection.Count, Name = name });
}

??

PS: Oczywiście żeby Ci zadziałało to co Ty chcesz robić to Id musi być własnością publiczną.
PS2: W ogóle inkrementacja w konstruktorze klasy Kontrahent obiektu NextId jest bez sensu. Lepiej użyć własności kolekcji tak jak Ci pokazałem.

0

Nie bardzo wiem gdzie to dopisać.
A po zatym chciałbym serializować i deserialziować pliki z konstruktorami sparametryzowanym. Jak to robi??

2
  • Po co Ci konstruktor z parametrami kiedy możesz spokojnie używać domyślnego i tworzyć obiekty od razu inicjalizowane wartościami np: collection.Add(new Class { Id = collection.Count, Name = name });?
  • Po co Ci konstruktor z parametrami skoro i tak wszystkie własności masz publiczne i konstruowanie obiektu możesz zrobić tak jak wyżej?
  • Kiedy dodajesz obiekt do kolekcji i chcesz numerować jakoś indeksy itp. to nie korzystaj z jakiegoś static'a, bo to jest bez sensu. Zamiast tego korzystaj z własności kolekcji Count, która zmienia się w zależności od ilość danych w kolekcji i nie musisz pamiętać o tym, żeby coś samemu inkrementować w konstruktorze. Nie mówiąc już o dekrementacji, bo destruktor w C# jest niedeterministyczny i nie masz wpływu na to kiedy się wykona. Musiałbyś samemu dekrementować tego static'a przy metodzie usuwającej, a po co?
  • Nie bardzo wiem gdzie to dopisać. No jak nie wiesz gdzie dopisać: collection.Add(new Class { Id = collection.Count, Name = name });? Tam gdzie chcesz używać niepotrzebnie konstruktora z parametrami.
  • A po zatym chciałbym serializować i deserialziować pliki z konstruktorami sparametryzowanym. Jak to robi?? Serializer używa i tak tylko domyslnego konstruktora więc jeżeli chcesz coś serializować to konstruktor z parametrami i tak nic Ci nie da.
0

Dziękuję Ci za cierpliwość i wyrozumiałość i okazaną pomoc. Ale nadal nie rozumiem jak to ugryźć zastosowałem Twoje ostatnie rady i poprzednie wszystko to rozumiem i to jest jasne gdy umieszczam dane do dodania w kodzie.

  private List<Kontrahent> kontrahent;

        

        public Form1()
        {
            InitializeComponent();
                kontrahent = new List<Kontrahent>();
            

           // kontrahent = DeSerializacja.XMLDeserialize(@"c:\fakturka\kontrahent.xml");
            
            kontrahent.Add(new Kontrahent() { Id = kontrahent.Count, FirstName = "Mirek ", LastName = "Nowak" });
            kontrahent.Add(new Kontrahent() { Id = kontrahent.Count, FirstName = "Anna", LastName = "Panna" });
           
            kontrahent[kontrahent.Count - 1].Id = kontrahent.Count;

         
        } 

Ale co zrobić jeżeli dane wczytuje z pliku i chciałbym numerować id kolejno???

 private List<Kontrahent> kontrahent;

        

        public Form1()
        {
            InitializeComponent();
           //     kontrahent = new List<Kontrahent>();
            

            kontrahent = DeSerializacja.XMLDeserialize(@"c:\fakturka\kontrahent.xml"); 
0

No przecież przy serializowaniu obiektu zapisane zostały również Id. Zostaną odtworzone w tej samej numeracji przy odczywyaniu. Niczego nie trzeba będzie robić.

A jeżeli masz np jakieś dziury, bo powiedzmy coś usuwales z grida to będziesz musiał po deserializacji przejechać jakimś ´forem´ wszystkie Id i numerować od początku. Albo numerować od początku przy każdym usuwaniu obiektu z grida. (Od początku albo od numeru Id poprzednika usuwanego obiektu)

0

Niestety podczas dodawania nwego obiektu Id jest zawsze zero.
Wysłam mój projekt
https://www.sendspace.com/file/a1h1a4

2

Ojeeeej no przecież wiadomo, że jak ładujesz dane do pustej kolekcji to Id musi wynosić zero więc jeżeli chcesz numerować od jedynki to musisz zrobić Count + 1. To co Ci napisałem to był tylko przykład. Zamiast wiesz... kopiować i wklejać kod to powinieneś pomyśleć, że jak ładujesz dane do pustej kolekcji to przecież dla pierwszego obiektu Count zawsze będzie równe zero więc trzeba je powiększyć o jeden żeby numeracja była prawidłowa (chyba, że chcesz numerować obiekty od zera):

        public Form1() {
            InitializeComponent();
            kontrahent = DeSerializacja.XMLDeserialize(@"c:\fakturka\kontrahent.xml");
            kontrahent.Add(new Kontrahent() { Id = kontrahent.Count + 1, FirstName = "Mirek ", LastName = "Nowak" });
            kontrahent.Add(new Kontrahent() { Id = kontrahent.Count + 1, FirstName = "Anna", LastName = "Panna" });
        }

A żeby przy dodawaniu i usuwaniu za pomocą GUI wartość Id była modyfikowana to po prostu dopisz zdarzenie do dataGridView1 w konstuktorze Form:

            this.dataGridView1.AllowUserToAddRows = false;
            this.dataGridView1.RowsAdded += (gridSender, gridArgs) => {
                if (this.bindingNavigatorAddNewItem.Pressed)
                    this.kontrahent[this.kontrahent.Count - 1].Id = this.kontrahent.Count;
            };

            this.dataGridView1.RowsRemoved += (gridSender, gridArgs) => {
                if (this.bindingNavigatorDeleteItem.Pressed)
                    for (int i = 0; i < this.kontrahent.Count; i++)
                        this.kontrahent[i].Id = i + 1;
            };

Przetestuj sobie to najlepiej na pustym pliku kontrahent.xml ale pustym w sensie pustym dla serializera czyli plik musi posiadać informacje o pustej kolekcji:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfKontrahent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>

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