在Forms中实现良好的绘图刷新率

时间:2015-08-12 22:15:12

标签: c# forms user-interface

我正在尝试绘制一个从另一个线程异步接收输入的表单。输入过程会消耗大量的CPU时间。因此,我试图设置每秒大约10次的刷新率,但我正在努力实现这一目标。我想知道你们认为我能做些什么来加速我的代码,如果有的话?我试着将它减少到最简单的代码量。有些代码最初是从网络上发现的不同应用程序复制的,所以如果我遗漏了一些无关的东西,我会道歉(随时告诉我)。我可以根据请求尝试从抽象方法中提供更多代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TagReader;
using Interactor;
using MathNet.Numerics.Distributions;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Media;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace RFIDInteraction
{
    abstract class RFIDApplication : Form
    {


        DateTime lastTime = DateTime.Now;
        int refreshEllapse = (int)1000 / 10; //only refresh if 10 ms have passed

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;


        protected void maybeRefresh()
        {
            if ((DateTime.Now - lastTime).Milliseconds > refreshEllapse)
            {
                this.Refresh();
                lastTime = DateTime.Now;
            }



        }



        [MethodImpl(MethodImplOptions.Synchronized)]
        private void InputWrapper(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {

            updateLoopStep(inputs); //code in this method (not shown) does some update based on the input - this could be expensive


            maybeRefresh();
        }



        #region boringstuff
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.SuspendLayout();

            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1000, 1000);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
            this.Name = "Application";
            this.Text = "Application";
            this.Load += new System.EventHandler(this.LoadContent);
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
            this.ResumeLayout(false);

        }

        #endregion

        protected Application()
        {

            InitializeComponent();

            System.Collections.Specialized.NotifyCollectionChangedEventHandler inputCallback =
                new System.Collections.Specialized.NotifyCollectionChangedEventHandler(InputWrapper);



            new TagStatesThread(inputCallback); //Thread runs in background - could be an expensive process

        }


        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }


        protected void LoadContent(object sender, EventArgs e)
        {

            this.SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.UserPaint |
                ControlStyles.DoubleBuffer,
                true);
            this.UpdateStyles();

            loadAllContent(); //some simple method (not shown) which loads Image assets and such (once)

        }


        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            e.Graphics.Clear(BackColor);

            //code in this method (not shown) handles drawing all the Images - by simply iterating through them all and calling e.Graphics.DrawImage.  May have to draw a large number of images (up to 100)
           displayNewState(e.Graphics);


        }





    }
}

另外,一个问题 - 当进行绘制调用,并且不需要做任何事情时,它似乎绘制得更快 - 这意味着如果不必更改像素,则消耗几乎可以忽略不计的处理时间(建议系统会自动检查需要绘制的内容并且非常智能。这是真的吗?

编辑:示例应用程序的屏幕截图:

TicTacToeBoard

1 个答案:

答案 0 :(得分:2)

好吧,这就是你在WPF中的表现:

第1步:

创建一个小的数据模型,用他们的方块代表您的Tic Tac Toe游戏:

public enum SquareValue
{
    Empty = 0,
    X = 1,
    O = 2
}

public class Square: INotifyPropertyChanged
{
    private SquareValue _value;
    public SquareValue Value
    {
        get { return _value; }
        set
        {
            _value = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class Game
{
    public List<Square> Squares { get; private set; }

    public Game()
    {
        this.Squares = 
            Enumerable.Range(0, 9)
                      .Select(x => new Square { Value = SquareValue.Empty })
                      .ToList();
    }
}
  

请注意,Square类需要Implement INotifyPropertyChanged才能支持双向DataBinding。

第2步:

使用ItemsControl,定义每个Tic Tac Toe板的外观:

<ItemsControl ItemsSource="{Binding Squares}" Margin="5" BorderThickness="1" BorderBrush="DarkBlue">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Rows="3" Columns="3"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="DarkGray"  BorderThickness="1">
                <Path x:Name="Path" Stretch="Fill"
                      Margin="2"/>
            </Border>

            <!-- to be continued... -->
  

请注意,我们将ItemsPanel设置为3x3 UniformGridItemTemplate包含空Path

第3步:

使用DataTrigger,我们会定义广场的外观,具体取决于它是EmptyX还是O

            <!-- continuation of the above XAML -->
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Value}" Value="X">
                    <Setter TargetName="Path" Property="Stroke" Value="Red"/>
                    <Setter TargetName="Path" Property="Data" Value="M0,0 L10,10 M0,10 L10,0"/>
                </DataTrigger>

                <DataTrigger Binding="{Binding Value}" Value="O">
                    <Setter TargetName="Path" Property="Stroke" Value="Blue"/>
                    <Setter TargetName="Path" Property="Data">
                        <Setter.Value>
                            <EllipseGeometry RadiusX="10" RadiusY="10" Center="0.0,0.0"/>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

第4步:

我们现在每个Tic Tac Toe板都有XAML,让我们现在创建一些XAML代表许多板,再次使用另一个ItemsControl

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Rows="10" Columns="10"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <!-- the above XAML for a single Tic Tac Toe board goes here -->
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
  

请注意,这次我们使用10 x 10 UniformGrid,因为我们将展示100个Tic Tac Toe板

第5步:

现在我们将Window&#39; DataContext设置为我们的Game类列表:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        this.Loaded += (sender, args) =>
        {
            this.DataContext = Enumerable.Range(0,100)
                                         .Select(x => new Game())
                                         .ToList();
        };
    }
}

第6步:

使用您想要的任何方法,修改Value的{​​{1}}属性,使其变为SquareX。在这种情况下,因为这只是一个演示,我将使用O并随机设置它。像这样修改Timer类:

Game

结果:

enter image description here

  • 立即渲染,绝对没有延迟
  • UI与分辨率无关,并根据窗口大小进行调整。您可以尝试运行此项目并调整窗口大小以查看它的平滑程度。
  • 数据模型与UI完全分离,UI不需要任何类型的代码来支持黑客攻击。它只是简单,简单的属性和数据绑定。
  • 您可以根据需要进一步自定义电路板,X和Os。 WPF使用矢量图形,可以无限延伸而不会降低质量。
  • 此示例中的总代码是25行C#+ 40行XAML。比winforms中做类似的东西要少得多,但效果要好得多。
  • GitHub上的完整源代码。
  • 忘记winforms。