فهرست منبع

Merge branch 'LCD组装' of http://49.235.130.76/XXF_1122/DaisyNPILine into LCD组装

wanghan 7 ماه پیش
والد
کامیت
836f233892
1فایلهای تغییر یافته به همراه241 افزوده شده و 36 حذف شده
  1. 241 36
      TeamAAS-VM/Views/Home/ShowVisionRender.xaml.cs

+ 241 - 36
TeamAAS-VM/Views/Home/ShowVisionRender.xaml.cs

@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Drawing;
 using System.IO;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
@@ -35,6 +36,10 @@ namespace TeamAAS_VP.Views.Home
         private readonly ISystemDatabaseService _systemDatabaseService;
         private readonly IProductService _productService;
 
+        // ======================= 【新增】保存任务控制:防闪退/防资源泄露 =======================
+        private CancellationTokenSource _saveImageCts = new CancellationTokenSource();
+        private readonly SemaphoreSlim _saveSemaphore = new SemaphoreSlim(2, 2);
+
         public ShowVisionRender()
         {
             InitializeComponent();
@@ -51,6 +56,19 @@ namespace TeamAAS_VP.Views.Home
             viewModel.UpdateLayout += ViewModel_UpdateLayout;
             _eventAggregator.GetEvent<RenderUpdateNotification>().Subscribe(UpdateRenderModuleSource);
             _eventAggregator.GetEvent<ProductChangedNotification>().Subscribe(ProductChanged);
+
+            // 【可选】控件卸载时取消所有保存任务,避免页面关闭后仍在截图/保存导致闪退
+            this.Unloaded += ShowVisionRender_Unloaded;
+        }
+
+        private void ShowVisionRender_Unloaded(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                _saveImageCts.Cancel();
+                _saveImageCts.Dispose();
+            }
+            catch { }
         }
 
         /// <summary>
@@ -77,25 +95,9 @@ namespace TeamAAS_VP.Views.Home
                             if (render.Record != null)
                                 disp.Record = render.Record;
 
-                            if (render.IsSaveImage)
-                            {
-                                // 注意:CreateContentBitmap 可能比较耗时,但你这里已经在 UI 线程里调用了;
-                                // 如果卡顿明显,建议把 bitmap 生成放到后台线程(需谨慎处理线程访问)
-                                App.Current.Dispatcher.BeginInvoke(new Action(() =>
-                                {
-                                    var bmp = (Bitmap)disp.CreateContentBitmap(Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom);
-
-                                    SaveImage(
-                                        render.SaveImageModel,
-                                        render.SaveImagePathModel,
-                                        render.SavePath,
-                                        render.Image,
-                                        bmp,
-                                        render.Result,
-                                        render.IsCompress
-                                    );
-                                }));
-                            }
+                            // ======================= 【关键优化】保存图像走安全入口 =======================
+                            RequestSaveImage(render, disp);
+
                             break;
                         }
                     }
@@ -108,6 +110,15 @@ namespace TeamAAS_VP.Views.Home
         /// </summary>
         private async void ViewModel_UpdateLayout(System.Collections.ObjectModel.ObservableCollection<Models.ShowRender> obj)
         {
+            // ======================= 【关键优化】布局刷新前先取消旧保存任务 =======================
+            try
+            {
+                _saveImageCts.Cancel();
+                _saveImageCts.Dispose();
+            }
+            catch { }
+            _saveImageCts = new CancellationTokenSource();
+
             System.Windows.Media.FontFamily font = Application.Current.Resources["DefaultFont"] as System.Windows.Media.FontFamily;
 
             // 1) 清理旧控件
@@ -132,15 +143,7 @@ namespace TeamAAS_VP.Views.Home
             // 3) 限制最大 16(防止越界)
             int count = Math.Min(obj.Count, 16);
 
-            // 4) 计算行列(1~16自动铺满:尽量接近正方形;最大 4*4)
-            //    1  -> 1*1
-            //    2  -> 1*2
-            //    3  -> 2*2
-            //    4  -> 2*2
-            //    5~6 -> 2*3
-            //    7~9 -> 3*3(9满)
-            //    10~12 -> 3*4(12满)
-            //    13~16 -> 4*4(16满)
+            // 4) 计算行列(最大 4*4)
             GetGridSize(count, out int rows, out int cols);
 
             // 5) 创建主Grid的行列
@@ -158,13 +161,39 @@ namespace TeamAAS_VP.Views.Home
                 var sr = obj[i];
 
                 // (1) 创建显示控件(WindowsFormsHost + CogRecordDisplay)
-                var host = new WindowsFormsHost()
+                var disp = new CogRecordDisplay()
                 {
                     Tag = sr.ProceductName,
-                    Child = new CogRecordDisplay()
+                };
+
+                disp.HandleCreated += (s, e) =>
+                {
+                    try
                     {
-                        Tag = sr.ProceductName,
+                        ConfigureCogDisplay(disp); // 句柄创建后再配置,最稳
+                        // 绑定 WinForms 双击事件
+                        //disp.MouseDoubleClick += (r, w) =>
+                        //{
+                        //    try
+                        //    {
+                        //        OnDisplayDoubleClick(disp);
+                        //    }
+                        //    catch (Exception ex)
+                        //    {
+                        //        LogHelper.WriteLogError("双击预览弹窗出错", ex);
+                        //    }
+                        //};
                     }
+                    catch (Exception ex)
+                    {
+                        LogHelper.WriteLogError("ConfigureCogDisplay 失败", ex);
+                    }
+                };
+
+                var host = new WindowsFormsHost()
+                {
+                    Tag = sr.ProceductName,
+                    Child = disp
                 };
                 vmRenders.Add(sr.Id, host);
 
@@ -209,7 +238,6 @@ namespace TeamAAS_VP.Views.Home
         /// </summary>
         private void GetGridSize(int count, out int rows, out int cols)
         {
-            // 默认值
             rows = 1;
             cols = 1;
 
@@ -220,7 +248,6 @@ namespace TeamAAS_VP.Views.Home
             if (count <= 9) { rows = 3; cols = 3; return; }
             if (count <= 12) { rows = 3; cols = 4; return; }
 
-            // 13~16
             rows = 4;
             cols = 4;
         }
@@ -233,11 +260,94 @@ namespace TeamAAS_VP.Views.Home
             disp.HorizontalScrollBar = false;
             disp.VerticalScrollBar = false;
             disp.AutoFit = true;
+            disp.AutoFitWithGraphics = true;
             disp.BackColor = System.Drawing.SystemColors.ActiveCaption;
         }
 
+        // ======================= 【新增】安全保存入口:解决闪退 =======================
+        private void RequestSaveImage(ShowRender render, CogRecordDisplay disp)
+        {
+            if (render == null) return;
+            if (disp == null) return;
+            if (!render.IsSaveImage) return;
+
+            // Original 模式不需要 CreateContentBitmap(你的 SaveImage 里 Original 只写 ICogImage)
+            bool needRecordedBitmap =
+                render.SaveImageModel == ProcedureSaveImageModel.Recorded ||
+                render.SaveImageModel == ProcedureSaveImageModel.OriginalAndRecorded;
+            _saveImageCts = new CancellationTokenSource();
+            var token = _saveImageCts.Token;
+
+            // 必须在控件的 UI 线程上执行 CreateContentBitmap
+            disp.BeginInvoke(new Action(() =>
+            {
+                Bitmap clonedBitmap = null;
+
+                try
+                {
+                    token.ThrowIfCancellationRequested();
+
+                    if (needRecordedBitmap)
+                    {
+                        // UI线程创建
+                        Bitmap uiBitmap = (Bitmap)disp.CreateContentBitmap(
+                            Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom);
+
+                        // 立刻 Clone,后台线程只用 Clone(防止控件刷新/释放导致崩溃)
+                        clonedBitmap = (Bitmap)uiBitmap.Clone();
+
+                        // UI线程立刻释放
+                        uiBitmap.Dispose();
+                    }
+
+                    // 后台保存(限并发,防止GDI/内存瞬时飙升)
+                    Task.Run(async () =>
+                    {
+                        await _saveSemaphore.WaitAsync(token);
+                        try
+                        {
+                            token.ThrowIfCancellationRequested();
+
+                            SaveImage(
+                                render.SaveImageModel,
+                                render.SaveImagePathModel,
+                                render.SavePath,
+                                render.Image,
+                                clonedBitmap,
+                                render.Result,
+                                render.IsCompress
+                            );
+                        }
+                        catch (OperationCanceledException)
+                        {
+                            // 取消属于正常情况,不记录
+                        }
+                        catch (Exception ex)
+                        {
+                            LogHelper.WriteLogError("后台保存图像任务出错!", ex);
+                        }
+                        finally
+                        {
+                            // 确保释放 Clone 的 Bitmap,避免 GDI 泄漏导致闪退
+                            try { clonedBitmap?.Dispose(); } catch { }
+                            _saveSemaphore.Release();
+                        }
+                    }, token);
+                }
+                catch (OperationCanceledException)
+                {
+                    try { clonedBitmap?.Dispose(); } catch { }
+                }
+                catch (Exception ex)
+                {
+                    try { clonedBitmap?.Dispose(); } catch { }
+                    LogHelper.WriteLogError("RequestSaveImage(UI截图阶段) 出错!", ex);
+                }
+            }));
+        }
+
         /// <summary>
-        /// 保存图像(保持你原有逻辑不变)
+        /// 保存图像(保持你原有逻辑不变,补充 Dispose 兜底,防止压缩分支不释放导致闪退
         /// </summary>
         private void SaveImage(ProcedureSaveImageModel saveImageModel,
                                ProcedureSaveImagePathModel saveImagePathModel,
@@ -358,7 +468,6 @@ namespace TeamAAS_VP.Views.Home
                         else
                         {
                             reimage.Save(Recordedpath);
-                            reimage.Dispose();
                         }
                     }
                     else if (saveImageModel == ProcedureSaveImageModel.OriginalAndRecorded)
@@ -438,7 +547,6 @@ namespace TeamAAS_VP.Views.Home
                         else
                         {
                             reimage.Save(Recordedpath);
-                            reimage.Dispose();
                         }
                     }
                 }
@@ -446,6 +554,11 @@ namespace TeamAAS_VP.Views.Home
                 {
                     LogHelper.WriteLogError("保存流程运行结果的图片时出错!", ex);
                 }
+                finally
+                {
+                    // ======================= 【关键修复】无论是否压缩都释放Bitmap =======================
+                    try { reimage?.Dispose(); } catch { }
+                }
             });
         }
 
@@ -478,5 +591,97 @@ namespace TeamAAS_VP.Views.Home
                 }
             }));
         }
+        private void OnDisplayDoubleClick(CogRecordDisplay disp)
+        {
+            if (disp == null) return;
+
+            // 必须在 WinForms/ActiveX 线程截图
+            disp.BeginInvoke(new Action(() =>
+            {
+                Bitmap bmp = null;
+
+                try
+                {
+                    if (disp.IsDisposed || !disp.IsHandleCreated)
+                        return;
+
+                    // 优先使用显示内容截图(带图形/record叠加)
+                    try
+                    {
+                        var uiBmp = (Bitmap)disp.CreateContentBitmap(
+                            Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom);
+
+                        bmp = (Bitmap)uiBmp.Clone();
+                        uiBmp.Dispose();
+                    }
+                    catch
+                    {
+                        // 如果 ActiveX 状态不允许截图,退化:尝试从 Image 转 Bitmap
+                        bmp = TryConvertCogImageToBitmap(disp.Image);
+                    }
+
+                    if (bmp == null) return;
+
+                    // 回到 WPF UI 线程弹窗
+                    this.Dispatcher.BeginInvoke(new Action(() =>
+                    {
+                        //var win = new ImagePreviewWindow(bmp, $"预览:{disp.Tag ?? "Display"}");
+                        //win.Owner = Window.GetWindow(this); // 让弹窗归属当前窗口
+                        //win.Show();
+                    }));
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.WriteLogError("OnDisplayDoubleClick 截图/弹窗出错", ex);
+                }
+                finally
+                {
+                    // 这里 bmp 不要 Dispose,因为传给窗口了
+                    // 窗口关闭时会释放
+                }
+            }));
+        }
+
+        /// <summary>
+        /// 将 VisionPro 的 ICogImage 尝试转换成 Bitmap(作为 CreateContentBitmap 失败时的兜底)
+        /// </summary>
+        private Bitmap TryConvertCogImageToBitmap(ICogImage cogImage)
+        {
+            try
+            {
+                if (cogImage == null) return null;
+
+                // VisionPro 常用转换:CogImage8Grey / CogImage24PlanarColor / CogImage24PackedColor 等
+                // 这里给通用兜底:用 CogImageFileBMP 落地到内存流,再读回 Bitmap
+                using (var ms = new MemoryStream())
+                {
+                    // 需要引用 Cognex.VisionPro.ImageFile
+                    using (var bmpFile = new CogImageFileBMP())
+                    {
+                        // CogImageFileBMP 只能写文件路径,不直接写流
+                        // 所以用临时文件方式最稳(如你不想落盘,可以另写转换器)
+                        var tmp = Path.Combine(Path.GetTempPath(), $"vp_preview_{Guid.NewGuid():N}.bmp");
+                        try
+                        {
+                            bmpFile.Open(tmp, CogImageFileModeConstants.Write);
+                            bmpFile.Append(cogImage);
+                            bmpFile.Close();
+
+                            var bitmap = (Bitmap)Bitmap.FromFile(tmp);
+                            return (Bitmap)bitmap.Clone();
+                        }
+                        finally
+                        {
+                            try { if (File.Exists(tmp)) File.Delete(tmp); } catch { }
+                        }
+                    }
+                }
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
     }
 }