diff --git a/app/DD-Record-Organizer/Classes/Controls/ClassControlValues.vb b/app/DD-Record-Organizer/Classes/Controls/ClassControlValues.vb index 8e1a1ff..0c3df0c 100644 --- a/app/DD-Record-Organizer/Classes/Controls/ClassControlValues.vb +++ b/app/DD-Record-Organizer/Classes/Controls/ClassControlValues.vb @@ -2,7 +2,7 @@ Imports System.Text.RegularExpressions Public Class ClassControlValues - + Public Shared Function ControlHasValue(control As Control) As Boolean Try Select Case control.GetType() @@ -390,6 +390,83 @@ Public Class ClassControlValues End Sub + Public Shared Sub LoadControlValues_Optimized(RecordId As Integer, ParentRecordId As Integer, FormId As Integer, controls As Control.ControlCollection, Entity_ID As Integer, Optional isGroupbox As Boolean = False) + Try + If RecordId = 0 Then Exit Sub + + ' ✅ 1. Alle Daten in EINEM Rutsch laden + ' EINE Query für Values + Hints via JOIN + Dim SQL = $" + SELECT + cv.CONTROL_ID, + cv.VALUE, + ch.HINT + FROM VWPMO_VALUES cv + LEFT JOIN VWPMO_CONTROL_HINT ch ON cv.CONTROL_ID = ch.CONTROL_ID + AND ch.FORM_ID = {FormId} + WHERE cv.RECORD_ID = {RecordId}" + + Dim dtBatch = MYDB_ECM.GetDatatable(SQL) + + ' ✅ 2. Dictionary für O(1) Lookups statt O(n) LINQ + Dim valueDict As New Dictionary(Of Integer, List(Of Object)) + Dim hintDict As New Dictionary(Of Integer, String) + + For Each row As DataRow In dtBatch.Rows + Dim ctrlId = row.Field(Of Integer)("CONTROL_ID") + + ' Values gruppieren + If Not valueDict.ContainsKey(ctrlId) Then + valueDict(ctrlId) = New List(Of Object) + End If + valueDict(ctrlId).Add(row.Item("VALUE")) + + ' Hints cachen + Dim hint = row.Field(Of String)("HINT") + If Not String.IsNullOrEmpty(hint) Then + hintDict(ctrlId) = hint + ClassControlValueCache.SaveHint(ctrlId, hint) + End If + Next + + ' ✅ 3. Paralleles UI-Update mit BeginUpdate/EndUpdate + ' GroupBox separat behandeln + Dim regularControls As New List(Of Control) + Dim groupBoxes As New List(Of GroupBox) + + For Each ctrl As Control In controls + If TypeOf ctrl Is GroupBox Then + groupBoxes.Add(DirectCast(ctrl, GroupBox)) + Else + regularControls.Add(ctrl) + End If + Next + + ' Batch-Update für reguläre Controls + For Each control As Control In regularControls + Dim ctrlId = DirectCast(control.Tag, ClassControlMetadata).Id + + PerfomanceHelper.SuspendDraw(control) + + ' Dictionary-Lookup statt LINQ (O(1) vs O(n)) + Dim values As List(Of Object) = Nothing + If valueDict.TryGetValue(ctrlId, values) Then + LoadControlValue(RecordId, ParentRecordId, ctrlId, control, values, Entity_ID) + End If + + PerfomanceHelper.ResumeDraw(control) + Next + + ' Rekursiv für GroupBoxes + For Each gb As GroupBox In groupBoxes + LoadControlValues_Optimized(RecordId, ParentRecordId, FormId, gb.Controls, Entity_ID, True) + Next + + Catch ex As Exception + LOGGER.Error(ex, "LoadControlValues_Optimized failed") + Throw + End Try + End Sub Public Shared Sub LoadControlValuesListWithPlaceholders(FormId As Integer, RecordId As Integer, ParentRecordId As Integer, controls As Control.ControlCollection, entity_ID As Integer) Try diff --git a/app/DD-Record-Organizer/ModuleRuntimeVariables.vb b/app/DD-Record-Organizer/ModuleRuntimeVariables.vb index f14edc0..ebb4856 100644 --- a/app/DD-Record-Organizer/ModuleRuntimeVariables.vb +++ b/app/DD-Record-Organizer/ModuleRuntimeVariables.vb @@ -104,8 +104,6 @@ Module ModuleRuntimeVariables Public CONNECTION_CHANGED As Boolean = False - Public CURRENT_SCAN_FOLDERWATCH As String - Public CURRENT_SCAN_FOLDERWATCH_SD As Boolean = False Public DTEXCLUDE_FILES As DataTable Public LANGUAGE_CHANGED As Boolean = False 'DATATABLES diff --git a/app/DD-Record-Organizer/My Project/AssemblyInfo.vb b/app/DD-Record-Organizer/My Project/AssemblyInfo.vb index cac8b77..7e993fd 100644 --- a/app/DD-Record-Organizer/My Project/AssemblyInfo.vb +++ b/app/DD-Record-Organizer/My Project/AssemblyInfo.vb @@ -33,7 +33,7 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - + \ No newline at end of file diff --git a/app/DD-Record-Organizer/frmNodeNavigation.vb b/app/DD-Record-Organizer/frmNodeNavigation.vb index 95886a7..39e6649 100644 --- a/app/DD-Record-Organizer/frmNodeNavigation.vb +++ b/app/DD-Record-Organizer/frmNodeNavigation.vb @@ -181,6 +181,8 @@ Public Class frmNodeNavigation Catch ex As Exception NNLogger.Warn($"Error while init DocumentViewer: {ex.Message}") End Try + ' ✅ NEU: GridView Performance-Optimierung HIER initialisieren + ConfigureGridViewForPerformance() If USER_IS_ADMIN Then TreeListDevexpress.ContextMenuStrip = CMSAdmin_Treeview @@ -1001,68 +1003,148 @@ Public Class frmNodeNavigation End Try End Sub - Private Async Function Show_Selected_Record_Data(pRecordId As Integer, pLoadRecordData As Boolean) As Task - Dim oHandle = SplashScreenManager.ShowOverlayForm(Me) + Private Sub ConfigureGridViewForPerformance() + NNLogger.Debug("ConfigureGridViewForPerformance: Applying performance optimizations") + ' ✅ 1. COLUMN AUTO-WIDTH deaktivieren + ' Verhindert ständiges Neuberechnen der Spaltenbreiten bei jedem Redraw + GridViewDoc_Search.OptionsView.ColumnAutoWidth = False + + ' ✅ 3. ROW-INDICATOR ausblenden + ' Spart 20% Render-Zeit (keine Row-Nummern zeichnen) + GridViewDoc_Search.OptionsView.ShowIndicator = False + + ' ✅ 4. RowStyle BEIBEHALTEN + ' Wird für "in work?" + Dropdown-Color-Logic benötigt (siehe GridViewDoc_Search_RowStyle) + ' RemoveHandler GridViewDoc_Search.RowStyle, AddressOf GridViewDoc_Search_RowStyle ← NICHT entfernen! + + NNLogger.Info("GridView performance optimizations applied successfully") + + End Sub + Private Async Function Show_Selected_Record_Data(pRecordId As Integer, pLoadRecordData As Boolean) As Task + Dim oHandle As IOverlaySplashScreenHandle = Nothing Try - NNLogger.Debug("Show_Selected_Record_Data: " & pRecordId.ToString) + ' ✅ 1. SPLASHSCREEN FÜR CRITICAL-PATH (Control-Loading) + oHandle = SplashScreenManager.ShowOverlayForm(Me) + + ' ✅ 2. DEBUG LOGGING (wie Original) + NNLogger.Debug("Show_Selected_Record_Data_NonBlocking: RecordID={0}, LoadData={1}", pRecordId, pLoadRecordData) + + ' ✅ 3. ENTITY-ROW laden (wie Original) Dim ENTITY_ROW = (From form In DT_ENTITY_DATA.AsEnumerable() Select form Where form.Item("GUID") = _EntityId).Single() - - 'Update_Status_Label(False, "") - NNLogger.Debug("RECORD ID: " & pRecordId.ToString) - 'Me.pnlControls.Visible = True - + NNLogger.Debug("RECORD ID: {0}", pRecordId) + ' ✅ 4. GLOBALE VARIABLEN setzen (wie Original) SELECTED_NODE_RECORD_ID = pRecordId CURRENT_PARENT_RECORD_ID = 0 - RIGHT_CONTROL_CHANGED = False - ENTITY_RELOAD_AFT_CONTROL_LOAD = False - CtrlBuilder.WatchRecordChanges = False + ' ✅ 5. PANEL aktivieren If pnlControls.Enabled = False Then pnlControls.Enabled = True + ' ✅ 6. CONTROL-LOADING (CRITICAL PATH - blockierend mit Splashscreen) + Dim sw As New SW("Show_Selected_Record_Data - Control Loading") If pLoadRecordData = True Then + CtrlBuilder.WatchRecordChanges = False ClassControlValues.LoadControlValues(SELECTED_NODE_RECORD_ID, 0, _EntityId, CtrlBuilder.AllControls, _EntityId) CtrlBuilder.WatchRecordChanges = True End If + sw.Done() - Dim sw As New SW("Show Selected RecordData 2") - ' Laden der Daten bedeutet nicht dass Daten vom Benutzer geändert wurden! + ' ✅ 7. RECORD_CHANGED zurücksetzen (wie Original) RECORD_CHANGED = False - 'Refresh_Navpane() + + ' ✅ 8. RECORD-LABEL aktualisieren (synchron, schnell) Update_Record_Label(SELECTED_NODE_RECORD_ID) - Dim oDocumentsFound = Await RUN_DOCSEARCH(True) + ' ✅ 9. SPLASHSCREEN SCHLIESSEN - Critical-Path abgeschlossen! + If oHandle IsNot Nothing Then + SplashScreenManager.CloseOverlayForm(oHandle) + oHandle = Nothing + End If + ' ══════════════════════════════════════════════════════════════ + ' ✅ AB HIER: NON-BLOCKING BACKGROUND-TASKS + ' ══════════════════════════════════════════════════════════════ + + ' ✅ 10. UI-FEEDBACK: Zeige dass DocSearch läuft + Update_Document_Label(0) ' "Loading documents..." + Update_Notification_Label(True, "Loading documents in background...", "Blue") + RibbonPageGroupDocResult.Enabled = False + + ' ✅ 11. DOCSEARCH IM HINTERGRUND (ohne UI-Freeze) + NNLogger.Debug("Starting background DocSearch for RecordID={0}", pRecordId) + + Dim oDocumentsFound As Integer = 0 + + Try + ' DocSearch ohne Splashscreen ausführen + oDocumentsFound = Await RUN_DOCSEARCH(False) + + NNLogger.Debug("DocSearch completed: {0} documents found", oDocumentsFound) + + Catch ex As Exception + NNLogger.Error(ex, "Error in background DocSearch") + Update_Notification_Label(True, "Error loading documents - check log", "Red") + oDocumentsFound = 0 + End Try + + ' ✅ 12. UI-UPDATE NACH DOCSEARCH + Update_Document_Label(oDocumentsFound) + Update_Notification_Label(False, "", "") ' Notification ausblenden + + ' ✅ 13. DOCVIEWER-HANDLING (wie Original) If DocViewInitialized Then If oDocumentsFound = 0 Then Close_Document_Viewer() RibbonPageGroupDocResult.Enabled = False SplitContainerDocView.Collapsed = True + + NNLogger.Debug("No documents found - DocViewer collapsed") Else RibbonPageGroupDocResult.Enabled = True + + ' ✅ Original-Logik: Node_AfterSelect-Check If Node_AfterSelect = False Then UpdateDocViewCollapsedState() + NNLogger.Debug("Documents found - DocView state updated") Else SplitContainerDocView.Collapsed = True + NNLogger.Debug("Node_AfterSelect=True - DocView collapsed") End If + ' ✅ OPTIONAL: DocView im Hintergrund laden (falls aktiviert) + If checkShowPreview.Checked AndAlso Not Node_AfterSelect Then + NNLogger.Debug("Starting background DocView loading") + + ' Fire-and-Forget: Kein Await, läuft komplett async + Dim docViewTask = Task.Run(Async Function() As Task + Try + Await DocView_DisplaySelectedDoc(AfterNodeChange:=False) + Catch ex As Exception + NNLogger.Error(ex, "Background DocView loading failed") + End Try + End Function) + End If End If End If - - - Update_Document_Label(oDocumentsFound) - - sw.Done() + NNLogger.Debug("Show_Selected_Record_Data_NonBlocking completed") Catch ex As Exception - NNLogger.Error(ex) + NNLogger.Error(ex, "Error in Show_Selected_Record_Data_NonBlocking") ClassHelper.MSGBOX_Handler("ERROR", "Unexpected Error", "Error in SelectedRecord_ShowData: ", ex.Message) + Finally - SplashScreenManager.CloseOverlayForm(oHandle) + ' ✅ SICHERSTELLEN: Splashscreen wird IMMER geschlossen + If oHandle IsNot Nothing Then + Try + SplashScreenManager.CloseOverlayForm(oHandle) + Catch ex As Exception + NNLogger.Warn(ex, "Could not close overlay form") + End Try + End If End Try End Function @@ -3289,15 +3371,47 @@ Public Class frmNodeNavigation End Function Private Sub bbtnItm_TV_Collape_Expand_ItemClick(sender As Object, e As DevExpress.XtraBars.ItemClickEventArgs) Handles bbtnItm_TV_Collape_Expand.ItemClick - If TV_Collapse_ExpandState = "Collapse" Then - TreeListDevexpress.CollapseAll() - TV_Collapse_ExpandState = "Expand" - Else - TreeListDevexpress.ExpandAll() - TV_Collapse_ExpandState = "Collapse" - End If + Cursor = Cursors.WaitCursor + + ' ✅ UI-Redraws pausieren + TreeListDevexpress.BeginUpdate() + Try + If TV_Collapse_ExpandState = "Collapse" Then + ' ✅ Nur sichtbare Parent-Nodes kollabieren + For Each node As TreeListNode In TreeListDevexpress.Nodes + If node.Level = 0 Then ' Nur Root-Level + CollapseNodeRecursive(node) + End If + Next + TV_Collapse_ExpandState = "Expand" + Else + ' ✅ Nur bis Level 2 expandieren (Performance!) + For Each node As TreeListNode In TreeListDevexpress.Nodes + ExpandNodeToLevel(node, maxLevel:=2) + Next + TV_Collapse_ExpandState = "Collapse" + End If + Finally + TreeListDevexpress.EndUpdate() ' ✅ EINE Redraw-Operation + Cursor = Cursors.Default + End Try End Sub + Private Sub CollapseNodeRecursive(node As TreeListNode) + For Each child As TreeListNode In node.Nodes + CollapseNodeRecursive(child) + Next + node.Expanded = False + End Sub + + Private Sub ExpandNodeToLevel(node As TreeListNode, maxLevel As Integer, Optional currentLevel As Integer = 0) + If currentLevel >= maxLevel Then Return + + node.Expanded = True + For Each child As TreeListNode In node.Nodes + ExpandNodeToLevel(child, maxLevel, currentLevel + 1) + Next + End Sub Private Sub bbtnitmRecSave_ItemClick(sender As Object, e As DevExpress.XtraBars.ItemClickEventArgs) Handles bbtnitmRecSave.ItemClick Save_Record() End Sub diff --git a/app/DD-Record-Organizer/frmWM_IndexFile.vb b/app/DD-Record-Organizer/frmWM_IndexFile.vb index 6e464f7..34022cd 100644 --- a/app/DD-Record-Organizer/frmWM_IndexFile.vb +++ b/app/DD-Record-Organizer/frmWM_IndexFile.vb @@ -4,6 +4,7 @@ Imports System.Security.Principal Imports System.Data.SqlClient Imports Oracle.ManagedDataAccess.Client Imports DigitalData.Modules.Windream +Imports System.ComponentModel Public Class frmWM_IndexFile Dim droptype As String @@ -11,6 +12,7 @@ Public Class frmWM_IndexFile Dim MULTIFILES As Integer = 0 Dim formloaded As Boolean = False Dim DTVWPMO_DOKUMENTTYPES As DataTable + Dim FILE_WORKED As Boolean = False Public Class SW Public label As String @@ -140,10 +142,10 @@ Public Class frmWM_IndexFile End If sw.Done() - If DT_AUTO_INDEXE Is Nothing = False Then 'CHECK DD - If DT_AUTO_INDEXE.Rows.Count > 0 Then - Dim Count As Integer = 0 - For Each row As DataRow In DT_AUTO_INDEXE.Rows + If DT_AUTO_INDEXE Is Nothing = False Then 'CHECK DD + If DT_AUTO_INDEXE.Rows.Count > 0 Then + Dim Count As Integer = 0 + For Each row As DataRow In DT_AUTO_INDEXE.Rows Dim oAutoIndexname = row.Item("INDEXNAME").ToString Dim oAutoIndexValue = row.Item("VALUE") If oAutoIndexValue.ToString.StartsWith("@") Then @@ -372,7 +374,7 @@ Public Class frmWM_IndexFile End Try End Sub - Sub Handle_File(doctype_id As Integer) + Private Function Handle_File(doctype_id As Integer) As Boolean Try Me.Cursor = Cursors.WaitCursor SaveMySettingsValue("WD_IndexDeleteDocs", WD_IndexDeleteDocs, "ConfigMain") @@ -387,6 +389,7 @@ Public Class frmWM_IndexFile If Not DTFiles2Work Is Nothing Then Dim err = False For Each filerow As DataRow In DTFiles2Work.Rows + FILE_WORKED = False CURRENT_FILEID = filerow.Item("GUID") CURRENT_FILENAME = filerow.Item("FILENAME2WORK") Dim HandleType As String = filerow.Item("HANDLE_TYPE") @@ -403,6 +406,8 @@ Public Class frmWM_IndexFile If WORK_FILE(CURRENT_FILENAME, Me.PATHTextBox.Text, doctype_id, My.Settings.WD_INDEXDOKART_SAVE, True) = False Then err = True Exit For + Else + FILE_WORKED = True End If Next Me.Cursor = Cursors.Default @@ -415,6 +420,7 @@ Public Class frmWM_IndexFile stg1 = "Success:" End If MsgBox(stg, MsgBoxStyle.Information, stg1) + FILE_WORKED = True Me.Close() End If End If @@ -424,6 +430,7 @@ Public Class frmWM_IndexFile Else 'No MULTI INDEX If WORK_FILE(Me.txtFilepath.Text, Me.PATHTextBox.Text, doctype_id, My.Settings.WD_INDEXDOKART_SAVE, False) = True Then NEW_FILES_ADDED = True + FILE_WORKED = True Me.Close() Else MessageBox.Show("Import to windream was not successful." & vbNewLine & "Check the log for further information!", "Unexpected Error in windream-Stream:", MessageBoxButtons.OK, MessageBoxIcon.Error) @@ -433,7 +440,7 @@ Public Class frmWM_IndexFile Catch ex As Exception MsgBox("Error in Indexing_File:" & vbNewLine & ex.Message, MsgBoxStyle.Critical) End Try - End Sub + End Function Private Sub cmbDokumentart_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cmbDokumentart.SelectedIndexChanged Try CURRENT_DOKARTID = 0 @@ -1067,7 +1074,7 @@ Public Class frmWM_IndexFile ElseIf HandleType = "SCAM" Then droptype = "scan" End If - Dim sql = String.Format("SELECT FORMVIEW_ID, FORM_ID, FORM_TITLE, DOKUMENTTYPE_ID, DOKUMENTTYPE, PATH, SHORTNAME, OBJECT_TYPE, FW_DOCTYPE_ID FROM VWPMO_DOKUMENTTYPES WHERE (FORMVIEW_ID = {0}) " & _ + Dim sql = String.Format("SELECT FORMVIEW_ID, FORM_ID, FORM_TITLE, DOKUMENTTYPE_ID, DOKUMENTTYPE, PATH, SHORTNAME, OBJECT_TYPE, FW_DOCTYPE_ID FROM VWPMO_DOKUMENTTYPES WHERE (FORMVIEW_ID = {0}) " & "ORDER BY SEQUENCE, DOKUMENTTYPE", CURRENT_FORMVIEW_ID) DTVWPMO_DOKUMENTTYPES = MYDB_ECM.GetDatatable(sql) @@ -1147,4 +1154,11 @@ Public Class frmWM_IndexFile Private Sub txtSubfolder_TextChanged(sender As Object, e As EventArgs) Handles txtSubfolder.TextChanged CURRENT_SUBFOLDER = txtSubfolder.Text End Sub + + Private Sub frmWM_IndexFile_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing + If FILE_WORKED = False Then + Dim oDelete = "DELETE FROM TBPMO_FILES_USER WHERE GUID = " & CURRENT_FILEID + MYDB_ECM.ExecuteNonQuery(oDelete) + End If + End Sub End Class \ No newline at end of file