Boero ListView: 第一部分






4.80/5 (3投票s)
一个扩展的属主绘制 ListView,

背景
偶尔,论坛上有人会问到在ListView
背景中渲染视频、DirectX 或 OpenGL 的问题。大多数情况下,他们会被断然告知“不可能”,除非用户愿意使用覆盖层。覆盖层在 Vista Aero 中悄然消失了。这就是视频播放器 VLC 和 BSPlayer 的墙纸模式被禁用,以及我曾经在一些 Quadro 卡上使用的、用于渲染背景的炫酷 ListView
被禁用。
此控件实现了 ListView
应有的功能——完全通过 OpenGL 进行硬件渲染。源代码中包含了一个 BoeroLV 和一个完全相同的标准 System.Windows.Forms.ListView
的对比示例。查看示例源代码,了解其中一些可用的可能性。
Using the Code
本文档假设您已完全熟悉 OpenGL 和 Tao Framework OpenGL 包装器,可在 taoframework.com 找到。该控件应该能直接替换大多数标准的、未分组的大图标 ListView
。像 BackgroundColor
、Font
等常用属性都已涵盖。像 Tile
、Detail
、SmallIcon
视图和分组等一些选项目前尚不支持。任何有时间或有兴趣实现这些功能的人都非常欢迎。我之所以留下这些,将在本文第二部分中揭示原因。
一旦用它替换了标准的 ListView
,RenderBackground
事件就可以用来在当前渲染上下文中执行几乎任何 OpenGL 调用,前提是避免使用某些保留的显示列表和纹理。保留的纹理和显示列表在代码中已标记,并且如果需要使用特定范围,则可以完全自定义。另请注意 maxItems
常量。为了在渲染时降低 CPU 开销,项目是使用一个简陋的场景图进行渲染和调整的。说我老古董也好,我没有使用顶点缓冲区,但是显示列表能够很好地管理这些并保持简单。这也意味着,如果 OpenGL 的下一个主要版本计划中将移除显示列表,那么此控件可能会遇到麻烦。
Visual Studio 窗体设计器
设计器可以处理此控件*……* 在我的 XP 和 Vista 系统上(但在我的 Mono 体验中不行),我从未能从同一个进程中成功地重新实例化相同的渲染上下文两次。即使在捆绑的 Tao 示例中,如果您尝试运行同一个示例两次,应用程序也会在没有任何错误或解释的情况下消失。同样,如果您关闭设计器窗口并重新打开它,Visual Studio 会在没有任何解释、异常或保存的情况下关闭!在找到补丁之前,请自行承担风险。
示例中演示了一个非常简单的旋转茶壶背景。请注意,所有渲染调用都来自线程安全的 System.Windows.Forms.Timer
,这样渲染代码就不会与事件或跨线程的混乱情况并行运行。这使得渲染您想要的内容变得简单,而无需担心有人更改 Color
或 Font
属性,并且对其他进程也非常友好,将 CPU 活动保持在最低水平。
RenderBackground
private void Teapot_Background(long frame)
{
// Call projection matrix.
Gl.glCallList(100);
Gl.glPushMatrix();
Gl.glTranslatef(40, -20, -80);
Gl.glRotated(frame / 15.0, 1, 0, 0);
Gl.glRotated(frame / 12.0, 0, 1, 0);
Gl.glRotated(frame / 7.0, 0, 0, 1);
// Teapot list pops the model view matrix.
Gl.glCallList(101);
}
等等,现在。显示列表 100 和 101 是在哪里构建的?在渲染上下文变为当前之前不可能构建这些列表,并且在窗体显示之前也不可能使渲染上下文变为当前。最初,这里有一个检查,在第一次遍历时构建列表。“如果 100 不是一个有效的列表,则构建它。”一旦我的列表构建完成,每次帧渲染都会进行大量不必要的分支。除了 RenderBackground
之外,还需要第二个事件。这个事件就是 AfterInitRC
。
AfterInitRC
在示例中,我使用了 glutWireTeapot
。这意味着我必须初始化 FreeGlut
,但只需要执行一次——在渲染上下文建立后的一次。在 AfterInitRC
中,立即调用 glutInit()
。既然我们在这里,就为其他一些示例构建一些其他的列表。为什么不提供一个显示我天气预报的滚动字幕,或者一个模拟时钟的选项呢?在此事件中加载或创建所有纹理和列表,以避免动画卡顿。请注意,可能会希望使用多线程并拥有一个具有列表共享的第二个上下文。这超出了本文档的范围,但请继续关注第二部分。目前,此控件旨在基本且美观,同时不会耗尽 CPU 或使您的液冷 Halfquake Tournament 显卡过热。
/// John Boero 17-AUG-2008 <summary>
/// Use the AfterInitRC event to create some basic display lists used in our backgrounds.
/// </summary>
private void boeroLV_AfterInitRC()
{
// Optional: Do this if we draw glut teapots, etc.
Glut.glutInit();
ProjectionSetup();
// Initialize teapot list.
Gl.glNewList(101, Gl.GL_COMPILE);
Gl.glEnable(Gl.GL_LINE_SMOOTH);
Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_NICEST);
Gl.glLineWidth((float)Height / 100);
Gl.glColor4f(1, 1f, 0, 0.1f);
Gl.glEnable(Gl.GL_BLEND);
Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE);
Glut.glutWireTeapot(40);
Gl.glLineWidth((float)Width / 100);
Gl.glColor4f(1, 0, 1, 0.2f);
Glut.glutWireTeapot(40);
Gl.glPopMatrix();
Gl.glEndList();
Control
类提供的一个最好的函数是 DrawToBitmap
。严格来说,您甚至不需要创建文件。天才!考虑到这一点,我们可以获取任何控件并将其直接绘制到 GL_TEXTURE_RECTANGLE_ARB
中。这使得设置天气预报非常方便。
// Initialize weather feed.
try
{
int weatherTexture = 0x200001;
XmlDocument weatherMilwaukee = new XmlDocument();
// Surely everyone wants to know Milwaukee weather instead of their own, right?
weatherMilwaukee.Load(HttpWebRequest.Create
(http://www.google.com/ig/api?weather=milwaukee).
GetResponse().GetResponseStream());
foreach (XmlNode weather in weatherMilwaukee.GetElementsByTagName("forecast_conditions"))
{
foreach (XmlElement condition in weather)
{
switch (condition.Name)
{
....
case "icon":
weaControl.picConditions.Image =
Image.FromStream(
HttpWebRequest.Create( "http://google.com"
+ condition.GetAttribute("data")).GetResponse().GetResponseStream());
break;
// Ignore humidity wind_condition
}
}
Bitmap bm = new Bitmap(weaControl.Width, weaControl.Height);
weaControl.BackColor = boeroLV.BackColor;
weaControl.DrawToBitmap(bm, weaControl.Bounds);
BoeroView.ImageToTexture(weatherTexture++, bm);
}
.....
}
ListView
中还缺少其他几个属性。
HighlightColor
使用此属性来自定义标准 SystemColors.Highlight
的着色和标签颜色。请注意,可以轻松地按项目自定义此颜色,但代码留给读者自行解决。您甚至可以忘记着色,并在每个选定项目后面绘制古怪的背景圆角正方形,就像 Vista 一样……如果您喜欢这种东西的话……
请注意,为文本着色很棘手。图标图像很简单,因为它们带有 alpha 通道。图标下方的文本是通过一个隐藏的 System.Windows.Forms.Label
创建的,但 Control.DrawToBitmap
不允许 alpha 通道。相反,标签会在黑色背景上绘制白色文本,并使用自定义的片段着色器来组合前景和背景颜色。默认的纹理模式 GL_MODULATE
不足以实现这一点,因为它没有多个活动颜色用于前景和背景。取而代之的是创建了以下简单的 GLSL 片段着色器,它们似乎效果很好:
图标着色器
uniform sampler2DRect sampler0;
uniform vec4 fc;
void main(void)
{
vec4 texture = texture2DRect(sampler0, gl_TexCoord[0].st);
gl_FragColor = mix(gl_TextureEnvColor[0], mix(fc, texture, 0.5), texture.a);
}
文本(标签)着色器
uniform sampler2DRect sampler0;
uniform vec4 bc, fc;
void main(void)
{
vec4 texture = texture2DRect(sampler0, gl_TexCoord[0].st);
gl_FragColor = mix(bc, fc, texture.r);
}
关于 ClearType 平滑的一点说明。尽管 Joel Splosky 有什么看法,但我喜欢 ClearType 平滑。它通过偏移不同的颜色通道,使文本边缘看起来更加平滑,这是非常巧妙的。不幸的是,在这个着色器中它会丢失。如果不放大屏幕截图很难看清,但 ClearType 平滑不会被标签纹理的灰度处理所捕捉。
HighlightAlpha
在当今的现代 UI 中,这会产生很大的影响。半透明一切!
MaxFrameRate
此值反比于用于刷新背景的计时器。将此值设置为 90 并不保证 90 FPS。它保证进程将尝试每 1/90 秒渲染一帧,但不包括 GPU 渲染所需的时间,甚至不包括以更高优先级运行的其他进程所需的时间。示例默认设置为 50。在我的 C2D/GF8600M GS 上,它通常以 33fps 运行,CPU 使用率通常为 0%。请注意,如果我拔掉笔记本电脑的电源,我的 GPU 会进入节能模式,那么 CPU 负担会增加到大约 15%,因为我的 GPU 和 CPU 需要更长的时间来渲染。
如果您对代码中的慢动画或高 CPU 利用率感到沮丧,可以尝试使用 VBO 或显示列表进行优化。或者找到一种方法来减少您为顶点创建的大网格。
关注点或常见问题解答
问:为什么没有分组?
答:与第二部分无关。
问:为什么 XP 中的窗口会留下无效区域?
答:无效区域事件的规避方式就像 Keanu Reeves 躲避 paparazzi 一样。Vista 是未来的 Windows,其复合窗口层不需要这些事件。要么实现三缓冲,要么不要将窗口移到 ListView
前面。
问:为什么滚动时会出现伪影?
答:我从未能消除这些。
问:当水平滚动条可见时,为什么图标会偏移?
答:这是留给读者的另一个谜题。
问:关于 MONO 呢?
答:Tao 完全兼容 Mono。我在 Sabayon 中尝试过,但效果有限。核心代码可以工作,但目前我无法在现有的 Form 上获取渲染上下文。我尝试过 SDL,它似乎只能创建一个单独的窗口。
问:关于 DirectX 呢?
答:有可能。如果 视频纹理 bug 能够修复,那将是一个不错的选择。目前,我更倾向于为 Mac/Linux 社区考虑潜在的 Mono 兼容性。
问:是否可以使用此技术将其他标准控件 OpenGL 化?
答:可能可以,如果您遵循构造函数并正确初始化渲染上下文。我个人认为 3D GridView 没有意义,除非有人愿意为此投入巨款。随心所欲吧。
问:我的立体 ListView
呢?
答:%) 试过了。让我花了三天时间眼花缭乱。后果自负……
问:Shell 编程 - 有使用此技术的文件浏览器吗?
答:请继续关注第二部分。
历史
该控件已经过几次修改
- 从每次刷新时重新构建所有显示列表和纹理
- 到中断刷新并使
WNDPROC
消息无效,仅在必要时进行绘制 - 到无 if 分支的定时器驱动刷新委托
- 到完全优化的场景图,仅更改需要更改的部分
它终于达到了我满意的优化水平。
抱歉
Tao 框架已在我的 GAC 中注册,但我没有将其包含在原始示例源代码下载中。Zip 文件已更新,可以在没有安装 Tao 的机器上运行。