diff --git a/app/TaskFlow/ClassControlCreator.vb b/app/TaskFlow/ClassControlCreator.vb index 428cd5d..184d70f 100644 --- a/app/TaskFlow/ClassControlCreator.vb +++ b/app/TaskFlow/ClassControlCreator.vb @@ -69,6 +69,8 @@ Public Class ClassControlCreator Public Property GridTables As New Dictionary(Of Integer, Dictionary(Of String, RepositoryItem)) Public Property GridColumns As New Dictionary(Of Integer, DataTable) + + ''' ''' Standard Eigenschaften für alle Controls ''' @@ -533,12 +535,11 @@ Public Class ClassControlCreator End Function Public Function CreateExistingGridControl(row As DataRow, DT_MY_COLUMNS As DataTable, designMode As Boolean, pcurrencySymbol As String) As GridControl - Dim oGridControlCreator = New ControlCreator.GridControl(LogConfig, GridTables) + Dim oGridControlCreator = New ControlCreator.GridControl(LogConfig, GridTables, pcurrencySymbol) Dim oControl As GridControl = CreateBaseControl(New GridControl(), row, designMode) Dim oControlId = DirectCast(oControl.Tag, ControlMetadata).Guid Dim oView As GridView Dim oControlName = oControl.Name - oControl.ForceInitialize() oView = oControl.MainView @@ -635,9 +636,13 @@ Public Class ClassControlCreator End Try End If - oGridControlCreator.ConfigureViewColumns(DT_MY_COLUMNS, oView, oControl, pcurrencySymbol) + ' *** KORRIGIERT: ConfigureViewColumns OHNE currencySymbol-Parameter *** + oGridControlCreator.ConfigureViewColumns(DT_MY_COLUMNS, oView, oControl) + + ' *** NEU: ConfigureViewColumnsCurrency() für editierbare Währungsspalten *** + oGridControlCreator.ConfigureViewColumnsCurrency(DT_MY_COLUMNS, oView, oControl) + oGridControlCreator.ConfigureViewEvents(DT_MY_COLUMNS, oView, oControl, oControlId) - ' 08.11.2021: Fix editor being empty on first open oView.FocusInvalidRow() Return oControl diff --git a/app/TaskFlow/ClassFormat.vb b/app/TaskFlow/ClassFormat.vb index 40e1923..331c44b 100644 --- a/app/TaskFlow/ClassFormat.vb +++ b/app/TaskFlow/ClassFormat.vb @@ -28,6 +28,60 @@ Public Class ClassFormat End Select End Function + ''' + ''' Normalisiert einen numerischen String für die invariante Kultur-Konvertierung. + ''' Entfernt Tausendertrennzeichen und ersetzt Dezimaltrennzeichen durch Punkt. + ''' + Private Shared Function NormalizeNumericString(pValue As String) As String + If String.IsNullOrWhiteSpace(pValue) Then + Return pValue + End If + + Dim normalized As String = pValue.Trim() + + ' Entferne Währungssymbole und Leerzeichen + normalized = System.Text.RegularExpressions.Regex.Replace(normalized, "[€$£¥\s]", "") + + ' Prüfe, ob der String sowohl Punkt als auch Komma enthält + Dim hasDot As Boolean = normalized.Contains(".") + Dim hasComma As Boolean = normalized.Contains(",") + + If hasDot AndAlso hasComma Then + ' Beide vorhanden: Das letzte ist der Dezimaltrenner + Dim lastDotPos As Integer = normalized.LastIndexOf(".") + Dim lastCommaPos As Integer = normalized.LastIndexOf(",") + + If lastDotPos > lastCommaPos Then + ' Punkt ist Dezimaltrenner, Komma ist Tausendertrenner + normalized = normalized.Replace(",", "") + Else + ' Komma ist Dezimaltrenner, Punkt ist Tausendertrenner + normalized = normalized.Replace(".", "").Replace(",", ".") + End If + ElseIf hasComma Then + ' Nur Komma: Könnte Dezimal- oder Tausendertrenner sein + ' Wenn mehr als ein Komma → Tausendertrenner + ' Wenn nur ein Komma und <= 3 Stellen danach → Dezimaltrenner + Dim commaCount As Integer = normalized.Count(Function(c) c = ","c) + If commaCount = 1 Then + Dim lastCommaPos As Integer = normalized.LastIndexOf(",") + Dim digitsAfterComma As Integer = normalized.Length - lastCommaPos - 1 + If digitsAfterComma <= 3 Then + ' Wahrscheinlich Dezimaltrenner + normalized = normalized.Replace(",", ".") + Else + ' Wahrscheinlich Tausendertrenner + normalized = normalized.Replace(",", "") + End If + Else + ' Mehrere Kommas → Tausendertrenner + normalized = normalized.Replace(",", "") + End If + End If + ' Wenn nur Punkt vorhanden: bereits im richtigen Format + + Return normalized + End Function ''' ''' Converts a string according to the type information, using the invariant culture @@ -41,25 +95,35 @@ Public Class ClassFormat Select Case pType Case ClassControlCreator.CONTROL_TYPE_DOUBLE - If Double.TryParse(pValue, NumberStyles.Float, CultureInfo.InvariantCulture, oConvertedValue) Then - Return oConvertedValue - End If - - Case ClassControlCreator.CONTROL_TYPE_CURRENCY Try - LOGGER.Debug($"GetConvertedValue: Converting {pValue.ToString} to Currency ") - If Double.TryParse(pValue, NumberStyles.Currency, CultureInfo.InvariantCulture, oConvertedValue) Then + Dim normalizedValue As String = NormalizeNumericString(pValue?.ToString()) + If Double.TryParse(normalizedValue, NumberStyles.Float, CultureInfo.InvariantCulture, oConvertedValue) Then Return oConvertedValue End If Catch ex As Exception LOGGER.Error(ex) End Try + Case ClassControlCreator.CONTROL_TYPE_CURRENCY + Try + Dim normalizedValue As String = NormalizeNumericString(pValue?.ToString()) + LOGGER.Debug($"GetConvertedValue: Converting {pValue.ToString} (normalized: {normalizedValue}) to Currency ") + If Double.TryParse(normalizedValue, NumberStyles.Float, CultureInfo.InvariantCulture, oConvertedValue) Then + Return oConvertedValue + End If + Catch ex As Exception + LOGGER.Error(ex) + End Try Case ClassControlCreator.CONTROL_TYPE_INTEGER - If Integer.TryParse(pValue, NumberStyles.Integer, CultureInfo.InvariantCulture, oConvertedValue) Then - Return oConvertedValue - End If + Try + Dim normalizedValue As String = NormalizeNumericString(pValue?.ToString()) + If Integer.TryParse(normalizedValue, NumberStyles.Integer, CultureInfo.InvariantCulture, oConvertedValue) Then + Return oConvertedValue + End If + Catch ex As Exception + LOGGER.Error(ex) + End Try Case Else LOGGER.Debug($"GetConvertedValue - Case ELSE - pType is {pType}") Try diff --git a/app/TaskFlow/ClassIDBData.vb b/app/TaskFlow/ClassIDBData.vb index ca0542c..35a7efd 100644 --- a/app/TaskFlow/ClassIDBData.vb +++ b/app/TaskFlow/ClassIDBData.vb @@ -209,7 +209,7 @@ If IDB_USES_WMFILESTORE Then oID_IS_FOREIGN = 1 End If - + oTerm2Delete = oTerm2Delete.Replace("'", "''") Dim oDELSQL = $"EXEC PRIDB_DELETE_TERM_OBJECT_METADATA {CURRENT_DOC_ID},'{oAttributeName}','{oTerm2Delete}','{USER_USERNAME}','{USER_LANGUAGE}',{oID_IS_FOREIGN};" LOGGER.Debug($"Delete_Term_Object_From_Metadata: {oDELSQL}") 'DatabaseFallback.ExecuteNonQueryIDB(oDELSQL) @@ -322,6 +322,7 @@ Return True Else 'oNewValue = oNewValue.Replace("'", "' + NCHAR(39) + '") + oNewValue = oNewValue.Replace("'", "''") Dim oPRIDB_NEW_OBJ_DATA = $"DECLARE @NEW_OBJ_MD_ID BIGINT " & vbNewLine & $"EXEC PRIDB_NEW_OBJ_DATA {CURRENT_DOC_ID},'{oAttributeName}','{USER_USERNAME}','{oNewValue}','{USER_LANGUAGE}',0,@OMD_ID = @NEW_OBJ_MD_ID OUTPUT;" LOGGER.Debug(oPRIDB_NEW_OBJ_DATA) ' Return DatabaseFallback.ExecuteNonQueryIDB(oPRIDB_NEW_OBJ_DATA) diff --git a/app/TaskFlow/ControlCreator/GridControl.vb b/app/TaskFlow/ControlCreator/GridControl.vb index 918b71d..b2a6eca 100644 --- a/app/TaskFlow/ControlCreator/GridControl.vb +++ b/app/TaskFlow/ControlCreator/GridControl.vb @@ -27,10 +27,12 @@ Namespace ControlCreator 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))) + Private _currencySymbol As String = "€" + Public Sub New(pLogConfig As LogConfig, pGridTables As Dictionary(Of Integer, Dictionary(Of String, RepositoryItem)), pCurrencySymbol As String) _LogConfig = pLogConfig _Logger = pLogConfig.GetLogger() _GridTables = pGridTables + _currencySymbol = pCurrencySymbol End Sub Public Function CreateGridColumns(pColumnTable As DataTable) As DataTable @@ -201,15 +203,15 @@ Namespace ControlCreator End If End Function ' Hilfsroutine: passt NUR das Summary-Item an (ohne FormatInfo) - Private Sub ApplyCurrencySummaryFormat(oCol As GridColumn, currencySymbol As String) + Private Sub ApplyCurrencySummaryFormat(oCol As GridColumn) oCol.SummaryItem.SummaryType = DevExpress.Data.SummaryItemType.Sum ' Variante A: Standard-Währungsformat aus aktueller Kultur ' oCol.SummaryItem.DisplayFormat = "SUM: {0:C2}" ' Variante B: Kulturunabhängig, Symbol explizit anhängen - oCol.SummaryItem.DisplayFormat = $"SUM: {{0:N2}} {currencySymbol}" + oCol.SummaryItem.DisplayFormat = $"SUM: {{0:N2}} {_currencySymbol}" End Sub - Public Sub ConfigureViewColumns(pColumnTable As DataTable, pGridView As GridView, pGrid As DevExpress.XtraGrid.GridControl, pcurrencySymbol As String) + Public Sub ConfigureViewColumns(pColumnTable As DataTable, pGridView As GridView, pGrid As DevExpress.XtraGrid.GridControl) Dim oShouldDisplayFooter As Boolean = False For Each oCol As GridColumn In pGridView.Columns Dim oColumnData As DataRow = pColumnTable. @@ -255,7 +257,8 @@ Namespace ControlCreator Case "CURRENCY" oCol.DisplayFormat.FormatType = FormatType.Custom - oCol.DisplayFormat.FormatString = "C2" + oCol.DisplayFormat.FormatString = $"N2 {_currencySymbol}" + End Select Dim oSummaryFunction As String = oColumnData.Item("SUMMARY_FUNCTION") @@ -272,7 +275,7 @@ Namespace ControlCreator oShouldDisplayFooter = True Case Constants.AGGREGATE_TOTAL_CURRENCY - ApplyCurrencySummaryFormat(oCol, pcurrencySymbol) + ApplyCurrencySummaryFormat(oCol) oShouldDisplayFooter = True Case Constants.AGGREGATE_TOTAL_AVG @@ -307,10 +310,10 @@ Namespace ControlCreator End With End If End Sub - Public Sub ConfigureViewColumnsCurrency(pColumnTable As DataTable, pGridView As GridView, pGrid As DevExpress.XtraGrid.GridControl, pCurrency As String) + Public Sub ConfigureViewColumnsCurrency(pColumnTable As DataTable, pGridView As GridView, pGrid As DevExpress.XtraGrid.GridControl) Dim oCultureInfo As CultureInfo = New CultureInfo("de-DE") - oCultureInfo.NumberFormat.CurrencySymbol = pCurrency + oCultureInfo.NumberFormat.CurrencySymbol = _currencySymbol Dim riTextEdit As RepositoryItemTextEdit = New RepositoryItemTextEdit() riTextEdit.MaskSettings.Configure(Of MaskSettings.Numeric)(Sub(settings) settings.MaskExpression = "c" @@ -328,6 +331,12 @@ Namespace ControlCreator Continue For End If + ' *** NEU: Prüfe ob Spalte editierbar ist *** + If Not oCol.OptionsColumn.AllowEdit Then + _Logger.Debug("Skipping ColumnEdit for read-only column [{0}]", oCol.FieldName) + 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) @@ -340,7 +349,7 @@ Namespace ControlCreator Select Case oColumnType Case "CURRENCY" - oCol.DisplayFormat.FormatType = FormatType.Custom + ' *** WICHTIG: NUR ColumnEdit setzen, KEIN DisplayFormat mehr! *** oCol.ColumnEdit = riTextEdit End Select Next @@ -380,6 +389,55 @@ Namespace ControlCreator newRowModified = False End Try End Sub + ' *** NEU: CustomColumnDisplayText für robuste CURRENCY-Formatierung *** + AddHandler pGridView.CustomColumnDisplayText, + Sub(sender As Object, e As CustomColumnDisplayTextEventArgs) + If e.Column Is Nothing OrElse e.Value Is Nothing OrElse IsDBNull(e.Value) Then + Return + End If + + ' Prüfe ob Spalte vom Typ CURRENCY ist + Dim oColumnData As DataRow = pColumnTable. + Select($"SPALTENNAME = '{e.Column.FieldName}'"). + FirstOrDefault() + + If oColumnData IsNot Nothing AndAlso + oColumnData.Item("TYPE_COLUMN").ToString() = "CURRENCY" Then + + Try + Dim oValue As Double + ' *** KRITISCH: Robustes Parsing unabhängig vom Dezimaltrenner *** + If TypeOf e.Value Is Double OrElse TypeOf e.Value Is Decimal Then + oValue = Convert.ToDouble(e.Value) + ElseIf TypeOf e.Value Is String Then + Dim oStringValue As String = e.Value.ToString().Trim() + + ' Versuche zuerst deutsches Format (1.234,56) + Dim oDeCulture As CultureInfo = New CultureInfo("de-DE") + If Double.TryParse(oStringValue, NumberStyles.Currency Or NumberStyles.Number, oDeCulture, oValue) Then + ' Erfolgreich mit deutschem Format geparst + ElseIf Double.TryParse(oStringValue, NumberStyles.Currency Or NumberStyles.Number, CultureInfo.InvariantCulture, oValue) Then + ' Erfolgreich mit invariantem Format (Punkt als Dezimaltrenner) + Else + ' Fallback: Systemkultur + oValue = Convert.ToDouble(oStringValue, CultureInfo.CurrentCulture) + End If + Else + oValue = Convert.ToDouble(e.Value) + End If + + ' Formatierung IMMER mit deutscher Kultur (Komma als Dezimaltrenner) + Dim oDeCultureInfo As CultureInfo = New CultureInfo("de-DE") + e.DisplayText = oValue.ToString("N2", oDeCultureInfo) & " " & _currencySymbol + + Catch ex As Exception + _Logger.Warn("⚠️ Could not format currency value [{0}] for column [{1}]: {2}", + e.Value, e.Column.FieldName, ex.Message) + ' Fallback: Original-Wert + Symbol + e.DisplayText = e.Value.ToString() & " " & _currencySymbol + End Try + End If + End Sub AddHandler pGridView.CustomRowCellEdit, Sub(sender As Object, e As CustomRowCellEditEventArgs) Try @@ -800,16 +858,7 @@ Namespace ControlCreator Return entry End Function - Private Sub View_CustomColumnDisplayText(ByVal eSender As Object, ByVal e As CustomColumnDisplayTextEventArgs) - If IsNothing(e.Value) Then - Exit Sub - End If - Dim view As GridView = eSender - 'Dim view As GridView = TryCast(GridView1, GridView) - If e.Column.FieldName = "SpalteCurrency" Then - ' e.DisplayText = e.Value.ToString().Replace("€", "CHF") - End If - End Sub + Private Sub View_PopupMenuShowing(sender As Object, e As PopupMenuShowingEventArgs) Dim view As GridView = TryCast(sender, GridView) Dim oFocusedColumn As GridColumn = view.FocusedColumn diff --git a/app/TaskFlow/My Project/AssemblyInfo.vb b/app/TaskFlow/My Project/AssemblyInfo.vb index a7bdb3a..791a22a 100644 --- a/app/TaskFlow/My Project/AssemblyInfo.vb +++ b/app/TaskFlow/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - + diff --git a/app/TaskFlow/frmMain.resx b/app/TaskFlow/frmMain.resx index 77b0e07..89056e1 100644 --- a/app/TaskFlow/frmMain.resx +++ b/app/TaskFlow/frmMain.resx @@ -125,7 +125,7 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADw - CAAAAk1TRnQBSQFMAgEBAgEAAcgBCwHIAQsBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo + CAAAAk1TRnQBSQFMAgEBAgEAAfABCwHwAQsBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo AwABQAMAARADAAEBAQABCAYAAQQYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5 AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA @@ -1496,36 +1496,9 @@ 0, 0 - - Allgemein - - - Auswertungen - - - Verwaltung - - - Grundeinstellungen - - - Workflow - - - Funktionen/App Start - - - Ad Hoc Workflows - Start - - Funktionen - - - Workflow Tabelle - Tabelle @@ -1583,24 +1556,6 @@ 0 - - True - - - Tahoma, 9.75pt, style=Bold - - - 3, 3 - - - 127, 16 - - - 0 - - - Choose a profile ... - lblCaptionMainGrid @@ -1692,12 +1647,6 @@ 863, 17 - - 219, 26 - - - Starte Validierung für Profil - 220, 30 @@ -1880,6 +1829,69 @@ 2 + + Allgemein + + + Auswertungen + + + Verwaltung + + + Grundeinstellungen + + + Workflow + + + Funktionen/App Start + + + Ad Hoc Workflows + + + Funktionen + + + Workflow Tabelle + + + True + + + Tahoma, 9.75pt, style=Bold + + + 3, 3 + + + 127, 16 + + + 0 + + + Choose a profile ... + + + lblCaptionMainGrid + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Panel2 + + + 0 + + + 219, 26 + + + Starte Validierung für Profil + 986, 17 @@ -1889,27 +1901,6 @@ 1220, 17 - - 290, 30 - - - Popup Erinnerung deaktivieren - - - 287, 6 - - - 290, 30 - - - In den Vordergrund - - - 290, 30 - - - Out of Range - Fenster wiederherstellen - 291, 100 @@ -2005,6 +1996,27 @@ True + + 290, 30 + + + Popup Erinnerung deaktivieren + + + 287, 6 + + + 290, 30 + + + In den Vordergrund + + + 290, 30 + + + Out of Range - Fenster wiederherstellen + 605, 17 @@ -3432,6 +3444,9 @@ &Ansicht + + &Hintergrund + &Seiten Layout @@ -3444,9 +3459,6 @@ Bars - - &Hintergrund - PDF Dokument diff --git a/app/TaskFlow/frmMain.vb b/app/TaskFlow/frmMain.vb index 0da3c4f..6e3dbef 100644 --- a/app/TaskFlow/frmMain.vb +++ b/app/TaskFlow/frmMain.vb @@ -1977,6 +1977,8 @@ Public Class frmMain Dim useWaitCursorApplied As Boolean = False Dim previousMessage As String = bsiMessage.Caption Dim messageApplied As Boolean = False + Dim oHandle As Object = Nothing + Dim overlayStartedHere As Boolean = False If LOG_HOTSPOTS Then perfStart = DateTime.Now @@ -1987,6 +1989,14 @@ Public Class frmMain CURRENT_ProfilGUID = pProfilID WM_AHWF_docPath = String.Empty + ' ========== OVERLAY ANZEIGEN ========== + If Not _overlayActive Then + oHandle = SplashScreenManager.ShowOverlayForm(Me) + _overlayActive = True + overlayStartedHere = True + End If + + ' ========== UI-VORBEREITUNG ========== Me.UseWaitCursor = True useWaitCursorApplied = True @@ -2091,6 +2101,13 @@ Public Class frmMain MsgBox("Unexpected error in Load_Profil_from_Grid: " & ex.Message & vbNewLine & ADDITIONAL_TITLE & " will try to reload the overview - Please try again!", MsgBoxStyle.Information, ADDITIONAL_TITLE) Dim task = Decide_Load(False, True) Finally + ' ========== OVERLAY SCHLIESSEN (FALLBACK) ========== + If overlayStartedHere AndAlso _overlayActive Then + SplashScreenManager.CloseOverlayForm(oHandle) + LOGGER.Debug("Overlay closed in Load_Profil_from_Grid") + _overlayActive = False + End If + ' ========== UI AUFRÄUMEN ========== If useWaitCursorApplied Then Me.UseWaitCursor = False diff --git a/app/TaskFlow/frmValidator.vb b/app/TaskFlow/frmValidator.vb index 77b7cab..cea128c 100644 --- a/app/TaskFlow/frmValidator.vb +++ b/app/TaskFlow/frmValidator.vb @@ -135,9 +135,10 @@ Public Class frmValidator Private _CachedControlsByGuid As Dictionary(Of Integer, Control) 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 _overlayRefCount As Integer = 0 ' ← NEU: Zähler für verschachtelte Calls + Private _overlayLock As New Object() ' ← NEU: Thread-Safe Lock Private Class Translation_Strings @@ -163,7 +164,70 @@ Public Class frmValidator End Sub + ' ========== NEU: Zentrale Overlay-Verwaltung ========== + ''' + ''' Thread-sicheres Öffnen des Overlays (verschachtelte Calls werden ignoriert) + ''' + Private Function ShowOverlaySafe() As Boolean + SyncLock _overlayLock + If _overlayRefCount = 0 Then + Try + _overlayHandle = SplashScreenManager.ShowOverlayForm(Me) + MyValidationLogger.Debug($"[Overlay] Geöffnet (RefCount: 0 → 1)") + Catch ex As Exception + MyValidationLogger.Error($"[Overlay] Fehler beim Öffnen: {ex.Message}") + Return False + End Try + Else + MyValidationLogger.Debug($"[Overlay] Bereits offen (RefCount: {_overlayRefCount} → {_overlayRefCount + 1})") + End If + _overlayRefCount += 1 + Return True + End SyncLock + End Function + ''' + ''' Thread-sicheres Schließen des Overlays (nur wenn RefCount = 0) + ''' + Private Sub CloseOverlaySafe() + SyncLock _overlayLock + If _overlayRefCount > 0 Then + _overlayRefCount -= 1 + MyValidationLogger.Debug($"[Overlay] RefCount: {_overlayRefCount + 1} → {_overlayRefCount}") + End If + + If _overlayRefCount = 0 AndAlso _overlayHandle IsNot Nothing Then + Try + SplashScreenManager.CloseOverlayForm(_overlayHandle) + MyValidationLogger.Debug("[Overlay] ✓ Geschlossen") + Catch ex As Exception + MyValidationLogger.Warn($"[Overlay] Fehler beim Schließen: {ex.Message}") + Finally + _overlayHandle = Nothing + End Try + End If + End SyncLock + End Sub + + ''' + ''' Erzwingt sofortiges Schließen (nur für Fehlerfall / FormClosing) + ''' + Private Sub ForceCloseOverlay() + SyncLock _overlayLock + If _overlayHandle IsNot Nothing Then + Try + SplashScreenManager.CloseOverlayForm(_overlayHandle) + MyValidationLogger.Debug("[Overlay] ⚠️ FORCE CLOSED") + Catch ex As Exception + MyValidationLogger.Warn($"[Overlay] Force-Close failed: {ex.Message}") + Finally + _overlayHandle = Nothing + _overlayRefCount = 0 + End Try + End If + End SyncLock + End Sub + ' ========== ENDE NEU ========== Private Function GetOperationMode() As OperationMode Dim oOperationMode As OperationMode @@ -2015,6 +2079,11 @@ Public Class frmValidator End Try End Sub Private Sub GridView_CellValueChanged(sender As Object, e As DevExpress.XtraGrid.Views.Base.CellValueChangedEventArgs) + ' Prevent cascading events during validation + If _isShowingErrorDialog Then + MyValidationLogger.Debug("GridView_CellValueChanged suppressed (currently showing error dialog)") + Return + End If Dim oView As GridView = sender Dim oGrid As GridControl = oView.GridControl Dim oMeta As ClassControlCreator.ControlMetadata = oGrid.Tag @@ -2439,7 +2508,10 @@ Public Class frmValidator Catch ex As Exception MyValidationLogger.Error(ex) - MyValidationLogger.Warn($"⚠️ Error while Control2Set for [{oControlname2Set}]: " & ex.Message) + MyValidationLogger.Warn($"⚠️ SetControlValues_FromControl - Error in SetControlValues_FromControl for control [{pControl?.Name}]: {ex.Message}") + ' WICHTIG: Overlay schließen bei Fehler + CloseOverlaySafe() + Finally _SetControlValue_In_Action = False End Try @@ -2806,6 +2878,12 @@ Public Class frmValidator End Sub Public Sub OnCmbselectedIndex(sender As System.Object, e As System.EventArgs) + ' Prevent cascading events during validation + If _isShowingErrorDialog Then + MyValidationLogger.Debug($"OnCmbselectedIndex: Currently showing error dialog, skipping event handling to prevent cascade.") + Return + End If + Dim oCombobox As Windows.Forms.ComboBox = sender If oCombobox.SelectedIndex <> -1 And _Indexe_Loaded = True Then Dim oMeta As ClassControlCreator.ControlMetadata = oCombobox.Tag @@ -3261,34 +3339,55 @@ Public Class frmValidator - Private Function CreateWMObject() As String - MyValidationLogger.Debug($"in GetWMDocFileString...'") - + Private Function CreateWMObject() As Boolean Dim oWMOwnPath As String - If WM_AHWF_docPath <> String.Empty Then - oWMOwnPath = WM_AHWF_docPath - WMDocPathWindows = oWMOwnPath - Else - oWMOwnPath = WMDocPathWindows.Replace(WMSUFFIX, "") - End If - - MyValidationLogger.Debug($"oWMOwnPath: {oWMOwnPath}") Try - Dim oNormalizedPath = WINDREAM_MOD.GetNormalizedPath(oWMOwnPath, 1) - CURRENT_WMFILE = WINDREAM_MOD.Session.GetWMObjectByPath(WMEntity.WMEntityDocument, oNormalizedPath) - MyValidationLogger.Debug("CURRENT_WMFILE: [{0}]", CURRENT_WMFILE) - Return True + MyValidationLogger.Debug($"in GetWMDocFileString...'") + + + If WM_AHWF_docPath <> String.Empty Then + oWMOwnPath = WM_AHWF_docPath + WMDocPathWindows = oWMOwnPath + Else + oWMOwnPath = WMDocPathWindows.Replace(WMSUFFIX, "") + End If + + Try + Dim oNormalizedPath = WINDREAM_MOD.GetNormalizedPath(oWMOwnPath, 1) + CURRENT_WMFILE = WINDREAM_MOD.Session.GetWMObjectByPath(WMEntity.WMEntityDocument, oNormalizedPath) + MyValidationLogger.Debug("CURRENT_WMFILE: [{0}]", CURRENT_WMFILE) + Return True ' ← Erfolg + + Catch comEx As System.Runtime.InteropServices.COMException + ' ========== OPTIMIERT: Detaillierte COM-Fehlerbehandlung ========== + MyValidationLogger.Error($"COM-Fehler HRESULT: {comEx.ErrorCode:X8}, Pfad: [{oWMOwnPath}]") + + Select Case comEx.ErrorCode + Case &H8004C005 ' Objekt nicht gefunden + errormessage = $"Dokument [{oWMOwnPath}] existiert nicht in windream!" + Case &H80040E14 ' Zugriff verweigert + errormessage = $"Keine Berechtigung für [{oWMOwnPath}]!" + Case Else + errormessage = $"windream-Fehler: {comEx.Message}" + End Select + + ' ========== FIX 2: OpenfrmError() statt frmError.ShowDialog() ========== + OpenfrmError(errormessage) + ' ========== ENDE FIX 2 ========== + + Return False ' ← Fehler + End Try + Catch ex As Exception - Dim _err1 As Boolean = False MyValidationLogger.Error(ex) MyValidationLogger.Info("Unexpected error creating WMObject(1) in GetWMDocFileString: " & ex.Message) MyValidationLogger.Info("Error Number: " & Err.Number.ToString) errormessage = $"Could not create a WMObject(1) for [{oWMOwnPath}]!" - frmError.ShowDialog() + ' ========== FIX 4: Auch hier OpenfrmError() verwenden ========== + OpenfrmError(errormessage) Return False - + ' ========== ENDE FIX 4 ========== End Try - End Function Private Function GetDocPathWindows(_CheckStandard As Integer) Try @@ -3377,7 +3476,6 @@ Public Class frmValidator Dim oMilliseconts As Double clsPatterns.ClearControlCache() ' Cache-Invalidierung - Dim perfStart As DateTime = DateTime.MinValue Dim perfLastCheck As DateTime = DateTime.MinValue If LOG_HOTSPOTS Then @@ -3676,6 +3774,7 @@ Public Class frmValidator End If MyValidationLogger.Debug("frmValidator: LoadNextDocument finished!") + Catch ex As Exception MyValidationLogger.Error(ex) errormessage = "unexpected error in Load_Next_Document:" & ex.Message @@ -3686,7 +3785,6 @@ Public Class frmValidator If layoutSuspended Then PanelValidatorControl.ResumeLayout() End If - If LOG_HOTSPOTS Then ' ========== DIAGNOSE ENDE ========== MyValidationLogger.Info($"[INFO] Load_Next_Document ENDE") @@ -5089,7 +5187,7 @@ Public Class frmValidator End Sub Private Sub frmValidation_Shown(sender As Object, e As System.EventArgs) Handles Me.Shown - Dim oHandle = SplashScreenManager.ShowOverlayForm(Me) + ShowOverlaySafe() Dim perfStart As DateTime = DateTime.MinValue Dim perfLastCheck As DateTime = DateTime.MinValue If LOG_HOTSPOTS Then @@ -5185,49 +5283,69 @@ Public Class frmValidator End If MyValidationLogger.Debug("frmValidation_Shown finished!") + ' ✅ Overlay explizit schließen + Try + If SplashScreenManager.Default IsNot Nothing AndAlso SplashScreenManager.Default.IsSplashFormVisible Then + SplashScreenManager.CloseForm(False) + MyValidationLogger.Debug("✓ Overlay closed in frmValidation_Shown") + Else + MyValidationLogger.Debug("ℹ Overlay war bereits geschlossen") + End If + ' ✅ UI-Refresh erzwingen + Application.DoEvents() + Me.Refresh() + Catch ex As Exception + MyValidationLogger.Error($"❌ Error closing overlay in Shown: {ex.Message}") + End Try + If LOG_HOTSPOTS Then MyValidationLogger.Info($"[PERF] frmValidation_Shown GESAMT: {(DateTime.Now - perfStart).TotalMilliseconds}ms") End If Finally - SplashScreenManager.CloseOverlayForm(oHandle) + CloseOverlaySafe() + ' ✅ UI-Refresh erzwingen + Try + Application.DoEvents() + Me.Refresh() + Me.BringToFront() + Catch ex As Exception + MyValidationLogger.Error($"❌ Error in UI refresh: {ex.Message}") + End Try End Try + Try + ' Alle offenen Forms durchsuchen + For Each frm As Form In Application.OpenForms + If frm.GetType().FullName.StartsWith("DevExpress.XtraSplashScreen") Then + MyValidationLogger.Warn($"⚠️ Unerwartete SplashForm gefunden: {frm.Name}") + frm.Close() + End If + Next + Catch ex As Exception + MyValidationLogger.Error($"Error checking for splash forms: {ex.Message}") + End Try + End Sub Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click - ' ========== FIX 1: Button-State-Management ========== If btnSave.Enabled = False Then MyValidationLogger.Warn("btnSave_Click: Button bereits disabled, Exit Sub") Exit Sub End If btnSave.Enabled = False - ' ========== ENDE FIX 1 ========== - - ' ========== FIX 2: Overlay-Handle global speichern ========== - _overlayHandle = SplashScreenManager.ShowOverlayForm(Me) - _overlayActive = True - ' ========== ENDE FIX 2 ========== - Try If ForceGridValidation() = True Then - Finish_WFStep() + ShowOverlaySafe() + Try + Finish_WFStep() + Finally + CloseOverlaySafe() + End Try End If Finally - ' ========== 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 - ' ========== ENDE FIX 4 ========== End Try End Sub @@ -5374,13 +5492,6 @@ Public Class frmValidator End Function Sub Finish_WFStep(Optional includeFI As Boolean = True) - Dim oHandle As Object = Nothing - Dim overlayStartedHere As Boolean = False - If Not _overlayActive Then - oHandle = SplashScreenManager.ShowOverlayForm(Me) - _overlayActive = True - overlayStartedHere = True - End If Dim perfStart As DateTime = DateTime.MinValue Dim perfLastCheck As DateTime = DateTime.MinValue If LOG_HOTSPOTS Then @@ -5392,6 +5503,7 @@ Public Class frmValidator MyValidationLogger.Debug("Abschluss für DocID " & CURRENT_DOC_ID & " wird gestartet ...") Dim oErrorOcurred As Boolean = False If OverrideAll = False Then + If Check_UpdateIndexe() = True Then If LOG_HOTSPOTS Then MyValidationLogger.Info($"[PERF] Finish_WFStep nach Check_UpdateIndexe: {(DateTime.Now - perfLastCheck).TotalMilliseconds}ms") @@ -5399,10 +5511,7 @@ Public Class frmValidator End If If PROFIL_FINISH_SQL <> String.Empty Then If btnFinish_continue() = False Then - If overlayStartedHere Then - _overlayActive = False - SplashScreenManager.CloseOverlayForm(oHandle) - End If + CloseOverlaySafe() Exit Sub End If End If @@ -5544,6 +5653,8 @@ Public Class frmValidator My.Settings.Save() frmError.ShowDialog() oErrorOcurred = True + Else + End If End If If oErrorOcurred = True Then @@ -5562,7 +5673,9 @@ Public Class frmValidator End If Try If Override = True And Override_SQLCommand <> "" Then + DatabaseFallback.ExecuteNonQueryECM(Override_SQLCommand) + End If If LOG_HOTSPOTS Then MyValidationLogger.Info($"[PERF] Finish_WFStep nach Override-SQL: {(DateTime.Now - perfLastCheck).TotalMilliseconds}ms") @@ -5728,10 +5841,6 @@ Public Class frmValidator frmError.ShowDialog() oErrorOcurred = True MyValidationLogger.Info("Unexpected error in Finish: " & ex.Message, True) - If overlayStartedHere Then - _overlayActive = False - SplashScreenManager.CloseOverlayForm(oHandle) - End If Exit Sub End Try Else @@ -5739,17 +5848,6 @@ Public Class frmValidator 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 - 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 @@ -5764,7 +5862,7 @@ Public Class frmValidator perfLastCheck = DateTime.Now End If OverrideAll = False - ' Overlay wird weiter unten geschlossen (vor Load_Next_Document bzw. BeginInvoke) + End If If oErrorOcurred = True Then @@ -5782,13 +5880,11 @@ Public Class frmValidator End If End If + If CURRENT_JUMP_DOC_GUID <> 0 Then CURRENT_DOC_GUID = 0 MyValidationLogger.Info($"[Finish_WFStep] CURRENT_JUMP_DOC_GUID <> 0 → verzögertes Close()") - If overlayStartedHere Then - _overlayActive = False - SplashScreenManager.CloseOverlayForm(oHandle) - End If + CloseOverlaySafe() BeginInvoke(New Action(Sub() If Not Me.IsDisposed Then MyValidationLogger.Debug("[BeginInvoke] Führe Me.Close() aus") @@ -5801,27 +5897,12 @@ Public Class frmValidator Else Load_Next_Document(False) - If overlayStartedHere Then - _overlayActive = False - SplashScreenManager.CloseOverlayForm(oHandle) - End If If LOG_HOTSPOTS Then MyValidationLogger.Info($"[PERF] Finish_WFStep nach Load_Next_Document: {(DateTime.Now - perfLastCheck).TotalMilliseconds}ms") perfLastCheck = DateTime.Now 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 @@ -6842,11 +6923,7 @@ Public Class frmValidator Return End If _isShowingErrorDialog = True - ' ========== ENDE FIX ========== - - ' ========== KRITISCH: Overlay NICHT hier schließen! ========== - ' Das macht der Aufrufer (Finish_WFStep oder btnSave_Click)! - ' ========== ENDE KRITISCH ========== + CloseOverlaySafe() ' 2. Events blockieren _suppressLookupEvents = True @@ -7120,7 +7197,8 @@ Public Class frmValidator End Sub Sub Datei_ueberspringen() - Dim oHandle = SplashScreenManager.ShowOverlayForm(Me) + ShowOverlaySafe() + Dim perfStart As DateTime = If(LOG_HOTSPOTS, DateTime.Now, Nothing) Dim perfLastCheck As DateTime = perfStart If LOG_HOTSPOTS Then @@ -7183,7 +7261,7 @@ Public Class frmValidator MyValidationLogger.Info($" frmValidator.IsDisposed: {Me.IsDisposed}") ' ========== ENDE DIAGNOSE ========== End If - SplashScreenManager.CloseOverlayForm(oHandle) + CloseOverlaySafe() End Try End Sub @@ -7413,7 +7491,7 @@ Public Class frmValidator Private Sub bbtniRefresh_ItemClick(sender As Object, e As ItemClickEventArgs) Handles bbtniRefresh.ItemClick ' ========== KRITISCH: Events KOMPLETT blockieren während Refresh ========== _suppressLookupEvents = True - Dim oHandle = SplashScreenManager.ShowOverlayForm(Me) + ShowOverlaySafe() Try Reload_Controls("") @@ -7424,8 +7502,8 @@ Public Class frmValidator listChangedLookup.Clear() SetStatusLabel("All Data refreshed", "Yellow") Finally - _suppressLookupEvents = False ' ← Erst NACH allem wieder freigeben - SplashScreenManager.CloseOverlayForm(oHandle) + _suppressLookupEvents = False ' + CloseOverlaySafe() End Try End Sub @@ -7520,15 +7598,12 @@ Public Class frmValidator End Sub Private Sub BbtnItm_ItemClick(sender As Object, e As ItemClickEventArgs) Handles BbtnitmSave.ItemClick - ' ========== FIX 1: Button-State-Management ========== If BbtnitmSave.Enabled = False Then MyValidationLogger.Warn("BbtnitmSave_ItemClick: Button bereits disabled, Exit Sub") Exit Sub End If BbtnitmSave.Enabled = False - ' ========== ENDE FIX 1 ========== - - Dim oHandle = SplashScreenManager.ShowOverlayForm(Me) + ShowOverlaySafe() ' ✅ Overlay hier öffnen Try ' ========== FIX 2: Nur EINEN Check-Aufruf ========== If Check_UpdateIndexe() = True Then @@ -7545,16 +7620,15 @@ Public Class frmValidator MsgBox("Unexpeceted error while savign data! Please check Your log and try again.", MsgBoxStyle.Critical) End If - ' ========== ENDE FIX 2 ========== + Finally - ' ========== FIX 3: Button nur re-enablen wenn Form nicht schließt ========== + CloseOverlaySafe() If Not _FormClosing AndAlso Not Me.IsDisposed Then BbtnitmSave.Enabled = True Else MyValidationLogger.Debug("BbtnitmSave_ItemClick: Form closing, Button bleibt disabled") End If - SplashScreenManager.CloseOverlayForm(oHandle) - ' ========== ENDE FIX 3 ========== + End Try End Sub Private Sub SaveDevExpressGridControl_Layout(pProfilID As Integer, pControlID As Integer, pGridView As DevExpress.XtraGrid.Views.Grid.GridView)