代码之家  ›  专栏  ›  技术社区  ›  catfood

重叠系统.绘图.图形对象

  •  1
  • catfood  · 技术社区  · 16 年前

    我有一个WinForms控件,我想在其上显示两件事:

    1. 从外部输入设备一点一点地加载的底层图像;以及
    2. 在该图像上创建可视图案的一系列绘制线调用。

    事情1,就我们的目的而言,不会改变,我宁愿不重新绘制它。

    事情2必须相对快速地重新绘制,因为当用户旋转另一个控件时,它会旋转。

    在我的幻想中,我想把每件东西放在它自己的图形对象中,给2一个透明的背景,然后简单地用旋转变换点击2以匹配用户控制设置。但我看不到一种使图形对象透明的方法,也看不到旋转已绘制的对象的方法。所以我可能会要求图形做一些它没有设计的事情。

    我的问题是:最好的方法是什么?我应该尝试重叠我的图形对象,还是有一些完全不同的更好的方法来做这件事,我没有想到?

    2 回复  |  直到 16 年前
        1
  •  1
  •   Hans Passant    16 年前

    Windows绘制模型非常符合您的要求。它将绘制背景(onpaint background)与前景(onpaint)分开。但这并不意味着你只能画一次背景,然后就可以完成了。窗口表面无效会同时调用这两者。最重要的是,要使消除混叠效果正常工作,它们只能在已知的背景色下看起来很好。

    Punt并在onpaintbackground()重写中绘制图像。通过分配BackgroundImage属性,可以让控件自动为您执行此操作。您可能需要将DoubleBuffer属性设置为true,以避免在绘制背景时看到闪烁,从而暂时擦除前景像素。如果需要更新前台,请调用invalidate()。

    要完整,你的幻想实际上是可能的。你需要用一个顶层的分层窗口覆盖图像。对于设置了transparencykey属性的窗体,这很容易实现。下面是一个示例实现:

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    
    class OverlayedPictureBox : PictureBox {
        private Form mOverlay;
        private bool mShown;
        public event PaintEventHandler PaintOverlay;
        public OverlayedPictureBox() {
            mOverlay = new Form();
            mOverlay.FormBorderStyle = FormBorderStyle.None;
            mOverlay.TransparencyKey = mOverlay.BackColor = Color.Magenta;
            mOverlay.ShowInTaskbar = false;
        }
        protected void OnPaintOverlay(PaintEventArgs e) {
            // NOTE: override this or implement the PaintOverlay event
            PaintEventHandler handler = PaintOverlay;
            if (handler != null) handler(this, e);
        }
        public void RefreshOverlay() {
            // NOTE: call this to force the overlay to be repainted
            mOverlay.Invalidate();
        }
        protected override void Dispose(bool disposing) {
            if (disposing) mOverlay.Dispose();
            base.Dispose(disposing);
        }
        protected override void OnVisibleChanged(EventArgs e) {
            if (!mShown && !this.DesignMode) {
                Control parent = this.Parent;
                while (!(parent is Form)) parent = parent.Parent;
                parent.LocationChanged += new EventHandler(parent_LocationChanged);
                mOverlay.Paint += new PaintEventHandler(mOverlay_Paint);
                mOverlay.Show(parent);
                mShown = true;
            }
            base.OnVisibleChanged(e);
        }
        protected override void OnLocationChanged(EventArgs e) {
            mOverlay.Location = this.PointToScreen(Point.Empty);
            base.OnLocationChanged(e);
        }
        protected override void OnSizeChanged(EventArgs e) {
            mOverlay.Size = this.Size;
            base.OnSizeChanged(e);
        }
        void parent_LocationChanged(object sender, EventArgs e) {
            mOverlay.Location = this.PointToScreen(Point.Empty);
        }
        private void mOverlay_Paint(object sender, PaintEventArgs e) {
            OnPaintOverlay(e);
        }
    }
    

    一个有趣的工件:最小化表单并重新恢复它看起来,呃,很特别。

        2
  •  1
  •   itowlson    16 年前

    gdi+不是保留模式,因此您需要在每个绘制上重新绘制整个控件。所以不幸的是,你不能只拥有两个“东西”并对其中一个应用旋转。您对GDI+的最佳选择可能是:

    • 将1存储在图像对象中。(可以预渲染元素 通过使用graphics.fromimage或在 预构建位图。)
    • 将2存储为一组坐标对。

    然后在绘制处理程序中使用graphics.drawimage快速重新绘制1,使用graphics.rotateTransform设置旋转变换,并绘制线。您应该能够使用双缓冲(controlStyles.doubleBuffer)使其看起来平滑。

    至于“完全不同”的方式,嗯,你所说的“幻想”被称为“Windows演示基金会”。WPF确实有一个保留模式的图形系统,可以更方便地处理“旋转一层,同时保持另一层不变”。您可以使用elementHost控件在WinForms中承载WPF。粗略的想法是使用网格在图像上覆盖画布,向画布添加线条对象,将画布的renderTransform设置为RotateTransform,并将RotateTransform的角度绑定到其他控件的值。但是,这确实会引起项目考虑(目标平台、学习曲线)和技术考虑(加载WPF DLL的初始开销、互操作约束)。

    推荐文章