Laden...

Problem der Neuzeichnung einer DataGridView in einer Forms App

Letzter Beitrag vor 3 Monaten 6 Posts 347 Views
Problem der Neuzeichnung einer DataGridView in einer Forms App

Ich programmiere eine Kalenderapplikation als Forms App, und dabei werden in einer dataGridAppointmentsOnClickedDay die Termine dargestellt. Es ist möglich, diese zu löschen, und zwar durch den Eventlistener DataGridView_CellClick. Ich frage auch ab, ob man einen Serientermin ("y" für yearly / jährlich) bzw. einen einzelnen Termin innerhalb dieses Serientermins löschen soll. Wenn ich nach dem Löschen die DrawAppointmentsForFreshMonth( - Methode aufrufe, wird die DataGridView dataGridViewAppointmentsOnClickedDay nicht neugezeichnet, erst dann, wenn ich die Application neu starte. Woran könnte das liegen? Aus dem Debuggen bin ich nicht schlau geworden. Was muss ich tun, dass auch wenn die Schleife else if (result == DialogResult.No) durchlaufen wird, die Methode  DrawAppointmentsForFreshMonth( derart aufgerufen wird, dass die DataGridView neu gezeichnet wird?


        private void DataGridView_CellClick(object sender, DataGridViewCellEventArgs e)
        {

            if (e.RowIndex >= 0)
            {
                if (e.ColumnIndex == dataGridViewAppointmentsOnClickedDay.Columns["xColumn"].Index)
                {

                    // Zugriff auf die aktuelle Reihe
                    DataGridViewRow row = ((DataGridView)sender).Rows[e.RowIndex];


                    string idToDelete = row.Cells["id"].Value.ToString();

                    string whichRepetition = row.Cells["repeat"].Value.ToString();

                    if ("y".Equals(whichRepetition) || "m".Equals(whichRepetition))
                    {

                        // Abfrage: Serie oder einzelnes Ereignis löschen
                        string askDelete = resourceManager.GetString("Do you want to delete the entire series (yes) or just this individual event (no)?");
                        string confirm = resourceManager.GetString("Confirm deletion");

                        DialogResult result = MessageBox.Show(
                            askDelete,
                            confirm,
                            MessageBoxButtons.YesNoCancel,
                            MessageBoxIcon.Question,
                            MessageBoxDefaultButton.Button3);

                        // Entscheidung anhand der Auswahl des Benutzers
                        if (result == DialogResult.Yes)
                        {
                            // Ganze Serie löschen
                            dateDao.DeleteEntryById(idToDelete);
                        }
                        else if (result == DialogResult.No)
                        {
                            string exception_start = row.Cells["start"].Value.ToString();
                            DateTime selectedDate = monthCalendar.SelectionStart;

                            string[] dateParts = exception_start.Split('.');
                            int day = Convert.ToInt32(dateParts[0]);
                            int month = Convert.ToInt32(dateParts[1]);
                            int year = Convert.ToInt32(dateParts[2].Substring(0, 4));

                            DateTime newDateTime = new DateTime(selectedDate.Year, selectedDate.Month, day);
                            string formattedDate = newDateTime.ToString("dd.MM.yyyy");

                            exception_start = formattedDate;

                            string exception_end = row.Cells["end"].Value.ToString();
                            selectedDate = monthCalendar.SelectionStart;

                            dateParts = exception_end.Split('.');
                            day = Convert.ToInt32(dateParts[0]);
                            month = Convert.ToInt32(dateParts[1]);
                            year = Convert.ToInt32(dateParts[2].Substring(0, 4));

                            newDateTime = new DateTime(selectedDate.Year, selectedDate.Month, day);
                            formattedDate = newDateTime.ToString("dd.MM.yyyy");

                            exception_end = formattedDate;

                            dateDao.WriteExceptionIntoExceptionTBL(idToDelete, exception_start, exception_end);

                        }

                        // Nur bei Ja oder Nein neu zeichnen, nicht bei Cancel
                        if (result != DialogResult.Cancel)
                        {
                            int d = monthCalendar.SelectionStart.Day;
                            int m = monthCalendar.SelectionStart.Month;
                            int y = monthCalendar.SelectionStart.Year;

                            //DrawAppointmentsForFreshMonth(d, m, y);
                            //Wenn ich oben die DrawAppointmentsForFreshMonth( - Methode aufrufe,
            //Wird die DataGridView dataGridViewAppointmentsOnClickedDay nicht neugezeichnet, erst dann, wenn 
            //ich die Application neu starte. Woran könnte das liegen? Aus dem Debuggen bin 
            //ich nicht schlau geworden.

                            Application.Restart(); 
                            Environment.Exit(0);
                        }
                    }
                    else
                    {
                        dateDao.DeleteEntryById(idToDelete);
                        int d = monthCalendar.SelectionStart.Day;
                        int m = monthCalendar.SelectionStart.Month;
                        int y = monthCalendar.SelectionStart.Year;
                        DrawAppointmentsForFreshMonth(d, m, y);
                    }
                }
                else
                {

                    // Zugriff auf die aktuelle Reihe
                    DataGridViewRow row = ((DataGridView)sender).Rows[e.RowIndex];
                    string id = row.Cells["id"].Value.ToString();

                    DetailsForm detailsForm = new DetailsForm(id, this);
                    detailsForm.ShowDialog();

                }
            }
        }

        private void DataGridViewAppointmentsOnClickedDay_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            // Überprüfen, ob es sich um die Spalten "start" oder "end" handelt
            if (dataGridViewAppointmentsOnClickedDay.Columns[e.ColumnIndex].Name == "start" ||
                dataGridViewAppointmentsOnClickedDay.Columns[e.ColumnIndex].Name == "end")
            {
                // Hole das Startdatum aus der Zelle
                DateTime startDate = DateTime.ParseExact(dataGridViewAppointmentsOnClickedDay.Rows[e.RowIndex].Cells["start"].Value.ToString(), "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture);

                // Hole das Enddatum aus der Zelle
                DateTime endDate = DateTime.ParseExact(dataGridViewAppointmentsOnClickedDay.Rows[e.RowIndex].Cells["end"].Value.ToString(), "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture);

                // Vergleiche die Daten mit dem aktuellen Datum
                if (startDate.Date <= DateTime.Now.Date && endDate.Date >= DateTime.Now.Date)
                {
                    // Setze die Hintergrundfarbe der gesamten Zeile auf Hellblau
                    dataGridViewAppointmentsOnClickedDay.Rows[e.RowIndex].DefaultCellStyle.BackColor = System.Drawing.Color.LightBlue;
                }
            }
        }

        private void DataGridView_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
        {
            for (int i = e.RowIndex; i < e.RowIndex + e.RowCount; i++)
            {
                // Setzen des Wertes "x" für jede neue Zeile in der "xColumn" Spalte
                dataGridViewAppointmentsOnClickedDay.Rows[i].Cells["xColumn"].Value = "x";
            }
        }
}
  1. Wenn Du mit DateTime arbeitest, wirst Du sehr wahrscheinlich in solchen Fällen in den typsichen Zeitzonen-Fehler laufen, siehe [FAQ] DateTime vs. DateTimeOffset und der Umgang mit Zeiten in .NET
  2. Wenn Du unbedingt DateTime verwenden willst, fehlt Dir überall der DateTime.Kind
  3. Solche Dinge wie ein String.Split mit einem Datum auf "." zu teilen, funktioniert vielleicht mit dem deutschen Datumsformat, aber dafür gibts DateTimeOffset.TryParse.

Die bessere Idee sind eigentlich Bindings, spricht Du hälst die Daten in einem State und die UI rendert die State.
Was Du machst ist die UI als "Speicher" zu missbrauchen. Das macht sehr viel sehr umständlich.


Du sprichst zwar von der DrawAppointmentsForFreshMonth Methode, Du verwendest sie auch, aber Du hast im Code die Methode nirgends hinterlegt. Da wir keine Glaskugel haben, solltest Du den Inhalt der Methode schon zeigen, sonst machts kein Sinn.

Ob Du Methode aufgerufen wird, kannst Du selbst mit dem Debugger testen.
[Artikel] Debugger: Wie verwende ich den von Visual Studio?

PS: Dein Code ist sehr sehr umständlich und wenn man als Aussenstehender drauf schaut, weiß man nicht wirklich warum Du was tust.

Das Neuzeichnen mußt du selbst im Code anstoßen, d.h. mittels Invalidate(), Refresh() oder Update()(lies dir die Unterschiede in der Doku dazu durch).

PS:

Statt direkt das DataGridView auf einer Form zu benutzen, solltest du bessere eine eigene Kalender-Komponente (als UserControl oder von DataGridView abgeleitetes Steuerelement) erzeugen.

Dein Code selbst ist auch noch verbesserungsfähig:
- Trennung von UI und Logik: benutze DataBinding mittels DataSource
- dementsprechend Auslagerung von Code in eigene Klassen/Methoden
- inkonsistente Datumsbehandlung (Split vs. Parse(Exact)): bei Verwendung von direkt DateTime(Offset)als Datenwert aber eh unnötig
- Performanceoptimierung im CellFormatting-Ereignis (unnötige mehrfache Farbzuweisung je Zeile)

Vielen Dank für die Tipps. Ich werde mir das nun alles einmal ansehen. Anbei liefere ich noch meine Methode DrawAppointmentsForFreshMonth:

   private void DrawAppointmentsForFreshMonth(int day, int month, int year)
 {
    
     DrawAppointmentsOnClickedDay(day, month, year, dateDao);

 }

 public void DrawAppointmentsOnClickedDay(int day, int month, int year, DateDao dateDao)
 {

     Controls.Remove(dataGridViewAppointmentsOnClickedDay);

     dataGridViewAppointmentsOnClickedDay = new DataGridView();

     dataGridViewAppointmentsOnClickedDay.CellFormatting += DataGridViewAppointmentsOnClickedDay_CellFormatting;

     dataGridViewAppointmentsOnClickedDay.Location = new System.Drawing.Point(250, 10);
     dataGridViewAppointmentsOnClickedDay.Size = new System.Drawing.Size(600, 250);

     dataGridViewAppointmentsOnClickedDay.ScrollBars = ScrollBars.Vertical;
     dataGridViewAppointmentsOnClickedDay.AllowUserToAddRows = false;
     dataGridViewAppointmentsOnClickedDay.ReadOnly = true;
     dataGridViewAppointmentsOnClickedDay.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

     dataGridViewAppointmentsOnClickedDay.CellClick += DataGridView_CellClick;

     appointments = dateDao.GetDatesFor(day, month, year);


     int selectedMonth = monthCalendar.SelectionStart.Month;
     int selectedYear = monthCalendar.SelectionStart.Year;
     GetMonthsAppointments(selectedMonth, selectedYear);

     dataGridViewAppointmentsOnClickedDay.DataSource = appointments;

     DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn();
     column.DataPropertyName = "id";
     column.Name = "id";
     dataGridViewAppointmentsOnClickedDay.Columns.Add(column);
     dataGridViewAppointmentsOnClickedDay.Columns["id"].Visible = false;

     column = new DataGridViewTextBoxColumn();
     column.Name = "xColumn";
     column.HeaderText = "";
     dataGridViewAppointmentsOnClickedDay.Columns.Add(column);

     column = new DataGridViewTextBoxColumn();
     column.HeaderText = resourceManager.GetString("Appointment");
     column.DataPropertyName = "text";
     column.Name = "text";
     dataGridViewAppointmentsOnClickedDay.Columns.Add(column);

     column = new DataGridViewTextBoxColumn();
     column.DataPropertyName = "start";
     column.Name = "start";
     dataGridViewAppointmentsOnClickedDay.Columns.Add(column);

     column = new DataGridViewTextBoxColumn();
     column.DataPropertyName = "end";
     column.Name = "end";
     dataGridViewAppointmentsOnClickedDay.Columns.Add(column);

     column = new DataGridViewTextBoxColumn();
     column.HeaderText = resourceManager.GetString("repetition");
     column.DataPropertyName = "repeat";
     column.Name = "repeat";
     dataGridViewAppointmentsOnClickedDay.Columns.Add(column);

     dataGridViewAppointmentsOnClickedDay.RowsAdded += new DataGridViewRowsAddedEventHandler(DataGridView_RowsAdded);

     Controls.Add(dataGridViewAppointmentsOnClickedDay);


     LoadHolidays();

     CreateCalendar(year, month, day);

 }

Warum erzeugst du denn in dieser Methode jedesmal ein neues DataGridView-Objekt?

Ich habe meinen Code nun überarbeitet:

Es gibt eine InitializeGird-Methode:


 protected override async void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            InitializeGrid();

        }

Diese baut die DataGridView (== UI):

        private void InitializeGrid() {

            dataGridViewAppointmentsOnClickedDay = new DataGridView();

            dataGridViewAppointmentsOnClickedDay.CellFormatting += DataGridViewAppointmentsOnClickedDay_CellFormatting;

            dataGridViewAppointmentsOnClickedDay.Location = new System.Drawing.Point(250, 10);
            dataGridViewAppointmentsOnClickedDay.Size = new System.Drawing.Size(600, 250);

            dataGridViewAppointmentsOnClickedDay.ScrollBars = ScrollBars.Vertical;
            dataGridViewAppointmentsOnClickedDay.AllowUserToAddRows = false;
            dataGridViewAppointmentsOnClickedDay.ReadOnly = true;
            dataGridViewAppointmentsOnClickedDay.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

            dataGridViewAppointmentsOnClickedDay.CellClick += DataGridView_CellClick;

            DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn();
            column.DataPropertyName = "id";
            column.Name = "id";
            dataGridViewAppointmentsOnClickedDay.Columns.Add(column);
            dataGridViewAppointmentsOnClickedDay.Columns["id"].Visible = false;

Nun nur Datenlogik. Diese ist in eine Methode UpdateAppointments verlegt sowie in die folgenden Methoden. Das Problem ist: Ich muss erst ein Datum im Kalender selektieren (welches nicht das heutige Datum ist), dann erst wird die DataGridView gebaut. Und erst wenn ich dann ein zweites, abweicendes, Datum selektiere, werden die Feiertage (qua Nager.Holiday) in den Kalender geladen. Ich habe in der OnLoad-Methode herumexperimentiert, nichtsdestotrotz weiß ich nicht, was ich mir jetzt ankucken soll, damit es sofort flutscht, also sowohl die GridView als auch der Kalender (mit den Feiertagen) gleich erscheint, sobakd die View geladen ist. Ich bin für diesbezügliche Tipps sehr dankbar.

private void UpdateAppointments(int day, int month, int year)

{
  appointments = dateDao.GetDatesFor(day, month, year);
  GetMonthsAppointments(monthCalendar.SelectionStart.Month, 
  monthCalendar.SelectionStart.Year);
  dataGridViewAppointmentsOnClickedDay.DataSource = appointments;

}
...

private void CreateCalendar()
{
//Kalender

monthCalendar = new MonthCalendar
{

CalendarDimensions = new Size(1, 1),
Location = new Point(10, 40)

};

monthCalendar.DateChanged += MonthCalendar_DateChanged;

d = monthCalendar.SelectionStart.Day; m = monthCalendar.SelectionStart.Month; y = monthCalendar.SelectionStart.Year;

Controls.Add(monthCalendar);

}
…

public void DrawAppointmentsOnClickedDay(int day, int month, int year, DateDao dateDao)
{

 UpdateAppointments(day, month, year);

 LoadHolidays();
 
 CreateCalendar(year, month, day);

}
private void DataGridView_CellClick(object sender, DataGridViewCellEventArgs e)

{

if (e.RowIndex >= 0)

{

if (e.ColumnIndex == dataGridViewAppointmentsOnClickedDay.Columns["xColumn"].Index)
{

 // Zugriff auf die aktuelle Reihe

 DataGridViewRow row = ((DataGridView)sender).Rows[e.RowIndex];
 string idToDelete = row.Cells["id"].Value.ToString();
 string whichRepetition = row.Cells["repeat"].Value.ToString();

if ("y".Equals(whichRepetition) || "m".Equals(whichRepetition))
 {
  // Abfrage: Serie oder einzelnes Ereignis löschen

  string askDelete = resourceManager.GetString("Do you want to delete the entire series (yes) or just this individual event (no)?");
  string confirm = resourceManager.GetString("Confirm deletion");

  DialogResult result = MessageBox.Show(
  askDelete,
  confirm,
  MessageBoxButtons.YesNoCancel,
  MessageBoxIcon.Question,
  MessageBoxDefaultButton.Button3);

// Entscheidung anhand der Auswahl des Benutzers

if (result == DialogResult.Yes)
{
 // Ganze Serie löschen
 dateDao.DeleteEntryById(idToDelete);
}

else if (result == DialogResult.No)

{

string exception_start = row.Cells["start"].Value.ToString();

DateTime selectedDate = monthCalendar.SelectionStart;

string[] dateParts = exception_start.Split('.');

int day = Convert.ToInt32(dateParts[0]);

int month = Convert.ToInt32(dateParts[1]);

int year = Convert.ToInt32(dateParts[2].Substring(0, 4));




DateTime newDateTime = new DateTime(selectedDate.Year, selectedDate.Month, day);

string formattedDate = newDateTime.ToString("dd.MM.yyyy");

exception_start = formattedDate;

string exception_end = row.Cells["end"].Value.ToString();

selectedDate = monthCalendar.SelectionStart;

dateParts = exception_end.Split('.'); //<- Da werde ich mich noch drum kümmern.

day = Convert.ToInt32(dateParts[0]);
month = Convert.ToInt32(dateParts[1]);
year = Convert.ToInt32(dateParts[2].Substring(0, 4));

newDateTime = new DateTime(selectedDate.Year, selectedDate.Month, day);
formattedDate = newDateTime.ToString("dd.MM.yyyy");
exception_end = formattedDate;
dateDao.WriteExceptionIntoExceptionTBL(idToDelete, exception_start, exception_end);
}




// Nur bei Ja oder Nein neu zeichnen, nicht bei Cancel
if (result != DialogResult.Cancel)
{
dateDao.DeleteEntryById(idToDelete);

UpdateAppointments(monthCalendar.SelectionStart.Day,

monthCalendar.SelectionStart.Month,

monthCalendar.SelectionStart.Year);

dataGridViewAppointmentsOnClickedDay.Refresh();




int d = monthCalendar.SelectionStart.Day;

int m = monthCalendar.SelectionStart.Month;

int y = monthCalendar.SelectionStart.Year;




DrawAppointmentsOnClickedDay(d, m, y, dateDao);

//Wenn das oben nicht geht, dann mache ich es eben so...:

//Application.Restart();

//Environment.Exit(0);

}

}

else

{

dateDao.DeleteEntryById(idToDelete);

int d = monthCalendar.SelectionStart.Day;

int m = monthCalendar.SelectionStart.Month;

int y = monthCalendar.SelectionStart.Year;

//DrawAppointmentsForFreshMonth(d, m, y);

DrawAppointmentsOnClickedDay(d, m, y, dateDao);

}

}

else {
// Zugriff auf die aktuelle Reihe

DataGridViewRow row = ((DataGridView)sender).Rows[e.RowIndex];

string id = row.Cells["id"].Value.ToString();
DetailsForm detailsForm = new DetailsForm(id, this);
detailsForm.ShowDialog();
  }
 }
}
…

private async void LoadHolidays()

{
 string currentCulture = Thread.CurrentThread.CurrentCulture.Name;
 string[] cultureParts = currentCulture.Split('-');
 string region = cultureParts.Length > 1 ? cultureParts[1] : "DE";

  var holidayClient = new HolidayClient();
  try
  {

    PublicHoliday[] phs = await holidayClient.GetHolidaysAsync(monthCalendar.SelectionStart.Year, region);
    holidays = phs.ToList();
}

catch (Exception ex)

  {

   MessageBox.Show("Fehler beim Abrufen der Feiertage: " + ex.Message);

  }

}