Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9529

C# - 通知領域に常駐してタスクバーに接するFormを力技で表示する - マルチディスプレイ対応

$
0
0

環境

Windows10。例によってライブラリレスです。

スクショ

アイコンは面倒なので、ただの青い四角にしてます。
アイコンをクリックすると、表示・非表示を切り替えます。

例として、右配置と上配置のスクショを載せておきます。

右配置

image.png

上配置

image.png

やっていること

以下のような感じ。Formは起動時に生成しておく。

(1). 通知領域に常駐する(NotifyIcon)
(2). クリックするとタスクバーを探す
(2-1). タスクバーをWin32API使って検出する(さらに、プロセスが正規のパスにあるexplorer.exeであることもチェックする)
(2-2). タスクバーの矩形領域(スクリーン上の座標とサイズ)を取得する
(2-3). タスクバーの所属するスクリーンを判定し、座標とサイズをもとに、タスクバーが左右上下のどこに配置されているかを判定する
(3). クリックされた位置とタスクバーの配置情報をもとに、表示するFormの位置を決める

ソースコード

usingSystem;usingSystem.ComponentModel;usingSystem.Diagnostics;usingSystem.Drawing;usingSystem.IO;usingSystem.Runtime.InteropServices;usingSystem.Text;usingSystem.Windows.Forms;internalclassTaskbarInfo{publicIntPtrWindowHandle{get;privateset;}bool_lockInfo;// trueを設定時、情報更新を停止させるRectangle_windowRect;Rectangle_screenRect;publicenumTaskbarDockStyle{Top=0,Bottom=1,Left=2,Right=3}// -----------------------------------------------------------publicvoidUpdateInfo(){UpdateInfo(false);}publicboolUpdateInfo(boolthrowError){boolretCode;NativeMethods.WINDOWINFOwi;wi=MyGetWindowInfo(WindowHandle,outretCode);if(retCode){_windowRect=wi.rcWindow.rect;// タスクバーが配置されているスクリーンを取得するPointp=newPoint(_windowRect.X+_windowRect.Width/2,_windowRect.Y+_windowRect.Height/2);Screenscreen=Screen.FromPoint(p);_screenRect=screen.Bounds;}else{// failedthrownewWin32Exception(Marshal.GetLastWin32Error());}returnretCode;}publicvoidLockInfo(){UpdateInfo();_lockInfo=true;// trueを設定時、情報更新を停止させる}publicvoidUnlockInfo(){UpdateInfo();_lockInfo=false;}// -----------------------------------------------------------publicRectangleRect{get{if(!_lockInfo){UpdateInfo();}return_windowRect;}}publicSizeSize{get{if(!_lockInfo){UpdateInfo();}return_windowRect.Size;}}publicTaskbarDockStyleDock{get{if(!_lockInfo){UpdateInfo();}// ※※※ 以下、UpdateInfoが呼ばれないように、プロパティではなくフィールドを直接参照すること。// タスクバーの移動やスクリーン設定の変更などのタイミングによっては// おそらく、情報の整合性が取れない場合がありえる。// その場合は一番使われていそうな bottom を返すようにする。if(_screenRect.Width==_windowRect.Width){if(_screenRect.Top==_windowRect.Top){returnTaskbarDockStyle.Top;}elseif(_screenRect.Bottom==_windowRect.Bottom){returnTaskbarDockStyle.Bottom;}}if(_screenRect.Height==_windowRect.Height){if(_screenRect.Left==_windowRect.Left){returnTaskbarDockStyle.Left;}elseif(_screenRect.Right==_windowRect.Right){returnTaskbarDockStyle.Right;}}returnTaskbarDockStyle.Bottom;}}// -----------------------------------------------------------staticreadonlystringPrimaryTaskbarClassName="Shell_TrayWnd";staticreadonlystringTaskbarExeName="explorer.exe";staticclassNativeMethods{[DllImport("user32.dll",SetLastError=true,CharSet=CharSet.Auto)]publicstaticexternIntPtrFindWindowEx(IntPtrparentWnd,IntPtrpreviousWnd,stringclassName,stringwindowText);[DllImport("user32.dll",SetLastError=true,CharSet=CharSet.Auto)]publicstaticexternintGetClassName(IntPtrhWnd,StringBuilderlpClassName,intnMaxCount);[DllImport("user32.dll",SetLastError=true)]publicstaticexternintGetWindowThreadProcessId(IntPtrhWnd,outintlpdwProcessId);[DllImport("user32.dll",SetLastError=true)]publicstaticexternintGetWindowInfo(IntPtrhwnd,refWINDOWINFOpwi);[StructLayout(LayoutKind.Sequential)]publicstructWINDOWINFO{publicintcbSize;publicRECTrcWindow;publicRECTrcClient;publicintdwStyle;publicintdwExStyle;publicintdwWindowStatus;publicuintcxWindowBorders;publicuintcyWindowBorders;publicshortatomWindowType;publicshortwCreatorVersion;}[StructLayout(LayoutKind.Sequential)]publicstructRECT{publicintleft;publicinttop;publicintright;publicintbottom;publicintwidth{get{returnright-left;}}publicintheight{get{returnbottom-top;}}publicRectanglerect{get{returnnewRectangle(left,top,width,height);}}}}privateTaskbarInfo(IntPtrwindowHandle){_lockInfo=false;WindowHandle=windowHandle;UpdateInfo(true);}publicstaticTaskbarInfoGetPrimaryTaskbarInfo(){stringexpectedExePath=Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows),TaskbarExeName).ToLowerInvariant();IntPtrhWnd=IntPtr.Zero;while(IntPtr.Zero!=(hWnd=NativeMethods.FindWindowEx(IntPtr.Zero,hWnd,PrimaryTaskbarClassName,""))){intpid;NativeMethods.GetWindowThreadProcessId(hWnd,outpid);Processp=Process.GetProcessById(pid);ProcessModulepm=p.MainModule;// ※MainModuleでよいのかよくわからない//Console.WriteLine(pm.FileName);if(pm.FileName.ToLowerInvariant()==expectedExePath){// c:\windows\explorer.exe がつくった正規のタスクバーと判定したreturnCreateTaskbarInfo(hWnd);}}returnnull;// failed}staticTaskbarInfoCreateTaskbarInfo(IntPtrwindowHandle){try{returnnewTaskbarInfo(windowHandle);}catch(Win32Exceptione){Console.WriteLine(e);}returnnull;}publicRectangleCalcNearRect(PointbaseP,Sizesz){boolbackup=_lockInfo;try{_lockInfo=true;vardock=Dock;Pointp=newPoint();switch(dock){caseTaskbarDockStyle.Left:p.X=_windowRect.Right;p.Y=baseP.Y-sz.Height/2;break;caseTaskbarDockStyle.Right:p.X=_windowRect.Left-sz.Width;p.Y=baseP.Y-sz.Height/2;break;caseTaskbarDockStyle.Top:p.X=baseP.X-sz.Width/2;p.Y=_windowRect.Bottom;break;caseTaskbarDockStyle.Bottom:p.X=baseP.X-sz.Width/2;p.Y=_windowRect.Top-sz.Height;break;}/*
            if ( p.X + sz.Width > _screenRect.Right ) {
                p.X = _screenRect.Right - sz.Width;
            }
            if ( p.X < _screenRect.Left ) {
                p.X = _screenRect.Left;
            }
            if ( p.Y + sz.Height > _screenRect.Bottom ) {
                p.Y = _screenRect.Bottom - sz.Height;
            }
            if ( p.Y < _screenRect.Top ) {
                p.Y = _screenRect.Top;
            }
            */returnnewRectangle(p,sz);}finally{_lockInfo=backup;}}// if GetWindowInfo failed, retCode = false.staticNativeMethods.WINDOWINFOMyGetWindowInfo(IntPtrhWnd,outboolretCode){varwi=newNativeMethods.WINDOWINFO();wi.cbSize=Marshal.SizeOf(wi);retCode=(NativeMethods.GetWindowInfo(hWnd,refwi)!=0);returnwi;}}classJohchuTest:Form{NotifyIconnotifyIcon;Buttonbtn;JohchuTest(){this.Visible=false;this.ShowInTaskbar=false;this.WindowState=FormWindowState.Minimized;this.ControlBox=false;this.Text="";this.FormBorderStyle=FormBorderStyle.FixedToolWindow;this.StartPosition=FormStartPosition.Manual;//this.Visible = false;this.Size=newSize(200,150);btn=newButton(){Text="Exit"};btn.Click+=(s,e)=>{MyExit();};btn.Location=newPoint((200-btn.Width)/2,(150-btn.Height)/2);Controls.Add(btn);notifyIcon=newNotifyIcon();notifyIcon.Icon=Create16x16Icon();notifyIcon.Visible=true;notifyIcon.MouseClick+=NotifyIcon_MouseClick;varmenu=newContextMenuStrip();menu.Items.AddRange(newToolStripMenuItem[]{newToolStripMenuItem("E&xit",null,(s,e)=>{MyExit();},"Exit")});notifyIcon.ContextMenuStrip=menu;//HandleCreated+=(s,e)=>{Console.WriteLine("HandleCreated event occured.");};//Load+=(s,e)=>{Console.WriteLine("Load event occured.");};//Shown+=(s,e)=>{Console.WriteLine("Shown event occured.");};//Activated+=(s,e)=>{Console.WriteLine("Activated event occured.");};}voidNotifyIcon_MouseClick(objectsender,MouseEventArgse){if(e.Button==MouseButtons.Left){if(this.WindowState==FormWindowState.Normal){this.WindowState=FormWindowState.Minimized;}else{try{this.Opacity=0;// 移動前に表示されてしまうので透過させておくthis.WindowState=FormWindowState.Normal;MoveFormTo(Cursor.Position);}finally{this.Opacity=1;}}}}boolMoveFormTo(Pointp){TaskbarInfotaskbar=TaskbarInfo.GetPrimaryTaskbarInfo();Rectanglerect=taskbar.CalcNearRect(p,Size);this.Location=rect.Location;if(taskbar==null){returnfalse;}returntrue;}// -------------------------------------------staticIconCreate16x16Icon(){Bitmapbmp=newBitmap(16,16);using(Graphicsg=Graphics.FromImage(bmp)){g.Clear(Color.Blue);}returnIcon.FromHandle(bmp.GetHicon());}voidMyExit(){vare=newCancelEventArgs();notifyIcon.Visible=false;Application.Exit(e);if(e.Cancel){Console.WriteLine("Application.Exit is canceled.");notifyIcon.Visible=true;}}[STAThread]staticvoidMain(string[]args){//Application.EnableVisualStyles();Application.Run(newJohchuTest());}}

マルチディスプレイ(マルチスクリーン)で注意すべきこと

  • マルチ画面になると、画面の左上=(0,0)とは限らなくなる。
  • 主画面(PrimaryScreen)が右側や下側にくる場合がありえる。

マルチディスプレイにおけるタスクバー

  • 通知領域は主タスクバー(Shell_TrayWnd)にしか表示されない。
  • PrimaryScreen以外の画面にShell_TrayWndを置くことができる。
    (つまり、主画面に主タスクバーがいない場合がある。)

感想

.NETではタスクバーは取り残されているような気がする。
(NotifyIconはあるものの、お作法的なものがあまり見つけられない。)
わりと需要ある機能だと思うが、標準で機能提供されてないものか。

参考サイト


Viewing all articles
Browse latest Browse all 9529

Trending Articles