65.9K
CodeProject 正在变化。 阅读更多。
Home

TaskbarNotifiers,可调整大小的带皮肤的类似 MSN Messenger 的带视频的弹出窗口

2012 年 1 月 8 日

CPOL

12分钟阅读

viewsIcon

32094

downloadIcon

4379

可从网页调整大小的蒙皮,带视频。

引言

我一直很喜欢玩非矩形皮肤,这篇文章就是关于如何以不同方式进行皮肤创作的乐趣。John O'Byrne 在 2003 年在这里的 CodeProject 上写了一篇题为 TaskbarNotifier,一个可皮肤化的类似 MSN Messenger 的 C# 弹出窗口 的文章,这是一篇关于创建非矩形 TaskbarNotifier 的好文章。我认为可以以他的代码为起点,修复一些小错误,并添加一些附加功能,例如使用 jQuery 从网页创建皮肤以实现很酷的动画,添加非矩形视频播放器,以及通过从文件或资源加载非矩形区域并使用 XFORM 和 Transform 进行缩放来使非矩形皮肤可调整大小。与他的原始项目不同,我创建了一个 DLL,可以被其他应用程序加载和使用,这对我来说更有意义。我不是艺术家,所以一些皮肤的边缘有点粗糙,但我希望读者能理解,这篇文章只是关于区域的乐趣,而不是关于我的艺术品!

特点

类似 MSN messenger 的弹出窗口包括

  • 从 webbrowser 控件创建的皮肤 AND 从位图创建的皮肤
  • 使用 XFORM 和 Transform 使所有皮肤可调整大小
  • 使用网页作为皮肤,以获得酷炫的皮肤效果
  • 用于位图皮肤的皮肤化的 3 状态关闭按钮
  • 可点击的标题文本 & 可点击的内容文本
  • 文本不同状态(正常/悬停)的自定义字体和颜色
  • 动画速度参数
  • 在皮肤中的非矩形窗口中播放视频
  • 使用容差因子从位图快速创建区域
  • 从文件加载区域 & 动态调整区域大小
  • 将区域保存到文件(正确方式!)
  • 从嵌入式资源加载区域

DLL 和演示工作原理

我包含了一个名为“NotifierDemo”的单独项目,该项目加载了 DLL“TaskNotifier.dll”来演示如何使用此 DLL。此外,NotifierDemo 还允许您创建和保存区域到文件,以便我们可以将保存的区域用作文件或嵌入式资源。此演示中有两种类型的皮肤。第一种是仅用位图绘制的皮肤化 WinForm,即“Bmp Skin”按钮。另一种类型是带 WebBrowser 控件和媒体播放器实例的皮肤化 WinForm,即下面 NotifierDemo 屏幕中的“WebSkin”按钮,它们是“Cargo Door”、“Stone TV”和“Stargate”。下面屏幕的下半部分允许您从位图中创建区域,包括容差因子。屏幕左下角的按钮“Create Form from Region File”将从您选择的区域文件中加载区域,并将区域重置为适合右侧“Width”和“Height”字段的尺寸。我只为从屏幕右下角进行的动画滑动设置了代码,但读者可以轻松修改代码以从屏幕的任何角落滑动。需要指出的是,这些皮肤的某些资源可以作为嵌入式资源放在 DLL 中,或者可以放置在任何目录中。在此演示中,为了方便起见,我将一些皮肤资源放在目录中,另一些作为嵌入式资源,以说明这两种方法。

TaskbarNotifiers/TaskbarNotifiers2.jpg

 

使用容差从位图创建区域

 

您可以找到数十个从位图创建区域的示例。我在 C# 中使用了以下方法,其中包括使用“容差因子”来帮助平滑粗糙的曲线。这里的速度不是关键,因为我们将只使用我们皮肤中创建的区域,而不是动态地从位图中创建区域。当我刚开始写这篇文章时,我通过先调整位图图像的大小,然后从调整大小后的位图中重新创建区域来调整皮肤大小——如果您更喜欢这种方法,这也很好。我个人更喜欢创建区域并从文件或嵌入式区域资源调整区域大小,这似乎稍微快一些。

        public Region getRegion(Bitmap inputBmp, Color transperancyKey, int tolerance)
        {
            // Stores all the rectangles for the region
            GraphicsPath path = new GraphicsPath();

            // Scan the image
            for (int x = 0; x < inputBmp.Width; x++)
            {
                for (int y = 0; y < inputBmp.Height; y++)
                {
                    if (!colorsMatch(inputBmp.GetPixel(x, y), transperancyKey, tolerance))
                        path.AddRectangle(new Rectangle(x, y, 1, 1));
                }
            }

            // Create the Region
            Region outputRegion = new Region(path);

            // Clean up
            path.Dispose();

            return outputRegion;
        }

        private static bool colorsMatch(Color color1, Color color2, int tolerance)
        {
            if (tolerance < 0) tolerance = 0;
            return Math.Abs(color1.R - color2.R) <= tolerance &&
                   Math.Abs(color1.G - color2.G) <= tolerance &&
                   Math.Abs(color1.B - color2.B) <= tolerance;
        }

将区域保存到文件

正确保存区域文件并不简单。我想您在任何地方都找不到 C# 中正确保存区域到文件的示例代码。可能是因为在 C# 中将区域保存到文件有点棘手,因为获取组合区域数据和区域头的所有方法都接受一个指向区域结构的指针。由于此项目中的皮肤都从文件或嵌入式资源加载区域,因此我们需要能够在 C# 中将从位图创建的区域保存到文件,就像我在 Bmp2Rgn.cs 中所做的那样。在 C# 中有其他不使用不安全指针的方式来将区域保存或序列化到文件,但这只是我偏爱的方式。

         // Create a region called "myRegion" by some means and pass the
        // handle to the region, i.e., Hrgn, to "SaveRgn2File" like so:
        using (Graphics g = this.CreateGraphics())
            SaveRgn2File(myRegion.GetHrgn(g), sSaveRgnFile);

        [SuppressUnmanagedCodeSecurity()]
        public unsafe void SaveRgn2File(IntPtr hRgn, string sSaveRgnFile)
        {
            Win32.RECT[] regionRects = null;
            IntPtr pBytes = IntPtr.Zero;
            try
            {
                // See how much memory we need to allocate
                int regionDataSize = Win32.GetRegionData(new HandleRef(null, hRgn), 0, IntPtr.Zero);
                if (regionDataSize != 0)
                {
                    pBytes = Marshal.AllocCoTaskMem(regionDataSize);
                    // Get the pointer, i.e., pBytes, to BOTH the region header AND the region data!
                    int ret = Win32.GetRegionData(new HandleRef(null, hRgn), regionDataSize, pBytes);
                    if (ret == regionDataSize) // make sure we have RDH_RECTANGLES
                    {
                        // Cast to the structure
                        Win32.RGNDATAHEADER* pRgnDataHeader = (Win32.RGNDATAHEADER*)pBytes;
                        if (pRgnDataHeader->iType == 1)  // Make sure we have RDH_RECTANGLES
                        {
                            using (FileStream writeStream = new FileStream(sSaveRgnFile, FileMode.Create, FileAccess.ReadWrite))
                            {
                                WriteToStream(writeStream, (void*)pBytes, (uint)ret);
                                writeStream.Close();
                            }
                        }
                    }
                }
            }
            finally
            {
                if (pBytes != IntPtr.Zero)
                {
                    Marshal.FreeCoTaskMem(pBytes);
                }
            }
        }

        [SuppressUnmanagedCodeSecurity()]
        public unsafe static void WriteToStream(FileStream output, void* pvBuffer, uint length)
        {
            IntPtr hFile = output.SafeFileHandle.DangerousGetHandle();
            WriteToStream(hFile, pvBuffer, length);
            GC.KeepAlive(output);
        }

        [SuppressUnmanagedCodeSecurity()]
        public unsafe static void WriteToStream(IntPtr hFile, void* pvBuffer, uint length)
        {
            if (hFile == NativeConstants.INVALID_HANDLE_VALUE)
                throw new ArgumentException("output", "File is closed");

            void* pvWrite = pvBuffer;

            while (length > 0)
            {
                uint written;
                bool result = SafeNativeMethods.WriteFile(hFile, pvWrite, length, out written, IntPtr.Zero);

                if (!result)
                    return;

                pvWrite = (void*)((byte*)pvWrite + written);
                length -= written;
            }
        }
     

我创建皮肤的方法是让用户通过在皮肤的右下角提供一个“抓手区域”来动态设置任何皮肤的宽度和高度,用户可以拖动该区域来调整皮肤大小。下面的方法将一个区域文件转换为一个使用 XFORM & Transform 的缩放区域,并为给定的句柄设置一个对象区域。我遇到的一个问题是,我发现我必须在调用“SetBounds”并为视频播放器设置区域之前删除 AnchorStyles,然后我必须在之后将 AnchorStyles 重置为“Top”和“Left”。

         // Converts region file to scaled region using XFORM & Transform & sets region to object given it's handle
        private void File2RgnStretch(System.IntPtr hWnd, string strRgnFile, int bmpWidth, int bmpHeight, int rgnWidth, int rgnHeight)
        {
            using (FileStream fs = new FileStream(strRgnFile, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                byte[] regionData = null;
                BinaryReader reader = new BinaryReader(fs);
                regionData = reader.ReadBytes((int)fs.Length);
                using (Region region = Region.FromHrgn(ExtCreateRegion(0, regionData.Length, regionData)))
                {
                    // The bounding rectangle of a region is usually smaller than the WinForm's height and width so we use the size 
                    // of the default bitmap we paint the WinForm's background with to calculate the xScale and yScale values
                    float xScale = (float)rgnWidth / (float)bmpWidth;
                    float yScale = (float)rgnHeight / (float)bmpHeight;

                    Win32.XFORM xForm;
                    xForm.eDx = 0;
                    xForm.eDy = 0;
                    xForm.eM11 = xScale;
                    xForm.eM12 = 0;
                    xForm.eM21 = 0;
                    xForm.eM22 = yScale;

                    // Scale the region
                    region.Transform(xForm);

                    Graphics g = this.CreateGraphics();
                    IntPtr hRgn = region.GetHrgn(g);
                    //IntPtr hDC = g.GetHdc();
                    if (this.Handle == hWnd)  // Set the WinForm Region
                    {
                        // Get the Bounding Rectangle for this region
                        RectangleF rect = region.GetBounds(g);
                        rectSkin.Width = Convert.ToInt32(rect.Width) + 30;
                        if((ZSkinName == "skin1") || (ZSkinName == "skin2") || (ZSkinName == "skin3"))
                            rectSkin.height = Convert.ToInt32(rect.Height);
                        else
                            rectSkin.height = Convert.ToInt32(rect.Height) + 10;

                        SetWindowRgn(hWnd, hRgn, false);
                    }
                    else if (dShowPlayer1.Handle == hWnd)  // Set the Video Player Region
                    {
                        rectVideo = new Win32.RGNRECT();
                        rectVideo.x1 = 0;
                        rectVideo.y1 = 0;
                        rectVideo.Width = this.Width;
                        rectVideo.Height = this.Height;
                        dShowPlayer1.RectVideo = rectVideo;
                        dShowPlayer1.Visible = true;

                        // Remove AnchorStyles before calling "SetBounds"
                        dShowPlayer1.Anchor = AnchorStyles.None;
                        dShowPlayer1.Dock = DockStyle.None;
                        dShowPlayer1.SetBounds(rectVideo.x1, rectVideo.y1, rectVideo.Width, rectVideo.Height);
                        Win32.SetWindowRgn(dShowPlayer1.Handle, hRgn, true);
                        // Set AnchorStyles
                        dShowPlayer1.Anchor = AnchorStyles.Top | AnchorStyles.Left;
                    }
                    else                   
                        SetWindowRgn(hWnd, hRgn, false); // Set the WebBrowser's Region

                    // Clean Up
                    region.ReleaseHrgn(hRgn);

                    //g.ReleaseHdc(hDC);
                    g.Dispose();
                }
            }
        }

        // Note: To load a region from a resourse we use:
        global::System.Resources.ResourceManager rm = 
        new global::System.Resources.ResourceManager("TaskNotifier.Properties.Resources", typeof(Resources).Assembly);
        regionData = (byte[])rm.GetObject(strRgnRes);

WebBrowser 皮肤的基本布局

下面是 WebBrowser 皮肤的基本结构和布局。它由一个带有停靠在窗体上的 WebBrowser 控件的 WinForm 组成。然后我们设置一个区域,该区域是 WinForm 整体形状的“饼干切割器”,如下图所示。下面的门只是一个普通的网页,位于非矩形区域内的 wbbrowser 控件中。对于“Cargo Door”皮肤,门是一个 html 文件,门本身由大量使用纯 JavaScript 动画的小图像组成。

有两种主要方法可以在 WebBrowser 皮肤上创建抓手区域。一种技术是将抓手区域的图像放在皮肤网页的单独图层中,并将其放置在您想要的位置,并像拖动 Web 皮肤一样在 C# 代码中触发回调。但我决定采用一种也可以用于非 WebBrowser 皮肤的方法,即直接在 WinForm 的背面绘制抓手区域并在调整窗体大小时重新绘制抓手。为了让用户能够点击 WinForm 上绘制的抓手区域,有必要从 WebBrowser 的区域中切出一个区域,以便下面的抓手区域可见,如下图所示。

TaskbarNotifiers/TaskbarNotifiers3.jpg

TaskbarNotifiers/TaskbarNotifiers4.gif

 

调整皮肤大小抓手图像的缩放

当我们首次创建皮肤时,我们根据窗体的大小与我们将用于绘制窗体背景的位图的大小来计算 sizeFactorX 和 sizeFactorY。然后,我们将这些比例应用于正确调整我们抓手图像的大小。抓手的位置也根据这些比例重新计算,如下所示。

     // Note: In the method UpdateSkin() we calculate the Scale for the width and height as follows:
    sizeFactorX = (double)this.Size.Width / backBmap.Size.Width;
    sizeFactorY = (double)this.Size.Height / backBmap.Size.Height;

    public void SetGripBitmap(Image image, Color transparencyColor, Point position)
    {
        dGripBitmapW = (double)image.Width * (double)sizeFactorX;
        dGripBitmapH = (double)image.Height * (double)sizeFactorY;
        dGripBitmapX = (double)position.X * (double)sizeFactorX;
        dGripBitmapY = (double)position.Y * (double)sizeFactorY;

        GripBitmap = null;
        GripBitmap = new Bitmap(Convert.ToInt32(dGripBitmapW), Convert.ToInt32(dGripBitmapH));
        Graphics gr = Graphics.FromImage(GripBitmap);
        gr.SmoothingMode = SmoothingMode.None;
        gr.CompositingQuality = CompositingQuality.HighQuality;
        gr.InterpolationMode = InterpolationMode.HighQualityBilinear;
        gr.DrawImage(image, new Rectangle(0, 0, Convert.ToInt32(dGripBitmapW), Convert.ToInt32(dGripBitmapH)), new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);

        gr.Dispose();

        GripBitmap.MakeTransparent(transparencyColor);
        GripBitmapSize = new Size(GripBitmap.Width, GripBitmap.Height);
        GripBitmapLocation = new Point(Convert.ToInt32(dGripBitmapX), Convert.ToInt32(dGripBitmapY));
    }

缩放/放大显示 HTML 皮肤的 WebBrowser 控件

由于非矩形 WebBrowser 控件包含我们的 HTML 皮肤,当用户通过拖动抓手区域来调整 WinForm 大小时,我们也必须成比例地放大 WebBrowser 控件以匹配 WinForm 的大小变化,因此我添加了必要的代码,使 WebBrowser 控件能够实现这一点,如下所示。

    [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
    public void Zoom(int zoomvalue)
    {
        if ((zoomvalue < 10) || (zoomvalue > 1000))
            return;
  
        try
        {
            // In Windows Internet Explorer 8 or higher we can call OLECMDIDF_OPTICAL_ZOOM_NOPERSIST = 0x00000001 but this is BUGGY!
            // Windows Internet Explorer 8 does not automatically persist the specified zoom percentage. 
            // But it is safer to just call extendedWebBrowser1.Zoom(100) to reset the zoom factor back to 100% when we create our skins
            this.axIWebBrowser2.ExecWB(NativeMethods.OLECMDID.OLECMDID_OPTICAL_ZOOM, NativeMethods.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, zoomvalue, System.IntPtr.Zero);
            }
        catch 
        {
                
        }
    }

在简单的位图皮肤情况下,我们可以成比例地将宽度和高度都更改为 WinForm 的新尺寸。但是,在 WebBrowser 皮肤的情况下,由于 WebBrowser 的缩放工作方式,我们受到限制,因此我们通过 x 坐标的变化来调整 WebBrowser 皮肤的大小,如下所示。

     // Original background bitmap for "Stargate" skin was: width: 372 and height: 380
    // Where dx and dy are change in WinForm width and height after dragging the Grip area
    double dHeight = (double)(380 * (this.Width + dx)) / (double)372;

    // Change the WinForm size to the new dimensions
    Win32.SetWindowPos(this.Handle, (System.IntPtr)Win32.HWND_TOPMOST, this.Location.X, this.Location.Y, 
	this.Width + dx, Convert.ToInt32(dHeight), Win32.SWP_SHOWWINDOW);
    
    // Sets the new scaled regions and calculates the new "sizeFactorX"
    UpdateSkin(); 

    // Calculate the zoom percentage for the WebBrowser
    double dZoom = 100 * (double)sizeFactorX;

    // Zoom the WebBrowser Control display of our html skin
    extendedWebBrowser1.Zoom(Convert.ToInt32(dZoom)); 

    // Scale our painted Grip area to new dimensions
    SetGripBitmap((Bitmap)Resources.stargate_grip, Color.FromArgb(255, 0, 255), new Point(270, 320));

使用 DirectShowNet 包装器播放视频

正如我在文章开头提到的,我包含了 DirectShow 的 C# 包装器,即 DirectShowLib-2005.dll,以便用户可以在弹出窗口中播放视频消息。如果您不想播放视频,可以删除此引用。添加视频有两种方法,我添加了一个我使用包装器创建的 C# 视频播放器实例。下面是一个皮肤化 WinForm,它由一个 WebBrowser 控件和一个媒体播放器实例组成,该播放器只是 DirectShowLib-2005.dll 的包装器。您可以在 http://directshownet.sourceforge.net/ 下载此 DLL 及其源代码。

WebBrowser 控件停靠在没有边框的父 WinForm 上。您看到的实际皮肤不是在 WinForm 上绘制的位图,如仅使用 WinForm 的皮肤“Bmp Skin 1”、“Bmp Skin 2”和“Bmp Skin 3”的情况。对于 WebSkins,皮肤只是 WebBrowser 控件中的一个普通 html 网页,该控件停靠在父 WinForm 上。下面是 WebBrowser 皮肤图层的插图。我们可以为视频添加额外的缩放,但这篇论文只是关于区域的乐趣,这会有点太多了!


TaskbarNotifiers/TaskbarNotifiers5.jpg


 

如果用户右键单击视频播放器区域,我会添加一个上下文菜单,允许用户全屏播放视频,因此我们需要订阅视频播放器的“ToggleChange”事件,以便我们可以处理用户从全屏返回到播放器正常大小的时间。在此事件中,我们需要调用“SetSkin()”来重建皮肤,如下所示。

    // dShowPlayer1.ToggleChange += new ZToggleFullScreenHandler(dShowPlayer1_ToggleChange);
    void dShowPlayer1_ToggleChange(object o, ZToggleEventArgs e)
    {
        if (!e.ZIsFullScreen)
        {
            dShowPlayer1.menuFileClose_Click(this, null);
            SetSkin(ZSkinName, true);
        }
    }    

简单的位图皮肤的详细信息

     SetBackgroundBitmap((Bitmap)Resources.skin1, Color.FromArgb(255, 0, 255));;0, 255));;
    SetCloseBitmap((Bitmap)Resources.close1, Color.FromArgb(255, 0, 255), new Point(220, 8));
    TitleRectangle = new Rectangle(60, 8, 70, 25);
    ContentRectangle = new Rectangle(60, 8, 150, 140);

第一行从嵌入式资源位图中设置背景位图皮肤和透明色,第二行设置可选的 3 状态关闭按钮及其透明色和在窗口中的位置。这两行允许我们定义将在其中显示标题和内容文本的矩形。您可以为简单的位图皮肤设置这些属性。

  • 标题、内容字体和颜色
  • 可点击或不可点击的标题/内容/关闭按钮
  • 您可以禁用焦点矩形
  • 将简单的位图皮肤弹出窗口定位在屏幕上的任何位置
void SetCloseBitmap(string strFilename, Color transparencyColor, Point position)

为我们的普通位图皮肤设置 3 状态关闭按钮位图、其透明色和坐标。

参数

  • strFilename:磁盘上 3 状态关闭按钮位图的路径,其中宽度必须是 3 的倍数
  • transparencyColor:位图中不可见的颜色
  • position:弹出窗口中关闭按钮的位置
  void SetCloseBitmap(Image image, Color transparencyColor, Point position)

设置 3 状态关闭按钮位图、其透明色和坐标。

参数

  • imageImage/Bitmap 对象,表示 3 状态关闭按钮位图(宽度必须是 3 的倍数)
  • transparencyColor:位图中不可见的颜色
  • position:弹出窗口中关闭按钮的位置

所有皮肤类型的属性

     string TitleText (get/set)
    string ContentText (get/set)
    TaskbarStates TaskbarState (get)
    Color NormalTitleColor (get/set)
    Color HoverTitleColor (get/set)
    Color NormalContentColor (get/set)
    Color HoverContentColor (get/set)
    Font NormalTitleFont (get/set)
    Font HoverTitleFont (get/set)
    Font NormalContentFont (get/set)
    Font HoverContentFont (get/set)
    Rectangle TitleRectangle (get/set) //must be defined before calling show())
    Rectangle ContentRectangle (get/set) //must be defined before calling show())
    
    bool TitleClickable (get/set) (default = false);
    bool ContentClickable (get/set) (default = true);
    bool CloseClickable (get/set) (default = true);
    bool EnableSelectionRectangle (get/set) (default = true);

SkinDlg 类文档

方法

public void Show(string strAction, string strTitle, string strContent, int nTimeToShow, int nTimeToStay, int nTimeToHide)

 我在原始代码中添加了一个“Action”参数,即 strAction,用于指示弹出窗口启动时应该执行的操作,其他参数设置标题、内容和显示弹出窗口的时间。

参数

  • strAction:字符串决定弹出窗口的操作,即屏幕位置和滑动/无滑动
  • strTitlee:将显示为弹出窗口标题的字符串
  • strContent:将显示为弹出窗口内容的字符串
  • nTimeToShow:显示动画的持续时间(以毫秒为单位)
  • nTimeToStay:在折叠之前可见状态的持续时间(以毫秒为单位)
  • nTimeToHide:隐藏动画的持续时间(以毫秒为单位)
void Hide()

 这会隐藏弹出窗口。

绘制背景

弹出窗口的refresh() 使用了 John O'Byrne 原始文章中的双缓冲技术,以避免闪烁,并进行了一些小的更改。

    protected override void OnPaintBackground(PaintEventArgs e)
   {
        if (m_alphaBitmap != null)
        {
            Graphics grfx = e.Graphics;
            grfx.PageUnit = GraphicsUnit.Pixel;

            Graphics offScreenGraphics;
            Bitmap offscreenBitmap;

            offscreenBitmap = new Bitmap(m_alphaBitmap.Width, m_alphaBitmap.Height);
            offScreenGraphics = Graphics.FromImage(offscreenBitmap);

            if (m_alphaBitmap != null)
                offScreenGraphics.DrawImage(m_alphaBitmap, 0, 0, m_alphaBitmap.Width, m_alphaBitmap.Height);

            DrawGrip(offScreenGraphics);
            DrawCloseButton(offScreenGraphics);
            DrawText(offScreenGraphics);

            grfx.DrawImage(offscreenBitmap, 0, 0);

            // The bitmap and "offScreenGraphics" object should be disposed.
            // BUT, The grfx object should NOT be disposed. 
            offScreenGraphics.Dispose();
            offscreenBitmap.Dispose();

        }
    }

此外,为了避免闪烁,我还添加了

this.SetStyle(System.Windows.Forms.ControlStyles.DoubleBuffer, true);
this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, false); 
this.SetStyle(System.Windows.Forms.ControlStyles.ResizeRedraw, true); 
this.SetStyle(System.Windows.Forms.ControlStyles.UserPaint, true); 
this.UpdateStyles();

创建阴影

读者可以调整代码并轻松更改弹出窗口的工作方式。例如,您可以将 html 和图像嵌入 DLL 中并将其作为嵌入式资源加载。您会注意到弹出窗口是使用 Win32 函数 ShowWindow(SW_SHOWNOACTIVATE) 显示的,以防止弹出窗口抢占焦点。

为了添加漂亮的阴影,我们需要创建一个每像素 32 位并带有 alpha 通道的位图,并将阴影作为图像本身的一部分添加到位图中。但为了添加轻微的阴影,我们只需添加下面的代码。

// Adds a slight dropshadow to our skin
    private const int WS_THICKFRAME = 0x40000;
    private const int WS_CAPTION = 0xC00000;
    private const int CS_DROPSHADOW = 0x20000;
    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ClassStyle |= CS_DROPSHADOW;
            cp.Style = cp.Style & ~WS_CAPTION;
            cp.Style = cp.Style & ~WS_THICKFRAME;
            return cp;
        }
    }

拖动皮肤

有许多方法可以拖动没有标题栏的非矩形窗体,但我喜欢保持简单,所以我使用了下面的代码。对于简单的位图皮肤,我们重写 OnMouseDown,并确保我们不在绘制的抓手区域上方,如下所示。

    // Dragging is achieved with the following in OnMouseDown(MouseEventArgs e)
    Win32.ReleaseCapture();
    Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0);

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        bIsMouseDown = true;

        if (e.Button == MouseButtons.Left)
        {
            if (bIsMouseOverClose)
            {
                Refresh();
            }
            else if (!bIsMouseOverGrip)
            {
                Win32.ReleaseCapture();
                Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0);
            }
            else if (bIsMouseOverGrip)
            {
                nMouseStartX = e.X;
                nMouseStartY = e.Y;
            }
        }
    }

对于我们的 WebBrowser 皮肤,我在这类皮肤中使用了两种主要方法,并且效果都很好。第一种方法是在 JavaScript 中放置一个链接,如下所示,这样当用户点击 HTML 中被指定为框架图像一部分的图像时,我们会导航到“EVENT:DRAG”,并在 C# 的“Navigating”事件中捕获它,如下所示。

    // METHOD #1. Dragging The Skin Using The Navigating Event in Our WebBrowser Control

    // On the Click event of a drag image in Javascript we call the function dragimage()
    <script language="javascript" type="text/jscript"> 
        function dragimage(){window.location.href ="EVENT:DRAG";}
    </script>

    // In C# we capture this Navigate event from our Javascript in the C# Navigating event
    private void extendedWebBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
    {
        string csTestUrl = string.Empty;
        string csEvent = string.Empty;
        string csAction = string.Empty;
        string csData = string.Empty;
        string csQuestionMark = string.Empty;
        char[] delimiterChars = { ':' };
        char[] delimiterChars2 = { '?' };
        try {
            csTestUrl = e.Url.ToString();
            string[] words = csTestUrl.Split(delimiterChars, StringSplitOptions.None);
            if (words.Length > 1) {
                csEvent = words[0];
                csAction = words[1];
            }
            if (words.Length > 2) csData = words[2];
            string[] words2 = csTestUrl.Split(delimiterChars2, StringSplitOptions.None);
            if (words2.Length > 1) csQuestionMark = words2[1];
        }
        catch { }

        csEvent = csEvent.ToUpper();

        if (csEvent != "EVENT") { }
        else
        {
            try
            {
                csAction = csAction.ToUpper();
                if (csAction == "DRAG")
                {
                    e.Cancel = true;
                    Win32.ReleaseCapture();
                    Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0);
                }
            }
            catch { e.Cancel = true; }
        }
    }

我还在示例项目中使用的另一种方法效果也一样好,那就是使用 JavaScript 中的“window.external”并创建一个“External”类,该类调用我们皮肤中的“SendDragData”方法,如下所示。

    // METHOD #2. Dragging The Skin Using "window.external" in Javascript

    // On the Click event of a drag image in Javascript we call window.external in the function dragimage()
    <script language="javascript" type="text/jscript"> 
        function HandleDrag(a) { window.external.SendDragData("EVENT:DRAG"); }
    </script>

    
    // In C# External Class we call SendDragData and pass the data, namely, "EVENT:DRAG", to our skin window
    [System.Runtime.InteropServices.ComVisibleAttribute(true)]
    public class External
    {
        private static SkinDlg m_mainWindow = null;
        public void SendDragData(string zdata)
        {
            m_mainWindow.SendDragData(zdata);
        }
    }

    // In our skin we receive the data, namely, "EVENT:DRAG", sent from our External Class
    public void SendDragData(string zdata)
    {
        Win32.ReleaseCapture();
        Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0);
    }

结论

基本上,我着手看看皮肤的调整大小效果如何,整体效果还不错。本文的目的是只是玩转皮肤,享受乐趣。

© . All rights reserved.