使用WinForm绘制实时2D图形

时间:2014-06-30 07:51:00

标签: c# winforms 2d

名为floorLayout的表具有存储的对象的原始坐标。这些细节显示在具有基本2D形状的2D图片框中。        上表的从属实时更新对象的新坐标,这些新位置也应该更新为2D形状(通过改变颜色,位置等)。

我当然是图形新手。以下是我想澄清的几个问题。

  1. 我是否需要一个带线程的后台工作者来处理2D图形的更新?
  2. 此方案还有其他方法吗?
  3. 在有用的评论后编辑:

    有一张桌子,里面有基本的座位计划细节。 座位号,座位类型(由日食或方形表示),原始座位位置

    当座位被占用或保留时,必须更改图片框颜色中的参考形状。

    座位可以在不同的部分。然而,有时某个座位可以与另一个座位耦合。当座位与另一个座位耦合时,其当前位置将成为其座位的位置(位置保持原始状态)。两个座位'颜色变化。

    分离时,辅助座椅位置会变回原来的位置并且颜色会发生变化。

    这意味着对于每个DML交易,座位lsyout都会产生影响。这就是我想要管理而不影响性能的。

    该应用程序有三个部分。设置(登录是设置的一部分),座位分配,图形布局。虽然它位于C#中,但该模型采用3层分层体系结构,以便将来进行Web扩展(如果需要)。此外,单独提供服务和数据访问可以提供很多自由,易于管理。

    鉴于这种情况,我的选择是什么?

    EDIT2: [2014年7月1日]

    在尝试基于Bitmap的绘图时,我遇到了与我的线程相关的问题。我在这里发帖,因为它与我对有能力的回答者的讨论有关。

        public void bgWorker_DoWork(object sender, DoWorkEventArgs d)
        {
            //insert seats (sql call via service/access interaces) to table
             AddSeats();
        }
    
        //this doesn't get fired----<<<-------
        public void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs p)
        {
            txtFlag.Text = p.ProgressPercentage.ToString();
    
        }
    
        //this works fine
        public void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs r)
        {
            if (r.Cancelled)
            {
                txtFlag.Text = "Failed";
            }
            else
            {
                //load datagridview using datatable returned by select query
                GetSeats();
                //draw the bitmap with a loop over the datagridview  
                DrawFuntion();
    
                //bgWorker.ReportProgress(prog, txtFlag.Text);
            }
        }
    

    一个主要问题:  1.这是否真的有意义我使用bgworker来插入数据库,当它完成时我正在调用loadgridview并绘制方法?  我实际上认为,最好在bgworker中调用draw方法,但我无法弄清楚如何(逻辑上和功能上流动)  3.当我尝试在DoWork()中运行DrawFunction()时,它只是抛出了最大的交叉线程错误:不允许访问使用不同线程创建的表单中的UI控件。

    我怎样才能理解这一点?

1 个答案:

答案 0 :(得分:2)

考虑到我们聊天的结果,这是我想要的布局:

考虑到当前项目的时间限制,请将其保留在Winforms中,但请记住WPF以便将来进行修订。

绘制一千个席位不是一个大问题,但为了保持GUI响应,它应该由这样的后台工作者完成:

创建两个Bitmap属性:

public Bitmap bmp_Display { get; set; }
public Bitmap bmp_Buffer { get; set; }

在显示面板的Paint事件中,您只需将bmp_Display转储到面板上,如下所示:

e.Graphics.DrawImage(bmp_Display,  Point.Empty);

这是一个单一命令,会很快发生。

要创建更新的平面布局,背景线程将座位绘制到bmp_Buffer上,可能是这样的:

 foreach (Seat s in SeatList)
 {
    e.Graphics.FillRectangle(seatBrushes[s.State], 
                             new Rectangle(s.Location, s.Size);
    e.Graphics.DrawString(s.Name, seatFont, Brushes.Black, s.Location);                        
 }

完成后,它将它转储到bmp_Display上,如下所示:

bmp_Display = (Bitmap)bmp_Buffer.Clone();

要做到这一点,你应该确保线程安全,也许是锁定。

最后,显示面板为invalidated

详细信息取决于您将使用的数据结构和业务逻辑。如果您只传输更改,请使用它们更新数据结构并仍然全部绘制。您可以使用表格和其他内容的平面图初始化缓冲区Bitmap

如果需要,您可以创建一个帮助应用程序作为平面布局编辑器,它将创建座位数据结构并将其映射到座位的平面布局坐标。

以下是后台工作者如何更新位图的示例。请注意,错误处理几乎不存在;也只需要其中一个锁。你需要以某种方式得到数据。 Seat类也只是一个假人。

List<SolidBrush> seatBrushes = new List<SolidBrush>() 
    { (SolidBrush)Brushes.Red, (SolidBrush)Brushes.Green  /*..*/ };

public Bitmap bmp_Display { get; set; }
public Bitmap bmp_Buffer { get; set; }

public class Seat  
{
    public string Name { get; set; }
    public int State { get; set; }
    public Point Location { get; set; } 
    //...
}

private void drawFloorplan(List<Seat> seats)
{
    Graphics G = Graphics.FromImage(bmp_Buffer);
    Font sFont = new Font("Consolas", 8f);
    Size seatSize = new Size(32, 20);
    foreach (Seat s in seats)
    {
        G.FillRectangle(seatBrushes[s.State], new Rectangle(s.Location, seatSize));
        G.DrawString(s.Name, sFont, Brushes.Black, s.Location);
    }
    G.Dispose();
    sFont.Dispose();


}

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

        if ((worker.CancellationPending == true))
        {
            e.Cancel = true;
        }
        else
        {
            // get the seat data..
            List<Seat> seats = new List<Seat>();

            if (seats.Count > 0)
            {
                drawFloorplan(seats);
                try { bmp_Display = (Bitmap)bmp_Buffer.Clone(); } 
                catch { /*!!just for testing!!*/ }
                //lock(bmp_Display) { bmp = (Bitmap) bmp_Buffer.Clone(); }
            }

        }
    }

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if ((e.Cancelled == true))
    { this.tbProgress.Text += "Cancelled!"; }
    else if (!(e.Error == null))
    { this.tbProgress.Text += ("Error: " + e.Error.Message); }
    else
    { panel1.Invalidate(); }
}


private void panel1_Paint(object sender, PaintEventArgs e)
{
    try { e.Graphics.DrawImage(bmp, Point.Empty); } catch {/*!!just for testing!!*/ }
    //lock (bmp){   e.Graphics.DrawImage(bmp, Point.Empty);    }

}

编辑2 关于线程安全的几句话:TS的目的是确保没有两个线程同时尝试访问同一个对象。查看代码可能会想知道它是如何发生的,因为只有在完成后,BW线程才会触发Invalidate。但是线程更复杂。 它们总是如此!例如,当BW线程将其结果转储到显示位图时,系统intself触发无效时会出现问题。 (只要系统看到更新屏幕的原因,系统就可以自由执行此操作。)

为了避免这种情况,其中一个同时发生的线程应该在使用资源时阻止其访问。我建议使用do_Work方法。您可能从未在测试中遇到问题。我的测试台每秒运行30次(!),几分钟后就完成了。 try - catch只能防止崩溃。对于生产代码,应使用lock来避免整个情况。

小心lock:&#39;锁定块&#39;如果使用得太慷慨,就会造成死锁..