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)
' *** FIX: Cache-Key ERST NACH ReplaceAllValues erstellen! ***
Dim beforeReplace = resolvedSql
resolvedSql = clsPatterns.ReplaceAllValues(resolvedSql, _ParentControl, True)
' *** JETZT Cache-Key erstellen (mit aufgelösten Control-Werten) ***
Dim cacheKey = resolvedSql.GetHashCode().ToString()
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
Dim beforeReplace = resolvedSql
resolvedSql = clsPatterns.ReplaceAllValues(resolvedSql, _ParentControl, True)
' Cache speichern (für nächsten Aufruf)
If Not _ResolvedSqlCache.ContainsKey(cacheKey) Then
_ResolvedSqlCache(cacheKey) = resolvedSql
' *** DEBUG: Log VOR/NACH (nur bei MISS) ***
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
_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
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
_Logger.Info("[CustomRowCellEdit] 🆕 MISS: Creating editor for [{0}]", e.Column.FieldName)
Dim realEditor = CreateRowSpecificEditor(e.Column.FieldName, resolvedSql, oConnectionId, oIsAdvancedLookup)
If realEditor IsNot Nothing Then
' *** IN CACHE SPEICHERN ***
_DynamicEditorCacheShared(cacheKey) = realEditor
e.RepositoryItem = realEditor
_Logger.Info("[CustomRowCellEdit] ✓ Cached [{0}] editor (Type=[{1}])", e.Column.FieldName, realEditor.GetType().Name)
Else
_Logger.Warn("[CustomRowCellEdit] CreateRowSpecificEditor returned Nothing for [{0}]", e.Column.FieldName)
End If
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,
DirectCast(sender, GridView),
e.RowHandle
)
If realEditor IsNot Nothing Then
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
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
@@ -1398,10 +1462,13 @@ CheckNewItemRow:
''' Wird verwendet, wenn SQL_COMMAND Platzhalter wie {#TBCOL#...} enthält.
''' </summary>
Private Function CreateRowSpecificEditor(
columnName As String,
resolvedSql As String,
connectionId As Integer,
isAdvancedLookup As Boolean) As RepositoryItem
columnName As String,
resolvedSql As String,
connectionId As Integer,
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,22 +1485,17 @@ 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
Dim oEditor = New RepositoryItemLookupControl3 With {
.DisplayMember = oDataTable.Columns(0).ColumnName,
.ValueMember = oDataTable.Columns(0).ColumnName,
.DataSource = oDataTable
}
.DisplayMember = oDataTable.Columns(0).ColumnName,
.ValueMember = oDataTable.Columns(0).ColumnName,
.DataSource = oDataTable
}
_Logger.Debug("[CreateRowSpecificEditor] Created LookupControl3 with DisplayMember=[{0}], ValueMember=[{1}]",
oEditor.DisplayMember, oEditor.ValueMember)
oEditor.DisplayMember, oEditor.ValueMember)
Return oEditor
Else
Dim oEditor = New RepositoryItemComboBox()
@@ -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