WinForm(二) WinForm进阶与复杂控件使用

news/2024/7/3 3:16:50

一.复合控件

1.控件的使用综述:在WinForm中使用控件主要包括以下几种情况
(1)标准控件:直接使用winform自带的控件,比如button、textBox等。我们只需要修改属性,添加事件即可。
(2)复合控件:将一些标准控件组合起来,实现复杂功能。需要定义 class YourControl:UserControl{ }
(3)扩展控件:拓展或者改变一些标准控件的功能。需要继承于标准控件,class YourButton:Button { }
(4)自定义控件:完全自定义一个控件,定义属性、方法、样式等。需要继承于Control(Control是所有控件的基类),class YourControl:Control { }
 *注意:我们之前学习了标准控件的使用,所以这节课我们就学习一下复合控件如何定义和使用,循序渐进。


 2.复合控件:实现一个搜索框控件(组合文本框与图片框)
(1)定义:复合控件又称为用户控件,专门用来将标准控件进行组合,其继承自 UserControl
(2)定义复合控件:
        - 右键解决方案->添加 用户控件窗体(UserControl),命名为SearchBox。会自动生成一个SearchBox类,继承自 UserControl
        - 双击 SearchBox.cs文件,可以打开设计界面,像窗口一样设计符合组件的样式
                a. 拖上去一个textBox文本框作为搜索框
                b. 拖上去一个图片框,设置图片为搜索图标
                c. 使用表格布局,将二者组合到一起,并调整大小和位置(Anchor、Dock、表格比例等)
                d.保存后,重新生成解决方案,再点击界面中的工具箱即可看到我们自己的复合控件    
(3)复合控件的摆放和原理
        - 在界面中可以直接拖拽符合控件,并且复合控件作为一个整体也具有Size、Location、事件等等常规属性。
        - 复合控件本质上就是一个窗体!复合控件中的组件和窗体中的组件一样,在初始化时放置到窗体上。     

 1.复合控件SearchBox可视化设计

在SearchBox.cs窗体设计界面中,对复合控件进行可视化样式设计,其步骤如下:

  • 添加一个TableLayoutPanel表格布局容器,保留一行两列。并设置其Dock为Fill填满整个父窗口,然后设置两列表格大小比例为7:3。(用来放置文本框和图片框按钮)
  • 添加一个TextBox到第一个单元格,并设置其Anchor为左右边距固定(可以随父窗口拉伸而拉伸)
  • 添加一个pictureBox到第二个单元格,并设置其Dock为Fill填满单元格,添加图片资源-搜索图标

注意:现在只是设计好了SearchBox的样式,但是还没有设计其属性访问和事件处理,比如复合控件如何获取文本框的值?如何获取/设置图片框的点击事件?复合控件如何当作一个整体来用?

 2.复合控件的属性访问和事件处理

3. 复合控件的访问和事件处理:我们有时需要获取复合控件内子控件的数据、或者给复合控件的某个位置(图片、按钮)设置事件  
(1)设置public直接访问(最简单,最直接,最不常用):将复合控件内的子控件成员全部设置为public,直接访问即可
        - 在复合控件设计界面内选择每个子控件,设置其 Modifiers 属性为 public (默认为private)
        - 然后在复合控件源码中,相关的子控件就会自动变为 public 访问修饰的,我们可以直接使用 [复合控件.子控件.属性] 来访问
        - 给SearchBox的搜索图片设置点击事件:searchBox1.pictureBox1.Click += new EventHandler(searchEvent);
        - 获取文本框搜索内容:MessageBox.Show("开始搜索..." + searchBox1.textBox1.Text);

(2)在复合控件内设置事件和获取数据,就可以像窗口一样直接使用子组件了(没试过)

(3)复合控件自定义属性
        - 添加属性:
                a.在SearchBox逻辑代码中,添加属性访问器

public string SearchText
{
     get
     {
          return this.textBox1.Text;
     }
     set
     {
          this.textBox1.Text = value;
     }
}

                b.重新生成解决方案,打开Form3界面设计,在杂项中就可以看到我们自定义的属性SearchText。通过修改值可以直接修改内部的TextBox。其本质都是调用了set、get方法。

        - 重写属性:
                 a.在SearchBox逻辑代码中,重写UserControl已有的属性,比如Text等

public override string Text {
     get
     {
         return this.textBox1.Text;
     }
     set
     {
         this.textBox1.Text = value;
     }
}

                b.重新生成解决方案,打开Form3界面设计,原本的属性就成为了我们重写的属性。
        - C# Attribute 特性:特性是一种可以被编译器识别的声明性标签,类似于Java的注解
                a.语法:[attribute(positional_parameters, name_parameter = value, ...)]
                b.常用特性:
                        [Browsable(true)]: 控件属性是否在设计器内可见(一般默认为false)
                        [Category("Appearance")]:控件属性显示在哪个类别中。Action(与可用操作相关)、Appearance(与实体的外观相关)、Behavior(与实体的行为相关)、Data(与数据和数据源管理相关)
                        [DesignerSerializationVisibility()]: 将我们在控件属性上设定的值持久化到代码中,这样我们下次再查看控件的值依然是我们最后一次设定的值。指示一个属性是否串行化和如何串行化,它的值是一个枚举,一共有三种类型Content,Hidden,Visible。

(4)复合控件自定义事件
        - 在SearchBox逻辑代码中,添加声明自定义事件。 public event EventHandler SearchEvent;
        - 重新生成解决方案后,可以在事件-杂项中看到我们自定义的事件 SearchEvent 
        - 在设计界面可以给事件添加/实现处理回调方法 onSearch(),配合自定义属性食用
        - 在复合组件中,将图片的点击事件 链接到 自定义事件上,即点击图片时触发自定义事件。编写图片的Click事件

//PictureBox的点击事件 -> 触发自定义事件SearchEvent的回调方法
private void onClick(object sender, EventArgs e)
{
      //调用事件,固定写法
      if(SearchEvent != null)
      {
           SearchEvent.Invoke(this, e);
      }
      //简化写法 SearchEvent?.Invoke(this,e);
}

        *注意:一个控件还可以添加多个事件,顺序触发

(1)界面设计

(2)SearchBox.cs 逻辑代码

namespace WindowsFormsApp_learning
{
    public partial class SearchBox : UserControl
    {
        //添加自定义事件
        public event EventHandler SearchEvent;

        public SearchBox()
        {
            //初始化控件(TextBox+PictureBox)
            InitializeComponent();
        }

        /**
         * 添加新的自定义属性 SearchText
         */
        public string SearchText
        {
            get
            {
                return this.textBox1.Text;
            }
            set
            {
                this.textBox1.Text = value;
            }
        }

        /**
         * 重写属性 Text
         */
        [Browsable(true)]
        public override string Text {
            get
            {
                return this.textBox1.Text;
            }
            set
            {
                this.textBox1.Text = value;
            }
        }

        //图片点击事件onClick -> 链接自定义事件SearchEvent
        //注意:SearchEvent的事件处理方法,需要在Form.cs中像基本控件的事件处理一样进行添加才行
        private void onClick(object sender, EventArgs e)
        {
            //调用事件,固定写法
            if(SearchEvent != null)
            {
                SearchEvent.Invoke(this, e);
            }
            //简化写法 SearchEvent?.Invoke(this,e);
        }
    }
}

(3)Form3.cs 主窗体逻辑代码

namespace WindowsFormsApp_learning
{

    public partial class Form3 : Form
    {
        public Form3()
        {
            InitializeComponent();

            //设置复合控件 图片的点击事件(public方式)
            //searchBox1.pictureBox1.Click += new EventHandler(searchEvent);
        }
        //public方式设置复合控件-子控件点击事件
        private void searchEvent(object sender,EventArgs args)
        {
            //获取并展示 文本框的内容
            MessageBox.Show("开始搜索..." + searchBox1.textBox1.Text);
        }

        //SearchBox的自定义事件实现 SearchEvent -> onSearch
        private void onSearch(object sender, EventArgs e)
        {
            //获取并展示 文本框的内容(通过自定义属性/重写属性)
            MessageBox.Show("开始搜索..." + searchBox1.SearchText);
        }
    }
}

二.控件包装 

1.控件包装基本设计

1.控件包装:
(1)定义:控件包装是复合控件的一种特殊形式,其内部只有一个控件
(2)目的:对一些基础控件的功能进行拓展和自定义修改,比如TextBox包装后可以修改高度和内边距padding等
(3)步骤:
        - 新建包装控件类AfTextBox,继承自UserControl,其内部只放置一个TextBox组件,作为对TextBox的包装拓展
        - 为了实现可修改TextBox高度宽度的效果,需要设置TextBox的Size随着外部Form的变化而变化 -> onLayout()
        - 为了实现可修改TextBox内边距padding的效果,需要设置Form与TextBox的边距来代替文字内边距 -> onLayout()
        - 为了无缝实现上述效果,看着是一个TextBox整体,需要设置TextBox和Form背景色相同,并且取消TextBox边框。

2.Location属性解释:控件左上角相对于其容器左上角的坐标,单位是像素。
(1)对于窗体来说,就是窗口左上角相对于屏幕左上角的坐标;
(2)对于一般控件来说,就是控件左上角相对于其容器左上角的坐标,比如说相对于窗体,图片框等。
(3)父容器左上角默认(0,0)
3.窗口位置调节:
        - this.StartPosition = FormStartPosition.Manual; //窗体的位置由Location属性决定
        - this.StartPosition = FormStartPosition.CenterParent; //窗体在其父窗体中居中
        - this.StartPosition = FormStartPosition.CenterScreen; //窗体在当前显示窗口中居中,尺寸在窗体大小中指定
        - this.StartPosition = FormStartPosition.WindowsDefaultBounds; //窗体定位在windows默认位置,边界也由windows默认决定(默认)
        - this.StartPosition = FormStartPosition.WindowsDefaultLocation; //窗体定位在windows默认位置,尺寸在窗体大小中指定

 (1)TextBox包装控件AfTextBox 逻辑代码

namespace WindowsFormsApp_learning
{
    public partial class AfTextBox : UserControl
    {
        public AfTextBox()
        {
            //初始化内部控件
            InitializeComponent();
        }

        //重写布局实现
        protected override void OnLayout(LayoutEventArgs levent)
        {
            base.OnLayout(levent);
            //1.获取子控件
            if (this.Controls.Count == 0) return;
            Control C = this.Controls[0];

            //2.获取父窗口参数
            Padding p = this.Padding;//父窗口的边距Padding
            int x = 0, y = 0;//子控件相对于容器的初始坐标(左上角) 
            int w = this.Width, h = this.Height;
            w -= (p.Left + p.Right);//TextBox的宽度 = 容器宽度 - 左右边距
            x += p.Left;//TextBox的相对x = p.Left

            //3.计算文本框的高度,使其显示在中间
            //  (1)PreferredSize:获取可以容纳控件的矩形区域的大小。
            //  (2)Size:获取控件的大小
            int h2 = C.PreferredSize.Height;
            if (h2 > h) h2 = h;
            y = (h - h2) / 2;//TextBox的相对y = (h-h2)/2 高度居中

            //4.设置子控件的位置和尺寸
            C.Location = new Point(x, y);
            C.Size = new Size(w, h2);
            //5.为什么不调整高度大小:文本框为单行,高度调节无效。只会随着文字格式的大小变化!
        }
    }
}

2.控件包装单文件开发

2.包装、复合控件单文件开发
(1)定义:每次创建UserControl都会自动给我们创建一个相应的designer.cs布局设计文件,将界面和逻辑分开开发。如何将二者合并到一个文件开发呢?
(2)步骤:
        - 【手动】创建AfTextBox2普通类 class文件,并手动继承UserControl
        - 双击打开AfTextBox2.cs可以进入设计界面,拖放控件。会直接给AfTextBox2.cs内自动添加控件布局(不会分开添加了)
        - 手动实现构造函数,初始化调用布局 InitializeComponent()
        - 添加逻辑代码和事件、属性
(3)好处:以后可以直接将单个控件cs文件复制到别的项目里直接使用,而不用分多个了

3.包装控件的自定义属性和事件:和复合控件一样(AfTextBox2为例)
(1)添加/重写自定义属性 Text、BackColor、Front
(2)添加自定义事件 ReturnPressed(回车事件)

 (1)AfTextBox2包装控件cs逻辑代码

namespace WindowsFormsApp_learning
{
    class AfTextBox2 : UserControl
    {
        /**
         * 自动生成布局代码,直接添加到本文件
         */
        private TextBox edit;

        private void InitializeComponent()
        {
            this.edit = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // edit
            // 
            this.edit.BorderStyle = System.Windows.Forms.BorderStyle.None;
            //this.edit.Font = new System.Drawing.Font("宋体", 16F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
            this.edit.Location = new System.Drawing.Point(106, 79);
            this.edit.Name = "edit";
            this.edit.Size = new System.Drawing.Size(263, 37);
            this.edit.TabIndex = 0;
            this.edit.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.OnPress);
            // 
            // AfTextBox2
            // 
            this.BackColor = System.Drawing.Color.White;
            this.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
            this.Controls.Add(this.edit);
            this.Name = "AfTextBox2";
            this.Size = new System.Drawing.Size(524, 246);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        /**
         * 手动添加构造函数,初始化布局
         */
        public AfTextBox2()
        {
            InitializeComponent();
        }

        /**
         * 添加逻辑代码,实现组件自适应
         */
        protected override void OnLayout(LayoutEventArgs levent)
        {
            base.OnLayout(levent);
            //1.获取子控件
            if (this.Controls.Count == 0) return;
            Control C = this.Controls[0];

            //2.获取父窗口参数
            Padding p = this.Padding;//父窗口的边距Padding
            int x = 0, y = 0;//子控件相对于容器的初始坐标(左上角) 
            int w = this.Width, h = this.Height;
            w -= (p.Left + p.Right);//TextBox的宽度 = 容器宽度 - 左右边距
            x += p.Left;//TextBox的相对x = p.Left

            //3.计算文本框的高度,使其显示在中间
            //  (1)PreferredSize:获取可以容纳控件的矩形区域的大小。
            //  (2)Size:获取控件的大小
            int h2 = C.PreferredSize.Height;
            if (h2 > h) h2 = h;
            y = (h - h2) / 2;//TextBox的相对y = (h-h2)/2 高度居中

            //4.设置子控件的位置和尺寸
            C.Location = new Point(x, y);
            C.Size = new Size(w, h2);
            //5.为什么不调整高度大小:文本框为单行,高度调节无效。只会随着文字格式的大小变化!
        }

        /**
         * 重写自定义属性 Text
         *  Browsable:True则属性在设计界面显示可修改,False则属性在设计界面不显示。默认为True
         *  DesignerSerializationVisibility:设置属性的序列化方式,默认为Visible
         */
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override string Text {
            get
            {
                return edit.Text;
            }
            set
            {
                edit.Text = value;
            }
        }
        //重写自定义属性 Color
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override Color BackColor
        {
            get
            {
                return edit.BackColor;
            }
            set
            {
                //容器和TextBox的颜色一起整体变化
                base.BackColor = value;
                edit.BackColor = value;
            }
        }
        /**
         * 注意:Font的设置有些特殊,需要提供初始的默认值
         * **此处仍未解决,无法修改Font字体**
         */
        private Font initFont = new Font("宋体", 10f);
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public override Font Font
        {
            get
            {
                return initFont;
            }
            set
            {
                this.initFont = value;
                this.edit.Font = value;
            }
        }
        /**
         * 添加自定义事件
         */
        public event EventHandler ReturnPressed;
        //TextBox的keyPress -> 触发 ReturnPressed事件
        private void OnPress(object sender, KeyPressEventArgs e)
        {
            char ch = e.KeyChar;
            if (ch == '\r')//如果是回车符号
            {
                //回调ReturnPressed事件
                ReturnPressed?.Invoke(this, e);
            }
        }
    }
}

(2)Form窗体应用

namespace WindowsFormsApp_learning
{

    public partial class Form4 : Form
    {
        public Form4()
        {
            InitializeComponent();

        }
        //包装控件的 ReturnPressed 回车触发事件
        private void OnEnterPress(object sender, EventArgs e)
        {
            MessageBox.Show("正在检索..." + afTextBox21.Font);
        }
    }
}

 三.对话框

1.对话框基本概念

1.对话框
(1)构建:对话框本质还是一个Form窗体,只不过以对话框的形式显示。所以对话框的构建参照窗体的设计即可
(2)使用:新建一个对话框 MyDiglog ,在点击事件中显示对话框 ShowDialog()
        MyDialog dialog = new MyDialog(); //新建窗体
        dialog.ShowDialog(); //作为对话框弹出在当前界面。注意如果是Show()则是显示窗体,非对话框
        dialog.Dispose(); //销毁窗体,释放资源
(3)运行流程:
        - 在调用ShowDialog方法后,会[阻塞]父窗口进程,不再往下执行。表现为子窗口可以活动,而父窗口不行
        - 对话框阻塞直到用户关闭对话框,父窗口进程才继续向下执行
        - 执行Dispose方法释放对话框资源
(4)对话框属性(和窗体Form属性一样):
        - Text:窗体左上角显示文字
        - MaximizeBox:最大化按钮(True显示,Flase不显示)
        - MinimizeBox:最小化按钮(True显示,Flase不显示)、
        - ShowInTaskBar:是否在任务栏显示
        - StartPosition:窗体显示位置 WindowsDefaultLocation窗口默认位置、CenterSceen窗口中央、CenterParent父窗口中央等
        - FormBorderStyle:边界样式 Sizable可修改、FixedDialog固定对话框大小、FixedSignle固定窗体
(5)对话框返回值与数据传递
        - 对话框含有属性 DialogResult 
                DialogResult.OK :设置DialogResult为OK,窗体会立即关闭,并返回DialogResult.OK值
                DialogResult.Cancel:设置DialogResult为Cancel,窗体会立即关闭,并返回DialogResult.Cancel值
        - 数据传递:可以将传递数据组件设置为public,在关闭窗口后Dispose之前调用dialog.xxx来获取属性值

 (1)对话框界面设计

(2)对话框逻辑代码(MyDialog.cs) 

namespace WindowsFormsApp_learning
{
    public partial class MyDialog : Form
    {
        public MyDialog()
        {
            InitializeComponent();
        }
        //对话框确认按钮
        private void OnClickOk(object sender, EventArgs e)
        {
            //关闭当前对话框,并返回OK
            this.DialogResult = DialogResult.OK;
        }
        //对话框取消按钮
        private void OnClickCancel(object sender, EventArgs e)
        {
            //关闭当前对话框,并返回Cancel
            this.DialogResult = DialogResult.Cancel;
        }
    }
}

 (3)Form窗体逻辑代码

1.两大功能:

  • 通过点击按钮,创建并展示对话框
  • 获取对话框返回值,并显示在主窗体界面上

namespace WindowsFormsApp_learning
{
    public partial class Form5 : Form
    {
        public Form5()
        {
            InitializeComponent();
        }
        //点击按钮,触发对话框展示事件
        private void show_Dialog(object sender, EventArgs e)
        {
            MyDialog dialog = new MyDialog(); //新建窗体
            DialogResult res =  dialog.ShowDialog(); //作为对话框弹出在当前界面
            //阻塞当前进程,直到用户关闭对话框
            if(res == DialogResult.OK)//表示点击了确定按钮关闭的窗口
            {
                //获取数据,显示在父界面
                this.textPanel.Text += (dialog.dlg_text.Text + "\r\n");
            }
            dialog.Dispose(); //销毁窗体,释放资源
        }
    }
}

2.快捷操作+默认行为:
        - 对话框按钮快捷关闭:按钮上带有一个 DialogResult属性:OK、Cancel等都可以设置。其效果和手写代码是一样的,关闭对话框并返回值
        - 对话框的AcceptButton属性:当在对话框点击 回车 时会触发的按钮,默认为None
        - 对话框的CancelButton属性:当在对话框点击 ESC 时会触发的按钮,默认为None

 2.对话框小练习:字体设置器

        要求实现一个文本输入器,可以通过点击按钮弹出字体设置对话框,来选择相应的字体和字号。确认后将主窗口的文本输入变为相应的字体样式。

(1)主窗口界面设计 

 (2)对话框界面设计

 (3)对话框逻辑代码StyleDialog.cs

namespace WindowsFormsApp_Demo2
{
    public partial class StyleDialog : Form
    {
        public StyleDialog()
        {
            InitializeComponent();
        }
        //获取选择字体类型 public
        public string FontType
        {
            get
            {
                return (string)this.dlg_wordType.SelectedItem;
            }
            set
            {
                this.dlg_wordType.SelectedItem = value;
            }
        }
        //获取选择字体大小 public
        public int FontSize
        {
            get
            {
                return (int)this.dlg_wordSize.Value;
            }
            set
            {
                this.dlg_wordSize.Value = value;
            }
        }
        //按钮点击事件:返回值
        private void onClickOk(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
        }

        private void onClickCancel(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
        }
    }
}

(4)主窗体逻辑代码Form1.cs

namespace WindowsFormsApp_Demo2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //设置按钮点击事件:弹出对话框,并获取返回值
        private void onClickStyle(object sender, EventArgs e)
        {
            StyleDialog dlg = new StyleDialog();
            DialogResult res =  dlg.ShowDialog();
            if(res == DialogResult.OK)
            {
                //获取返回值,设置当前字体样式
                string fontType = dlg.FontType;
                int fontSize = dlg.FontSize;
                this.text_panel.Font = new Font(fontType, fontSize);
            }
            dlg.Dispose();
        }
    }
}

 四.系统对话框

1.系统对话框:WinForm提供了很多内置的系统对话框,比如文件浏览、保存,颜色、字体选择等。
(1)OpenFileDialog : 文件打开对话框
        - new OpenFileDialog():新建对话框
        - InitialDirectory:设置打开的初始展示路径
        - DefaultExt:获取或设置默认显示的文件扩展名。
        - Filter:获取或设置当前文件名筛选器字符串。其格式为 "自定义字符串标签|类型列表"
                a.单个文件类型:图片|*.jpg,*.png,*.gif
                b.多个文件类型:图片|*.jpg,*.png,*.gif|文本|*.txt,*.doc|所有|*.*
        - FileName:获取或设置对话框中选定的文件名称字符串
        - FileNames:获取或设置对话框中所有选定的文件名称字符串列表
(2)SaveFileDialog : 文件保存对话框(跟OpenFileDialog差不多)
(3)FolderBrowserDialog:文件夹选择对话框
        - new FolderBrowserDialog():新建对话框
        - InitialDirectory:    获取或设置打开的初始路径
        - SelectedPath:获取或设置选择的文件夹路径
(4)ColorDialog:颜色选择对话框
(5)FontDialog:字体选择对话框

1.系统对话框功能使用

        为了展示系统对话框的功能,此处设计一个文件浏览窗口,能够选择、保存文件或文件夹,并将所选定的文件/目录路径名称展示到主界面文本框。

(1)界面设计

(2)Form.cs逻辑代码

namespace WindowsFormsApp_learning
{
    public partial class Form6 : Form
    {
        public Form6()
        {
            InitializeComponent();
        }
        //选择文件按钮 点击事件
        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog fileDialog = new OpenFileDialog(); //新建文件对话框
            fileDialog.InitialDirectory = "D:\\日常材料\\学习资料\\WinForm\\第十二章 系统对话框\\12.1 系统对话框\\项目源码\\FormApp1201\\FormApp1201"; //设置初始路径
            //fileDialog.SelectedPath = Path.GetFullPath("."); 
            fileDialog.DefaultExt = ".cs"; //筛选展示 .cs后缀文件
            fileDialog.Filter = "代码|*.cs;*.config"; //设置文件选择器
            if (fileDialog.ShowDialog() == DialogResult.OK)
            {
                //获取并设置文件路径名称到主窗体
                string fileName = fileDialog.FileName;
                this.textBox1.Text = fileName;
            }

        }
        //保存文件按钮 点击事件
        private void button2_Click(object sender, EventArgs e)
        {
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.FileName = "新的文本文件";
            dlg.DefaultExt = ".txt";
            dlg.Filter = "文本|*.txt;*.doc";

            if (dlg.ShowDialog() == DialogResult.OK)
            {
                string fileName = dlg.FileName;
                this.textBox1.Text = fileName;
            }
        }
        //选择文件夹按钮 点击事件
        private void button3_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dlg = new FolderBrowserDialog();
            dlg.SelectedPath = Path.GetFullPath(".");//返回当前文件夹的绝对路径字符串
            if (dlg.ShowDialog() == DialogResult.OK)
            {
                string path = dlg.SelectedPath;
                this.textBox1.Text = path;
            }
        }
    }
}

 2.系统对话框练习-图片查看器

        设计一个图片查看器窗口,可以在主窗体中选择浏览目录路径,选择后将该目录下的所有图片名称加载到主窗口左侧的列表栏,用户通过点击列表栏的图片名称可以切换显示相应的图片在主窗口右侧的图片展示区。

 (1)界面设计

        主窗体中用到了一个自定义文本框AfTextBox,可以自由拉伸。同时有一个浏览按钮放置在其右侧,可以打开文件目录浏览窗口。主窗体还包括一个列表框,用于显示图片名称列表;除此之外还有一个图片框,用于展示指定路径图片。

(2) 自定义对话框AfTextBox单文件开发

namespace WindowsFormsApp_Demo
{
    class AfTextBox:UserControl
    {
        private TextBox edit;

        private void InitializeComponent()
        {
            this.edit = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // edit
            // 
            this.edit.BorderStyle = System.Windows.Forms.BorderStyle.None;
            this.edit.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
            this.edit.Location = new System.Drawing.Point(52, 52);
            this.edit.Name = "edit";
            this.edit.Size = new System.Drawing.Size(89, 19);
            this.edit.TabIndex = 0;
            this.edit.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.onTextPressed);
            // 
            // AfTextBox
            // 
            this.BackColor = System.Drawing.Color.White;
            this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.Controls.Add(this.edit);
            this.Name = "AfTextBox";
            this.Size = new System.Drawing.Size(199, 105);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        public AfTextBox()
        {
            InitializeComponent();
        }

        protected override void OnLayout(LayoutEventArgs e)
        {
            base.OnLayout(e);
            if (this.Controls.Count == 0) return;
            Control c = this.Controls[0];

            int w = this.Width, h = this.Height;
            int x = 0, y = 0;
            int left = this.Padding.Left, right = this.Padding.Right;
            w -= (left + right);
            x += left;
            //PreferredSize获取控件矩形容器框的大小
            //原因是文本框为单行输入,没法调节高度!但是外部矩形容器可以
            int h2 = c.PreferredSize.Height;
            if (h2 > h) h2 = h;
            y  += (h - h2) / 2;

            c.Location = new Point(x, y);
            c.Size = new Size(w, h2);
        }

        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override string Text {
            set
            {
                this.edit.Text = value;
            }
            get
            {
                return this.edit.Text;
            }
        }

        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override Color BackColor
        {
            get
            {
                return this.edit.BackColor;
            }
            set
            {
                this.edit.BackColor = value;
                //此处是base!!!!不是this!!!
                base.BackColor = value;
            }
        }

        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override Color ForeColor
        {
            get
            {
                return edit.ForeColor;
            }
            set
            {
                edit.ForeColor = value;
            }
        }


        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public bool ReadOnly
        {
            get
            {
                return this.edit.ReadOnly;
            }
            set
            {
                this.edit.ReadOnly = value;
            }
        }


        public event EventHandler ReturnPressed;

        private void onTextPressed(object sender, KeyPressEventArgs e)
        {
            char ch = e.KeyChar;
            if (ch == '\r')
            {
                ReturnPressed?.Invoke(this, e);
            }
        }
    }
}

(3)主窗体Form.cs逻辑代码

namespace WindowsFormsApp_Demo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //浏览按钮 点击事件
        private void onScanClick(object sender, EventArgs e)
        {
            FolderBrowserDialog dlg = new FolderBrowserDialog();
            if(dlg.ShowDialog() == DialogResult.OK)
            {
                string path = dlg.SelectedPath;
                this.afTextbox.Text = path;
                // 加载图片列表
                ShowPictureList(path);
            }

        }

        private void ShowPictureList(string dir)
        {
            // 清空显示
            listFile.Items.Clear();
            // 遍历所有的文件,检查文件名后缀
            string[] files = Directory.GetFiles(dir);
            foreach(string f in files)
            {
                if (f.EndsWith(".jpg") || f.EndsWith(".png") || f.EndsWith(".jpeg"))
                {
                    // 取得文件名
                    PictureListItem item = new PictureListItem();
                    item.name = Path.GetFileName(f);
                    item.filePath = f;
                    //Console.WriteLine("name:" + f + ",path:" + item.filePath);
                    // 加到列表框显示
                    this.listFile.Items.Add(item);
                }
            }
            // 默认打开第一个文件显示(手动SetSelect也会触发IndexChanged事件)



            if (listFile.Items.Count > 0)
                listFile.SetSelected(0, true);
        }

        private void listFile_SelectedIndexChanged(object sender, EventArgs e)
        {
            PictureListItem item = (PictureListItem)listFile.SelectedItem;
            if (item == null) return;
            //加载图片
            pathPic.Load(item.filePath);
        }
    }

    // 文件项
    class PictureListItem
    {
        public string name;
        public string filePath;

        public override string ToString()
        {
            return name;
        }
    }
}

五.菜单栏和工具栏

1.菜单栏 MenuStrip:窗口顶部停靠菜单栏
(1)使用:设计界面可视化设计,可以添加菜单项、下拉框、分割线、文本
(2)属性:每个Item都可以设置ID、Text、Image(图标)、点击事件Click
2.工具栏 ToolStrip:窗口顶部停靠工具栏,一般位于菜单栏下方(图标栏)
(1)使用:设计界面可视化设计,可以添加工具按钮、分割线等。一般窗口上方第一排为菜单栏,第二排为工具栏,二者对应
(2)属性:每个Item都可以设置ID、Image(图标)、点击事件Click等
(3)开发技巧:工具栏与菜单栏一般具有对应关系,每个菜单都有对应的工具图标按钮,二者的点击回调事件可以共用。在事件中点击下拉箭头->选择菜单栏设置的事件即可
3.右键菜单(上下文菜单): ContextMenuStrip
(1)定义:在窗口不同部位右键显示的菜单栏应该具有不同的选项,一般项目中菜单栏和工具栏可能没有,但是右键菜单是一定具有的!
(2)使用:
        - 在设计界面添加 ContextMenuStrip上下文菜单,并添加菜单项。但此时仅是对上下文菜单进行了定义和声明,右键还不能显示
        - 设置界面或者控件的鼠标点击事件MouseUp/MouseDown,在事件中展示菜单ContextMenuStrip.show(展示控件,展示位置)
        - 进一步,我们希望在listBox中,右键item的时候显示编辑和删除,而右键空白位置只显示添加,才符合我们的逻辑(不同的逻辑展示不同的右键菜单内容)
        - 设置菜单项点击事件

 (1)界面设计

(2)Form.cs逻辑代码

namespace WindowsFormsApp_learning
{
    public partial class Form5 : Form
    {
        public Form5()
        {
            InitializeComponent();
        }

        //工具栏Item 点击事件
        private void 打开ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("点击了打开...");
        }
        //listBox 列表控件 点击事件
        private void listBox1_MouseDown(object sender, MouseEventArgs e)
        {
            //如果是鼠标右键点击了,才触发右键菜单
            if(e.Button == MouseButtons.Right)
            {
                //IndexFromPoint:返回从指定位置处开始的第一个Item索引
                //e.Location:鼠标点击的位置Point
                int index = listBox1.IndexFromPoint(e.Location);
                //如果index>=0,则说明右键了某个Item项而不是空白位置
                if (index >= 0)
                {
                    //要显示/允许选择 编辑/删除项
                    listBox1.SetSelected(index, true);//选中该项
                    Item_Edit.Enabled = true;//右键菜单项设置允许点击
                    Item_Del.Enabled = true;
                }
                else
                {
                    //清空选择,不显示/禁用编辑/删除
                    listBox1.ClearSelected();//清空
                    Item_Edit.Enabled = false;//禁用
                    Item_Del.Enabled = false;

                    //Item_Edit.Visible = false;//直接不显示该项
                    //Item_Del.Visible = false;
                }
                this.contextMenu.Show(listBox1, e.Location);//显示右键菜单
            }
        }
        //右键菜单项[添加] 点击事件
        private void Item_Add_Click(object sender, EventArgs e)
        {
            MessageBox.Show("上下文菜单:选择了添加...");
        }
    }
}

六.列表视图控件 ListView

1.ListView基本使用

1.列表控件ListView:升级版的ListBox,其显示类似于windows的文件夹视图
(1)特点:列表显示模式可以切换(详情、大图标、小图标),可以多字段显示,可以设置图标、标签可以编辑,每列可以排序,可以自定义设计
(2)控件基本属性:View(显示模式)、Columns(集合,列头名称)、Items(集合,列表中的项)、Items.SubTetms(集合,列表中每项的子项-其他列显示的数据)、LabelEdit(是否允许用户就地编辑标签)、LargeImageList(大图标列表)、SmallImageList(小图标列表)、Sorting(排序方式)等
(3)简单使用步骤:
        - 在设计界面将ListView拖入,并设置Dock为占满(接下来可以在设计界面进行可视化设计,也可以通过代码来添加设计数据项)
        - 代码方式设计ListView(推荐):
                a.设置显示方式
                b.设置列名
                c.添加数据项
                d.设置每个数据项的子项数据
        - 推荐使用代码方式添加数据的原因:很多时候我们并不知道要遍历的项有多少,所以不能提前设计好。只能通过代码方式动态添加

(1)ListView基本用法展示 

        界面设计非常简单,就是拖入一个ListView控件,并设置Dock为Fill填充满父容器,无其他控件,这里就不再展示设计界面了。基本用法主要展示如何在ListView中使用代码方式添加列名、数据项等。

namespace WindowsFormsApp_learning2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            //初始化ListView控件
            InitializeListView();
        }

        private void InitializeListView()
        {
            //1.设置显示模式为详情模式:显示图标+主项+所有子项
            listView1.View = View.Details;
            // 设置整行选中(选中某一项的时候选中整行,而不只是主项)
            listView1.FullRowSelect = true;
            //2.设置列名Add(列名,宽度[像素值],对齐方式)
            // 宽度值-2表示自动调整宽度
            listView1.Columns.Add("文件名",-2,HorizontalAlignment.Left);
            listView1.Columns.Add("修改时期", 150, HorizontalAlignment.Left);
            listView1.Columns.Add("类型", 100, HorizontalAlignment.Left);
            listView1.Columns.Add("大小", -2, HorizontalAlignment.Left);
            //3.添加数据项 ListViewItem(数据名称,图标下标)
            ListViewItem item1 = new ListViewItem("Java学习指南.pdf", 0);
            //4.设置子项数据(对应每列的数据)
            item1.SubItems.Add("2020-2-20 12:10");
            item1.SubItems.Add("PDF");
            item1.SubItems.Add("192 KB");
            listView1.Items.Add(item1);
        }
    }
}

 (2)ListView基本使用小练习:本地文件目录列表展示

2.ListView控件高级使用:本地文件可视化
(1)在设计界面,将ListView控件拖入,并设置Dock为填充Fill
(2)添加数据项图标的图片资源到Resources
(3)在代码中初始化ListView基本配置,包括列名、显示模式、图标列表等。 InitListView()

namespace WindowsFormsApp_learning2
{

    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
            //初始化ListView控件
            InitListView();
            //加载添加文件数据项
            LoadDir(new DirectoryInfo("D:\\"));
        }

        //1.初始化ListView控件
        private void InitListView()
        {
            //设置显示模式为详情模式
            listView1.View = View.Details;
            //设置整行选中
            listView1.FullRowSelect = true;
            //设置列名
            listView1.Columns.Add("名称", -2, HorizontalAlignment.Left);
            listView1.Columns.Add("修改时间", 150, HorizontalAlignment.Left);
            listView1.Columns.Add("类型", 100, HorizontalAlignment.Left);
            listView1.Columns.Add("大小", -2, HorizontalAlignment.Left);
            //创建ImageList(提供一些方法,来管理一些列图片资源的列表类),并添加2个小图标
            ImageList imgList = new ImageList();
            imgList.ImageSize = new Size(16, 16);//设置列表中图片的大小
            imgList.Images.Add(Properties.Resources.Icon_file);//事先添加到Resources的图标
            imgList.Images.Add(Properties.Resources.Icon_folder);
            // 设置 SmallImageList 用于显示小图标(适用于小图标、详情、列表模式显示图标)
            // 设置 LargeImageList 用于显示大图标(适用于大图标模式显示图标)
            listView1.SmallImageList = imgList;
        }

        //2.遍历本地资源,获取文件或目录
        private void LoadDir(DirectoryInfo dir)
        {
            // 加载优化:BeginUpdate()+EndUpdate(),该方法指明内部的程序全部执行完后才进行一次总更新,而不是每Add一项就更新一次。(避免频繁刷新)
            // listView1.BeginUpdate();

            // 获取[子目录]列表
            DirectoryInfo[] subDirs = dir.GetDirectories();
            foreach (DirectoryInfo d in subDirs)
            {           
                //如果该目录是隐藏文件,则跳过
                if ((d.Attributes & FileAttributes.Hidden) > 0) continue;
                //否则就添加到ListVie中显示(目录名称,最新修改时间,数据项类型,文件大小)
                AddListItem(d.Name, d.LastWriteTime, "文件夹", -1);
            }
            // 获取[子文件]列表
            FileInfo[] subFiles = dir.GetFiles();
            foreach (FileInfo f in subFiles)
            {
                //如果该文件是隐藏文件,则跳过
                if ((f.Attributes & FileAttributes.Hidden) > 0) continue;
                string ext = f.Extension.ToUpper(); // 获取文件拓展名后缀(.zip、.png、.doc等)
                //否则就添加到ListVie中显示(文件名称,最新修改时间,文件后缀类型,文件大小)
                AddListItem(f.Name, f.LastWriteTime, ext, f.Length);
            }

            // listView1.EndUpdate();
        }

        //3,将文件和目录按照不同类型添加到ListView数据项
        private void AddListItem(string label, DateTime time, string type, long size)
        {
            // 判断是文件还是文件夹,使用不同的图标
            int imageIndex = 0;
            if (!type.Equals("文件夹")) imageIndex = 1;

            ListViewItem item = new ListViewItem(label, imageIndex);//imageIndex会去listView下的SmallImageList找图标

            // 添加子项 - 时间
            item.SubItems.Add(time.ToString("yyyy-MM-dd HH:mm"));
            // 添加子项 - 类型
            item.SubItems.Add(type);
            // 添加子项 - 文件大小
            string sizeStr = "";
            if (size < 0)
                sizeStr = ""; // 文件夹不显示大小
            else if (size < 1000)
                sizeStr = "" + size;
            else if (size < 1000000)
                sizeStr = size / 1000 + " KB";
            else if (size < 1000000000)
                sizeStr = size / 1000000 + " MB";
            else
                sizeStr = size / 1000000000 + " GB";

            item.SubItems.Add(sizeStr);

            listView1.Items.Add(item);
        }

    }
}

 2.ListView模式切换

3.ListView控件:模式切换(切换详情、列表、大图标、小图标模式显示)
(1)设计要求:在上节内容文件目录展示案例的基础上,实现右键菜单切换显示模式
(2)开发流程:
        - 在设计界面,拖入ListView控件,并设置Dock为充满Fill
        - 在代码中初始化ListView控件列名、[大图标列表]、[小图标列表]等(因为大图标模式和小图标模式需要使用两套图标
        - 加载遍历文件和目录,并添加数据项Item
        - 在设计界面拖入ContextMenu控件,并设置ListView的MouseUp点击事件,显示右键菜单
        - 右键菜单实现切换模式功能

namespace WindowsFormsApp_learning2
{

    public partial class Form3 : Form
    {
        public Form3()
        {
            InitializeComponent();
            //1.初始化ListView控件
            InitListView();
            //2.载入文件和目录
            LoadDir(new DirectoryInfo("D:\\"));
        }

        //1.初始化ListView控件
        private void InitListView()
        {
            listView1.View = View.Details;
            listView1.FullRowSelect = true;
            //设置列名
            listView1.Columns.Add("名称", -2, HorizontalAlignment.Left);
            listView1.Columns.Add("修改时间", 150, HorizontalAlignment.Left);
            listView1.Columns.Add("类型", 100, HorizontalAlignment.Left);
            listView1.Columns.Add("大小", -2, HorizontalAlignment.Left);
            //*创建SmallImageList 小图标列表(用于Detail、SmallIcon等模式里显示小图标)
            ImageList smallImgList = new ImageList();
            smallImgList.ImageSize = new Size(16, 16);//设置列表中图片的大小
            smallImgList.Images.Add(Properties.Resources.Icon_file);
            smallImgList.Images.Add(Properties.Resources.Icon_folder);
            listView1.SmallImageList = smallImgList;
            //*创建LargeImageList 大图标列表(用于LargeIcon模式里显示大图标)
            ImageList largeImgList = new ImageList();
            largeImgList.ImageSize = new Size(64, 64);//设置列表中图片的大小
            largeImgList.Images.Add(Properties.Resources.Icon_file2);
            largeImgList.Images.Add(Properties.Resources.Icon_folder2);
            listView1.LargeImageList = largeImgList;
        }

        //2.加载数据项:遍历文件和目录,按照不同类型添加数据项Item及其子项
        private void LoadDir(DirectoryInfo dir)
        {
           listView1.BeginUpdate();

            // 获取[子目录]列表
            DirectoryInfo[] subDirs = dir.GetDirectories();
            foreach (DirectoryInfo d in subDirs)
            {
                if ((d.Attributes & FileAttributes.Hidden) > 0) continue;
                AddListItem(d.Name, d.LastWriteTime, "文件夹", -1);
            }
            // 获取[子文件]列表
            FileInfo[] subFiles = dir.GetFiles();
            foreach (FileInfo f in subFiles)
            {
                if ((f.Attributes & FileAttributes.Hidden) > 0) continue;
                string ext = f.Extension.ToUpper();//文件后缀名
                AddListItem(f.Name, f.LastWriteTime, ext, f.Length);
            }

           listView1.EndUpdate();
        }

        //3,将文件和目录按照不同类型添加到ListView数据项
        private void AddListItem(string label, DateTime time, string type, long size)
        {
            // 判断是文件还是文件夹,使用不同的图标
            int imageIndex = 0;
            if (!type.Equals("文件夹")) imageIndex = 1;
            ListViewItem item = new ListViewItem(label, imageIndex);
            // 添加子项 - 时间
            item.SubItems.Add(time.ToString("yyyy-MM-dd HH:mm"));
            // 添加子项 - 类型
            item.SubItems.Add(type);
            // 添加子项 - 文件大小
            string sizeStr = "";
            if (size < 0)
                sizeStr = ""; // 文件夹不显示大小
            else if (size < 1000)
                sizeStr = "" + size;
            else if (size < 1000000)
                sizeStr = size / 1000 + " KB";
            else if (size < 1000000000)
                sizeStr = size / 1000000 + " MB";
            else
                sizeStr = size / 1000000000 + " GB";

            item.SubItems.Add(sizeStr);
            listView1.Items.Add(item);
        }

        //4.ListView添加MouseUp点击事件:显示右键菜单(菜单中默认选中当前的显示模式)
        private void listView1_MouseUp(object sender, MouseEventArgs e)
        {
            if(e.Button == MouseButtons.Right)
            {
                View view = listView1.View;
                //根据显示模式,设置右键菜单的选中项
                menuDetail_Item.Checked = (view == View.Details);
                menuList_Item.Checked = (view == View.List);
                menuLarge_Item.Checked = (view == View.LargeIcon);
                menuSmall_Item.Checked = (view == View.SmallIcon);
                //显示右键菜单(在鼠标右键位置e.Location)
                contextMenuStrip.Show(listView1, e.Location);
            }
        }


        //5.设置右键菜单的点击事件:切换显示模式
        private void menuDetail_Item_Click(object sender, EventArgs e)
        {
            listView1.View = View.Details;
        }

        private void menuList_Item_Click(object sender, EventArgs e)
        {
            listView1.View = View.List;
        }

        private void menuLarge_Item_Click(object sender, EventArgs e)
        {
            listView1.View = View.LargeIcon;
        }

        private void menuSmall_Item_Click(object sender, EventArgs e)
        {
            listView1.View = View.SmallIcon;
        }

    }
}

 3.ListView数据项列排序

4.ListView的列排序实现:点击每一列可以实现升序/降序排序(此处只以第0列为例)
(1)编码步骤(在文件目录展示案例的基础上):
        - 添加箭头图标资源,表示升序和降序标识
        - 将图标加入SmallImageList,其中Images.Add (key, image ) 表示添加一个图像image,并关联一个key值。后面可以根据key值获取此图片。
        - 添加数据项Tag:每个ListView可以关联一个Tag,一般为一个对象。用于排序的时候便于直接使用对象做比较
        - 定义比较器MyListItemSorter,实现绑定Tag对象的自定义排序
        - 添加列的点击事件:ColumnClick 切换升序/降序

 (1)自定义Tag对象,封装信息

namespace WindowsFormsApp_learning2
{
    // 每个列表项关联的Tag数据
    class MyListItemTag
    {
        public int type = 0; //类型:文件夹0, 文件1 
        public string path;
        public string name;
        public DateTime time;
        public long size = -1;
        public string ext; // 显示文件后缀/文件夹

        public MyListItemTag(int type, string path, string name, DateTime time, long size, string ext)
        {
            this.type = type;
            this.path = path;
            this.name = name;
            this.time = time;
            this.size = size;
            this.ext = ext;
        }
    }
}

(2)自定义比较器,实现对象排序(传入两个Tag对象)

namespace WindowsFormsApp_learning2
{
    //继承IComparer接口,实现对象比较方法
    public class MyListItemSorter : IComparer
    {
        public bool asc = true;//是否升序排序

        public MyListItemSorter(bool asc)
        {
            this.asc = asc;
        }

        public int Compare(object x, object y)
        {
            //按列排序时,传入的是两个ListViewItem对象
            ListViewItem item1 = (ListViewItem)x;
            ListViewItem item2 = (ListViewItem)y;

            // Tag: 每一项关联的数据
            MyListItemTag tag1 = (MyListItemTag)item1.Tag;
            MyListItemTag tag2 = (MyListItemTag)item2.Tag;

            // 目录在前, 文件在后
            if (tag1.type != tag2.type)
                return CompareInt(true, tag1.type, tag2.type);

            // 按名字比较
            return CompareStringIgnoreCase(asc, tag1.name, tag2.name);
        }

        // 两个 Bool 值的比较
        public int CompareBool(bool asc, bool x, bool y)
        {
            int xx = x ? 1 : 0;
            int yy = y ? 1 : 0;
            return CompareInt(asc, xx, yy);
        }

        // 两个 Int 值的比较
        public int CompareInt(bool asc, int x, int y)
        {
            if (asc)
                return x - y;
            else
                return y - x;
        }

        // 两个String值的比较
        public int CompareString(bool asc, string x, string y)
        {
            if (asc)
                return x.CompareTo(y);
            else
                return y.CompareTo(x);
        }

        // 两个String值的比较 (不区分大小写)
        public int CompareStringIgnoreCase(bool asc, string x, string y)
        {
            return CompareString(asc, x.ToLower(), y.ToLower());
        }
    }
}

(3)主窗体Form.cs逻辑代码 

namespace WindowsFormsApp_learning2
{
    public partial class Form4 : Form
    {
        // 当前默认排序:升序
        private bool asc = true;
        public Form4()
        {

            InitializeComponent();
            InitListView();
            LoadDir(new DirectoryInfo("D:\\"));
        }

        //1.初始化ListView控件
        private void InitListView()
        {
            listView1.View = View.Details;
            listView1.FullRowSelect = true;
            //设置列名
            listView1.Columns.Add("名称", -2, HorizontalAlignment.Left);
            listView1.Columns.Add("修改时间", 150, HorizontalAlignment.Left);
            listView1.Columns.Add("类型", 100, HorizontalAlignment.Left);
            listView1.Columns.Add("大小", -2, HorizontalAlignment.Left);
            //创建smallImageList,添加小图标资源
            ImageList imgList = new ImageList();
            imgList.ImageSize = new Size(16, 16);//设置列表中图片的大小
            imgList.Images.Add(Properties.Resources.Icon_file);
            imgList.Images.Add(Properties.Resources.Icon_folder);
            listView1.SmallImageList = imgList;
            // 点击列标题时,指示排序的图标
            // 把图标资源加到 SmallImageList 里,供列标题头部显示
            imgList.Images.Add("Sort_ASC", Properties.Resources.up);
            imgList.Images.Add("Sort_DESC", Properties.Resources.down);
            //ListView首列图标显示为 key=""Sort_ASC"
            listView1.Columns[0].ImageKey = "Sort_ASC";
        }

        //2.遍历本地资源,获取文件或目录
        private void LoadDir(DirectoryInfo dir)
        {
            listView1.BeginUpdate();

            // 获取[子目录]列表
            DirectoryInfo[] subDirs = dir.GetDirectories();
            foreach (DirectoryInfo d in subDirs)
            {
                if ((d.Attributes & FileAttributes.Hidden) > 0) continue;
                //**将数据打包为Tag对象进行添加
                MyListItemTag tag = new MyListItemTag(0,d.FullName,d.Name,d.LastWriteTime,-1,"文件夹");
                AddListItem(tag);
            }
            // 获取[子文件]列表
            FileInfo[] subFiles = dir.GetFiles();
            foreach (FileInfo f in subFiles)
            {
                if ((f.Attributes & FileAttributes.Hidden) > 0) continue;
                //**将数据打包为Tag对象进行添加
                MyListItemTag tag = new MyListItemTag(1, f.FullName, f.Name, f.LastWriteTime, f.Length, f.Extension.ToUpper());
                AddListItem(tag);
            }

            listView1.EndUpdate();
        }

        //3,将文件和目录按照不同类型添加到ListView数据项
        private void AddListItem(MyListItemTag tag)
        {
            // 判断是文件还是文件夹,使用不同的图标
            int imageIndex = 0;
            if (!tag.type.Equals("文件夹")) imageIndex = 1;
            ListViewItem item = new ListViewItem(tag.name, imageIndex);
            // **Item关联Tag数据对象(方便我们从Item中获取数据、进行排序等)
            item.Tag = tag;
            // 事件子项
            item.SubItems.Add(tag.time.ToString("yyyy-MM-dd HH:mm"));
            // 后缀名称子项
            item.SubItems.Add(tag.ext);
            // 大小子项
            long size = tag.size;
            string sizeStr = "";
            if (size < 0)
                sizeStr = ""; // 文件夹不显示大小
            else if (size < 1000)
                sizeStr = "" + size;
            else if (size < 1000000)
                sizeStr = size / 1000 + " KB";
            else if (size < 1000000000)
                sizeStr = size / 1000000 + " MB";
            else
                sizeStr = size / 1000000000 + " GB";

            item.SubItems.Add(sizeStr);
            listView1.Items.Add(item);
        }

        //4.ListView的列点击事件:切换升序/降序(只针对第0列)
        private void listView1_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            if (e.Column == 0)//如果点击的是第0列,才触发事件
            {
                //切换当前排序
                this.asc = !asc;
                if (asc)
                {
                    //设置排序器
                    listView1.ListViewItemSorter = new MyListItemSorter(true);
                    listView1.Sort();//排序
                    listView1.Columns[0].ImageKey = "Sort_ASC";//切换图标
                }
                else
                {
                    listView1.ListViewItemSorter = new MyListItemSorter(false);
                    listView1.Sort();
                    listView1.Columns[0].ImageKey = "Sort_DESC";
                }
            }
        }
    }
}

 4.ListView标签编辑

5.ListView:编辑标签
(1)设计ListView显示,并添加数据项
(2)在设计界面或代码中设置LabelEdit=True,表示允许对数据项标签进行编辑
(3)标签编辑的启动方式:
        - 在窗口中双击标签,启动编辑
        - 使用代码方式,在事件中使用 ListViewItem.BeginEdit() 来启动编辑(比如右键菜单-重命名 事件):
                a.添加右键菜单,新建菜单项-重命名
                b.ListView添加MouseUp鼠标点击事件,显示右键菜单,获取右键点中的项
                c.右键菜单项添加鼠标点击事件,点击右键菜单-重命名,开启该项的标签编辑
                d.编辑验证:在编辑之后/之前,进行标签内容的验证(比如不能重复名称,不能包含某些字符等),在ListView中添加事件BeforeLabelEdit/AfterLabelEdit,开启对每个数据项的编辑验证(自动在编辑前后触发)
(4)注意:
        - 只能对主项进行编辑
        - 标签编辑默认不会修改源数据(比如文件显示不会修改真实文件名称),要想同步修改可以设置事件

namespace WindowsFormsApp_learning2
{
    public partial class Form5 : Form
    {
        // 当前鼠标事件点中的项
        private ListViewItem mouseClickItem;

        public Form5()
        {
            InitializeComponent();

            InitListView();

            // 初始化添加几项数据
            AddListItem(new Student(20201001, "shao", "13810012xxx"));
            AddListItem(new Student(20201002, "wang", "18799122xxx"));
            AddListItem(new Student(20201003, "li", "13490912xxx"));
        }

        //1.设计ListView显示,并添加数据项
        private void InitListView()
        {
            listView1.View = View.Details;
            listView1.FullRowSelect = true;
            //**设置LabelEdit,允许对数据项标签进行编辑
            listView1.LabelEdit = true;

            // 设置列名
            // 宽度值-2表示自动调整宽度
            listView1.Columns.Add("姓名", 150, HorizontalAlignment.Left);
            listView1.Columns.Add("学号", 150, HorizontalAlignment.Left);
            listView1.Columns.Add("手机号", -2, HorizontalAlignment.Left);
        }
        private void AddListItem(Student stu)
        {
            ListViewItem item = new ListViewItem(stu.Name, 0);
            item.Tag = stu;

            item.SubItems.Add(stu.Id + "");
            item.SubItems.Add(stu.Phone);
            listView1.Items.Add(item);
        }

        //2.右键显示右键菜单
        private void listView1_MouseUp(object sender, MouseEventArgs e)
        {
            if(e.Button == MouseButtons.Right)
            {
                //获取 右键点击的ListViewItem数据项
                ListViewItem item = listView1.GetItemAt(e.X, e.Y);
                this.mouseClickItem = item;
                //判断是否可以重命名该项
                renameMenu_Item.Enabled = (item != null);
                //展示该位置/该项 的 右键菜单
                contextMenuStrip1.Show(listView1, e.Location);
            }
        }

        //3.右键菜单-重命名 点击事件,开启该项的标签编辑
        private void renameMenu_Item_Click(object sender, EventArgs e)
        {
            this.mouseClickItem.BeginEdit();
        }

        //4.开启列表项的编辑 自动触发事件BeforeLabelEdit
        private void listView1_BeforeLabelEdit(object sender, LabelEditEventArgs e)
        {

        }

        //5.结束列表项的编辑 自动触发事件AfterLabelEdit(事件发送器 sender,事件处理对象 e)
        private void listView1_AfterLabelEdit(object sender, LabelEditEventArgs e)
        {
            //获取ListView当前编辑项的下标Index
            int index = e.Item;
            //获取新编辑的文本
            string label = e.Label;
            // 检查名称是否冲突
            for (int i = 0; i < listView1.Items.Count; i++)
            {
                if (index == i) continue;
                if (label == listView1.Items[i].Text)
                {
                    //CancelEdit=True 取消新编辑的内容,恢复原始内容
                    e.CancelEdit = true;
                    MessageBox.Show("名字重复", "提示");
                    return;
                }
            }
            // 否则就没有重复名称,接受新的更改
            e.CancelEdit = false; 
            // 同步更新后台数据(对象为引用数据)
            Student stu = (Student)listView1.Items[index].Tag;
            stu.Name = label;
        }
    }
}

 七.表格视图控件 DataGridView

1.表格视图基本用法

1.表格视图 DataGridView 基础: 以单元格表格的形式展示数据和操作
(1)在设计界面,拖入DataGridView控件,并设置Dock为填满Fill
(2)在设计界面或代码中,设置列名Columns
(3)在设计界面或代码中添加数据项,推荐代码添加(动态拓展)
 * 注意:表格视图的每一单元格应都是可编辑的!

namespace WindowsFormsApp_learning2
{
    public partial class Form6 : Form
    {
        public Form6()
        {
            InitializeComponent();
            //初始化表格视图布局
            InitGridView();
            //初始化添加数据
            AddRows(new Student(202101, "wx", "18264886xxx"));
            AddRows(new Student(202102, "my", "13012459xxx"));
            AddRows(new Student(202103, "tom", "19857138xxx"));
        }

        //1.初始化列数、列名
        private void InitGridView()
        {
            //设置表格显示列数
            this.dataGridView1.ColumnCount = 4;
            //设置每列的列头名称
            dataGridView1.Columns[0].Name = "学号";
            dataGridView1.Columns[1].Name = "姓名";
            dataGridView1.Columns[2].Name = "性别";
            dataGridView1.Columns[3].Name = "手机号";
        }
        //2.添加表格数据项
        private void AddRows(Student stu)
        {
            //设置Object数组!代表一行数据
            Object[] row =
            {
                stu.Id,stu.Name,"男",stu.Phone
            };
            //添加数据行
            dataGridView1.Rows.Add(row);
        }
    }
}

 2.表格视图高级用法

2.表格视图 DataGridView 基本操作:属性、增删改查
(1)DataGridView 常用属性:
        - [杂项] Columns : 设置列相关的列数、列名等
        - [外观] ColumnHeadersVisible : 设置列头是否可见(默认为True)
        - [外观] RowHeadersVisible : 设置行的头是否可见(默认为True)
        - [行为] MultiSelect : 是否允许多项选择(默认为True)
        - [行为] AllowUserToxxx:是否允许用户对单元格进行 增删改查等修改
        - [行为] 列还支持点击 升序/降序 排序(自动已实现)
(2)DataGridView 常用操作:
        - 添加一行数据:grid.Rows.Add(Object[]) 或 界面手动添加(需要触发事件,持久化到数据库)
        - 获取行数据:
                a.索引方式:grid[col,row] 注意是列索引在前,行索引在后
                b.函数方式:grid.Rows[i].Cells[j] 
        - 删除行数据:
                a.单行删除:grid.Rows.RemoveAt(int i):删除第i行数据
                b.多行删除(ctrl多选):grid.Rows.Remove(DataGridViewRow row):删除指定的row对象

3.表格视图 DataGridView 单元格编辑
(1)实现方式:
        - 界面直接编辑:DataGridView默认每个单元格都是可以直接编辑的,编辑修改后按下回车即可将修改显示到界面中。
        - 添加事件触发编辑:也可以通过添加右键菜单或双击事件来进行编辑,或者弹出对话框编辑。
(2)相关属性:ReadOnly
        - 设计界面:Columns中,设置某列为ReadOnly=True只读,不能被编辑修改;ReadOnly=False可编辑
        - 代码中:grid.Columns[0].ReadOnly = True;
(3)单元格编辑事件步骤:
        - 设置状态:设置ReadOnly = False 可编辑状态(默认为False)
        - 启动编辑:界面中点击单元格选中,再点击一次,则启动进入编辑状态
        - 编辑后的验证事件:当编辑完成按下回车时,触发 [焦点] CellValidating 单元格验证时发生,CellValidating 执行完成后 触发CellValidated 单元格验证后发生

(1)案例界面设计

        设计一个案例,实现对表格视图的基本操作(增删改查),以及编辑事件验证。

(2)Form.cs主窗体逻辑代码

namespace WindowsFormsApp_learning2
{
    public partial class Form7 : Form
    {
        public Form7()
        {
            InitializeComponent();
            InitGridView();
            AddRows(new Student(202101, "wx", "18264886xxx"));
            AddRows(new Student(202102, "my", "13012459xxx"));
            AddRows(new Student(202103, "tom", "19857138xxx"));
        }

        //1.初始化列数、列名
        private void InitGridView()
        {
            this.grid.ColumnCount = 4;
            grid.Columns[0].Name = "学号";
            grid.Columns[1].Name = "姓名";
            grid.Columns[2].Name = "性别";
            grid.Columns[3].Name = "手机号";
        }
        //2.添加表格数据项
        private void AddRows(Student stu)
        {
            Object[] row =
            {
                stu.Id,stu.Name,"男",stu.Phone
            };
            grid.Rows.Add(row);
        }

        //3.添加按钮点击事件:添加一行数据
        private void btn_add_Click(object sender, EventArgs e)
        {
            Object[] row = new object[4];
            row[0] = 202104;
            row[1] = "testUser";
            row[2] = "女";
            row[3] = "1928373838";
            grid.Rows.Add(row);
        }

        //4.获取数据按钮点击事件:获取行数据
        private void btn_get_Click(object sender, EventArgs e)
        {
            //获取第0行数据
            Object id = grid[0, 0].Value;
            Object name = grid[1, 0].Value;
            Object sex = grid.Rows[0].Cells[2].Value;
            Object phone = grid.Rows[0].Cells[3].Value;
            //显示在MessageBox
            MessageBox.Show(id + "," + name + "," + sex + "," + phone);
        }

        //5.删除按钮点击事件:删除选中行
        private void btn_del_Click(object sender, EventArgs e)
        {
            //单行删除 删除第0行
            //grid.Rows.RemoveAt(0);

            //多行删除 删除[选中]的所有行
            foreach(DataGridViewRow row in grid.SelectedRows)
            {
                grid.Rows.Remove(row);
            }
        }

        //6.编辑验证:单元格验证事件
        private void grid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        {
            //e.ColumnIndex 表示 编辑单元格的列索引
            if (e.ColumnIndex == 1)
            {
                //验证姓名的编辑
            }else if (e.ColumnIndex == 2)
            {
                //验证性别的编辑
            }
            else if(e.ColumnIndex == 3)
            {
                //验证手机号的编辑(手机号必须是11位合法数字)
                string str = e.FormattedValue.ToString();
                if(str.Length != 11)
                {
                    MessageBox.Show("手机号必须为11位", "Error");
                    e.Cancel = true;//取消编辑事件
                    return;
                }
                foreach(char ch in str)
                {
                    if(ch < '0' || ch > '9')
                    {
                        MessageBox.Show("手机号必须为数字", "Error");
                        e.Cancel = true;//取消编辑事件
                        return;
                    }
                }
            }
        }

        //7.单元格验证完成后执行的事件
        private void grid_CellValidated(object sender, DataGridViewCellEventArgs e)
        {

        }
    }
}

 八.综合练习:学生信息管理系统


http://www.niftyadmin.cn/n/4423124.html

相关文章

canal番外篇-otter

前置知识点 主从复制binlogcanal正则dockerjava 前置工具 dockerotter-all 场景描述&#xff08;增量同步&#xff09; 目前项目中使用的是mysql5.5&#xff0c;计划升级为mysql8.1&#xff0c;版本跨度较大&#xff0c;市面上可靠工具选择较少。otter符合预期&#xff0c…

显示表结构描述的Sql语句

select column_name,data_type,column_default,character_maximum_length from information_schema.columns where table_namet_conclusion select objname,value from ::fn_listextendedproperty(default,user,dbo,table,t_conclusion,column,default)

深度学习(一) Python基本科学计算库

一.Numpy NumPy(Numerical Python) 是 Python 语言的一个扩展程序库&#xff0c;支持大量的维度数组与矩阵运算&#xff0c;此外也针对数组运算提供大量的数学函数库。其基本运算对象是一个N维数组对象 ndarray。 - 导入Numpy&#xff1a;import numpy as np 1.ndarray 数组初始…

hibernate中调用存储过程hibernate中调用存储过程

如果底层数据库&#xff08;如Oracle&#xff09;支持存储过程&#xff0c;也可以通过存储过程来执行批量更新。存储过程直接在数据库中运行&#xff0c;速度更加快。在Oracle数据库中可以定义一个名为batchUpdateStudent()的存储过程&#xff0c;代码如下&#xff1a; create…

深度学习(二) 神经网络基础算法推导与实践

深度学习的核心就是各种不同的神经网络模型&#xff08;CNN、RNN、GCN、GNN等&#xff09;的学习和训练过程。这些神经网络模型的共同点都是一个“黑盒子”&#xff0c;通过一定的学习算法将大量数据交给模型训练&#xff0c;不断缩小模型预测值与真实值之间的误差&#xff0c;…

获取居于网内的机器名

查看文章获取局域网内的机器名2006-08-29 12:15unit main; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ImgList,ExtCtrls, ComCtrls; type TNetResourceArray ^TnetResource; TForm1 class(TForm) TreeVie…

深度学习(三) 卷积神经网络入门与实践

一.全连接神经网络的弊端 在上章学习完神经网络基础后&#xff0c;几乎所有问题的输入都能够Resize处理一下后扔进全连接神经网络&#xff08;FNN&#xff09;中进行训练。在图像学习任务中&#xff0c;图像&#xff08;多通道&#xff09;这类(N*C*W*H)多维输入格式数据需要通…

深度学习(四) 图像语义分割问题概述与实践

一.图像语义分割问题概述 图像语义分割(Semantic Segmentation) 是图像处理和机器学习视觉技术中关于图像理解的重要一环&#xff0c;也是 AI 领域中一个重要的分支。图像语义分割问题就是对图像中的不同物体打上语义标签&#xff08;用不同的颜色代表不同类别的物体&#xff0…