diff --git a/Controls.DocumentViewer/App.config b/Controls.DocumentViewer/App.config index b00767fb..742d154b 100644 --- a/Controls.DocumentViewer/App.config +++ b/Controls.DocumentViewer/App.config @@ -31,7 +31,7 @@ - + @@ -39,7 +39,7 @@ - + @@ -51,7 +51,15 @@ - + + + + + + + + + diff --git a/Controls.DocumentViewer/DocumentViewer.vb b/Controls.DocumentViewer/DocumentViewer.vb index 436a1602..99af5180 100644 --- a/Controls.DocumentViewer/DocumentViewer.vb +++ b/Controls.DocumentViewer/DocumentViewer.vb @@ -7,7 +7,7 @@ Imports GdPicture14 Imports DigitalData.Modules.Logging Imports DigitalData.Modules.Messaging Imports DigitalData.Modules.Config - +Imports MsgReader Public Class DocumentViewer Private Enum ZoomMode Zoom50 @@ -513,12 +513,16 @@ Public Class DocumentViewer End Sub Public Sub DeleteTempFiles() - For Each oFile In _TempFiles + For Each oPath In _TempFiles Try - _logger.Debug("Deleting temp file [{0}]", oFile) - File.Delete(oFile) + _logger.Debug("Deleting temp path [{0}]", oPath) + If Directory.Exists(oPath) Then + Directory.Delete(oPath, True) + ElseIf File.Exists(oPath) Then + File.Delete(oPath) + End If Catch ex As Exception - _logger.Warn("Could not delete temp file [{0}]", oFile) + _logger.Warn("Could not delete temp path [{0}]", oPath) End Try Next End Sub @@ -530,7 +534,7 @@ Public Class DocumentViewer Select Case Extension.ToUpper Case ".CSV" _ViewerMode = ViewerMode.Excel - Case ".DOC", ".DOCX", ".ODT", ".RTF", ".TXT" + Case ".DOC", ".DOCX", ".ODT", ".RTF", ".TXT", ".MSG", ".EML" _ViewerMode = ViewerMode.Richtext Case Else _ViewerMode = ViewerMode.GDPicture @@ -702,22 +706,109 @@ Public Class DocumentViewer HideGdViewer() End Sub + ''' + ''' Lädt eine Richtext-kompatible Datei in den RichEditControl. + ''' MSG-Dateien werden vor dem Laden in HTML konvertiert. + ''' Private Function LoadRichtextFile(FilePath As String, Extension As String) As Boolean Try - RichEditControl1.LoadDocument(FilePath, GetDocumentFormat(Extension)) + ' Eingabevalidierung + If String.IsNullOrEmpty(FilePath) Then + _logger.Warn("LoadRichtextFile: FilePath ist leer oder Nothing") + Return False + End If + + If String.IsNullOrEmpty(Extension) Then + _logger.Warn("LoadRichtextFile: Extension ist leer oder Nothing für Datei [{0}]", FilePath) + Return False + End If + + If Not IO.File.Exists(FilePath) Then + _logger.Warn("LoadRichtextFile: Datei existiert nicht [{0}]", FilePath) + ShowFileNotLoadedError(FilePath) + Return False + End If + + _logger.Debug("LoadRichtextFile: Starte Laden von [{0}] mit Extension [{1}]", FilePath, Extension) + + Dim oLoadPath As String = FilePath + Dim oFormat = GetDocumentFormat(Extension) + + ' MSG-Dateien müssen zuerst in HTML konvertiert werden + If Extension = ".MSG" Then + _logger.Info("LoadRichtextFile: MSG erkannt – starte Konvertierung zu HTML") + oLoadPath = ConvertMsgToMht(FilePath) + + If String.IsNullOrEmpty(oLoadPath) Then + _logger.Warn("LoadRichtextFile: Konvertierung hat keinen Pfad zurückgegeben für [{0}]", FilePath) + ShowFileNotLoadedError(FilePath) + Return False + End If + + If Not IO.File.Exists(oLoadPath) Then + _logger.Warn("LoadRichtextFile: Konvertierte HTML-Datei existiert nicht [{0}]", oLoadPath) + ShowFileNotLoadedError(FilePath) + Return False + End If + + oFormat = XtraRichEdit.DocumentFormat.Html + _logger.Debug("LoadRichtextFile: MSG konvertiert zu [{0}], Format: Html", oLoadPath) + End If + + _logger.Debug("LoadRichtextFile: Rufe RichEditControl1.LoadDocument auf – Pfad: [{0}], Format: [{1}]", oLoadPath, oFormat) + RichEditControl1.LoadDocument(oLoadPath, oFormat) + RichEditControl1.Visible = True RichEditControl1.Dock = DockStyle.Fill - _ViewOverride = "Richtext" _ViewerMode = ViewerMode.Richtext - lblInfo.Visible = True - lblInfo.Text = "This docx-file contains a generic error and will be displayed in a reduced viewer. Please try to open the file in WORD" + _ViewOverride = "" + lblInfo.Visible = False + + _logger.Info("LoadRichtextFile: Erfolgreich geladen [{0}]", FilePath) Return True + Catch ex As Exception _logger.Error(ex) + ShowFileNotLoadedError(FilePath) Return False End Try End Function + + ''' + ''' Konvertiert eine MSG-Datei in eine temporäre MHT-Datei und gibt deren Pfad zurück. + ''' + Private Function ConvertMsgToMht(MsgFilePath As String) As String + Try + _logger.Info("Converting MSG to MHT: [{0}]", MsgFilePath) + + Dim oTempDir = Path.Combine(Path.GetTempPath(), "DocViewer_" & Guid.NewGuid().ToString("N")) + Directory.CreateDirectory(oTempDir) + + Dim oReader As New MsgReader.Reader() + oReader.ExtractToFolder(MsgFilePath, oTempDir) + + ' MsgReader erzeugt eine .htm/.html Datei im Zielordner + Dim oHtmlFile = Directory.GetFiles(oTempDir, "*.htm").FirstOrDefault() + If oHtmlFile Is Nothing Then + oHtmlFile = Directory.GetFiles(oTempDir, "*.html").FirstOrDefault() + End If + + If String.IsNullOrEmpty(oHtmlFile) Then + _logger.Warn("MSG conversion produced no HTML file in [{0}]", oTempDir) + Return String.Empty + End If + + ' Temporäres Verzeichnis für späteres Aufräumen merken + _TempFiles.Add(oTempDir) + _logger.Info("MSG converted to [{0}]", oHtmlFile) + Return oHtmlFile + + Catch ex As Exception + _logger.Error(ex) + Return String.Empty + End Try + End Function Private Function LoadSpreadsheetFile(FilePath As String, Extension As String) As Boolean Try _logger.Debug("Loading Spreadsheet: {0}", FilePath) @@ -742,14 +833,22 @@ Public Class DocumentViewer Try _logger.Debug("Loading GdPicture: {0}", FilePath) - ' Set force temporary mode for specific file types + ' DWG/DXF: Spezielle GdPicture-Einstellungen für bessere CAD-Darstellung + If Extension = ".DWG" OrElse Extension = ".DXF" Then + _logger.Info("LoadGdPictureFile: CAD-Datei erkannt – setze optimierte Darstellungsoptionen") + MyGDPViewer.BackColor = Color.White + MyGDPViewer.ZoomMode = ViewerZoomMode.ZoomModeFitToViewer + MyGDPViewer.DocumentAlignment = ViewerDocumentAlignment.DocumentAlignmentMiddleCenter + Else + MyGDPViewer.DocumentAlignment = ViewerDocumentAlignment.DocumentAlignmentTopCenter + End If + Select Case Extension - Case ".EML", ".DOC", ".DOCX", ".XLS", ".XLSX", ".ODT", ".RTF", ".TXT" + Case ".EML", ".MSG", ".DOC", ".DOCX", ".XLS", ".XLSX", ".ODT", ".RTF", ".TXT" MyGDPViewer.ForceTemporaryMode = True _logger.Info("ForceTemporaryMode enabled for extension [{0}]", Extension) End Select - MyGDPViewer.DocumentAlignment = ViewerDocumentAlignment.DocumentAlignmentTopCenter If Viewer_ForceTemporaryMode Then MyGDPViewer.ForceTemporaryMode = True End If @@ -759,8 +858,6 @@ Public Class DocumentViewer If oGDPState <> GdPictureStatus.OK Then _logger.Warn("GdPictureStatus: {0}", oGDPState) - - ' Fallback to Richtext for corrupted DOCX If Extension = ".DOCX" AndAlso oGDPState = GdPictureStatus.GenericError Then HideGdViewer() Return LoadRichtextFile(FilePath, Extension) @@ -927,6 +1024,7 @@ Public Class DocumentViewer Select Case Extension.ToUpper Case ".EML" : oFormat = XtraRichEdit.DocumentFormat.Mht + Case ".MSG" : oFormat = XtraRichEdit.DocumentFormat.Mht Case ".DOC" : oFormat = XtraRichEdit.DocumentFormat.Doc Case ".DOCX" : oFormat = XtraRichEdit.DocumentFormat.OpenXml Case ".ODT" : oFormat = XtraRichEdit.DocumentFormat.OpenDocument diff --git a/Controls.DocumentViewer/DocumentViewer.vbproj b/Controls.DocumentViewer/DocumentViewer.vbproj index f73cd584..91c0ad40 100644 --- a/Controls.DocumentViewer/DocumentViewer.vbproj +++ b/Controls.DocumentViewer/DocumentViewer.vbproj @@ -56,99 +56,75 @@ False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - False - ..\DDModules\Base\bin\Debug\DigitalData.Modules.Base.dll @@ -241,16 +217,22 @@ packages\Google.Protobuf.3.33.2\lib\net45\Google.Protobuf.dll - - packages\Microsoft.Bcl.AsyncInterfaces.10.0.5\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + ..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.12\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll - - packages\Microsoft.Bcl.Cryptography.10.0.5\lib\net462\Microsoft.Bcl.Cryptography.dll + + ..\packages\Microsoft.Bcl.Cryptography.10.0.12\lib\net462\Microsoft.Bcl.Cryptography.dll packages\Microsoft.Bcl.HashCode.6.0.0\lib\net462\Microsoft.Bcl.HashCode.dll + + ..\packages\Microsoft.IO.RecyclableMemoryStream.3.0.1\lib\netstandard2.0\Microsoft.IO.RecyclableMemoryStream.dll + + + ..\packages\MsgReader.6.2.0\lib\net462\MsgReader.dll + packages\GdPicture.14.4.4\lib\net462\NativeSDK.Exceptions.dll @@ -263,8 +245,8 @@ packages\NLog.5.0.5\lib\net46\NLog.dll - - packages\OpenMcdf.3.1.4\lib\netstandard2.0\OpenMcdf.dll + + ..\packages\OpenMcdf.3.3.0\lib\netstandard2.0\OpenMcdf.dll @@ -288,15 +270,18 @@ - - packages\System.Formats.Asn1.10.0.5\lib\net462\System.Formats.Asn1.dll + + ..\packages\System.Drawing.Common.10.0.12\lib\net462\System.Drawing.Common.dll + + + ..\packages\System.Formats.Asn1.10.0.12\lib\net462\System.Formats.Asn1.dll packages\System.IO.Packaging.10.0.5\lib\net462\System.IO.Packaging.dll - - packages\System.IO.Pipelines.10.0.5\lib\net462\System.IO.Pipelines.dll + + ..\packages\System.IO.Pipelines.10.0.12\lib\net462\System.IO.Pipelines.dll @@ -311,15 +296,18 @@ - - packages\System.Security.Cryptography.Pkcs.10.0.5\lib\net462\System.Security.Cryptography.Pkcs.dll + + ..\packages\System.Security.Cryptography.Pkcs.10.0.12\lib\net462\System.Security.Cryptography.Pkcs.dll - - packages\System.Text.Encodings.Web.10.0.5\lib\net462\System.Text.Encodings.Web.dll + + ..\packages\System.Text.Encoding.CodePages.4.7.1\lib\net461\System.Text.Encoding.CodePages.dll - - packages\System.Text.Json.10.0.5\lib\net462\System.Text.Json.dll + + ..\packages\System.Text.Encodings.Web.10.0.12\lib\net462\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.10.0.12\lib\net462\System.Text.Json.dll packages\System.Threading.Channels.10.0.5\lib\net462\System.Threading.Channels.dll @@ -329,7 +317,7 @@ - packages\System.ValueTuple.4.6.1\lib\net462\System.ValueTuple.dll + ..\packages\System.ValueTuple.4.6.2\lib\net462\System.ValueTuple.dll @@ -337,6 +325,9 @@ + + ..\packages\UTF.Unknown.2.7.0\lib\netstandard2.0\UtfUnknown.dll + @@ -446,9 +437,10 @@ Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". + - + \ No newline at end of file diff --git a/Controls.DocumentViewer/My Project/AssemblyInfo.vb b/Controls.DocumentViewer/My Project/AssemblyInfo.vb index 4f6d62ec..7a96ecf2 100644 --- a/Controls.DocumentViewer/My Project/AssemblyInfo.vb +++ b/Controls.DocumentViewer/My Project/AssemblyInfo.vb @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - - + + diff --git a/Controls.DocumentViewer/packages.config b/Controls.DocumentViewer/packages.config index f0aa0d44..7549c60a 100644 --- a/Controls.DocumentViewer/packages.config +++ b/Controls.DocumentViewer/packages.config @@ -7,28 +7,35 @@ - - + + + + + + - + - + + - + - - - + + + + - + + \ No newline at end of file diff --git a/Controls.SnapPanel/My Project/AssemblyInfo.vb b/Controls.SnapPanel/My Project/AssemblyInfo.vb index 650bbd2b..0c775ee7 100644 --- a/Controls.SnapPanel/My Project/AssemblyInfo.vb +++ b/Controls.SnapPanel/My Project/AssemblyInfo.vb @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' indem Sie "*" wie unten gezeigt eingeben: ' - - + + diff --git a/Controls.SnapPanel/SnapPanel.vb b/Controls.SnapPanel/SnapPanel.vb index 5659b8d6..4db7b1ab 100644 --- a/Controls.SnapPanel/SnapPanel.vb +++ b/Controls.SnapPanel/SnapPanel.vb @@ -8,12 +8,22 @@ Public Class ClassSnapPanel Private _ShowGrid As Boolean = True Private _GridSize As Integer = 16 Private _NeedsScrollUpdate As Boolean = False - + Private _SnappingEnabled As Boolean = True ' ← Neu: Snapping deaktivierbar Public Sub New() Me.AutoScroll = True Me.DoubleBuffered = True ' Flackern reduzieren End Sub - + ''' + ''' Deaktiviert das Snapping temporär (z.B. beim Laden von Controls aus der DB) + ''' + Public Property SnappingEnabled As Boolean + Get + Return _SnappingEnabled + End Get + Set(value As Boolean) + _SnappingEnabled = value + End Set + End Property Public Property GridSize As Integer Get Return _GridSize @@ -40,15 +50,11 @@ Public Class ClassSnapPanel Protected Overrides Sub OnControlAdded(e As ControlEventArgs) MyBase.OnControlAdded(e) - AddHandler e.Control.LocationChanged, AddressOf AlignToGrid - AddHandler e.Control.MouseUp, AddressOf AlignToGrid ScheduleScrollUpdate() End Sub Protected Overrides Sub OnControlRemoved(e As ControlEventArgs) MyBase.OnControlRemoved(e) - RemoveHandler e.Control.LocationChanged, AddressOf AlignToGrid - RemoveHandler e.Control.MouseUp, AddressOf AlignToGrid ScheduleScrollUpdate() End Sub @@ -56,19 +62,38 @@ Public Class ClassSnapPanel MyBase.OnLayout(levent) ScheduleScrollUpdate() End Sub + ''' + ''' Snappt ein Control manuell auf das Grid - wird von frmFormDesigner nach einem echten Drag aufgerufen + ''' + Public Sub SnapControlToGrid(ctrl As Control) + If Not _SnappingEnabled OrElse Not _ShowGrid OrElse Me.DesignMode Then Return + Dim x As Integer = Math.Floor(ctrl.Left / _GridSize) * _GridSize + Dim y As Integer = Math.Floor(ctrl.Top / _GridSize) * _GridSize + ctrl.Location = New Point(x, y) + ScheduleScrollUpdate() + End Sub Protected Overrides Sub OnPaint(e As PaintEventArgs) MyBase.OnPaint(e) - If Not Me.DesignMode AndAlso _ShowGrid Then - Dim scrollOffset As Point = Me.AutoScrollPosition - Dim gridRect As New Rectangle(-scrollOffset.X, -scrollOffset.Y, Me.ClientSize.Width, Me.ClientSize.Height) + Dim scroll As Point = Me.AutoScrollPosition + ' Grid-Ursprung am virtuellen Canvas (0,0) ausrichten + ' AutoScrollPosition ist negativ → Modulo ergibt positiven Offset im sichtbaren Bereich + Dim offsetX As Integer = ((scroll.X Mod _GridSize) + _GridSize) Mod _GridSize + Dim offsetY As Integer = ((scroll.Y Mod _GridSize) + _GridSize) Mod _GridSize + ' Rechteck um je eine Rasterzelle vergrößern damit keine Lücken entstehen + Dim gridRect As New Rectangle( + offsetX - _GridSize, + offsetY - _GridSize, + Me.ClientSize.Width + _GridSize * 2, + Me.ClientSize.Height + _GridSize * 2) ControlPaint.DrawGrid(e.Graphics, gridRect, New Size(_GridSize, _GridSize), Me.BackColor) End If End Sub Private Sub AlignToGrid(sender As Object, e As EventArgs) - If Not _ShowGrid OrElse Me.DesignMode Then Return + ' ← Snapping nur wenn aktiv + If Not _SnappingEnabled OrElse Not _ShowGrid OrElse Me.DesignMode Then Return Dim ctrl As Control = CType(sender, Control) Dim x As Integer = Math.Floor(ctrl.Left / _GridSize) * _GridSize @@ -87,6 +112,11 @@ Public Class ClassSnapPanel End Sub)) End If End Sub + Protected Overrides Sub OnScroll(se As ScrollEventArgs) + MyBase.OnScroll(se) + ' Nach dem Scrollen vollständig neu zeichnen + Invalidate() + End Sub Private Sub UpdateScrollArea() Dim maxWidth As Integer = 0 diff --git a/GUIs.Common/DocumentResultList/frmDocumentResultList.vb b/GUIs.Common/DocumentResultList/frmDocumentResultList.vb index b476053b..c208dc93 100644 --- a/GUIs.Common/DocumentResultList/frmDocumentResultList.vb +++ b/GUIs.Common/DocumentResultList/frmDocumentResultList.vb @@ -380,26 +380,32 @@ Public Class frmDocumentResultList oDocument = Documentloader.Load(oObjectId, oFullPath) If oDocument Is Nothing Then + _Logger.Warn("GridView_FocusedRowChanged: Documentloader returned Nothing for ObjectId [{0}], FullPath [{1}]", oObjectId, oFullPath) Exit Sub End If UpdateRibbonActions(oDocument) - If IsNothing(oDocument) Then - DocumentViewer1.CloseDocument() - FormHelper.ShowErrorMessage(New IOException("File could not be loaded!"), "GridView_FocusedRowChanged") - Exit Sub - End If - ' Save reference to current document _CurrentDocument = oDocument ' Load Document in Document Viewer Dim oFileName = $"{oObjectId}.{oDocument.Extension}" + If Not IsNothing(oDocument.Contents) And CopyWM2Temp = False Then - _Logger.Debug("Loading document into viewer with MemoryStream handler (LoadFile_FromStream). ObjectId: [{0}], Filename: [{1}], FullPath: [{2}]", oObjectId, oFileName, oFullPath) + ' Stream-Pfad: Inhalt liegt bereits im Speicher + _Logger.Debug("Loading document via MemoryStream. ObjectId: [{0}], Filename: [{1}]", oObjectId, oFileName) DocumentViewer1.LoadFile_FromStream(oFileName, New MemoryStream(oDocument.Contents)) Else + ' Datei-Pfad: Pfad auflösen und vorab prüfen Dim oResolvedFilename As String = oFullPath - If CopyWM2Temp = True AndAlso Not String.IsNullOrWhiteSpace(oResolvedFilename) Then + + ' Pfad-Validierung: Leer oder nicht gesetzt? + If String.IsNullOrWhiteSpace(oResolvedFilename) Then + _Logger.Warn("GridView_FocusedRowChanged: FullPath ist leer für ObjectId [{0}] – Dokument wird nicht geladen.", oObjectId) + DocumentViewer1.CloseDocument() + Exit Sub + End If + + If CopyWM2Temp = True Then Dim options As New DocumentPathHandler.DocumentPathOptions With { .EnableMapping = True, .WMSuffix = WM_SUFFIX, @@ -411,19 +417,24 @@ Public Class frmDocumentResultList } Dim result = _documentPathHandler.ProcessDocumentPath(oResolvedFilename, options) If result.Success AndAlso Not String.IsNullOrWhiteSpace(result.FinalPath) AndAlso File.Exists(result.FinalPath) Then - _Logger.Info($"✓ Zusatzdokument via Temp aufgelöst: [{result.FinalPath}]") + _Logger.Info("✓ Dokument via Temp aufgelöst: [{0}]", result.FinalPath) oResolvedFilename = result.FinalPath Else - _Logger.Warn($"⚠️ Temp-Auflösung fehlgeschlagen für [{oResolvedFilename}]: {result.ErrorMessage} - Fallback auf Originalpfad") + _Logger.Warn("⚠️ Temp-Auflösung fehlgeschlagen für [{0}]: {1} – Fallback auf Originalpfad", oResolvedFilename, result.ErrorMessage) oResolvedFilename = oFullPath End If - Else - _Logger.Debug("Keine Temp-Kopie für Dokument erforderlich oder möglich. Originalpfad wird verwendet. ObjectId: [{0}], Filename: [{1}], FullPath: [{2}]", oObjectId, oFileName, oFullPath) End If - _Logger.Debug("Loading document into viewer from filepath (LoadFile_FromPath). ObjectId: [{0}], Filename: [{1}], FullPath: [{2}]", oObjectId, oFileName, oResolvedFilename) + + ' Pfad-Validierung: Existiert die Datei? + If Not File.Exists(oResolvedFilename) Then + _Logger.Warn("GridView_FocusedRowChanged: Datei existiert nicht [{0}] für ObjectId [{1}] – Dokument wird nicht geladen.", oResolvedFilename, oObjectId) + DocumentViewer1.CloseDocument() + Exit Sub + End If + + _Logger.Debug("Loading document from filepath. ObjectId: [{0}], Filename: [{1}], FullPath: [{2}]", oObjectId, oFileName, oResolvedFilename) DocumentViewer1.LoadFile_FromPath(oResolvedFilename) End If - 'End If If OperationMode = OperationMode.ZooFlow Or OperationMode = OperationMode.WithAppServer Then Await CtrlObjectPropertyDialog.LoadObject(oObjectId)