65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (110投票s)

2010年4月1日

CPOL

8分钟阅读

viewsIcon

382947

downloadIcon

1406

这是真正的代码,您说什么

目录

引言

正如你们中的一些人可能知道的,我和我的几个老搭档都在使用 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 内容转换为更受欢迎的“Baboonstring 内容的实现。

通过将与原生 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 应用程序中遇到的任何绑定需求。

© . All rights reserved.