# 控件使用

添加控件前,先选定添加的父控件

# Label

  • this.label1.AutoSize = false 否则它会自己计算所需的大小

# TextBox

  • this.textBox1.AutoSize = false 否则它会自己计算所需的大小
  • 自定义背景色:this.txtmoble.BackColor = System.Drawing.Color.FromArgb(241,242,247);

# PictureBox

  • 背景透明:backColor属性设置成Transparent,再设置picturebox.Parent = 父容器(注意控件顺序)
//注意控件的顺序,先写的在上面
this.picHome.Parent = this.picMain;
this.picPre.Parent = this.picMain;
this.picNext.Parent = this.picMain;
this.panelMenu.Parent = this.picMain;
this.panelCon.Parent = this.picMain;
  • Image:抽象类,图像的统称,Bitmap:具体类,位图,像素图
Bitmap img = new Bitmap("c:\\example\\123.jpg");
//使用资源 Resources.resx
Bitmap img = Properties.Resources.Img_GeNie;
//使用资源 form1.resx
ComponentResourceManager myTest = new ComponentResourceManager(typeof(Form1));
Bitmap photo=myTest.GetObject("12121212") as Bitmap;

int w = img.Width;
int h = img.Height;

//设置缩放模式
picbox.SizeMode = PictureBoxSizeMode.Zoom;
//显示图片
picbox.Image = img;
//或者加载图片
picbox.Load("c:\\example\\123.jpg");

# Button

  • 去掉边框线:this.btnlogin.FlatStyle = System.Windows.Forms.FlatStyle.Flat;

# 系统对话框

系统自带的一些对话框类:

  • OpenFileDialog 打开文件对话框
  • SaveFileDialog 保存文件对话框
  • FolderBrowserDialog 目录选择对话框
  • ColorDialog 颜色选择对话框
  • FontDialog 字体选择对括框

# 普通对话框

//form1.cs
public Form1()
{
	InitializeComponent();

	// 初始化文本显示
	string str = "   迷 离 \r\n"
		+ "此 城 如 迷,\r\n"
		+ "此 人 如 离。\r\n"
		+ "此 生 如 戏,\r\n"
		+ "此 世 如 棋。\r\n"
		;
	contentEdit.Text = str;
}

private void styleButton_Click(object sender, EventArgs e)
{
	StyleDialog dlg = new StyleDialog();
	
	if( dlg.ShowDialog() == DialogResult.OK)
	{
		string family = dlg.fontBox.SelectedItem.ToString();
		int size =int.Parse(dlg.fontsizeBox.Value.ToString());
		contentEdit.Font = new Font(family, (float)size);
	}

	dlg.Dispose();
}
}

//StyleDialog.cs
public partial class StyleDialog : Form
{
	public StyleDialog()
	{
		InitializeComponent();
        //下拉框初始值
		this.fontBox.SelectedIndex = 0;
	}
    
	//Button类有一个 DialogResult属性,可用于直接设置DialogResult的值
	//当按回车键时触发DialogResult.OK,当按ESC键时触发DialogResult.Cancel
	//当点叉号关闭窗口时,相当于Cancel
	private void okButton_Click(object sender, EventArgs e)
	{
		this.DialogResult = DialogResult.OK;
	}
	private void cancelButton_Click(object sender, EventArgs e)
	{
		this.DialogResult = DialogResult.Cancel;
	}
}

DialogResult的作用

1、关闭对话框
2、设定返回值, 即 ShowDialog() 返回的值

# Listbox/Textbox/Richtextbox

Listbox:列表框,可以添加文本输出,只能以行输出,若想要一行数据自动换行,只能通过计算宽度来Add多次,文本不可以自动换行,但是每行可以添加颜色

//获取当前最底行,滚动到最底行
listbox.TopIndex = this.listbox.Items.Count - (int)(listbox.Height/listbox.ItemHeight); 

//颜色变换
//先在前面窗体load中加入
listbox.DrawMode = DrawMode.OwnerDrawFixed;//控件中元素可以手动绘制
//在listbox的DrawItem事件中重绘当前行的字体颜色
void listbox_DrawItem(object sender, DrawItemEventArgs e)
{
    e.DrawBackground();
    Brush mybsh = Brushes.Black;
	//如果当前行的内容中有“Error”,那么该行显示为红色
    if(listbox.Items[e.Index].ToString().IndexOf("Error")  != -1)  
        mybsh = Brushes.Red;
        e.DrawFocusRectangle();
        e.Graphics.DrawString(listbox.Items[e.Index].ToString(), 
		                      e.Font, mybsh, e.Bounds, StringFormat.GenericDefault);
}

Textbox:文本框,可以添加文本输出,文本支持自动换行,每行不可以添加颜色进行显示,换行符"\r\n"

textbox.SelectionStart = richtextbox.Text.Length - 1; //获取文本起始点 滚动最底行
textbox.ScrollTocaret();//滚动条滚动到当前插入符号位置

Richtextbox:Textbox的加强版,可以添加文本输出,文本支持自动换行,每行可以添加颜色显示,换行符"\n"。注意如果需要将该richtextbox的内容打到日志中,需要先将字符串中的换行符"\n"全部替换为"\r\n"

richtextbox.SelectionFont = new Font ("楷体", 12, FontStyle.Bold); //设置SelectionFont属性
richtextbox.SelectionColor = Color.Red;//设置文本起始点颜色
richtextbox.SelectionStart = richtextbox.Text.Length - 1; //获取文本起始点
richtextbox.ScrollToCaret();//滚动条滚动到当前插入符号位置
//添加文本
richtextbox.AppendText(Environment.NewLine+"["+DateTime.Now.ToString()+"]:"+message);

# 右键菜单 ContextMenuStrip

//给ListBox 添加鼠标事件 MouseUp
private void listBox1_MouseUp(object sender, MouseEventArgs e)
{
	if(e.Button == MouseButtons.Right)
	{
		//根据鼠标点击的位置,判断点中了哪一项
		int index = listBox1.IndexFromPoint(e.Location);
		if(index>=0)
		{
			listBox1.SetSelected(index, true);
			menuItem_Edit.Enabled = true;
			menuItem_Del.Enabled = true;
		}
		else
		{
			listBox1.ClearSelected();
			menuItem_Edit.Enabled = false;
			menuItem_Del.Enabled = false;

			// menuItem_Add.Visible = true;
		}

		this.contextMenuStrip1.Show(listBox1, e.Location);
	}
}

# 列表控件 ListView

列表控件的几个特点:

  • 显示模式可以切换
  • 可以多字段显示
  • 可以设置图标
  • 标签可以编辑
  • 每列可以单独排序
  • 图片最大是256*256

提示

在批量添加数据时,为了避免界面频繁更新
listView1.BeginUpdate();
… 修改显示数据 …
listView1.EndUpdate();

//winform利用ImageList控件和ListView控件组合制作图片文件浏览器
//需要在窗体增加imageList和listview控件,并把ListView控件的LargeImageList设置为imageList1
//ListView控件显示图片的大小可以在imageList1控件中调整ImageSize属性
private void bindListView()
{
	List<string> imageLists = new List<string>();
	
	DirectoryInfo dir = new DirectoryInfo("E:\\bg\\");
	string[] files = new string[100];
	string ext = "";
	foreach (FileInfo d in dir.GetFiles())
	{
		ext = System.IO.Path.GetExtension("E:\\bg\\" + d.Name);
		if (ext == ".jpg" || ext == ".jpeg") //在此只显示Jpg
		{
			imageLists.Add("E:\\bg\\" + d.Name);
		}
	}
	for (int i = 0; i < imageLists.Count; i++)
	{
		imageList1.Images.Add(System.Drawing.Image.FromFile(imageLists[i].ToString()));
		listView1.Items.Add(System.IO.Path.GetFileName(imageLists[i].ToString()), i);
		listView1.Items[i].ImageIndex = i;
		listView1.Items[i].Name = imageLists[i].ToString();
	}
}

# 表格视图 DataGridView

几个基本的属性:

  • 列设定 [杂项] Columns
  • 列标题是否可见 [外观] ColumnHeadersVisible
  • 行标题是否可见 [外观] RowHeadersVisible
  • 允许用户添加 [行为] AllowUserToAddRows

表格的基础操作

  • 增加一行数据 grid.Rows.Add()
  • 获取所有行的数据 grid[col, row] / grid.Rows[i].Cells[j]
  • 选中行 grid.SelectedRows
  • 删除一行 grid.Rows.RemoveAt(i) 删除选中的行 grid.Rows.Remove(row)

单元格的编辑

  • 设置某列为不可编辑 grid.Columns[0].ReadOnly = true
  • 编辑后的验证 grid_CellValidating事件(e.ColumnIndex第几列)
// 根据鼠标点击的位置,判断该位置所在的单位格的索引
public static Point GetCellAt(DataGridView grid, Point location)
{
	int row = -1, col = -1;

	// 一共显示的行数: DisplayedRowCount()
	// 第一个显示的行: FirstDisplayedScrollingRowIndex
	// 某行的显示区域:  GetRowDisplayRectangle()
	for (int i= grid.FirstDisplayedScrollingRowIndex; 
		i< grid.FirstDisplayedScrollingRowIndex + grid.DisplayedRowCount(true);
		i++)
	{
		Rectangle rect = grid.GetRowDisplayRectangle(i, true);
		if(location.Y > rect.Top && location.Y < rect.Bottom)
		{
			row = i;
			break;
		}
	}

	for (int k = grid.FirstDisplayedScrollingColumnIndex; 
		k < grid.FirstDisplayedScrollingColumnIndex + grid.DisplayedColumnCount(true); 
		k++)
	{
		Rectangle rect = grid.GetColumnDisplayRectangle(k, true);
		if (location.X > rect.Left && location.X < rect.Right)
		{
			col = k;
			break;
		}
	}

	return new Point(row, col);
}

# Timer控件

Timer控件只有绑定了Tick事件,和设置Enabled=True后才会自动计时,停止计时可以用Stop()控制,通过Stop()停止之后,如果想重新计时,可以用Start()方法来启动计时器
Timer控件和它所在的Form属于同一个线程;timer1_Tick:是Timer对象的一个事件,表示在设定的时间间隔后自动触发的事件

private void Form1_Load(object sender, EventArgs e){
	timer1.Interval = 1000;//毫秒为单位 默认的
}
private void timer1_Tick(object sender, EventArgs e){
	//自动执行的代码
}

# Form窗体

  • Size:窗口大小(含标题栏和边框)、ClientSize:仅窗口客户区的大小
  • WindowState:窗体状态(放大、缩小)
    • this.WindowState = FormWindowState.Maximized,会自动调用resize方法
  • TransparencyKey:只支持透明或不透明
    • this.BackColor =Color.White; this.TransparencyKey = Color.White;
  • Opacity:窗体的透明度,Opacity制作的透明窗体盖在其他程序的窗口上,你看得到后面的窗口

# 关闭退出

this.Close() //关闭当前窗口
Application.Exit() //关闭所有窗口,退出应用程序

# 打开窗口的三种方式

  • 从一个窗口打开另一个窗口
    • 新窗口对象.show()
  • 从一个窗口打开另一个对话框
    • 新窗口对象.showDialog()
  • 在一个窗口内部打开另一个窗口(MDI父窗体)
    • 先设置父窗口属性IsMdiContainer 为True
    • new 一个新窗口对象后,新窗口对象.MdiParent = this
    • 新窗口对象.Show()

# FormClosing与FormClosed事件区别

  • FormClosing事件是在关闭窗体时发生,用户可以在该事件中 取消关闭,窗体仍然保持打开状态。因此可以在该事件中提示一些状态信息,询问用户是否关闭窗口
  • ormClosed事件 是在 关闭窗体后发生,可以在该事件中处理保存窗口的一些信息等操作,不能取消窗口关闭
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
	if (MessageBox.Show("确定要退出本系统吗?", "警告", MessageBoxButtons.OKCancel
	                                        , MessageBoxIcon.Warning) == DialogResult.OK)
	{
		this.Dispose();
		Application.Exit();
	}
	else
	{
		e.Cancel = true;   //不关闭窗口 
		//关闭窗口 e.Cancel = false;
	}
}

# Form去掉默认的控件焦点

//注意不是load是shown
private void Form1_Shown(object sender, EventArgs e)
{
  this.ActiveControl = null;
}

# 页面切换导致闪烁的解决方法

将此代码写在要解决闪烁问题的父窗体中

protected override CreateParams CreateParams
{
	get
	{
		CreateParams cp = base.CreateParams;
		cp.ExStyle |= 0x02000000;
		return cp;
	}
}

# 无法接受Keydown事件

  • 此时需要将窗体的 KeyPreview属性设置为true,将系统传入的键值先传递给窗体,再传递给控件。
  • 覆盖默认的系统键处理方式,遇到方向键,则直接返回,系统不处理,这样键值就会被传递到窗体,触发KeyDown事件。
protected override bool ProcessDialogKey(Keys keyData){
	if (keyData == Keys.Up || keyData == Keys.Down ||
		keyData == Keys.Left || keyData== Keys.Right)
			return false;
	else
	return base.ProcessDialogKey(keyData);
}

# 绝对路径的获取

//结果:D:\MyWork\MyProject\bin\Debug
string strpath=System.Windows.Forms.Application.StartupPath;
//结果: D:\MyWork\MyProject\bin\b.text
string  _path=Path.GetFullPath(Path.Combine(strpath,"./Content/music.wav"));

# 嵌入的资源和资源文件

生成操作:嵌入的资源;复制到输出目录:不复制

  • 获取“嵌入的资源”数据
//TestCustomForm为项目名称
//Res为项目下的文件夹
//btndown.bmp是文件名称。
Image.FromStream(Assembly.GetExecutingAssembly()
                         .GetManifestResourceStream(@"TestCustomForm.Res.btndown.bmp"));
  • 获取项目中“资源文件”的数据
Assembly asm = Assembly.GetExecutingAssembly();
ResourceManager rm = new ResourceManager("TestCustomForm.Properties.Resources", asm);
String str = rm.GetString("ApplicationName");    //添加到资源文件的字符串
Image img = rm.GetObject("bamboo") as Image;  //添加到资源文件的图片

//获取资源文件的图片到Panel
Properties.Resources res = new Properties.Resources();
PropertyInfo[] properInfos = res.GetType().GetProperties(BindingFlags.Static 
                                                         | BindingFlags.NonPublic 
														 | BindingFlags.Instance);
foreach (PropertyInfo item in properInfos.OrderBy(a => a.Name)) {
	if (item.Name.StartsWith("lesson_1")) {
		Panel panel = new Panel();
		panel.Margin = new Padding(0);
		panel.Width = 375;
		panel.Height = 110;
		PictureBox pic = new PictureBox();
		pic.Image = (Image)TestCustomForm.Properties
		                                 .Resources
										 .ResourceManager
		                                 .GetObject(item.Name, 
										            Properties.Resources.Culture);
		pic.Width = 265;
		pic.Height = 62;
		pic.Location = new Point(55, 24);
		panel.Controls.Add(pic);
		panelMenu.Controls.Add(panel);
	}
}
  • 获取项目图片资源并转换为Image对象
System.Drawing.Image.FromFile(@"../../Resources/a.png")

# 手动添加控件

InitializeComponent();
//在它之后添加自己的初始化代码
Button testButton = new Button();
this.Controls.Add(testButton);
testButton.Location = new Point(40, 40);
testButton.Size = new Size(100, 40);

# 手工事件处理

//在Form1.cs里,添加一个回调方法
void OnTest(object sender, EventArgs e){}
//添加事件处理
testButton.Click += new EventHandler(this.OnTest);

事件委托

delegate void EventHandler(object sender, EventArgs e)
sender : 事件发送者,即点中的控件
e : 事件的参数,比如鼠标点击的位置

# 自定义控件的使用

  • 工具|选项,Windows窗体设计器 | 常规 自动填充工具箱:设为True
  • 添加自定义Panel或Control的类
  • 生成解决方案 F7
  • 重新打开Form1.cs,在工具箱界面可以看到自己的控件

# 自定义控件的方式

# 复合控件:将标准控件组合起来

class YourControl : UserControl{}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FormApp0901
{
   public partial class SearchBox : UserControl
   {
	   public SearchBox()
	   {
		   InitializeComponent();
	   }
	   //自定义属性
	   [Browsable(true)]//是否显示在属性面板
	   [Category("Appearance")]//属性面板中显示的分类
		//让设置器自动生成代码
	   [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] 
	   public override string Text 
	   {
		   get
		   {
			   return edit.Text;
		   }
		   set
		   {
			   edit.Text = value;
		   }
	   }
	   // 添加自定义事件
	   public event EventHandler SearchEvent;
	   // 当点击按钮时,触发自定义事件
	   private void btn_Click(object sender, EventArgs e)
	   {
		   SearchEvent?.Invoke(this, e);
	   }
   }
}

在Form中使用复合控件,最简单的办法:将各个子控件设为 public ,即可以直接访问

public Form1()
{
   InitializeComponent();
   searchBox1.edit.Text = "Java";
   // 添加事件处理
   searchBox1.btn.Click += new EventHandler(this.searchBox1_SearchEvent);
}

private void searchBox1_SearchEvent(object sender, EventArgs e)
{
   MessageBox.Show("开始搜索..." + searchBox1.edit.Text);
}

# 扩展控件:继承于标准控件

class YourControl : Button {}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Af.Winform
{
   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.Location = new System.Drawing.Point(62, 31);
		   this.edit.Name = "edit";
		   this.edit.Size = new System.Drawing.Size(100, 14);
		   this.edit.TabIndex = 0;
		   // 
		   // 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(242, 76);
		   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];

		   // 父窗口参数
		   Padding p = this.Padding;
		   int x = 0, y = 0;
		   int w = this.Width, h = this.Height;
		   w -= (p.Left + p.Right);
		   x += p.Left;

		   // 计算文本框的高度,使其显示在中间
		   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 
	   {
		   get
		   {
			   return edit.Text;
		   }
		   set
		   {
			   edit.Text = value;
		   }
	   }

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

	   // Font属性的设定:自己定义一个默认值
	   private Font font = new Font("宋体", 10f);

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

	   [Browsable(true)]
	   [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
	   public override Color ForeColor
	   {
		   get
		   {
			   return edit.ForeColor;
		   }
		   set
		   {
			   edit.ForeColor = value;
		   }
	   }
	   
	   // 自定义事件: 回车
	   public event EventHandler ReturnPressed;
	   private void edit_KeyPress(object sender, KeyPressEventArgs e)
	   {
		   char ch = e.KeyChar;
		   if (ch == '\r')
		   {
			   ReturnPressed?.Invoke(this, e);
		   }
	   }
   }
}

# 自定义控件:完全地自定义一个控件

class YourControl : Control {}

# 控件布局的方式

# 可视化布局 :在设计器里拖放操作

是在窗口初始化的时候,使用代码设置了每个控件的位置和大小
当窗口改变大小时,布局并不能够自动适应

button1.Location = new Point(375, 12);
button1.Size = new Size(75, 23);

# 手工布局 :用代码计算每个控件的位置

重写 OnLayout方法,当窗口大小改变时,会自动调用这个方法重新布局

protected override void OnLayout(LayoutEventArgs levent)
{
  // 1 调用父类的OnLayout(),不是必需的
  base.OnLayout(levent);

  // 2 获取窗口大小 ClientSize (仅客户区,不含标题栏)
  int w = this.ClientSize.Width;
  int h = this.ClientSize.Height;

  // 3 计算每一个控件的位置和大小
  int yoff = 0;

  yoff = 4;
  this.textBox1.Location = new Point(0, yoff);
  this.textBox1.Size = new Size(w - 80, 30);
  this.button1.Location = new Point(w - 80, yoff);
  this.button1.Size = new Size(80, 30);

  yoff += 30; // 第一行的高度
  yoff += 4; // 间隔
  this.pictureBox1.Location = new Point(0, yoff);
  this.pictureBox1.Size = new Size(w, h - yoff - 4);
}

# 使用布局器 :用布局器自动布局

Anchor:锚定,将控件固定于某个位置
Dock:停靠,将控件停靠在一侧或中央
当设置 Dock 属性时,Anchor属性无效

# 布局器

# 自定义布局器 LayoutEngine : 负责子控件的布局

默认地,一个Form 或 Panel 都自带了一个布局器
在窗口改变大小时,由窗口的布局器来负责调整布局

自定义一个Panel,并自己实现一个LayoutEngine

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.Layout;

namespace FormApp0601
{
	// 自定义面板
	class SimpleLayoutPanel : Panel
	{
		// 布局器
		private LayoutEngine layoutEngine = new SimpleLayoutEngine();
		public override LayoutEngine LayoutEngine
		{
			get
			{                
				return layoutEngine;
			}
		}
	}

	// 自定义布局器
	class SimpleLayoutEngine :  LayoutEngine
	{
		public override bool Layout(object container,LayoutEventArgs layoutEventArgs)
		{
			// 容器
			SimpleLayoutPanel parent = (SimpleLayoutPanel)container;

			int w = parent.Width;
			int h = parent.Height;

			// 去除Padding
			int x = parent.Padding.Left;
			int y = parent.Padding.Top;
			w -= (parent.Padding.Left + parent.Padding.Right);
			h -= (parent.Padding.Top + parent.Padding.Bottom);

			int gap = 2;

			foreach (Control c in parent.Controls)
			{
				c.Location = new Point(x, y);
				c.Size = new Size(w, c.PreferredSize.Height);

				y += c.Size.Height;
				y += gap;
			}
			return false;
		}
	}
}

# FlowLayoutPanel :流式布局

子控件依次排列,一行排满之后换行继续排

# TableLayoutPanel:表格布局

以表格的形式进行布局。列的宽度:

  • 绝对值:固定大小
  • 百分比:占据剩余空间的百分比
  • 自动大小:根据所需的空间自动分配

# 线程控制

# 控件的InvokeRequired方法

C#为控件单独开辟了一个线程,当另外一个线程的方法需要修改控件或者调用控件的方法时,需要通过控件的InvokeRequired方法来进行。
比如,当另一个线程想调用控件的方法时:

//定义委托
private delegate void SendCallBack(List<byte[]> bufferList,bool feedback);

//Send方法属于某个窗口
private void Send(List<byte[]> bufferList,bool feedback) {
	if (this.InvokeRequired) { //跨线程调用时的执行逻辑                         
		try {
			SendCallBack cb = new SendCallBack(Send);
			this.Invoke(cb,bufferList,feedback);
		} catch(Exception ex) {
			MessageBox.Show(ex.Message);
		}
		
	} 
    else 
    {
      //不是跨线程调用此方法时的执行逻辑
    } 
} 
     
private void serialPort1_DataReceived(object sender,SerialDataReceivedEventArgs e) {
    Send(bufferlist,true);
} 

控件的InvokeRequired属性:bool值,为true时表示调用Send方法的是另一个线程,此时需要将Send方法传送给一个委托让委托所在的线程来代替执行Send方法;为false时表示Send的调用者没有跨线程调用Send方法,此时直接执行else中的代码即可。
串口的DataReceived事件和Send方法所属的窗口不在同一个线程,因此在serialPort1_DataReceived事件中调用Send方法时就会执行Send方法中if块中的代码。

# this.Invoke()中委托的定义

在主线程中开了一个子线程,如果要在子线程中修改主线程某个控件,会触发异常:“线程间操作无效: 从不是创建控件“button1”的线程访问它”。原来从.net framework 2.0开始,为了安全,不允许跨线程操作控件。

  • 正确的写法是需要使用Invoke,Invoke方法需要创建一个委托。如下,要修改一个Button控件的文字:
Thread testThread1=new Thread(new ThreadStart(process1));//主函数中创建一个子线程
testThread1.IsBackground = true;
testThread1.Start();

void process1()
{   //使用委托
	this.Invoke(new EventHandler(delegate
	{
		button1.Text = "测试";
	}));
}
  • 使用Lamda表达式,在.Net Framework3.5及以后的版本中使用Action封装方法,代码如下:
void process1()
{
	this.Invoke(new Action(()=>
	{
		button1.Text = "测试";
	}));
}

# this.Invoke和this.BeginInvoke的区别

  • Invoke会阻止当前主线程的运行;BeginInvoke不会阻止当前主线程的运行,而是等当前主线程做完事情之后再执行BeginInvoke中的代码内容
  • 这2个方法都是由主线程运行的,并不是异步执行,如果代码耗时过长,同样会造成界面卡死
private void button1_Click(object sender, EventArgs e)     
{
  this.textBox1.Text = "1";
    this.Invoke(new EventHandler(delegate{
	   this.textBox1.Text += "2";
    }));
    this.textBox1.Text += "3";
}  
//输出:123

private void button1_Click(object sender, EventArgs e)     
{
  this.textBox1.Text = "1";
	this.BeginInvoke(new EventHandler(delegate{
		this.textBox1.Text += "2";
	}));
    this.textBox1.Text += "3";
}
//输出:132

# 播放音频文件

微软提供了三种方式来播放音频文件

  • 通过System.Media.SoundPlayer来播放
  • 通过Com组件,添加axWindowsMediaPlayer控件播放。
  • 通过ApI函数mcisendstring播放

这三种方法都没法实现amr文件的播放,可以通过第三方插件(libvlc播放器是个不错的选择)的方式实现

# 通过System.Media.SoundPlayer

  • 首先,在Com类型库中添加Windows Media Player组件的引用
  • 添加的引用之后,就可以直接使用了
private void button2_Click(object sender, EventArgs e)
{
  string path=@"D:/test.wav";
	System.Media.SoundPlayer player = new System.Media.SoundPlayer();
	player.SoundLocation = path;
	//或者加载资源中的文件
	//player.Stream = Properties.Resources.musicbg;.
	player.LoadAsync(); //异步加载
	//player.PlayLooping(); //循环播放
	player.PlaySync(); 
}

注意

  • 只支持wav格式的文件,其余的格式文件都不支持
  • SoundPlayer非常有限,不支持暂停和恢复

# 通过Com组件,添加axWindowsMediaPlayer控件播放

  • 首先,需要在工具箱中添加Com组件,具体步骤,找到工具箱,在工具箱的空白位置,右键,选择“选择项”,点击“Com”组件,选择 Windows Media Player
  • 选择之后,在工具箱中就会显示Windows Media Player
  • 将“Windows Media Player”控件拖到Form表单中,就显示了Windows Media Player播放器,如果不想显示可以使用visible属性隐藏
private void button1_Click(object sender, EventArgs e)
{            
    axWindowsMediaPlayer1.URL = @"D:/Test.mp3";
    axWindowsMediaPlayer1.settings.autoStart = true;   
    this.axWindowsMediaPlayer1.settings.playCount = 2;//播放次数;
	if (this.axWindowsMediaPlayer1.Ctlcontrols.currentPosition > 0)
	{
	   this.axWindowsMediaPlayer1.Ctlcontrols.pause();//暂停播放文件
	}
	else {
	   this.axWindowsMediaPlayer1.Ctlcontrols.play();//播放文件
	}
}

注意

  • 这种方式支持的文件格式比较多,主要包括:mp3、wav、mp4,wmv格式
  • url参数可以直接访问服务器端的文件,本地文件时必须是绝对路径

# 通过ApI函数mcisendstring播放

//封装Play类
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
    class AudioPlay
    {
        //定义API函数使用的字符串变量 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        private string Name = "";
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        private string durLength = "";
        [MarshalAs(UnmanagedType.LPTStr, SizeConst = 128)]
        private string TemStr = "";
        int ilong;

        //定义播放状态枚举变量
        public enum State
        {
            mPlaying = 1,
            mPuase = 2,
            mStop = 3
        };

        //结构变量
        public struct structMCI
        {
            public bool bMut;
            public int iDur;
            public int iPos;
            public int iVol;
            public int iBal;
            public string iName;
            public State state;
        };

        public structMCI mc = new structMCI();

        //取得播放文件属性
        public string FileName
        {
            get
            {
                return mc.iName;
            }
            set
            {
                //ASCIIEncoding asc = new ASCIIEncoding(); 
                try
                {
                    TemStr = "";
                    TemStr = TemStr.PadLeft(127, Convert.ToChar(" "));
                    Name = Name.PadLeft(260, Convert.ToChar(" "));                   
                    mc.iName = value;
                    ilong = APIClass.GetShortPathName(mc.iName, Name, Name.Length);
                    Name = GetCurrPath(Name);
                    Name = "open " + Convert.ToChar(34) + Name 
					               + Convert.ToChar(34) + " alias media";
                    ilong = APIClass.mciSendString("close all", TemStr, TemStr.Length, 0);
                    ilong = APIClass.mciSendString(Name, TemStr, TemStr.Length, 0);
                    ilong = APIClass.mciSendString("set media time format milliseconds", 
					                                TemStr, TemStr.Length, 0);
                    mc.state = State.mStop;
                }
                catch
                {

                }
            }
        }

        //播放
        public void play()
        {
            TemStr = "";
            TemStr = TemStr.PadLeft(127, Convert.ToChar(" "));
            APIClass.mciSendString("play media", TemStr, TemStr.Length, 0);
            mc.state = State.mPlaying;
        }

        //停止
        public void StopT()
        {
            TemStr = "";
            TemStr = TemStr.PadLeft(128, Convert.ToChar(" "));
            ilong = APIClass.mciSendString("close media", TemStr, 128, 0);
            ilong = APIClass.mciSendString("close all", TemStr, 128, 0);
            mc.state = State.mStop;
        }

        public void Puase()
        {
            TemStr = "";
            TemStr = TemStr.PadLeft(128, Convert.ToChar(" "));
            ilong = APIClass.mciSendString("pause media", TemStr, TemStr.Length, 0);
            mc.state = State.mPuase;
        }

        private string GetCurrPath(string name)
        {
            if (name.Length < 1) return "";
            name = name.Trim();
            name = name.Substring(0, name.Length - 1);
            return name;
        }

        //总时间
        public int Duration
        {
            get
            {
                durLength = "";
                durLength = durLength.PadLeft(128, Convert.ToChar(" "));
                APIClass.mciSendString("status media length", durLength, 
				                                              durLength.Length, 0);
                durLength = durLength.Trim();
                if (durLength == "") return 0;
                return (int)(Convert.ToDouble(durLength) / 1000f);
            }
        }
        //当前时间
        public int CurrentPosition
        {
            get
            {
                durLength = "";
                durLength = durLength.PadLeft(128, Convert.ToChar(" "));
                APIClass.mciSendString("status media position", durLength, 
				                                                durLength.Length, 0);
                mc.iPos = (int)(Convert.ToDouble(durLength) / 1000f);
                return mc.iPos;
            }
        }
    }
    public class APIClass
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern int GetShortPathName(
          string lpszLongPath,
          string shortFile,
          int cchBuffer
        );

        [DllImport("winmm.dll", EntryPoint = "mciSendString", CharSet = CharSet.Auto)]
        public static extern int mciSendString(
          string lpstrCommand,
          string lpstrReturnString,
          int uReturnLength,
          int hwndCallback
        );
    }
}

//调用也非常简单
private void button3_Click(object sender, EventArgs e)
{
	AudioPlayer cm = new AudioPlayer ();
	cm.FileName =@"D:/mp3222.mp3";
	cm.play();
}

注意

  • 这种方式支持mp3格式,其余的格式没有测试
  • 在AudioPlayer类中,通过APIClass.GetShortPathName(mc.iName, Name, Name.Length)方法根据长路径获得短路径,但这种方法不能获得服务器的短路径,所以,目前不知道根据这种方法播放服务器端的文件。但是可以通过将服务器端的文件加载的本地,在通知这种方式来播放本地文件

# 基于VLC的视频播放

  • 安装Vlc.DotNet.Forms
  • 下载VLC播放器安装包 (opens new window) 获取组件plugin、libvlc.dll、libvlccore.dll
  • VLC类库安装成功后,即可使用封装好的VlcControl控件,在工具箱中可以看到此控件,可以直接拖拽到界面使用
  • 必须设置VLC类库目录到VlcLibDirectory,这里设置为程序所在目录下的“vlclib”文件夹
private void vlcControl1_VlcLibDirectoryNeeded(object sender, Vlc.DotNet.Forms.VlcLibDirectoryNeededEventArgs e)
{
	var currentAssembly = Assembly.GetEntryAssembly();
	var currentDirectory = new FileInfo(currentAssembly.Location).DirectoryName;
	// Default installation path of VideoLAN.LibVLC.Windows
	e.VlcLibDirectory = new DirectoryInfo(Path.Combine(currentDirectory, "libvlc", IntPtr.Size == 4 ? "win-x86" : "win-x64"));

	if (!e.VlcLibDirectory.Exists)
	{
		var folderBrowserDialog = new FolderBrowserDialog();
		folderBrowserDialog.Description = "Select Vlc libraries folder.";
		folderBrowserDialog.RootFolder = Environment.SpecialFolder.Desktop;
		folderBrowserDialog.ShowNewFolderButton = true;
		if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
		{
			e.VlcLibDirectory = new DirectoryInfo(folderBrowserDialog.SelectedPath);
		}
	}
}
  • 常用方法
// 播放本地文件
vlcControl.SetMedia(new Uri(videoFilePath));
vlcControl.Play();

//重复播放
var vlcOption = new string[] { "input-repeat=1000"};
vlcVideo.Play(fileStream, vlcOption);

// 暂停
vlcControl.Pause();
 
// 停止
vlcControl.Stop();
 
// 截图保存到指定文件
vlcControl.TakeSnapshot(imageSavePath);
 
// 设置进度,position为进度百分比,等于1时会停止播放
vlcControl.Position = position;

//vlcControl默认屏蔽了鼠标事件响应(当视频在播放时,vlcControl的鼠标事件无效),我们可以设置
vlcControl1.Video.IsMouseInputEnabled = false;

//设置全屏核心代码 去除黑边
this.vlcVideo.Video.AspectRatio = this.vlcVideo.Width + ":" + this.vlcVideo.Height;