Updating UI in C# using Timer

后端 未结 4 1110
孤独总比滥情好
孤独总比滥情好 2020-12-02 02:56

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

相关标签:
4条回答
  • 2020-12-02 03:36

    Looks like you're doing this in Windows Forms? Use:

    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. 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. No matter how 'efficient' you get with winforms, you'll never be able to compete with anything that's backed by hardware acceleration.

    0 讨论(0)
  • 2020-12-02 03:41

    This is the second time I provide a WPF solution for a winforms problem.

    Just copy and paste my code in a file -> new project -> WPF Application and see the results for yourself.

    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. 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). 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:

    <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>
    
    0 讨论(0)
  • 2020-12-02 03:47

    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. A frame rate of about 24 - 30 fps should be enough. Anything more is overkill in this scenario and it will mostly just waste CPU cycles.

    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. It sounds the timer that updates the UI doesn't run fast enough to detect a change.. What are your intervals?

    0 讨论(0)
  • 2020-12-02 03:48

    Rotating a bitmap using GDI+ is going to be slow, period. 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. 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 (i.e. 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).

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

    0 讨论(0)
提交回复
热议问题