<!--
google_ad_client = "pub-0410788977836329";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
//2007-07-10: csdn.net
google_ad_channel = "9958187656";
google_color_border = "6699CC";
google_color_bg = "003366";
google_color_link = "FFFFFF";
google_color_text = "AECCEB";
google_color_url = "AECCEB";
//-->
原文:http://www.codeproject.com/csharp/SingleInstanceApplication.asp
翻译:Anders Liu
出处:http://www.cnblogs.com/AndersLiu/archive/2007/07/09/811354.html
简介
本文解决下列问题:
1 创建单实例应用程序。
2 当用户试图启动新的实例时,恢复前一个实例。
3 当窗口关闭时间起最小化到任务栏的通知区域中(带动画)。
如何创建单实例应用程序?
通常,需要确保在任何时候都只有程序的一个实例在运行。如果用户试图运行另外一个实例,或者通知用户已经有一个实例了,或者激活之前运行的实例并将其带到前台。对于Windows应用程序,我们可能希望恢复现有的主窗口。因此当应用程序启动的时候,应该查看是否已经有正在运行的实例了。如果有,应该退出当前实例并激活前一个实例的主窗口并显示给用户。
将应用程序做成单实例的,可以通过mutex(Mutual Exclusion Semaphore)[互斥体(互斥信号量)]来实现。Windows应用程序通过Application.Run()方法来加载住窗体。在Main方法中,创建一个新的mutex。如果可以创建新的mutex,则允许应用程序运行。如果mutex已经被创建了,应用程序就不会启动。这样就能确保任何使用只有一个实例在运行。
// 用于检测是否创建了新的mutex
bool newMutexCreated = false;
// mutex的名字以Local\作为前缀,
// 确保将其创建在每会话(per-session)命名空间中,
// 而不是全局命名空间中。
string mutexName = "Local\\" +
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Mutex mutex = null;
try
{
// 使用唯一的名字创建一个新的mutex
mutex = new Mutex(false, mutexName, out newMutexCreated);
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
Application.Exit ();
}
// 如果是第一次创建mutex,则启动应用程序实例,
// 因为这是第一次运行
if(newMutexCreated)
{
Application.Run(new AnimatedWindowForm());
}
当创建mutex时,其名字的前缀可以是Globa\或Local\。Global\前缀意味着该mutex将影响全局命名空间。
以Local\为前缀意味着该mutex只会影响到用户会话命名空间。
Windows XP和Windows 2003允许通过Terminal Services Sessions(终端服务会话)快速切换用户。因此如果mutext使用Global\前缀,在整个系统范围内只能有一个实例运行。如果一个用户启动了该应用程序,其他用户就无法在他们的会话中再次创建一个实例了。如果mutext的前缀不是Local\,它也只会影响每个会话。
要了解Kernel Object命名空间,请阅读这篇MSDN文章(http://msdn.microsoft.com/library/en-us/termserv/termserv/kernel_object_namespaces.asp)。
现在,还有一个任务要完成——将前一个实例移到前台。在Windows应用程序中这意味着将应用程序主窗口恢复到顶端,如果已经隐藏,则显示给用户。
恢复前一个实例
要恢复主窗口,必须要得到应用程序主窗口的句柄。通过下面这段代码可以得到进程的MainWindowHandle:
Process[] currentProcesses =
Process.GetProcessesByName("SingleInstanceApplication");
System.IntPtr mainWindowHandle = currentProcesses[0].MainWindowHandle;
if(mainWindowHandle != IntPtr.Zero)
{
ShowWindow(mainWindowHandle,SW_RESTORE); // Restore the Window
UpdateWindow(mainWindowHandle);
}
但当应用程序的主窗口被隐藏时,这段代码会失败,因为句柄返回的是0。
一个可靠的机制是使MainWindowHandle变成必需的。这就轮到共享内存上场了。共享内存是IPC(Inter Process Communication,进程间通信)的一种方法,使用这种方法,两个或更多个进程可以使用共享的内存片段进行通信。在C#中创建共享内存可以使用Win32 API调用。内存映射可以将文件内容关联到你的进程地址空间或系统页文件或系统内存的特定地址中的一个特定的地址区域。
要在两个进程之间共享数据,需要在系统页文件中创建共享内存。
为了使一个进程能够将通过内存映射文件(Memory Mapped File,MMF)将数据共享给其他进程,每个进程都必须访问该文件。这通过为MMF对象起一个名字来实现,每个进程都能够使用这个名字来访问共享内存。
private const int INVALID_HANDLE_VALUE = -1;
private const int FILE_MAP_WRITE = 0x2;
private const int FILE_MAP_READ = 0x0004;
[DllImport("kernel32.dll",EntryPoint="OpenFileMapping",
SetLastError=true, CharSet=CharSet.Auto) ]
private static extern IntPtr OpenFileMapping (int
wDesiredAccess, bool bInheritHandle,String lpName );
[DllImport("Kernel32.dll",EntryPoint="CreateFileMapping",
SetLastError=true,CharSet=CharSet.Auto)]
private static extern IntPtr CreateFileMapping(int hFile,
IntPtr lpAttributes, uint flProtect,
uint dwMaximumSizeHigh, uint dwMaximumSizeLow,
string lpName);
[DllImport("Kernel32.dll")]
private static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
uint dwDesiredAccess, uint dwFileOffsetHigh,
uint dwFileOffsetLow, uint dwNumberOfBytesToMap);
[DllImport("Kernel32.dll",EntryPoint="UnmapViewOfFile",
SetLastError=true,CharSet=CharSet.Auto)]
private static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32.dll",EntryPoint="CloseHandle",
SetLastError=true,CharSet=CharSet.Auto)]
private static extern bool CloseHandle(uint hHandle);
[DllImport("kernel32.dll",EntryPoint="GetLastError",
SetLastError=true,CharSet=CharSet.Auto)]
private static extern uint GetLastError();
private IntPtr memoryFileHandle;
public enum FileAccess : int
{
ReadOnly = 2,
ReadWrite = 4
}
为共享内存对象创建新的MMF,可以使用CreateFileMapping()函数。创建了新的MMF对象后,系统页文件就会为其保留一部分。
参数
- hFile——要进行内存映射的文件句柄。当在系统页文件中创建MMF时,这个值必须是0xFFFFFFFF(-1)。
- lpAttributes——指向一个SECURITY_ATTRIBUTES结构体的指针
- flProtect——为内存映射文件指定的保护类型。
- PAGE_READONLY——只读访问。
- PAGE_READWRITE——读/写访问。
- PAGE_WRITECOPY——Copy-on-write访问。
- PAGE_EXECUTE_READ——读取和执行访问。
- PAGE_EXECUTE_READWRITE——读取、写入和执行访问。
- dwMaximumSizeHigh——文件映射对象的最大大小的DWORD值的高位。
- dwMaximumSizeLow——文件映射对象的最大大小的DWORD值的低位。
- lpName——文件映射对象的名字。
public static MemoryMappedFile CreateMMF(string fileName, FileAccess access, int size)
{
if(size < 0)
throw new ArgumentException("The size parameter" +
" should be a number greater than Zero.");
IntPtr memoryFileHandle = CreateFileMapping (0xFFFFFFFF,
IntPtr.Zero,(uint)access,0,(uint)size,fileName);
if(memoryFileHandle == IntPtr.Zero)
throw new SharedMemoryException("Creating Shared Memory failed.");
return new MemoryMappedFile(memoryFileHandle);
}
下面我们启动应用程序的第一个实例,创建MMF对象。
// 当第一次创建mutex时,运行程序,因为这是第一个实例。
if(newMutexCreated)
{
//Create the Shared Memory to store the window handle.
lock(typeof(AnimatedWindowForm))
{
sharedMemory = MemoryMappedFile.CreateMMF("Local\\" +
"sharedMemoryAnimatedWindow",
MemoryMappedFile.FileAccess .ReadWrite, 8);
}
Application.Run(new AnimatedWindowForm());
}
一旦得到了内存映射文件的句柄,就可以用它来将文件视图映射到调用进程的地址空间。只要MMF对象存活着,就能对视图进行映射和取消映射。MapViewOfFile()和UnmapViewOfFile()函数用于映射和取消映射视图。我们是否可以执行读/写操作,取决于在调用MapViewOfFile()函数时指定的访问类型。
MapViewOfFile()的参数:
- hFileMappingObject——MMF对象的句柄。CreateFileMapping和OpenFileMapping函数可以返回这个句柄。
- dwDesiredAccess——MMF对象的访问类型。这个参数可以取下列值:
- FILE_MAP_READ——只读访问。MMF对象必须具备PAGE_READWRITE或PAGE_READONLY访问。
- FILE_MAP_WRITE——读/写访问。MMF对象必须具备PAGE_READWRITE访问。
- FILE_MAP_COPY——Copy-on-write访问。MMF对象必须具备PAGE_WRITECOPY访问。
- FILE_MAP_EXECUTE——执行访问。MMF对象必须具备PAGE_EXECUTE_READWRITE或PAGE_EXECUTE_READ访问。
- dwFileOffsetHigh——映射视图在文件中的起始偏移量的DWORD高位。
- dwFileOffsetLow——映射视图在文件中的起始偏移量的DWORD低位。
- dwNumberOfBytesToMap——文件映射映射到视图的字节数。
在创建了内存映射文件的视图后,可以在任何时候通过调用UnmapViewOfFile()函数来取消映射。其惟一必须的参数就是映射视图的句柄。
UnmapViewOfFile(mappedViewHandle);
为了能够写入共享内存,首先需要使用FILE_MAP_WRITE创建MMF对象的映射视图。由于我们要在主窗口中写入该句柄,可以使用Marshal.WriteIntPtr()方法写入共享内存。写入操作完成后,需要取消映射视图,最后通过调用CloseHandle()函数释放映射视图。
public void WriteHandle(IntPtr windowHandle)
{
IntPtr mappedViewHandle = MapViewOfFile(memoryFileHandle,
(uint)FILE_MAP_WRITE,0,0,8);
if(mappedViewHandle == IntPtr.Zero)
throw new SharedMemoryException("Creating" +
" a view of Shared Memory failed.");
Marshal.WriteIntPtr(mappedViewHandle,windowHandle );
UnmapViewOfFile(mappedViewHandle);
CloseHandle((uint)mappedViewHandle);
}
要读取共享内存,需要使用FILE_MAP_READ访问创建MMF对象的映射视图。使用Marshal.ReadIntPtr()方法来读取共享内存。完成读取操作后,取消映射视图并调用CloseHandle()函数释放映射视图。
public static IntPtr ReadHandle(string fileName)
{
IntPtr mappedFileHandle =
OpenFileMapping((int)FileAccess.ReadWrite, false, fileName);
if(mappedFileHandle == IntPtr.Zero)
throw new SharedMemoryException("Opening the" +
" Shared Memory for Read failed.");
IntPtr mappedViewHandle = MapViewOfFile(mappedFileHandle,
(uint)FILE_MAP_READ,0,0,8);
if(mappedViewHandle == IntPtr.Zero)
throw new SharedMemoryException("Creating" +
" a view of Shared Memory failed.");
IntPtr windowHandle = Marshal.ReadIntPtr(mappedViewHandle);
if(windowHandle == IntPtr.Zero)
throw new ArgumentException ("Reading from the specified" +
" address in Shared Memory failed.");
UnmapViewOfFile(mappedViewHandle);
CloseHandle((uint)mappedFileHandle);
return windowHandle;
}
当应用程序主窗口句柄创建之后,我们就将其写入共享内存。
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated (e);
IntPtr mainWindowHandle = this.Handle;
try
{
lock(this)
{
//Write the handle to the Shared Memory
sharedMemory.WriteHandle (mainWindowHandle);
}
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+ "\n\n"+ex.StackTrace+
"\n\n"+ "Application Exiting...","Exception thrown");
Application.Exit();
}
}
当用户尝试启动应用程序的第二个实例时,从共享内存中可以得到前一个实例的窗口句柄,并使用ShowWindow()和UpdateWindow()函数恢复主窗口。
// 如果mutex已经存在,不需要启动应用程序的新实例,
// 因为前一个实例已经在运行了。
try
{
// 获取程序主窗口的句柄,
// 该句柄由前一个实例存储到共享内存中。
IntPtr mainWindowHandle = System.IntPtr.Zero;
lock(typeof(AnimatedWindowForm))
{
mainWindowHandle = MemoryMappedFile.ReadHandle("Local" +
"\\sharedMemoryAnimatedWindow");
}
if(mainWindowHandle != IntPtr.Zero)
{
// Restore the Window
ShowWindow(mainWindowHandle,SW_RESTORE);
UpdateWindow(mainWindowHandle);
}
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+ "\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
}
因此我们的应用程序的main方法看起来是下面这样的:
static void Main()
{
// Used to check if we can create a new mutex
bool newMutexCreated = false;
// The name of the mutex is to be prefixed with Local\ to make
// sure that its is created in the per-session
// namespace, not in the global namespace.
string mutexName = "Local\\" +
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Mutex mutex = null;
try
{
// Create a new mutex object with a unique name
mutex = new Mutex(false, mutexName, out newMutexCreated);
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
Application.Exit ();
}
// When the mutex is created for the first time
// we run the program since it is the first instance.
if(newMutexCreated)
{
// Create the Shared Memory to store the window
// handle. This memory is shared between processes
lock(typeof(AnimatedWindowForm))
{
sharedMemory = MemoryMappedFile.CreateMMF("Local" +
"\\sharedMemoryAnimatedWindow",
MemoryMappedFile.FileAccess .ReadWrite ,8);
}
Application.Run(new AnimatedWindowForm());
}
else
// If the mutex already exists, no need to launch
// a new instance of the program because
// a previous instance is running .
{
try
{
// Get the Program's main window handle,
// which was previously stored in shared memory.
IntPtr mainWindowHandle = System.IntPtr.Zero;
lock(typeof(AnimatedWindowForm))
{
mainWindowHandle =
MemoryMappedFile.ReadHandle("Local" +
"\\sharedMemoryAnimatedWindow");
}
if(mainWindowHandle != IntPtr.Zero)
{
// Restore the Window
ShowWindow(mainWindowHandle,SW_RESTORE);
UpdateWindow(mainWindowHandle);
}
return;
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
}
// Tell the garbage collector to keep the Mutex alive
// until the code execution reaches this point,
// ie. normally when the program is exiting.
GC.KeepAlive(mutex);
// Release the Mutex
try
{
mutex.ReleaseMutex();
}
catch(ApplicationException ex)
{
MessageBox.Show (ex.Message + "\n\n"+ ex.StackTrace,
"Exception thrown");
GC.Collect();
}
}
}
将窗口最小化到通知区域
这包含四个任务:
第一步是防止用户单击关闭按钮时关闭窗口,重写protected virtual OnClosing方法,取消Close事件。窗体应该被隐藏,而应用程序在后台运行。但当用户关闭系统时呢?操作系统会像所有打开着的窗口发送Close消息。如果我们的应用程序拒绝关闭窗口,系统将无法关闭,它会持续等待,直到所有窗口都关闭。因此我们需要重写WndProc需方法,处理WM_QUERYENDSESSION消息。
protected override void OnClosing(CancelEventArgs e)
{
if(systemShutdown == true)
e.Cancel = false;
else
{
e.Cancel = true;
this.AnimateWindow();
this.Visible = false;
}
}
protected override void WndProc(ref Message m)
{
// 一旦程序收到WM_QUERYENDSESSION消息,
// 将systemShutdown布尔值设置为true。
if(m.Msg == WM_QUERYENDSESSION)
systemShutdown = true;
base.WndProc(ref m);
}
接下来,我们希望在任务栏的通知区域显示一个通知图标。向主窗体添加一个NotifyIcon控件并为其设置图标。该图标将会显示在任务栏的通知区域中。我们的下一个目的是实现窗口向通知区域靠拢的动画。在做这个动画之前,我们需要确保用户没有禁用系统中的窗口动画。用户可以通过设置HKeyCurrentUser\Control Panel\Desktop下的MinAnimate键来启用/禁用窗口动画。我们检查这个值,并根据用户的偏好来设置一个布尔值。
RegistryKey animationKey =
Registry.CurrentUser.OpenSubKey("Control Panel" +
"\\Desktop\\WindowMetrics",true);
object animKeyValue = animationKey.GetValue("MinAnimate");
if(System.Convert.ToInt32 (animKeyValue.ToString()) == 0)
this.AnimationDisabled = true;
else
this.AnimationDisabled = false;
如果可以使用动画,我们使用DrawAnimatedRects(IntPtr hwnd, int idAni, ref RECT lprcFrom, ref RECT lprcTo)函数来绘制窗口动画。该函数有四个参数。hwnd是要进行动画的窗口句柄。idAni是动画的类型。如果指定为IDANI_CAPTION,则窗口标题会以动画方式从lprcFrom指定的位置移动到lprcTo指定的位置。否则它会绘制一个外框矩形并对其进行动画。lprcFrom和lprcTo都是RECT类型的,指定了动画的起止矩形。我们使用GetWindowRect(IntPtr hwnd, ref RECT lpRect)函数从窗体的句柄获取其矩形。最小化时,起始位置是窗口的RECT。而终止位置是通知区域的RECT。所以下一个任务是获取通知区域的句柄。任务栏的类名字是Shell_TrayWnd。任务栏包含很多其他子窗口。我们需要“notification area”的句柄,其中包含了通知图标。我们可以通过枚举Shell_TrayWnd的子窗口来获取其句柄。现在我们就可以使用GetWindowRect(IntPtr hwnd, ref RECT lpRect)函数来获取通知区域的RECT了。
private void AnimateWindow()
{
// if the user has not disabled animating windows...
if(!this.AnimationDisabled)
{
RECT animateFrom = new RECT();
GetWindowRect(this.Handle, ref animateFrom);
RECT animateTo = new RECT ();
IntPtr notifyAreaHandle = GetNotificationAreaHandle();
if (notifyAreaHandle != IntPtr.Zero)
{
if ( GetWindowRect(notifyAreaHandle, ref animateTo) == true)
{
DrawAnimatedRects(this.Handle,
IDANI_CAPTION,ref animateFrom,ref animateTo);
}
}
}
}
private IntPtr GetNotificationAreaHandle()
{
IntPtr hwnd = FindWindowEx(IntPtr.Zero,IntPtr.Zero,"Shell_TrayWnd",null);
hwnd = FindWindowEx(hwnd , IntPtr.Zero ,"TrayNotifyWnd",null);
hwnd = FindWindowEx(hwnd , IntPtr.Zero ,"SysPager",null);
if (hwnd != IntPtr.Zero)
hwnd = FindWindowEx(hwnd , IntPtr.Zero ,null,"Notification Area");
return hwnd;
}
结论
诚然,获取通知区域的窗口句柄有的时候会失败,因为“TrayNotifyWnd”、“SysPager”和“Notification Area”都是undocumented(非编档)的窗口类名,可能在未来的Windows版本中有所变化。
已知问题
在应用程序的Debug版本和Release版本之间存在着一个冲突。如果首先启动了Release版本,然后用户再启动Debug版本,则两个实例都会运行。Mutex不能在开始时组织第二个实例的起动。
<!--
google_ad_client = "pub-0410788977836329";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
//2007-07-10: csdn.net
google_ad_channel = "9958187656";
google_color_border = "6699CC";
google_color_bg = "003366";
google_color_link = "FFFFFF";
google_color_text = "AECCEB";
google_color_url = "AECCEB";
//-->
分享到:
相关推荐
在描述中提到的“简单的系统托盘实例”,指的是在程序中添加了一个系统托盘图标,用户可以通过点击这个图标来访问程序的各种功能。这个实例包含右键菜单,提供了“演示”、“最大化”、“最小化”、“退出”和“关于...
在编程领域,尤其是Windows应用程序开发中,"最小化到系统托盘"是一个常见的功能,它允许用户将程序窗口隐藏到任务栏的系统托盘区域,而不是完全关闭或最小化到任务栏。Delphi是一个强大的集成开发环境(IDE),主要...
在Windows编程中,将程序最小化到系统托盘是一项常见的功能,这允许用户在不关闭程序的情况下将其隐藏,仅保留一个图标在任务栏的系统托盘区域。在本实例中,我们将探讨如何使用C++和VC++实现这一功能。这个案例主要...
总之,"vc++最小化到托盘与实例启动唯一实例源码"涵盖了Windows编程中的两个重要概念:系统托盘管理和单实例应用控制。通过研究和实践,开发者可以掌握这些技术,提升Windows桌面应用的用户体验。
标题中的“PB最小化到托盘以及右键菜单”指的是在使用PowerBuilder(PB)开发的应用程序中,实现一个功能,使得程序窗口可以被最小化到操作系统任务栏的托盘区域,并且在托盘图标上提供右键菜单,增强用户交互体验。...
将上述代码整合到Flex项目中,即可实现标题所描述的功能:在最小化窗口时,应用将出现在系统托盘区,托盘图标带有"测试生成托盘图标"的提示,用户可以通过右键菜单打开或关闭应用。通过这种方式,我们可以使Flex应用...
例如,当用户点击窗口的最小化按钮时,我们可以捕获这个`Form_Resize`事件,并在此事件处理程序中编写代码,以使窗口最小化到系统托盘而不是桌面的任务栏。 为了实现这一功能,我们需要创建一个托盘图标( TrayIcon...
本文将详细介绍如何在PowerBuilder中利用Win32 API函数来创建一个系统托盘程序,该程序能够在用户暂时不使用或希望将程序置于后台运行时,将PowerBuilder应用程序最小化至系统托盘,并通过图标显示。 #### 二、设计...
在VB(Visual Basic)编程中,将窗口最小化到系统托盘是一项常见的需求,它可以使应用程序在用户按下最小化按钮时,不从任务栏消失而是转换为托盘图标,这样可以保持程序运行并提供一种非干扰式的用户体验。...
在Windows操作系统中,"托盘程序"是指那些可以最小化到任务栏右侧通知区域(通常称为系统托盘)的应用程序,而不是直接消失在任务栏按钮列表中。这种设计允许用户在不占据桌面空间的情况下,方便地管理和访问这些...
标题中的"QT将应用程序缩小到右下角任务栏的系统托盘内"就是指利用QT框架来实现在用户点击最小化按钮时,程序窗口不消失而是隐藏到操作系统右下角的任务栏通知区域,通常显示为一个图标。这样,用户可以通过单击这个...
本文将详细解析如何利用MFC实现一个功能,即当用户点击程序窗口的最小化按钮时,程序窗口不消失而是隐藏到系统托盘区域。这种功能常见于各种后台运行的应用程序,如音乐播放器、聊天软件等,便于用户在不关闭程序的...
在编程领域,特别是使用C#这种面向对象的编程语言时,有时我们需要实现一个功能,让程序在被最小化时不是完全消失在任务栏上,而是隐藏到系统托盘区域。这个功能常见于许多应用程序,如音乐播放器、聊天软件等,它...
在Windows Presentation Foundation (WPF)应用开发中,有时我们需要实现一些特定的功能,比如将应用程序窗口最小化到系统托盘,或者提供查看本机服务的能力。这些功能可以增强用户体验,使得应用在后台运行时更加...
在C#编程中,WinForm应用经常需要实现窗口最小化时隐藏到系统托盘区的功能,这能够提供更好的用户体验,使应用程序在用户不主动关闭时仍然保持后台运行。本实例"**C# WinForm最小化隐藏到托盘实例**"正是针对这一...
1. **创建TrayIcon对象**:首先,你需要获取系统的系统托盘实例,这可以通过`SystemTray.getSystemTray()`方法完成。然后,创建一个`TrayIcon`对象,传入一个`Image`对象作为图标,以及一个`ActionListener`来处理...
标题中的“托盘程序”指的是在操作系统任务栏通知区域(通常称为系统托盘或系统 tray)运行的应用程序。这样的程序通常在启动后不占据主屏幕空间,而是将图标隐藏到托盘区,以便用户可以轻松访问但不会干扰其他工作...
在VC++(Visual C++)编程环境中,我们可以利用系统提供的API(应用程序接口)函数来实现一个程序的最小化到托盘功能,而不是让其完全隐藏或仅显示为任务栏中的一个图标。 首先,我们需要理解托盘区域是如何工作的...
本实例关注的是如何利用`wxPython`实现应用程序的托盘功能,特别是当用户最小化程序时,将程序图标隐藏到系统托盘区,而不是完全关闭或隐藏程序。 首先,我们需要了解`wxPython`中的`TaskBarIcon`类,这是实现托盘...
在Java编程环境中,实现窗口应用最小化到系统托盘是一项常见的功能,特别是在开发桌面应用程序时。这个功能使得用户可以将程序窗口隐藏到任务栏的托盘区域,而不是将其关闭或最小化到任务栏,从而方便用户快速访问。...