介绍 所提供的代码是对我自己的气球对话框的概念验证。我将代码保持在最低限度,因为我想演示以下内容: 如何使用一个窗体的区域属性来定义一个自定义窗体形状 简单的生成一个阴影的气球形式 使用表单的所有者属性 使用GraphicsPath对象生成区域 将一种形式的事件链接到另一种形式的处理程序 没有WndProc拦截或钩子。参考以前的气球提交,其中充分涵盖了这些主题。 编译程序或运行可执行文件! 运行该程序,将出现一个带有‘press me’标签的静态气球。单击标签会弹出一个包含文本框的普通表单。文本框用作气球的锚点。按下“打开气球”按钮,我无耻的自我形象就会出现。 您可以拖动表单,如果幸运的话,气球也会跟随。尾巴的位置和方向应该改变,以适应屏幕内的气球。如果你最小化形式,气球也应该最小化。通过单击“按下我”标签或单击窗体表面来关闭气球。在气球内点击阴影或控制面不会起作用(这就是为什么你可能要考虑使用钩子的原因之一) 这是怎么做到的呢? 所有重要的代码都驻留在CBalloonBase类中。开始时显示的静态气球是这个基类的一个实例,而我的示例气球(CExample)只是一个子类,没有任何值得注释的额外代码。CExample的视觉外观(图片框、标签等)是通过设置属性和使用IDE的Designer视图放置控件生成的。 建立气球的自定义形状 在我的第一次尝试中,我创建了一个GraphicsPath,填充了四条弧线和创建气球轮廓所需的直线。画在表面上,如窗体或图片框,轮廓看起来很完美。但是当我尝试使用GraphicsPath对象构造一个区域时,结果却远远不能令人满意。 我的第二次尝试简单得多。一个区域本质上是一个相互作用的矩形的集合。所以我创建了一个区域,用一系列重叠的矩形来代表气球的主体。气球的尾巴是使用GraphicsPath对象创建的。气球区域通过合并(与区域。union (GraphicsPath)方法)尾部来完成。隐藏,收缩,复制Code
GraphicsPath m_path = new GraphicsPath(); // generate balloon tail outer path (pA, pB, pC // are pre-calculated points) m_path.AddLines(new Point[] { pA, pB, pC } ); // generate the outer frame (m_rOuterFrame) // which becomes the shape of the balloon form Size pSize = new Size(nW - (nInner + nInner), nH - (nOuter + nOuter)); Point pLoc = new Point(nInner, nOuter); Rectangle aRect = new Rectangle(pLoc, pSize); m_rOuterFrame = new Region(aRect); // generate 2nd pass region for the outer frame, // starting to form the rounded edges pSize.Width += 4; pSize.Height -= 2; pLoc.X -= 2; pLoc.Y += 1; m_rOuterFrame.Union(new Rectangle(pLoc, pSize)); // generate 3rd pass for the outer frame, developing rounded edges pSize.Width += 2; pSize.Height -= 2; pLoc.X -= 1; pLoc.Y += 1; m_rOuterFrame.Union(new Rectangle(pLoc, pSize)); // generate 4th pass for the outer frame, developing rounded edges pSize.Width += 2; pSize.Height -= 2; pLoc.X -= 1; pLoc.Y += 1; m_rOuterFrame.Union(new Rectangle(pLoc, pSize)); // generate 5th pass for the outer frame, completing the rounded edges pSize.Width += 2; pSize.Height -= 4; pLoc.X -= 1; pLoc.Y += 2; m_rOuterFrame.Union(new Rectangle(pLoc, pSize)); // generate 6th pass for the outer frame, adding the balloon tail m_rOuterFrame.Union(m_path); // set the region for the form this.Region = m_rOuterFrame;
第二种方法非常有效。最后一步是将气球区域(参见上面的最后一个代码语句)分配给表单的Region属性。CBalloonBase本身在运行时之前不会改变形状,但是在IDE的设计器中,任何子类都将作为一个外观时髦的气球出现。 做气球的框架 制定大纲才是真正的技巧。内部框架的组成方式与轮廓大致相同。首先克隆轮廓区域,然后从克隆中排除客户区域和内部尾部路径。剩下的是框架,我们将使用渐变笔刷在reDrawMe()函数中绘制它。 建筑的阴影 阴影被实现为一个表单对象,在bMakeShadow()函数的CBalloonBase中生成。这是和气球本身一样的形式,但是它没有框架,使用固定的背景色(color . darkgray),阴影形式的不透明度设置为0.6(或者60%,如果你喜欢的话)。我也选择用阴影的形式来掩盖气球的轮廓。我不确定这是否会带来实时的改进,但是它在运行时看起来确实更好。复制Code
// for shadow windows we don't need to paint the // area under the foreground balloon if (m_bIsShadow == true) { Region rgnEx; rgnEx = m_rOuterFrame.Clone(); // the shadow is offset five pixels down and to the right rgnEx.Translate(-5, -5); m_rOuterFrame.Exclude(rgnEx); }
设置所有权 因为你们都很聪明,你们已经知道Owner Property保证了所有的表单永远不会出现在它的Owner后面。同样,如果最小化了所有者,那么所拥有的表单也会最小化。因此,气球为阴影所拥有,而阴影为包含锚点的形式所拥有。 唯一的问题是阴影在我的气球实现中是可选的(由CBalloonBase中的一个属性关闭)。在这种情况下,气球直接由具有锚的窗体拥有。设置所有权的简单过程节省了大量代码!不好的一面是,您确实应该记得在解除关系时从所有者那里移除ownedform()(参见CBalloon_Closing()和DestroyShadow())。 围绕着移动事件的微弱魔法 当包含锚点的表单移动时会发生什么?气球是如何知道随着锚的形式移动的?秘密在于向锚表单的Move事件添加事件处理程序。建立锚点的调用是setBalloonPosition(),它总是包含锚点窗体的句柄。(锚本身可以是一个点,也可以是一个控件)。隐藏,复制Code
// entry point creates an anchor control point // (onControl) within an anchoring form (onForm) public void setBalloonPosition(Form onForm, Control onControl) { // determine ownership so the balloon stays ontop of the owning form if (m_fmShadow == null) { this.Owner = onForm; } else { m_fmShadow.Owner = onForm; } // remember the control's position and dimensions (snip!) // (end of snip) attach a callback so we know when // the owner is moved. Oh the joys and advantages of delegates! m_evhMove = new System.EventHandler(this.Parent_Move); onForm.Move += m_evhMove; }
CBalloonBase只是在移动事件中向锚点的表单添加一个事件处理程序(委托),然后在锚点的表单被移动时收到自动通知。更复杂的版本还可以在其父窗体中跟踪锚点!啊,代表们的欢乐! 同样,当气球被取消时,事件处理程序应该从锚的表单中删除。否则,对气球对象的引用将在锚的形式的生命周期中被维护。垃圾收集将有困难关闭气球,即使它是不可见的! 属性 我添加了几个属性。它们是tailOffset, tailAlign, tailSide, IsShadow, FrameTopLeft, FrameBottomRight和HasShadow。在这些选项中,您应该只设置FrameTopLeft、FrameBottomRight和HasShadow。 尾距、尾对齐和尾侧在设计时很有趣,但是当锚定到控件时,代码本身将决定所有这三个属性,而不管设计时的设置如何。 其余的属性IsShadow永远不能使用。这个属性,以及其他一些属性,比如FormBorderStyle(必须设置为“None”),都应该从设计器的属性窗口中删除。 HasShadow确定气球是否有阴影(显然) 还有什么? 我打算用这段代码来说明什么是可能的,我省略了一个托管应用程序,因为它不需要。在进行了小的修改之后,您可以将CBalloonBase表单包含在您的项目中,并将子类包含在您的核心内容中。如果你有时间和精力,你甚至可以自定义控件。在开始学习这门课程之前,我建议你阅读Eric White和他的公司写的“GDI+编程:使用c#创建自定义控件”。它很简洁(不像我),非常有帮助。 已知的错误包括对函数reSizeMe()的调用过多,这可以通过适当的设计技术来删除(我在这个项目中削减了我的GDI+功能,所以我没有使用任何技术)。 在自定义控制版本中(不幸的是我不能发布这个版本),气球有一个“渐入渐退”功能,这使它的外观和消失更令人愉快。我还建议改善框架的视觉外观,特别是尾巴本身。玩得开心! 本文转载于:http://www.diyabc.com/frontweb/news6738.html