实现形式好画刷新率 [英] Achieving good drawing refresh rate in Forms

查看:197
本文介绍了实现形式好画刷新率的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在这是从另一个线程异步接收输入的形式来绘制。输入处理消耗大量的CPU时间。因此,我想设置为每秒约10次的东西刷新率,但我在努力取得这样的。我想知道你们的想法我可以做,以加快我的代码,如果有什么?我试图将其降低到这里的代码最简单的数量。一些代码最初是从网络上找到不同的应用程序复制的,所以我道歉,如果我离开多余的东西(随时告诉我的)。我可以尝试提供从申请抽象方法的详细代码



 使用系统; 
使用System.Collections.Generic;
使用System.Linq的;
使用System.Text;使用System.Threading.Tasks
;
使用TagReader;
使用交互器;使用MathNet.Numerics.Distributions
;使用System.Runtime.CompilerServices
;
使用的System.Threading;使用System.Media
;
使用System.Drawing.Drawing2D;使用System.Windows.Forms的
;

命名空间RFIDInteraction
{
抽象类RFIDApplication:表
{


的DateTime lastTime = DateTime.Now;
INT refreshEllapse =(int)的十分之一千; //只有10毫秒已通过

刷新///<总结>
///必需的设计变量。
///< /总结>
私人System.ComponentModel.IContainer成分= NULL;


保护无效maybeRefresh()
{
如果((DateTime.Now - lastTime).Milliseconds> refreshEllapse)
{
this.Refresh();
lastTime = DateTime.Now;
}



}



[MethodImpl(MethodImplOptions.Synchronized)
私人无效InputWrapper(对象发件人,System.Collections.Specialized.NotifyCollectionChangedEventArgs E)
{

updateLoopStep(输入); //在此方法(未示出)代码执行根据所输入的一些更新 - 这可能是昂贵的


maybeRefresh();
}



#地区boringstuff
私人无效的InitializeComponent()
{
this.components =新System.ComponentModel 。容器();
this.SuspendLayout();

this.AutoScaleDimensions =新System.Drawing.SizeF(6F,13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize =新System.Drawing.Size(1000,1000);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
this.Name =应用程序;
this.Text =应用程序;
this.Load + =新System.EventHandler(this.LoadContent);
的SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw,真正的);
this.ResumeLayout(假);

}

#endregion

受保护的应用程序()
{

的InitializeComponent();

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



新TagStatesThread(inputCallback); //线程在后台运行 - 可能是一个昂贵的过程

}


保护覆盖无效的Dispose(BOOL处置)
{
如果(设置&放大器;及(成分=空)!)
{
components.Dispose();
}
base.Dispose(处置);
}


保护无效LoadContent(对象发件人,EventArgs五)
{

this.SetStyle(
的ControlStyles。 AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.DoubleBuffer,
真);
this.UpdateStyles();

loadAllContent(); //用来加载图像资产,这样的(一次)

}


保护覆盖无效的OnPaint(PaintEventArgs的E)$ B $一些简单的方法(未显示) b {
base.OnPaint(E);

e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.Clear(背景色);

//此方法中(未显示)代码处理绘制所有图片 - 只是通过他们所有的迭代,并调用e.Graphics.DrawImage。可能要绘制大量图像(100)
displayNewState(e.Graphics)的;


}





}
}

另外,一个问题 - 当一个呼叫绘制而成,并没有什么必须做,这似乎画得更快 - 言下之意如果一个像素没有被改变,它消耗几乎可以忽略不计的处理时间(提示系统自动检查什么需要拉伸,是相当智能)。这是真的



编辑:一个示例应用程序的截图:




解决方案

好了的话,这是你会怎么做,在WPF:



步骤1:



创建一个小的数据模型将代表他们的正方形的井字游戏:

 公共枚举SquareValue 
{
空= 0,
X = 1,
O = 2
}

公共类广场:INotifyPropertyChanged的
{
私人SquareValue _value;
公共SquareValue价值
{
得到{_value; }

{
_value =值;
OnPropertyChanged();
}
}

公共事件PropertyChangedEventHandler的PropertyChanged;

受保护的虚拟无效OnPropertyChanged([CallerMemberName]字符串参数propertyName = NULL)
{
VAR处理器=的PropertyChanged;
如果(处理!= NULL)处理(这一点,新PropertyChangedEventArgs(propertyName的));
}
}

公共类游戏
{
公开名单<广场>广场{搞定;私人集; }

公共游戏()
{
this.Squares =
Enumerable.Range(0,9)
。选择(X =>新建广场{值= SquareValue.Empty})
.ToList();
}
}




注意,广场类需要实施 INotifyPropertyChanged的 以支持双向数据绑定




步骤2:



使用的ItemsControl ,定义每个井字板会如何看这样的:

 <的ItemsControl的ItemsSource ={结合广场}保证金=5了borderThickness =1BorderBrush = DarkBlue> 
< ItemsControl.ItemsPanel>
< ItemsPanelTemplate>
< UniformGrid行=3列=3/>
< / ItemsPanelTemplate>
< /ItemsControl.ItemsPanel>

< ItemsControl.ItemTemplate>
<&DataTemplate的GT;
< BORDER BorderBrush =深灰了borderThickness =1>
<路径X:名称=路径拉伸=填充
保证金=2/>
< /边框>

<! - 未完待续... - >




请注意,我们设置的 ItemsPanel 以一个3x3 UniformGrid ,那<一个HREF =http://drwpf.com/blog/2008/01/03/itemscontrol-d-is-for-datatemplate/相对=nofollow> 的ItemTemplate 包含一个空 <$ C 。$ C>路径




步骤3:



使用 DataTrigger S,我们定义广场将看怎么样取决于它是否是空缺 X 0

 <  - 上面的延续! XAML  - > 
< DataTemplate.Triggers>
< DataTrigger绑定={绑定值}VALUE =X>
<二传手的TargetName =路径属性=中风VALUE =红/>
<二传手的TargetName =路径属性=数据VALUE =M0,0 L10,10 M0,10 L10,0/>
< / DataTrigger>

< DataTrigger绑定={绑定值}VALUE =O>
<二传手的TargetName =路径属性=中风VALUE =蓝/>
<二传手的TargetName =路径属性=数据>
< Setter.Value>
< EllipseGeometry半径X =10半径=10中心=0.0,0.0/>
< /Setter.Value>
< /二传手>
< / DataTrigger>
< /DataTemplate.Triggers>
< / DataTemplate中>
< /ItemsControl.ItemTemplate>
< / ItemsControl的>



步骤4:



我们现在有每个井字板的XAML,现在让我们来创建一些XAML代表的许多的板,再使用另一个的ItemsControl

 <的ItemsControl的ItemsSource ={结合}> 
< ItemsControl.ItemsPanel>
< ItemsPanelTemplate>
< UniformGrid行=10栏目=10/>
< / ItemsPanelTemplate>
< /ItemsControl.ItemsPanel>

< ItemsControl.ItemTemplate>
<&DataTemplate的GT;
<! - 为一个井字板上面的XAML放在这里 - >
< / DataTemplate中>
< /ItemsControl.ItemTemplate>
< / ItemsControl的>




注意,这一次我们使用的是10×10 UniformGrid ,因为我们将展示100井字板




步骤5:



现在我们设置了窗口的的 的DataContext 来我们的游戏类的列表:

 公共部分类窗口1:窗口
{
公共窗口1()
{
的InitializeComponent();
this.Loaded + =(发件人,参数)=>
{
this.DataContext = Enumerable.Range(0,100)
。选择(X =>全新游戏())
.ToList();
};
}
}



步骤6:



用你想要的任何方法,修改的广场 S中的属性,使他们成为 X 0 。在因为这这种情况下,仅仅是一个演示中,我将使用定时并设置它随机。修改游戏类,像这样:

 公共类游戏
{
公开名单<广场>广场{搞定;私人集; }
私人定时器定时器;
私人随机随机=新的随机();

公共游戏()
{
this.Squares = Enumerable.Range(0,9)。选择(X =>新建广场{值= SquareValue.Empty}) .ToList();
this.timer =新的定时器(回调,空,TimeSpan.FromSeconds(3),TimeSpan.FromMilliseconds(10));
}

私人的回调(对象状态)
{
VAR平方=广场[random.Next(0,9)];
变种值= random.Next(0,3);
sq.Value =(SquareValue)值;
}
}



结果:






  • 渲染是即时的,绝对没有滞后

  • 用户界面是与分辨率无关,然后调整到窗口大小。您可以尝试运行该项目,并调整窗口的大小,看看它是如何顺利。

  • 的数据模型是完全从用户界面分离和UI不需要任何形式的黑客背后的代码。这只是的简单,简单的属性和数据绑定。

  • 您可以进一步自定义板,两个X和O的不过你想要的样子。 WPF使用它可以不失去质量被无限拉长矢量图形。

  • 在此示例中的总码是25行C#+ XAML的40行。远远小于它需要做的的WinForms类似的事情,但结果要好得多。

  • 在的 GitHub上

  • 忘记的WinForms。


I am trying to draw in a form which is receiving input asynchronously from another thread. The input process consumes a large amount of CPU time. I therefore am trying to set a refresh rate for something around 10 times per second, but am struggling to achieve even that. I am wondering what you guys think I can do to speed up my code, if anything? I've tried to reduce it to the simplest amount of code here. Some code was originally copied from a different application found on the web, so I apologize if I've left something extraneous (feel free to tell me). I can try to provide more code from abstracted methods on request.

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);


        }





    }
}

Also, a question - when a call to draw is made, and nothing has to be done, it seems to draw much faster - implying that if a pixel does not have to be changed, it consumes almost negligible processing time (suggesting the system automatically checks what needs to be drawn and is pretty intelligent). Is this true?

Edit: Screenshot of an example application:

解决方案

Alright so, this is how you'd do that in WPF:

Step 1:

Create a small Data Model that will represent your Tic Tac Toe Games with their squares:

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();
    }
}

Notice that the Square class needs to Implement INotifyPropertyChanged in order to support two-way DataBinding.

Step 2:

Using an ItemsControl, define how each Tic Tac Toe board is going to look like:

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

Notice that we set the ItemsPanel to a 3x3 UniformGrid, and that the ItemTemplate contains an empty Path.

Step 3:

Using DataTriggers, we define how the square will look like depending on whether it's Empty, X, or 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>

Step 4:

We now have the XAML for each Tic Tac Toe board, let's now create some XAML to represent many boards, again, using another 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>

Notice that this time we're using a 10 x 10 UniformGrid because we will show 100 Tic Tac Toe boards

Step 5:

Now we set our Window's DataContext to a list of our Game class:

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

Step 6:

Using whatever method you desire, modify the Value property of the Squares so that they become X, or O. In this case since this is just a demo I'll use a Timer and set it randomly. Modify the Game class like so:

public class Game
{
    public List<Square> Squares { get; private set; }
    private Timer timer;
    private Random random = new Random();

    public Game()
    {
        this.Squares = Enumerable.Range(0, 9).Select(x => new Square {Value = SquareValue.Empty}).ToList();
        this.timer = new Timer(Callback, null, TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(10));
    }

    private void Callback(object state)
    {
        var sq = Squares[random.Next(0, 9)];
        var value = random.Next(0, 3);
        sq.Value = (SquareValue) value;
    }
}

Result:

  • Rendering is immediate and there's absolutely no lag
  • UI is resolution independent and adjusts to the window size. You can try running this project and resizing the window to see how smooth it is.
  • The Data Model is completely decoupled from the UI and the UI does not need any sort of code behind hacks. It's just Simple, Simple Properties and DataBinding.
  • You can further customize the boards, Xs and Os to look however you want. WPF uses vector graphics which can be infinitely stretched without losing quality.
  • Total code in this sample is 25 lines of C# + some 40 lines of XAML. Much less than it would require to do something similar in winforms, and yet the result is much better.
  • Full source code on GitHub.
  • Forget winforms.

这篇关于实现形式好画刷新率的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆