2013年9月11日 星期三

[轉貼] 跟我做WinForm開發(1)-自定義UI

出處:http://www.cnblogs.com/kongyiyun/archive/2012/01/07/2315636.html

前言

前陣子,學英文的時候聽發音,意外之中發現Google的發音相比大部分TTS發音更準確,而且讀句子也沒有普通TTS那種一聽就是機器人的聲音,心血來潮,想利用Google發音做一個小軟件,所以就有了本文。
image
這是最後的UI成品圖,可以看到,沒有了常見的按鈕,也沒有了常見的Title框,整個佈局隨心所欲,GDI+?No。下面,就帶大家跟我一起來用最簡單的方式開發你所期望的UI吧!

自定義窗體

WinForm開發中,我們都知道窗體和控件的作用,實際上,以上的UI實現也是通過自定義窗體和用戶控件實現,那該如何做,才能讓窗體變成我們所想要的樣子呢?
首先,新建一個窗體,在這裡,我命名為MainForm.cs,打開我們就可以見到以往的樣子:
imageimage
選中窗體,右鍵=》屬性,將FormBorderStyle設置成None, 窗體就變成了右圖所示;接著,我再將其拖拉成我需要的長度和寬度,此時若編譯運行,會發現實際上什麼都東西都看不到,這正是我們所需要的效果,接著,就防止我們想呈現的元素。接著,我將其拉成一個長方形,並在四周放4個PictureBox,正中間放一個Panel。
image
這裡需要注意的就是4個PictureBox的寬度,長度和未知,實際上我也不是拖控件,而是通過修改控件是屬性,這裡就需要精確到像素,聰明的你應該想到它們就是4條邊框線和中間的內容塊了,在這裡我推薦一個軟件,MarkMan,傳說中的標注神器,做UI方面,特別是開發人員,很有幫助。
image
在將所需的圖片填充上去就可以了。
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方面就已經完成了;只
剩下後面的邏輯處理,就是輸入句子能發音,並且支持快捷鍵屏幕取詞等;

沒有留言:

張貼留言