Baboon Framework:一个革命性的 WPF 框架,可为您节省时间和精力






4.88/5 (110投票s)
这是真正的代码,您说什么
目录
引言
正如你们中的一些人可能知道的,我和我的几个老搭档都在使用 WPF。我个人使用它已经很多年了,也见证了许多文章/讨论和框架/CLR 的改进。尽管如此,我一直觉得在现实世界的框架方面,缺少一些东西,能够让任何水平的用户快速上手,用最少的麻烦快速创建现实世界的应用程序。
我与两位有学识的同事讨论了这个问题,我们一起提出了一个我们认为非常棒的框架。我们利用业余时间为您进行的这项广泛工作的成果是:一个完全可测试的 WPF 框架,它将大大减少您在新 WPF 项目中所需花费的时间。在某些情况下,我们观察到时间与复杂度的减少高达 1000%。
在这个时间就是金钱的世界里,我们相信这会对您有意义。如果这次介绍引起了您的兴趣,请继续阅读,了解我们在我们代号为“Baboon framework
”的 WPF 框架中包含的所有有趣且省时的方案。
先决条件
唯一的先决条件是以下几点
- VS2008
- .NET 3.5 SP1
- Cinch(包含在下载的代码中)
- Log4Net(包含在下载的代码中)
它是如何工作的/它做了什么
值转换器
首先要看到的是“Baboon framework
”,它对绑定提供了出色的支持。看看这个方便的 IValueConverter
。
using System;
using System.Collections.Generic;
using System.Windows.Data;
using System.Windows;
using System.Globalization;
using BaboonApp.Interop;
namespace BaboonApp
{
[ValueConversion(typeof(String), typeof(String))]
public class BaboonConverter : IValueConverter
{
private static readonly string TargetBaboonString = "Baboon";
#region IValueConverter implementation
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
#if GetMeABaboonNatively
return NativeBaboonWrapper.ConvertToBaboon((string)value);
#else
return TargetBaboonString;
#endif
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
#if GetMeABaboonNatively
return NativeBaboonWrapper.ConvertToBaboon((string)value);
#else
return TargetBaboonString;
#endif
}
#endregion
}
}
请注意 NativeBaboonWrapper
的使用,如果定义了 GetMeABaboonNatively
编译符号,就会使用它,稍后将详细介绍。
这是一个在 XAML 窗口绑定中使用它的例子。
<TextBox Text="{Binding Path=DataEntry1,
Converter={StaticResource BabConv}, Mode=TwoWay}"
Width="150" Margin="5"/>
在这个例子中,我们获取一些用户输入,并通过 ViewModel
(是的,没错,Baboon Framework
开箱即用,完全支持 MVVM)的 ICommand
来运行它。基本上,用户在 XAML 中的 TextBox
中输入。然后 Baboon Framework
会做它的事情。用户输入的任何内容(是的,任何内容)都会立即转换为字符串“Baboon
”。看看这节省了多少时间。无论输入什么,是的,无论输入什么,都会转换为“Baboon
”。这不仅节省了时间,而且还意味着我们只需要一个 IValueConverter
,而且它在测试方面也非常出色。如果输入的数据不等于“Baboon
”,我们就遇到了问题。所以我们的测试非常小巧而优雅。一个简单的测试可能看起来像这样
Assert.AreEqual(dataEntered, "Baboon");
工作完成。
这是一个演示。用户输入一个 string
。

Baboon Framework
完成它的工作

更多证据

工具提示
我们不限于这样使用 Baboon Framework
。看看另一个例子。我们也可以在 ToolTips 中使用它。看看这两个不同的 ToolTips。

这里有一些实现这些的 XAML 示例
<Grid Background="CornflowerBlue" HorizontalAlignment="Stretch" Height="40">
<Label Foreground="Black" Content="Use It In A Tooltip"
FontWeight="Bold" FontSize="14"/>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="10">
<Button Content="Look At My Tooltip" ToolTip="{Binding Path=DataStored1,
Converter={StaticResource BabConv}}" Margin="5"/>
<Button Content="Look At My Tooltip" Margin="5">
<Button.ToolTip>
<Image Source="{local:BaboonProvider RequiresImage=true,
BoundDataContext={Binding DataStored1}}" />
</Button.ToolTip>
</Button>
</StackPanel>
但是等等,还有更多。让我们看看它在 Label
控件上的使用。
<Grid Background="LightCyan" HorizontalAlignment="Stretch" Height="40">
<Label Foreground="Black" Content="Use It In Label"
FontWeight="Bold" FontSize="14"/>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="10">
<Label Content="{Binding Path=DataStored1,
Converter={StaticResource BabConv}}" Width="150" Margin="5"/>
</StackPanel>
您可以在此处看到

那么我们还能做什么呢?亲爱的读者,请继续阅读。
OpenFileDialog
我们甚至可以让用户选择他们想要的文件,我们可以直接从 ViewModel 使用类似这样的代码来完成
public void ExecutePickFileCommand()
{
openfileService.InitialDirectory = @"c:\";
bool? result = openfileService.ShowDialog(null);
if (result.HasValue && !String.IsNullOrEmpty(openfileService.FileName))
{
popupShowerService.ShowDialog("Popup1", null);
messagerService.ShowInformation(String.Format("File picked was Baboon"));
}
}
运行后会显示类似这样的内容:。

太棒了……不是吗?同样,用户选择的任何内容都会瞬间被转换(利用 Baboon
框架的内置强大功能),直接变成“Baboon
”。纯粹的天才,告诉我,您见过如此强大的力量吗?

标记扩展
我们并没有止步于此,哦不,我们竭尽全力为您解决绑定难题。我们创建了一个非常简单的 BaboonProvider MarkupExtension
。这使得返回“Baboon
”到所有绑定变得如此容易。这是 BaboonProvider MarkupExtension
。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media.Imaging;
namespace BaboonApp
{
public class BaboonProvider : MarkupExtension
{
public Binding BoundDataContext { get; set; }
public Boolean RequiresImage { get; set; }
public override object ProvideValue(
IServiceProvider serviceProvider)
{
if (!RequiresImage)
{
//modify the binding by setting the converter
BoundDataContext.Converter = new BaboonConverter { };
return BoundDataContext.ProvideValue(serviceProvider);
}
else
{
return new BitmapImage(
new Uri("../../Images/baboon.jpg",
UriKind.RelativeOrAbsolute));
}
}
}
}
您可以在自己的 XAML 中像这样使用它
<ListView ItemsSource="{Binding PersonData}"
Height="150" Background="LightGray">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="LightGray" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="FirstName" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{local:BaboonProvider RequiresImage=false,
BoundDataContext={Binding FirstName}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="LastName" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{local:BaboonProvider RequiresImage=false,
BoundDataContext={Binding LastName}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Image" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{local:BaboonProvider RequiresImage=true,
BoundDataContext={Binding FirstName}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
其中 ListBox
的源在 ViewModel 中声明如下
/// <summary>
/// DataEntry1
/// </summary>
static PropertyChangedEventArgs personDataChangeArgs =
ObservableHelper.CreateArgs<BaboonViewModel>(x => x.PersonData);
public ObservableCollection<PersonViewModel> PersonData
{
get { return personData; }
set
{
personData = value;
NotifyPropertyChanged(personDataChangeArgs);
}
}
并且可以使用以下 ViewModel ICommand
创建一个新的 Person
/// <summary>
/// AddNewPersonCommand
/// </summary>
public void ExecuteAddNewPersonCommand()
{
PersonViewModel person = new PersonViewModel();
bool? result = popupShowerService.ShowDialog("NewPersonPopup", person);
if (result.HasValue && result.Value)
{
if (String.IsNullOrEmpty(person.FirstName) ||
String.IsNullOrEmpty(person.LastName))
{
messagerService.ShowInformation(
"You must fill in both first and last names");
}
else
{
personData.Add(person);
messagerService.ShowInformation("Person entered was added");
}
}
}
这就是它的作用
步骤 1:用户填写新个人信息

步骤 2:新个人信息神奇地转换为 Baboon
Baboon
框架再次通过简单地返回一个新的“Baboon
”填充的 Person 来为用户节省时间。我相信您会同意,这将加快单元测试的速度。

原生获取 Baboon
尽管 .NET Framework 非常出色,但我认为我们都遇到过需要原生获取某些与 baboon 相关的数据的情况。在这些情况下,.NET Framework 根本不够用。
为了解决这个限制,我们包含了一个原生的 baboon 转换器,它充分利用了 C++ 的强大功能来获取一个 baboon 字符串。
通过声明一个 DLL 导出的头文件 NativeBaboon.hpp;
extern "C" __declspec(dllexport) const char* convert_to_baboon(const char* input);
然后是它的实现,NativeBaboon.cpp;
#include "stdafx.h"
#include <string>
#include <exception>
#include "NativeBaboon.hpp"
extern "C" __declspec(dllexport) const char* convert_to_baboon(const char* input)
{
static const std::string baboon_string = "Baboon";
return baboon_string.c_str();
}
我们实现了一个保证能将任何 string
内容转换为更受欢迎的“Baboon
” string
内容的实现。
通过将与原生 DLL 的互操作调用封装在一个包装类 NativeBaboonWrapper
中,可以以 .NET 的方式利用原生调用的强大功能。
namespace BaboonApp.Interop
{
// Wrapper
internal class NativeBaboonWrapper
{
[DllImport("NativeBaboon.dll")]
private static extern string convert_to_baboon(string input);
public static string ConvertToBaboon(string input)
{
return NativeBaboonWrapper.convert_to_baboon(input);
}
}
}
return NativeBaboonWrapper.ConvertToBaboon((string)value);
在某些情况下,原生 baboon 可能过于强大,在这种情况下,最好使用 .NET CLR baboon
。此示例项目中的 BaboonConverter
允许使用编译符号 GetMeABaboonNatively
来更改转换器实现,该符号可以在项目属性窗口中设置或取消设置。

注入、销毁和模拟 Baboon
上周,一位初级开发人员向我展示了他编写的一些使用了 baboon
框架特性的代码。我们喜欢让初级开发人员从 baboon 入门,因为它能帮助他们理解常见的代码问题以及如何有效地解决它们。他向我展示的代码使用了简单的 baboon
实现和一个名为 Jungle
的类,该类消费了它。
namespace BaboonApp.Novice
{
class Jungle
{
public Double Boogie()
{
Baboon boon = BaboonProvider.GetBaboon();
return boon.Foo();
}
}
//Simple baboon implementation
class Baboon
{
public Double Foo()
{
return 3.84; //average height of male baboon
}
}
}
很棒的 boogie!但是,如果您的 baboons 需要从框架中的默认 BaboonProvider
以外的地方提供怎么办?如果您的公司开始从第三方服务消费 baboons 怎么办?您如何在独立于 baboon
源的情况下测试您的 Boogie()
?我让这位下属去寻找一个 IOC 容器。
那天晚些时候,他带着一个新版本的代码和他对 Castle Windsor IOC 容器的基本理解回来了。他重构了代码,以受益于他的学习。
namespace BaboonApp.Intermediate
{
class Jungle
{
public int Boogie()
{
WinsdorContainer container = new WindsorContainer
( new XmlInterpreter(new ConfigResource("castle")));
IPrimateProvider pp = container.Resolve(typeof(IPrimateProvider))
as IPrimateProvider;
Baboon boon = pp.GetBaboon();
return boon.Foo();
}
}
}
太好了!现在我们可以配置 IOC,在运行时为我们提供一个我们选择的 IPrimateProvider
实现。它可以是第三方 baboon
提供商,甚至是提供 mock baboons 的 mock baboon
提供商。Castle Windsor 的内部细节超出了本文档的范围,但它是免费且易于理解的,只需访问 Castle 网站,或查看本文附加的代码。
但是,现在我们有了这些失控的 baboons,我们需要某种控制器来管理它们的生命周期,并确保它们不会失控(程序员笑话!)。进入 FactoryFuneral
包装器。这个包装器是 baboon
框架中最有用的功能之一,它有效地封装了大量的 IOC 和生命周期管理所需的底层代码,您可以看到。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cinch;
namespace BaboonApp.BaboonServices
{
/// <summary>
/// BoonPipe is an IOC container wrapper that uses a factory
/// pipeline implementation to Emit and Quash baboon instances.
/// </summary>
public class BaboonFactoryFuneral : IFactoryFuneral<Baboon>
{
#region Data
private IMessageBoxService messagerService;
#endregion
#region Ctor
public BaboonFactoryFuneral()
{
messagerService = ViewModelBase.ServiceProvider.Resolve<IMessageBoxService>();
}
#endregion
#region IFactoryFuneral<Baboon> Members
/// <summary>
/// IFactoryPipe Method
/// </summary>
/// <returns></returns>
public Baboon ReleaseOne()
{
BaboonApp.BaboonServices.IBaboonProvider provider =
(IBaboonProvider)ViewModelBase.ServiceProvider.GetService
(typeof(BaboonApp.BaboonServices.IBaboonProvider));
return provider.GetBaboon();
}
/// <summary>
/// IFactoryFuneralMethod. Considered too dangerous to implement just yet.
/// </summary>
/// <returns></returns>
public Baboon ReleaseMany()
{
throw new Exception("Dangerous method call attempted");
}
/// <summary>
/// IFactoryFuneralMethod
/// </summary>
/// <returns></returns>
public void Quash(Baboon boon)
{
boon.Dispose();
messagerService.ShowWarning("Death of a Baboon");
boon = null;
}
#endregion
}
}
实际的服务看起来像这样
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BaboonApp.BaboonServices
{
public interface IFactoryFuneral<T>
{
T ReleaseOne();
T ReleaseMany();
void Quash(T boon);
}
}
一个非常方便的类!所以,随着这最后一块的到位,我的初级开发人员也筋疲力尽,学习了很多,让我们来生成最终的重构。这是它,这是我们的服务如何注入到服务定位器实例中,在 ViewModelBase
类内部,以便所有 ViewModel 都可以使用它。
ViewModelBase.ServiceProvider.Add(typeof(BaboonApp.BaboonServices.IBaboonProvider),
new BaboonApp.BaboonServices.BaboonProvider());
ViewModelBase.ServiceProvider.Add
(typeof(BaboonApp.BaboonServices.IFactoryFuneral<Baboon>),
new BaboonApp.BaboonServices.BaboonFactoryFuneral());
就这样,专业的 baboon
管理以一种简单/易于扩展的方式完成,我们认为它也易于理解。
延伸阅读
好像这一切还不够,我们还出版了备受期待的书籍《学习 Baboon 转换器第二版》,这本书应该可以在所有主要的书店和在线供应商处找到。我们几乎没有触及 Baboon
框架可以做什么的表面。如果您觉得这是适合您的框架,请不要浪费时间购买这本书,它是一本引人入胜的书。

总之,就目前而言就这些了,但请继续阅读,看看未来还有哪些精彩内容等着您。这仅仅是个开始。
已知问题
目前,本文介绍的 Baboon
框架并不处理其他灵长类动物,但却能异常出色地处理 baboon。我们有宏伟的计划,不仅要涵盖所有灵长类动物,还要涵盖其他动物物种。敬请关注这些激动人心的增强功能。
就是这样,各位
我相信大家都会同意,Baboon
框架在帮助您在自己的 WPF 应用程序中快速使用 Baboon
方面将是最有价值的。这是一项充满爱的工作,涉及我们写过的最复杂的一些代码。我们认为结果不言而喻。简而言之,本文介绍的 Baboon
框架是同类产品中的佼佼者,是所有 WPF 框架之母,我们觉得它无所不能,几乎能满足您在 WPF 应用程序中遇到的任何绑定需求。