博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[翻译] 关闭时最小化到系统托盘中的单实例应用程序
阅读量:6091 次
发布时间:2019-06-20

本文共 12818 字,大约阅读时间需要 42 分钟。

原文:

翻译:Anders Liu
出处:

简介

  本文解决下列问题:

1 创建单实例应用程序。
2 当用户试图启动新的实例时,恢复前一个实例。
3 当窗口关闭时间起最小化到任务栏的通知区域中(带动画)。

如何创建单实例应用程序?

  通常,需要确保在任何时候都只有程序的一个实例在运行。如果用户试图运行另外一个实例,或者通知用户已经有一个实例了,或者激活之前运行的实例并将其带到前台。对于Windows应用程序,我们可能希望恢复现有的主窗口。因此当应用程序启动的时候,应该查看是否已经有正在运行的实例了。如果有,应该退出当前实例并激活前一个实例的主窗口并显示给用户。

  将应用程序做成单实例的,可以通过mutex(Mutual Exclusion Semaphore)[互斥体(互斥信号量)]来实现。Windows应用程序通过Application.Run()方法来加载住窗体。在Main方法中,创建一个新的mutex。如果可以创建新的mutex,则允许应用程序运行。如果mutex已经被创建了,应用程序就不会启动。这样就能确保任何使用只有一个实例在运行。

 

None.gif
//
 用于检测是否创建了新的mutex
None.gif
bool
 newMutexCreated 
=
 
false
;
None.gif
//
 mutex的名字以Local\作为前缀,
None.gif
//
 确保将其创建在每会话(per-session)命名空间中,
None.gif
//
 而不是全局命名空间中。
None.gif
string
 mutexName 
=
 
"
Local\\
"
 
+
 
None.gif  System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
None.gif
None.gifMutex mutex 
=
 
null
;
None.gif
try
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
// 使用唯一的名字创建一个新的mutex
InBlock.gif
    mutex = new Mutex(false, mutexName, out newMutexCreated);
ExpandedBlockEnd.gif}
None.gif
catch
(Exception ex)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    MessageBox.Show (ex.Message
+"\n\n"+ex.StackTrace+ 
InBlock.gif         
"\n\n"+"Application Exitingdot.gif","Exception thrown");
InBlock.gif    Application.Exit ();
ExpandedBlockEnd.gif}
None.gif
None.gif
//
 如果是第一次创建mutex,则启动应用程序实例,
None.gif
//
 因为这是第一次运行
None.gif
if
(newMutexCreated)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    Application.Run(
new AnimatedWindowForm());
ExpandedBlockEnd.gif}

 

  当创建mutex时,其名字的前缀可以是Globa\或Local\。Global\前缀意味着该mutex将影响全局命名空间。

  以Local\为前缀意味着该mutex只会影响到用户会话命名空间。

  Windows XP和Windows 2003允许通过Terminal Services Sessions(终端服务会话)快速切换用户。因此如果mutext使用Global\前缀,在整个系统范围内只能有一个实例运行。如果一个用户启动了该应用程序,其他用户就无法在他们的会话中再次创建一个实例了。如果mutext的前缀不是Local\,它也只会影响每个会话。

  要了解Kernel Object命名空间,请阅读这篇MSDN文章()。

  现在,还有一个任务要完成——将前一个实例移到前台。在Windows应用程序中这意味着将应用程序主窗口恢复到顶端,如果已经隐藏,则显示给用户。

恢复前一个实例

  要恢复主窗口,必须要得到应用程序主窗口的句柄。通过下面这段代码可以得到进程的MainWindowHandle:

 

None.gif
Process[] currentProcesses 
=
 
None.gif    Process.GetProcessesByName(
"
SingleInstanceApplication
"
);
None.gifSystem.IntPtr mainWindowHandle 
=
 currentProcesses[
0
].MainWindowHandle;
None.gif
if
(mainWindowHandle 
!=
 IntPtr.Zero)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    ShowWindow(mainWindowHandle,SW_RESTORE); 
// Restore the Window 
InBlock.gif
    UpdateWindow(mainWindowHandle);
ExpandedBlockEnd.gif}

 

  但当应用程序的主窗口被隐藏时,这段代码会失败,因为句柄返回的是0。

  一个可靠的机制是使MainWindowHandle变成必需的。这就轮到共享内存上场了。共享内存是IPC(Inter Process Communication,进程间通信)的一种方法,使用这种方法,两个或更多个进程可以使用共享的内存片段进行通信。在C#中创建共享内存可以使用Win32 API调用。内存映射可以将文件内容关联到你的进程地址空间或系统页文件或系统内存的特定地址中的一个特定的地址区域。

  要在两个进程之间共享数据,需要在系统页文件中创建共享内存。

  为了使一个进程能够将通过内存映射文件(Memory Mapped File,MMF)将数据共享给其他进程,每个进程都必须访问该文件。这通过为MMF对象起一个名字来实现,每个进程都能够使用这个名字来访问共享内存。

 

None.gif
private
 
const
 
int
 INVALID_HANDLE_VALUE 
=
 
-
1
;
None.gif
private
 
const
 
int
 FILE_MAP_WRITE 
=
 
0x2
;
None.gif
private
 
const
 
int
 FILE_MAP_READ 
=
 
0x0004
;
None.gif
None.gif[DllImport(
"
kernel32.dll
"
,EntryPoint
=
"
OpenFileMapping
"
None.gif              SetLastError
=
true
, CharSet
=
CharSet.Auto) ]
None.gif
private
 
static
 
extern
 IntPtr OpenFileMapping (
int
 
None.gif    wDesiredAccess, 
bool
 bInheritHandle,String lpName );
None.gif
None.gif[DllImport(
"
Kernel32.dll
"
,EntryPoint
=
"
CreateFileMapping
"
None.gif                 SetLastError
=
true
,CharSet
=
CharSet.Auto)]
None.gif
None.gif
private
 
static
 
extern
 IntPtr CreateFileMapping(
int
 hFile, 
None.gif        IntPtr lpAttributes, 
uint
 flProtect, 
None.gif        
uint
 dwMaximumSizeHigh, 
uint
 dwMaximumSizeLow, 
None.gif        
string
 lpName);
None.gif    
None.gif[DllImport(
"
Kernel32.dll
"
)]
None.gif
private
 
static
 
extern
 IntPtr MapViewOfFile(IntPtr hFileMappingObject, 
None.gif        
uint
 dwDesiredAccess, 
uint
 dwFileOffsetHigh, 
None.gif        
uint
 dwFileOffsetLow, 
uint
 dwNumberOfBytesToMap);
None.gif    
None.gif[DllImport(
"
Kernel32.dll
"
,EntryPoint
=
"
UnmapViewOfFile
"
None.gif        SetLastError
=
true
,CharSet
=
CharSet.Auto)]
None.gif
private
 
static
 
extern
 
bool
 UnmapViewOfFile(IntPtr lpBaseAddress);
None.gif
None.gif[DllImport(
"
kernel32.dll
"
,EntryPoint
=
"
CloseHandle
"
None.gif        SetLastError
=
true
,CharSet
=
CharSet.Auto)]
None.gif
private
 
static
 
extern
 
bool
 CloseHandle(
uint
 hHandle);
None.gif[DllImport(
"
kernel32.dll
"
,EntryPoint
=
"
GetLastError
"
None.gif        SetLastError
=
true
,CharSet
=
CharSet.Auto)]
None.gif
None.gif
private
 
static
 
extern
 
uint
 GetLastError();
None.gif
private
 IntPtr memoryFileHandle;
None.gif
None.gif
public
 
enum
 FileAccess : 
int
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    ReadOnly 
= 2,
InBlock.gif    ReadWrite 
= 4
ExpandedBlockEnd.gif}

 

  为共享内存对象创建新的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——文件映射对象的名字。

 

None.gif
public
 
static
 MemoryMappedFile CreateMMF(
string
 fileName, FileAccess access, 
int
 size)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
if(size < 0)
InBlock.gif        
throw new ArgumentException("The size parameter" + 
InBlock.gif            
" should be a number greater than Zero.");
InBlock.gif
InBlock.gif    IntPtr memoryFileHandle 
= CreateFileMapping (0xFFFFFFFF
InBlock.gif        IntPtr.Zero,(
uint)access,0,(uint)size,fileName);
InBlock.gif
InBlock.gif    
if(memoryFileHandle == IntPtr.Zero)
InBlock.gif        
throw new SharedMemoryException("Creating Shared Memory failed.");
InBlock.gif
InBlock.gif    
return new MemoryMappedFile(memoryFileHandle);
ExpandedBlockEnd.gif}
None.gif

 

  下面我们启动应用程序的第一个实例,创建MMF对象。

 

None.gif
//
 当第一次创建mutex时,运行程序,因为这是第一个实例。
None.gif
if
(newMutexCreated)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
//Create the Shared Memory to store the window handle.
InBlock.gif
    lock(typeof(AnimatedWindowForm))
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        sharedMemory 
= MemoryMappedFile.CreateMMF("Local\\" + 
InBlock.gif            
"sharedMemoryAnimatedWindow"
InBlock.gif            MemoryMappedFile.FileAccess .ReadWrite, 
8);
ExpandedSubBlockEnd.gif    }
InBlock.gif    Application.Run(
new AnimatedWindowForm());
ExpandedBlockEnd.gif}

 

  一旦得到了内存映射文件的句柄,就可以用它来将文件视图映射到调用进程的地址空间。只要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()函数来取消映射。其惟一必须的参数就是映射视图的句柄。

 

None.gif
UnmapViewOfFile(mappedViewHandle);

 

  为了能够写入共享内存,首先需要使用FILE_MAP_WRITE创建MMF对象的映射视图。由于我们要在主窗口中写入该句柄,可以使用Marshal.WriteIntPtr()方法写入共享内存。写入操作完成后,需要取消映射视图,最后通过调用CloseHandle()函数释放映射视图。

 

None.gif
public
 
void
 WriteHandle(IntPtr windowHandle)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    IntPtr mappedViewHandle 
= MapViewOfFile(memoryFileHandle, 
InBlock.gif        (
uint)FILE_MAP_WRITE,0,0,8);
InBlock.gif    
if(mappedViewHandle == IntPtr.Zero)
InBlock.gif        
throw new SharedMemoryException("Creating" + 
InBlock.gif            
" a view of Shared Memory failed.");
InBlock.gif
InBlock.gif    Marshal.WriteIntPtr(mappedViewHandle,windowHandle );
InBlock.gif
InBlock.gif    UnmapViewOfFile(mappedViewHandle);
InBlock.gif    CloseHandle((
uint)mappedViewHandle);
ExpandedBlockEnd.gif}
None.gif

 

  要读取共享内存,需要使用FILE_MAP_READ访问创建MMF对象的映射视图。使用Marshal.ReadIntPtr()方法来读取共享内存。完成读取操作后,取消映射视图并调用CloseHandle()函数释放映射视图。

 

None.gif
public
 
static
 IntPtr ReadHandle(
string
 fileName)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    IntPtr mappedFileHandle 
= 
InBlock.gif      OpenFileMapping((
int)FileAccess.ReadWrite, false, fileName);
InBlock.gif
InBlock.gif    
if(mappedFileHandle == IntPtr.Zero)
InBlock.gif        
throw new SharedMemoryException("Opening the" + 
InBlock.gif                    
" Shared Memory for Read failed.");
InBlock.gif
InBlock.gif    IntPtr mappedViewHandle 
= MapViewOfFile(mappedFileHandle, 
InBlock.gif                                        (
uint)FILE_MAP_READ,0,0,8);
InBlock.gif    
if(mappedViewHandle == IntPtr.Zero)
InBlock.gif        
throw new SharedMemoryException("Creating" + 
InBlock.gif                  
" a view of Shared Memory failed.");
InBlock.gif
InBlock.gif    IntPtr windowHandle 
= Marshal.ReadIntPtr(mappedViewHandle);
InBlock.gif    
if(windowHandle == IntPtr.Zero)
InBlock.gif        
throw new ArgumentException ("Reading from the specified" + 
InBlock.gif                             
" address in  Shared Memory failed.");
InBlock.gif
InBlock.gif    UnmapViewOfFile(mappedViewHandle);
InBlock.gif    CloseHandle((
uint)mappedFileHandle);
InBlock.gif    
return windowHandle;
ExpandedBlockEnd.gif}

 

  当应用程序主窗口句柄创建之后,我们就将其写入共享内存。

 

None.gif
protected
 
override
 
void
 OnHandleCreated(EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
base.OnHandleCreated (e);
InBlock.gif    IntPtr mainWindowHandle 
= this.Handle;
InBlock.gif    
try
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
lock(this)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
//Write the handle to the Shared Memory 
InBlock.gif
            sharedMemory.WriteHandle (mainWindowHandle);
ExpandedSubBlockEnd.gif        }
ExpandedSubBlockEnd.gif    }
InBlock.gif    
catch(Exception ex)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        MessageBox.Show (ex.Message
+ "\n\n"+ex.StackTrace+ 
InBlock.gif             
"\n\n"+ "Application Exitingdot.gif","Exception thrown");
InBlock.gif        Application.Exit();
ExpandedSubBlockEnd.gif    }
ExpandedBlockEnd.gif}

 

  当用户尝试启动应用程序的第二个实例时,从共享内存中可以得到前一个实例的窗口句柄,并使用ShowWindow()和UpdateWindow()函数恢复主窗口。

 

None.gif
//
 如果mutex已经存在,不需要启动应用程序的新实例,
None.gif
//
 因为前一个实例已经在运行了。
None.gif
try
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
// 获取程序主窗口的句柄,
InBlock.gif    
// 该句柄由前一个实例存储到共享内存中。
InBlock.gif
    IntPtr mainWindowHandle = System.IntPtr.Zero;
InBlock.gif    
lock(typeof(AnimatedWindowForm))
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        mainWindowHandle 
= MemoryMappedFile.ReadHandle("Local" + 
InBlock.gif                                
"\\sharedMemoryAnimatedWindow");
ExpandedSubBlockEnd.gif    }
InBlock.gif
InBlock.gif    
if(mainWindowHandle != IntPtr.Zero)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// Restore the Window 
InBlock.gif
        ShowWindow(mainWindowHandle,SW_RESTORE);
InBlock.gif        UpdateWindow(mainWindowHandle);
ExpandedSubBlockEnd.gif    }
ExpandedBlockEnd.gif}
None.gif
catch
(Exception ex)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    MessageBox.Show (ex.Message
+ "\n\n"+ex.StackTrace+ 
InBlock.gif           
"\n\n"+"Application Exitingdot.gif","Exception thrown");
ExpandedBlockEnd.gif}

 

  因此我们的应用程序的main方法看起来是下面这样的:

 

None.gif
static
 
void
 Main() 
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
// Used to check if we can create a new mutex
InBlock.gif
    bool newMutexCreated = false;
InBlock.gif    
// The name of the mutex is to be prefixed with Local\ to make
InBlock.gif    
// sure that its is created in the per-session
InBlock.gif    
// namespace, not in the global namespace.
InBlock.gif
    string mutexName = "Local\\" + 
InBlock.gif      System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
InBlock.gif
InBlock.gif    Mutex mutex 
= null;
InBlock.gif    
try
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// Create a new mutex object with a unique name
InBlock.gif
        mutex = new Mutex(false, mutexName, out newMutexCreated);
ExpandedSubBlockEnd.gif    }
InBlock.gif    
catch(Exception ex)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        MessageBox.Show (ex.Message
+"\n\n"+ex.StackTrace+
InBlock.gif           
"\n\n"+"Application Exitingdot.gif","Exception thrown");
InBlock.gif        Application.Exit ();
ExpandedSubBlockEnd.gif    }
InBlock.gif
InBlock.gif    
// When the mutex is created for the first time
InBlock.gif    
// we run the program since it is the first instance.
InBlock.gif
    if(newMutexCreated)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// Create the Shared Memory to store the window
InBlock.gif        
// handle. This memory is shared between processes
InBlock.gif
        lock(typeof(AnimatedWindowForm))
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            sharedMemory 
= MemoryMappedFile.CreateMMF("Local" + 
InBlock.gif              
"\\sharedMemoryAnimatedWindow"
InBlock.gif              MemoryMappedFile.FileAccess .ReadWrite ,
8);
ExpandedSubBlockEnd.gif        }
InBlock.gif        Application.Run(
new AnimatedWindowForm());
ExpandedSubBlockEnd.gif    }
InBlock.gif    
else
InBlock.gif    
// If the mutex already exists, no need to launch
InBlock.gif    
// a new instance of the program because
InBlock.gif    
// a previous instance is running .
ExpandedSubBlockStart.gifContractedSubBlock.gif
    dot.gif{
InBlock.gif        
try
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif        
// Get the Program's main window handle,
InBlock.gif        
// which was previously stored in shared memory.
InBlock.gif
            IntPtr mainWindowHandle = System.IntPtr.Zero;
InBlock.gif            
lock(typeof(AnimatedWindowForm))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                mainWindowHandle 
= 
InBlock.gif                  MemoryMappedFile.ReadHandle(
"Local" + 
InBlock.gif                  
"\\sharedMemoryAnimatedWindow");
ExpandedSubBlockEnd.gif            }
InBlock.gif            
if(mainWindowHandle != IntPtr.Zero)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
// Restore the Window 
InBlock.gif
                ShowWindow(mainWindowHandle,SW_RESTORE);
InBlock.gif                UpdateWindow(mainWindowHandle);
ExpandedSubBlockEnd.gif            }
InBlock.gif            
return;
ExpandedSubBlockEnd.gif        }
InBlock.gif        
catch(Exception ex)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            MessageBox.Show (ex.Message
+"\n\n"+ex.StackTrace+ 
InBlock.gif              
"\n\n"+"Application Exitingdot.gif","Exception thrown");    
ExpandedSubBlockEnd.gif        }
InBlock.gif        
// Tell the garbage collector to keep the Mutex alive
InBlock.gif        
// until the code execution reaches this point,
InBlock.gif        
// ie. normally when the program is exiting.
InBlock.gif
        GC.KeepAlive(mutex);
InBlock.gif        
// Release the Mutex 
InBlock.gif
        try
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            mutex.ReleaseMutex();
ExpandedSubBlockEnd.gif        }
InBlock.gif        
catch(ApplicationException ex)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            MessageBox.Show (ex.Message 
+ "\n\n"+ ex.StackTrace, 
InBlock.gif                                            
"Exception thrown");    
InBlock.gif            GC.Collect();
ExpandedSubBlockEnd.gif        }
ExpandedSubBlockEnd.gif    }
ExpandedBlockEnd.gif}
None.gif

 

将窗口最小化到通知区域

  这包含四个任务:

  第一步是防止用户单击关闭按钮时关闭窗口,重写protected virtual OnClosing方法,取消Close事件。窗体应该被隐藏,而应用程序在后台运行。但当用户关闭系统时呢?操作系统会像所有打开着的窗口发送Close消息。如果我们的应用程序拒绝关闭窗口,系统将无法关闭,它会持续等待,直到所有窗口都关闭。因此我们需要重写WndProc需方法,处理WM_QUERYENDSESSION消息。

 

None.gif
protected
 
override
 
void
 OnClosing(CancelEventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
if(systemShutdown == true)
InBlock.gif        e.Cancel 
= false;
InBlock.gif    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        e.Cancel 
= true;
InBlock.gif        
this.AnimateWindow();
InBlock.gif        
this.Visible = false;
ExpandedSubBlockEnd.gif    }
ExpandedBlockEnd.gif}
None.gif
None.gif
protected
 
override
 
void
 WndProc(
ref
 Message m)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
// 一旦程序收到WM_QUERYENDSESSION消息,
InBlock.gif    
// 将systemShutdown布尔值设置为true。
InBlock.gif
    
InBlock.gif    
if(m.Msg == WM_QUERYENDSESSION)
InBlock.gif        systemShutdown 
= true;
InBlock.gif    
base.WndProc(ref m);
ExpandedBlockEnd.gif}
None.gif

 

  接下来,我们希望在任务栏的通知区域显示一个通知图标。向主窗体添加一个NotifyIcon控件并为其设置图标。该图标将会显示在任务栏的通知区域中。我们的下一个目的是实现窗口向通知区域靠拢的动画。在做这个动画之前,我们需要确保用户没有禁用系统中的窗口动画。用户可以通过设置HKeyCurrentUser\Control Panel\Desktop下的MinAnimate键来启用/禁用窗口动画。我们检查这个值,并根据用户的偏好来设置一个布尔值。

 

None.gif
RegistryKey animationKey 
=
 
None.gif  Registry.CurrentUser.OpenSubKey(
"
Control Panel
"
 
+
 
None.gif  
"
\\Desktop\\WindowMetrics
"
,
true
);
None.gif
object
 animKeyValue 
=
 animationKey.GetValue(
"
MinAnimate
"
);
None.gif            
None.gif
if
(System.Convert.ToInt32 (animKeyValue.ToString()) 
==
 
0
)
None.gif    
this
.AnimationDisabled 
=
 
true
;
None.gif
else
None.gif
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了。

 

None.gif
private
 
void
 AnimateWindow()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    
// if the user has not disabled animating windowsdot.gif
InBlock.gif
    if(!this.AnimationDisabled)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        RECT animateFrom 
= new RECT();
InBlock.gif        GetWindowRect(
this.Handle, ref animateFrom);
InBlock.gif
InBlock.gif        RECT animateTo 
= new RECT ();
InBlock.gif        IntPtr notifyAreaHandle 
= GetNotificationAreaHandle();
InBlock.gif
InBlock.gif        
if (notifyAreaHandle != IntPtr.Zero)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
if ( GetWindowRect(notifyAreaHandle, ref animateTo) == true)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                DrawAnimatedRects(
this.Handle, 
InBlock.gif                     IDANI_CAPTION,
ref animateFrom,ref animateTo);
ExpandedSubBlockEnd.gif            }
ExpandedSubBlockEnd.gif        }
ExpandedSubBlockEnd.gif    }
ExpandedBlockEnd.gif}
None.gif
None.gif
private
 IntPtr GetNotificationAreaHandle()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
InBlock.gif    IntPtr hwnd 
= FindWindowEx(IntPtr.Zero,IntPtr.Zero,"Shell_TrayWnd",null);
InBlock.gif    hwnd 
= FindWindowEx(hwnd , IntPtr.Zero ,"TrayNotifyWnd",null);
InBlock.gif    hwnd 
= FindWindowEx(hwnd , IntPtr.Zero ,"SysPager",null);
InBlock.gif    
InBlock.gif    
if (hwnd != IntPtr.Zero)
InBlock.gif        hwnd 
= FindWindowEx(hwnd , IntPtr.Zero ,null,"Notification Area");
InBlock.gif
InBlock.gif    
return hwnd;        
ExpandedBlockEnd.gif}
None.gif

 

结论

  诚然,获取通知区域的窗口句柄有的时候会失败,因为“TrayNotifyWnd”、“SysPager”和“Notification Area”都是undocumented(非编档)的窗口类名,可能在未来的Windows版本中有所变化。

已知问题

  在应用程序的Debug版本和Release版本之间存在着一个冲突。如果首先启动了Release版本,然后用户再启动Debug版本,则两个实例都会运行。Mutex不能在开始时组织第二个实例的起动。

你可能感兴趣的文章
Eclipse整合Tomcat开发Dynamic Web Project环境总结
查看>>
实战博客园访问流量计数器-三步操作简化教程
查看>>
JDBC与JAVA数据库编程
查看>>
Android开发之旅:环境搭建及HelloWorld
查看>>
Red Hat Enterprise Linux 各个版本以及发布日期
查看>>
J2EE全面介绍
查看>>
深入浅出Cocoa多线程编程之 block 与 dispatch quene
查看>>
UIWebView
查看>>
并发集合(三)使用阻塞线程安全的列表
查看>>
【机房合作】状态模式与上机
查看>>
iOS中alloc与init
查看>>
Raw Sockets programming on Linux with C
查看>>
纸上谈兵: AVL树[转]
查看>>
SpriteBuilder中粒子发射器的reset on visibility toggle选项解释
查看>>
深入浅出jackrabbit之十三 查询之AST和QT
查看>>
动态规划算法计算网络的最长路线和最短路线
查看>>
eclipse中ant build 控制台乱码解决解决方法(ant执行java)
查看>>
搭建Maven私服(使用Nexus)
查看>>
采集数据库中未绑定变量的sql
查看>>
一个统计网站访问IP的实例
查看>>