如何实现真正有效的精灵组合?

时间:2014-12-23 15:12:33

标签: c# winforms

我的要求:

  • 具有背景的控件可以在其上绘制精灵。
  • 需要能够以编程方式移动精灵并设置拖动事件。
  • 精灵的图像可能具有Alpha透明度;精灵必须正确地与背景和彼此混合。
  • 绘图顺序必须与精灵的逻辑顺序相匹配 - 单击一个看起来位于顶部的精灵应该开始拖动该精灵,而不是一个出现在它下面的精灵。

尝试1

显而易见的方法是创建一个自定义控件来表示Sprite。我们来试试吧:

public partial class Sprite : Control
{
    public Sprite()
    {
        InitializeComponent();

出于测试目的,我们会这样做,所以点击一个精灵只是把它带到前面,没有拖动:

        this.Click += Sprite_Click;
    }

    void Sprite_Click(object sender, EventArgs e)
    {
        this.BringToFront();
    }

因此我们可以识别每个精灵并验证绘图顺序,让我们设置一个'框架'每种颜色:

    public Color FrameColor { get; set; }

将精灵绘制成具有纯色框架的半透明白色身体 - 然后我们应该能够验证背景在单个空间后面显示为阴影,在精灵重叠的地方阴影更强,并且边框按预期重叠:

    protected override void OnPaint(PaintEventArgs pe)
    {
        Graphics g = pe.Graphics;
        g.FillRectangle(
            new SolidBrush(Color.FromArgb(128, 255, 255, 255)),
            DisplayRectangle
        );
        g.DrawRectangle(new Pen(FrameColor, 10), DisplayRectangle);
    }

然后我们可以设计一个深色背景的表格,在其上设置几个精灵,使它们重叠,给它们不同的帧颜色,并进行测试。

当然,它不起作用。默认情况下,控件有一个背景颜色,默认为白色或接近它的颜色。所以这些精灵恰好重叠,但它们有不透明的白色中间。

尝试2

嗯,当然我们可以将背景颜色设置为透明?微软的文档中的一些谷歌搜索告诉我们,我们当然可以,但不能直接(as someone else on SO found out the hard way)。它需要一些配置:

        // in constructor
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        this.BackColor = Color.Transparent;

好的,现在通过每个Sprite显示背景,但 Sprite边框不会互相显示。是什么赋予了?多一点谷歌搜索告诉我们这种透明的背景颜色'支持实际上是一个黑客攻击;基本上,父控件实现了它的背景绘制,以便从父组件复制图像数据并复制它,忽略其他所有内容。

说实话,我希望系统的设计能够自动完成 - 即任何时间任何绘图,它与任何&#组合39;屏幕下方,控件之间不断展示的唯一原因是它们明显不透明的背景。但没有这样的运气。我想整个系统都是在人们出于性能原因而默认不需要那种东西时设计的。

反正。

尝试3

好吧,如果背景画是造成问题的原因,也许我们可以完全禁用它?

    protected override void OnPaintBackground(PaintEventArgs ignored) { }

都能跟得上即可。如果我们点击精灵,我们可以看到它们重新排序,并相互合成 - 但它们不与背景图像合成。更奇怪的是,当父窗口无效时(通过调整窗体大小,或最小化并恢复它),精灵再次变为不透明。

尝试4

在更多谷歌搜索之后,我们发现StackOverflow的答案如thisthis,文章如this等。而且他们都指向同一个低级黑客:

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle = cp.ExStyle | 0x20; 
            return cp;
        }
    }

为了实现这一点,我们需要禁用绘制背景,但当然,如果我们设置"透明"背景,因为那个绘画逻辑已被压制。 (奇怪的是,它也可以通过在Opaque中设置ControlStyles选项来抑制绘制背景;虽然这听起来与我们想要的相反,但似乎也可以起作用。)

好。 几乎有效。精灵与他们自己和背景相结合。但现在发生了一件非常奇怪的事情:绘图顺序错误,甚至不一致。以不同方式使窗口无效(调整大小与最小化和恢复)将在视觉上将不同的Sprite带到前面;但一般来说,它与点击的内容和顺序不对应。如果我们添加显式的失效逻辑:

    // in click event handler
    Parent.Invalidate(this.Bounds, true);

然后点击精灵实际上会在视觉上将其发送到后面 - 尽管如果我们调整窗口大小或最小化并恢复它,绘图顺序可能会改变。


是什么给出的?我怎样才能一劳永逸地解决这个问题?我考虑的选项:

  • 将控件烧到地上;使父控件跟踪精灵列表Images,跟踪鼠标拖动和单击事件,并自行完成所有拾取和渲染逻辑。应该保证工作,但这是一项荒谬的工作量。

  • 通过覆盖背景和前景绘制使控件完全不绘制,并让它们使用所需的图像显示属性。让父母处理所有的渲染,但是回归内置的控制逻辑,用于拾取,鼠标拖动等。减少工作,但似乎不确定(可能还有一些隐藏的东西,仍然迫使控件绘制一些东西?) ,是一种相当紧密耦合的设计,如果采用一种天真的方法来实现无效(?),可能仍会出现性能问题。

1 个答案:

答案 0 :(得分:0)

经过更多的研究,我似乎有了答案。


首先,关于绘制顺序的一些解释:据我现在所知,在尝试4中是一致,但通过调整窗口大小而失效会导致不同区域逐渐失效,从而产生误导性结果。顺序始终从上到下,这与您想要进行合成的顺序相反,但是当您通过剪切无效区域将所有内容视为不透明时,它会启用优化。 (我不确定这可以节省多少CPU工作量,因为剪切路径可能会变得非常复杂,但我想当你没有使用双缓冲时它也有助于减少闪烁,因为你避免重新绘制任何给定刷新的给定像素。)

但实际情况是,如果您在行之间阅读,则discover that在"扩展窗口样式中会有另一个标志" (我们在尝试4中设置的0x20标志是WS_EX_TRANSPARENT),称为WS_EX_COMPOSITED (value 0x02000000),它将绘图顺序反转为我们想要的。或者你可以在第一时间更仔细地阅读文档。糟糕。

(你认为他们可以设计它来自动处理所有这些逻辑 - 检测哪些精灵具有半透明或透明的背景颜色,首先从顶部向下绘制具有不透明背景的精灵以获得速度,然后其他人自下而上正确,同时剪掉不透明的。哦,好吧。)

这里有一个快速说明:因为合成自然涉及多次重新绘制同一区域,这个标志还设置了双缓冲,适用于使用该标志创建的窗口的所有子项(据我所知,在Windows API中)用语,每个控件都是一个"窗口"以及表单本身)。所以它似乎people often use it to avoid flickering on forms with lots of controls,即使它们并不打算让控件重叠。但是,具有讽刺意味的是,WS_EX_COMPOSITED可能实际上导致 huge amounts of flickering某些控件,例如TabPage。 (我正在运行8.1并且能够非常清楚地重现所描述的问题。)文档陈述

  

如果窗口具有CS_OWNDC或CS_CLASSDC的类样式,则不能使用此方法。

所以也许这与它有关。我们可以通过限制"复合区域来避免这种情况。使用简单控件(例如Panel)到区域。至少在我的测试中,只要TabPage不是Panel的孩子,就会解决问题,即使它重叠了。

似乎这可能不适用于XP,但嘿,现在是2014年。


无论如何,代码。

我们需要做的是在Sprite的某些祖先中设置一个CreateWindowEx标记,使用相同的CreateParams技术,例如表格'继续:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle = cp.ExStyle | 0x02000000;
        return cp;
    }
}

我们仍然需要 WS_EX_TRANSPARENT设置,并在Sprite控件上禁用背景绘制,尝试3和4.我们需要设置透明的BackColor,因为我们不打算绘制BackColor。我不知道为什么要让完全透明的 BackColor被涂上混乱的东西,但显然是这样。

即使存在干预控制,即使我们将Sprite控件放在Panel Form上,这显然也会有效。 (如果我们想将效果限制为Panel,如上所述,我们必须继承Panel;但我的项目已经这样做了:))一切都在我的测试项目中完美运行,我不知道甚至需要明确Invalidate任何事情。