You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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内部状态,是最可靠的判断方式:

  1. 安装NuGet包 Microsoft.Office.Interop.Outlook
  2. 在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;
        }
    }
    
  3. 在Razor页面的UpdateStatus方法中,当Outlook活跃时调用该方法,扩展ActivePeriod类添加ActivityType属性,记录对应行为。

注意:需确保用户已安装Outlook,且应用有足够权限访问Outlook进程,发布时需处理版本兼容问题。

方案2:Windows UI Automation(无需Interop)

通过UI自动化识别Outlook窗口控件结构,兼容性更强:

  1. 添加NuGet包 System.Windows.Automation
  2. 扩展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:窗口标题分析(最简单但精度有限)

通过窗口标题关键词判断行为,适合快速验证:

  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 23:49:53