C#上位机框架源码,winform界面,清晰可见的源码 标准机项目上位机控制软件程序 界面美观实用,数据采集功能

最近在折腾一个工业控制项目,偶然发现一套挺有意思的C#上位机框架。这玩意儿用WinForm搞得有模有样,界面看着比大多数工控软件顺眼多了。咱们今天就扒开源码看看门道,顺便聊聊实战中怎么玩转数据采集。

主界面布局一看就是老司机手笔,双缓冲处理得明明白白。看这段核心代码:

public class MainForm : Form
{
    private BufferedGraphicsContext context;
    private BufferedGraphics bufferedGraphics;

    public MainForm()
    {
        // 双缓冲配置
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
        context = BufferedGraphicsManager.Current;
        bufferedGraphics = context.Allocate(this.CreateGraphics(), this.DisplayRectangle);
    }
    
    protected override void OnPaint(PaintEventArgs e)
    {
        bufferedGraphics.Render(e.Graphics);
    }
}

这波操作直接解决了WinForm界面闪烁的老大难问题。特别是当咱们要实时刷新几十个仪表盘控件时,没这个配置界面能闪到你怀疑人生。

数据采集模块整得挺专业,用BackgroundWorker处理耗时操作:

private BackgroundWorker dataWorker;

private void InitDataWorker()
{
    dataWorker = new BackgroundWorker();
    dataWorker.WorkerReportsProgress = true;
    dataWorker.DoWork += (sender, e) => 
    {
        while (!dataWorker.CancellationPending)
        {
            var sensorData = SerialPortManager.ReadData();
            dataWorker.ReportProgress(0, sensorData);
            Thread.Sleep(100); // 100ms采样间隔
        }
    };
    dataWorker.ProgressChanged += (sender, e) =>
    {
        UpdateChart(e.UserState as SensorData);
    };
}

这写法既保证了界面响应速度,又能稳定采集数据。记得在窗体关闭时调用dataWorker.CancelAsync(),不然后台线程会变成幽灵进程。

通信模块设计得也挺讲究,抽象了个BaseCommunicator:

public abstract class BaseCommunicator
{
    public event Action<byte[]> DataReceived;
    
    public abstract void Connect();
    public abstract void Send(byte[] command);
    
    protected virtual void OnDataReceived(byte[] data)
    {
        DataReceived?.Invoke(data);
    }
}

// 具体实现举个栗子
public class ModbusCommunicator : BaseCommunicator
{
    private SerialPort port;
    
    public override void Connect()
    {
        port = new SerialPort("COM3", 9600);
        port.DataReceived += (s, e) => 
        {
            byte[] buffer = new byte[port.BytesToRead];
            port.Read(buffer, 0, buffer.Length);
            OnDataReceived(buffer);
        };
        port.Open();
    }
}

这种架构扩展性拉满,想加个TCP通信只要再继承个类就行,完全不用动现有代码。

界面美化方面作者明显下了功夫,用自定义控件搞了个仿工业HMI的按钮:

public class IndustrialButton : Button
{
    private Color edgeColor = Color.SteelBlue;
    
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        
        // 画立体边框
        using (Pen p = new Pen(edgeColor, 3))
        {
            Rectangle rect = new Rectangle(2, 2, Width-4, Height-4);
            e.Graphics.DrawRectangle(p, rect);
        }
        
        // 添加LED指示灯
        if (Enabled)
        {
            e.Graphics.FillEllipse(Brushes.Lime, Width-20, 5, 15, 15);
        }
    }
}

这控件在实际项目中贼实用,操作人员隔老远都能看清按钮状态。要是再配上SoundPlayer搞点按键音效,工业那味儿就更正了。

C#上位机框架源码,winform界面,清晰可见的源码 标准机项目上位机控制软件程序 界面美观实用,数据采集功能

项目里还藏着个牛逼的曲线绘制算法,处理10万级数据点不卡顿:

public void OptimizedDrawCurve(Graphics g, List<DataPoint> points)
{
    int step = (int)(points.Count / (this.Width / 2)); // 动态采样
    
    var visiblePoints = points
        .Where((p, index) => index % step == 0)
        .Where(p => p.X >= visibleRange.Start && p.X <= visibleRange.End)
        .ToList();

    using (var path = new GraphicsPath())
    {
        for (int i = 1; i < visiblePoints.Count; i++)
        {
            path.AddLine(
                XToPixel(visiblePoints[i-1].X), 
                YToPixel(visiblePoints[i-1].Y),
                XToPixel(visiblePoints[i].X),
                YToPixel(visiblePoints[i].Y));
        }
        g.DrawPath(Pens.Blue, path);
    }
}

这算法精髓在于动态采样+局部绘制,比无脑重绘性能提升几十倍。实际测试中,5秒采集周期下CPU占用能稳定在3%以下。

源码里还有个骚操作——用WindowsAPI实现窗体阴影:

[DllImport("dwmapi.dll")]
private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMargins);

public static void ApplyFormShadow(Form form)
{
    if (Environment.OSVersion.Version.Major >= 6) // Vista以上系统
    {
        form.BackColor = Color.Black;
        MARGINS margins = new MARGINS()
        {
            leftWidth = 1,
            rightWidth = 1,
            topHeight = 1,
            bottomHeight = 1
        };
        DwmExtendFrameIntoClientArea(form.Handle, ref margins);
    }
}

这比用PictureBox模拟阴影优雅多了,系统级的效果还不吃性能。不过记得在窗体加载时调用,不然可能渲染异常。

项目里埋了个彩蛋——按Ctrl+Alt+U能调出性能监控面板:

protected override void OnKeyDown(KeyEventArgs e)
{
    if (e.Control && e.Alt && e.KeyCode == Keys.U)
    {
        var monitor = new PerformanceMonitor();
        monitor.Show();
    }
}

// 监控面板核心代码
public class PerformanceMonitor : Form
{
    private PerformanceCounter cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
    private PerformanceCounter ramCounter = new PerformanceCounter("Memory", "Available MBytes");

    public PerformanceMonitor()
    {
        Timer timer = new Timer { Interval = 1000 };
        timer.Tick += (s, e) => 
        {
            lblCpu.Text = $"{cpuCounter.NextValue():0.0}%";
            lblMemory.Text = $"{ramCounter.NextValue()}MB free";
        };
        timer.Start();
    }
}

这玩意儿在调试时简直救命,能快速定位性能瓶颈。特别是当现场设备配置不高时,实时监控资源占用非常有必要。

整套代码最让我惊艳的是异常处理机制——用AOP搞了个全局异常捕获:

public static class ExceptionHandler
{
    public static void Wire()
    {
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
        Application.ThreadException += (s, e) => HandleException(e.Exception);
        AppDomain.CurrentDomain.UnhandledException += (s, e) => 
            HandleException(e.ExceptionObject as Exception);
    }

    private static void HandleException(Exception ex)
    {
        Logger.Error(ex);
        MessageBox.Show($"程序抽风啦:{ex.Message}\n详细日志已保存");
    }
}

启动时在Program.cs里调用一下,所有未处理异常自动记录日志+友好提示。现场维护时再也不怕工人师傅看着崩溃界面干瞪眼了。

要说改进建议,可以加个插件系统。不过现有架构已经足够应付大多数工业场景,从数据采集到设备控制都整得明明白白。源码里那些自定义控件直接扒下来用在自己的项目里也挺香,比用WPF省事多了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐