zoukankan      html  css  js  c++  java
  • 【译】Caliburn——Action

          最近在学习Caliburn,主要的学习资源还是官网上的文档,蛮详细的,所以翻译一下与园友们共同学习。

          官网中的文档主要是基于其Sample的,有需要的朋友可以去这里下载(我用的VS2010不能打开项目,惨淡。。。Disappointed smile),由于公司用的是1.1 RTW版本的,所以我这里用的也是1.1的。如果您下载的是其他版本的,我不能保证能正确编译运行。Smile 本文源码下载:Caliburn.Action.7z

    所需要的程序集

    • Caliburn.Core
    • Caliburn.PresentationFramework
    • Microsoft.Practices.ServiceLocation

    最小配置

    Note:下面的代码在WPF中应放置于App.xaml.cs中的构造函数中,而在Sliverlight中应放置于App.xaml.cs中的Application_Startup中。

    请确保已添加上述程序集,并引入命名空间。

    using Caliburn.Core;
    using Caliburn.PresentationFramework;
    CaliburnFramework
        .ConfigureCore()
        .WithPresentationFramework()
        .Start();

    Note: 另一可选的手动配置是继承CaliburnApplication.

    使用Actions

        Actions是Caliburn能够支持诸如MVC,MVP和MVVM等UI模式的核心特性。下面让我们了解一下Actions的基本使用。

        在您的项目中,添加一个名为Calculator的类。它将作为您的第一个控制器,放置actions。代码如下:

    public class Calculator
    {
        public int Divide(int left, int right)
        {
            return left / right;
        }
    }
     

    接下来,使用下面的标记填充您的Main Page(Silverlight)或Window(WPF)。Silverlight

    <UserControl x:Class="Actions.Page"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:ca="clr-namespace:Caliburn.Actions;assembly=Caliburn.Actions"
                 xmlns:local="clr-namespace:Actions"
                 xmlns:cm="clr-namespace:Caliburn.RoutedUIMessaging;assembly=Caliburn.RoutedUIMessaging"
                 xmlns:ct="clr-namespace:Caliburn.RoutedUIMessaging.Triggers;assembly=Caliburn.RoutedUIMessaging"
                 Width="400"
                 Height="300">
        <ca:Action.Target>
            <local:Calculator />
        </ca:Action.Target>
     
        <StackPanel x:Name="LayoutRoot">
            <Grid Margin="10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
     
                <TextBox x:Name="left"
                         Grid.Column="0" />
                <TextBlock Text="/"
                           Margin="10 0"
                           Grid.Column="1" />
                <TextBox x:Name="right"
                         Grid.Column="2" />
                <Border BorderBrush="Black"
                        BorderThickness="0 0 0 1"
                        Margin="10 0 0 0"
                        Grid.Column="3">
                    <TextBlock x:Name="DivideResult" />
                </Border>
            </Grid>
     
            <Button Content="Divide (Trigger Collection w/ Explicit Parameters)">
                <cm:Message.Triggers>
                    <ct:RoutedMessageTriggerCollection>
                        <ct:EventMessageTrigger EventName="Click">
                            <ct:EventMessageTrigger.Message>
                                <ca:ActionMessage MethodName="Divide"
                                                  OutcomePath="DivideResult.Text">
                                    <cm:Parameter ElementName="left"
                                                  Path="Text" />
                                    <cm:Parameter ElementName="right"
                                                  Path="Text" />
                                </ca:ActionMessage>
                            </ct:EventMessageTrigger.Message>
                        </ct:EventMessageTrigger>
                    </ct:RoutedMessageTriggerCollection>
                </cm:Message.Triggers>
            </Button>
        </StackPanel>
    </UserControl>

    WPF

    <Window x:Class="Actions.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:Actions"
            xmlns:cal="http://www.caliburnproject.org"
            Title="Window1"
            SizeToContent="WidthAndHeight">
        <cal:Action.Target>
            <local:Calculator />    
        </cal:Action.Target>
         
        <StackPanel>
            <Grid Margin="10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                
                <TextBox x:Name="left" 
                         Grid.Column="0"/>
                <TextBlock Text="/"
                           Margin="10 0"
                           Grid.Column="1"/>
                <TextBox x:Name="right" 
                         Grid.Column="2"/>
                <Border BorderBrush="Black"
                        BorderThickness="0 0 0 1"
                        Margin="10 0 0 0"
                        Grid.Column="3">
                    <TextBlock x:Name="DivideResult" />
                </Border>
                
            </Grid>
             
            <Button Content="Divide (Trigger Collection w/ Explicit Parameters)">
                <cal:Message.Triggers>
                    <cal:RoutedMessageTriggerCollection>
                        <cal:EventMessageTrigger EventName="Click">
                            <cal:EventMessageTrigger.Message>
                                <cal:ActionMessage MethodName="Divide"
                                               OutcomePath="DivideResult.Text">
                                    <cal:Parameter Value="{Binding ElementName=left, Path=Text}"/>
                                    <cal:Parameter Value="{Binding ElementName=right, Path=Text}"/>
                                </cal:ActionMessage>
                            </cal:EventMessageTrigger.Message>
                        </cal:EventMessageTrigger>
                    </cal:RoutedMessageTriggerCollection>
                </cal:Message.Triggers>
            </Button>
        </StackPanel>
    </Window>

    Note:值得一提的是,在WPF与Sliverlight两个版本中有些许微妙的差别。首先,WPF版本中只使用一个命名空间就包含了所有Caliburn的特性。WPF可以通过XmlnsDefinition特性支持,而在SL中却是不起作用的。其次,声明变量的语法也有些差别。SL V2中不支持ElementName绑定和Freezables,在V3中也只是部分支持,因此不能像WPF那样直接绑定参数值。然而可以使用ElementName和Path去实现达到相同的效果。你可以从这里获得更多关于parameters的细节。(我没学过SL,所以上面的区别对我来说不是区别,不过还是请懂SL的朋友注意一下Open-mouthed smile)

    Note:该示例中内联了Calculator的实例化。不过本人不太喜欢View Control(视图控件?没看懂啥意思,也不会翻译,还请园友们指教)。这里仅作掩饰用。

        那么,这些标记又是干什么用的呢?(不要担心标记的数量,我待会将向您演示如何大量精简)。先看看<Button/>元素。注意Message.Triggers附加属性的使用。有了这个属性,我们可以添加传递消息的触发器到任意元素。这里,我们可以使用一个EventMessageTrigger(详情请看Message Triggers)。EventMessageTriggers允许我们使用触发的事件传递信息给控制器。因此,当Button的Click事件被触发时,我们将发送附加的信息到控制器。而我们正在发送的信息是一个ActionMessage,指明将要调用的方法是"Divide”。并且ActionMessage也指明了”left”和”right”元素的Text属性应将作为参数传递给”Divide”方法。最终,“Divide”方法的返回值绑定到了“DivideResult”元素的Text属性。

        现在我们已经有了可以发送具体消息的触发器了,但是问题来了——谁将处理这个消息呢?仔细观察后发现:根元素UserControl/Window拥有一个具有值得附加属性。通过使用Action.Target属性我们可以指明一个具体的实例去处理ActionMessages。对于这个简单的例子,我们声明了一个内联实例,然而在大多数应用程序中更有可能会通过数据绑定或Resolve Extension实现。

    Note:ResolveExtension仅在WPF中可用,因为在SL中目前不支持自定义标价扩展。为避免这个问题,可以通过为Action.Target指明一个字符串值。在这种情况下,该字符串被用来在实现了IContainer的实例中检索资源。如果你使用SimpleContainer(Caliburn的默认Container),且所有的服务都通过其Type注册过,那么也可以实现上面的方法——只要将其Type作为Key就好了。

        现在我们已经有了必须的代码和标记,我们也理解了基本使用,好了,是时候运行了。在两个textbox中输入一些值然后单击按钮。(如果输入正确)你会看到正确的除法运算的结果。在”Divide”方法中设置一个断点并重新运行。接下来,在右边的textbox中输入“0”,单击按钮后,抛出异常。糟糕!这可不行!我们必须修正它。

    改变Calculator类定义,如下:

    using Caliburn.PresentationFramework.Filters;
    [Rescue("GeneralRescue")]
    public class Calculator
    {
        public int Divide(int left, int right)
        {
            return left / right;
        }
     
        public void GeneralRescue(Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    Note: 在非视图类中调用MessageBox.Show并不是一个好的实践,考虑实现MessageBoxService。

        再次运行,在右侧的textbox中输入0。这次,会弹出一个消息框,程序也没有崩溃。一个resuce就是一个具体类型的filter,可以捕获异常。filter所需的参数只有一个,就是可以将异常信息传递过去的方法的名称。如果resuce放置一个类上,那么在该类中所有有Caliburn调用action而产生的异常都会被指定的方法所处理。此外,resuces可以被放置在具体的方法上。在这种情况下,类级别的resuce会被方法级别的重写。resuce虽然阻止了程序的崩溃,但是并未提供给我们想要的用户体验。理想情况下,我们希望如果文本框输入值不合法,按钮就应该被禁用。接下来,看另一个类型的filter可以帮助我们。

    再次更新Calculator,如下:

    [Rescue("GeneralRescue")]
    public class Calculator
    {
        [Preview("CanDivide", AffectsTriggers = true)]
        public int Divide(int left, int right)
        {
            return left / right;
        }
     
        public bool CanDivide(int left, int right)
        {
            return right != 0;
        }
      
        public void GeneralRescue(Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

        现在我们增加了Preview过滤器。该方法会在其装饰的方法执行前而执行。如果返回false,其装饰的方法不会允许执行。注意该方法和其所要装饰的方法的参数是一样的。并且,如果AffectsTriggers设置为true,Preview过滤器会影响UI的状态。这是默认值,不需要具体指明除非你想关闭这个行为。再次运行,输入值。你会发现如果输入不合法的值,按钮会自动被禁用。

    Note: 按照惯例,如果有一个名为Can{ActionName}的方法,Caliburn会自动为相应的Action加上Preview,而你不需要自己加上该特性。

    Note: 你也可以将Can{ActionName}作为属性,它也会被自动触发(因此,你仅仅需要为非惯例名称手动加上Preview特性)。当你这样做时,也可以引发属性改变通知强制相关联的触发器重新评估。

        现在你已经理解了一些主要概念,接下来我们将研究一些可选的标记语法去帮助我们使Caliburn更加方便。在接下来的例子中,我们会使用与前面相同的xaml,除了<Button/>。所有其他的标记对于WPF和SL都是一样。

    使用下面的标记替换<Button/>:

    <Button Content="Divide (Trigger Collection w/ Inferred Parameters)">
        <cm:Message.Triggers>
            <ct:RoutedMessageTriggerCollection>
                <ct:EventMessageTrigger EventName="Click">
                    <ct:EventMessageTrigger.Message>
                        <ca:ActionMessage MethodName="Divide" />
                    </ct:EventMessageTrigger.Message>
                </ct:EventMessageTrigger>
            </ct:RoutedMessageTriggerCollection>
        </cm:Message.Triggers>
    </Button>

        这个标记和上面最初的trigger/message声明非常相似。惟一的区别是没有参数。如果没有指定参数,Caliburn将尝试通过基于(方法)参数的名称检索UI元素和资源而决定其值。如果Action有返回值,方法的名称+“Result”会被用作key去检索UI而实现绑定。运行,你会发现几乎依旧正常工作。然而如果你在右侧的文本框内输入0,按钮仍然可以使用,但是action却不会触发(你可以设置断点调试确认)。这是因为Caliburn在消息被发送之前都不知道该消息包含哪些UI元素,此外它也不能提前更新UI,但仍能过滤action。(请注意)仅当不需要通过输入值去改变UI的情况下才使用推断参数。

    现在,再次替换<Button/>,如下:

    <Button Content="Divide (Attachment w/ Explicit Parameters)"
            cm:Message.Attach="[Event Click] = [Action Divide(left.Text, right.Text) : DivideResult.Text]" />

    这是应优先使用的声明triggers/messages的方式。我们使用了另一个附加属性:Message.Attach。通过这个属性,我们可以提供一个字符串,然后它将被解析传入trigger。你看到的声明在运行时其实是和原来的例子是一模一样的。再次运行,确认一下行为。在等号左侧,我们指出了trigger的类型和参数。在这里,我们为Click事件声明了一个ventMessageTrigger。你可以在这里获得更多缩减trigger语法的详细信息。在等号的右侧,我们声明了消息的类型和内容。这样,我们有了一个ActionMessage,其参数从left和right文本框中获得,返回值绑定回了”Divideresult”的text属性。其中,方括号”[“是可选的,但是它显得更清晰更可读。

    Note for WPF: 如果需要,你也可以为某些参数指明具体的绑定模式。如下:

    <Button Content="Divide (Attachment w/ Explicit Parameters and Modes)"
            cm:Message.Attach="[Event Click] = [Action Divide(left.Text:TwoWay, right.Text:OneWay) : DivideResult.Text]" />

    接下来,再次替换<Button/>,如下:

    <Button Content="Divide (Attachment w/ Inferred Parameters)"
            cm:Message.Attach="[Event Click] = [Action Divide]" />

         这个在功能上和第二个比较长的标记示例是一样的。在这种情况下,参数被Caliburn自动推断出。

    再次替换<Button/>,如下:

    <Button Content="Divide (Attachment w/ Default Trigger/Message and Explicit Parameters)"
            cm:Message.Attach="Divide(left.Text, right.Text) : DivideResult.Text" />

        这个示例很有趣。在这里,Caliburn会基于被绑定元素的类型选择一个默认的触发器。Caliburn也会用默认的消息解析器解析(消息)。这里,Click事件的默认的触发器是EventMessageTrigger,默认的消息类型是ActionMessage,所以该标记产生了与先前的第一个例子一样的运行时行为。即使这是非常简洁的。但我个人仍更喜欢显示声明trigger和message类型,这取决于你的愿望。

    再次替换<Button/>,如下:

    <Button Content="Divide (Attachment w/ Defaults)"
            cm:Message.Attach="Divide" />

        在上面的标记中,所有东西都会被Caliburn通过推断得出:触发器类型,消息类型,参数类型和返回值。它仍然可以工作,但请谨慎使用。

        经过上面的例子,你可能会困惑——哪有人会使用那么长的语法?的确,在绝大多数情况下,你不需要这么做,但是它提供了最大程度的灵活性。最后一点,如果你希望为一个元素附加多个消息,你可以用分号隔开。就像下面这样:

    <Button Content="Divide or Multiply"
            cm:Message.Attach="[Event Click] = [Action Divide(left.Text:TwoWay, right.Text:OneWay) : DivideResult.Text];
                               [Event MouseRightButtonUp] = [Action Multiply(left.Text:TwoWay, right.Text:OneWay) : MultiplyResult.Text]" />
    物有所不足,智有所不明。
  • 相关阅读:
    block的使用
    控制器的多种创建方式
    指针函数和函数指针
    UIScrollView实现图片轮播器及其无限循环效果
    如何按顺序执行两个动画
    代理、通知、KVO的应用
    CAlayer层的属性
    UIView的autoresizingMask和autoresizesSubviews属性的剖析
    面向对象编程思想(OOP)总结
    scala中闭包的使用
  • 原文地址:https://www.cnblogs.com/mezero/p/2746514.html
Copyright © 2011-2022 走看看