無意中看到 Python 有 PyAutoGUI 這東東可以做些自動化操作 , 手癢也來用 c# 玩玩 , 沒很認真找不曉得有無比較完整的 Lib 能用 , 只好半土炮來兜 這類東東做法跟爬蟲類似 , 不外乎就是抓取按鈕在螢幕上的座標位置然後點選 or 輸入文字 開工第一步想要抓 Global 的滑鼠位置馬上就陣亡了 , 所以參考這個 Lib 來達成目的
撈滑鼠座標 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private IKeyboardMouseEvents m_GlobalHook; public void Subscribe() { // Note: for the application hook, use the Hook.AppEvents() instead m_GlobalHook = Hook.GlobalEvents(); m_GlobalHook.MouseMove += M_GlobalHook_MouseMove; } private void M_GlobalHook_MouseMove( object sender, MouseEventArgs e ) { Color c = GetPixel( e.Location ); lblThumbnail.BackColor = c; int x = e.Location.X; int y = e.Location.Y; lblMousePos.Text = $"目前位置 (x:{x},y:{y})"; lblColor.Text = $"RGBA ({c.R},{c.G},{c.B},{c.A})"; }
撈目前座標顏色 1 2 3 4 5 6 7 8 9 10 11 12 //https://rosettacode.org/wiki/Color_of_a_screen_pixel#C.23 Color GetPixel( Point position ) { using (var bitmap = new Bitmap( 1, 1 )) { using (var graphics = Graphics.FromImage( bitmap )) { graphics.CopyFromScreen( position, new Point( 0, 0 ), new Size( 1, 1 ) ); } return bitmap.GetPixel( 0, 0 ); } }
trigger 滑鼠動作 用 mouse_event 執行滑鼠 click or double click
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 //This is a replacement for Cursor.Position in WinForms [System.Runtime.InteropServices.DllImport("user32.dll")] static extern bool SetCursorPos(int x, int y); [System.Runtime.InteropServices.DllImport("user32.dll")] public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo); public const int MOUSEEVENTF_LEFTDOWN = 0x02; public const int MOUSEEVENTF_LEFTUP = 0x04; //This simulates a left mouse click public static void LeftMouseClick(int xpos, int ypos) { SetCursorPos(xpos, ypos); mouse_event(MOUSEEVENTF_LEFTDOWN, xpos, ypos, 0, 0); mouse_event(MOUSEEVENTF_LEFTUP, xpos, ypos, 0, 0); } public static void MouseDoubleClick(int xpos, int ypos) { mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, xpos, ypos, 0, 0); Thread.Sleep(150); mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, xpos, ypos, 0, 0); }
trigger 鍵盤輸入 用 SendKeys.Send 送出需要輸入的文字 , 注意要用 Thread 睡一下
1 2 3 4 5 Cursor.Position = new Point( 441, 371 ); LeftMouseClick( 441, 371 ); SendKeys.Send( textBoxId.Text ); Thread.Sleep( 200 );
開啟程式 開啟 IE or 其他 Application 用 return Process 方便執行完就 kill
1 2 3 4 5 6 7 8 9 10 Process OpenIE() { Process ps = new Process(); string url = "http://xxx.xxx.xxx"; ps.StartInfo.FileName = "iexplore.exe"; ps.StartInfo.Arguments = url; ps.Start(); return ps; }
Outlook 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Runtime.InteropServices; using Microsoft.Office.Interop.Outlook; using System.Diagnostics; using System.Text.RegularExpressions; namespace LazyHelper { public partial class FormLog : Form { Microsoft.Office.Interop.Outlook.Application outlookApplication = null; NameSpace outlookNamespace = null; MAPIFolder inboxFolder = null; public FormLog() { InitializeComponent(); this.Load += FormLog_Load; this.buttonLog.Click += ButtonLog_Click; this.FormClosing += FormLog_FormClosing; richTextBoxLog.ReadOnly = true; richTextBoxLog.BackColor = Color.White; } private void FormLog_FormClosing( object sender, FormClosingEventArgs e ) { if (e.CloseReason == CloseReason.UserClosing) { e.Cancel = true; this.Hide(); } } protected override void OnFormClosed( FormClosedEventArgs e ) { //ReleaseComObject( mailItems ); ReleaseComObject( inboxFolder ); ReleaseComObject( outlookNamespace ); ReleaseComObject( outlookApplication ); base.OnFormClosed( e ); } string FetchLog( string folder, string keyword) { var sb = new StringBuilder(); var develop = inboxFolder.Folders[folder]; var filter = develop.Items.Cast<MailItem>() .Where( x => x.Subject.Contains( keyword ) ) .OrderByDescending( x => x.CreationTime ) .Take( 20 ) .ToList(); filter.ForEach( item => { sb.AppendLine( "Time: " + item.CreationTime ); sb.AppendLine( "From: " + item.SenderEmailAddress ); sb.AppendLine( "To: " + item.To ); sb.AppendLine( "" ); sb.AppendLine( "Subject: " + item.Subject ); sb.AppendLine( item.Body ); Marshal.ReleaseComObject( item ); } ); return sb.ToString(); } async Task<string> FetchLogAsync() { return await Task.Run( () => { try { return FetchLog(textBoxFolder.Text , textBoxSubjectFilter.Text); } catch (System.Exception ex) { Console.WriteLine( "{0} Exception caught: ", ex ); throw ex; } } ); } private async void ButtonLog_Click( object sender, EventArgs e ) { buttonLog.Enabled = false; labelStatus.Text = "Loading..."; richTextBoxLog.Text = string.Empty; richTextBoxLog.Text = await FetchLogAsync(); //設定顏色 //注意會被覆蓋掉 setColor( "Error" ); setColor( "([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])" ); buttonLog.Enabled = true; labelStatus.Text = "Ready"; } void setColor(string pattern) { //這樣選不 ok richTextBoxLog.SelectAll(); richTextBoxLog.SelectionColor = Color.Black; richTextBoxLog.SelectionBackColor = Color.White; Regex regex = new Regex( pattern ); MatchCollection matches = regex.Matches( richTextBoxLog.Text ); if (matches.Count > 0) { foreach (Match m in matches) { richTextBoxLog.Select( m.Index, m.Length ); richTextBoxLog.SelectionColor = Color.Red; richTextBoxLog.SelectionBackColor = Color.Black; } } } private void FormLog_Load( object sender, EventArgs e ) { var existing = Process.GetProcessesByName( "OUTLOOK" ).Any(); outlookApplication = existing ? Marshal.GetActiveObject( "Outlook.Application" ) as Microsoft.Office.Interop.Outlook.Application : new Microsoft.Office.Interop.Outlook.Application(); outlookNamespace = outlookApplication.GetNamespace( "MAPI" ); inboxFolder = outlookNamespace.GetDefaultFolder( OlDefaultFolders.olFolderInbox ); var develop = inboxFolder.Folders["Develop"]; develop.Items.ItemAdd += Items_ItemAdd; //develop.Items.ItemRemove += Items_ItemRemove; } private void Items_ItemAdd( object Item ) { MailItem item = (MailItem)Item; var sb = new StringBuilder(); sb.AppendLine( "Time: " + item.CreationTime ); sb.AppendLine( "From: " + item.SenderEmailAddress ); sb.AppendLine( "To: " + item.To ); sb.AppendLine( "" ); sb.AppendLine( "Subject: " + item.Subject ); sb.AppendLine( item.Body ); richTextBoxLog.BeginInvoke( new System.Action( ()=> { richTextBoxLog.Text += sb.ToString(); } )); Marshal.ReleaseComObject( item ); } private void Items_ItemRemove() { Console.WriteLine("remove"); richTextBoxLog.BeginInvoke( new System.Action( ()=> { richTextBoxLog.Text = "remove"; } )); } private static void ReleaseComObject( object obj ) { if (obj != null) { Marshal.ReleaseComObject( obj ); obj = null; } } } }
Singleton 的 WPF 程式 複寫 OnStartup 的內容 , 並且把 app.xaml 的 startup 拿掉 使用 Shutdown 的原因是會關閉這個準備要開啟的 Thread (Instance)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); Mutex m = new Mutex(true , "Singleton" , out bool isExists); if (isExists != true) { var findWindow = FindWindow( null, "Singleton" ); if (findWindow!=IntPtr.Zero) { SetForegroundWindow( findWindow ); } Shutdown(); } var window1 = new Window1(); window1.Show(); } [DllImport("User32" , CharSet = CharSet.Unicode)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("User32" , CharSet = CharSet.Unicode)] static extern IntPtr SetForegroundWindow(IntPtr hWnd); }
這個 case 比較特別 , 主要是有做縮到右下角 icon 的功能 , 如果沒加上 ShowWindowAsync 會 show 不出來
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault( false ); Mutex m = new Mutex(true, "Singleton", out bool created); //程式的沒開的話才跑進來 if (created != true) { //找這個 window 的指標 (instance) var findWindow = FindWindow(null, "Singleton"); if (findWindow != IntPtr.Zero) { //這邊要加上 ShowWindowAsync 這樣子如果縮小成右下角的 Notify Icon 才會有顯示的效果 //https://dotblogs.com.tw/chou/2009/06/30/9049 ShowWindowAsync( findWindow, WS_SHOWNORMAL ); //設定到前景 SetForegroundWindow(findWindow); } Environment.Exit(0); } Application.Run( new Form1() ); } [DllImport( "user32.dll", CharSet = CharSet.Unicode)] static extern IntPtr FindWindow( string lpClassName, string lpWindowName ); [DllImport("User32.dll" , CharSet = CharSet.Unicode)] private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow); [DllImport("User32.dll" , CharSet = CharSet.Unicode)] private static extern bool SetForegroundWindow(IntPtr hWnd); private const int WS_SHOWNORMAL = 1; }
錯誤處理 WPF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); Dispatcher.UnhandledException += Dispatcher_UnhandledException; } private void Dispatcher_UnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { //Log Here Debug.Write($"{e.Exception.ToString()}"); e.Handled = true; } }
1 2 3 4 5 6 7 8 9 10 11 <Button Content="AAA" Width="50" Height="50" > <Button.ToolTip> <Grid> <Image Width="200" Height="200"> <Image.Source> <BitmapImage UriSource="nono.jpg"></BitmapImage> </Image.Source> </Image> </Grid> </Button.ToolTip> </Button>