[C#] 軟硬結合第二篇——酷我音樂盒的逆天玩法
5.1、C#串口通信
5.1.1、獲取當前可用串口列表
復制代碼
1 //Get all port list for selection
2 //獲得所有的端口列表,並顯示在列表內
3 PortList.Items.Clear();
4 string[] Ports = SerialPort.GetPortNames();
5
6 for (int i = 0; i < Ports.Length; i++)
7 {
8 string s = Ports[i].ToUpper();
9 Regex reg = new Regex("[^COM\\d]", RegexOptions.IgnoreCase | RegexOptions.Multiline);//正則表達式
10 s = reg.Replace(s, "");
11
12 PortList.Items.Add(s);
13 }
14 if (Ports.Length >1) PortList.SelectedIndex = 1;
復制代碼
調用串口要引用 using System.IO.Ports;
第9行的正則表達式要引用 using System.Text.RegularExpressions;
第3行的PortList是那個下拉框;
整體的功能就是通過第4行的函數獲取所有可用串口,然後加入下拉框顯示,如果有可用的就把第一個選中;
5.1.2、串口連接按鈕事件
復制代碼
1 private void btn_link_Click(object sender, EventArgs e)
2 {
3 if (!Connection.IsOpen)
4 {
5 //Start
6 Status = "正在連接...";
7 Connection = new SerialPort();
8 btn_link.Enabled = false;
9 Connection.PortName = PortList.SelectedItem.ToString();
10 Connection.Open();
11 Connection.ReadTimeout = 10000;
12 Connection.DataReceived += new SerialDataReceivedEventHandler(PortDataReceived);
13 Status = "連接成功";
14 }
15 }
復制代碼
PS:整體很好理解就是把下拉框選中的串口號連接上,這裡第12行比較重要,它調用SerialDataReceivedEventHandler(Func Name)來定義一個數據接收函數的句柄,這裡PortDataReceived你可以隨便寫,但是接下來你要寫對應的實現函數:(這裡說句柄比較難理解,你就理解成一個函數,綁定串口的函數,一旦串口有數據發動過來就執行這個函數....)
復制代碼
1 //接收串口數據
2 private int num=0; //障礙物進入范圍的時間
3 private bool enter=false; //是否有障礙物進入
4 private int signal=0; //對每次進入范圍的時間分段形成控制信號
5 private void PortDataReceived(object o, SerialDataReceivedEventArgs e)
6 {
7 int length = 1;
8 byte[] data = new byte[length];
9 Connection.Read(data, 0, length);
10 for (int i = 0; i < length; i++)
11 {
12 ReceivedData = string.Format("{0}",data[i]);
13 }
14
15 //數據濾波轉換為控制信號
16 if (data[0] != 136 && !enter){ //當有障礙物進入時,傳過來數據不是136並且是第一個
17 enter = true;
18 num = 1;
19 }else if (data[0] == 136 && enter){ //當障礙物離開時,傳過來數據變為136且是第一個
20 enter = false;
21 if (num > 1 && num < 6){
22 signal = 1;
23 }else if (num > 5 && num < 10){
24 signal = 2;
25 }else if (num > 9){
26 signal = 3;
27 }
28 num = 0;
29 }else if (data[0] != 136 && data[0] >= 0 && enter){
30 num++;
31 }
32 }
復制代碼
PS:這就是串口數據接收函數實現,先別看其他內容,因為裡面涉及濾波算法和控制信號生成的算法,只要看第7~13行的代碼核心部分就是第9行從緩沖區讀取串口數據放到data[]數組中,這樣串口數據就放在data[]中啦!怎麼處理是下面的事啦~
5.1.3、重量級功能函數:
復制代碼
1 /// <summary>
2 /// 模擬鼠標點擊函數
3 /// </summary>
4 /// <param name="n_control_type">0是上一曲,1是暫停,2是下一曲</param>
5 public void func(int n_control_type)
6 {
7 //bool isVisabled; //窗口原來狀態,隱藏還是顯示
8 IntPtr hCurWin = GetForegroundWindow(); //獲取當前激活窗口
9
10 IntPtr hMusic = FindWindow("kwmusicmaindlg", null); //找到窗口句柄
11 if (hMusic == null)
12 {
13 return;
14 }
15 Point pt; //獲取鼠標當前位置
16 GetCursorPos(out pt);
17 ShowWindow(hMusic,SW_SHOWNORMAL); //如果是隱藏的就讓他正常顯示出來
18 SetForegroundWindow(hMusic); //將音樂盒窗口放在最上層
19
20 RECT rect = new RECT(); //獲取窗口矩形
21 GetWindowRect(hMusic, ref rect);
22 int width = rect.Right - rect.Left; //窗口的寬度
23 int height = rect.Bottom - rect.Top; //窗口的高度
24 int x = rect.Right; //窗口的位置
25 int y = rect.Top;
26
27 int X=0,Y=0;
28 if(n_control_type==0)//坐標[-20,200]:第3列表 [-120,200]:第2列表 [-220,200]第1列表
29 { //坐標[-200,100]:上一曲 [-170,100]暫停 [-145,100]下一曲
30 X = x - 200;
31 Y = y + 100;
32 }
33 else if (n_control_type == 1)
34 {
35 X = x - 170;
36 Y = y + 100;
37 }
38 else
39 {
40 X = x - 145;
41 Y = y + 100;
42 }
43
44 SetCursorPos(X, Y); //移動鼠標
45 mouse_event(MOUSEEVENTF_LEFTDOWN, X * 65536 / 1024, X * 65536 / 768, 0, 0); //發送鼠標信息
46 mouse_event(MOUSEEVENTF_LEFTUP, Y * 65536 / 1024, Y * 65536 / 768, 0, 0);
47 SetCursorPos(pt.X, pt.Y); //移動鼠標回到原位置
48
49 //if (isVisabled == 24) ShowWindow(hMusic, SW_HIDE);
50 //SetParent(hMusic, this.Handle);
51 //EnableWindow((IntPtr)this.Handle, true);
52 SetWindowPos(hMusic, (IntPtr)this.Handle, x, y, width, height, SWP_NOMOVE); //使能窗口聚焦原窗口
53 SetForegroundWindow(hCurWin); //將原來窗口放在最上層
54 }
復制代碼
PS:這個函數負責找到酷我音樂盒的窗口(第10行)、頂層窗口切換(第18行、第52行、第53行)、鼠標位置設置(第16行、第44行、第47行)、鼠標點擊消息的生成(第45行、第46行)、點擊區域計算(第27~42行)
GetForegroundWindow(); 獲取當前頂層窗口句柄,不懂百度一下,就windows API介紹很多,初學者知道怎麼用就行啦![在調用它之前要寫這些代碼,下面說的調用API都要這樣的!]
1 [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
2 public static extern IntPtr GetForegroundWindow();
FindWindow("kwmusicmaindlg", null);根據窗口類名或者窗口名獲得窗口句柄。PS:該如何知道某個窗口的類名或者窗口名呢?一般是用VC6.0或者是VS系列軟件的Tool-->Spy++,具體請見我寫的一篇博文,裡面有詳細介紹:http://www.cnblogs.com/zjutlitao/p/3889900.html
1 [DllImport("user32.dll", EntryPoint = "FindWindow")]
2 public static extern IntPtr FindWindow(
3 string lpClassName,
4 string lpWindowName
5 );
GetCursorPos(out pt);獲取當前鼠標的位置,保存在Point結構體內,這裡因為我們想讓鼠標點擊一下按鈕然後回到原來的位置,所以要保存原來的位置!
1 [DllImport("user32.dll")]
2 public static extern bool GetCursorPos(out Point pt);
ShowWindow(hMusic,SW_SHOWNORMAL);根據句柄顯示窗口,這裡第二個參數是設定窗口以哪種方式顯示的,主要有以最小化顯示、最大化顯示、正常顯示.....具體參見度娘~我們這裡是為了避免有時候音樂盒最小化,我們得把它打開才能觸發點擊事件有效。(我本來想用個標記來標記它原來的狀態然後在處理之後恢復音樂盒自身的狀態,但是覺得還得寫些代碼,沒時間啦,調試這個浪費了很長時間~)
1 //private readonly int SW_HIDE = 0; //隱藏
2 private readonly int SW_SHOWNORMAL = 1; //還原
3 [DllImport("user32.dll", EntryPoint = "ShowWindow", SetLastError = true)]
4 private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
SetForegroundWindow(hMusic); 將活動窗口切換到句柄所指窗口,這樣鼠標點擊對應區域窗口才能接收到鼠標點擊消息!
1 [DllImport("user32.dll")]
2 private static extern bool SetForegroundWindow(IntPtr hWnd);
GetWindowRect(hMusic, ref rect); 獲取指定窗口的在桌面上的矩形坐標(這樣就能根據這個值計算目標窗口的大小和位置啦:20~25行就是干這個的)
復制代碼
1 [DllImport("user32.dll")]
2 [return: MarshalAs(UnmanagedType.Bool)]
3 static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
4
5 [StructLayout(LayoutKind.Sequential)]
6 public struct RECT
7 {
8 public int Left; //最左坐標
9 public int Top; //最上坐標
10 public int Right; //最右坐標
11 public int Bottom; //最下坐標
12 }
復制代碼
SetCursorPos(X, Y); 設置鼠標光標位置(X,Y)
1 [DllImport("user32.dll", EntryPoint = "SetCursorPos")]
2 private static extern int SetCursorPos(int x, int y);
mouse_event(MOUSEEVENTF_LEFTDOWN, X * 65536 / 1024, X * 65536 / 768, 0, 0); 發送消息函數,我們知道windows是消息機制的,你點一下鼠標其實就是光標移到指定位置,然後向系統發送一個鼠標按動消息,這裡我仿制一個鼠標左擊時間,第45行負責在指定位置發送個鼠標左鍵按下的消息,第46行發送個對應的鼠標左鍵抬起的消息,這樣一按一抬就組成了一個點擊事件。
1 private readonly int MOUSEEVENTF_LEFTDOWN = 0x2;
2 private readonly int MOUSEEVENTF_LEFTUP = 0x4;
3 [DllImport("user32")]
4 public static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
SetWindowPos(hMusic, (IntPtr)this.Handle, x, y, width, height, SWP_NOMOVE); 這個函數和ShowWindow有點像,只是這個可以設置窗口的三維顯示,為什麼是三維?平面窗口還有一維是窗口的疊放順序,具體可以問度娘~(這裡刪了這句好像也沒啥影響,當初因為沒有下面那句,所以需要這個函數將焦點放到C#軟件窗口)
1 static readonly IntPtr HWND_TOP = new IntPtr(0);
2 const UInt32 SWP_NOMOVE = 0x0002;
3 [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
4 private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
5.1.4、時間函數TImer
往窗口裡加一個Timer控件:[長下面那個模樣,屬性設置為Interval:100,然後給它一個消息函數,屬性中的那個閃電的標志],C#比MFC要方便的多,MFC要自己寫這貨,有點麻煩,但是對於打基礎的童鞋還是建議從win32學起,然後再學MFC這樣你對windows消息機制會有比較清晰的理解!嘿嘿,撤遠啦!其實這個Timer對應的消息函數就像一個會定時執行的函數一樣,你只要在裡面寫些邏輯,它會每隔一定的時間執行的。比如你想做動畫效果,讓一個小球移動,那麼小球的坐標的改變的計算可以放在這裡面寫。下面看一下我的這個函數中寫了什麼:
復制代碼
1 private string Status, ReceivedData;
2 private void timer1_Tick(object sender, EventArgs e)
3 {
4 StatusMessage.Text = Status;
5 StatusMessage.Text = ReceivedData;
6 //當有有效信號過來觸發控制
7 if (signal == 1) func(2);//下一曲
8 if (signal == 2) func(0);//上一曲
9 if (signal == 3) func(1);//暫 停
10 signal = 0;
11 }
復制代碼
PS:其實就是更新那個文本顯示區的內容和根據上面串口收來的數據進行處理然後產生的3種不同的控制命令,來調用func函數執行不同的點擊命令!
>_<:好啦,軟件部分終於說完啦(那其它3個功能按鈕直接調用func函數就行啦),其實硬件部分更多,剛才一直沒有說那個濾波算法,及對應的命令信號signal是如何產生的....下面就要介紹啦!