前言
前陣子,學英文的時候聽發音,意外之中發現Google的發音相比大部分TTS發音更準確,而且讀句子也沒有普通TTS那種一聽就是機器人的聲音,心血來潮,想利用Google發音做一個小軟件,所以就有了本文。
這是最後的UI成品圖,可以看到,沒有了常見的按鈕,也沒有了常見的Title框,整個佈局隨心所欲,GDI+?No。下面,就帶大家跟我一起來用最簡單的方式開發你所期望的UI吧!
自定義窗體
WinForm開發中,我們都知道窗體和控件的作用,實際上,以上的UI實現也是通過自定義窗體和用戶控件實現,那該如何做,才能讓窗體變成我們所想要的樣子呢?
首先,新建一個窗體,在這裡,我命名為MainForm.cs,打開我們就可以見到以往的樣子:
選中窗體,右鍵=》屬性,將FormBorderStyle設置成None, 窗體就變成了右圖所示;接著,我再將其拖拉成我需要的長度和寬度,此時若編譯運行,會發現實際上什麼都東西都看不到,這正是我們所需要的效果,接著,就防止我們想呈現的元素。接著,我將其拉成一個長方形,並在四周放4個PictureBox,正中間放一個Panel。
這裡需要注意的就是4個PictureBox的寬度,長度和未知,實際上我也不是拖控件,而是通過修改控件是屬性,這裡就需要精確到像素,聰明的你應該想到它們就是4條邊框線和中間的內容塊了,在這裡我推薦一個軟件,MarkMan,傳說中的標注神器,做UI方面,特別是開發人員,很有幫助。
在將所需的圖片填充上去就可以了。
private Image GetResourceImg(string name) { return Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(name)); }
void InitFormStyle() { //邊框 var borderImg = GetResourceImg(@"Speaker.Resource.Images.border.jpg"); Bitmap borderMap = new Bitmap(borderImg); borderMap.MakeTransparent(Color.FromArgb(255, 0, 255)); this.pb_borderLeft.BackgroundImage = borderMap; this.pb_borderRight.BackgroundImage = borderMap; this.pb_borderTop.BackgroundImage = borderMap; this.pb_borderBottom.BackgroundImage = borderMap; //主面板 var mainImg = GetResourceImg(@"Speaker.Resource.Images.main.jpg"); this.pl_main.BackgroundImage = new Bitmap(mainImg); //Logo var logoImg = GetResourceImg(@"Speaker.Resource.Images.logo.jpg"); this.btn_setting.NormalImage = new Bitmap(logoImg); btn_setting.Reset(); //Speak Button var normalImg = GetResourceImg(@"Speaker.Resource.Images.button.png"); var moveImg = GetResourceImg(@"Speaker.Resource.Images.buttonMove.png"); var downImg = GetResourceImg(@"Speaker.Resource.Images.buttonDown.png"); btn_speak.NormalImage = normalImg; btn_speak.MoveImage = moveImg; btn_speak.DownImage = downImg; btn_speak.Reset(); }
編譯通過之後,邊框雛形就出現了~:-)
自定義控件
窗體我們已經有了,接下來就是裡面一些控件的實現,這裡,我主要用到了兩個控件,ImageButton和LightTextBox,顧名思義,ImageButton就是一個圖片按鈕,但它還提供鼠標按下,懸移時的圖片選擇;LightTextBox是一個TextBox,鼠標懸移的時候,邊框高亮;
LightTextBox
新建用戶控件LightTextBox,選中控件,右鍵=》屬性,將BorderStyle改為None,這樣,控件也不可見了!拖出一個TextBox,並標注為MultiLine,至此,UI就這樣了,接著是控件的繪製編碼;
public LightTextBox() { SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); InitializeComponent(); BackColor = Color.Transparent; txt.Location = new Point(3, 6); txt.MouseEnter += new EventHandler(txt_MouseEnter); txt.MouseLeave += new EventHandler(txt_MouseLeave); }
實現TextBox的鼠標懸移事件就是為了實現邊框高亮效果;
#region Events void txt_MouseLeave(object sender, EventArgs e) { _isFouse = false; this.Invalidate(); } void txt_MouseEnter(object sender, EventArgs e) { _isFouse = true; this.Invalidate(); } #endregion
方法部分(控件實現的主要方法)
#region Methods protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; txt.Width = Width-6; CalculateSizeAndPosition(); Draw(e.ClipRectangle, e.Graphics); base.OnPaint(e); } private void CalculateSizeAndPosition() { if (!txt.Multiline) { Height = txt.Height + 9; } else { txt.Height = Height - 9; } } private void Draw(Rectangle rectangle, Graphics g) { #region 畫背景 using (SolidBrush backgroundBrush = new SolidBrush(Color.White)) { g.FillRectangle(backgroundBrush, 2, 2, this.Width - 4, this.Height - 4); } #endregion #region 畫陰影(外邊框) Color drawShadowColor = _shadowColor; if (!_isFouse) //判斷是否獲得焦點 { drawShadowColor = Color.Transparent; } using (Pen shadowPen = new Pen(drawShadowColor)) { if (_radius == 0) { g.DrawRectangle(shadowPen, new Rectangle(rectangle.X, rectangle.Y, rectangle.Width - 1, rectangle.Height - 1)); } else { g.DrawPath(shadowPen, DrawHelper.DrawRoundRect(rectangle.X, rectangle.Y, rectangle.Width - 2, rectangle.Height - 1, _radius)); } } #endregion #region 畫邊框 using (Pen borderPen = new Pen(_borderColor)) { if (_radius == 0) { g.DrawRectangle(borderPen, new Rectangle(rectangle.X + 1, rectangle.Y + 1, rectangle.Width - 3, rectangle.Height - 3)); } else { g.DrawPath(borderPen, DrawHelper.DrawRoundRect(rectangle.X + 1, rectangle.Y + 1, rectangle.Width - 3, rectangle.Height - 2, _radius)); } } #endregion } #endregion
由於字段,屬性比較多,我就不貼出來了,感興趣的可以在後續的源碼中查看;此時,若將該控件放到窗體中,鼠標移
動上去,則可發現邊框有一層光暈;
ImageButton
這裡的自定義控件實現方式一致,都先去掉了BorderStyle,再自己控制呈現內容,所以才能達到顯示特殊UI的目的;ImageButton的UI設計就不詳述了,直接放出後台實現主代碼;
public ImageButton() { this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); InitializeComponent(); Reset(); } #region Methods public void Reset() { if (_normalImage != null) { this.BackgroundImage = _normalImage; this.Size = new Size(_normalImage.Width, _normalImage.Height); } } private void MakeTransparent(Image image) { Bitmap bitmap = image as Bitmap; bitmap.MakeTransparent(Color.FromArgb(255, 0, 255)); } #endregion private void ImageButton_MouseEnter(object sender, EventArgs e) { if (_moveImage != null) this.BackgroundImage = _moveImage; } private void ImageButton_MouseLeave(object sender, EventArgs e) { this.BackgroundImage = _normalImage; } private void ImageButton_MouseDown(object sender, MouseEventArgs e) { if (_downImage != null) this.BackgroundImage = _downImage; } private void ImageButton_MouseUp(object sender, MouseEventArgs e) { if (_moveImage != null) this.BackgroundImage = _moveImage; }
MainForm
萬事俱備,只欠東風;把控件都放入MainForm中,並初始其狀態即可(上面第一部分代碼已放出);此時,編譯運行;已達到我們預期的UI效果;但是,UI效果是有了,移動效果,縮小(點擊任務欄圖標)等卻都失效了,該怎麼辦?Win32API千呼萬喚使出來~
private void pl_main_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { Win32.ReleaseCapture(); Win32.SendMessage(Handle, 274, 61440 + 9, 0); } }
這裡實際上是調用了Win32API,在這裡,又有一個好東西分享;平時做這些Win32API交互/C#與C++交互,需
要做類型轉換,特別是C++裡面一些指針什麼的,很是糾結,http://clrinterop.codeplex.com能幫到你;它能根據你輸入的C++函數生成C#的代碼;不可謂不是一大殺器啊!
移動解決了,那縮小的問題,也必須解決了;
protected override CreateParams CreateParams { get { const int WS_MINIMIZEBOX = 0x00020000; // Winuser.h中定義 CreateParams cp = base.CreateParams; cp.Style = cp.Style | WS_MINIMIZEBOX; // 允許最小化操作 return cp; } }
重寫CreateParams屬性就可以了;到這裡,我們的應用已經能正常顯示出我們所想要的UI;但還不夠,大部
分輔助類型的軟件都有最小化的功能,那,我們也將其加上去吧;
NotifyIcon
這個其實很簡單,就是拖一個NotifyIcon到窗體中,並綁定一個ContextMenu到這個NotifyIcon中就可以了;然後在觸發一些事件;
private void MainForm_Resize(object sender, EventArgs e) { if (WindowState == FormWindowState.Minimized) { this.Visible = false; } }
後面,我還主要用NotifyIcon來通知用戶,做提示,錯誤提示等;到這裡,整個UI方面就已經完成了;只
剩下後面的邏輯處理,就是輸入句子能發音,並且支持快捷鍵屏幕取詞等;