Windows 7 / VS2010 演示应用程序






4.81/5 (89投票s)
使用 Windows7 / VS2010 和一些新功能,如任务栏/跳转列表以及拖放
- 整体解决方案 zip:MefFX Solution.zip
- 演示代码库:Lib.zip
- MEFExports 项目 zip:MEFExports.zip
- MefFX 项目 zip:MefFX.zip
- PixelShaders 项目 zip:PixelShaders.zip
说明:由于 zip 文件的大小限制,我不得不将每个项目单独打包。因此请仔细阅读以下说明
- 在您的桌面创建一个名为 MefFX 的新文件夹,并将 MefFX Solution.zip 文件解压到其中。
- 将 Lib.zip、MEFExports.zip、MefFX.zip 和 PixelShaders.zip(它们分别是 Lib 解决方案文件夹和三个项目文件)解压到您解压 MefFX Solution.zip 的位置。
- 如果缺少引用(可能存在,因为我在压缩前清理了应用程序),您可以在 Lib.zip 文件中找到它们
对此感到抱歉,我只是想尽快将其上传,所以我不得不拆分文件。希望您能理解。
目录
引言
我最近将我的家用电脑和笔记本电脑从 Vista 升级到 Windows7,我不得不说我很高兴我这样做了。我不仅清理了机器上不需要的大量垃圾,而且摆脱了 Vista,太棒了。
所以既然我现在有了 Windows7,我就一直在玩弄它,可以说是了解了一下情况,看看在我的应用程序中可以使用什么,比如 TaskBar
/JumpLists。
碰巧我的另一个最喜欢的消遣 www.codeproject.com 也似乎希望人们撰写关于 Windows7 / VS2010 技术的文章,所以我想我会尝试一下,写一篇小文章,展示一些 Windows7 的功能,并展示 VS2010 如何原生支持这些功能。我还想写一篇使用一些我以前没有尝试过的内容的文章,所以我决定编写一个执行以下操作的应用程序:
- 专为 Windows7 和 VS2010 编写,因此两者都是必需的(对此感到抱歉)
- 展示如何使用新的 Windows 7
TaskBar
和 Jumplist - 展示如何以 Windows7 方式进行拖放
- 使用 .NET 4.0 中的新 Dynamic 类型
- 使用 .NET 4.0 中的新 Expando 类型
- 使用托管可扩展性框架 (MEF),我认为它很快就会成为主流 .NET 运行时的一部分。
- 使用像素着色器,这些着色器从一个项目导出并通过 MEF 导入到主项目。
- 使用真正的 WPF WebBrowser,它原生支持复杂变换,并可以托管在 3D 网格上,如果您曾经尝试使用标准 .NET 3.5 SP1 WebBrowser 控件,您就知道这是不可能的。
先决条件
正如我之前所说,本文是为 Windows 7 编写并使用 VS2010,所以您**需要**这两者才能运行代码。
它做什么?
我想引言已经说清楚了大部分,但我会再回顾一遍,以免对本文的作用/不作用产生混淆。简而言之,本文的代码是使用 **托管可扩展性框架 (MEF)** 搜索位于特定路径(本例中是主应用程序的 obj\debug 文件夹)的多个 DLL,查找所有标记为 MEF 可导出的部件。然后,这些部件作为可行的 MEF 导入包含到主应用程序中。MEF 导入部件中的代码可以被主界面(MEF 导入部件的消费者)调用。
MEF 导入部件非常简单,它们只是封装了一个特定的像素着色器效果,并提供了关于着色器的一些额外元数据,例如是谁制作的,从哪里下载的,其原始 URL 等等。尽管元数据是使用 .NET 4.0 的新 dynamic
和 ExpandoObject
类型公开的。
然后,这些导入的像素着色器用于在主界面中创建一些 ViewModel,这些 ViewModel 然后用于绑定到 ItemControl
(s),以呈现 MEF 导入的插件。用户可以单击 ItemControl
(s) 中的项目,然后会显示一个 3D 面板,展示 ItemsControl
中项目试图表示的像素着色器。用户还可以翻转 3D 面板,以显示有关他们选择的像素着色器的更多信息。
当然,我还决定展示一些 Windows7 / VS2010 的新功能,因此您可以使用一些新的 Windows 7 shell 拖放代码来选择 MEF 导入的像素着色器使用的图片,我还展示了如何将应用程序与 Windows7 TaskBar
集成,以及如何使用 Windows7 TaskBar
与自己的应用程序交互。此外,还有一些 Windows7 跳转列表功能,以备不时之需。
我应该指出,我首选的 UI 技术是 WPF,所以演示代码使用了 WPF,但这里介绍的技术也可以用于 Winforms。
它长什么样?
实际上,我认为它看起来非常棒,相当简单,它一开始是这样的,它找到了所有 MEF 导入的像素着色器,但还不知道该怎么做,因为它正在等待您提供图像。
所以你现在可以提供一张图片,通过将你最喜欢的图片之一拖到应用程序中,它将看起来如下:
所以一旦你选择了一张图片,你可以使用底部或左侧的导航方法来查看更多关于像素着色器的信息,两者都是完全可滚动的区域。
你可以从左侧的 ItemsControl 或使用底部的 ItemsControl
打开一个应用了像素着色器的图片,底部 ItemsControl
实际上是 Paul Tallett 出色的 FishEyePanel 的一个实例,你可以在他原始的 www.codeproject.com 文章链接 Panels.aspx 中找到。
下面的例子展示了当我双击 FishEyePanel 中受像素着色器影响的图像项之一时,演示应用程序的样子。
从这里你还可以将这个弹出窗口在 3D 空间中翻转,这与我之前的一篇文章《我的朋友们》中做的事情类似,在那篇文章中,我有两个用户控件托管在 3D 网格上,可以在 3D 空间中翻转。第一次写这个非常费劲,幸运的是,Josh Smith 对此进行了改造,并将其封装在一个漂亮的 WPF 内容控件中,该控件是他 Thriple 3D WPF 库 的一部分。干得好,史密斯特工。
您可以在这里看到它的实际效果,我正在将用户选择的像素着色器项目翻转一半。我在这里实际做的是显示一个 Web 浏览器(请注意,这不是 Winforms 浏览器,也不是同样糟糕的 .NET 3.5 SP1 WebBrowser 控件,两者都很糟糕,在我看来远非一流的 WPF 公民),它能够托管并在 3D 网格上通过 3D 空间进行翻转。
这是 WebBrowser 控件,显示了用户选定的像素着色器项目的详细信息。
另一个感兴趣的区域是 Windows7 TaskBar
区域,它看起来像这样:
从上图中可以看到,有两个自定义的 Windows7 TaskBar
按钮,您很快就会了解更多。我不得不说,有一点让我有点失望的是,用于按钮的图像质量极差,这很遗憾,因为原始图像的质量实际上非常好,没有出现这种糟糕的渲染。
我喜欢 TaskBar
的地方是,我可以直接在我的 ViewModel 中触发 ICommand
,这真的很棒。我稍后会再次向您展示这一点,但现在让您知道我没有说谎,这是当我单击演示应用程序的 TaskBar
条目中的信息按钮时显示的内容。
这是一个“关于”窗口,它是直接在我的 ViewModel 中运行 ICommand
的结果。
另一个有趣的特点是,如果您右键单击附加演示应用程序中的 TaskBar
区域,您会看到一些 JumpList 条目。我们实际上可以向 Windows7 JumpList 添加自定义程序启动,我稍后会再次介绍这一点。
我想这样就结束了它的外观,所以我想您现在想知道它是如何工作的,这对我来说没问题,我们现在就可以研究一下。
工作原理
在接下来的几个小节中,我将向您介绍演示应用程序的所有组成部分是如何工作的。在我们继续之前,让我声明一下,您接下来将阅读的一些部分可能依赖于一个或两个已作为 Microsoft.WindowsAPICodePack 发布 的 Microsoft Dll。 Microsoft.WindowsAPICodePack 实际上只是一个针对 Windows7 的托管代码 Dll,它为您很好地处理了所有 P/Invoke 相关内容。它包含诸如 shell 操作/缩略图/拖放/跳转列表以及新的 Windows7 任务栏等功能。
我将明确指出我在哪里使用了 Microsoft.WindowsAPICodePack,以及在哪里没有使用,因为有些功能可以使用 .NET 4.0 代码非常容易地完成,而无需使用 Microsoft.WindowsAPICodePack。
像素着色器
我不是说我非常了解像素着色器,但维基百科是这样说的:
像素着色器是计算每个 像素 的 颜色 和其他属性的计算核心函数。像素着色器可以从始终输出相同颜色,到应用光照值,再到进行 凹凸贴图、阴影、镜面高光、半透明和其他现象。它们可以改变像素的深度(用于 Z-缓冲),或者在激活多个渲染目标时输出多种颜色。单独的像素着色器无法产生非常复杂的效果,因为它只对单个像素进行操作,不了解场景的几何形状或相邻像素。
那么这对 .NET 有何影响?微软所做的是允许用户使用一种高级语言,称为高级着色器语言 (HLSL) 来创建自己的像素着色器。
高级着色器语言 (High Level Shader Language 或 HLSL) 是 微软 为与其 Direct3D API 配合使用而开发的一种专有 着色语言。它类似于与 OpenGL 标准一起使用的 GLSL 着色语言。它与 NVIDIA Cg 着色语言非常相似,因为它与 Cg 同时开发。[1]
HLSL 程序有三种形式:顶点 着色器、几何着色器和像素(或片段)着色器。顶点着色器针对应用程序提交的每个顶点执行,主要负责将顶点从对象空间变换到视图空间,生成纹理坐标,并计算光照系数,例如顶点的切线、副法线和法线向量。当一组顶点(通常是 3 个,形成一个三角形)通过顶点着色器时,它们的输出位置被插值以在其区域内形成像素;这个过程称为 光栅化。这些像素中的每一个都通过像素着色器,从而计算出最终的屏幕颜色。
可选地,使用 Direct3D10 接口和 Direct3D10 硬件的应用程序也可以指定几何着色器。这个着色器以三角形的三个顶点作为输入,并使用这些数据生成(或 曲面细分)额外的三角形,然后每个三角形都被发送到光栅化器。
这如何转换为 .NET 代码是这样的:
我们有一个 FX 文件(HLSL 部分)
sampler2D input : register(s0);
float someInput : register(c0);
float4 main(float2 uv : TEXCOORD) : COLOR {
float4 color;
uv.x = uv.x + cos((uv.x-someInput)*50)*0.02;
uv.y = uv.y + sin((uv.y-someInput)*50)*0.02;
color = tex2D(input, uv.xy);
// uncomment the line below to invert the red color
//color.r = -color.r;
return color;
}
然后我们必须使用 fxc 编译器,它知道如何处理这个文件,我们可以这样做:
:: use /Gec option for compatibility mode (required by some .fx files)
echo ******* Compiling Pixel Shaders *******
"%DXSDK_DIR%\Utilities\bin\x86\fxc" /T ps_2_0 /E main /Fo
"%1\MyEffect1\MyEffect1.ps" "%1\MyEffect1\MyEffect1.fx"
echo ******* Compiling Pixel Shaders Completed *******
然后我们可以用托管的 C# 或 VB .NET 包装原始像素着色器,这可能看起来像这样:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Effects;
using System.Windows.Media;
using System.Windows;
namespace PixelShaders {
public class MyEffect1 : ShaderEffect
{
/// <summary>
/// Pixel shader that this effect is using
/// </summary>
private static PixelShader _shader = new PixelShader()
{
UriSource =
Utilities.GetResourcePackUri("MyEffect1/MyEffect1.ps")
};
public MyEffect1()
{
PixelShader = _shader;
// remember to call UpdateShaderValue() for all shader input
// arguments here
UpdateShaderValue(InputProperty);
UpdateShaderValue(SomeInputProperty);
}
/// <summary>
/// The default shader input - the visual on which the shader operates
/// Note: you can add more texture inputs in a similar way as Input is added
/// </summary>
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
/// <summary>
/// The WPF dependency property that backs up the shader input,
/// assigned to sampler register s0
/// </summary>
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty("Input",
typeof(MyEffect1), 0);
/// <summary>
/// Some other input property that our shader will use
/// </summary>
public double SomeInput
{
get { return (float)GetValue(SomeInputProperty); }
set { SetValue(SomeInputProperty, value); }
}
/// <summary>
/// Using Dependency property to store SomeInput in order to enable binding,
/// animation and other
/// use PixelShaderConstantCallback() to assing to a shader register -
//// in this case it's register c0
/// </summary>
public static readonly DependencyProperty SomeInputProperty =
DependencyProperty.Register("SomeInput", typeof(double), typeof(MyEffect1),
new UIPropertyMetadata(0.0, PixelShaderConstantCallback(0)));
}
}
有一个很好的 Visual Studio 模板示例,用于像素着色器开发,网址是 http://www.nokola.com/sources/ShaderEffectTemplate.zip,还有一个很棒的资源在 http://wpffx.codeplex.com,它还包含一个自定义的 VS MSBuild 任务,以简化创建自定义像素着色器的开发。
我还应该指出 Walt Ritscher 的工作以及他惊人的 Shazzam 工具 http://blog.wpfwonderland.com/2008/10/08/shazzam-wpf-pixel-shader-effect-testing-tool-now-available/
所以一旦你创建了你的第一个像素着色器,你就可以在你的 XAML 中自由使用它们,如下所示:
<Image>
<Image.Effect>
<DropShadowEffect/>
</Image.Effect>
</Image>
WPF 实际上只带了几个预构建的 Effect
,例如 DropShadowEffect
。这些 Effect
不要与旧的 BitmapEffect
混淆,后者不是硬件加速的。
通过 MEF 导出/导入
编写可扩展应用程序非常流行,现在很多应用程序都支持插件。对于那些了解情况的人来说,这最终归结为实现一个特定的接口,比如一些假设的 IAddIn
接口,人们可能会使用反射来搜索特定目录中所有程序集中的所有 Type
,如果找到实现该 IAddIn
接口的 Type
,则将其作为可用的插件导入到我们的应用程序中。这很容易。
还有一些正式的插件框架,其中一个随 .NET 3.5 SP1 发布,它使用了 System.AddIn
命名空间,我之前在我的文章 https://codeproject.org.cn/KB/dotnet/AddInModel.aspx 中写过。使用 System.AddIn
命名空间并非易事,至少需要 7 个项目,如下图所示。
在我撰写关于使用 System.AddIn
命名空间的文章时,我不禁认为实现我前面所述的功能所需的底层架构有点太多了。我们只是寻找一个实现特定接口的 Type
,然后将其作为可用插件导入到我们的应用程序中。
幸运的是,帮助就在眼前,你可以使用 托管可扩展性框架 (MEF),它使这个过程几乎像我上面介绍的句子一样简单。当我第一次看到 MEF 时,我以为我可能不会喜欢它,因为我在看了托管插件框架 (MAF,使用 System.AddIn
命名空间) 之后仍然心存芥蒂,但我不得不说,我非常高兴 MEF 的使用是如此简单。它真的很容易。
我们马上就会讲到如何操作,我只想花一点时间解释一下我如何在演示应用程序中使用 MEF,然后再向您展示所有这些的代码。
这个应用程序的基本思想是,我想研究一些我以前没有研究过的领域,而且我喜欢闪亮漂亮的东西,所以我立刻想到了我们刚才讲过的像素着色器。那么这个演示应用程序是如何工作的,它又做了什么呢?
简单来说,这个演示应用程序从另外两个项目 **PixelShaders** 和 **MEFExports** 导入像素着色器,并且在这两个程序集中找到的任何像素着色器都使用 MEF 导入到主应用程序 **MefFX** 中。对于找到的每个 IPixelShader
(MEF 部件),都会在主应用程序 **MefFX** 中创建一个新的 ShaderViewModel
,然后用于驱动主应用程序的 UI。我们有点超前了,但本质上接下来发生的是,对于每个 ShaderViewModel
,都会显示一个图像,该图像应用了 ShaderViewModel
中的 Effect
(该 Effect
又来自 MEF 导入部件)。ShaderViewModel
还包含一些动态数据,我们稍后会介绍,这些数据是为每个 IPixelShader
MEF 导入部件导入的数据的一部分。
我想,当你看到一些代码时,这会更有意义。
在 MEF 中声明可导出部件
要声明您希望某个内容可导出(即在其他代码中使用),您只需设计一个契约接口,并对此接口有一个具体的实现,然后使用 MEF ExportAttribute 标记该具体实现。这如下所示。
契约接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Effects;
namespace MEFExports
{
public interface IPixelShader
{
Effect ActualEffect { get; }
dynamic ShaderDetails { get; }
}
}
这是实现契约接口的具体类。正如我之前所述,目标是公开像素着色器效果以及关于着色器的一些附加数据,作为可 MEF 导出的内容。下面的代码就是这样做的:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//MEF
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Windows.Media.Effects;
using System.Dynamic;
//PixelShaders
using PixelShaders;
//have a look at http://wpffx.codeplex.com/
using ShaderEffectLibrary;
namespace MEFExports
{
[Export(typeof(IPixelShader))]
public class ColorKeyAlphaEffectPixelShader : IPixelShader
{
public Effect ActualEffect
{
get { return new ColorKeyAlphaEffect(); }
}
public dynamic ShaderDetails
{
get
{
dynamic shaderDetails = new ExpandoObject();
shaderDetails.ShaderName = "ColorKeyAlphaEffect";
shaderDetails.Description = "Color key alpha effect";
shaderDetails.Attributes = new ExpandoObject();
shaderDetails.Attributes.Author = "Microsoft";
shaderDetails.Attributes.Url = "http://wpffx.codeplex.com/";
return shaderDetails;
}
}
}
}
在 MEF 中声明可导入部分
MEF 拼图的下一块是将可导出的 MEF 部分导入到其他代码中。这也很简单,我们所做的只是像这样标记一些属性,我们可以使用 MEF ImportManyAttribute
,其中我们表示要导入多个部分。这些将是实现原始 IPixelShader
契约并用 MEF ExportAttribute
标记的所有单独代码部分。
[ImportMany(RequiredCreationPolicy = CreationPolicy.Shared)]
public IEnumerable<IPixelShader> Shaders { get; set; }
可以看出,我们期望导入一些项目(每个像素着色器效果一个),所以我们可以使用 MEF ImportManyAttribute
。
在附加的演示应用程序中实际发生的是,这些 MEF 部件用于创建一个 MainWindowViewModel
,它是 MainWindow
的 DataContext
。
public MainWindow()
{
App.ComposeMEFContainer(this);
mainWindowViewModel = new MainWindowViewModel(Shaders);
this.DataContext = mainWindowViewModel;
InitializeComponent();
}
全部连接起来
上面的导入代码之所以有效,要归功于演示代码中 App 类上的一个静态方法,其作用是配置 MEF 容器。现在让我们来看看:
public static void ComposeMEFContainer(Object part)
{
var directory = System.IO.Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location);
var container = new CompositionContainer(new DirectoryCatalog(directory));
var batch = new CompositionBatch();
batch.AddPart(part);
container.Compose(batch);
}
就是这样。MEF 的优点在于它真的很容易使用。
.NET 4.0 中的动态/ExpandoObject 类型
ExpandoObject
表示一个其成员可以在运行时动态添加和删除的对象。这与 .NET 4.0 动态 API 一起出现,允许我们构建复杂的对象结构,而在此之前我们必须在编译时创建具体的类。
使用 MSDN 的一两个例子:
第一个例子展示了你如何轻松构建复杂的对象结构:
class Program
{
static void Main(string[] args)
{
dynamic employee, manager;
employee = new ExpandoObject();
employee.Name = "John Smith";
employee.Age = 33;
manager = new ExpandoObject();
manager.Name = "Allison Brown";
manager.Age = 42;
manager.TeamSize = 10;
WritePerson(manager);
WritePerson(employee);
}
private static void WritePerson(dynamic person)
{
Console.WriteLine("{0} is {1} years old.",
person.Name, person.Age);
// The following statement causes an exception
// if you pass the employee object.
// Console.WriteLine("Manages {0} people", person.TeamSize);
}
}
// This code example produces the following output:
// John Smith is 33 years old.
// Allison Brown is 42 years old.
这个例子展示了如何轻松地向 dynamic
对象添加新方法:
sampleObject.number = 10;
sampleObject.Increment = (Action)(() => { sampleObject.number++; });
// Before calling the Increment method.
Console.WriteLine(sampleObject.number);
sampleObject.Increment();
// After calling the Increment method.
Console.WriteLine(sampleObject.number);
// This code example produces the following output:
// 10
// 11
附加的演示应用程序简单地使用 ExpandoObject
来表示关于像素着色器的信息,例如作者是谁、它的名称、在哪里可以获得更多信息。正如我们之前看到的,MEF 用于解析 MainWindow 中的 public IEnumerable<IPixelShader> Shaders { get; set; }
属性,如果我们检查 MEF 导入的内容,我们可以看到以下 dynamic/ExpandoObject
的用法。
public dynamic ShaderDetails
{
get
{
dynamic shaderDetails = new ExpandoObject();
shaderDetails.ShaderName = "ColorToneEffect";
shaderDetails.Description = "Color tone effect";
shaderDetails.Attributes = new ExpandoObject();
shaderDetails.Attributes.Author = "Microsoft";
shaderDetails.Attributes.Url = "http://wpffx.codeplex.com/";
return shaderDetails;
}
}
起初您可能会认为这不适合 WPF 使用。但 WPF 团队已经使我们能够非常轻松地绑定到动态对象,因为 ExpandoObject
已经实现了绑定所需的一切。那就是最出色的 INotifyPropertyChanged
接口。
所以,我们要做的,绑定到一个 Expando 如下,很简单,对吧。
<Image.ToolTip>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="Shader Name" Style="{StaticResource boldLabel}"/>
<Label Content="{Binding Path=ShaderDetails.ShaderName}"
Style="{StaticResource normalLabel}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Shader Description" Style="{StaticResource boldLabel}"/>
<Label Content="{Binding Path=ShaderDetails.Description}"
Style="{StaticResource normalLabel}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Shader Author" Style="{StaticResource boldLabel}"/>
<Label Content="{Binding Path=ShaderDetails.Attributes.Author}"
Style="{StaticResource normalLabel}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Shader Url" Style="{StaticResource boldLabel}"/>
<Label Content="{Binding Path=ShaderDetails.Attributes.Url}"
Style="{StaticResource normalLabel}"/>
</StackPanel>
</StackPanel>
</Image.ToolTip>
Windows7 方式的拖放
现在我们大多数人可能都使用 DragEffects 和 DataObject 等在 .NET 中完成了拖放操作。嗯,看起来在使用 Microsoft.WindowsAPICodePack 时,事情与平时有所不同。例如,这是过去可能做过的标准拖放代码,用于允许将图像拖到 PictureBox(这是 Winforms 代码,而附加应用程序是 WPF,但原理相同):
public Form1()
{
InitializeComponent();
Image img = Image.FromFile(@"C:\pics\1.jpg");
this.btnImage.Image = img;
this.picBox.AllowDrop = true;
this.btnImage.MouseDown += this. btnImage_MouseDown;
this.picBox.DragDrop += this.pictureBox_DragDrop;
this.picBox.DragEnter += this.pictureBox_DragEnter;
}
private void btnImage_MouseDown(object sender, MouseEventArgs e)
{
Button btnPic = (Button)sender;
btnPic.DoDragDrop(btnPic.Image, DragDropEffects.Copy);
}
private void pictureBox_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Bitmap))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}
private void pictureBox_DragDrop(object sender, DragEventArgs e)
{
PictureBox picbox = (PictureBox)sender;
Graphics g = picbox.CreateGraphics();
g.DrawImage((Image)e.Data.GetData(DataFormats.Bitmap), new Point(0, 0));
}
这一切都很好,我非常喜欢这种拖放方式。
该演示应用程序没有使用标准的 .NET 拖放功能,而是我选择了使用 Microsoft.WindowsAPICodePack 来查看如何完成此类操作,所以让我们来看看如何使用 Microsoft.WindowsAPICodePack 来完成这类事情。
以下代码展示了如何允许用户将单个图像从其文件系统拖放到具有 AllowDrop="true"
的 WPF 控件上。
所以我在 XAML 中就只有这样:
<Grid AllowDrop="True" Drop="GridDrop".../>
然后使用 Microsoft.WindowsAPICodePack,我通过以下代码支持在此 Grid 上拖放:
private void GridDrop(object sender, DragEventArgs e)
{
if (!inDragDrop)
{
string[] formats = e.Data.GetFormats();
foreach (string format in formats)
{
// Shell items are passed using the "Shell IDList Array" format.
if (format == "Shell IDList Array")
{
ShellObject obj = ShellObjectCollection.FromDataObject(e.Data).First();
txtImage.Visibility = Visibility.Collapsed;
imgDrop.Source = obj.Thumbnail.BitmapSource;
e.Handled = true;
mainWindowViewModel.HasImages = true;
return;
}
}
}
e.Handled = false;
}
好的,所以这与 Winforms 代码并没有太大区别,需要注意的是 ShellObjectCollection
,它是 Microsoft.WindowsAPICodePack 中的一个类,它具有各种属性,可用于确定拖动对象的 shell 信息的各种值。
Windows7 任务栏
Windows7 TaskBar
有点特别,因为它可以使用 Microsoft.WindowsAPICodePack 完成,但是 .NET 4.0 中对 Windows7 TaskBar
有原生支持,使用了新的 System.Windows.Shell 命名空间。我不是个十足的白痴,所以当有内置机制可以做某事时,我每次都会选择它,而不是使用第三方 DLL,即使第三方 DLL 是微软的。
所以,我将向您展示如何使用原生 .NET 4.0 编码方法创建 TaskBar
并与之交互。但我应该声明,我热爱 WPF,所以将使用 WPF 向您展示。如果您使用 WinForms,同样的规则也适用,但您显然没有 XAML。
这很简单,一切都按如下方式进行:
我们只需在 XAML 中声明一个 TaskBar
如下:
<Window x:Class="MefFX.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MefFX" Height="600" Width="800">
<Window.TaskbarItemInfo>
<TaskbarItemInfo
ProgressState="Normal"
Description="Opens the browser for a particular PixelShader inside MefFX"
ThumbnailClipMargin="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=BorderThickness}">
<TaskbarItemInfo.ThumbButtonInfos>
<ThumbButtonInfo
Click="SearchForPixelShadersInside_Click"
DismissWhenClicked="False"
ImageSource="Images/ie.png" />
<ThumbButtonInfo Command="{Binding AboutCommand}"
DismissWhenClicked="False"
ImageSource="Images/about.png" />
</TaskbarItemInfo.ThumbButtonInfos>
</TaskbarItemInfo>
</Window.TaskbarItemInfo>
....
....
....
....
</Window>
请注意这两个 ThumbButtonInfo
对象,其中一个使用 Click 事件,“SearchForPixelShadersInside_Click”,它会触发一些代码隐藏代码,另一个使用 Command
(Command="{Binding AboutCommand}"
)绑定到 ViewModel ICommand
,它允许您在与 TaskBar 交互时在 ViewModel 中触发代码。
这是 Click 事件的代码隐藏,它只是显示 3D 面板,并将其翻转以显示 WebBrowser 控件,其中在 3D 面板内显示了上次查看的像素着色器的网址。
private void SearchForPixelShadersInside_Click(object sender, EventArgs e)
{
if (thriplePanel.IsFrontInView)
{
ContentControl3D.RotateCommand.Execute(null, btnBack);
}
thriplePanel.DataContext = lastSelectedShaderVm;
thriplePanel.Visibility = Visibility.Visible;
}
为了完整起见,这里是附加演示应用程序中 MainWindowViewModel
内部的第二个 ThumbButtonInfo
调用的 ICommand
。
/// <summary>
/// Logic to determine if AboutCommand can execute
/// </summary>
private Boolean CanExecuteAboutCommand
{
get
{
return true;
}
}
/// <summary>
/// Executes the AboutCommand
/// </summary>
private void ExecuteAboutCommand()
{
uiVisualizerService.ShowDialog("AboutWindow", null);
}
这个 ViewModel 代码使用我的 Cinch MVVM 框架 来使用 IUIVisualizerService
显示自定义对话框,您可以在 Cinch MVVM 框架 网站上阅读更多相关信息。
Windows7 跳转列表及其交互
与之前一样,JumpList 可以使用 Microsoft.WindowsAPICodePack 完成,也可以使用新的 .NET 4.0 System.Windows.Shell 命名空间完成。
我选择了使用 System.Windows.Shell 命名空间。因此,要创建 JumpList
,我们只需要做以下事情。
JumpList jumpList = new JumpList();
JumpList.SetJumpList(Application.Current, jumpList);
JumpTask jumpTask = new JumpTask();
jumpTask.Title = "IE";
jumpTask.CustomCategory = "Keep Notes";
jumpTask.ApplicationPath = @"C:\Program Files\Internet Explorer\iexplore.exe";
String systemFolder = Environment.GetFolderPath(Environment.SpecialFolder.System);
jumpTask.IconResourcePath = @"C:\Program Files\Internet Explorer\iexplore.exe";
jumpTask.IconResourceIndex = 0;
jumpTask.Arguments = "pixel shaders";
jumpList.JumpItems.Add(jumpTask);
jumpList.Apply();
所以当你运行演示应用程序,并在其 TaskBar
图标上右键单击时,你会看到当前的 JumpList
,其中包含代码中所示的“**Keep Notes**”类别的新自定义条目。还将有一个 IE 链接,点击后会显示 IE 已传递了命令行参数“pixel shaders”。
基本上,您可以将 JumpList
视为运行外部进程,而 TaskBar 和 ThumbButtonInfo
则能够与当前应用程序进行交互。
真正的 WPF 浏览器
正如我之前所说,我目前对 WPF 的任何可用 WebBrowser
控件都不是最大的粉丝。我不喜欢它们的原因是,在我看来,它们不够 WPF 化,并且是使用基于 HWnd 的控件渲染,而不是 WPF 的 DirectX 渲染引擎。因此,它们总是矩形的,总是绘制在顶部,并且不能用于 LayoutTransform
或 3D,这显然是不酷的。
幸运的是,一些聪明的家伙也不喜欢这样,并开发了一个很酷的 C++ DLL 叫做 Awesomium,你可以在这里下载:
http://princeofcode.com/awesomium.php#download
然后最棒的是 Chris Cavanagh(物理学天才)将其封装,使其具有 WPF 的特性。他称之为:
WPF 3D Chromium 浏览器
您可以在 Chris Cavanagh 的网站上找到它,网址是:
http://chriscavanagh.wordpress.com/2009/08/27/wpf-3d-chromium-browser/
所以这个代码使用了 Chris Cavanagh 的 Chromium 浏览器,它支持 WPF 中的 3D 功能。所以我所要做的就是:
- 引用 Cjc.AwesomiumWrapper.dll
- 引用 Cjc.ChromiumBrowser.dll
- 包含 Awesomium.dll(原始 C++ 包装器(非 WPF)),并确保将其复制到输出文件夹
- 然后我在代码中使用浏览器
我希望每次单击新的像素着色器项时都交换浏览器的 URL,所以我每次单击新项时都会创建一个 ChromiumBrowser
,这根本不是最有效的代码,但我认为这更多地与它在 Josh Smith 的 Thriple 3D WPF 库 中的托管方式有关,而不是 ChromiumBrowser
本身。对我来说,使用单个 ChromiumBrowser
并仅导航到新 URL 效果很好。哎呀,我们现在就是这样,所以我所做的事情并不是最有效的,但为了这个演示应用程序,它确实有效,并且向您展示了有一个 WebBrowser 可以在 3D 中工作并支持 WPF LayoutTransform
,如下所示:
这是实现此功能的代码:
private void CreateBrowser()
{
if(thripleBackGrid.Children.Count == 3)
{
var oldBrowser =thripleBackGrid.Children[2];
if (oldBrowser.GetType().Equals(typeof(Cjc.ChromiumBrowser.WebBrowser)))
thripleBackGrid.Children.RemoveAt(2);
}
Cjc.ChromiumBrowser.WebBrowser newBrowser = new Cjc.ChromiumBrowser.WebBrowser();
newBrowser.SetValue(Grid.RowProperty,1);
newBrowser.Margin = new Thickness(5);
newBrowser.EnableAsyncRendering = true;
newBrowser.Width = 460;
newBrowser.Height = 390;
newBrowser.LayoutTransform = new ScaleTransform(0.5, 0.5, 0.5, 0.5);
newBrowser.Ready += new EventHandler(newBrowser_Ready);
thripleBackGrid.Children.Add(newBrowser);
}
private void newBrowser_Ready(object sender, EventArgs e)
{
String url = lastSelectedShaderVm.ShaderDetails.Attributes.Url;
try
{
WebClient fileReader = new WebClient();
using (Stream data = fileReader.OpenRead(url))
{
String webText = new StreamReader(data).ReadToEnd();
(sender as Cjc.ChromiumBrowser.WebBrowser).LoadHtml(webText);
}
}
catch (WebException ex)
{
Console.WriteLine("Error accessing site " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Error accessing site " + ex.Message);
}
}
我确实在使用 VS2010/.NET 4.0 时遇到了一个问题,原来 ChromiumBrowser
是使用 V2 兼容版本的 .NET 构建的,所以我不得不在 App.Config 中考虑到这一点,您可以在下面看到我使用了 useLegacyV2RuntimeActivationPolicy
属性。
<?xml version="1.0"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
就是这样,希望您喜欢
好了,就是这样,希望您喜欢。即使您觉得现在用不上,当您开始使用 Windows7 / VS2010 时,仍然有一些零碎的东西可以派上用场。
谢谢。
一如既往,欢迎投票/评论。