2013-05-09 47 views
1

[C#.NET 4.0]如何防止调整大小(C#)时无边界Windows窗体闪烁?

我学习C#,我试图建立有FormBorderStyle = FormBorderStyle.None和可移动/使用Windows API调整Windows窗体中使用C#。举个例子,我使用圆角或用于Google Chrome和Norton 360的自定义(可移动/可调整大小)边框设计作为我的表单的基础。

我已经取得了很大的进步,到目前为止,并得到的一切工作,除了当我调整的形式,有黑/白一起闪烁当调整表格右侧和底部边框的长度快速

我尝试添加在构造函数中this.DoubleBuffer = true,并且还试图this.SetStyles(ControlStyles.AllPaintInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);

因为我是图形方面的东西,我喜欢控制窗体的完整设计,所以我可以看到这是永远困扰我的东西... 所以如果有人可以帮助我解决这个问题,所以闪烁不再发生,这将对我的学习过程非常有用。

我还要提到的是,我使用的是Windows XP,所以我不知道this post会帮助我,因为它似乎集中在Vista/7的(与DWM)......不是我”米先进到足以理解这篇文章中的所有内容。

与该API工作的代码的两个部分的下方。我有一个WM_NCHITTEST的Windows API公开枚举...你可以看到值in this link

的OnPaint重写方法

protected override void OnPaint(PaintEventArgs e) 
{ 
    System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0, 
     this.ClientSize.Width, this.ClientSize.Height, 15, 15); 

    SetWindowRgn(this.Handle, ptrBorder, true); 

    Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, 
     this.ClientSize.Height - cGrip, cGrip, cGrip); 
    ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc); 
    rc = new Rectangle(0, 0, this.ClientSize.Width, 32); 
    e.Graphics.FillRectangle(Brushes.SlateGray, rc); 
} 

WNDPROC覆盖方法

protected override void WndProc(ref Message m) 
{ 
    if (m.Msg == (int)HitTest.WM_NCHITTEST) 
    { 
     // Trap WM_NCHITTEST 
     Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16); 
     pos = this.PointToClient(pos); 

     if (pos.Y < cCaption) 
     { 
      m.Result = (IntPtr)HitTest.HTCAPTION; 
      return; 
     } 

     if (pos.X <= cGrip && pos.Y >= this.ClientSize.Height - cGrip) 
     { 
      m.Result = (IntPtr)HitTest.HTBOTTOMLEFT; 
      return; 
     } 

     if (pos.X >= this.ClientSize.Width - cGrip && 
      pos.Y >= this.ClientSize.Height - cGrip) 
     { 
      m.Result = (IntPtr)HitTest.HTBOTTOMRIGHT; 
      return; 
     } 

     if (pos.X >= this.ClientSize.Width - cBorder) 
     { 
      m.Result = (IntPtr)HitTest.HTRIGHT; 
      return; 
     } 

     if (pos.Y >= this.ClientSize.Height - cBorder) 
     { 
      m.Result = (IntPtr)HitTest.HTBOTTOM; 
      return; 
     } 

     if (pos.X <= cBorder) 
     { 
      m.Result = (IntPtr)HitTest.HTLEFT; 
      return; 
     } 
    } 

    base.WndProc(ref m); 
} 

下面是完整的代码

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Windows.Forms; 
using System.Runtime.InteropServices; 

namespace PracticeForm 
{ 
    public partial class Form2 : Form 
    { 
     [DllImport("user32.dll")] 
     private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw); 

     [DllImport("gdi32.dll")] 
     private static extern IntPtr CreateRoundRectRgn(int x1, int y1, int x2, int y2, int cx, int cy); 

     [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] 
     private static extern bool DeleteObject(System.IntPtr hObject); 

     private const int cGrip = 20; 
     private const int cCaption = 35; 
     private const int cBorder = 7; 
     private Point mouseOffset; 

     public Form2() 
     { 
      InitializeComponent(); 
      this.FormBorderStyle = FormBorderStyle.None; 
      this.MaximumSize = new Size(670, 440); 
      this.DoubleBuffered = true; 
      this.SetStyle(ControlStyles.ResizeRedraw | 
          ControlStyles.OptimizedDoubleBuffer | 
          ControlStyles.AllPaintingInWmPaint | 
          ControlStyles.UserPaint, true); 
     } 

     protected override void OnPaint(PaintEventArgs e) 
     { 
      System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0, 
       this.ClientSize.Width, this.ClientSize.Height, 15, 15); 

      SetWindowRgn(this.Handle, ptrBorder, true); 

      Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, 
       this.ClientSize.Height - cGrip, cGrip, cGrip); 
      ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc); 
     } 

     protected override void WndProc(ref Message m) 
     { 
      if (m.Msg == (int)HitTest.WM_NCHITTEST) 
      { 
       // Trap WM_NCHITTEST 
       Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16); 
       pos = this.PointToClient(pos); 

       if (pos.Y < cCaption) 
       { 
        m.Result = (IntPtr)HitTest.HTCAPTION; 
        return; 
       } 

       if (pos.X <= cGrip && pos.Y >= this.ClientSize.Height - cGrip) 
       { 
        m.Result = (IntPtr)HitTest.HTBOTTOMLEFT; 
        return; 
       } 

       if (pos.X >= this.ClientSize.Width - cGrip && 
        pos.Y >= this.ClientSize.Height - cGrip) 
       { 
        m.Result = (IntPtr)HitTest.HTBOTTOMRIGHT; 
        return; 
       } 

       if (pos.X >= this.ClientSize.Width - cBorder) 
       { 
        m.Result = (IntPtr)HitTest.HTRIGHT; 
        return; 
       } 

       if (pos.Y >= this.ClientSize.Height - cBorder) 
       { 
        m.Result = (IntPtr)HitTest.HTBOTTOM; 
        return; 
       } 

       if (pos.X <= cBorder) 
       { 
        m.Result = (IntPtr)HitTest.HTLEFT; 
        return; 
       } 
      } 

      base.WndProc(ref m); 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 
      this.Close(); 
     } 

     private void button2_MouseClick(object sender, MouseEventArgs e) 
     { 
      this.WindowState = FormWindowState.Minimized; 
     } 

     private void panel1_MouseDown(object sender, MouseEventArgs e) 
     { 
      mouseOffset = new Point(-e.X, -e.Y); 
     } 

     private void panel1_MouseMove(object sender, MouseEventArgs e) 
     { 
      if (e.Button == MouseButtons.Left) 
      { 
       Point p = Control.MousePosition; 
       p.Offset(mouseOffset.X, mouseOffset.Y); 
       Location = p; 
      } 
     } 

     private void label1_MouseDown(object sender, MouseEventArgs e) 
     { 
      mouseOffset = new Point(-e.X, -e.Y); 
     } 

     private void label1_MouseMove(object sender, MouseEventArgs e) 
     { 
      if (e.Button == MouseButtons.Left) 
      { 
       Point p = Control.MousePosition; 
       p.Offset(mouseOffset.X, mouseOffset.Y); 
       Location = p; 
      } 
     } 
    } 
} 

感谢您的帮助。

回答

0

只设置区域时,形式实际上在paint()事件不会每次都有改变SIZE,:

protected override void OnSizeChanged(EventArgs e) 
    { 
     base.OnSizeChanged(e); 

     System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0, 
      this.ClientSize.Width, this.ClientSize.Height, 15, 15); 

     SetWindowRgn(this.Handle, ptrBorder, true); 
    } 

    protected override void OnPaint(PaintEventArgs e) 
    { 

     Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, 
      this.ClientSize.Height - cGrip, cGrip, cGrip); 
     ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc); 
    } 
+0

这可能会稍微好一些,但它仍然闪烁非常糟糕。另外,出于某种原因,您的代码似乎已经覆盖了我的表单开始位置(CenterScreen)。 – user2063351 2013-05-09 18:00:25

+0

您是否有图像作为表单的背景?或者可能是Paint()事件中的一些更复杂的绘画? – 2013-05-09 18:25:39

+0

不,表单中的所有内容都是在代码中创建的(除了枚举类以外,我的所有代码都列在了我的文章中)。这只是一个矩形区域,覆盖无边界形式来创建圆角(据我了解)。当然,还有自定义SizeGrip的附加矩形。我认为创建圆角的矩形是导致问题的原因,因为当我完全删除该CreateRoundRectRgn代码时,它看起来不会闪烁(或闪烁几乎不如坏)。但圆角对我来说是一个重要的视觉实现。 – user2063351 2013-05-09 19:03:57

1

闪烁是因为你的显示器领域正在迅速改变颜色,这反过来又发生因为你正在透支 - 在同一像素上绘制不止一件东西。

这是因为:

  • 如果你是重绘缓慢,那么东西,是在屏幕上(例如窗口边框),你绘制了会一会儿可见。例如用户可能会看到滚动条的两个副本,直到用完表单内容完成擦除旧副本。
  • 窗口会自动为您擦除窗口的背景,通常为白色。绘图中任何不是白色的区域因此在透过正确的图像之前会瞬间变白。
  • ,如果你在同一个地方画多的事情,你会看到闪烁,你不断在屏幕

要解决这些问题的那个区域改变颜色,你需要的东西的结合(更越好)

  • 禁用擦除背景,或将擦除色彩的主色图像
  • 优化重绘代码,使其速度更快,所以闪烁不太突出
  • 优化你重绘代码以消除过度绘制。例如。要在矩形页面周围放置边框,可以绘制背景颜色并将其与页面重叠绘制,但会闪烁。相反,将顶部边框绘制为矩形,然后将底部左侧和右侧绘制,然后在中间绘制页面。由于nopixels不止一次被绘制,它不会闪烁
  • 在控件上启用DoubleBuffered模式。这样,所有的绘图实际上都会发生在内存中的位图图像中,然后最终图像被复制到屏幕上,以便每个像素只显示一次,而且没有闪烁。
+0

我很感激你花时间来解释事情......但是,由于我还在学习,很多你写的东西没有任何意义(尽管我会查找一些东西)。我想指出,我在我的帖子中指出,我不仅使用您提到的DoubleBuffered模式,而且还使用SetStyles(ControlStyles ...)无济于事。 – user2063351 2013-05-09 19:06:58

+0

我确实看到我在同一时间在同一个地方绘制了两件东西,但我还没有足够的经验来实现您的建议,以解决我绘制的矩形之间的像素绘制冲突的形式和矩形我画的服务作为SizeGrip。 – user2063351 2013-05-09 19:12:01

+0

你为“没有足够经验”做了相当先进的事情:-)尝试重载OnPaintBackground(不做任何事情,避免双重油漆),优化你的代码,这样你就不会在你的OnPaint中做任何你不喜欢的事情不需要 - 例如在OnSizeChanged中创建您的region/rects而不是每个绘制请求。另请注意,更改窗口区域可能会导致重绘,本身可能会导致闪烁。 – 2013-05-09 20:34:09

0

尽管这是一个相当古老的线程,OP很可能找到了解决方案并继续前进,但我想添加一些附加点以防他们对.NET开发人员有利。正在解决类似的问题。

首先,我的帽子熄灭于你试图在Windows XP中解决这个问题。我去过那里,花了很多时间在那里学习了所有艰难的课程。不幸的是,由于Windows XP缺乏我们大多数人习以为常的DWM,因此没有简单的解决方案。

设置了正确的ControlStyles绝对是至关重要的 - 我还包括:

SetStyle(ControlStyles.Opaque, True) 

双缓冲您打算绘制控制是重要的,因为闪烁被控制主要是引起被重新绘制在中间的显示器垂直回扫。仅仅因为你调用了Invalidate(),它并不一定意味着控件将在你想要的时候重新绘制 - 你将受到Windows的支配,并且操作系统会在它准备好时执行它。您可以通过从DirecDraw 7借力像WaitForVerticalBlank功能(在Windows XP很多支持该API),并通过使用GetVerticalBlankStatus和GetScanLine相应地计时您的渲染和演示解决这个(像我一样),在Windows XP上。