简体   繁体   English

使用计时器在C#中更新UI

[英]Updating UI in C# using Timer

I am working on making my application that reads data from the serial port and updates a gauge on the UI more efficient and I wanted to ask for some advice on my code that processes the UI changes. 我正在使我的应用程序能够从串行端口读取数据并更有效地更新UI上的仪表,我想在处理UI更改的代码上寻求一些建议。 I have a timer set to check for data being sent to the COM port and another timer that updates the UI with the variable received from the COM port. 我设置了一个计时器来检查是否将数据发送到COM端口,另一个计时器用从COM端口接收的变量更新UI。 Basically what is happening is I am rotating a gauge. 基本上发生的是我正在旋转量规。 Here is my code for handling the graphics... 这是我处理图形的代码...

void timer_Tick(object sender, EventArgs e) //Timer regulates how often the gauge is     updated on the UI
{
    if (pictureBox1.Image != null)
        pictureBox1.Image.Dispose(); // dispose old image (you might consider reusing it rather than making a new one each frame)

    Point test = new Point((int)_xCor, (int)_yCor);
    Image img = new Bitmap(400, 400); // The box tht contains the image <--- Play around with this more
    pictureBox1.Image = img; // Setting the img Image to the pictureBox class?


    Graphics g = Graphics.FromImage(pictureBox1.Image); // G represents a drawing surface
    Matrix mm1 = new Matrix();
    //
    mm1.RotateAt((float)(90 + (((12.5 * state) - 20.95) * 6)), new Point((int)_xrotate, (int)_yrotate), MatrixOrder.Append);
    GraphicsPath gp = new GraphicsPath();
    g.Transform = mm1; // transform the graphics object so the image is rotated
    g.DrawImage(imgpic, test); // if the image needs to be behind the path, draw it beforehand
    mm1.Dispose();// prevent possible memory leaks
    gp.Dispose();// prevent possible memory leaks
    g.Dispose(); // prevent possible memory leaks
    pictureBox1.Refresh();
}

I am wondering if there is a more efficient way that I can rotate the Image on screen. 我想知道是否有一种更有效的方法可以在屏幕上旋转图像。 i feel like there has to be but I can't figure it out. 我觉得一定有,但我不知道。

This is the second time I provide a WPF solution for a winforms problem. 这是我第二次为Winforms问题提供WPF解决方案。

Just copy and paste my code in a file -> new project -> WPF Application and see the results for yourself. 只需将我的代码复制并粘贴到文件->新项目-> WPF应用程序中,然后亲自查看结果。

Also take a look at how simple this code really is (I'm using random values, so you can remove that and adapt it to your needs). 还请看一下这段代码实际上有多简单(我使用的是随机值,因此您可以删除它并使其适应您的需求)。

The drawing I used (the <Path/> part in XAML) is not adequate for a Gauge. 我使用的图形(XAML中的<Path/>部分)不足以用于量规。 I just had that Path already drawn and I'm too lazy to create a new one. 我只是已经绘制了该路径,所以我懒得创建一个新路径。 You should create a new drawing (I recommend using Expression Blend). 您应该创建一个新图形(我建议使用Expression Blend)。 But you can see the Rotation being applied and how fast it works. 但是您可以看到正在应用旋转,以及旋转的速度。

using System;
using System.Threading;
using System.Windows;
using System.ComponentModel;

namespace WpfApplication4
{
    public partial class Window2
    {
        public Window2()
        {
            InitializeComponent();
            DataContext = new ViewModel();
        }
    }

    public class ViewModel: INotifyPropertyChanged
    {
        private double _value;
        public double Value
        {
            get { return _value; }
            set
            {
                _value = value;
                NotifyPropertyChange("Value");
            }
        }

        private int _speed = 100;
        public int Speed
        {
            get { return _speed; }
            set
            {
                _speed = value;
                NotifyPropertyChange("Speed");
                Timer.Change(0, value);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChange(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private System.Threading.Timer Timer;

        public ViewModel()
        {
            Rnd = new Random();
            Timer = new Timer(x => Timer_Tick(), null, 0, Speed);
        }

        private void Timer_Tick()
        {
            Application.Current.Dispatcher.BeginInvoke((Action) (NewValue));
        }

        private Random Rnd;
        private void NewValue()
        {
            Value = Value + (Rnd.Next(20) - 10);
        }
    }
}

XAML: XAML:

<Window x:Class="WpfApplication4.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window2" WindowState="Maximized">
    <DockPanel>
        <StackPanel DockPanel.Dock="Top">
            <TextBlock Text="Delay (MS):" Margin="2"/>
            <Slider Width="200" Minimum="100" SmallChange="1" LargeChange="10" Maximum="1500" Value="{Binding Speed}" Margin="2"/>
            <TextBlock Text="Current Value:" Margin="2"/>
            <TextBox Text="{Binding Value}" Margin="2"/>
        </StackPanel>

        <Path Data="M0.95991516,0.5 L73.257382,1.866724 90.763535,1.866724 90.763535,90.822725 66.430534,90.822725 66.430534,26.075016 0.5,24.828653 z" Fill="#FF506077" RenderTransformOrigin="0.861209625003783,0.507482926584064" Stretch="Fill" Stroke="Black">
            <Path.LayoutTransform>
                <TransformGroup>
                    <ScaleTransform ScaleY="1" ScaleX="-1"/>
                    <SkewTransform AngleY="0" AngleX="0"/>
                    <RotateTransform Angle="{Binding Value}" x:Name="Rotation"/>
                    <TranslateTransform/>
                </TransformGroup>
            </Path.LayoutTransform>
        </Path>
    </DockPanel>
</Window>

It's difficult to answer your question because your asking for "more efficient" rotation of the image is pretty vague. 很难回答您的问题,因为您要求图像的“更有效”旋转非常模糊。 I'm not sure if by more efficient you mean: 我不确定是否更有效地表示:

  • better performance; 更好的性能;
  • less memory usage; 更少的内存使用;
  • or simply less, or more elegant, code 或只是更少或更优雅的代码

In any case, unless you are talking about making the code more "elegant" than the only thing that I can come up with is that you could, and probably should, re-use the same image/bitmap. 无论如何,除非您在谈论使代码比我能想到的唯一“优雅”的东西,否则您可以并且可能应该重复使用相同的图像/位图。 Instead of creating a new one each time you could just clear the one you are using and re-draw your image. 不必每次都创建一个新图像,只需清除您正在使用的图像并重新绘制图像即可。

You might also want to check the refresh rate of your timer that is used to update the UI. 您可能还需要检查用于更新UI的计时器的刷新率。 A frame rate of about 24 - 30 fps should be enough. 大约24-30 fps的帧速率就足够了。 Anything more is overkill in this scenario and it will mostly just waste CPU cycles. 在这种情况下,任何其他措施都是过大的,它只会浪费CPU周期。

You should also enable double buffering to prevent flickering. 您还应该启用双重缓冲以防止闪烁。

EDIT 编辑

Based on your comments, it sounds like the problem is not performance but a discrepancy between the interval of the COM port timer and the UI timer. 根据您的评论,听起来问题不在于性能,而是COM端口计时器和UI计时器之间的时间间隔不一致。 It sounds the timer that updates the UI doesn't run fast enough to detect a change.. What are your intervals? 听起来,更新UI的计时器运行得不够快,无法检测到更改。.您的间隔是多少?

Looks like you're doing this in Windows Forms? 看起来您正在Windows窗体中执行此操作? Use: 采用:

Graphics.RotateTransform Graphics.RotateTransform

If I may humbly suggest, though, if you're trying to do anything even remotely interesting graphically, it may be worth the investment to step up to WPF. 但是,如果我谦虚地建议,如果您试图以图形方式甚至做一些有趣的事情,那么升级到WPF可能是值得的投资。 Windows Forms relies on the old GDI apis which are not hardware accelerated (unlike WPF which is built on DirectX), making it a poor platform for any kind of serious graphics. Windows Forms依赖于未通过硬件加速的旧版GDI api(与DirectX上构建的WPF不同),这使得它对于任何类型的严重图形而言都是一个糟糕的平台。 No matter how 'efficient' you get with winforms, you'll never be able to compete with anything that's backed by hardware acceleration. 无论您使用winforms有多“高效”,您都将无法与硬件加速支持的任何产品竞争。

Rotating a bitmap using GDI+ is going to be slow, period. 使用GDI +旋转位图的过程将会很慢。 The biggest performance increase you could probably make is to stop using a bitmap for this purpose and just custom draw your gauge with GDI+ vector graphics. 您可能会获得的最大性能提升就是停止为此使用位图,而仅使用GDI +矢量图形自定义绘制量规。 You could still use a bitmap for the background and use vector graphics for drawing the gauge's needle, if applicable. 您仍然可以使用位图作为背景,并使用矢量图形绘制量规的针头(如果适用)。 That would be orders of magnitude faster than rotating a bitmap. 那将比旋转位图快几个数量级。

The next thing I'd look it is whether using a picture box in conjunction with a dynamic bitmap (ie constantly changing) is really the right way to go; 我接下来要看的是将图片框与动态位图结合使用(即不断变化)是否真的是正确的方法。 the picture box might be doing extra processing on your bitmap every time it's updated, which is really just wasted cycles. 图片框可能会在每次更新时对位图进行额外的处理,这实际上是在浪费时间。 Why not just draw the bitmap onto the screen yourself? 为什么不自己将位图绘制到屏幕上呢? Also, make sure that your bitmap is created with the correct pixel format for optimal drawing performance (PArgb32bpp). 另外,请确保使用正确的像素格式创建位图,以获得最佳绘图性能(PArgb32bpp)。

Finally, unless your input data is a constant stream of changing values, I would consider ditching the timer entirely and just using BeginInvoke to signal your UI thread when it's time to redraw the screen. 最后,除非您的输入数据是不断变化的值流,否则我将考虑完全放弃计时器,仅在需要重绘屏幕时使用BeginInvoke来向UI线程发出信号。 Your current solution might be suffering from unnecessary latency between timer ticks, and it might also be redrawing the gauge more often than is necessary. 您当前的解决方案可能受到计时器滴答之间不必要的延迟的困扰,并且可能比所需的重新绘制频率表的频率更高。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM