Windows端.NET MAUI Blazor Hybrid应用Outlook邮件读写行为监控需求
问题:区分Outlook活跃状态下的邮件阅读/撰写行为
我正在开发一款面向Windows平台的.NET MAUI Blazor混合应用,目前已经实现了监控Outlook是否处于活跃状态及统计聚焦时长的功能,但无法区分用户在Outlook内是阅读邮件还是撰写/回复邮件,需要可行的实现方案。
现有实现代码
WindowsApplicationMonitor.cs(监控应用活跃状态)
using System; using System.Runtime.InteropServices; using MAUIDemo.Services; namespace MAUIDemo.PlatformServices.Windows { public class WindowsApplicationMonitor : IApplicationMonitor { [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); private DateTime? outlookStartTime; public bool IsApplicationActive(string applicationName) { System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(applicationName); if (processes.Length > 0) { // Get the handle of the active window IntPtr hWnd = GetForegroundWindow(); // Get the process ID associated with the active window uint processId; GetWindowThreadProcessId(hWnd, out processId); // Check if the active window belongs to the application return processId == processes[0].Id; } else { return false; } } public TimeSpan GetApplicationActiveTime(string applicationName) { if (IsApplicationActive(applicationName)) { if (outlookStartTime == null) { outlookStartTime = DateTime.Now; } return DateTime.Now - outlookStartTime.Value; } else { TimeSpan elapsed = TimeSpan.Zero; if (outlookStartTime != null) { elapsed = DateTime.Now - outlookStartTime.Value; outlookStartTime = null; } return elapsed; } } } }
Razor监控页面代码
@page "/monitor" @inject MAUIDemo.Services.IApplicationMonitor ApplicationMonitor @using System.Timers @using MAUIDemo.Services @inject MAUIDemo.Services.ActivePeriodService ActivePeriodService <h3>Outlook Status</h3> <button class="btn btn-primary" @onclick="ClearList">Clear List</button> <p>@statusMessage</p> <table class="table"> <thead> <tr> <th>Start Time</th> <th>End Time</th> <th>Duration (hh:mm:ss)</th> </tr> </thead> <tbody> @foreach (var period in activePeriods) { <tr> <td>@period.Start</td> <td>@period.End</td> <td>@String.Format("{0:D2}:{1:D2}:{2:D2}", period.Duration.Hours, period.Duration.Minutes, period.Duration.Seconds)</td> </tr> } </tbody> </table> @code { private string statusMessage = ""; private List<ActivePeriod> activePeriods => ActivePeriodService.ActivePeriods; private ActivePeriod currentPeriod; private Timer timer; protected override void OnInitialized() { timer = new Timer(1000); // Update every second timer.Elapsed += UpdateStatus; timer.Start(); } private bool lastStatus = false; // keeps track of the last known status of the Outlook application (whether it was active or not). private void UpdateStatus(Object source, ElapsedEventArgs e) { var isOutlookActive = ApplicationMonitor.IsApplicationActive("OUTLOOK"); if (isOutlookActive != lastStatus) // Only update the status message and UI when the status changes { statusMessage = isOutlookActive ? "Outlook is active." : "Outlook is not active."; if (isOutlookActive) { if (currentPeriod == null) { currentPeriod = new ActivePeriod { Start = DateTime.Now }; } } else { if (currentPeriod != null) { // Create a new instance when adding to the list activePeriods.Add(new ActivePeriod { Start = currentPeriod.Start, End = DateTime.Now }); currentPeriod = null; // This will ensure a new instance is created each time } } InvokeAsync(() => StateHasChanged()); // Update the UI } lastStatus = isOutlookActive; // Remember the last status for the next time } public void Dispose() { timer?.Dispose(); } public class ActivePeriod { public DateTime Start { get; set; } public DateTime End { get; set; } public TimeSpan Duration => End - Start; } void ClearList() { ActivePeriodService.ActivePeriods.Clear(); } }
可行实现方案
方案1:使用Outlook Interop API(最精准)
直接访问Outlook内部状态,是最可靠的判断方式:
- 安装NuGet包
Microsoft.Office.Interop.Outlook - 在
WindowsApplicationMonitor中扩展方法,获取当前行为类型:using Microsoft.Office.Interop.Outlook; using System.Runtime.InteropServices; public enum OutlookActivityType { Unknown, Reading, Composing } public OutlookActivityType GetCurrentOutlookActivity() { try { Application outlookApp = Marshal.GetActiveObject("Outlook.Application") as Application; if (outlookApp == null) return OutlookActivityType.Unknown; // 撰写/回复窗口属于Inspector类型 Inspector activeInspector = outlookApp.ActiveInspector(); if (activeInspector != null && activeInspector.CurrentItem is MailItem) { return OutlookActivityType.Composing; } // 阅读邮件属于Explorer类型(主窗口) Explorer activeExplorer = outlookApp.ActiveExplorer(); if (activeExplorer != null && activeExplorer.Selection.Count > 0 && activeExplorer.Selection[1] is MailItem) { return OutlookActivityType.Reading; } return OutlookActivityType.Unknown; } catch { return OutlookActivityType.Unknown; } } - 在Razor页面的
UpdateStatus方法中,当Outlook活跃时调用该方法,扩展ActivePeriod类添加ActivityType属性,记录对应行为。
注意:需确保用户已安装Outlook,且应用有足够权限访问Outlook进程,发布时需处理版本兼容问题。
方案2:Windows UI Automation(无需Interop)
通过UI自动化识别Outlook窗口控件结构,兼容性更强:
- 添加NuGet包
System.Windows.Automation - 扩展
WindowsApplicationMonitor方法:using System.Windows.Automation; public OutlookActivityType GetCurrentOutlookActivityViaUIA() { if (!IsApplicationActive("OUTLOOK")) return OutlookActivityType.Unknown; IntPtr outlookWnd = GetForegroundWindow(); AutomationElement outlookElement = AutomationElement.FromHandle(outlookWnd); // 查找撰写窗口特有的发送按钮 Condition sendBtnCondition = new PropertyCondition(AutomationElement.NameProperty, "Send"); AutomationElement sendBtn = outlookElement.FindFirst(TreeScope.Descendants, sendBtnCondition); // 查找邮件编辑框 Condition editBoxCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit); AutomationElement editBox = outlookElement.FindFirst(TreeScope.Descendants, editBoxCondition); if (sendBtn != null && editBox != null) { return OutlookActivityType.Composing; } // 查找阅读窗口特有的回复按钮 Condition replyBtnCondition = new PropertyCondition(AutomationElement.NameProperty, "Reply"); AutomationElement replyBtn = outlookElement.FindFirst(TreeScope.Descendants, replyBtnCondition); if (replyBtn != null) { return OutlookActivityType.Reading; } return OutlookActivityType.Unknown; }
这种方式无需依赖Outlook Interop,但需针对不同Outlook版本调整控件识别规则。
方案3:窗口标题分析(最简单但精度有限)
通过窗口标题关键词判断行为,适合快速验证:
- 在
WindowsApplicationMonitor中添加窗口标题获取方法:[DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count); public OutlookActivityType GetCurrentOutlookActivityViaTitle() { if (!IsApplicationActive("OUTLOOK")) return OutlookActivityType.Unknown; IntPtr hWnd = GetForegroundWindow(); StringBuilder sb = new StringBuilder(256); GetWindowText(hWnd, sb, sb.Capacity); string title = sb.ToString(); // 撰写窗口标题通常包含Reply/Forward/Message等关键词 if (title.Contains("Reply") || title.Contains("Forward") || title.Contains("Message (")) { return OutlookActivityType.Composing; } // 阅读窗口标题多为邮件主题或包含Inbox等文件夹名 else if (title.Contains("Inbox") || !title.Contains("-")) { return OutlookActivityType.Reading; } return OutlookActivityType.Unknown; }
注意:该方法对自定义标题、非英文版本Outlook可能误判,仅作为辅助方案。
内容的提问来源于stack exchange,提问作者Suman Mondal
相关产品推荐
相关产品推荐

