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 Shared _ResolvedSqlCache As New Dictionary(Of String, String)
Private _isRefreshingFormula As Boolean = False ' *** NEU: Flag für Formel-Refresh *** 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 = "" Private _currencySymbol As String = ""
''' <summary> ''' <summary>
''' SHARED Dictionary: Speichert das aktuelle Währungssymbol PRO GridView (via Name). ''' SHARED Dictionary: Speichert das aktuelle Währungssymbol PRO GridView (via Name).
@@ -90,70 +91,34 @@ Namespace ControlCreator
For Each match As Match In matches For Each match As Match In matches
Dim colName = match.Groups(1).Value Dim colName = match.Groups(1).Value
Dim cellValue = pView.GetRowCellValue(rowHandle, colName) 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) resolvedSql = resolvedSql.Replace(match.Value, safeValue)
Next Next
' *** SCHRITT 2: {#CTRL#...} via clsPatterns - MIT CACHE ***
' *** SCHRITT 2: {#CTRL#...} via clsPatterns - MIT CACHE *** ' *** SCHRITT 2: {#CTRL#...} via clsPatterns - MIT CACHE ***
If _ParentControl IsNot Nothing AndAlso resolvedSql.Contains("{#CTRL#") Then If _ParentControl IsNot Nothing AndAlso resolvedSql.Contains("{#CTRL#") Then
' Cache-Key: Hash aus SQL + Control-Werten ' *** FIX: Cache-Key ERST NACH ReplaceAllValues erstellen! ***
Dim cacheKey = GenerateCacheKey(resolvedSql, _ParentControl) 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 SyncLock _ResolvedSqlCache
If _ResolvedSqlCache.ContainsKey(cacheKey) Then ' Cache speichern (für nächsten Aufruf)
' ✅ CACHE HIT: Kein Logging → spart 380+ Log-Zeilen If Not _ResolvedSqlCache.ContainsKey(cacheKey) Then
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)
_ResolvedSqlCache(cacheKey) = resolvedSql _ResolvedSqlCache(cacheKey) = resolvedSql
_Logger.Debug("[ResolveSqlTemplate] ✓ Cached SQL: Key=[{0}]", cacheKey)
' *** 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
End If End If
End SyncLock 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 Else
If _ParentControl Is Nothing Then If _ParentControl Is Nothing Then
_Logger.Warn("[ResolveSqlTemplate] _ParentControl is NOTHING! Cannot replace {{#CTRL#...}}") _Logger.Warn("[ResolveSqlTemplate] _ParentControl is NOTHING! Cannot replace {{#CTRL#...}}")
@@ -853,7 +818,7 @@ Namespace ControlCreator
AddHandler pGridView.CustomRowCellEdit, AddHandler pGridView.CustomRowCellEdit,
Sub(sender As Object, e As CustomRowCellEditEventArgs) Sub(sender As Object, e As CustomRowCellEditEventArgs)
Try Try
' *** NEU: Dynamische Editoren aus Cache oder neu erstellen *** ' *** SCHRITT 1: Dynamische Editoren ZUERST prüfen ***
If _DynamicEditorColumns.Contains(e.Column.FieldName) Then If _DynamicEditorColumns.Contains(e.Column.FieldName) Then
Dim oColumnData As DataRow = pColumnTable. Dim oColumnData As DataRow = pColumnTable.
Select($"SPALTENNAME = '{e.Column.FieldName}'"). Select($"SPALTENNAME = '{e.Column.FieldName}'").
@@ -864,38 +829,52 @@ Namespace ControlCreator
Dim oConnectionId As Integer = oColumnData.ItemEx("CONNECTION_ID", 1) Dim oConnectionId As Integer = oColumnData.ItemEx("CONNECTION_ID", 1)
Dim oIsAdvancedLookup As Boolean = oColumnData.ItemEx("ADVANCED_LOOKUP", False) Dim oIsAdvancedLookup As Boolean = oColumnData.ItemEx("ADVANCED_LOOKUP", False)
' *** SQL auflösen (für Cache-Key) *** If String.IsNullOrEmpty(oSqlCommand) Then
Dim resolvedSql = ResolveSqlTemplate(oSqlCommand, TryCast(sender, GridView), e.RowHandle) _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()}" Dim cacheKey As String = $"{e.Column.FieldName}|{resolvedSql.GetHashCode()}"
' *** CACHE-CHECK *** ' Cache prüfen
SyncLock _DynamicEditorCacheShared SyncLock _DynamicEditorCacheShared
If _DynamicEditorCacheShared.ContainsKey(cacheKey) Then If _DynamicEditorCacheShared.ContainsKey(cacheKey) Then
' ✅ CACHE HIT: Editor wiederverwenden _Logger.Info("[CustomRowCellEdit] ✓ CACHE HIT: [{0}]", cacheKey)
e.RepositoryItem = _DynamicEditorCacheShared(cacheKey) e.RepositoryItem = _DynamicEditorCacheShared(cacheKey)
Else Return
' ❌ 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
End If End If
End SyncLock 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 End If
Return Return ' ✅ WICHTIG: Verlässt Handler nach dynamischem Editor!
End If End If
' Standard-Editor aus Cache ' *** SCHRITT 2: Statische Editoren (nur wenn NICHT dynamisch) ***
Dim oEditorExists = GridTables_TestEditorExistsByControlAndColumn(pControlId, e.Column.FieldName) Dim oEditorExists = GridTables_TestEditorExistsByControlAndColumn(pControlId, e.Column.FieldName)
If oEditorExists Then If oEditorExists Then
_Logger.Debug("[CustomRowCellEdit] Assigning RepositoryItem for column [{0}] from GridTables cache.", e.Column.FieldName) _Logger.Debug("[CustomRowCellEdit] Assigning RepositoryItem for column [{0}] from GridTables cache.", e.Column.FieldName)
@@ -1065,6 +1044,91 @@ Namespace ControlCreator
End If End If
End Sub 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 pGridView.ValidateRow, AddressOf View_ValidateRow
AddHandler pControl.LostFocus, AddressOf Control_LostFocus AddHandler pControl.LostFocus, AddressOf Control_LostFocus
@@ -1398,10 +1462,13 @@ CheckNewItemRow:
''' Wird verwendet, wenn SQL_COMMAND Platzhalter wie {#TBCOL#...} enthält. ''' Wird verwendet, wenn SQL_COMMAND Platzhalter wie {#TBCOL#...} enthält.
''' </summary> ''' </summary>
Private Function CreateRowSpecificEditor( Private Function CreateRowSpecificEditor(
columnName As String, columnName As String,
resolvedSql As String, resolvedSql As String,
connectionId As Integer, connectionId As Integer,
isAdvancedLookup As Boolean) As RepositoryItem isAdvancedLookup As Boolean,
pView As GridView, ' ← NEU!
pRowHandle As Integer ' ← NEU!
) As RepositoryItem
Try Try
If LOG_HOTSPOTS Then _Logger.Debug("[CreateRowSpecificEditor] Executing SQL for column [{0}]: {1}", columnName, resolvedSql) If LOG_HOTSPOTS Then _Logger.Debug("[CreateRowSpecificEditor] Executing SQL for column [{0}]: {1}", columnName, resolvedSql)
@@ -1418,22 +1485,17 @@ CheckNewItemRow:
Return Nothing Return Nothing
End If End If
If LOG_HOTSPOTS Then _Logger.Debug("[CreateRowSpecificEditor] Retrieved {0} rows for column [{1}]", oDataTable.Rows.Count, columnName) 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
' Editor erstellen (analog zu GridTables_GetRepositoryItemForColumn) ' Editor erstellen (analog zu GridTables_GetRepositoryItemForColumn)
If isAdvancedLookup Then If isAdvancedLookup Then
Dim oEditor = New RepositoryItemLookupControl3 With { Dim oEditor = New RepositoryItemLookupControl3 With {
.DisplayMember = oDataTable.Columns(0).ColumnName, .DisplayMember = oDataTable.Columns(0).ColumnName,
.ValueMember = oDataTable.Columns(0).ColumnName, .ValueMember = oDataTable.Columns(0).ColumnName,
.DataSource = oDataTable .DataSource = oDataTable
} }
_Logger.Debug("[CreateRowSpecificEditor] Created LookupControl3 with DisplayMember=[{0}], ValueMember=[{1}]", _Logger.Debug("[CreateRowSpecificEditor] Created LookupControl3 with DisplayMember=[{0}], ValueMember=[{1}]",
oEditor.DisplayMember, oEditor.ValueMember) oEditor.DisplayMember, oEditor.ValueMember)
Return oEditor Return oEditor
Else Else
Dim oEditor = New RepositoryItemComboBox() Dim oEditor = New RepositoryItemComboBox()
@@ -1618,12 +1680,22 @@ CheckNewItemRow:
If pView Is Nothing OrElse pArgs Is Nothing OrElse pArgs.Column Is Nothing Then If pView Is Nothing OrElse pArgs Is Nothing OrElse pArgs.Column Is Nothing Then
Return Return
End If End If
' *** NEU: Bei Formel-Refresh überspringen *** ' *** NEU: Bei Formel-Refresh überspringen ***
If _isRefreshingFormula Then If _isRefreshingFormula Then
_Logger.Debug("Skipping HandleInheritedColumnValue during formula refresh.") _Logger.Debug("Skipping HandleInheritedColumnValue during formula refresh.")
Return Return
End If 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 OrElse pArgs.RowHandle = DevExpress.XtraGrid.GridControl.InvalidRowHandle Then
If isApplyingInheritedValue Then If isApplyingInheritedValue Then
_Logger.Debug("Currently applying inherited value for column {0} skipping to prevent recursion.", pArgs.Column.FieldName) _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 affectedRowsCount = pView.DataRowCount - listIndex - 1
Dim confirmationEntry = GetInheritanceConfirmationEntry(pArgs.Column.FieldName) 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 If affectedRowsCount > 0 AndAlso confirmationEntry.Count < InheritanceMsgAmount Then
Dim confirmMessage As String = String.Format( Dim confirmMessage As String = String.Format(
"Möchten Sie den Wert '{0}' an {1} nachfolgende Zeile(n) vererben?", "Möchten Sie den Wert '{0}' an {1} nachfolgende Zeile(n) vererben?",
@@ -1758,7 +1830,6 @@ CheckNewItemRow:
pView.SetRowCellValue(targetHandle, pArgs.Column.FieldName, valueToApply) pView.SetRowCellValue(targetHandle, pArgs.Column.FieldName, valueToApply)
' *** SCHRITT 2: Cache-Keys für DIESE Zeile sammeln *** ' *** 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 For Each dynColName In _DynamicEditorColumns
Dim oColDef As DataRow = pColumnDefinition. Dim oColDef As DataRow = pColumnDefinition.
Select($"SPALTENNAME = '{dynColName}'"). Select($"SPALTENNAME = '{dynColName}'").
@@ -1768,9 +1839,7 @@ CheckNewItemRow:
Dim oSqlCommand As String = oColDef.ItemEx("SQL_COMMAND", "") Dim oSqlCommand As String = oColDef.ItemEx("SQL_COMMAND", "")
' Prüfe ob SQL {#TBCOL#<ColumnName>} enthält
If oSqlCommand.Contains($"{{#TBCOL#{pArgs.Column.FieldName}}}") Then If oSqlCommand.Contains($"{{#TBCOL#{pArgs.Column.FieldName}}}") Then
' SQL auflösen mit NEUEM Wert (targetHandle!)
Try Try
Dim resolvedSql = ResolveSqlTemplate(oSqlCommand, pView, targetHandle) Dim resolvedSql = ResolveSqlTemplate(oSqlCommand, pView, targetHandle)
Dim cacheKey = $"{dynColName}|{resolvedSql.GetHashCode()}" Dim cacheKey = $"{dynColName}|{resolvedSql.GetHashCode()}"
@@ -1809,7 +1878,6 @@ CheckNewItemRow:
isApplyingInheritedValue = False isApplyingInheritedValue = False
End Try End Try
End Sub End Sub
Private Sub SetInheritanceConfirmationCount(columnName As String, newCount As Integer) Private Sub SetInheritanceConfirmationCount(columnName As String, newCount As Integer)
Dim entries = UserInheritance_ConfirmationByColumn Dim entries = UserInheritance_ConfirmationByColumn
If entries Is Nothing Then If entries Is Nothing Then

File diff suppressed because it is too large Load Diff