当前位置: 代码网 > it编程>编程语言>Asp.net > 基于C# WinForms开发的Windows系统监控工具

基于C# WinForms开发的Windows系统监控工具

2025年12月25日 Asp.net 我要评论
前言最近开发了一个轻量级的 windows 系统监控工具,可以实时监控 cpu、内存、gpu、温度、网络流量等硬件信息,并通过曲线图表直观展示。整个项目基于 .net 10.0 和 winforms

前言

最近开发了一个轻量级的 windows 系统监控工具,可以实时监控 cpu、内存、gpu、温度、网络流量等硬件信息,并通过曲线图表直观展示。整个项目基于 .net 10.0 和 winforms 框架,使用了 librehardwaremonitor 硬件监控库和 scottplot 图表库。

本文分享一下开发过程中的核心技术点和关键代码实现。

技术栈

  • .net 10.0 - 最新的 .net 平台
  • windows forms - 桌面 ui 框架
  • librehardwaremonitor 0.9.3 - 硬件传感器数据采集
  • scottplot 5.0 - 高性能实时图表绘制
  • performancecounter - windows 性能计数器

核心功能

  1. 实时监控 cpu 使用率、主频、温度
  2. 内存使用情况监控
  3. gpu 使用率和温度监控
  4. 网络上传/下载速度统计
  5. 磁盘 i/o 读写速度
  6. 电池电量和充电状态(笔记本)
  7. 历史数据曲线图表展示
  8. 任务栏悬浮小窗口(支持三种显示模式)
  9. 配置持久化存储

一、硬件监控实现

1.1 系统监控服务设计

系统监控服务是整个工具的核心,负责采集各类硬件数据。主要使用两种方式:

  • performancecounter:采集 cpu 使用率、内存使用率、网络流量等
  • librehardwaremonitor:采集温度、频率、gpu 等硬件传感器数据

核心数据结构:

public class monitordata
{
    // cpu信息
    public float cpuusage { get; set; }
    public float cpufrequency { get; set; }  // cpu当前主频 (mhz)
    public float cputemperature { get; set; }
    public int cpucorecount { get; set; }
    public int cputhreadcount { get; set; }
    public string cpuname { get; set; } = string.empty;

    // 内存信息
    public float memoryusage { get; set; }
    public long totalmemorymb { get; set; }
    public long usedmemorymb { get; set; }
    public long availablememorymb { get; set; }

    // gpu信息
    public float gputemperature { get; set; }
    public float gpuusage { get; set; }
    public string gpuname { get; set; } = string.empty;

    // 网络信息
    public float networkuploadspeed { get; set; }    // kb/s
    public float networkdownloadspeed { get; set; }  // kb/s

    // 磁盘信息
    public float diskusage { get; set; }
    public long diskreadspeed { get; set; }
    public long diskwritespeed { get; set; }

    // 电池信息
    public int batterylevel { get; set; }
    public bool ischarging { get; set; }
}

1.2 性能计数器初始化

public class systemmonitor : idisposable
{
    private readonly computer _computer;
    private readonly performancecounter _cpucounter;
    private readonly performancecounter _ramcounter;
    private performancecounter? _networksentcounter;
    private performancecounter? _networkreceivedcounter;

    public systemmonitor()
    {
        // 初始化性能计数器
        _cpucounter = new performancecounter("processor", "% processor time", "_total");
        _ramcounter = new performancecounter("memory", "% committed bytes in use");

        // 初始化网络计数器
        initializenetworkcounters();

        // 初始化 librehardwaremonitor
        _computer = new computer
        {
            iscpuenabled = true,
            isgpuenabled = true,
            ismemoryenabled = true,
            isbatteryenabled = true,
            isnetworkenabled = true,
            isstorageenabled = true
        };
        _computer.open();
    }
}

1.3 网络流量监控

网络流量监控需要注意的是 performancecounter 返回的是累计值,需要计算两次采样之间的差值来得到速度:

private void initializenetworkcounters()
{
    try
    {
        var category = new performancecountercategory("network interface");
        var instancenames = category.getinstancenames();
        if (instancenames.length > 0)
        {
            string networkinterface = instancenames[0];
            _networksentcounter = new performancecounter(
                "network interface", "bytes sent/sec", networkinterface);
            _networkreceivedcounter = new performancecounter(
                "network interface", "bytes received/sec", networkinterface);
        }
    }
    catch { }
}

private void updatenetworkdata(monitordata data)
{
    try
    {
        if (_networksentcounter != null && _networkreceivedcounter != null)
        {
            long bytessent = (long)_networksentcounter.nextvalue();
            long bytesreceived = (long)_networkreceivedcounter.nextvalue();

            var now = datetime.now;
            var timediff = (now - _lastnetworkupdate).totalseconds;

            if (timediff > 0 && _lastbytessent > 0)
            {
                // 计算速度 (kb/s)
                data.networkuploadspeed =
                    (float)((bytessent - _lastbytessent) / timediff / 1024);
                data.networkdownloadspeed =
                    (float)((bytesreceived - _lastbytesreceived) / timediff / 1024);
            }

            _lastbytessent = bytessent;
            _lastbytesreceived = bytesreceived;
            _lastnetworkupdate = now;
        }
    }
    catch { }
}

1.4 温度和频率监控

使用 librehardwaremonitor 获取 cpu/gpu 温度和频率信息:

private void updatedata()
{
    var data = new monitordata();

    // 获取 cpu 使用率
    data.cpuusage = _cpucounter.nextvalue();

    // 更新硬件传感器信息
    foreach (var hardware in _computer.hardware)
    {
        hardware.update();

        // cpu 温度和频率
        if (hardware.hardwaretype == hardwaretype.cpu)
        {
            foreach (var sensor in hardware.sensors)
            {
                if (sensor.sensortype == sensortype.temperature && sensor.value.hasvalue)
                {
                    data.cputemperature = math.max(data.cputemperature, sensor.value.value);
                }

                // 获取 cpu 主频
                if (sensor.sensortype == sensortype.clock &&
                    sensor.name.contains("core") && sensor.value.hasvalue)
                {
                    data.cpufrequency = math.max(data.cpufrequency, sensor.value.value);
                }
            }
        }

        // gpu 温度和使用率
        if (hardware.hardwaretype == hardwaretype.gpunvidia ||
            hardware.hardwaretype == hardwaretype.gpuamd ||
            hardware.hardwaretype == hardwaretype.gpuintel)
        {
            foreach (var sensor in hardware.sensors)
            {
                if (sensor.sensortype == sensortype.temperature && sensor.value.hasvalue)
                {
                    data.gputemperature = math.max(data.gputemperature, sensor.value.value);
                }

                if (sensor.sensortype == sensortype.load &&
                    sensor.name.contains("core") && sensor.value.hasvalue)
                {
                    data.gpuusage = math.max(data.gpuusage, sensor.value.value);
                }
            }
        }
    }

    dataupdated?.invoke(this, data);
}

1.5 电池状态监控(win11 兼容)

电池状态监控在 win11 上需要特别处理,使用 systeminformation.powerstatus api:

try
{
    var status = system.windows.forms.systeminformation.powerstatus;

    // 检查是否有电池
    if (status.batterylifepercent >= 0 && status.batterylifepercent <= 1 &&
        status.batterychargestatus != system.windows.forms.batterychargestatus.nosystembattery)
    {
        // 电池百分比
        data.batterylevel = (int)(status.batterylifepercent * 100);

        // 充电状态
        data.ischarging = status.powerlinestatus == system.windows.forms.powerlinestatus.online ||
                         status.batterychargestatus.hasflag(system.windows.forms.batterychargestatus.charging);
    }
    else
    {
        // 台式机或没有电池的设备
        data.batterylevel = -1;  // 用 -1 表示无电池
        data.ischarging = false;
    }
}
catch
{
    data.batterylevel = -1;
    data.ischarging = false;
}

二、历史数据管理

为了绘制历史曲线,需要维护一个固定大小的数据队列:

public class datahistory
{
    private readonly int _maxdatapoints;
    private readonly queue<float> _cpuhistory;
    private readonly queue<float> _memoryhistory;
    private readonly queue<float> _cputemphistory;
    private readonly queue<float> _gputemphistory;
    // ... 其他数据队列

    public datahistory(int maxdatapoints = 60)
    {
        _maxdatapoints = maxdatapoints;
        _cpuhistory = new queue<float>(maxdatapoints);
        _memoryhistory = new queue<float>(maxdatapoints);
        // 初始化其他队列...
    }

    public void adddata(monitordata data)
    {
        addtoqueue(_cpuhistory, data.cpuusage);
        addtoqueue(_memoryhistory, data.memoryusage);
        addtoqueue(_cputemphistory, data.cputemperature);
        // 添加其他数据...
    }

    private void addtoqueue(queue<float> queue, float value)
    {
        if (queue.count >= _maxdatapoints)
        {
            queue.dequeue();
        }
        queue.enqueue(value);
    }

    public double[] getcpuhistory() => _cpuhistory.select(x => (double)x).toarray();
    public double[] getmemoryhistory() => _memoryhistory.select(x => (double)x).toarray();
    // 其他获取方法...
}

三、scottplot 图表绘制

3.1 图表初始化

scottplot 5.0 提供了强大的实时图表绘制能力:

private void setupplot(formsplot plot, string title, color linecolor, double ymin = 0, double ymax = 100)
{
    plot.plot.title(title);
    plot.plot.axes.title.label.forecolor = scottcolor.fromcolor(color.white);
    plot.plot.figurebackground.color = scottcolor.fromcolor(color.fromargb(40, 40, 40));
    plot.plot.databackground.color = scottcolor.fromcolor(color.fromargb(30, 30, 30));
    plot.plot.axes.color(scottcolor.fromcolor(color.gray));
    plot.plot.grid.majorlinecolor = scottcolor.fromcolor(color.fromargb(60, 60, 60));

    // 设置 y 轴范围
    plot.plot.axes.setlimitsy(ymin, ymax);
}

3.2 单线图表更新

private void updateplot(formsplot? plot, double[] data, color linecolor)
{
    if (plot == null || data.length == 0) return;

    plot.plot.clear();

    var signal = plot.plot.add.signal(data);
    signal.color = scottcolor.fromcolor(linecolor);
    signal.linewidth = 2;

    plot.plot.axes.setlimitsx(0, data.length);
    plot.plot.axes.setlimitsy(0, 100);

    plot.refresh();
}

3.3 双线图表(温度/网络)

温度图表同时显示 cpu 和 gpu 温度,网络图表同时显示上传和下载速度:

private void updateduallineplot(formsplot? plot, double[] data1, double[] data2,
    string legend1, string legend2, color color1, color color2)
{
    if (plot == null) return;

    plot.plot.clear();

    if (data1.length > 0)
    {
        var signal1 = plot.plot.add.signal(data1);
        signal1.color = scottcolor.fromcolor(color1);
        signal1.linewidth = 2;
        signal1.legendtext = legend1;
    }

    if (data2.length > 0)
    {
        var signal2 = plot.plot.add.signal(data2);
        signal2.color = scottcolor.fromcolor(color2);
        signal2.linewidth = 2;
        signal2.legendtext = legend2;
    }

    plot.plot.showlegend();
    plot.plot.axes.setlimitsx(0, math.max(data1.length, data2.length));

    // 自动调整 y 轴范围
    if (data1.length > 0 || data2.length > 0)
    {
        double maxvalue = 0;
        if (data1.length > 0) maxvalue = math.max(maxvalue, data1.max());
        if (data2.length > 0) maxvalue = math.max(maxvalue, data2.max());

        plot.plot.axes.setlimitsy(0, math.max(10, maxvalue * 1.2));
    }

    plot.refresh();
}

四、任务栏悬浮窗口

4.1 窗口基本设置

任务栏小窗口使用无边框窗体,置顶显示,不在任务栏显示:

private void initializecomponent()
{
    this.suspendlayout();

    this.autoscaledimensions = new sizef(7f, 17f);
    this.autoscalemode = autoscalemode.font;
    this.clientsize = new size(400, 135);
    this.formborderstyle = formborderstyle.none;
    this.name = "taskbarwindow";
    this.topmost = true;
    this.showintaskbar = false;
    this.startposition = formstartposition.manual;
    this.backcolor = color.fromargb(30, 30, 30);

    this.resumelayout(false);
}

protected override createparams createparams
{
    get
    {
        createparams cp = base.createparams;
        cp.exstyle |= 0x80; // ws_ex_toolwindow - 不显示在 alt+tab 中
        return cp;
    }
}

4.2 三种显示模式

小窗口支持三种显示模式,通过右键菜单切换:

模式 0:cpu + 内存图表

private void setupmode0_cpumemory()
{
    _cpuplot = new formsplot
    {
        location = new point(5, 30),
        size = new size(190, 100),
        backcolor = color.fromargb(40, 40, 40)
    };
    setupplot(_cpuplot, "cpu", color.fromargb(0, 174, 219));
    this.controls.add(_cpuplot);

    _memoryplot = new formsplot
    {
        location = new point(205, 30),
        size = new size(190, 100),
        backcolor = color.fromargb(40, 40, 40)
    };
    setupplot(_memoryplot, "内存", color.fromargb(142, 68, 173));
    this.controls.add(_memoryplot);
}

模式 1:温度 + 网络图表

private void setupmode1_tempnetwork()
{
    _tempplot = new formsplot
    {
        location = new point(5, 30),
        size = new size(190, 100),
        backcolor = color.fromargb(40, 40, 40)
    };
    setupplot(_tempplot, "温度", color.fromargb(231, 76, 60));
    this.controls.add(_tempplot);

    _networkplot = new formsplot
    {
        location = new point(205, 30),
        size = new size(190, 100),
        backcolor = color.fromargb(40, 40, 40)
    };
    setupplot(_networkplot, "网络", color.fromargb(52, 152, 219));
    this.controls.add(_networkplot);
}

模式 2:详细信息列表

private void setupmode2_detailedinfo()
{
    int ypos = 30;
    int labelheight = 13;

    var labels = new[]
    {
        ("cpu使用率", "0%"),
        ("cpu频率", "0 mhz"),
        ("cpu温度", "0°c"),
        ("gpu使用率", "0%"),
        ("gpu温度", "0°c"),
        ("内存使用率", "0%"),
        ("网络上传", "0 kb/s"),
        ("网络下载", "0 kb/s")
    };

    foreach (var (name, value) in labels)
    {
        var namelabel = new label
        {
            text = name + ":",
            location = new point(10, ypos),
            size = new size(100, labelheight),
            forecolor = color.fromargb(200, 200, 200),
            font = new font("microsoft yahei ui", 8f),
            textalign = contentalignment.middleleft
        };
        this.controls.add(namelabel);

        var valuelabel = new label
        {
            text = value,
            location = new point(120, ypos),
            size = new size(270, labelheight),
            forecolor = color.white,
            font = new font("microsoft yahei ui", 8f, fontstyle.bold),
            textalign = contentalignment.middleleft,
            tag = name // 用于在更新时识别标签
        };
        this.controls.add(valuelabel);

        ypos += labelheight;
    }
}

4.3 窗口拖拽功能

实现窗口拖拽移动:

private point _dragstartpoint;
private bool _isdragging = false;

private void taskbarwindow_mousedown(object? sender, mouseeventargs e)
{
    if (e.button == mousebuttons.left)
    {
        _isdragging = true;
        _dragstartpoint = e.location;
    }
}

private void taskbarwindow_mousemove(object? sender, mouseeventargs e)
{
    if (_isdragging)
    {
        point newlocation = this.location;
        newlocation.x += e.x - _dragstartpoint.x;
        newlocation.y += e.y - _dragstartpoint.y;
        this.location = newlocation;
    }

    if (e.button == mousebuttons.none)
    {
        _isdragging = false;
    }
}

4.4 右键菜单切换模式

private void setupcontextmenu()
{
    var contextmenu = new contextmenustrip();
    var modemenu = new toolstripmenuitem("切换显示模式");

    var mode0 = new toolstripmenuitem("cpu + 内存图表")
    {
        checked = _displaymode == 0
    };
    mode0.click += (s, e) => switchdisplaymode(0);

    var mode1 = new toolstripmenuitem("温度 + 网络图表")
    {
        checked = _displaymode == 1
    };
    mode1.click += (s, e) => switchdisplaymode(1);

    var mode2 = new toolstripmenuitem("详细信息列表")
    {
        checked = _displaymode == 2
    };
    mode2.click += (s, e) => switchdisplaymode(2);

    modemenu.dropdownitems.add(mode0);
    modemenu.dropdownitems.add(mode1);
    modemenu.dropdownitems.add(mode2);

    contextmenu.items.add(modemenu);
    contextmenu.items.add(new toolstripseparator());
    contextmenu.items.add("关闭", null, (s, e) => this.close());

    this.contextmenustrip = contextmenu;
}

private void switchdisplaymode(int mode)
{
    _displaymode = mode;
    _settings.taskbarwindowdisplaymode = mode;

    // 保存配置
    utils.settingsmanager.save(_settings);

    // 重新创建界面
    setupwindow();
}

五、配置持久化

5.1 配置数据结构

public class appsettings
{
    public bool autostart { get; set; } = false;
    public int refreshinterval { get; set; } = 1000;
    public bool showtaskbarwindow { get; set; } = true;
    public int taskbarwindowx { get; set; } = -1;
    public int taskbarwindowy { get; set; } = -1;
    public int historyduration { get; set; } = 120;
    public bool startminimized { get; set; } = false;
    public bool enabletemperaturemonitoring { get; set; } = true;
    public int mainwindowwidth { get; set; } = 900;
    public int mainwindowheight { get; set; } = 600;
    public int taskbarwindowdisplaymode { get; set; } = 0;
}

5.2 json 序列化存储

public static class settingsmanager
{
    private static readonly string settingspath = path.combine(
        environment.getfolderpath(environment.specialfolder.applicationdata),
        "windowsmonitor",
        "settings.json"
    );

    public static appsettings load()
    {
        try
        {
            if (file.exists(settingspath))
            {
                string json = file.readalltext(settingspath);
                return jsonserializer.deserialize<appsettings>(json) ?? new appsettings();
            }
        }
        catch { }

        return new appsettings();
    }

    public static void save(appsettings settings)
    {
        try
        {
            string directory = path.getdirectoryname(settingspath)!;
            if (!directory.exists(directory))
            {
                directory.createdirectory(directory);
            }

            var options = new jsonserializeroptions { writeindented = true };
            string json = jsonserializer.serialize(settings, options);
            file.writealltext(settingspath, json);
        }
        catch { }
    }

    public static string getsettingspath() => settingspath;
}

六、开机自启动

6.1 注册表方式实现

public static class autostartmanager
{
    private const string registrykey = @"software\microsoft\windows\currentversion\run";
    private const string appname = "windowsmonitor";

    public static void setautostart(bool enable)
    {
        try
        {
            using var key = registry.currentuser.opensubkey(registrykey, true);
            if (key == null) return;

            if (enable)
            {
                string exepath = application.executablepath;
                key.setvalue(appname, $"\"{exepath}\"");
            }
            else
            {
                key.deletevalue(appname, false);
            }
        }
        catch { }
    }

    public static bool isautostartenabled()
    {
        try
        {
            using var key = registry.currentuser.opensubkey(registrykey, false);
            return key?.getvalue(appname) != null;
        }
        catch
        {
            return false;
        }
    }
}

七、ui 布局优化

7.1 避免控件重叠

在 winforms 中,控件重叠是常见问题。需要精确计算每个控件的位置和大小:

// 使用固定列位置和行位置
int col1x = 15, col2x = 250, col3x = 485, col4x = 720;
int row1y = 10, row2y = 40, row3y = 70, row4y = 100;

// 创建信息标签时使用固定大小
private label createinfolabel(string text, point location, panel parent)
{
    var label = new label
    {
        text = text,
        location = location,
        size = new size(210, 25),  // 固定宽度避免显示不全
        autosize = false,
        font = new font("microsoft yahei ui", 9f, fontstyle.regular),
        autoellipsis = true  // 文字太长时显示省略号
    };
    parent.controls.add(label);
    return label;
}

7.2 响应式布局

使用 anchor 属性实现窗口大小改变时控件自适应:

var datapanel = new panel
{
    location = new point(10, 10),
    size = new size(this.clientsize.width - 40, 140),
    backcolor = color.white,
    borderstyle = borderstyle.fixedsingle,
    anchor = anchorstyles.top | anchorstyles.left | anchorstyles.right
};

八、系统托盘功能

8.1 托盘图标和菜单

private void setupnotifyicon()
{
    _notifyicon = new notifyicon
    {
        icon = systemicons.application,
        visible = true,
        text = "windows 系统监控"
    };

    var contextmenu = new contextmenustrip();
    contextmenu.items.add("显示主窗口", null, (s, e) => showmainwindow());
    contextmenu.items.add("任务栏窗口", null, (s, e) => toggletaskbarwindow());
    contextmenu.items.add(new toolstripseparator());
    contextmenu.items.add("退出", null, (s, e) => application.exit());

    _notifyicon.contextmenustrip = contextmenu;
    _notifyicon.doubleclick += (s, e) => showmainwindow();
}

8.2 窗口最小化到托盘

private void mainwindow_resize(object? sender, eventargs e)
{
    if (this.windowstate == formwindowstate.minimized)
    {
        this.hide();
    }
}

private void mainwindow_formclosing(object? sender, formclosingeventargs e)
{
    if (e.closereason == closereason.userclosing)
    {
        e.cancel = true;
        this.windowstate = formwindowstate.minimized;
        this.hide();
    }
}

总结

这个系统监控工具的开发涉及了以下几个关键技术点:

  1. 硬件数据采集:结合 performancecounter 和 librehardwaremonitor 实现全面的硬件监控
  2. 数据可视化:使用 scottplot 实现高性能的实时曲线绘制
  3. ui 设计:winforms 布局优化,避免控件重叠
  4. 数据管理:使用队列管理历史数据,控制内存占用
  5. 配置持久化:json 序列化存储用户配置
  6. 系统集成:托盘图标、开机自启、悬浮窗口等系统功能

整个项目代码结构清晰,模块化设计良好,后续可以方便地扩展更多监控项和功能。

运行截图

主窗口展示了多个实时曲线图表,任务栏小窗口可以自由拖拽并切换显示模式,整体界面简洁美观,性能开销低。

开发环境要求

  • visual studio 2022 或更高版本
  • .net 10.0 sdk
  • windows 10/11 操作系统

依赖包

<packagereference include="librehardwaremonitorlib" version="0.9.3" />
<packagereference include="scottplot.winforms" version="5.0.47" />

以上就是基于c# winforms开发的windows系统监控工具的详细内容,更多关于c# winforms windows系统监控的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com