【问题标题】:Overlapping System.Drawing.Graphics objects重叠 System.Drawing.Graphics 对象
【发布时间】:2009-12-29 03:45:39
【问题描述】:

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

  1. 从外部输入设备逐位加载的底层图像;和
  2. 一系列 DrawLine 调用,在该图像上创建视觉图案。

就我们的目的而言,第 1 件事没有改变,我宁愿不必重新绘制它。

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

在我的幻想中,我想将每个 Thing 放入其自己的 Graphics 对象中,为 #2 提供透明背景,然后简单地通过旋转变换点击 #2 以匹配用户控件设置。但我没有看到一种使 Graphics 对象透明的方法,也没有一种方法来旋转已经绘制的对象。所以我可能会要求 Graphics 做一些它不是为它设计的事情。

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

【问题讨论】:

    标签: .net winforms graphics gdi+


    【解决方案1】:

    Windows 绘画模型非常适合您的要求。它将背景(OnPaintBackground)与前景(OnPaint)分开。然而,这并不意味着您只能绘制一次背景并完成它。窗口表面失效同时调用两者。这首先是使抗锯齿效果正常工作所必需的,它们只能在已知的背景颜色下看起来不错。

    Punt 并在 OnPaintBackground() 覆盖中绘制图像。您可以通过分配 BackgroundImage 属性让 Control 自动为您执行此操作。您可能需要将 DoubleBuffer 属性设置为 true 以避免在绘制背景时出现闪烁,从而暂时消除前景像素。如果需要更新前台,请调用 Invalidate()。

    完整地说,您的幻想实际上是可能的。您需要使用顶层分层窗口覆盖图像。使用设置了 TransparencyKey 属性的 Form 很容易做到这一点。这是一个示例实现:

    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】:

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

      • 将 #1 存储在 Image 对象中。 (您可以预渲染元素 通过使用 Graphics.FromImage 或通过在 预先构建的位图。)
      • 将 #2 存储为一组坐标对。

      然后在 Paint 处理程序中使用 Graphics.DrawImage 快速重绘 #1,使用 Graphics.RotateTransform 设置旋转变换,然后绘制线条。您应该能够使用双缓冲 (ControlStyles.DoubleBuffer) 使其看起来平滑。

      至于执行此操作的“完全不同”的方式,那么,您所描述的“幻想”称为 Windows Presentation Foundation。 WPF 确实具有保留模式图形系统,并且可能能够更方便地处理“旋转一层,同时保持另一层不变”。您可以使用 ElementHost 控件在 WinForms 中托管 WPF。粗略的想法是使用 Grid 将 Canvas 覆盖在 Image 上,将 Line 对象添加到 Canvas,将 Canvas 的 RenderTransform 设置为 RotateTransform,并将 RotateTransform 的 Angle 绑定到其他控件的值。然而,这确实会引起项目考虑(目标平台、学习曲线)以及技术方面的考虑(加载 WPF DLL 的初始开销、互操作约束)。

      【讨论】:

      • 两个答案都很棒。我不得不选择一个。 :-(
      猜你喜欢
      • 2021-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-23
      • 2014-09-04
      • 1970-01-01
      相关资源
      最近更新 更多