Aktualisierung, Speichern und Leeren von GridControl-Zellen
This commit is contained in:
@@ -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
Reference in New Issue
Block a user