[WinApp]寫個簡單的NotifyIcon(TrayIcon)小程式

[WinApp]寫個簡單的NotifyIcon(TrayIcon)小程式

前言

因為做一個NotifyIcon的程式,就將它記錄下來也分享給大家。

image

image

image

image

image

實作

1.建立一個Windows Form應用程式。

image

2.準備3個icon檔(可以從http://www.iconarchive.com找,但用在商業上要注意License)。

image

3.加入NotifyIcon元件,並設定Icon及在DoubleClick事件中加入開啟Form的Code。

image

private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
    ShowForm();
}

/// <summary>
/// 顯示出主畫面
/// </summary>
private void ShowForm()
{
    if (this.WindowState == FormWindowState.Minimized)
    {
        //如果目前是縮小狀態,才要回覆成一般大小的視窗
        this.Show();
        this.WindowState = FormWindowState.Normal;
    }
    // Activate the form.
    this.Activate();
    this.Focus();
}

 

 

4.加入contextMenuStrip元件,並設定NotifyIcon1的contextMenuStrip屬性為contextMenuStrip1,也設定Icon為前面的Idle.ico。

image

image

5.在contextMenuStrip1加入「結束(&X)」的選項,並在該選項的Click事件中加入結束程式的Code。

image

/// <summary>
/// 結束程式
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void mnuExit_Click(object sender, EventArgs e)
{
    this.WindowState = FormWindowState.Minimized;
    Close();
}

 

 

6.在Form的FormClosing事件中,在Form上按下「X」就將Form縮小。

/// <summary>
/// 使用者按下視窗關閉,把它最小化就好
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void frmTrayIcon_FormClosing(object sender, FormClosingEventArgs e)
{
    if (this.WindowState != FormWindowState.Minimized)
    {
        e.Cancel = true;
        this.WindowState = FormWindowState.Minimized;
        notifyIcon1.Tag = string.Empty;
        notifyIcon1.ShowBalloonTip(3000, this.Text ,
             "程式並未結束,要結束請在圖示上按右鍵,選取結束功能!",
             ToolTipIcon.Info);
    }
}

 

 

7.在NotifyIcon1的BalloonTipClicked事件中,加入開啟Form的Code。

/// <summary>
/// 在NotifyBallonTip上按下Click,就將Form開啟出來
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void notifyIcon1_BalloonTipClicked(object sender, EventArgs e)
{
    ShowForm();
}

 

 

8.設定Form的ShowInTaskbar屬性false(就不會顯示在Window Taskbar上),WindowState屬性為Minimized。

image

以上就完成了簡單的NotifyIcon程式。一開始程式會在icon區,在icon中Double Click後會開啟視窗,按右鍵可選取結束程式,在視窗關閉後,會顯示BalloonTip,按下BalloonTip後會再開啟視窗就像MSN那樣。

 

接下來我們要加入每隔一段時間就去檢查是否有待辦事項,

9.在Form上加入TextBox設定每隔幾分鐘更新,同時也加入更新的Button、Timer及一些顯示的Label,如下圖,

image

private DateTime lastRefresh = new DateTime(0);
private int refreshInterval = 3;
private string lastTitle = string.Empty;
private string lastMessage = string.Empty;
private int lastMessageCount = 0;
private string openURL = "www.google.com.tw";
private const string todoMsg = "您有({0})件待辦事項";
public enum NotifyIconType
{
    Idle,
    Refresh,
    HaveTodo
}

 

 

10.在更新時,為了讓UI不會卡住,所以我們可以用非同步的方式來處理取待辦的Function(frmTrayIcon為表單的class)。

#region Delegate declarations
/// <summary>
/// Delegate to refresh all messages.
/// </summary>
public delegate void RefreshAllMessagesDelegate();

/// <summary>
/// Callback function for RefreshAllMessagesDelegate().
/// </summary>
/// <param name="ar"></param>
static void RefreshAllCallback(IAsyncResult ar)
{
    // Retrieve the delegate.
    frmTrayIcon.RefreshAllMessagesDelegate dlgt = 
        (frmTrayIcon.RefreshAllMessagesDelegate)ar.AsyncState;

    // Call EndInvoke to retrieve the results.
    dlgt.EndInvoke(ar);
}
#endregion

 

 

11.在呼叫非同步更新前,先將NotifyIcon1的Icon改成Reresh.ico,更新後,再依是否有待辦資料來決定是否顯示Idle.ico或是HaveTodo.ico。如果有待辦,而視窗沒有開啟的話,要呼叫NotifyIcon1的ShowBalloonTip顯示待辦訊息,且設定它的Tag屬性為opneURL。請參考以下的Code,

/// <summary>
/// 更新檢查Todo
/// </summary>
public void RefreshAllMessages()
{
    for (long i = 0; i < 1000000000; i++)
    {
        ////不做事,只是看換icon的效果
    }
    lastMessageCount = 100;
    lastMessage = string.Format(todoMsg, lastMessageCount.ToString());
}

/// <summary>
/// 更新待辦件數
/// </summary>
private void RefreshAsync()
{
    //更新最後執行的時間
    UpdateLastRefreshTime();
    //建立查詢中狀態
    BuildMessagesQuerying();
    // 非同步呼叫更新
    RefreshAllMessagesDelegate refresher = new RefreshAllMessagesDelegate(this.RefreshAllMessages);
    IAsyncResult ar = refresher.BeginInvoke(new AsyncCallback(RefreshAllCallback), refresher);

    //等待更新完成
    while (!ar.IsCompleted)
    {
        Application.DoEvents();
    }
    //更新完成,顯示待辦件數
    BuildMessagesResult();
}

/// <summary>
/// 更新最後執行的時間
/// </summary>
private void UpdateLastRefreshTime()
{
    this.lastRefresh = DateTime.Now;
    //時間為30~60分之間
    refreshInterval = int.Parse(this.txtRefreshInterval.Text);
    lblNextRefreshTime.Text = String.Format("{0:yyyy/MM/dd HH:mm}", lastRefresh.AddMinutes(refreshInterval)); ;
}

/// <summary>
/// 建立查詢中狀態
/// </summary>
private void BuildMessagesQuerying()
{
    lastMessage = "正在檢查待辦中.....";
    linkTodo.Enabled = false;
    linkTodo.Text = lastMessage;
    btnRefresh.Enabled = false;
    mnuExit.Enabled = false;
    txtRefreshInterval.Enabled = false;
    // 將icon改成更新中,改成Refresh.ico
    ChangeNotifyIcon(NotifyIconType.Refresh);
}

/// <summary>
/// 依最後結果設定畫面
/// </summary>
private void BuildMessagesResult()
{
    bool IsShowNotify = (lastMessageCount > 0);
    //如果有待辦件數的話,就改成HaveTodo.ico,否則就使用Idle.ico
    ChangeNotifyIcon(IsShowNotify ? NotifyIconType.HaveTodo : NotifyIconType.Idle);
    notifyIcon1.Text = lastMessage;
    mnuExit.Enabled = true;
    btnRefresh.Enabled = true;
    txtRefreshInterval.Enabled = true;
    linkTodo.Text = lastMessage;
    //有待辦linkTodo才能Enable
    linkTodo.Enabled = IsShowNotify;

    if (IsShowNotify && this.WindowState == FormWindowState.Minimized)
    {
        //要顯示通知訊息
        notifyIcon1.Tag = openURL;
        notifyIcon1.ShowBalloonTip(3000, this.Text,
             lastMessage,
             ToolTipIcon.Info);
    }
}

 

 

12.在原本的NotifyIcon1的BalloonTipClicked事件中,要加入處理,如果NotifyIcon1的Tag屬性不為空值,就開啟該URL。

/// <summary>
/// 在NotifyBallonTip上按下Click,就將Form開啟出來
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void notifyIcon1_BalloonTipClicked(object sender, EventArgs e)
{
    string notifyTag = (string)notifyIcon1.Tag;
    if (string.IsNullOrEmpty(notifyTag))
    {
        //只是關閉form的訊息
        ShowForm();
    }
    else
    {
        //依notifyTag的值決定要做何事,如開啟URL
        System.Diagnostics.Process.Start(notifyTag);
    }
}
/// 
/// 更換NotifyIcon
/// 
private void ChangeNotifyIcon(NotifyIconType notiType)
{
	const string imageFolderName = "images";
	try
	{
		//內嵌資源的檔名為 namespace.foldername.filename 而且大小寫要相同哦
		System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
		
		string fileName = asm.GetName().Name + "." + imageFolderName + "." + notiType.ToString() + ".ico";
		Stream iconStream = asm.GetManifestResourceStream(fileName);
		Icon newIcon = new Icon(iconStream);
		this.notifyIcon1.Icon = newIcon;
		iconStream.Close();
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}

 

13.在refreshTimer的Tick事件中,處理時間到要再呼叫更新的Method,按下更新Button後,也要設定更新的分鐘數。

/// <summary>
/// 檢查是否需要更新
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void refreshTimer_Tick(object sender, EventArgs e)
{
    TimeSpan diff = DateTime.Now - this.lastRefresh;
    if (diff > new TimeSpan(0, refreshInterval, 0))
    {
        //更新待辦件數
        RefreshAsync();
    }
}

/// <summary>
/// 按下更新,要設定更新的時間,並同時更新
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnRefresh_Click(object sender, EventArgs e)
{
    refreshInterval = int.Parse(txtRefreshInterval.Text.Trim());
    //更新待辦件數
    RefreshAsync();
}

測試程式

參考資料

An Office 2003-like popup notifier

How To Add ToolTips To Controls On A Windows Form

[Thread] 非同步作業 IAsyncResult / AsyncCallback

[C#]Winform利用Assembly的GetManifestResourceStream來載入圖片資源

Hi, 

亂馬客Blog已移到了 「亂馬客​ : Re:從零開始的軟體開發生活

請大家繼續支持 ^_^