實現一個簡單的側邊導航Winform程序框架


簡介

每次新項目都要想着界面怎么設計好,但想來想去上位機界面就那幾種,按照導航方式可分為:菜單工具欄導航、漢堡包導航、側邊導航等。我用的最多的是側邊導航,導航菜單一般只有一級(最多二級),三級導航菜單基本很少用到。
本文實現一個簡單的側邊導航Winform程序框架,以后開發項目可以直接用,話不多說上圖:

整個程序界面分為上、中、下三個區域,分別是:

  • 標題區:顯示軟件的名稱、LOG、版本等信息,實現窗體標題欄的基本功能。
  • 導航區:顯示整個軟件的主要內容,即導航菜單和顯示面板。
  • 狀態區:顯示軟件的狀態信息,如用戶、報警、日志等,此狀態欄不是必須的,重要的狀態信息也可以在標題欄顯示。

整個界面的實現可以分為三個部分:實現窗體標題、實現導航面板、調整界面布局,主要的技術難點(或者說工作任務)集中在前面兩個部分,最終核心還是在於導航面板的實現。
下面將按重要程度依次介紹各個部分的實現過程,而且所有內容都盡量不使用VS的UI設計器,通過代碼來控制所有控件的屬性。

實現導航面板

為了代碼復用,導航面板封裝為一個用戶控件,主要功能就是點擊導航菜單后顯示指定的界面,使用時直接傳入菜單項名稱和對應的窗體實例即可。

實現方法

新建一個用戶控件命名為LeftNavigation,在用戶控件上添加SplitContainer控件,不需要設置任何屬性,后台代碼如下:

public partial class LeftNavigation : UserControl
{
    /// <summary>
    /// 生成菜單按鈕的方法,可以自己設置按鈕的風格、屬性,主要按鈕的父類是Control即可
    /// </summary>
    public Func<Control> CreateBtnFunc=()=> { return null; };

    /// <summary>
    /// 菜單按鈕的點擊事件,在處理完導航內容的顯示任務后觸發
    /// </summary>
    public event EventHandler BtnClick;

    /// <summary>
    /// 獲取或設置菜單按鈕的背景顏色
    /// </summary>
    public Color BtnBackColor{ get; set; } = Color.FromArgb(30, 56, 83);

    /// <summary>
    /// 獲取或設置菜單按鈕的選中顏色
    /// </summary>
    public Color BtnSelectkColor { get; set; } = Color.FromArgb(67, 92, 200);

    /// <summary>
    /// 獲取菜單按鈕的大小,這個屬性基本上很少變動,所以只支持在構造函數的參數中設置該值
    /// </summary>
    public Size BtnSize { get;}

    /// <summary>
    /// 獲取菜單按鈕之間、菜單按鈕與面板邊緣之間的間距,該值也作為拆分器的間隔設定值。
    /// 這個屬性基本上很少變動,所以只支持在構造函數的參數中設置該值。
    /// </summary>
    public int BtnInterval { get;}

    /// <summary>
    /// 構造函數
    /// </summary>
    /// <param name="btnWidth">菜單按鈕的寬度</param>
    /// <param name="btnHeight">菜單按鈕的高度</param>
    /// <param name="btnInterval">間距值</param>
    public LeftNavigation(int btnWidth=198, int btnHeight=66, int btnInterval=5)
    {
        InitializeComponent();
        this.Dock = DockStyle.Fill;
        //用戶控件太小時,splitContainer尺寸調整會失效
        this.Size = new Size(1000, 1000);

        BtnSize = new Size(btnWidth, btnHeight);
        BtnInterval = btnInterval;

        splitContainer1.Dock = DockStyle.Fill;
        splitContainer1.SplitterWidth = BtnInterval;
        splitContainer1.SplitterDistance = BtnSize.Width + BtnInterval * 2;            
        splitContainer1.IsSplitterFixed = true;

        
        splitContainer1.FixedPanel = FixedPanel.Panel1;
        splitContainer1.Panel1.AutoScroll = true;
        splitContainer1.Panel2.AutoScroll = true;

        splitContainer1.Panel1.BackColor = Color.White;
    }


    /// <summary>
    /// 菜單按鈕點擊事件處理
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button_Click(object sender, EventArgs e)
    {
        Control btnCtr = sender as Control;
         if (splitContainer1.Panel2.Controls.Count > 0 && splitContainer1.Panel2.Controls[0]==(Form)(btnCtr.Tag)) 
         {
             return;
         }
        foreach (var item in splitContainer1.Panel1.Controls)
        {
            Control btnItem = item as Control;
            Form form = btnItem.Tag as Form;
            if (btnItem == btnCtr)
            {
                btnItem.BackColor = BtnSelectkColor;
                splitContainer1.Panel2.Controls.Clear();
                if (form != null) 
                {                        
                    splitContainer1.Panel2.Controls.Add(form);
                    form.Visible = true;
                }
            }
            else 
            {
                btnItem.BackColor = BtnBackColor;
                if (form != null) form.Visible = false;
            }
        }

        if (BtnClick != null) BtnClick(sender, e);


    }

    /// <summary>
    /// 生成菜單按鈕
    /// </summary>
    /// <param name="text">顯示的文本</param>
    /// <param name="tag">對應的窗體實例</param>
    /// <param name="point">按鈕位置</param>
    /// <returns></returns>
    private Control CreateBtnCtr(string text,Form tag,Point point)
    {
        Control btnCtr= CreateBtnFunc();
        if (btnCtr == null) 
        {
            Button btn = new Button();
            btn.BackColor = BtnBackColor;
            btn.Font = new Font("Tahoma", 20, FontStyle.Bold);
            btn.ForeColor = Color.White;
            btn.FlatStyle = FlatStyle.Flat;                
            btnCtr = btn;
            
        }

        btnCtr.Click += button_Click;
        btnCtr.Text = text;
        btnCtr.Tag = tag;
        btnCtr.Size = BtnSize;
        btnCtr.Location = new Point(point.X, point.Y);

        return btnCtr;
    }
   
    /// <summary>
    /// 設置導航信息
    /// </summary>
    /// <param name="btnDic"></param>
    public void SetNavigation(Dictionary<string,Form> btnDic)
    {            
        splitContainer1.Panel1.Controls.Clear();
        splitContainer1.Panel2.Controls.Clear();

        Point point = new Point(BtnInterval, BtnInterval);
        foreach (var item in btnDic)
        {
            if (item.Value != null) 
            {
                item.Value.TopLevel = false;
                item.Value.FormBorderStyle = FormBorderStyle.None;
                item.Value.Dock = DockStyle.Fill;
                item.Value.AutoScroll = true;
                item.Value.Show();
            }
            if (!string.IsNullOrEmpty(item.Key)) 
            {
                Control btnCtr = CreateBtnCtr(item.Key, item.Value, point);
                point.Y = point.Y + BtnSize.Height + BtnInterval;
                splitContainer1.Panel1.Controls.Add(btnCtr);
            }          
        }           

        if (splitContainer1.Panel1.Controls.Count > 0)
        {
            button_Click(splitContainer1.Panel1.Controls[0], new EventArgs());
        }
    }
}

有以下幾點需要注意:

  • 為了使用方便,導航面板不支持隨意調整導航菜單的寬度,只能在初始化時一次性指定。
  • 為了簡化邏輯,窗體實例綁定在按鈕的Tag屬性上,每次點擊需要循環點擊遍歷控件,此處可以根據自己的需求進行優化。
  • 如果想在導航的子窗體顯示時做一些操作,建議在窗體的VisibleChanged事件中進行。

使用方法

使用起來也很簡單,在主界面中添加一個Panel控件命名為panel_Navigation,直接在主窗體的構造函數里面添加如下代碼:

LeftNavigation leftNavigation = new LeftNavigation();
Dictionary<string, Form> btnFormDic = new Dictionary<string, Form>()
{
    {"form1",new Form1()},
    {"form2",new Form2()},
    {"form3",new Form3()},
    {"退出",null}
};

leftNavigation.SetNavigation(btnFormDic);
leftNavigation.BtnClick += (sender, e) =>
{
    Control ctr = sender as Control;
    if (ctr.Text == "退出")
    {
        this.Close();
    }
};

panel_Navigation.Controls.Add(leftNavigation);

實現標題欄

標題欄的功能和系統窗體的標題欄功能類似,主要有窗體拖拽及最大化、自定義窗體按鈕兩個功能。自定義的標題欄擁有極大的可操作性,設計窗體時還是很有必要的。
先將主窗體重命名為MainForm,然后在主窗體中添加一個Panel控件命名為panel_Title

窗體拖拽及最大化

在主界面的后台代碼中實現窗體拖拽及最大化功能,代碼如下:

#region 無邊框窗體移動及最大化

// 鼠標按下
private bool isMouse = false; // 鼠標是否按下
// 原點位置
private int originX = 0;
private int originY = 0;
// 鼠標按下位置
private int mouseX = 0;
private int mouseY = 0;
private void windowMove_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    { // 判斷鼠標按鍵
        isMouse = true;
        // 屏幕坐標位置
        originX = this.Location.X;
        originY = this.Location.Y;
        // 鼠標按下位置
        mouseX = originX + e.X;
        mouseY = originY + e.Y;
    }
}

// 鼠標移動
private void windowMove_MouseMove(object sender, MouseEventArgs e)
{
    if (isMouse)
    {
        // 移動距離
        int moveX = (e.X + this.Location.X) - mouseX;
        int moveY = (e.Y + this.Location.Y) - mouseY;
        int targetX = originX + moveX;
        int targetY = originY + moveY;
        this.Location = new Point(targetX, targetY);
    }
}

// 鼠標釋放
private void windowMove_MouseUp(object sender, MouseEventArgs e)
{
    if (isMouse)
    {
        isMouse = false;
    }
}

// 鼠標雙擊
private void windowMove_DoubleClick(object sender, MouseEventArgs e)
{
    if (isMouse)
    {
        this.WindowState = this.WindowState == FormWindowState.Normal ? FormWindowState.Maximized : FormWindowState.Normal;
    }
}

#endregion

上面只是鼠標事件的處理程序,需要綁定到控件上才會生效。這里使用Panel控件作為標題欄,綁定到標題欄Panel控件的事件上即可。如果沒有使用控件作為標題欄,則需要綁定到主窗體的事件上。
在主界面的構造函數里面添加綁定事件,代碼如下:

this.FormBorderStyle = FormBorderStyle.None;
this.StartPosition = FormStartPosition.CenterScreen;
this.BackColor = SystemColors.ActiveCaption;
panel_Title.MouseDown += windowMove_MouseDown;
panel_Title.MouseUp += windowMove_MouseUp;
panel_Title.MouseMove += windowMove_MouseMove;
panel_Title.MouseDoubleClick += windowMove_DoubleClick;

自定義窗體按鈕

自定義窗體按鈕正常的操作是派生一個Button控件的子類,然后實現一些自定義的功能。這里不想搞得那么復雜,直接使用PictureBox控件作為按鈕即可。
在標題欄控件panel_Title中添加pictureBoxBtn_Min、pictureBoxBtn_Max、pictureBoxBtn_Close三個PictureBox控件,另外添加一個Label控件命名為label_Title顯示標題。

標題顯示

在主窗體的構造函數中設置標題Label控件的屬性,代碼如下:

label_Title.AutoSize = true;
label_Title.Anchor = AnchorStyles.None;
label_Title.Location = new Point((panel_Title.Size.Width - label_Title.Size.Width) / 2, (panel_Title.Size.Height - label_Title.Size.Height) / 2);

標題字體的樣式按自己的喜好設置,這里就不一一介紹了。

按鈕設置

首先需要導入按鈕的圖標,分別是以下幾個(文末源碼里面有完整圖標):

  • 最小化
  • 正常化
  • 最大化
  • 退出

在PictureBox控件的Image屬性中點擊“...”導入,如下圖所示:

在主界面的后台代碼中實現按鈕的單擊事件,代碼如下:

#region 自定義窗體按鈕
//鼠標進入按鈕
private void pictureBoxBtn_MouseEnter(object sender, EventArgs e)
{
    PictureBox pictureBox = sender as PictureBox;
    pictureBox.BackColor = Color.FromArgb(67, 92, 200);
}

//最小化
private void pictureBoxBtn_Min_Click(object sender, EventArgs e)
{
    this.WindowState = FormWindowState.Minimized;
}
//最大化
private void pictureBoxBtn_Max_Click(object sender, EventArgs e)
{
    PictureBox pictureBox = sender as PictureBox;
    if (this.WindowState == FormWindowState.Normal)
    {
        pictureBox.Image = Properties.Resources.MaxNormal;
        this.WindowState = FormWindowState.Maximized;
    }
    else if (this.WindowState == FormWindowState.Maximized)
    {
        pictureBox.Image = Properties.Resources.Max;
        this.WindowState = FormWindowState.Normal;
    }
}
//退出
private void pictureBoxBtn_Close_Click(object sender, EventArgs e)
{
    this.Close();
}       
//鼠標離開按鈕
private void pictureBoxBtn_MouseLeave(object sender, EventArgs e)
{
    PictureBox pictureBox = sender as PictureBox;
    pictureBox.BackColor = SystemColors.ActiveCaption;
}
#endregion

在主界面的構造函數中設置按鈕屬性並綁定事件,代碼如下:

pictureBoxBtn_Max.Image = Properties.Resources.Max;

pictureBoxBtn_Min.Anchor = AnchorStyles.Top | AnchorStyles.Right;
pictureBoxBtn_Max.Anchor = AnchorStyles.Top | AnchorStyles.Right;
pictureBoxBtn_Close.Anchor = AnchorStyles.Top | AnchorStyles.Right;

pictureBoxBtn_Min.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBoxBtn_Max.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBoxBtn_Close.SizeMode = PictureBoxSizeMode.CenterImage;

pictureBoxBtn_Min.Click += pictureBoxBtn_Min_Click;
pictureBoxBtn_Max.Click += pictureBoxBtn_Max_Click;
pictureBoxBtn_Close.Click += pictureBoxBtn_Close_Click;

pictureBoxBtn_Min.MouseEnter +=pictureBoxBtn_MouseEnter;
pictureBoxBtn_Max.MouseEnter += pictureBoxBtn_MouseEnter;
pictureBoxBtn_Close.MouseEnter += pictureBoxBtn_MouseEnter;

pictureBoxBtn_Min.MouseLeave += pictureBoxBtn_MouseLeave;
pictureBoxBtn_Max.MouseLeave += pictureBoxBtn_MouseLeave;
pictureBoxBtn_Close.MouseLeave += pictureBoxBtn_MouseLeave;

實現狀態欄

狀態欄就沒什么好說的,根據項目的情況自行添加,此處只實現一個實時時間顯示標簽意思一下。
在主界面中添加一個Panel控件命名為panel_State,在Panel控件中添加一個Label控件命名為label_Time,調整一下大小和位置。
在主界面的構造函數中設置定時器,代碼如下:

label_Time.Text = DateTime.Now.ToString();
label_Time.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
Timer timer = new Timer();
timer.Interval = 1000;
timer.Tick += (sender, obj) =>
{
    label_Time.Text = DateTime.Now.ToString();
};
timer.Start();

定時器的啟動有一點的延遲,需要先設置一下label_Time的顯示內容。

整體使用

在窗體設置界面調整好Panel控件的尺寸,主要是標題欄、狀態欄的高度。
在主界面的構造函數中設置Panel控件的布局,代碼如下:

panel_Title.Dock = DockStyle.Top;
panel_Navigation.Dock = DockStyle.Fill;
panel_State.Dock = DockStyle.Bottom;
//必須置於頂層
panel_Navigation.BringToFront();

然后新建Form1、Form2、Form3等窗體,實現自己的業務邏輯。

本文項目的下載鏈接(提取碼:sfbk):https://pan.baidu.com/s/18w85F7ebwV1PFY-4CkmBWA

參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM