diff --git a/app/TaskFlow/ControlCreator/GridControl.vb b/app/TaskFlow/ControlCreator/GridControl.vb index 87ae10e..918b71d 100644 --- a/app/TaskFlow/ControlCreator/GridControl.vb +++ b/app/TaskFlow/ControlCreator/GridControl.vb @@ -25,7 +25,8 @@ Namespace ControlCreator Private newRowModified As Boolean Private isApplyingInheritedValue As Boolean - + Private _FormulaColumnNames As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase) + Private _isRefreshingFormula As Boolean = False ' *** NEU: Flag für Formel-Refresh *** Public Sub New(pLogConfig As LogConfig, pGridTables As Dictionary(Of Integer, Dictionary(Of String, RepositoryItem))) _LogConfig = pLogConfig _Logger = pLogConfig.GetLogger() @@ -315,7 +316,7 @@ Namespace ControlCreator settings.MaskExpression = "c" settings.Culture = oCultureInfo End Sub) - riTextEdit.UseMaskAsDisplayFormat = True 'Optional + riTextEdit.UseMaskAsDisplayFormat = True pGrid.RepositoryItems.Add(riTextEdit) For Each oCol As GridColumn In pGridView.Columns @@ -327,6 +328,14 @@ Namespace ControlCreator Continue For End If + ' Formel-Spalten dürfen kein ColumnEdit bekommen, da der RepositoryItem-Cache + ' den berechneten Wert nach RefreshRowCell überschreibt. + Dim oFormulaExpression = ObjectEx.NotNull(oColumnData.Item("FORMULA_EXPRESSION"), String.Empty) + If oFormulaExpression <> String.Empty Then + _Logger.Debug("Skipping ColumnEdit assignment for formula column [{0}] – using DisplayFormat only.", oCol.FieldName) + Continue For + End If + Dim oColumnType As String = oColumnData.Item("TYPE_COLUMN") Select Case oColumnType @@ -337,6 +346,15 @@ Namespace ControlCreator Next End Sub Public Sub ConfigureViewEvents(pColumnTable As DataTable, pGridView As GridView, pControl As Windows.Forms.Control, pControlId As Integer) + ' Formel-Spalten-Namen einmalig cachen für View_ShowingEditor + _FormulaColumnNames.Clear() + For Each r As DataRow In pColumnTable.Rows + Dim oExpr = ObjectEx.NotNull(r.Item("FORMULA_EXPRESSION"), String.Empty).ToString() + If oExpr <> String.Empty Then + _FormulaColumnNames.Add(r.Item("SPALTENNAME").ToString()) + End If + Next + AddHandler pGridView.InitNewRow, Sub(sender As Object, e As InitNewRowEventArgs) Try _Logger.Debug("Initialzing new row") @@ -394,30 +412,233 @@ Namespace ControlCreator End Sub AddHandler pGridView.PopupMenuShowing, AddressOf View_PopupMenuShowing - AddHandler pGridView.InvalidRowException, AddressOf View_InvalidRowException AddHandler pGridView.ValidatingEditor, AddressOf View_ValidatingEditor - ' AddHandler pGridView.CustomColumnDisplayText, AddressOf View_CustomColumnDisplayText + AddHandler pGridView.ShownEditor, + Sub(sender As Object, e As EventArgs) + Dim view As GridView = TryCast(sender, GridView) + If view.IsNewItemRow(view.FocusedRowHandle) Then + _Logger.Debug("Attaching Modified Handler.") + AddHandler view.ActiveEditor.Modified, Sub() + _Logger.Debug("Row was modified.") + newRowModified = True + End Sub + End If + + ' *** KRITISCH: LIVE-REFRESH bei JEDER Eingabe (auch NewItemRow!) *** + If view.FocusedColumn IsNot Nothing AndAlso view.ActiveEditor IsNot Nothing Then + Dim oFocusedColumnName As String = view.FocusedColumn.FieldName + + ' Prüfen ob diese Spalte von Formel-Spalten referenziert wird + Dim oFormulaColumnsToRefresh As New List(Of String) + For Each oColumnData As DataRow In pColumnTable.Rows + Dim oExpr = ObjectEx.NotNull(oColumnData.Item("FORMULA_EXPRESSION"), String.Empty).ToString() + If oExpr = String.Empty Then Continue For + + Dim referencedColumns = GetReferencedColumnNames(oExpr) + If referencedColumns.Any(Function(col) String.Equals(col, oFocusedColumnName, StringComparison.OrdinalIgnoreCase)) Then + oFormulaColumnsToRefresh.Add(oColumnData.Item("SPALTENNAME").ToString()) + End If + Next + + If oFormulaColumnsToRefresh.Count > 0 Then + _Logger.Debug("[FormulaRefresh] Attaching EditValueChanged handler for LIVE formula updates on column [{0}].", oFocusedColumnName) + + ' Handler für LIVE Aktualisierung während JEDER Eingabe + AddHandler view.ActiveEditor.EditValueChanged, + Sub(editorSender As Object, editorArgs As EventArgs) + Try + Dim oRowHandle As Integer = view.FocusedRowHandle + If Not view.IsValidRowHandle(oRowHandle) Then Return + + Dim oEditor As DevExpress.XtraEditors.BaseEdit = TryCast(editorSender, DevExpress.XtraEditors.BaseEdit) + If oEditor Is Nothing Then Return + + Dim isNewItemRow As Boolean = view.IsNewItemRow(oRowHandle) + + Try + Dim oValue As Object = oEditor.EditValue + + If TypeOf oEditor Is DevExpress.XtraEditors.TextEdit Then + Dim oTextEdit As DevExpress.XtraEditors.TextEdit = DirectCast(oEditor, DevExpress.XtraEditors.TextEdit) + If oTextEdit.Properties.MaskSettings IsNot Nothing Then + oValue = oEditor.EditValue + End If + End If + + If isNewItemRow Then + _Logger.Debug("[FormulaRefresh] EditValueChanged (NewItemRow) – setting value for [{0}] = [{1}].", oFocusedColumnName, oValue) + + _isRefreshingFormula = True + Try + ' Wert setzen + view.SetRowCellValue(oRowHandle, oFocusedColumnName, If(oValue Is Nothing, DBNull.Value, oValue)) + + ' *** KRITISCH: DoEvents() damit SetRowCellValue processed wird *** + view.UpdateCurrentRow() + + ' Formel-Spalten SOFORT refreshen + For Each oFormulaColumnName As String In oFormulaColumnsToRefresh + Dim oGridColumn As GridColumn = view.Columns.ColumnByFieldName(oFormulaColumnName) + If oGridColumn IsNot Nothing Then + view.RefreshRowCell(oRowHandle, oGridColumn) + _Logger.Debug("[FormulaRefresh] (NewItemRow) Refreshed [{0}], current value: [{1}]", + oFormulaColumnName, view.GetRowCellValue(oRowHandle, oGridColumn)) + End If + Next + Finally + _isRefreshingFormula = False + End Try + + Else + ' Bestehende Row + Dim oDataRow As DataRow = view.GetDataRow(oRowHandle) + If oDataRow IsNot Nothing Then + _Logger.Debug("[FormulaRefresh] EditValueChanged – setting value for [{0}] in DataTable.", oFocusedColumnName) + oDataRow.Item(oFocusedColumnName) = If(oValue Is Nothing, DBNull.Value, oValue) + + For Each oFormulaColumnName As String In oFormulaColumnsToRefresh + Dim oGridColumn As GridColumn = view.Columns.ColumnByFieldName(oFormulaColumnName) + If oGridColumn IsNot Nothing Then + view.RefreshRowCell(oRowHandle, oGridColumn) + Dim currentValue = view.GetRowCellValue(oRowHandle, oGridColumn) + _Logger.Debug("[FormulaRefresh] Current value for [{0}]: [{1}]", oFormulaColumnName, currentValue) + End If + Next + End If + End If + + Catch parseEx As Exception + _Logger.Debug("[FormulaRefresh] Parse error during EditValueChanged: {0}", parseEx.Message) + End Try + + Catch ex As Exception + _Logger.Error(ex) + End Try + End Sub + End If + End If + End Sub - ' These handlers are all used for the custom DefaultValue functionality, additionally some code in the 'InitNewRow' event. - ' https://supportcenter.devexpress.com/ticket/details/t1035580/how-to-default-a-value-in-a-column-when-add-new-row-in-data-grid - AddHandler pGridView.ShowingEditor, AddressOf View_ShowingEditor - AddHandler pGridView.ShownEditor, AddressOf View_ShownEditor AddHandler pGridView.ValidateRow, AddressOf View_ValidateRow AddHandler pControl.LostFocus, AddressOf Control_LostFocus + + AddHandler pGridView.ShowingEditor, + Sub(sender As Object, e As CancelEventArgs) + Try + Dim oView As GridView = TryCast(sender, GridView) + If oView Is Nothing Then Return + + _Logger.Debug("Showing editor.") + + ' Formel-Spalten dürfen keinen Editor öffnen + If oView.FocusedColumn IsNot Nothing Then + Dim oFieldName As String = oView.FocusedColumn.FieldName + If _FormulaColumnNames.Contains(oFieldName) Then + _Logger.Debug("Cancelling editor – column [{0}] is a formula column.", oFieldName) + e.Cancel = True + Return + End If + End If + + If oView.IsNewItemRow(oView.FocusedRowHandle) AndAlso Not newRowModified Then + _Logger.Debug("Adding new row.") + oView.AddNewRow() + End If + Catch ex As Exception + _Logger.Error(ex) + End Try + End Sub + + AddHandler pGridView.FocusedColumnChanged, + Sub(sender As Object, e As FocusedColumnChangedEventArgs) + Try + Dim oView As GridView = TryCast(sender, GridView) + If oView Is Nothing Then Return + + Dim oRowHandle As Integer = oView.FocusedRowHandle + If oView.IsNewItemRow(oRowHandle) Then Return + If Not oView.IsValidRowHandle(oRowHandle) Then Return + + If oView.FocusedColumn IsNot Nothing AndAlso + _FormulaColumnNames.Contains(oView.FocusedColumn.FieldName) Then + _Logger.Debug("[FormulaRefresh] FocusedColumnChanged – closing editor on formula column [{0}].", oView.FocusedColumn.FieldName) + oView.CloseEditor() + End If + Catch ex As Exception + _Logger.Error(ex) + End Try + End Sub + AddHandler pGridView.CellValueChanged, Sub(sender As Object, e As CellValueChangedEventArgs) + ' *** HandleInheritedColumnValue MUSS zuerst aufgerufen werden *** Try HandleInheritedColumnValue(TryCast(sender, GridView), pColumnTable, e) Catch ex As Exception _Logger.Error(ex) End Try + + ' *** Formel-Refresh via CellValueChanged ist FALLBACK *** + ' (EditValueChanged macht das normalerweise schon LIVE) + Try + Dim oView As GridView = TryCast(sender, GridView) + If oView Is Nothing OrElse e.Column Is Nothing Then Return + + ' Prüfen ob überhaupt eine Formel-Spalte referenziert wird + Dim oFormulaColumnsToRefresh As New List(Of String) + + For Each oColumnData As DataRow In pColumnTable.Rows + Dim oExpr = ObjectEx.NotNull(oColumnData.Item("FORMULA_EXPRESSION"), String.Empty).ToString() + If oExpr = String.Empty Then Continue For + + Dim referencedColumns = GetReferencedColumnNames(oExpr) + If referencedColumns.Any(Function(col) String.Equals(col, e.Column.FieldName, StringComparison.OrdinalIgnoreCase)) Then + oFormulaColumnsToRefresh.Add(oColumnData.Item("SPALTENNAME").ToString()) + End If + Next + + If oFormulaColumnsToRefresh.Count = 0 Then + Return + End If + + ' *** FALLBACK: Nur wenn EditValueChanged NICHT gefeuert hat *** + ' (z.B. bei programmatischer SetRowCellValue oder Paste) + Dim oRowHandle As Integer = e.RowHandle + _Logger.Debug("[FormulaRefresh] CellValueChanged FALLBACK – refreshing for row [{0}] after column [{1}] changed.", oRowHandle, e.Column.FieldName) + + oView.GridControl.BeginInvoke(New Action( + Sub() + Try + If Not oView.IsValidRowHandle(oRowHandle) Then Return + + For Each oFormulaColumnName As String In oFormulaColumnsToRefresh + Dim oGridColumn As GridColumn = oView.Columns.ColumnByFieldName(oFormulaColumnName) + If oGridColumn Is Nothing Then Continue For + + oView.RefreshRowCell(oRowHandle, oGridColumn) + _Logger.Debug("[FormulaRefresh] FALLBACK DisplayText for [{0}]: [{1}]", + oFormulaColumnName, oView.GetRowCellDisplayText(oRowHandle, oGridColumn)) + Next + Catch ex As Exception + _Logger.Error(ex) + End Try + End Sub)) + + Catch ex As Exception + _Logger.Error(ex) + End Try End Sub End Sub Private Sub HandleInheritedColumnValue(pView As GridView, pColumnDefinition As DataTable, pArgs As CellValueChangedEventArgs) If pView Is Nothing OrElse pArgs Is Nothing OrElse pArgs.Column Is Nothing Then Return End If + ' *** NEU: Bei Formel-Refresh überspringen *** + If _isRefreshingFormula Then + _Logger.Debug("Skipping HandleInheritedColumnValue during formula refresh.") + Return + End If If isApplyingInheritedValue OrElse pArgs.RowHandle = DevExpress.XtraGrid.GridControl.InvalidRowHandle Then Return @@ -642,28 +863,12 @@ Namespace ControlCreator End If End Sub - Private Sub View_ShowingEditor(sender As Object, e As CancelEventArgs) - Dim view As GridView = TryCast(sender, GridView) - _Logger.Debug("Showing editor.") - If view.IsNewItemRow(view.FocusedRowHandle) AndAlso Not newRowModified Then - _Logger.Debug("Adding new row.") - view.AddNewRow() - End If - End Sub - Private Sub View_ShownEditor(sender As Object, e As EventArgs) - Dim view As GridView = TryCast(sender, GridView) - If view.IsNewItemRow(view.FocusedRowHandle) Then - _Logger.Debug("Attaching Modified Handler.") - AddHandler view.ActiveEditor.Modified, Sub() - _Logger.Debug("Row was modified.") - newRowModified = True - End Sub - End If - End Sub Private Sub View_ValidateRow(sender As Object, e As ValidateRowEventArgs) Dim view As GridView = TryCast(sender, GridView) + ' RowHandle für RowUpdated merken, bevor er sich nach dem Commit ändert + If view.IsNewItemRow(e.RowHandle) AndAlso Not newRowModified Then _Logger.Debug("Deleting unused row") view.DeleteRow(e.RowHandle) diff --git a/app/TaskFlow/clsPatterns.vb b/app/TaskFlow/clsPatterns.vb index 0001157..186869b 100644 --- a/app/TaskFlow/clsPatterns.vb +++ b/app/TaskFlow/clsPatterns.vb @@ -98,8 +98,13 @@ Public Class clsPatterns lookup.Properties.SelectedValues = New List(Of String) From {newValue.ToString()} End If - Case GetType(Windows.Forms.ComboBox) - DirectCast(ctrl, ComboBox).Text = newValue?.ToString() + ' ========== FIX START: Beide ComboBox-Typen unterstützen ========== + Case GetType(System.Windows.Forms.ComboBox) + DirectCast(ctrl, System.Windows.Forms.ComboBox).Text = newValue?.ToString() + + Case GetType(DevExpress.XtraEditors.ComboBoxEdit) + DirectCast(ctrl, DevExpress.XtraEditors.ComboBoxEdit).Text = newValue?.ToString() + ' ========== FIX END ========== Case GetType(CheckBox) If TypeOf newValue Is Boolean Then diff --git a/app/TaskFlow/frmError.vb b/app/TaskFlow/frmError.vb index c657629..ff56382 100644 --- a/app/TaskFlow/frmError.vb +++ b/app/TaskFlow/frmError.vb @@ -2,8 +2,21 @@ Public Class frmError Public ValidatorError As String = "" + Private _isClosing As Boolean = False + Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click - Me.Close() + ' ========== FIX 1: Event-Handler SOFORT deregistrieren ========== + RemoveHandler OK_Button.Click, AddressOf OK_Button_Click + + ' ========== DIAGNOSE: StackTrace ausgeben ========== + Dim st As New StackTrace(True) + LOGGER.Debug($"[frmError] OK_Button_Click aufgerufen von:") + For Each frame As StackFrame In st.GetFrames() + LOGGER.Debug($" {frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name} (Zeile {frame.GetFileLineNumber()})") + Next + ' ========== ENDE DIAGNOSE ========== + + CloseDialog() End Sub Private Sub frmError_Load(sender As Object, e As System.EventArgs) Handles Me.Load @@ -27,7 +40,45 @@ Public Class frmError End If End Sub - Private Sub frmError_Shown(sender As Object, e As System.EventArgs) Handles Me.Shown - Me.Label1.Focus() + Private Sub CloseDialog() + ' ========== FIX 2: Guard mit Dispose-Check ========== + If _isClosing OrElse Me.IsDisposed Then + LOGGER.Debug($"[frmError] CloseDialog blockiert (isClosing={_isClosing}, IsDisposed={Me.IsDisposed})") + Exit Sub + End If + + _isClosing = True + LOGGER.Debug($"[frmError] CloseDialog: Flag gesetzt, starte verzögerten Close") + + ' ========== FIX 3: VERZÖGERTER Close via BeginInvoke ========== + ' KRITISCH: Close wird NACH Abschluss des aktuellen Event-Handlers ausgeführt + Me.BeginInvoke(New Action(Sub() + If Not Me.IsDisposed Then + Me.DialogResult = DialogResult.OK + Me.Close() + LOGGER.Debug($"[frmError] Dialog geschlossen via BeginInvoke") + End If + End Sub)) + ' ========== ENDE FIX 3 ========== End Sub + Private Sub frmError_Shown(sender As Object, e As EventArgs) Handles Me.Shown + Me.Label1.Focus() + LOGGER.Debug($"[frmError] Dialog angezeigt - Enabled: {Me.Enabled}") + End Sub + + Private Sub frmError_Activated(sender As Object, e As EventArgs) Handles Me.Activated + LOGGER.Debug($"[frmError] Dialog aktiviert") + End Sub + ' ========== FIX 4: FormClosing-Handler hinzufügen ========== + Private Sub frmError_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing + If _isClosing Then + LOGGER.Debug($"[frmError] FormClosing: Close bereits aktiv, erlauben") + Return + End If + + ' Falls Close von außen (z.B. [X]-Button) ausgelöst wurde + _isClosing = True + LOGGER.Debug($"[frmError] FormClosing: Flag gesetzt via FormClosing") + End Sub + ' ========== ENDE FIX 4 ========== End Class diff --git a/app/TaskFlow/frmValidator.vb b/app/TaskFlow/frmValidator.vb index 88c4a1c..77b7cab 100644 --- a/app/TaskFlow/frmValidator.vb +++ b/app/TaskFlow/frmValidator.vb @@ -136,6 +136,10 @@ Public Class frmValidator Private _isUpdatingLookup As Boolean = False Private _suppressLookupEvents As Boolean = False Private _overlayActive As Boolean = False + Private _isShowingErrorDialog As Boolean = False ' ← NEU: Klassenvariable oben hinzufügen + Private _overlayHandle As Object = Nothing ' ← NEU: Klassenvariable + + Private Class Translation_Strings Inherits My.Resources.frmValidator_Strings End Class @@ -2084,9 +2088,17 @@ Public Class frmValidator If oView.FocusedRowHandle < 0 Then Exit Sub ' Nicht löschen wenn eine Zelle gerade bearbeitet wird If oView.ActiveEditor IsNot Nothing Then Exit Sub - Dim oGrid As GridControl = oView.GridControl Dim oMeta As ClassControlCreator.ControlMetadata = TryCast(oGrid.Tag, ClassControlCreator.ControlMetadata) + + If oMeta IsNot Nothing AndAlso oMeta.ReadOnly Then + MyValidationLogger.Debug($"[GridView_KeyDown] Grid [{oMeta.Name}] ist ReadOnly → Löschen blockiert") + e.Handled = True + e.SuppressKeyPress = True + Exit Sub + End If + + If oMeta IsNot Nothing Then oMeta.IsDirty = True MyValidationLogger.Debug($"[GridView_KeyDown] Grid [{oMeta.Name}] marked as dirty") @@ -5188,20 +5200,34 @@ Public Class frmValidator End If btnSave.Enabled = False ' ========== ENDE FIX 1 ========== - Dim oHandle = SplashScreenManager.ShowOverlayForm(Me) + + ' ========== FIX 2: Overlay-Handle global speichern ========== + _overlayHandle = SplashScreenManager.ShowOverlayForm(Me) + _overlayActive = True + ' ========== ENDE FIX 2 ========== + Try If ForceGridValidation() = True Then Finish_WFStep() End If Finally - ' ========== FIX 2: Nur Enable wenn Form NICHT schließt ========== + ' ========== FIX 3: Overlay IMMER schließen ========== + _overlayActive = False + Try + SplashScreenManager.CloseOverlayForm(_overlayHandle) + Catch ex As Exception + MyValidationLogger.Warn($"⚠️ [btnSave_Click] Overlay-Close failed: {ex.Message}") + End Try + _overlayHandle = Nothing + ' ========== ENDE FIX 3 ========== + + ' ========== FIX 4: Button nur re-enablen wenn Form nicht schließt ========== If Not _FormClosing AndAlso Not Me.IsDisposed Then btnSave.Enabled = True Else MyValidationLogger.Debug("btnSave_Click: Form closing, Button bleibt disabled") End If - SplashScreenManager.CloseOverlayForm(oHandle) - ' ========== ENDE FIX 2 ========== + ' ========== ENDE FIX 4 ========== End Try End Sub @@ -5710,12 +5736,20 @@ Public Class frmValidator End Try Else errormessage = oErrMsgMissingInput - frmError.ShowDialog() + MyValidationLogger.Warn($"⚠️ [Finish_WFStep] Validierung fehlgeschlagen → OpenfrmError") + OpenfrmError(oErrMsgMissingInput) ' ← Statt: frmError.ShowDialog() oErrorOcurred = True + ' ========== FIX: Overlay schließen NACH Dialog ========== If overlayStartedHere Then _overlayActive = False - SplashScreenManager.CloseOverlayForm(oHandle) + Try + SplashScreenManager.CloseOverlayForm(_overlayHandle) + Catch ex As Exception + MyValidationLogger.Warn($"⚠️ [Finish_WFStep] Overlay-Close failed: {ex.Message}") + End Try + _overlayHandle = Nothing End If + ' ========== ENDE FIX ========== Exit Sub End If Else @@ -5777,7 +5811,17 @@ Public Class frmValidator End If Focus_FirstControl() End If - + ' ========== FIX: Overlay schließen am Ende ========== + If overlayStartedHere Then + _overlayActive = False + Try + SplashScreenManager.CloseOverlayForm(_overlayHandle) + Catch ex As Exception + MyValidationLogger.Warn($"⚠️ [Finish_WFStep] Overlay-Close failed: {ex.Message}") + End Try + _overlayHandle = Nothing + End If + ' ========== ENDE FIX ========== If LOG_HOTSPOTS Then MyValidationLogger.Info($"[PERF] Finish_WFStep GESAMT: {(DateTime.Now - perfStart).TotalMilliseconds}ms") End If @@ -6181,6 +6225,11 @@ Public Class frmValidator oErrMsgMissingInput &= ":" & vbCrLf & oRegexMessage End If oControl.BackColor = Color.Red + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6246,6 +6295,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = $"Error while indexing Textbox {oControl} - Attribute {oIndexName} as VEKTOR - ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6259,6 +6313,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = $"Error while indexing Textbox {oControl} - Attribute {oIndexName} - ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6282,6 +6341,11 @@ Public Class frmValidator Dim st As New StackTrace(True) st = New StackTrace(ex, True) MyValidationLogger.Warn("⚠️ Unexpected error in Check_UpdateIndexe TextBox :" & ex.Message, True) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) ' Nach Fehler: Dirty-Flag zurücksetzen @@ -6298,6 +6362,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Please Choose an entry out of ComboBox '" & cmb.Name & "'" MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For Else @@ -6349,6 +6418,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error while indexing Combobox as VEKTOR - ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6362,6 +6436,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error while indexing Combobox - ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6371,6 +6450,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error indexing combobox idb" MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6405,6 +6489,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Please Choose DateValue for field'" & dtp.Name & "'" MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For ElseIf dtp.Value.ToString <> "01.01.0001 00:00:00" Then @@ -6425,6 +6514,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error while indexing DatePicker as VEKTOR - ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6437,6 +6531,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error while indexing DatePicker- ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6445,6 +6544,16 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error indexing datepicker idb" MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6477,6 +6586,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Please set Checkbox value for field '" & chk.Name & "'" MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6525,6 +6639,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error while indexing Checkbox as VEKTOR - ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6534,12 +6653,22 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error while indexing Checkbox - ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If Else If IDBData.SetVariableValue(oIndexName, chk.Checked.ToString) Then oErrMsgMissingInput = "error indexing checkbox idb" + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6572,6 +6701,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Fehlende Eingabe in Vektorfeld '" & dgv.Name & "'" MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For ElseIf Zeilen > 0 Then @@ -6608,6 +6742,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error while indexing Vektorfeld - ERROR: " & idxerr_message MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6618,6 +6757,11 @@ Public Class frmValidator oMissing = True oErrMsgMissingInput = "Error indexing Datagridview idb" MyValidationLogger.Warn(oErrMsgMissingInput) + ' Vor ShowDialog prüfen + If _FormClosing Then + MyValidationLogger.Warn("Form closing - skip error dialog") + Return False + End If OpenfrmError(oErrMsgMissingInput) Exit For End If @@ -6649,7 +6793,8 @@ Public Class frmValidator Dim oResult = ValidateGridControl(oGrid, oSettings, oGridColumnDefinition, oMissing, oErrMsgMissingInput) If oResult = False Then - Exit For + MyValidationLogger.Warn($"⚠️ Validierung fehlgeschlagen für Grid [{oGrid.Name}] → Exit For") + Exit For ' ← SOFORT stoppen, keinen zweiten Dialog! End If End Select @@ -6691,9 +6836,30 @@ Public Class frmValidator End Function Sub OpenfrmError(pErrormessage As String) - Dim ofrm As New frmError - ofrm.ValidatorError = pErrormessage - ofrm.ShowDialog() + ' ========== FIX: Verhindere mehrfache Dialoge ========== + If _isShowingErrorDialog Then + MyValidationLogger.Warn("⚠️ [OpenfrmError] Dialog bereits offen → Exit Sub") + Return + End If + _isShowingErrorDialog = True + ' ========== ENDE FIX ========== + + ' ========== KRITISCH: Overlay NICHT hier schließen! ========== + ' Das macht der Aufrufer (Finish_WFStep oder btnSave_Click)! + ' ========== ENDE KRITISCH ========== + + ' 2. Events blockieren + _suppressLookupEvents = True + Try + Using ofrm As New frmError With {.ValidatorError = pErrormessage} + ofrm.ShowDialog(Me) + End Using + Finally + _suppressLookupEvents = False + ' ========== FIX: Guard zurücksetzen ========== + _isShowingErrorDialog = False + ' ========== ENDE FIX ========== + End Try End Sub Private Class ControlSettings Public Name As String @@ -6712,7 +6878,19 @@ Public Class frmValidator 'Wenn kein Wert ausgewählt wurde und der Index aber gesetzt werden muss If pSettings.IsRequired = True And oRowCount = 0 Then pMissing = True - pMissingMessage = "Fehlende Eingabe in Tabelle '" & pGrid.Name & "'" + ' ========== NEU: Liste der Required-Spalten sammeln ========== + Dim requiredColumns = pColumnDefinition.AsEnumerable(). + Where(Function(row) row.ItemEx("VALIDATION", False) = True). + Select(Function(row) row.ItemEx("SPALTEN_HEADER_LANG", row.ItemEx("SPALTENNAME", "")).ToString()). + Where(Function(colName) Not String.IsNullOrWhiteSpace(colName)). + ToList() + + If requiredColumns.Count > 0 Then + pMissingMessage = $"Missing input in table '{pGrid.Name}'. At least on row with following required fields necessary: {String.Join(", ", requiredColumns)}" + Else + pMissingMessage = $"Missing input in table '{pGrid.Name}' - at least one row necessary" + End If + ' ========== ENDE NEU ========== pGrid.BackColor = Color.Red MyValidationLogger.Warn(pMissingMessage) @@ -7364,6 +7542,8 @@ Public Class frmValidator DatabaseFallback.ExecuteNonQueryECM(ins) Else SetStatusLabel("Error while saving data!", "Red") + MsgBox("Unexpeceted error while savign data! Please check Your log and try again.", MsgBoxStyle.Critical) + End If ' ========== ENDE FIX 2 ========== Finally