1. 程式人生 > >C# WPF QQ新訊息托盤懸浮窗效果實現

C# WPF QQ新訊息托盤懸浮窗效果實現

原文: C# WPF QQ新訊息托盤懸浮窗效果實現

今天在做一個專案的時候需要這麼一個效果,但是網上找了一會發現並沒有現成的給我參考(複製),但是呢,我千(到)辛(處)萬(抄)苦(襲)想(復)破(制)頭(粘)腦(貼)終於還是給做出來了~嘿嘿嘿

QQ新訊息懸浮窗即:QQ有新訊息時托盤圖示會閃動,此時移動滑鼠到托盤圖示上就會顯示一個彈框了,那麼呢我把這個彈框稱為“QQ新訊息托盤懸浮窗”。當滑鼠從托盤圖示移走後彈框隱藏,我們要做的效果就是這樣的。

專案效果圖:

涉及到的內容主要有:Popup,win32api,DispatcherTimer(定時器)。

新建wpf專案,命名為qqnotifybar

MainWindow.xaml程式碼:

<Window x:Class="qqnotifybar.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:qqnotifybar" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Grid> <Popup Name="NotifyBar" AllowsTransparency="True" Placement="Absolute"> <Grid Name="NotifyBar_Grid" Width="285" Height="126"
Background="Red"> <!--為了能更清晰地看到托盤圖示是否在彈框的中間位置加一個Grid--> <Grid Background="Yellow" Width="5" Height="126" HorizontalAlignment="Center"></Grid> </Grid> </Popup> </Grid> </Window>

後臺程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace qqnotifybar
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {

        //winform托盤圖示元件
        NotifyIcon notifyicon;

        //計時器
        DispatcherTimer notifybar_timer;

        //滑鼠是否在通知欄內變數
        bool NotifyBar_IsMouseEnter = false;

        public MainWindow()
        {

            InitializeComponent();

            //初始化托盤圖示
            notifyicon = new NotifyIcon();
            notifyicon.Icon = System.Drawing.Icon.ExtractAssociatedIcon(System.Windows.Forms.Application.ExecutablePath);
            notifyicon.Text = "Dove";
            notifyicon.Visible = true;
            notifyicon.MouseMove += notifyicon_mousemove;
   
            //初始化計時器
            notifybar_timer = new DispatcherTimer();
            notifybar_timer.Interval = TimeSpan.FromSeconds(1);
            notifybar_timer.Tick += notifybar_timer_tick;

            //給彈框中的grid加上滑鼠移入移出事件
            NotifyBar_Grid.MouseEnter += NotifyBar_MouseEnter;
            NotifyBar_Grid.MouseLeave += NotifyBar_MouseLeave;

        }

        #region win32api

        #region 獲取滑鼠指標相對於螢幕的座標
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;

            public POINT(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern bool GetCursorPos(out POINT pt);
        #endregion

        #region 獲取托盤圖示的位置
        //程式碼來自於:http://blog.csdn.net/haoqi9999/article/details/76527981?locationNum=6&fps=1
        public class RG
        {
            public int Left { get; set; }
            public int Top { get; set; }
            public int Widht { get; set; }
            public int Height { get; set; }

            public RG(int left, int top, int widht, int height)
            {
                Left = left;
                Top = top;
                Widht = widht;
                Height = height;
            }
            public override string ToString()
            {
                return "Left:" + Left + ",Top:" + Top + ",Width:" + Widht + ",Height:" + Height;
            }
        }
        public static RG GetIconRect(NotifyIcon icon)
        {
            RECT rect = new RECT();
            NOTIFYICONIDENTIFIER notifyIcon = new NOTIFYICONIDENTIFIER();

            notifyIcon.cbSize = Marshal.SizeOf(notifyIcon);
            //use hWnd and id of NotifyIcon instead of guid is needed
            notifyIcon.hWnd = GetHandle(icon);
            notifyIcon.uID = GetId(icon);

            int hresult = Shell_NotifyIconGetRect(ref notifyIcon, out rect);
            //rect now has the position and size of icon

            return new RG(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public Int32 left;
            public Int32 top;
            public Int32 right;
            public Int32 bottom;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct NOTIFYICONIDENTIFIER
        {
            public Int32 cbSize;
            public IntPtr hWnd;
            public Int32 uID;
            public Guid guidItem;
        }

        [DllImport("shell32.dll", SetLastError = true)]
        private static extern int Shell_NotifyIconGetRect([In]ref NOTIFYICONIDENTIFIER identifier, [Out]out RECT iconLocation);

        private static FieldInfo windowField = typeof(NotifyIcon).GetField("window", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        private static IntPtr GetHandle(NotifyIcon icon)
        {
            if (windowField == null) throw new InvalidOperationException("[Useful error message]");
            NativeWindow window = windowField.GetValue(icon) as NativeWindow;

            if (window == null) throw new InvalidOperationException("[Useful error message]");  // should not happen?
            return window.Handle;
        }

        private static FieldInfo idField = typeof(NotifyIcon).GetField("id", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        private static int GetId(NotifyIcon icon)
        {
            if (idField == null) throw new InvalidOperationException("[Useful error message]");
            return (int)idField.GetValue(icon);
        }
        #endregion

        #endregion

        #region 操作通知欄
        //定時器方法
        private void notifybar_timer_tick(object sender, EventArgs e)
        {
            POINT pt = new POINT();

            //獲取滑鼠的位置
            GetCursorPos(out pt);

            //獲取托盤圖示的位置
            RG rg = GetIconRect(notifyicon);

            if (pt.x > rg.Left && pt.x < (rg.Left + rg.Widht) && pt.y > rg.Top && pt.y < (rg.Top + rg.Height))
            {
                //滑鼠指標還在托盤圖示中,不需要處理              
            }
            else
            {
                //滑鼠移除托盤圖示區域

                //停止計時器
                notifybar_timer.Stop();

                //判斷指標是否移入了彈框popup的區域
                if (NotifyBar_IsMouseEnter == false)
                {
                    //不是則關閉popup
                    NotifyBar.IsOpen = false;
                }
              
            }
        }

        //托盤圖示滑鼠移入
        private void notifyicon_mousemove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            //滑鼠移入托盤圖示後顯示彈框
            NotifyBar.IsOpen = true;

            //如果計時器沒有啟動則啟動
            if (notifybar_timer.IsEnabled == false)
            {
                notifybar_timer.Start();
            }

            //獲取托盤圖示位置
            RG rg = GetIconRect(notifyicon);

            //計算彈框的位置,使其在托盤圖示的正上方中間的位置
            //彈框(popup)距離螢幕上方的位置 = 托盤距離螢幕上方位置 - 彈框的實際高度
            NotifyBar.VerticalOffset = rg.Top - NotifyBar_Grid.ActualHeight;
            //這裡計算使彈框在托盤圖示中間的位置
            NotifyBar.HorizontalOffset = rg.Left - (NotifyBar_Grid.ActualWidth / 2) + (rg.Widht / 2);
           
            
        }



        
        private void NotifyBar_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            //滑鼠移入彈框GRID區域了

            NotifyBar_IsMouseEnter = true;
        }

        private void NotifyBar_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            //滑鼠離開了彈框的GRID區域

            //判斷滑鼠是否曾經移入到彈框GRID區域中過
            if (NotifyBar_IsMouseEnter)
            {
                //是,關閉。
                
                NotifyBar.IsOpen = false;
            }

            //重置滑鼠是否移入彈框GRID區域變數值
            NotifyBar_IsMouseEnter = false;
        }
        #endregion

    }

}

程式碼已經寫有超級詳細的註釋了,部落格就不多廢話了。核心程式碼(獲取托盤圖示位置)的來源也在程式碼註釋標註了,下面也是給大家打包了專案方便下載。

提醒:在正式專案中要將popup換成新的window,因為當popup所在窗體不具備焦點時,popup內的控制元件無法正常互動。2018年1月23日09:33:08 

專案下載:

點我下載