Aktualisierung, Speichern und Leeren von GridControl-Zellen

This commit is contained in:
Developer01
2026-06-19 10:36:36 +02:00
parent e2016e4214
commit 540f9bcc12
2 changed files with 165 additions and 2892 deletions

View File

@@ -33,6 +33,7 @@ Namespace ControlCreator
Private Shared _ResolvedSqlCache As New Dictionary(Of String, String)
Private _isRefreshingFormula As Boolean = False ' *** NEU: Flag für Formel-Refresh ***
Private _clearOperationsInProgress As New HashSet(Of String) ' Format: "GridViewHashCode|ColumnName"
Private _currencySymbol As String = ""
''' <summary>
''' SHARED Dictionary: Speichert das aktuelle Währungssymbol PRO GridView (via Name).
@@ -90,70 +91,34 @@ Namespace ControlCreator
For Each match As Match In matches
Dim colName = match.Groups(1).Value
Dim cellValue = pView.GetRowCellValue(rowHandle, colName)
Dim safeValue As String = ConvertToSqlValue(cellValue) ' Hilfsfunktion
Dim safeValue As String = ConvertToSqlValue(cellValue)
resolvedSql = resolvedSql.Replace(match.Value, safeValue)
Next
' *** SCHRITT 2: {#CTRL#...} via clsPatterns - MIT CACHE ***
' *** SCHRITT 2: {#CTRL#...} via clsPatterns - MIT CACHE ***
If _ParentControl IsNot Nothing AndAlso resolvedSql.Contains("{#CTRL#") Then
' Cache-Key: Hash aus SQL + Control-Werten
Dim cacheKey = GenerateCacheKey(resolvedSql, _ParentControl)
SyncLock _ResolvedSqlCache
If _ResolvedSqlCache.ContainsKey(cacheKey) Then
' ✅ CACHE HIT: Kein Logging → spart 380+ Log-Zeilen
resolvedSql = _ResolvedSqlCache(cacheKey)
Else
' ❌ CACHE MISS: Nur HIER Debug-Logs
_Logger.Debug("[ResolveSqlTemplate] ReplaceAllValues-Cache MISS")
' *** DEBUG: Log Parent-Control (NUR beim ersten Aufruf) ***
If LOG_HOTSPOTS Then
_Logger.Debug("[ResolveSqlTemplate] _ParentControl is [{0}], has {1} controls",
_ParentControl.GetType().Name, _ParentControl.Controls.Count)
' *** ERWEITERTE SUCHE: Alle Controls durchsuchen ***
Dim testLU As Control = Nothing
For Each ctrl As Control In _ParentControl.Controls
If ctrl.Name = "LU_LieferantenNummer" Then
testLU = ctrl
Exit For
End If
' Rekursiv in Child-Controls suchen
If ctrl.HasChildren Then
testLU = FindControlRecursive(ctrl, "LU_LieferantenNummer")
If testLU IsNot Nothing Then Exit For
End If
Next
If testLU IsNot Nothing AndAlso TypeOf testLU Is LookupControl3 Then
Dim luValue = DirectCast(testLU, LookupControl3).EditValue
_Logger.Debug("[ResolveSqlTemplate] LU_LieferantenNummer.EditValue = [{0}]", luValue)
Else
_Logger.Warn("[ResolveSqlTemplate] LU_LieferantenNummer NOT FOUND in _ParentControl!")
' *** LISTE ALLE CONTROLS AUF (nur beim ersten Fehler) ***
_Logger.Debug("[ResolveSqlTemplate] Available controls in _ParentControl:")
For Each ctrl In _ParentControl.Controls
_Logger.Debug(" - {0} (Type: {1})", ctrl.Name, ctrl.GetType().Name)
Next
End If
End If
' Nur bei Cache-Miss ReplaceAllValues aufrufen
' *** FIX: Cache-Key ERST NACH ReplaceAllValues erstellen! ***
Dim beforeReplace = resolvedSql
resolvedSql = clsPatterns.ReplaceAllValues(resolvedSql, _ParentControl, True)
_ResolvedSqlCache(cacheKey) = resolvedSql
' *** DEBUG: Log VOR/NACH (nur bei MISS) ***
' *** JETZT Cache-Key erstellen (mit aufgelösten Control-Werten) ***
Dim cacheKey = resolvedSql.GetHashCode().ToString()
SyncLock _ResolvedSqlCache
' Cache speichern (für nächsten Aufruf)
If Not _ResolvedSqlCache.ContainsKey(cacheKey) Then
_ResolvedSqlCache(cacheKey) = resolvedSql
_Logger.Debug("[ResolveSqlTemplate] ✓ Cached SQL: Key=[{0}]", cacheKey)
End If
End SyncLock
' *** DEBUG: Log VOR/NACH (nur beim ersten Aufruf) ***
If LOG_HOTSPOTS Then
_Logger.Info("[ResolveSqlTemplate] BEFORE: {0}",
beforeReplace.Substring(0, Math.Min(150, beforeReplace.Length)))
_Logger.Info("[ResolveSqlTemplate] AFTER: {0}",
resolvedSql.Substring(0, Math.Min(150, resolvedSql.Length)))
End If
End If
End SyncLock
Else
If _ParentControl Is Nothing Then
_Logger.Warn("[ResolveSqlTemplate] _ParentControl is NOTHING! Cannot replace {{#CTRL#...}}")
@@ -853,7 +818,7 @@ Namespace ControlCreator
AddHandler pGridView.CustomRowCellEdit,
Sub(sender As Object, e As CustomRowCellEditEventArgs)
Try
' *** NEU: Dynamische Editoren aus Cache oder neu erstellen ***
' *** SCHRITT 1: Dynamische Editoren ZUERST prüfen ***
If _DynamicEditorColumns.Contains(e.Column.FieldName) Then
Dim oColumnData As DataRow = pColumnTable.
Select($"SPALTENNAME = '{e.Column.FieldName}'").
@@ -864,38 +829,52 @@ Namespace ControlCreator
Dim oConnectionId As Integer = oColumnData.ItemEx("CONNECTION_ID", 1)
Dim oIsAdvancedLookup As Boolean = oColumnData.ItemEx("ADVANCED_LOOKUP", False)
' *** SQL auflösen (für Cache-Key) ***
Dim resolvedSql = ResolveSqlTemplate(oSqlCommand, TryCast(sender, GridView), e.RowHandle)
If String.IsNullOrEmpty(oSqlCommand) Then
_Logger.Warn("⚠️ [CustomRowCellEdit] Column [{0}] has no SQL_COMMAND!", e.Column.FieldName)
Return
End If
' *** CACHE-KEY: Spaltenname + aufgelöstes SQL (HashCode) ***
' *** NEU: ResolveSqlTemplate HIER aufrufen! ***
Dim resolvedSql As String = ResolveSqlTemplate(oSqlCommand, DirectCast(sender, GridView), e.RowHandle)
' Cache-Key basiert auf RESOLVED SQL
Dim cacheKey As String = $"{e.Column.FieldName}|{resolvedSql.GetHashCode()}"
' *** CACHE-CHECK ***
' Cache prüfen
SyncLock _DynamicEditorCacheShared
If _DynamicEditorCacheShared.ContainsKey(cacheKey) Then
' ✅ CACHE HIT: Editor wiederverwenden
_Logger.Info("[CustomRowCellEdit] ✓ CACHE HIT: [{0}]", cacheKey)
e.RepositoryItem = _DynamicEditorCacheShared(cacheKey)
Else
' ❌ CACHE MISS: Neuen Editor erstellen
Return
End If
End SyncLock
' Editor erstellen mit RESOLVED SQL
_Logger.Info("[CustomRowCellEdit] 🆕 MISS: Creating editor for [{0}]", e.Column.FieldName)
Dim realEditor = CreateRowSpecificEditor(e.Column.FieldName, resolvedSql, oConnectionId, oIsAdvancedLookup)
Dim realEditor = CreateRowSpecificEditor(
e.Column.FieldName,
resolvedSql,
oConnectionId,
oIsAdvancedLookup,
DirectCast(sender, GridView),
e.RowHandle
)
If realEditor IsNot Nothing Then
' *** IN CACHE SPEICHERN ***
_DynamicEditorCacheShared(cacheKey) = realEditor
e.RepositoryItem = realEditor
SyncLock _DynamicEditorCacheShared
If Not _DynamicEditorCacheShared.ContainsKey(cacheKey) Then
_DynamicEditorCacheShared.Add(cacheKey, realEditor)
End If
End SyncLock
_Logger.Info("[CustomRowCellEdit] ✓ Cached [{0}] editor (Type=[{1}])", e.Column.FieldName, realEditor.GetType().Name)
e.RepositoryItem = realEditor
Else
_Logger.Warn("[CustomRowCellEdit] CreateRowSpecificEditor returned Nothing for [{0}]", e.Column.FieldName)
End If
End If
End SyncLock
End If
Return
Return ' ✅ WICHTIG: Verlässt Handler nach dynamischem Editor!
End If
' Standard-Editor aus Cache
' *** SCHRITT 2: Statische Editoren (nur wenn NICHT dynamisch) ***
Dim oEditorExists = GridTables_TestEditorExistsByControlAndColumn(pControlId, e.Column.FieldName)
If oEditorExists Then
_Logger.Debug("[CustomRowCellEdit] Assigning RepositoryItem for column [{0}] from GridTables cache.", e.Column.FieldName)
@@ -1065,6 +1044,91 @@ Namespace ControlCreator
End If
End Sub
AddHandler pGridView.ShownEditor,
Sub(sender As Object, e As EventArgs)
Try
Dim view As GridView = TryCast(sender, GridView)
If view Is Nothing OrElse view.ActiveEditor Is Nothing Then Return
' Nur für LookupControl3
Dim activeEditor = TryCast(view.ActiveEditor, LookupControl3)
If activeEditor Is Nothing Then Return
' Nur für dynamische Editoren
Dim columnName = view.FocusedColumn?.FieldName
If String.IsNullOrEmpty(columnName) OrElse Not _DynamicEditorColumns.Contains(columnName) Then Return
' Verhindere doppelte Registrierung via Tag
If activeEditor.Tag IsNot Nothing AndAlso activeEditor.Tag.ToString() = "ClearHandlerRegistered" Then
_Logger.Debug("[ShownEditor] Properties.SelectedValuesChanged-Handler bereits registriert für [{0}]", columnName)
Return
End If
_Logger.Debug("[ShownEditor] ✓ Registriere Properties.SelectedValuesChanged-Handler für [{0}]", columnName)
' *** Properties.SelectedValuesChanged mit GLOBALEM Guard ***
Dim clearHandler As RepositoryItemLookupControl3.SelectedValuesChangedHandler =
Sub(editorSender As Object, selectedValues As List(Of String))
Try
Dim isCleared As Boolean = selectedValues Is Nothing OrElse selectedValues.Count = 0
If isCleared Then
' *** GLOBALES GUARD: Key aus GridView-Hash + ColumnName ***
Dim guardKey As String = $"{view.GetHashCode()}|{columnName}"
SyncLock _clearOperationsInProgress
If _clearOperationsInProgress.Contains(guardKey) Then
_Logger.Debug("[LookupControl3] Clear already in progress for [{0}] → SKIP", columnName)
Return
End If
' Guard aktivieren
_clearOperationsInProgress.Add(guardKey)
End SyncLock
_Logger.Info("[LookupControl3] Properties.SelectedValuesChanged leer → clearing [{0}]", columnName)
' BeginInvoke: Lookup ERST schließen lassen, DANN Zelle leeren
view.GridControl.BeginInvoke(New Action(
Sub()
Try
Dim rowHandle = view.FocusedRowHandle
If view.IsValidRowHandle(rowHandle) Then
view.SetRowCellValue(rowHandle, columnName, String.Empty)
_Logger.Debug("[LookupControl3] ✓ Cell cleared: Row={0}, Column={1}", rowHandle, columnName)
End If
Catch ex As Exception
_Logger.Error("[LookupControl3] Error in BeginInvoke: {0}", ex.Message)
_Logger.Error(ex)
Finally
' *** GUARD ZURÜCKSETZEN (nach UI-Update) ***
SyncLock _clearOperationsInProgress
_clearOperationsInProgress.Remove(guardKey)
End SyncLock
End Try
End Sub))
Else
_Logger.Debug("[LookupControl3] Properties.SelectedValuesChanged.Count=[{0}] → keine Löschung", selectedValues.Count)
End If
Catch ex As Exception
_Logger.Error("[LookupControl3] Properties.SelectedValuesChanged Error: {0}", ex.Message)
_Logger.Error(ex)
End Try
End Sub
' Handler registrieren
AddHandler activeEditor.Properties.SelectedValuesChanged, clearHandler
' Markierung setzen (verhindert doppelte Registrierung)
activeEditor.Tag = "ClearHandlerRegistered"
Catch ex As Exception
_Logger.Error("[ShownEditor] Properties.SelectedValuesChanged-Handler Error: {0}", ex.Message)
_Logger.Error(ex)
End Try
End Sub
AddHandler pGridView.ValidateRow, AddressOf View_ValidateRow
AddHandler pControl.LostFocus, AddressOf Control_LostFocus
@@ -1401,7 +1465,10 @@ CheckNewItemRow:
columnName As String,
resolvedSql As String,
connectionId As Integer,
isAdvancedLookup As Boolean) As RepositoryItem
isAdvancedLookup As Boolean,
pView As GridView, ' ← NEU!
pRowHandle As Integer ' ← NEU!
) As RepositoryItem
Try
If LOG_HOTSPOTS Then _Logger.Debug("[CreateRowSpecificEditor] Executing SQL for column [{0}]: {1}", columnName, resolvedSql)
@@ -1418,12 +1485,7 @@ CheckNewItemRow:
Return Nothing
End If
If LOG_HOTSPOTS Then _Logger.Debug("[CreateRowSpecificEditor] Retrieved {0} rows for column [{1}]", oDataTable.Rows.Count, columnName)
' *** NEU: Log erste 5 Rows für Debugging ***
For i As Integer = 0 To Math.Min(4, oDataTable.Rows.Count - 1)
If LOG_HOTSPOTS Then _Logger.Debug("[CreateRowSpecificEditor] DataTable Row[{0}]: [{1}]", i, oDataTable.Rows(i)(0))
Next
LOGGER.Debug("[CreateRowSpecificEditor] Retrieved {0} rows for column [{1}]", oDataTable.Rows.Count, columnName)
' Editor erstellen (analog zu GridTables_GetRepositoryItemForColumn)
If isAdvancedLookup Then
@@ -1618,12 +1680,22 @@ CheckNewItemRow:
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
' *** NEU: Guard-Prüfung für btnClear-Operationen ***
Dim guardKey As String = $"{pView.GetHashCode()}|{pArgs.Column.FieldName}"
SyncLock _clearOperationsInProgress
If _clearOperationsInProgress.Contains(guardKey) Then
_Logger.Debug("[HandleInheritedColumnValue] Clear operation in progress for [{0}] → SKIP inheritance dialog", pArgs.Column.FieldName)
Return
End If
End SyncLock
If isApplyingInheritedValue OrElse pArgs.RowHandle = DevExpress.XtraGrid.GridControl.InvalidRowHandle Then
If isApplyingInheritedValue Then
_Logger.Debug("Currently applying inherited value for column {0} skipping to prevent recursion.", pArgs.Column.FieldName)
@@ -1665,7 +1737,7 @@ CheckNewItemRow:
Dim affectedRowsCount = pView.DataRowCount - listIndex - 1
Dim confirmationEntry = GetInheritanceConfirmationEntry(pArgs.Column.FieldName)
_Logger.Debug("Inheritance confirmation entry for column {0}: {1}", pArgs.Column.FieldName, confirmationEntry.Count)
If affectedRowsCount > 0 AndAlso confirmationEntry.Count < InheritanceMsgAmount Then
Dim confirmMessage As String = String.Format(
"Möchten Sie den Wert '{0}' an {1} nachfolgende Zeile(n) vererben?",
@@ -1758,7 +1830,6 @@ CheckNewItemRow:
pView.SetRowCellValue(targetHandle, pArgs.Column.FieldName, valueToApply)
' *** SCHRITT 2: Cache-Keys für DIESE Zeile sammeln ***
' Finde alle dynamischen Spalten, die die geänderte Spalte via {#TBCOL#...} referenzieren
For Each dynColName In _DynamicEditorColumns
Dim oColDef As DataRow = pColumnDefinition.
Select($"SPALTENNAME = '{dynColName}'").
@@ -1768,9 +1839,7 @@ CheckNewItemRow:
Dim oSqlCommand As String = oColDef.ItemEx("SQL_COMMAND", "")
' Prüfe ob SQL {#TBCOL#<ColumnName>} enthält
If oSqlCommand.Contains($"{{#TBCOL#{pArgs.Column.FieldName}}}") Then
' SQL auflösen mit NEUEM Wert (targetHandle!)
Try
Dim resolvedSql = ResolveSqlTemplate(oSqlCommand, pView, targetHandle)
Dim cacheKey = $"{dynColName}|{resolvedSql.GetHashCode()}"
@@ -1809,7 +1878,6 @@ CheckNewItemRow:
isApplyingInheritedValue = False
End Try
End Sub
Private Sub SetInheritanceConfirmationCount(columnName As String, newCount As Integer)
Dim entries = UserInheritance_ConfirmationByColumn
If entries Is Nothing Then

File diff suppressed because it is too large Load Diff