CSharpGL(45)自制控件的思路
CSharpGL(45)自制控件的思路
本文介绍CSharpGL实现自制控件的方法。
所谓自制控件,就是用纯OpenGL模仿WinForm里的Button、Label、TextBox、CheckBox等控件,支持布局、修改大小和文字等功能。
如上图所示,左下角就是一个显示二维图片的类似PictureBox的控件,我称之为CtrlImage。(所有的CSharpGL自制控件类型,都继承自GLControl,都添加前缀Ctrl)CtrlImage上方分别是一个CtrlButton和一个CtrlLabel。
下载
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
控件是什么?
一个控件,最基本的属性包括这几条:隶属关系(Parent、Children),布局属性(Location、Size、Anchor),渲染(Initialize、Render、BackgroundColor等),事件(Click、Resize等)。
隶属关系
WinForm里的控件们构成了一个树形关系,CSharpGL也是这样。有了这样的隶属关系,就可以以相对于Parent的位置来记录自己的位置。
而且,当我做好了CtrlLabel,就可以直接放到CtrlButton.Children里,于是CtrlButton上显示文字的功能就瞬间实现了(当然还要设置一下文字位置,但工作量已经可以忽略不计了)。
using System; namespace CSharpGL
{
/// <summary>
/// Control(widget) in OpenGL window.
/// </summary>
public abstract partial class GLControl
{
internal GLControl parent;
[Description("Parent control. This node inherits parent's layout properties.")]
public GLControl Parent
{
get { return this.parent; }
set
{
GLControl old = this.parent;
if (old != value)
{
this.parent = value; if (value == null) // parent != null
{
old.Children.Remove(this);
}
else // value != null && parent == null
{
value.Children.Add(this);
}
}
}
} [Description("Children Nodes. Inherits this node's IWorldSpace properties.")]
public GLControlChildren Children { get; private set; } public GLControl(GUIAnchorStyles anchor)
{
this.Children = new GLControlChildren(this); this.Anchor = anchor;
}
}
}
布局属性
首先要有Location和Size。然后,在Parent的Size改变时,自己要相应的改变Location和Size,那么就需要Anchor来指定“是不是维持与某一边的距离不变”。
如何计算自己更新后的Location和Size?这是个简单的算法问题。
using System; namespace CSharpGL
{
public partial class GLControl
{
/// <summary>
/// 获取或设置控件绑定到的容器的边缘并确定控件如何随其父级一起调整大小。
/// </summary>
public GUIAnchorStyles Anchor { get; set; } private int x;
private int y; /// <summary>
/// 相对于Parent左下角的位置(Left Down location)
/// </summary>
public GUIPoint Location
{
get { return new GUIPoint(x, y); }
set { this.x = value.X; this.y = value.Y; }
} public GUISize Size
{
get { return new GUISize(width, height); }
set { this.width = value.Width; this.height = value.Height; }
} private int width;
private int height; /// <summary>
/// 上次更新之后,parent的Width属性值。
/// </summary>
private int parentLastWidth;
/// <summary>
/// 上次更新之后,parent的Height属性值。
/// </summary>
private int parentLastHeight; /// <summary>
///
/// </summary>
protected int absLeft;
/// <summary>
///
/// </summary>
protected int absBottom; /// <summary>
/// Layout for this control.
/// </summary>
public virtual void UpdateAbsoluteLocation()
{
GLControl parent = this.Parent;
if (parent != null)
{
this.absLeft = parent.absLeft + this.x;
this.absBottom = parent.absBottom + this.y;
}
else
{
this.absLeft = this.x;
this.absBottom = this.y;
}
} /// <summary>
/// layout controls in OpenGL canvas.(
/// Updates absolute and relative (location and size) of specified node and its children nodes.
/// <para>This coordinate system is shown as below.</para>
/// <para> /\ y</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |</para>
/// <para> |----------------->x</para>
/// <para>(0, 0)</para>
/// </summary>
/// <param name="node"></param>
public static void Layout(GLControl node)
{
if (node == null) { return; } var parent = node.Parent;
if (parent != null)
{
NonRootNodeLayout(node, parent);
} node.UpdateAbsoluteLocation(); foreach (var item in node.Children)
{
GLControl.Layout(item);
} if (parent != null)
{
node.parentLastWidth = parent.width;
node.parentLastHeight = parent.height;
}
} private const GUIAnchorStyles leftRightAnchor = (GUIAnchorStyles.Left | GUIAnchorStyles.Right);
private const GUIAnchorStyles topBottomAnchor = (GUIAnchorStyles.Top | GUIAnchorStyles.Bottom); /// <summary>
/// Updates <paramref name="currentNode"/>'s location and size according to its state and parent's information.
/// </summary>
/// <param name="currentNode"></param>
/// <param name="parent"></param>
private static void NonRootNodeLayout(GLControl currentNode, GLControl parent)
{
int x, y, width, height;
if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor)
{
width = parent.width - currentNode.parentLastWidth + currentNode.width;
if (width < ) { width = ; }
}
else
{
width = currentNode.width;
} if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor)
{
height = parent.height - currentNode.parentLastHeight + currentNode.height;
if (height < ) { height = ; }
}
else
{
height = currentNode.height;
} if ((currentNode.Anchor & leftRightAnchor) == GUIAnchorStyles.None)
{
int diff = parent.width - currentNode.parentLastWidth;
x = currentNode.x + diff / ;
}
else if ((currentNode.Anchor & leftRightAnchor) == GUIAnchorStyles.Left)
{
x = currentNode.x;
}
else if ((currentNode.Anchor & leftRightAnchor) == GUIAnchorStyles.Right)
{
int diff = parent.width - currentNode.parentLastWidth;
x = currentNode.x + diff;
}
else if ((currentNode.Anchor & leftRightAnchor) == leftRightAnchor)
{
x = currentNode.x;
}
else
{ throw new Exception(string.Format("Not expected Anchor:[{0}]!", currentNode.Anchor)); } if ((currentNode.Anchor & topBottomAnchor) == GUIAnchorStyles.None)
{
int diff = parent.height - currentNode.parentLastHeight;
y = currentNode.y + diff / ;
}
else if ((currentNode.Anchor & topBottomAnchor) == GUIAnchorStyles.Bottom)
{
y = currentNode.y;
}
else if ((currentNode.Anchor & topBottomAnchor) == GUIAnchorStyles.Top)
{
int diff = parent.height - currentNode.parentLastHeight;
y = currentNode.y + diff;
}
else if ((currentNode.Anchor & topBottomAnchor) == topBottomAnchor)
{
y = currentNode.y;
}
else
{ throw new Exception(string.Format("Not expected Anchor:[{0}]!", currentNode.Anchor)); } currentNode.x = x; currentNode.y = y;
currentNode.width = width; currentNode.height = height;
}
}}
为了降低对.net库的依赖,我根据.net自带的Size、Point、Anchor等基础的数据结构,复制了GUISize、GUIPoint、GUIAnchor……
渲染
用OpenGL渲染控件,实际上是如何在固定位置以固定大小画图的问题。
在OpenGL的渲染流水线上,描述顶点位置的坐标,依次要经过object space, world space, view/camera space, clip space, normalized device space, Screen/window space这几个状态。下表列出了各个状态的特点。
Space |
Coordinate |
feature |
object |
(x, y, z, 1) |
从模型中读取的原始位置(x,y,z),可在shader中编辑 |
world |
(x, y, z, w) |
可在shader中编辑 |
view/camera |
(x, y, z, w) |
可在shader中编辑 |
clip |
(x, y, z, w) |
vertex shader中,赋给gl_Position的值 |
normalized device |
(x, y, z, 1) |
上一步的(x, y, z, w)同时除以w。OpenGL自动完成。x, y, z的绝对值小于1时,此顶点在窗口可见范围内。即可见范围为[-1, -1, -1]到[1, 1, 1]。 |
screen/window |
glViewport(x, y, width, height); glDepthRange(near, far) |
OpenGL以窗口左下角为(0, 0)。 上一步的顶点为(-1, -1, z)时,screen上的顶点为(x, y)。 上一步的顶点为(1, 1, z)时,screen上的顶点为(x + width, y + height)。 |
根据上表来看,object space, world space, view space三步可以省略跳过,而normalized device space是无法跳过的,所以我们在shader中给控件指定的坐标,就应该在[-1,-1,-1]和[1,1,1]之间。然后通过glViewport(x, y, width, height);指定控件的位置(x, y)和大小(width, height)。
为了避免影响到控件范围外的东西,要启用GL_SCISSOR_TEST。
using System; namespace CSharpGL
{
public abstract partial class GLControl
{
public virtual void RenderGUIBeforeChildren(GUIRenderEventArgs arg)
{
GL.Instance.Enable(GL.GL_SCISSOR_TEST);
GL.Instance.Scissor(this.absLeft, this.absBottom, this.width, this.height);
GL.Instance.Viewport(this.absLeft, this.absBottom, this.width, this.height); if (this.RenderBackground)
{
vec4 color = this.BackgroundColor;
GL.Instance.ClearColor(color.x, color.y, color.z, color.w);
GL.Instance.Clear(GL.GL_COLOR_BUFFER_BIT);
}
}
}
}
事件
事件这个东西太复杂,我们来一点一点的说清楚其设计思路。
WinGLCanvas是一个WinForm控件,所有的OpenGL渲染的内容都在此显示。
当我的WinForm控件WinGLCanvas收到一个消息(以鼠标按下mouse down为例)时,他会遍历所有的GLControl,告诉他们“有mouse down消息来了”。每个控件都会调用自己关联的mouseDown事件(如果有的话)。
然而细想一下,只有鼠标所在位置的那个GLControl才应该响应mouse Down消息。所以,在WinGLCanvas遍历GLControl时,要分辨出哪个控件在mouse Down的位置,然后通知它;不通知其他控件。类似的,只有得到Focus的控件才会收到key down消息,从而调用自己的KeyDown事件。
绘制文字
做一个CtrlLabel,核心工作就是要在指定的位置绘制文字。
大致思路是这样的:
首先,做出这样的文字贴图。当要绘制的文字比较多的时候,就会出现不止一张贴图。这里为了便于演示,我故意把贴图尺寸设定得比较小,从而出现了第二张贴图;并且用金色边框把贴图的边沿描绘出来,用红色或绿色边框把各个Glyph的位置和大小都表示出来。
然后,用OpenGL创建一个GL_TEXTURE_2D_ARRAY的纹理Texture,把上面这些贴图都放进去。
最后,用一个Dictionary<char, GlyphInfo>字典记录每个字符的字形信息(在Texture中的位置、大小)。
思路就这三步,详情直接看代码比较好(GlyphMap)。
需要注意的是,之前规定了“控件的顶点范围应该在[-1,-1,-1]和[1,1,1]之间”。所以在给CtrlLabel设定好各个字形的位置后,还要按比例缩放到[-1,-1,-1]和[1,1,1]中,并且调整CtrlLabel的Width属性。这样才能正常显示文字。这其实就是WinForm里的Label的AutoSize=true。
总结
看代码看代码看代码。这里只有思路。
CSharpGL(45)自制控件的思路的更多相关文章
- MyWidget【简单自制控件】
#coding=gbk from PyQt4 import QtGui,QtCore import random class MyWidget(QtGui.QWidget): def __init__ ...
- app每个页面都有一个相同的浮层控件 实现思路
可以创建一个window,设置其windowLevel为alert;
- CSharpGL(26)在opengl中实现控件布局/渲染文字
CSharpGL(26)在opengl中实现控件布局/渲染文字 效果图 如图所示,可以将文字.坐标轴固定在窗口的一角. 下载 CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入( ...
- 微软BI SSIS 2012 ETL 控件与案例精讲课程学习方式与面试准备详解
开篇介绍 微软BI SSIS 2012 ETL 控件与案例精讲 (http://www.hellobi.com/course/21) 课程从2014年9月开始准备,到2014年12月在 天善BI学院 ...
- UWP学习记录4-设计和UI之控件和模式1
UWP学习记录4-设计和UI之控件和模式1 1.控件和事件简介 在 UWP 应用开发中,控件是一种显示内容或支持交互的 UI 元素. 控件是用户界面的构建基块. 我们提供了超过 45 种控件供你使用, ...
- 自定义angularjs分页控件
继昨天写了knockoutjs+ jquery pagination+asp.net web Api 实现无刷新列表页 ,正好最近刚学习angularjs ,故琢磨着写一个angularjs版本的分页 ...
- 天津政府应急系统之GIS一张图(arcgis api for flex)讲解(四)地图导航控件模块
config.xml文件的配置如下: <widget left="10" top="50" config="widgets/Navigation ...
- C# 自定义FileUpload控件
摘要:ASP.NET自带的FileUpload控件会随着浏览器的不同,显示的样式也会发生改变,很不美观,为了提高用户体验度,所以我们会去自定义FileUpload控件 实现思路:用两个Button和T ...
- Android 在布局容器中动态添加控件
这里,通过一个小demo,就可以掌握在布局容器中动态添加控件,以动态添加Button控件为例,添加其他控件同样道理. 1.addView 添加控件到布局容器 2.removeView 在布局容器中删掉 ...
随机推荐
- Markdown使用简单示例
标题示例: 标题一 #标题一 标题二 #标题二 标题三 ###标题三 标题四 ####标题四 标题五 #####标题五 标题六 ######标题六 连接示例 [](跳转 ...
- Bootstrap——一款超好用的前端框架
前 言 Bootstrap Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的,用于开发响应式布局.移动设备优先的 WEB 项目.Bootstrap在JQuery的基础上进 ...
- org.springframework.core.io包内的源码分析
前些日子看<深入理解javaweb开发>时,看到第一章java的io流,发觉自己对io流真的不是很熟悉.然后看了下JDK1.7中io包的一点点代码,又看了org.springframewo ...
- notepad的快捷操作-代码速写
(一)先安装zencode插件 (二)运用插件 第一步:键入:html:xt 再Ctrl+Alt+Enter键 得到 <!DOCTYPE html PUBLIC "-//W3C//DT ...
- Spring学习—生成图片验证码
今天想学下一下验证码的生成,就之前搭建好的一个spring框架上写了一个demo,我会贴出细节代码,但是spring的配置就不在介绍了.需要完整代码可以联系我! 会从前台页面到后台实现完整的讲解: 1 ...
- 第4章 同步控制 Synchronization ----信号量(Semaphore)
许多文件中都会提到 semaphores(信号量),因为在电脑科学中它是最具历史的同步机制.它可以让你陷入理论的泥淖之中,教授们则喜欢问你一些有关于信号量的疑难杂 症.你可能不容易找到一些关于 sem ...
- Rundeck部署和基本使用
rundeck 介绍 Rundeck 是一款能在数据中心或云环境中的日常业务中使程序自动化的开源软件.Rundeck 提供了大量功能,可以减轻耗时繁重的体力劳动.团队可以相互协作,分享如何过程自动化, ...
- 修改host可以上的一些网站
打开路径 C:\Windows\System32\drivers\etc 博客园 223.6.248.220 www.cnblogs.com github 192.30.253.112 github. ...
- Javascript 面向对象编程—继承和封装
前 言 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类) ...
- 关闭eclipse自动弹出console的功能
当启动项目后,console有值时就会弹出,挺烦人的,可以如下修改