添加多个窗口(deviceform、cameraform、displayform、toolform等)
项目要求:
打开的各个功能模块像工作栏移动;
选择打开指定相机,输出采集数据;
输出数据可以传给指定工具。
项目环境:
C#、winform、DockPanelSuite
项目实现:
窗口初始化
NuGet 包管理器 :安装包
添加多个窗口(deviceform、cameraform、displayform、toolform等)
使用DockPanelSuite,子窗口必须继承:WeifenLuo.WinFormsUI.Docking.DockContent
public partial class Tool : WeifenLuo.WinFormsUI.Docking.DockContent
为主窗口选择theme
dockPanel1.Theme = vS2015LightTheme1;
向主窗口添加子窗口,同时设置子窗口的停靠方式
DeviceList DeviceForm = new DeviceList();
CameraList CameraForm = new CameraList();
DispalyPicture DisplayForm = new DispalyPicture();
private void Form3_Load(object sender, EventArgs e)
{
DeviceForm.Text = "cameratree";
DeviceForm.Show(dockPanel1);
DeviceForm.DockTo(dockPanel1, DockStyle.Left);
CameraForm.Text = "opentree";
CameraForm.Show(dockPanel1);
CameraForm.DockTo(dockPanel1, DockStyle.Right);
DisplayForm.Text = "display";
DisplayForm.Show(dockPanel1);
DisplayForm.DockTo(dockPanel1, DockStyle.Fill);
}
DeviceForm设计
DeviceForm指实现枚举相机treeview窗口,其中treeview命名为cameratree,节点命名规则为根节点为相机类型,根节点下子节点为该相机类型枚举到的相机,编号为相机索引。
添加节点:
注:枚举相机在主窗口实现
public void add_cameranode(string cameratype, uint count)
{
TreeNode[] targetnode = cameratree.Nodes.Find(cameratype, false);
if (targetnode.Length > 0)
{
for (int i = 0; i < count; i++)
{
TreeNode cameranode = new TreeNode((cameraname[cameratype] + " " + i.ToString()), i + 1, i + 1);
cameranode.Name = i.ToString();
cameranode.Tag = cameratype;
targetnode[0].Nodes.Add(cameranode);
}
}
else
{
MessageBox.Show("nofind cameratree");
}
}
删除节点记录:
将指定节点从已打开相机节点记录中删除
public void remove_opennode(TreeNode selectnode)
{
opennodes.RemoveAll(node => node.Tag == selectnode.Tag && node.Name == selectnode.Name);
}
拖拽节点:鼠标点击事件
private void cameratree_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
TreeNode selectedNode = cameratree.GetNodeAt(e.X, e.Y);
if (selectedNode != null && !cameraname.ContainsKey(selectedNode.Name) && !opennodes.Contains(selectedNode))
{
if (opennodes.Count == 4)
{
MessageBox.Show("out of camera_max");
return;
}
cameratree.SelectedNode = selectedNode;
DragDropEffects i = cameratree.DoDragDrop(selectedNode, DragDropEffects.Move);
if (i == DragDropEffects.Move && selectedNode.Tag.ToString() != "tool")
{
opennodes.Add(selectedNode);
}
}
}
}
CameraForm设计
CameraForm指打开相机的treeview窗口,即接收拖拽节点的treeview,其中treeview命名为opentree,节点命名规则同cameratree,区别是opentree根节点下的子节点为打开的相机。
接收传入节点
传入节点有两种:第一种从cameratree拖拽的相机节点,拖拽成功实现打开对应相机,并在其节点下添加子节点,子节点对应该相机的数据。第二种为第一种生成的数据节点,将其拖拽到待使用的工具上,使相机的数据传递给对应工具,同时连接的两个节点之间用线条连接。
第二种效果如下:
判断传入节点的方式是:判断该节点位于所属二叉树的深度
部分代码如下:(注:部分代码在主窗口,这里不予显示)
private void opentree_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(TreeNode)))
{
e.Effect = DragDropEffects.Move;
}
}
private void opentree_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(TreeNode)))
{
TreeNode draggedNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
if (draggedNode.Level == 2)
{
Point targetPoint = opentree.PointToClient(new Point(e.X, e.Y));
TreeNode targetNode = opentree.GetNodeAt(targetPoint);
TreeNode[] toolnode = opentree.Nodes.Find("tool", false);
if (targetNode != null && toolnode.Length > 0 && toolnode[0].Nodes.Contains(targetNode))
{
starnodelist.Add(draggedNode);
endnodelist.Add(targetNode);
paint(draggedNode, targetNode);
}
}
else
{
string cameratype = draggedNode.Tag.ToString(); //英文
string cameratype2 = cameraname[cameratype]; //中文
TreeNode[] targetNode = opentree.Nodes.Find(cameratype, false);
if (targetNode.Length < 1)
{
TreeNode camera = new TreeNode(cameratype2, draggedNode.ImageIndex, draggedNode.SelectedImageIndex);
camera.Name = cameratype;
opentree.Nodes.Add(camera);
}
targetNode = opentree.Nodes.Find(cameratype, false);
TreeNode cameranode = (TreeNode)draggedNode.Clone();
targetNode[0].Nodes.Add(cameranode);
opentree.SelectedNode = cameranode;
}
}
}
选择数据节点
private void opentree_ItemDrag(object sender, ItemDragEventArgs e)
{
TreeNode selectnode = (TreeNode)e.Item;
if (selectnode.Level == 2 && !starnodelist.Contains(selectnode))
this.DoDragDrop(e.Item, DragDropEffects.Move);
}
graphic
拖动相机节点传递数据给工具,相机与工具之间用线条显示连接;双击线段可以删除线段。
1.根据给出的起始和结束节点绘制线条
private void paint(TreeNode starnode, TreeNode endnode)
{
try
{
if (!starnode.IsVisible)
{
starnode = starnode.Parent;
paint(starnode, endnode);
return;
}
if (!endnode.IsVisible)
{
endnode = endnode.Parent;
}
Point pt1 = starnode.Bounds.Location;
pt1.Offset(starnode.Bounds.Width, starnode.Bounds.Height / 2);
Point pt2 = endnode.Bounds.Location;
pt2.Offset(endnode.Bounds.Width, endnode.Bounds.Height / 2);
Point pt12 = pt1;
int width = graphicsPaths.Count * 10 + 20;
pt12.Offset(width, 0);
Point pt21 = pt12;
pt21.Y = pt2.Y;
Point[] pointlist = { pt1, pt12, pt21, pt2 };
graphicsPaths.Add(new GraphicsPath());
graphicsPaths[graphicsPaths.Count - 1].AddLines(pointlist);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
}
2.删除线段
#region 删除线条
//根据传入节点,删除起始节点是传入节点的线段
public void deleteline(TreeNode starnode)
{
if (starnode == null)
return;
int index = starnodelist.IndexOf(starnode);
if (index > -1)
{
graphicsPaths.RemoveAt(index);
starnodelist.RemoveAt(index);
endnodelist.RemoveAt(index);
}
else
{
MessageBox.Show("select line again!");
}
}
//根据鼠标点击确定的线段索引,删除线段
public bool deleteline()
{
if (selectindex != -1)
{
graphicsPaths[selectindex].Dispose();
graphicsPaths.RemoveAt(selectindex);
starnodelist.RemoveAt(selectindex);
endnodelist.RemoveAt(selectindex);
selectindex = -1;
return true;
}
else
{
MessageBox.Show("select line again!");
return false;
}
}
//根据鼠标双击位置确认线段
private void opentree_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Point point = new Point(e.X, e.Y);
foreach (GraphicsPath pt in graphicsPaths)
{
bool isvisible = false;
isvisible = pt.IsOutlineVisible(point, pen);
if (isvisible)
{
Pen selectpen = new Pen(Color.Red, 2);
AdjustableArrowCap aac = new AdjustableArrowCap(3, 4);
selectpen.CustomEndCap = aac;
graphics.DrawPath(selectpen, pt);
selectindex = graphicsPaths.IndexOf(pt);
return;
}
}
}
}
#endregion
3.opentree展开和关闭节点,更新graphic
#region 刷新treeview,重新绘图
//更新函数
public void reflash()
{
opentree.Refresh();
graphicsPaths = new List<GraphicsPath>();
for (int i = 0; i < starnodelist.Count; i++)
{
paint(starnodelist[i], endnodelist[i]);
}
foreach (GraphicsPath path in graphicsPaths)
{
graphics.DrawPath(pen, path);
}
}
//鼠标停留一段时间,自动更新
private void opentree_NodeMouseHover(object sender, TreeNodeMouseHoverEventArgs e)
{
reflash();
}
//关闭节点更新
private void opentree_AfterCollapse(object sender, TreeViewEventArgs e)
{
reflash();
}
//展开节点更新
private void opentree_AfterExpand(object sender, TreeViewEventArgs e)
{
reflash();
}
//定时器更新
private void timer1_Tick(object sender, EventArgs e)
{
reflash();
}
#endregion
DisplayForm设计
List<PictureBox> camerapicture = new List<PictureBox>();
#region 显示模块
private void onecamera()
{
//布局对象
TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
//设置布局的列数与行数(1列1行)
tableLayoutPanel.ColumnCount = 1;
tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); //设置百分比
tableLayoutPanel.RowCount = 1;
tableLayoutPanel.RowStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); //设置百分比
//添加控件
tableLayoutPanel.Controls.Add(camerapicture[0]);
tableLayoutPanel.Dock = DockStyle.Fill;
tableLayoutPanel.AutoSize = true;
//this.Invoke(new AddControlDelegate(AddControl), new object[] { this.panel3, tableLayoutPanel });
panel1.Controls.Add(tableLayoutPanel);
}
private void twocamera()
{
//布局对象
TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
//设置布局的行数
tableLayoutPanel.RowCount = 3; //分成上下两部分
tableLayoutPanel.RowStyles.Add(new ColumnStyle(SizeType.Percent, 20F)); //设置百分比
tableLayoutPanel.RowStyles.Add(new ColumnStyle(SizeType.Percent, 60F));
tableLayoutPanel.RowStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
//创建布局器对象
TableLayoutPanel tableLayUP = new TableLayoutPanel() { Dock = DockStyle.Fill };
//左右等分(1行2列)
tableLayUP.ColumnCount = 2;
tableLayUP.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); //设置百分比
tableLayUP.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
//添加相机
tableLayUP.Controls.Add(camerapicture[0], 0, 0); //行列坐标
tableLayUP.Controls.Add(camerapicture[1], 1, 0);
//将上半部分添加到布局器中
tableLayUP.AutoSize = true;
tableLayoutPanel.Controls.Add(tableLayUP, 0, 1);
//添加控件
tableLayoutPanel.AutoSize = true;
tableLayoutPanel.Dock = DockStyle.Fill;
panel1.Controls.Add(tableLayoutPanel);
}
private void threecamera()
{
//布局对象
TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
//设置布局的行数
tableLayoutPanel.RowCount = 2; //分成上下两部分
tableLayoutPanel.RowStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); //设置百分比
tableLayoutPanel.RowStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
//创建布局器对象
TableLayoutPanel tableLayUP = new TableLayoutPanel() { Dock = DockStyle.Fill };
//左右等分(1行2列)
tableLayUP.ColumnCount = 2;
tableLayUP.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); //设置百分比
tableLayUP.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
//添加相机
tableLayUP.Controls.Add(camerapicture[0], 0, 0); //行列坐标
tableLayUP.Controls.Add(camerapicture[1], 1, 0);
//将上半部分添加到布局器中
tableLayoutPanel.Controls.Add(tableLayUP, 0, 0);
//创建布局器对象
TableLayoutPanel tableLayDown = new TableLayoutPanel() { Dock = DockStyle.Fill };
//左中右等分(1行3列)只用中间一列
tableLayDown.ColumnCount = 3;
tableLayDown.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F)); //设置百分比
tableLayDown.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 60F));
tableLayDown.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20F));
//添加相机
tableLayDown.Controls.Add(camerapicture[2], 1, 0); //行列坐标
//将下半部分添加到布局器中
tableLayoutPanel.Controls.Add(tableLayDown, 0, 1);
//添加控件
tableLayoutPanel.Dock = DockStyle.Fill;
panel1.Controls.Add(tableLayoutPanel);
}
private void fourcamera()
{
//布局对象
TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
//设置布局的行数与列数
tableLayoutPanel.ColumnCount = 2;
tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); //设置百分比
tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel.RowCount = 2;
tableLayoutPanel.RowStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); //设置百分比
tableLayoutPanel.RowStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
//添加lab标签
tableLayoutPanel.Controls.Add(camerapicture[0], 0, 0); //行列坐标
tableLayoutPanel.Controls.Add(camerapicture[1], 1, 0);
tableLayoutPanel.Controls.Add(camerapicture[2], 0, 1);
tableLayoutPanel.Controls.Add(camerapicture[3], 1, 1);
//添加控件
tableLayoutPanel.Dock = DockStyle.Fill;
panel1.Controls.Add(tableLayoutPanel);
}
public void imageshow(List<PictureBox> camera)
{
camerapicture = camera;
panel1.Controls.Clear();
foreach (PictureBox i in camerapicture)
{
i.Dock = DockStyle.Fill;
i.SizeMode = PictureBoxSizeMode.StretchImage;
}
switch (camerapicture.Count)
{
case 1: onecamera(); break;
case 2: twocamera(); break;
case 3: threecamera(); break;
case 4: fourcamera(); break;
default: break;
}
}
#endregion
ToolForm设计
略
主窗口设计
参数定义
hikvision my_hik = new hikvision();
do3think my_do3 = new do3think();
mindvision my_mind = new mindvision();ImageList ImageList = new ImageList();
List<PictureBox> camerapicture = new List<PictureBox>();
List<TreeNode> opennodelist = new List<TreeNode>();DeviceList DeviceForm = new DeviceList();
CameraList CameraForm = new CameraList();
DispalyPicture DisplayForm = new DispalyPicture();
System.Windows.Forms.TreeView T_cameratree;
System.Windows.Forms.TreeView T_opentree;Dictionary<string, string> cameraname = new Dictionary<string, string>()
{{"do3think","度申"},
{"hikvision","海康威视"},
{"mindvision","迈德威视"},
{"tool","工具" }
};
添加工作栏
工作栏部分代码
#region 工具栏
private void deviceFormToolStripMenuItem_Click(object sender, EventArgs e)
{
DeviceForm.Show(dockPanel1, DockState.DockLeftAutoHide);
}
private void cameraFormToolStripMenuItem_Click(object sender, EventArgs e)
{
CameraForm.Show(dockPanel1, DockState.Float);
}
private void displayFormToolStripMenuItem_Click(object sender, EventArgs e)
{
DisplayForm.Show(dockPanel1, DockState.Float);
}
#endregion
查找相机
/// <summary>
/// 查找可连接设备
/// </summary>
public void camerascan()
{
uint h_count, d_count;
int m_count;
my_hik.CameraScan(out h_count);
my_do3.CameraScan(out d_count);
my_mind.CameraScan(out m_count);
if (h_count > 0)
{
DeviceForm.add_cameranode("hikvision", h_count);
}
else
{
MessageBox.Show("nofind hikvision camera");
}
if (d_count > 0)
{
DeviceForm.add_cameranode("do3think", d_count);
}
else
{
MessageBox.Show("nofind do3think camera");
}
if (m_count > 0)
{
DeviceForm.add_cameranode("mindvision", (uint)m_count);
}
else
{
MessageBox.Show("nofind mindvision camera");
}
}
打开指定相机
/// <summary>
/// 打开指定相机
/// </summary>
public void cameraopen()
{
TreeNode opennode = T_opentree.SelectedNode;
bool openflag = false;
string cameratype = opennode.Tag.ToString();
int selectdevice = int.Parse(T_cameratree.SelectedNode.Name);
camerapicture.Add(new PictureBox());
camerapicture[camerapicture.Count - 1].Name = opennode.Tag.ToString() + " " + opennode.Name;
switch (cameratype)
{
case "do3think":
{
openflag = my_do3.CameraOpen((uint)selectdevice, camerapicture[camerapicture.Count - 1].Handle);
}
break;
case "hikvision":
{
openflag = my_hik.CameraOpen(selectdevice, camerapicture[camerapicture.Count - 1].Handle);
}
break;
case "mindvision":
{
openflag = my_mind.CameraOpen(selectdevice, camerapicture[camerapicture.Count - 1].Handle);
}
break;
default: break;
}
if (openflag == false)
{
DeviceForm.remove_opennode(opennode);
T_cameratree.SelectedNode.Text += " (disconnect)";
T_opentree.SelectedNode.Remove();
camerapicture.RemoveAt(camerapicture.Count - 1);
MessageBox.Show("fail open!");
}
else
{
opennodelist.Add(opennode);
add_datanode();
}
DisplayForm.imageshow(camerapicture);
}
添加相机功能菜单
#region 相机功能菜单
private void opentree_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
TreeNode selectedNode = T_opentree.GetNodeAt(e.X, e.Y);
if (selectedNode != null && selectedNode.Level == 1 && selectedNode.Tag.ToString() != "tool")
{
T_opentree.SelectedNode = selectedNode;
contextMenuStrip1.Show(T_opentree, e.Location);
}
}
}
private void SaveImageToolStripMenuItem_Click(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "bmp file(*.bmp)|*.bmp|jpeg file(*.jpeg)|*.jpeg|png file(*.png)|*.png|tif file(*.tif)|*.tif|gif file(*.gif)|*.gif|raw Files(*.dat)|*.dat";
if (sfd.ShowDialog() == DialogResult.OK)
{
TreeNode selectnode = T_opentree.SelectedNode;
TreeNodeCollection childNodes = selectnode.Parent.Nodes;
int nodeindex = childNodes.IndexOf(selectnode);
string filepath = sfd.FileName;
switch (selectnode.Parent.Name)
{
case "do3think": my_do3.savefunc(filepath, nodeindex); break;
case "hikvision": my_hik.savefunc(filepath, nodeindex); break;
case "mindvision": my_mind.savefunc(filepath, nodeindex); break;
default: break;
}
}
}
private void CloseCameraToolStripMenuItem_Click(object sender, EventArgs e)
{
TreeNode selectnode = T_opentree.SelectedNode;
TreeNodeCollection childNodes = selectnode.Parent.Nodes;
int nodeindex = childNodes.IndexOf(selectnode);
switch (selectnode.Tag.ToString())
{
case "do3think": my_do3.CameraClose(nodeindex); break;
case "hikvision": my_hik.CameraClose(nodeindex); break;
case "mindvision": my_mind.CameraClose(nodeindex); break;
default: break;
}
int index = opennodelist.IndexOf(selectnode);
camerapicture[index].Dispose();
camerapicture.RemoveAt(index);
opennodelist.Remove(selectnode);
DeviceForm.remove_opennode(selectnode);
List<TreeNode> starnodes = CameraForm.get_starnodelist();
if (selectnode.FirstNode != null)
{
if (starnodes.Contains(selectnode.FirstNode))
{
CameraForm.deleteline(selectnode.FirstNode);
}
}
selectnode.Remove();
DisplayForm.imageshow(camerapicture);
CameraForm.reflash();
updata_tool();
}
private void 关闭工具ToolStripMenuItem_Click(object sender, EventArgs e)
{
List<TreeNode> endnodes = CameraForm.get_endnodelist();
TreeNode toolnode = T_opentree.SelectedNode;
if (toolnode.Parent.Text == "工具" || toolnode.Parent.Text == "tool")
{
if (endnodes.Count > 0 && endnodes.Contains(toolnode))
{
return;
}
Tool toolform = (Tool)toolnode.Tag;
toolform.Close();
toolform.Dispose();
toolnode.Remove();
}
}
#endregion
成果展示
以海康虚拟相机为例。开始运行程序,在初始化过程中查找相机。
拖动节点到opentree,即打开相机。
关闭displayform
点击工作栏“displayform”,重新打开displayform。在关闭期间,窗口没有真正关闭,而是将窗口隐藏。
同样,重新打开的窗口也可以随意拖动位置。
打开多个相机
功能栏切换语言
向工具传递图像数据,其中可以多个相机数据传递给一个工具。
删除线段,即断开数据传输。
相机功能菜单:保存图像、关闭相机、关闭工具。关闭相机后,连接自动断开,但没有断开连接,工具不能关闭。
关闭全部相机和工具。