[WinApp]寫個簡單的NotifyIcon(TrayIcon)小程式
前言
因為做一個NotifyIcon的程式,就將它記錄下來也分享給大家。
實作
1.建立一個Windows Form應用程式。
2.準備3個icon檔(可以從http://www.iconarchive.com找,但用在商業上要注意License)。
3.加入NotifyIcon元件,並設定Icon及在DoubleClick事件中加入開啟Form的Code。
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。
5.在contextMenuStrip1加入「結束(&X)」的選項,並在該選項的Click事件中加入結束程式的Code。
/// <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。
以上就完成了簡單的NotifyIcon程式。一開始程式會在icon區,在icon中Double Click後會開啟視窗,按右鍵可選取結束程式,在視窗關閉後,會顯示BalloonTip,按下BalloonTip後會再開啟視窗就像MSN那樣。
接下來我們要加入每隔一段時間就去檢查是否有待辦事項,
9.在Form上加入TextBox設定每隔幾分鐘更新,同時也加入更新的Button、Timer及一些顯示的Label,如下圖,
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:從零開始的軟體開發生活」
請大家繼續支持 ^_^