[C#] Edycja danych i zapis do bazy SQL


(eureka 170) #1

Chciałem napisać program który pobiera bazę danych SQL do DataGridView i umożliwić użytkownikowi edycji komórek:

public partial class Form1 : Form

    {

        string connectionString;

        SqlCommand commandString;

        SqlConnection myConnection;

        DataTable dt;

        SqlDataAdapter dataAdapter;

        string ciag1, ciag2, ciag3;

        int a, b;

        public Form1()

        {

            InitializeComponent();

            connectionString = "Data Source = EUREKA-PC; Initial Catalog = Wydzialy; trusted_connection = yes";

            button2.Enabled = false;


        }


        private void Form1_Load(object sender, EventArgs e)

        {


        }


        private void button1_Click(object sender, EventArgs e)

        {

            commandString = new SqlCommand();

            commandString.CommandText = "Select * from widok";


            myConnection = new SqlConnection(connectionString);

            commandString.Connection = myConnection;

            dataAdapter = new SqlDataAdapter(commandString.CommandText,myConnection);

            commandString.Connection.Open();

            dt = new DataTable();

            dataAdapter.Fill(dt);

            dataGridView1.DataSource = dt;

            commandString.Connection.Close();



        }


        private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)

        {

            a = dataGridView1.SelectedCells[0].ColumnIndex;

            b = dataGridView1.SelectedCells[0].RowIndex;


            button2.Enabled = true;


        }


        private void button2_Click(object sender, EventArgs e)

        {


            if (a != 3)

            {

                ciag1 = dataGridView1.Columns[a].HeaderText;  

                ciag2 = (b + 1).ToString();

                if (textBox1.Text != string.Empty)

                {

                    ciag3 = textBox1.Text;


                    commandString.CommandText = String.Format("update Student SET {0}='{1}' where idStudent={2}", ciag1, ciag3, ciag2);

                    commandString.Connection.Open();

                    dataAdapter.SelectCommand = commandString;       

                    commandString.CommandText = "Select * from widok";


                    dataAdapter.SelectCommand = commandString;


                    DataTable dt = new DataTable();


                    dataAdapter.Fill(dt);

                    dataGridView1.DataSource = dt;

                    commandString.Connection.Close();

                }

            }

        }


        private void textBox1_TextChanged(object sender, EventArgs e)

        {


        }


        private void dataGridView2_CellContentClick(object sender, DataGridViewCellEventArgs e)

        {


        }

    }

W bazie danych utworzyłem 3 tabele: studentów, prowadzących i nazwy wydziałów. Następnie wpisałem komendę:

create view widok as select Student.idStudent, Student.Imię, Student.Nazwisko, Wydzial.[Nazwa wydziału] from Student join Wydzial on student.idWydzial = Wydzial.idWydzial

dzięki czemu mam "widok" gdzie mam idstudenta, imię nazwisko oraz pełną nazwę wydziału, do którego należy ten student. W programie trzeba najpierw kliknąć na button1. Dzięki temu program pobiera dane z bazy danych i wyświetla w DataGridView. Następnie, trzeba zaznaczyć pole, ktore chcemy edytować i uaktywnia się button2. W polu textbox1 trzeba wpisać nowy ciąg, który chcemy, aby się pojawił w tej komórce. Po kliknięciu na button2, program ma uaktualnić zawartość komórki. Niestety, po kliknięciu na button2 nic mi się nie zmienia. Cokolwiek bym wpisał w textbox1 i tak zawartość komórki pozostaje taka sama. Chciałem to zrobić za pomocą

commandString.CommandText = String.Format("update Student SET {0}='{1}' where idStudent={2}", ciag1, ciag3, ciag2);

jeżeli wpiszę w SQL Management Studio np.

update Student SET Imie='Jakub' where idStudent=8

to program zmienia mi zawartość komórki, a z poziomu C# tak nie działa. Czemu tak się dzieje?


(Tomek Matz) #2

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldataadapter.aspx

Instrukcję SQL, która ma wykonać aktualizację rekordów w tabeli, musisz przekazać do właściwości UpdateCommand, a nie SelectCommand. Następnie musisz wywołać metodę Update, do której jako parametr przekażesz dataGridView1.DataSource, a nie metodę Fill. Więcej informacji w linku, który podałem wyżej.


(eureka 170) #3

Teraz mój program wygląda taK:

public partial class Form1 : Form

    {

        static int c = 0;

        string connectionString;

        SqlCommand commandString;

        SqlConnection myConnection;

        DataTable dt;

        SqlDataAdapter dataAdapter;

        string ciag1, ciag2, ciag3;

        int a, b;


        public Form1()

        {

            InitializeComponent();

            connectionString = "Data Source = EUREKA-PC; Initial Catalog = Wydzialy; trusted_connection = yes";

            button2.Enabled = false;

            commandString = new SqlCommand();


        }


        private void Form1_Load(object sender, EventArgs e)

        {


        }


        private void button1_Click(object sender, EventArgs e)

        {

            if (c == 0)

            {

                commandString.CommandText = "Select * from widok";


                myConnection = new SqlConnection(connectionString);

                commandString.Connection = myConnection;

                dataAdapter = new SqlDataAdapter(commandString.CommandText, myConnection);

                commandString.Connection.Open();

                dt = new DataTable();

                dataAdapter.Fill(dt);

                dataGridView1.DataSource = dt;

                commandString.Connection.Close();

                c = 1;

            }

            else

            {

                commandString.CommandText = "Select * from widok";

                dataAdapter.UpdateCommand = commandString;

                dataAdapter.Update(dt);

                dataGridView1.DataSource = dt;

            }



        }


        private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)

        {

            a = dataGridView1.SelectedCells[0].ColumnIndex;

            b = dataGridView1.SelectedCells[0].RowIndex;


            button2.Enabled = true;


        }


        private void button2_Click(object sender, EventArgs e)

        {


            if (a != 3)

            {

                ciag1 = dataGridView1.Columns[a].HeaderText;  

                ciag2 = (b + 1).ToString();

                ciag3 = dataGridView1.Rows[b].Cells[a].Value.ToString();



                commandString.CommandText = String.Format("update Student SET {0}='{1}' where idStudent={2}", ciag1, ciag3, ciag2);


                commandString.Connection.Open();

                dataAdapter.UpdateCommand = commandString;

                dataAdapter.Update(dt);

                dataGridView1.DataSource = dt;


                commandString.Connection.Close();

                button2.Enabled = false;


            }

            if (a == 3)

            {

                ciag3 = (b + 1).ToString();


                if (comboBox1.SelectedIndex == 0)

                {

                    ciag2 = "1";

                    commandString.CommandText = String.Format("update Student SET idWydzial='{0}' where idStudent={1}", ciag2, ciag3);


                }/*

                else if (comboBox1.SelectedIndex == 1)

                {

                    ciag2 = "2";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 2)

                {

                    ciag2 = "3";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 3)

                {

                    ciag2 = "4";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 4)

                {

                    ciag2 = "5";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 5)

                {

                    ciag2 = "6";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 7)

                {

                    ciag2 = "8";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 8)

                {

                    ciag2 = "9";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 9)

                {

                    ciag2 = "10";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 10)

                {

                    ciag2 = "11";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                else if (comboBox1.SelectedIndex == 11)

                {

                    ciag2 = "12";

                    commandString.CommandText = String.Format("update Wydzial SET {0}='{1}' where idWydzial={2}", ciag1, ciag3, ciag2);

                }

                */

                commandString.Connection.Open();

                dataAdapter.UpdateCommand = commandString;

                dataAdapter.Update(dt);

                dataGridView1.DataSource = dt;


                commandString.Connection.Close();

                button2.Enabled = false;

            }

        }


        private void textBox1_TextChanged(object sender, EventArgs e)

        {


        }


        private void dataGridView2_CellContentClick(object sender, DataGridViewCellEventArgs e)

        {


        }

    }

Kiedy a!=3, to mi działa. Zmieniam sobie w komórce datagrid wartość, potem klikam na przycisk i mi się dane aktualizują. Natomiast w przypadku, gdy a jest równe 3, mi nie działa. Tutaj mam listę wydziałów. W combobox z listy wybieram sobie wydział ( na razie mam zrobiony pierwszy wydział, czyli combobox1.selectedindex ==0), następnie mam kliknąć przycisk i dane mają się nie aktualizować... ale tak się nie dzieje, mimo, że część kodu skopiowałem z a!=3. Co jest tego przyczyną?


(Tomek Matz) #4

Usuń cudzysłów z update Student SET {0}='{1}' where idStudent={2} i zobacz, czy ruszy. BTW ten kod powinieneś przemyśleć, bo się robi chaos (zresztą już w początkowej wersji było chaotycznie) i na prawdę ciężko się to przegląda.

To jest zupełnie źle:

commandString.CommandText = "Select * from widok";

dataAdapter.UpdateCommand = commandString;

dataAdapter.Update(dt);

dataGridView1.DataSource = dt;

Do właściwości UpdateCommand masz przekazywać tylko i wyłącznie instrukcje SQL oparte na poleceniu Update.

I taki tip, który może Ci się do czegoś przyda. Każdy obiekt klasy DataTable zawiera kolekcję obiektów klasy DataRow. W momencie, gdy wykonujesz aktualizację jakiegoś wiersza (lub, gdy go usuwasz) w obrębie DataGridView, to zmienia się właściwość RowState tego obiektu DataRow. Więcej o tej właściwości możesz przeczytać tutaj http://msdn.microsoft.com/en-us/library/system.data.datarow.rowstate.aspx. Anyway dzięki tej właściwości możesz określić, które wiersze zostały usunięte, które dodane, a które zostały zmienione.


(eureka 170) #5

Niestety, usunięcie cudzysłowu z update Student SET {0}='{1}' where idStudent={2} powoduje błąd kompilacji

Nadal mnie zastanawia, czemu w przypadku a!=3 działa dobrze, a w a=3 nie. Kody są prawie takie same


(Tomek Matz) #6

Jaki błąd? Testowałeś ten update na bazie danych?


(eureka 170) #7

Kiedy wpiszę commandString.CommandText = String.Format(update Student SET idWydzial='{0}' where idStudent={1}, ciag2, ciag3), wyświetli mi się błąd.

Błąd jest tutaj: http://imageshack.us/photo/my-images/54 ... rd01k.jpg/

w sql management studio wpisałem update Student SET idWydzial='1' where idStudent=9 i mi działa.

A w c# już nie. Kiedy ustawiłem label1 by wyświetlał commandStdring.CommandText i jak zaznaczałem studenta nr 9, to wyświetlała mi się dokładnie ta sama komenda, co wyżej. Tyle, że efektu nie widać


(Tomek Matz) #8

Mi chodziło o to, żebyś usunął pojedynczy cudzysłów z tej instrukcji SQL update Student SET idWydzial='{0}' where idStudent={1}. U Ciebie w bazie danych idWydzial to string?


(eureka 170) #9

U mnie idWydzial to int. Jednak z apostrofami też działało.

Usunąłem te apostrofy. W sql działa, a w c# to samo


(Tomek Matz) #10

Puść ten kod przez debugger i zobacz, czy w momencie wywoływania metody Update wiersz, który został zmodyfikowany, ma ustawioną flagę RowState na Modified.


(eureka 170) #11

Jest status Unchanged


(Tomek Matz) #12

OK, czyli to jest powodem tego, że kod nie działa. Teraz kwestia ustalenia, czemu tak się dzieje. Spróbuj do metody Update przekazać takie coś: (DataTable)dataGridView1.DataSource (zresztą wspominałem o tym na samym początku). Możesz podejrzeć też, czy ten wiersz w DataSource też ma status Unchanged, czy też Modified.

I jeszcze jedno. Tamten apostrof/pojedynczy cudzysłów, o którym mówiliśmy usuń. Nawet jeśli to działa (nie wiem czemu) to tak nie może być, że do pola gdzie ma być wartość całkowita, próbujesz wstawić łańcuch znaków.


(eureka 170) #13

ok, już dawno ten apostrof usunąłem

Teraz kod wygląda tak:

private void button2_Click(object sender, EventArgs e)

        {


            if (a != 3)

            {

                DataRow row;

                row = dt.Rows[b];

                ciag1 = dataGridView1.Columns[a].HeaderText;  

                ciag2 = (b + 1).ToString();

                ciag3 = dataGridView1.Rows[b].Cells[a].Value.ToString();


                commandString = new SqlCommand();

                commandString.CommandText = String.Format("update Student SET {0}='{1}' where idStudent={2}", ciag1, ciag3, ciag2);

                commandString.Connection = myConnection;

                commandString.Connection.Open();

                dataAdapter.UpdateCommand = commandString;

                dataAdapter.Update(dt);


                dataGridView1.DataSource = dt;


                commandString.Connection.Close();

                button2.Enabled = false;


            }

            else if (a == 3)

            {

                DataRow row;

                row = dt.Rows[b];

                ciag3 = (b + 1).ToString();


                commandString = new SqlCommand();

                commandString.Connection = myConnection;


                if (comboBox1.SelectedIndex == 0)

                {

                    ciag2 = "1";


                    commandString.CommandText = String.Format("update Student SET idWydzial={0} where idStudent={1}", ciag2, ciag3);

                    label1.Text = commandString.CommandText;

                }


                commandString.Connection.Open();

                dataAdapter.UpdateCommand = commandString;

                dataAdapter.Update((DataTable)dataGridView1.DataSource);


                dataGridView1.DataSource = dt;


                commandString.Connection.Close();

                button2.Enabled = false;

           }

     }

Ale status nadal jest unchanged


(Tomek Matz) #14

Coś musiałem przeoczyć (dawno nie używałem adapter-ów). Jak do rana nie znajdziesz rozwiązania to wyślij mi ten kod (daj link tutaj w topicu lub na priva) to Ci go poprawię. Teraz jestem w trakcie przeinstalowywania VS, więc nawet nie mam IDE, żeby to u siebie odpalić.


(eureka 170) #15

Niestety próbowałem jeszcze coś pokombinować z tym kodem w nocy ale nic to za bardzo nie dało. Przesyłam swój kod:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Data.SqlClient;


namespace WindowsFormsApplication1

{

    public partial class Form1 : Form

    {

        static int c = 0;

        string connectionString;

        SqlCommand commandString;

        SqlConnection myConnection;

        DataTable dt;

        SqlDataAdapter dataAdapter;

        string ciag1, ciag2, ciag3;

        int a, b;


        public Form1()

        {

            InitializeComponent();

            connectionString = "Data Source = EUREKA-PC; Initial Catalog = Wydzialy; trusted_connection = yes";

            button2.Enabled = false;

            myConnection = new SqlConnection(connectionString);


        }


        private void Form1_Load(object sender, EventArgs e)

        {


        }


        private void button1_Click(object sender, EventArgs e)

        {

            commandString = new SqlCommand();

            commandString.CommandText = "Select * from widok";

            commandString.Connection = myConnection;

            dataAdapter = new SqlDataAdapter(commandString.CommandText, myConnection);

            commandString.Connection.Open();

            dt = new DataTable();

            dataAdapter.Fill(dt);

            dataGridView1.DataSource = dt;

            commandString.Connection.Close();

            c = 1;


        }


        private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)

        {

            a = dataGridView1.SelectedCells[0].ColumnIndex;

            b = dataGridView1.SelectedCells[0].RowIndex;


            button2.Enabled = true;


        }


        private void button2_Click(object sender, EventArgs e)

        {


            if (a != 3)

            {

                DataRow row;

                row = dt.Rows[b];

                ciag1 = dataGridView1.Columns[a].HeaderText;  

                ciag2 = (b + 1).ToString();

                ciag3 = dataGridView1.Rows[b].Cells[a].Value.ToString();


                commandString = new SqlCommand();

                commandString.CommandText = String.Format("update Student SET {0}='{1}' where idStudent={2}", ciag1, ciag3, ciag2);

                commandString.Connection = myConnection;

                commandString.Connection.Open();

                dataAdapter.UpdateCommand = commandString;

                dataAdapter.Update(dt);

                label1.Text = row.RowState.ToString();

                dataGridView1.DataSource = dt;


                commandString.Connection.Close();

                button2.Enabled = false;


            }

            else if (a == 3)

            {

                DataRow row;

                row = dt.Rows[b];

                ciag3 = (b + 1).ToString();


                commandString = new SqlCommand();

                commandString.Connection = myConnection;


                if (comboBox1.SelectedIndex == 0)

                {

                    ciag2 = "1";


                    commandString.CommandText = String.Format("update Student SET idWydzial={0} where idStudent={1}", ciag2, ciag3);

                    label1.Text = commandString.CommandText;

                }


                commandString.Connection.Open();

                dataAdapter.UpdateCommand = commandString;

                dataAdapter.Update((DataTable)dataGridView1.DataSource);

                //label1.Text = row.RowState.ToString();

                dataGridView1.DataSource = dt;


                commandString.Connection.Close();

                button2.Enabled = false;


               }

          }


        private void textBox1_TextChanged(object sender, EventArgs e)

        {


        }


        private void dataGridView2_CellContentClick(object sender, DataGridViewCellEventArgs e)

        {


        }

    }

}

-- Dodane 22.05.2011 (N) 14:08 --

Odkryłem właśnie gdzie leży przyczyna. Chodzi o to, że żeby zmiany się zatwierdziły, muszę cokolwiek wpisać w komórkę którą chcę zmienić. Dopiero potem jak kliknę na przycisk nazwa wydziału zmieni mi się na nazwę pierwszego wydziału.

Teraz myślę nad tym jak ten problem rozwiązać, żeby nie trzeba było tej komórki edytować, i żeby zmiany zaszły tylko po naciśnięciu przycisku.


(Tomek Matz) #16

Masz rację. Jeśli użytkownik nie ma możliwości edycji danych bezpośrednio w dataGridView to RowState zawsze będzie Unchanged. Ale już pomijając to, to i tak użycie tego adaptera było jednak błędne. Zupełnie zapomniałem, że Ty dane pobierasz z perspektywy, a nie bezpośrednio z tabeli. Nie można aktualizować widoków. No i jeszcze trzeba dodać fakt, że jako parametr do metody Update przekazywany był obiekt DataTable utworzony w oparciu o perspektywę, a instrukcja SQL odnosiła się do tabeli Student. Tak też nie można robić. Jeśli adapter został utworzony dla jakiejś tabeli, to instrukcje Select, Update, Delete, Insert przypisane do właściwości SelectCommand, UpdateCommand itd. też muszą się odnosić do tej tabeli.

Wersja kodu, która raczej Ci zadziała:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Data.SqlClient;


namespace WindowsFormsApplication1

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }


        private void Form1_Load(object sender, EventArgs e)

        {

            button1.Enabled = true;

            button2.Enabled = false;

        }


        private void button1_Click(object sender, EventArgs e)

        {

            dataGridView1.DataSource = Fill("SELECT * FROM widok");

            button1.Enabled = false;

        }


        private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)

        {

            button2.Enabled = true;

        }


        private void button2_Click(object sender, EventArgs e)

        {

            string ciag1, ciag2, ciag3 = string.Empty;

            int columnIndex = dataGridView1.SelectedCells[0].ColumnIndex;

            int rowIndex= dataGridView1.SelectedCells[0].RowIndex;


            button2.Enabled = false;


            if (columnIndex != 3)

            {

                ciag1 = dataGridView1.Columns[columnIndex].HeaderText;

                ciag2 = dataGridView1.Rows[rowIndex].Cells["idStudent"].Value.ToString();

                ciag3 = textBox1.Text;


                ExecuteNonQuery(string.Format("UPDATE Student SET {0}='{1}' WHERE idStudent={2}", ciag1, ciag3, ciag2));

                dataGridView1.DataSource = Fill();

            }

            else

            {

                ciag3 = dataGridView1.Rows[rowIndex].Cells["idStudent"].Value.ToString();


                if (comboBox1.SelectedIndex == 0)

                {

                    ciag2 = "1";

                    ExecuteNonQuery(string.Format("UPDATE Student SET idWydzial={0} WHERE idStudent={1}", ciag2, ciag3));

                    dataGridView1.DataSource = Fill();

                }

            }

        }


        public SqlConnection GetConnection()

        {

            return new SqlConnection("Data Source=EUREKA-PC;Initial Catalog=Wydzialy;Integrated Security=True");

        }


        public DataTable Fill()

        {

            return Fill("SELECT * FROM widok");

        }


        public DataTable Fill(string sql)

        {

            DataTable tbl = null;

            SqlConnection conn = null;

            SqlDataAdapter dataAdapter = null;


            try

            {

                conn = GetConnection();

                dataAdapter = new SqlDataAdapter(new SqlCommand(sql, conn));

                tbl = new DataTable();


                conn.Open();

                dataAdapter.Fill(tbl);


                return tbl;

            }

            catch (Exception ex)

            {

                throw ex;

            }

            finally

            {

                if (conn != null && conn.State == ConnectionState.Open)

                    conn.Close();

            }

        }


        public int ExecuteNonQuery(string sql)

        {

            SqlConnection conn = null;

            SqlCommand cmd = null;


            try

            {

                conn = GetConnection();

                cmd = new SqlCommand(sql, conn);


                conn.Open();

                return cmd.ExecuteNonQuery();

            }

            catch (Exception ex)

            {

                throw ex;

            }

            finally

            {

                if (conn != null && conn.State == ConnectionState.Open)

                    conn.Close();

            }

        }

    }

}

A teraz uwagi:

  1. Nazwy tabel nie powinny zawierać spacji, ani polskich znaków (najlepiej jest tworzyć angielskie nazwy).

  2. Kod operujący na bazie danych powinien zostać umieszczony w osobnym pliku (a najlepiej osobnym pakiecie). Poczytaj o data access layer.

Aktualnie panuje tendencja do używania ORM-ów, np. NHibernate, czy też ADO.NET Entity Framework, ale czyste ADO .NET też warto znać (a nawet trzeba znać).

  1. Nie nazywaj zmiennych a,b,c itd. Używaj nazw, które mówią o przeznaczeniu danej zmiennej, bo inaczej zrozumienie Twojego kodu będzie trudne lub wręcz niemożliwe.

  2. Zacznij stosować obsługę wyjątków (try, catch, finally)

  3. Przy użyciu string.Format nie należy składać zapytań SQL. Jeśli zapytanie ma przyjmować parametr to zapisuje się go przy użyciu @, a następnie używa się obiektu takiej klasy http://msdn.microsoft.com/en-us/library ... meter.aspx

  4. Datagridview pozwala na edycję komórek, ich usuwanie, itd. dlaczego więc z tego nie korzystasz, a tworzysz jakieś dodatkowe textboxy,

comboboxy itd?


(eureka 170) #17

Dzięki naprawdę ogromne matzu za pomoc. W końcu ten program działa jak należy :slight_smile: