【问题标题】:Help Repainting a Line帮助重新绘制线条
【发布时间】:2010-03-04 13:55:08
【问题描述】:

我正在做一个自定义控件(继承自 VisualBasic.PowerPacks.LineShape),它应该像标准控件一样绘制,但附近还显示了一个图标。

所以,我只是像这样覆盖了OnPaint

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    e.Graphics.DrawIcon(myIcon, StartPoint.X, StartPoint.Y);
    base.OnPaint(e);
}

现在,一切正常,但是当我的控件移动时,图标仍然绘制在古老的地方。

有没有办法正确地绘制它?

alt text http://lh4.ggpht.com/_1TPOP7DzY1E/S5gXmp7xYiI/AAAAAAAADHI/pa1OhpKYSoM/Untitled-2.png 真实项目情况

代码:测试示例代码

alt text http://lh6.ggpht.com/_1TPOP7DzY1E/S5jSluxvtDI/AAAAAAAADHw/EUz0Tfet-rw/s800/Capture2.png

using Microsoft.VisualBasic.PowerPacks;
using System.Windows.Forms;
using System.Drawing;

namespace LineShapeTest
{
    /// 
    /// Test Form
    /// 
    public class Form1 : Form
    {        
        IconLineShape myLine = new IconLineShape();
        ShapeContainer shapeContainer1 = new ShapeContainer();
        Panel panel1 = new Panel();

        public Form1()
        {
            this.panel1.Dock = DockStyle.Fill;
            // load your back image here
            this.panel1.BackgroundImage = 
                global::WindowsApplication22.Properties.Resources._13820t;
            this.panel1.Controls.Add(shapeContainer1);

            this.myLine.StartPoint = new Point(20, 30);
            this.myLine.EndPoint = new Point(80, 120);
            this.myLine.Parent = this.shapeContainer1;

            MouseEventHandler panelMouseMove = 
                new MouseEventHandler(this.panel1_MouseMove);
            this.panel1.MouseMove += panelMouseMove;
            this.shapeContainer1.MouseMove += panelMouseMove;

            this.Controls.Add(panel1);
        }

        private void panel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                myLine.StartPoint = e.Location;
            }
        }
    }

    /// 
    /// Test LineShape
    /// 
    public class IconLineShape : LineShape
    {
        Icon myIcon = SystemIcons.Exclamation;

        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            e.Graphics.DrawIcon(myIcon, StartPoint.X, StartPoint.Y);
            base.OnPaint(e);
        }
    }
}

Nota Bene,对于 lineShape:

Parent = ShapeContainer
Parent.Parent = Panel

更新 1 跟踪

在这个 OnPaint 变体中,我们有痕迹:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    Graphics g = Parent.Parent.CreateGraphics();
    g.DrawIcon(myIcon, StartPoint.X, StartPoint.Y);            
    base.OnPaint(e);
}        

alt text http://lh4.ggpht.com/_1TPOP7DzY1E/S5j29lutQ0I/AAAAAAAADH4/4yEnZd_hPjA/s800/Capture3.png

更新 2 次闪烁

在这个 OnPaint 变体中,我们有一个闪烁的图像

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    Parent.Parent.Invalidate(this.Region, true);
    Graphics g = Parent.Parent.CreateGraphics();
    g.DrawIcon(myIcon, StartPoint.X, StartPoint.Y);            
    base.OnPaint(e);
}  

alt text http://lh5.ggpht.com/_1TPOP7DzY1E/S5j4Bam7hiI/AAAAAAAADIA/1hQWKyV8Fr0/s800/Capture4.png

更新 3:外部失效

这个变体效果很好,但是...来自 IconLineShape 类的外部:

private void panel1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        Region r = myLine.Region;
        myLine.StartPoint = e.Location;
        panel1.Invalidate(r);
    }
}


/// 
/// Test LineShape
/// 
public class IconLineShape : LineShape
{
    Icon myIcon = SystemIcons.Exclamation;
    Graphics parentGraphics;

    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        parentGraphics.DrawIcon(myIcon, StartPoint.X, StartPoint.Y);
        base.OnPaint(e);
    }

    protected override void OnParentChanged(System.EventArgs e)
    {
        // Parent is a ShapeContainer
        // Parent.Parent is a Panel
        parentGraphics = Parent.Parent.CreateGraphics();
        base.OnParentChanged(e);
    }
}

即使这样解决了测试示例的问题,我还需要在控件内部完成此控件,因为我不能强制此控件的外部“客户端”不要忘记保存旧区域并使父级无效每次换个位置...

【问题讨论】:

  • 失效仅发生在您的线路区域(自定义控件)。我看到您在控件的父级上调用 .Invalidate ,但是是在父级或父级的父级上绘制的图标。或者,您的父母是否设置了剪辑区域?
  • 我认为您必须使您的 .Parent 无效两次。一次使用图标的旧位置(擦除它),一次使用图标的新位置(绘制它)。
  • @stewbob:我怎么知道父母身上是否有剪报?自定义控件继承自 VisulBasic.PowerPacks.LineShape 控件,该控件有一个 ShapeContainer 作为父级,而作为父级的 ShapeContainer 有一个面板。
  • @serhio:对不起,我以为你也在画父级。我对 PowerPacks.LineShape 一无所知。
  • CW 由于编辑次数。

标签: c# .net vb.net gdi+ powerpacks


【解决方案1】:

您是否尝试过清除缓冲区(用背景颜色绘制一个填充矩形)?还要确保将剪辑区域重置为控件的大小,然后绘制图标,然后调用父级绘制。

【讨论】:

  • 您能建议我如何重置剪辑区域吗?
【解决方案2】:

尝试更改表单的以下功能,以使图标之前所在的缓冲区无效,从而删除其余部分(未测试代码):

    private void panel1_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Point oldPos = myLine.StartPoint;
            myLine.StartPoint = e.Location;
            this.Invalidate(new Recangle(oldPos.X, oldPos.Y, myLine.Width, myLine.Height));
        }
    }

如果这不起作用,请尝试:

    private void panel1_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            myLine.StartPoint = e.Location;
            this.Refresh();
        }
    }

由于性能问题(整个缓冲区被清除),可能不推荐使用此方法,但其他任何方法都不起作用......

【讨论】:

  • @henchman:谢谢,它有效,请参阅我的更新 3。但是我不能将此逻辑留在控件之外,我需要控件本身正确重绘。
  • @serhio:绘制线条时,将位置记录在另一个变量中。然后,下次绘制时,如果位置不同,可以使旧区域失效。
  • @John:请参阅更新 2。当我使旧区域无效时,可能与新区域相交,无限重新绘制循环开始。
  • @serhio:您根本不应该以无限的重绘周期结束。即使没有做任何工作来确保您不会使您将要绘制的部分无效。为什么?因为你会检查你是否已经在当前位置画了线。如果线没有移动,并且您正在重新绘制它,则不需要使旧位置无效。
【解决方案3】:

只是添加另一个完全不同的(希望再次工作:-) 解决方案。由于我不知道“代码必须在课堂内”的要求,因此这是后续行动。

公理:永远不要在 OnPaint 或 OnPaintBackground 中调用 Invalidate()Refresh(),因为您将(总是)以 infinite loop 结束。

所以我们必须为他们寻找另一个地方。我试图在 Visual Studio 中编译你的类,但我找不到 LineShape 类(Microsoft.VisualBasic.PowerPacks.Vs.dll 没有做到这一点),所以再次 untestet 代码。

我做了什么?

  1. 从 Form 中删除了 MouseMove 处理程序并将其放入 IconLineShape 类中。应该没问题,因为如果客户想要拖放,就可以了。尝试上述解决方案之一。
  2. 添加了一个属性以禁用 IconLineShape 中的拖放功能(如果客户对此不满意 :-)。如果没有拖放,我们一开始就不会有问题。

-

    public class Form1 : Form
    {
       IconLineShape myLine = new IconLineShape();
       ShapeContainer shapeContainer1 = new ShapeContainer();
       Panel panel1 = new Panel();

       public Form1()
       {
           this.panel1.Dock = DockStyle.Fill;
           // load your back image here
           this.panel1.BackgroundImage =
               global::WindowsApplication22.Properties.Resources._13820t;
           this.panel1.Controls.Add(shapeContainer1);

           this.myLine.StartPoint = new Point(20, 30);
           this.myLine.EndPoint = new Point(80, 120);
           this.myLine.Parent = this.shapeContainer1;

           this.Controls.Add(panel1);
       }
   }

   public class IconLineShape : LineShape
   {
       Icon myIcon = SystemIcons.Exclamation;

       protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
       {
           e.Graphics.DrawIcon(myIcon, StartPoint.X, StartPoint.Y);
           base.OnPaint(e);
       }

       protected override void OnMouseMove(MouseEventArgs e)
       {
           if (draggable && 
               e.Button == MouseButtons.Left &&
               !this.StartPoint.Equals(e.Location))
           {
               Region r = this.Region.Clone();

               this.StartPoint = e.Location;

               // try solution 1
               this.Invalidate(r);

               // solution 2; walk up to the upmost parent and refresh
               // as said before, this.Invalidate() is to be preferred
               Control currentParent = this.Parent;
               while (currentParent.Parent != null)
               {
                   currentParent = currentParent.Parent;
               }
               currentParent.Refresh();
           } 
       }

       private bool draggable = true;

       public bool Draggable
       {
           get { return this.draggable; }
           set { this.draggable = value; }
       }
   }

请提供反馈。

【讨论】:

  • MS PowerPacks (2.7 MB) msdn.microsoft.com/en-us/vbasic/bb735936.aspx 不包含在 .NET 框架中,因此需要单独安装。 MouseMove 在 lineShape 上不起作用,原因是非常纤细,当用户快速移动鼠标时它会丢失线条,并且 mousemove 事件结束。
  • 那会很奇怪。是否覆盖 onmousemove 方法或“从外部”分配 mousemove 事件处理程序没有区别,因为此事件处理程序仅像 onmousemove 那样被调用。你测试了吗?
  • ShapeContainer 仅在鼠标位于包含的形状(在我们的例子中为一行)上方时才检测鼠标移动。因此,要仅使用 ShapeContainer mouseMove 移动线条,我们应该非常缓慢地移动鼠标指针 - 不离开线条区域。
【解决方案4】:

你是在自定义控件上这样做的吗?

去除“频闪”效应

Public Sub New()
    Me.SetStyle(ControlStyles.ResizeRedraw Or _
                ControlStyles.DoubleBuffer Or _
                ControlStyles.UserPaint Or _
                ControlStyles.AllPaintingInWmPaint, _
                True)
    Me.UpdateStyles()

End Sub

【讨论】:

  • 很遗憾基类(PowerPacks.LineShape)没有这样的方法(SetStyle)
【解决方案5】:

最后还是加了PictureBox,而不是自己画图标。

using Microsoft.VisualBasic.PowerPacks;
using System.Windows.Forms;
using System.Drawing;

namespace LineShapeTest
{
    /// 
    /// Test Form
    /// 
    public class Form1 : Form
    {
        IconLineShape myLine = new IconLineShape();
        ShapeContainer shapeContainer1 = new ShapeContainer();
        Panel panel1 = new Panel();

        public Form1()
        {
            this.panel1.Dock = DockStyle.Fill;
            // load your back image here
            //this.panel1.BackgroundImage =
            //global::WindowsApplication22.Properties.Resources._13820t;
            this.panel1.BackColor = Color.White;
            this.panel1.Controls.Add(shapeContainer1);

            this.myLine.StartPoint = new Point(20, 30);
            this.myLine.EndPoint = new Point(80, 120);
            this.myLine.Parent = this.shapeContainer1;

            MouseEventHandler panelMouseMove =
                new MouseEventHandler(this.panel1_MouseMove);
            this.panel1.MouseMove += panelMouseMove;
            this.shapeContainer1.MouseMove += panelMouseMove;

            this.Controls.Add(panel1);
        }

        private void panel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                myLine.StartPoint = e.Location;
            }
        }
    }

    /// 
    /// Test LineShape
    /// 
    public class IconLineShape : LineShape
    {
        Icon myIcon = SystemIcons.Exclamation;
        PictureBox pictureBox = new PictureBox();

        public IconLineShape()
        {
            pictureBox.Image = Bitmap.FromHicon(myIcon.Handle);
            pictureBox.Size = myIcon.Size;
            pictureBox.Visible = true;
        }

        protected override void OnMove(System.EventArgs e)
        {
            base.OnMove(e);
            pictureBox.Location = this.StartPoint;
        }

        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            base.OnPaint(e);
            pictureBox.Invalidate();
        }

        public override bool HitTest(int x, int y)
        {            
            return base.HitTest(x, y) |
                pictureBox.RectangleToScreen(
                    pictureBox.DisplayRectangle).Contains(new Point(x, y));
        }

        protected override void OnParentChanged(System.EventArgs e)
        {
            // Parent is a ShapeContainer
            // Parent.Parent is a Panel
            pictureBox.Parent = Parent.Parent;
            base.OnParentChanged(e);
        }
    }
}

感谢大家的参与!

【讨论】:

  • 您不需要 OnPaint 方法。请注意,PictureBox 的背景绘制在您的线条上。删除 OnPaint 并在 pictureBox.Location = this.StartPoint; 之后调用 pictureBox.Invalidate()这将解决问题。
  • 调用pictureBox.Invalidate();正如追随者指出的那样,在 OnPaint 中使用并不是一个好主意。
  • pictureBox.Image = Bitmap.FromHicon(myIcon.Handle);这不是一个好主意。此方法用于从 Windows API 返回的句柄创建位图。您应该使用 SystemIcons.Exclamation.ToBitmap();您不需要维护图标。这将有助于节省资源
  • @AMissico:如果我不调用 pictureBox.Invalidate(),我会在图片上看到线条痕迹。这是因为我叫它。至于图标,在实际项目中我肯定不会使用 SystemIconsExcamation 而是自定义图标,仅用于演示目的。
  • @serhio:在您的示例代码中,您必须调用 Invalidate,因为您没有将 PictureBox 与控件集合相关联。我只是建议您在设置图片框的位置后调用 Invalidate。这将消除对 OnPaint 的需求,并将解决图片框在线条形状上绘制的问题。
【解决方案6】:

这有帮助吗?看来您对 LineShape 太着迷了。对我来说,从 RectangleShape 派生更有意义。我把所有东西都包裹在一个负责细节的助手中。这个助手是我用来将控件关联在一起而不创建“复合控件”的标准技术,这通常更容易。

using Microsoft.VisualBasic.PowerPacks;
using System.Windows.Forms;
using System.Drawing;

namespace LineShapeTest {

    public partial class Form1 : Form {

        /*  Designer support through
         *  Create Panel
         *  Set panel's background image
         *  Add LineShape
         *  Add IconShape
         *  Create IconicLineShapeHelper
         */
        public Form1() {
            InitializeComponent();
            IconicLineShapeHelper arbitrary1 = new IconicLineShapeHelper(lineShape1, iconShape1);
            IconicLineShapeHelper arbitrary2 = new IconicLineShapeHelper(lineShape2, iconShape2);
        }

        private Panel panel1;
        private ShapeContainer shapeContainer1;
        private LineShape lineShape1;
        private IconShape iconShape1;
        private ShapeContainer shapeContainer2;
        private LineShape lineShape2;
        private IconShape iconShape2;

    #region [ Form1.Designer.cs ]
        private System.ComponentModel.IContainer components = null;
        protected override void Dispose(bool disposing) {
            if (disposing && (components != null)) {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
        #region Windows Form Designer generated code
        private void InitializeComponent() {
            this.lineShape1 = new Microsoft.VisualBasic.PowerPacks.LineShape();
            this.panel1 = new System.Windows.Forms.Panel();
            this.shapeContainer1 = new Microsoft.VisualBasic.PowerPacks.ShapeContainer();
            this.iconShape1 = new LineShapeTest.IconShape();
            this.shapeContainer2 = new Microsoft.VisualBasic.PowerPacks.ShapeContainer();
            this.lineShape2 = new Microsoft.VisualBasic.PowerPacks.LineShape();
            this.iconShape2 = new LineShapeTest.IconShape();
            this.panel1.SuspendLayout();
            this.SuspendLayout();
            // 
            // lineShape1
            // 
            this.lineShape1.Name = "lineShape1";
            this.lineShape1.X1 = 13;
            this.lineShape1.X2 = 88;
            this.lineShape1.Y1 = 11;
            this.lineShape1.Y2 = 34;
            // 
            // panel1
            // 
            this.panel1.BackgroundImage = global::LineShapeTest.Properties.Resources._13820t;
            this.panel1.Controls.Add(this.shapeContainer1);
            this.panel1.Location = new System.Drawing.Point(27, 24);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(162, 122);
            this.panel1.TabIndex = 1;
            // 
            // shapeContainer1
            // 
            this.shapeContainer1.Location = new System.Drawing.Point(0, 0);
            this.shapeContainer1.Margin = new System.Windows.Forms.Padding(0);
            this.shapeContainer1.Name = "shapeContainer1";
            this.shapeContainer1.Shapes.AddRange(new Microsoft.VisualBasic.PowerPacks.Shape[] {
            this.iconShape1,
            this.lineShape1});
            this.shapeContainer1.Size = new System.Drawing.Size(162, 122);
            this.shapeContainer1.TabIndex = 0;
            this.shapeContainer1.TabStop = false;
            // 
            // iconShape1
            // 
            this.iconShape1.BorderStyle = System.Drawing.Drawing2D.DashStyle.Custom;
            this.iconShape1.Location = new System.Drawing.Point(88, 64);
            this.iconShape1.Name = "iconShape1";
            this.iconShape1.Size = new System.Drawing.Size(32, 32);
            // 
            // shapeContainer2
            // 
            this.shapeContainer2.Location = new System.Drawing.Point(0, 0);
            this.shapeContainer2.Margin = new System.Windows.Forms.Padding(0);
            this.shapeContainer2.Name = "shapeContainer2";
            this.shapeContainer2.Shapes.AddRange(new Microsoft.VisualBasic.PowerPacks.Shape[] {
            this.iconShape2,
            this.lineShape2});
            this.shapeContainer2.Size = new System.Drawing.Size(292, 266);
            this.shapeContainer2.TabIndex = 2;
            this.shapeContainer2.TabStop = false;
            // 
            // lineShape2
            // 
            this.lineShape2.Name = "lineShape2";
            this.lineShape2.X1 = 48;
            this.lineShape2.X2 = 123;
            this.lineShape2.Y1 = 187;
            this.lineShape2.Y2 = 210;
            // 
            // iconShape2
            // 
            this.iconShape2.Location = new System.Drawing.Point(136, 220);
            this.iconShape2.Name = "iconShape2";
            this.iconShape2.Size = new System.Drawing.Size(75, 23);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(292, 266);
            this.Controls.Add(this.panel1);
            this.Controls.Add(this.shapeContainer2);
            this.Name = "Form1";
            this.Text = "Form1";
            this.panel1.ResumeLayout(false);
            this.ResumeLayout(false);

        }
        #endregion
    }
    #endregion

    public class IconicLineShapeHelper {
        ShapeContainer _container;
        LineShape _line;
        IconShape _icon;
        public IconicLineShapeHelper(LineShape line, IconShape icon) {
            _container = line.Parent;
            _line = line;
            _icon = icon;
            _container.MouseMove += new MouseEventHandler(_container_MouseMove);
        }
        void _container_MouseMove(object sender, MouseEventArgs e) {
            if (e.Button == MouseButtons.Left) {
                _line.StartPoint = e.Location;
                _icon.Location = e.Location;
            }
        }
    }
    public class IconShape : RectangleShape {
        Icon _icon = SystemIcons.Exclamation;
        public IconShape() {
            this.Size = new System.Drawing.Size(32, 32);
            this.BorderStyle = System.Drawing.Drawing2D.DashStyle.Custom;
        }
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) {
            e.Graphics.DrawIcon(_icon, this.Location.X, this.Location.Y);
            base.OnPaint(e);
        }
    }
}

【讨论】:

  • 感谢您的想法。但是,LineShape 是必需的。查看我添加的真实项目屏幕。用户可以移动线条,而不是矩形(这里 LineShape 用于命中测试 - 点击、鼠标悬停 - 工具提示和重绘逻辑 - 透明度)
  • 我仍在使用 LineShape。我只是将图标与线相关联。助手类只保持图标随线条移动。 RectangeShape 知道如何在移动时正确更新背景。因此,它是一个很好的继承对象。
  • 您仍然可以使用 IconLineShape。只需在构造函数中创建一个 IconShape 和 IconicLineShapeHelper 供内部使用。
  • 我明白了。是不是要注意,容器作为 ShapeContainer 不好管理 MouseMove 事件,因为 MouseCursor 容易离开细线区域和移动端。我想我最后只添加一个 PictureBox :) 我什至测试过,它似乎可以工作。
  • 我尽可能地复制了你所拥有的东西,并试图让它尽可能简单和灵活。我想一旦你有一些工作,你就可以根据自己的需要进行调整。
猜你喜欢
  • 1970-01-01
  • 2020-04-18
  • 1970-01-01
  • 2013-12-27
  • 2020-02-27
  • 1970-01-01
  • 1970-01-01
  • 2018-10-02
  • 1970-01-01
相关资源
最近更新 更多