using XKRS.UI.Model.Winform; using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using static DH.Commons.Enums.EnumHelper; using DH.Commons.Enums; using System.ComponentModel; using System.Reflection; namespace DHSoftware.Views { public partial class FrmLog : UserControl { #region Win32 API双缓冲处理 private const int LVM_SETEXTENDEDLISTVIEWSTYLE = 0x1036; private const int LVS_EX_DOUBLEBUFFER = 0x00010000; [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern int SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam); #endregion #region 常量配置 private const string SOURCE_PROCESS = "流程"; private const int LOG_NUM_LIMIT = 2000; private const int BATCH_SIZE = 50; private const int PROCESS_INTERVAL = 100; private const int FIRST_COL_WIDTH = 120; #endregion #region 控件状态 private readonly ConcurrentQueue _logQueue = new ConcurrentQueue(); private List _logBuffer = new List(); private List _showLevels = new List(); private List _showSources = new List(); private Task _logTask; private static readonly object _logLock = new object(); #endregion public FrmLog() { InitializeComponent(); InitializeCustomComponents(); } private void InitializeCustomComponents() { // 启用双缓冲 SendMessage(lvLog.Handle, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_DOUBLEBUFFER, LVS_EX_DOUBLEBUFFER); lvLog.ShowItemToolTips = true; lvLog.FullRowSelect = true; lvLog.View = View.Details; // 启用自定义绘制 //lvLog.OwnerDraw = true; //lvLog.DrawColumnHeader += LvLog_DrawColumnHeader; //lvLog.DrawSubItem += LvLog_DrawSubItem; //lvLog.DrawItem += LvLog_DrawItem; // 初始化列头 lvLog.Columns.Add("时间", FIRST_COL_WIDTH); lvLog.Columns.Add("来源", 150); lvLog.Columns.Add("内容", 400); InitializeLevelFilter(); StartProcessingTask(); } private void InitializeLevelFilter() { _showLevels.Clear(); tsmiLogLevels.DropDownItems.Clear(); foreach (LogLevel level in Enum.GetValues(typeof(LogLevel))) { var item = new ToolStripMenuItem(level.GetEnumDescription()) { CheckOnClick = true, Checked = true, Tag = level, BackColor = level.GetEnumSelectedColor(), ForeColor = level.GetEnumSelectedFontColor() }; item.CheckedChanged += LevelItem_CheckedChanged; tsmiLogLevels.DropDownItems.Add(item); _showLevels.Add(level); } } #region 自定义绘制方法 private void LvLog_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) { e.DrawDefault = true; } private void LvLog_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) { var item = e.Item; var log = item.Tag as LogMsg; // 设置背景色 e.Graphics.FillRectangle(new SolidBrush(log.LogLevel.GetEnumSelectedColor()), e.Bounds); // 设置文字颜色 TextRenderer.DrawText(e.Graphics, e.SubItem.Text, lvLog.Font, e.Bounds, log.LogLevel.GetEnumSelectedFontColor(), TextFormatFlags.Left); } //private ListViewItem CreateLogItem(LogMsg log) //{ // var item = new ListViewItem(log.LogTime.ToString("HH:mm:ss.fff")); // item.SubItems.Add(FormatSource(log)); // item.SubItems.Add(log.Msg); // item.Tag = log; // 重要:将日志对象绑定到Tag // return item; //} private void LvLog_DrawItem(object sender, DrawListViewItemEventArgs e) { e.DrawDefault = false; // 禁用默认绘制 } #endregion private void StartProcessingTask() { lock (_logLock) { if (_logTask == null || _logTask.IsCompleted) { _logTask = Task.Run(ProcessLogs); } } } private async Task ProcessLogs() { while (!IsDisposed) { try { await Task.Delay(PROCESS_INTERVAL); ProcessBatch(BATCH_SIZE); } catch (Exception ex) { DebugWrite($"日志处理异常: {ex.Message}"); } } } private void ProcessBatch(int batchSize) { if (InvokeRequired) { BeginInvoke(new Action(() => ProcessBatch(batchSize))); return; } lvLog.BeginUpdate(); try { var items = new List(); int processed = 0; while (processed < batchSize && _logQueue.TryDequeue(out var log)) { _logBuffer.Add(log); if (ShouldShow(log)) { items.Add(CreateLogItem(log)); processed++; } } if (items.Count > 0) { lvLog.Items.AddRange(items.ToArray()); MaintainBuffer(); AutoScrollIfNeeded(); } } finally { lvLog.EndUpdate(); UpdateLayout(); } } private bool ShouldShow(LogMsg log) { return _showLevels.Contains(log.LogLevel) && (_showSources.Count == 0 || (string.IsNullOrEmpty(log.MsgSource) ? _showSources.Contains(SOURCE_PROCESS) : _showSources.Contains(log.MsgSource))); } private ListViewItem CreateLogItem(LogMsg log) { var item = new ListViewItem(log.LogTime.ToString("HH:mm:ss.fff")); item.SubItems.Add(FormatSource(log)); item.SubItems.Add(log.Msg); item.ToolTipText = log.Msg; item.BackColor = log.LogLevel.GetEnumSelectedColor(); item.ForeColor = log.LogLevel.GetEnumSelectedFontColor(); return item; } private string FormatSource(LogMsg log) { return string.IsNullOrEmpty(log.MsgSource) ? SOURCE_PROCESS : $"{log.MsgSource}[{log.ThreadId}]"; } private void MaintainBuffer() { if (_logBuffer.Count > LOG_NUM_LIMIT * 2) { _logBuffer = _logBuffer .Skip(_logBuffer.Count - LOG_NUM_LIMIT) .ToList(); RefreshLogs(); } } public void AddLog(LogMsg log) { _logQueue.Enqueue(log); } private void RefreshLogs() { lvLog.BeginUpdate(); try { lvLog.Items.Clear(); var items = _logBuffer .Where(ShouldShow) .Select(CreateLogItem); lvLog.Items.AddRange(items.ToArray()); } finally { lvLog.EndUpdate(); UpdateLayout(); } } private void AutoScrollIfNeeded() { try { if (lvLog.Items.Count > 0 /*&& chkAutoScroll.Checked*/) { lvLog.EnsureVisible(lvLog.Items.Count - 1); } } catch (Exception ex) { } } private void UpdateLayout() { if (lvLog.Columns.Count < 3) return; // 动态调整列宽 lvLog.Columns[0].Width = FIRST_COL_WIDTH; lvLog.Columns[1].Width = lvLog.Width > 600 ? 150 : 0; lvLog.Columns[2].Width = lvLog.ClientSize.Width - lvLog.Columns[0].Width - lvLog.Columns[1].Width - SystemInformation.VerticalScrollBarWidth; } #region 事件处理 private void LevelItem_CheckedChanged(object sender, EventArgs e) { _showLevels = tsmiLogLevels.DropDownItems .Cast() .Where(i => i.Checked) .Select(i => (LogLevel)i.Tag) .ToList(); RefreshLogs(); } private void SourceItem_CheckedChanged(object sender, EventArgs e) { _showSources = tsmiLogSources.DropDownItems .Cast() .Where(i => i.Checked) .Select(i => i.Text) .ToList(); RefreshLogs(); } private void tsmiClearLog_Click(object sender, EventArgs e) { lvLog.Items.Clear(); _logBuffer.Clear(); } private void lvLog_SizeChanged(object sender, EventArgs e) { UpdateLayout(); } #endregion #region 辅助方法 private void DebugWrite(string message) { System.Diagnostics.Debug.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] {message}"); } #endregion } //public partial class FrmLog1 : UserControl //{ // // 添加双缓冲字段 // private const int LVM_SETEXTENDEDLISTVIEWSTYLE = 0x1036; // private const int LVS_EX_DOUBLEBUFFER = 0x00010000; // [System.Runtime.InteropServices.DllImport("user32.dll")] // private static extern int SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam); // public FrmLog() // { // InitializeComponent(); // // 启用双缓冲 // SendMessage(lvLog.Handle, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_DOUBLEBUFFER, LVS_EX_DOUBLEBUFFER); // lvLog.ShowItemToolTips = true; // this.Load += (s, e) => // { // _showLevels.Clear(); // tsmiLogLevels.DropDownItems.Clear(); // JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(EnumHelper.GetEnumListByType(typeof(LogLevel)))).ForEach(d => // { // LogLevel lvl = (LogLevel)((int)d.Value); // ToolStripMenuItem item = new ToolStripMenuItem(d.Desc.ToString()); // item.CheckOnClick = true; // item.Checked = true; // item.Tag = lvl; // item.CheckedChanged += LevelItem_CheckedChanged; // item.BackColor = lvl.GetEnumSelectedColor(); // item.ForeColor = lvl.GetEnumSelectedFontColor(); // tsmiLogLevels.DropDownItems.Add(item); // _showLevels.Add(lvl); // }); // }; // } // //public override void OnProcessUpdated() // //{ // // Invoke(new Action(() => // // { // // _showDevice.Clear(); // // tsmiLogSources.DropDownItems.Clear(); // // ToolStripMenuItem processItem = new ToolStripMenuItem(SOURCE_PROCESS); // // processItem.CheckOnClick = true; // // processItem.Checked = true; // // processItem.CheckedChanged += SourceItem_CheckedChanged; // // tsmiLogSources.DropDownItems.Add(processItem); // // _showDevice.Add(SOURCE_PROCESS); // // Process.DeviceCollection.ForEach(d => // // { // // ToolStripMenuItem item = new ToolStripMenuItem(d.Name); // // item.CheckOnClick = true; // // item.Checked = true; // // item.CheckedChanged += SourceItem_CheckedChanged; // // tsmiLogSources.DropDownItems.Add(item); // // _showDevice.Add(d.Name); // // }); // // })); // //} // private void LevelItem_CheckedChanged(object sender, EventArgs e) // { // _showLevels.Clear(); // foreach (ToolStripMenuItem item in tsmiLogLevels.DropDownItems) // { // if (item.Checked) // { // LogLevel lv = (LogLevel)Convert.ToInt32(item.Tag); // _showLevels.Add(lv); // } // } // RefreshLogs(); // } // private void SourceItem_CheckedChanged(object sender, EventArgs e) // { // _showDevice.Clear(); // foreach (ToolStripMenuItem item in tsmiLogSources.DropDownItems) // { // if (item.Checked) // { // _showDevice.Add(item.Text); // } // } // RefreshLogs(); // } // //public TaskFactory _taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.LongRunning); // readonly ConcurrentQueue _logQueue = new ConcurrentQueue(); // Task _logTask = null; // static readonly object _logLock = new object(); // List _logBuffer = new List(); // List _showLevels = new List(); // List _showDevice = new List(); // const string SOURCE_PROCESS = "流程"; // const int LOG_NUM_LIMIT = 20; // public void LogDisplay(LogMsg msg) // { // _logQueue.Enqueue(msg); // lock (_logLock) // { // if (_logTask == null) // { // _logTask = Task.Run(async () => // { // while (true) // { // try // { // Invoke(new Action(() => // { // bool isNeedScroll = false; // while (_logQueue.TryDequeue(out LogMsg log)) // { // _logBuffer.Add(log); // if (_showLevels.Contains(log.LogLevel) && (_showDevice.Count == 0 || (string.IsNullOrWhiteSpace(log.MsgSource) && _showDevice.Contains(SOURCE_PROCESS)) || _showDevice.Contains(log.MsgSource))) // { // isNeedScroll = true; // ListViewItem item = new ListViewItem($"{log.LogTime.ToString("HH:mm:ss.fff")}"); // item.SubItems.Add($"{log.MsgSource}[{log.ThreadId}]"); // item.SubItems.Add(log.Msg); // item.ToolTipText = log.Msg; // item.ForeColor = log.LogLevel.GetEnumSelectedFontColor(); // item.BackColor = log.LogLevel.GetEnumSelectedColor(); // lvLog.Items.Add(item); // } // } // if (_logBuffer.Count > LOG_NUM_LIMIT * 2) // { // _logBuffer = _logBuffer.Skip(_logBuffer.Count - LOG_NUM_LIMIT).ToList(); // RefreshLogs(); // isNeedScroll = true; // } // if (isNeedScroll && lvLog.Items.Count > 0) // { // RefreshLvLayout(); // } // })); // } // catch (Exception ex) // { // } // await Task.Delay(2000); // } // }); // } // } // } // private void RefreshLogs() // { // lvLog.Items.Clear(); // _logBuffer.ForEach(log => // { // if (_showLevels.Contains(log.LogLevel) && ((string.IsNullOrWhiteSpace(log.MsgSource) && _showDevice.Contains(SOURCE_PROCESS)) || _showDevice.Contains(log.MsgSource))) // { // ListViewItem item = new ListViewItem($"{log.LogTime.ToString("HH:mm:ss.fff")}"); // item.SubItems.Add($"{log.MsgSource}[{log.ThreadId}]"); // item.SubItems.Add(log.Msg); // item.ToolTipText = log.Msg; // item.ForeColor = log.LogLevel.GetEnumSelectedFontColor(); // item.BackColor = log.LogLevel.GetEnumSelectedColor(); // lvLog.Items.Add(item); // } // }); // RefreshLvLayout(); // } // private void lvLog_SizeChanged(object sender, EventArgs e) // { // RefreshLvLayout(); // } // int width_1stCol = 80; // public event Action OnLogMsgOutput; // private void RefreshLvLayout() // { // if (lvLog.Columns.Count <= 0) // return; // lvLog.Columns[0].Width = width_1stCol; // if (lvLog.Width <= lvLog.Height) // { // lvLog.Columns[1].Width = 0; // } // else // { // lvLog.Columns[1].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent); // } // lvLog.Columns[2].Width = lvLog.Width - width_1stCol - lvLog.Columns[1].Width - 10; // if (lvLog.Items.Count > 0) // lvLog.EnsureVisible(lvLog.Items.Count - 1); // } // private void tsmiClearLog_Click(object sender, EventArgs e) // { // lvLog.Items.Clear(); // } // private void tsmiClearLog2_Click(object sender, EventArgs e) // { // lvLog.Items.Clear(); // } //} }