当前位置:网站首页 / WPF / 正文

AY GITHUB WPF TECH APPLY 2- Caliburn.Micro

时间:2019年08月26日 | 作者 : aaronyang | 分类 : WPF | 浏览: 1540次 | 评论 0

新建wpf4.5 nuget安装Caliburn.Micro

GITHUB地址:https://github.com/Caliburn-Micro/Caliburn.Micro

NuGet之于Visual Studio(C++, C#等), 犹pip之于Python, npm之于node, maven之于Java, gem之于Rub

image.png

在源码目录有个

image.png

我们拷贝,这些文件到项目,然后删除MainWindow.xaml,然后App.xaml中StartupUri的代码

image.png

<Application x:Class="CALIDEMO.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:CALIDEMO"
         >
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <local:Bootstrapper x:Key="bootstrapper" />
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

image.png

奇怪的写法,在App.xaml.cs的写法

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace CALIDEMO
{
    public partial class App
    {
        public App()
        {
            InitializeComponent();
        }
    }
}

然后每一个ViewModel都是继承

image.png

看一下Bootstrapper的代码

using System;
using System.Collections.Generic;
using System.Windows;
using Caliburn.Micro;
using CALIDEMO.ViewModels;

namespace CALIDEMO
{
    public class Bootstrapper : BootstrapperBase
    {
        private SimpleContainer container;

        public Bootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            container = new SimpleContainer();

            container.Singleton<IWindowManager, WindowManager>();

            container.PerRequest<ShellViewModel>();
        }

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            DisplayRootViewFor<ShellViewModel>();
        }

        protected override object GetInstance(Type service, string key)
        {
            return container.GetInstance(service, key);
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return container.GetAllInstances(service);
        }

        protected override void BuildUp(object instance)
        {
            container.BuildUp(instance);
        }
    }
}

整体风格,一个界面和VM的 IOC注册,然后启动一个Shell的窗体, 这里有个命名规则, 文件命名VM要ViewModel结尾,Views里面的界面要View结尾。


先绑定属性

using System;
using Caliburn.Micro;

namespace CALIDEMO.ViewModels
{
    public class ShellViewModel : Screen
    {
        private string _Left="左边";

        public string Left
        {
            get { return _Left; }
            set
            {
                _Left = value;
                NotifyOfPropertyChange();
            }
        }

        private string _Right="右边";

        public string Right
        {
            get { return _Right; }
            set { _Right = value; NotifyOfPropertyChange(); }
        }

    }
}

View:

<Window x:Class="CALIDEMO.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="ShellView" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBox Text="{Binding Left}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="40"/>
    </Grid>
</Window>

效果:

image.png

操作简单方法

cal:Message.Attach="[Event E]=[Action A]" (E是操作,比如Click, MouseDown, KeyDown等等,A是ViewModel中具体的方法名字

要使用方法,先在VM中定义方法

        private string _Result= "右边";

        public string Result
        {
            get { return _Result; }
            set { _Result = value; NotifyOfPropertyChange(); }
        }

        public async void Concat(string left, string right)
        {
            Result = left + right;
        }
<Window x:Class="CALIDEMO.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" Title="ShellView" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBox x:Name="txt1"  Text="{Binding Left}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Height="30" />
            <TextBlock Text=" 和 "/>
            <TextBox x:Name="txt2" Text="{Binding Right}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Height="30" />
            <Button Margin="10,0,0,0" Width="100" Height="30" Grid.Row="1" cal:Message.Attach="[Event Click]=[Action Concat(txt1.Text, txt2.Text):空白]"></Button>
        </StackPanel>

      
    </Grid>
</Window>

image.png

是可以调用方法的,也可以传入参数。


接下来复制ShellViewModel,改名为UC1ViewModel,你可以理解现在,我们需要把一个页面拆成多个用户控件,分布页那种。

using System;
using Caliburn.Micro;

namespace CALIDEMO.ViewModels
{
    public class UC1ViewModel : Screen
    {
        private string _Left="左边";

        public string Left
        {
            get { return _Left; }
            set
            {
                _Left = value;
                NotifyOfPropertyChange();
            }
        }

        private string _Right="右边";

        public string Right
        {
            get { return _Right; }
            set { _Right = value; NotifyOfPropertyChange(); }
        }


        private string _Result= "右边";

        public string Result
        {
            get { return _Result; }
            set { _Result = value; NotifyOfPropertyChange(); }
        }

        public async void Concat(string left, string right)
        {
            Result = left + right;
        }
    }
}

而我们ShellViewModel,改成

using System;
using Caliburn.Micro;

namespace CALIDEMO.ViewModels
{
    public class ShellViewModel : Conductor<object>
    {
        public ShellViewModel()
        {
        }
        public void ShowCalculator()
        {
            ActivateItem(new UC1ViewModel());
        }

    }
}

迁移ShellViewModel的你认为 UC1中应该的UI部分到 UC1View用户控件中

<UserControl x:Class="CALIDEMO.Views.UC1View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:CALIDEMO.Views" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="Yellow">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBox x:Name="txt1" Text="{Binding Left}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Height="30" />
            <TextBlock Text=" 和 " />
            <TextBox x:Name="txt2" Text="{Binding Right}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Height="30" />
            <Button Margin="10,0,0,0" Width="100" Height="30" Grid.Row="1" cal:Message.Attach="[Event Click]=[Action Concat(txt1.Text, txt2.Text)]"></Button>
        </StackPanel>

    </Grid>
</UserControl>

然后我们修改继承了ShellViewModel : Conductor<object>的View,貌似有命名约定

<Window x:Class="CALIDEMO.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" Title="ShellView" Height="300" Width="300">
    <Grid MinHeight="200">
        <Button Content="Show单击我" x:Name="ShowUC1" Grid.Row="0"></Button>
        <ContentControl x:Name="ActiveItem"></ContentControl>
    </Grid>
</Window>

ContentControl的Name是和ViewModel的,对应上,貌似就能显示对应的ViewModel,这里我就加了一个UC1ViewModel,我不知道一个Shell如果有多个VM组成,这里该怎么写?


同样的按钮的Name,如果没有给Click事件,感觉就像命令那样,自动根据名字绑定事件,这里给按钮找到一个叫ShowUC1的方法,默认不指定事件,默认是Click事件,绑定这个方法。

097.gif


拷贝UC1为UC2

image.png

UC2View换一个颜色,绿色

<UserControl x:Class="CALIDEMO.Views.UC2View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:CALIDEMO.Views" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="Green">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBox x:Name="txt1" Text="{Binding Left}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Height="30" />
            <TextBlock Text=" 和 " />
            <TextBox x:Name="txt2" Text="{Binding Right}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Height="30" />
            <!--<Button Margin="10,0,0,0" Width="100" Height="30" Grid.Row="1" cal:Message.Attach="[Event Click]=[Action Concat(txt1.Text, txt2.Text)]"></Button>-->

            <Button Margin="10,0,0,0" Width="100" Height="30" Grid.Row="1" >
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="Concat">
                            <cal:Parameter Value="{Binding Text,ElementName=txt1}" />
                            <cal:Parameter Value="{Binding Text,ElementName=txt2}" />
                        </cal:ActionMessage>
                    </i:EventTrigger>
                
                </i:Interaction.Triggers>
            </Button>
        </StackPanel>

    </Grid>
</UserControl>

然后修改shell

<Window x:Class="CALIDEMO.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" Title="AY" Height="300" Width="300">
    <Grid MinHeight="200">
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Button Content="Show1" x:Name="ShowUC1" Grid.Row="0"></Button>
        <Button Content="Show2" x:Name="ShowUC2"  Grid.Row="1"></Button>
        <ContentControl x:Name="ActiveItem" Grid.Row="2"></ContentControl>
    </Grid>
</Window>

shell的vm修改如下

using System;
using Caliburn.Micro;

namespace CALIDEMO.ViewModels
{
    public class ShellViewModel : Conductor<object>
    {
        public ShellViewModel()
        {
        }
        public void ShowUC1()
        {
            ActivateItem(new UC1ViewModel());
        }
        public void ShowUC2()
        {
            ActivateItem(new UC2ViewModel());
        }
    }
}

你可以理解一个vm对应一个view了,项目启动都自动键值绑定了。

33F.gif

感觉有点选项卡的感觉了,你也可以把shell的内容封装到一个UserControl,然后放入shell了。



接下来看下集合

我们在UC2ViewModel中修改代码

         using System;
using Caliburn.Micro;

namespace CALIDEMO.ViewModels
{
    public class UC2ViewModel : Screen
    {

        public BindableCollection<Model> Items { get; private set; }
        public UC2ViewModel()
        {
            Items = new BindableCollection<Model>{
                                new Model { Id = Guid.NewGuid() },
                                new Model { Id = Guid.NewGuid() },
                                new Model { Id = Guid.NewGuid() },
                                new Model { Id = Guid.NewGuid() }
                         };

        }
        public void Add()
        {
            Items.Add(new Model { Id = Guid.NewGuid() });
        }

        public void Remove(Model child)
        {
            Items.Remove(child);
        }

    }
    public class Model
    {
        public Guid Id { get; set; }
    }
}
    public class Model
    {
        public Guid Id { get; set; }
    }

对应的UC2View修改代码

<UserControl x:Class="CALIDEMO.Views.UC2View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:CALIDEMO.Views" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="Green">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <StackPanel >
     
            <ItemsControl x:Name="Items">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Id}" />
                            <Button Content="Remove" cal:Message.Attach="Remove($dataContext)" />
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>

        </StackPanel>
        <Button Grid.Row="1" Content="Add" cal:Message.Attach="Add" />
    </Grid>
</UserControl>

这里面Remove($dataContext) 传递自己当前的范围的DataContext


效果如下

1gif.gif

集合属性的名字约定,他也自动和x:Name绑定了,其实你也可以指定ItemsSource={Binding Item}


$的其他几个用法

$eventArgs

Passes the EventArgs or input parameter to your Action. Note: This will be null for guard methods since the trigger hasn’t actually occurred.

$dataContext

Passes the DataContext of the element that the ActionMessage is attached to. This is very useful in Master/Detail scenarios where the ActionMessage may bubble to a parent VM but needs to carry with it the child instance to be acted upon.

$source

The actual FrameworkElement that triggered the ActionMessage to be sent.

$view

The view (usually a UserControl or Window) that is bound to the ViewModel.

$executionContext

The action's execution context, which contains all the above information and more. This is useful in advanced scenarios.

$this

The actual UI element to which the action is attached. In this case, the element itself won't be passed as a parameter, but rather its default property.




AYUI       www.ayjs.net      AY         杨洋原创编写,请不要转载谢谢


有的博客的 Bootstrapper是继承Bootstrapper<T>的,我在3.2版本的Caliburn找不到,最后用的是BootstrapperBase

image.png

源码中文件名没有改。


接下来说到 Caliburn有很多 View的控件x:Name和ViewModel的命名约定。

刚才还有个 bool的,自动绑定一个方法,你也可以加一个 Can方法名的返回bool的来标记是否可用

image.png我默认返回false,这样,按钮不可以点击了。

这一点到像Command的CanExecute



刚才看到了2种在vm中的事件绑定,一种约定,另一种是 附加属性给个字符串。

方式1,配合Blend的行为库

  <Button Margin="10,0,0,0" Width="100" Height="30" Grid.Row="1" >
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="Concat" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>

image.png

需要参数

  <Button Margin="10,0,0,0" Width="100" Height="30" Grid.Row="1" >
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="Concat">
                            <cal:Parameter Value="{Binding Text,ElementName=txt1}" />
                            <cal:Parameter Value="{Binding Text,ElementName=txt2}" />
                        </cal:ActionMessage>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>

image.png

同时绑定多个事件的话,

<Button Content="AY按钮" cal:Message.Attach="[Event MouseEnter] = [Action Talk('Hello', Name.Text)]; [Event MouseLeave] = [Action Talk('Goodbye', Name.Text)]" />

这里的Name就是前面的控件的名字,多个事件用分号隔开


Caliburn感觉每个给wpf控件都有个默认的属性和事件,方便你不指定属性或者事件,自动绑定上。


打开UC1View

<UserControl x:Class="CALIDEMO.Views.UC1View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:CALIDEMO.Views" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="Yellow">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBox x:Name="txt1" Text="{Binding Left}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Height="30" />
            <TextBlock Text=" 和 " />
            <TextBox x:Name="txt2" Text="{Binding Right}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100" Height="30" />
            <Button Margin="10,0,0,0" Width="100" Height="30" Grid.Row="1" cal:Message.Attach="[Event Click]=[Action Concat(txt1, txt2)]"></Button>

            <!--<Button Margin="10,0,0,0" Width="100" Height="30" Grid.Row="1" >
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="Concat">
                            <cal:Parameter Value="{Binding Text,ElementName=txt1}" />
                            <cal:Parameter Value="{Binding Text,ElementName=txt2}" />
                        </cal:ActionMessage>
                    </i:EventTrigger>
                
                </i:Interaction.Triggers>
            </Button>-->
        </StackPanel>

    </Grid>
</UserControl>

修改了

          <Button cal:Message.Attach="[Event Click]=[Action Concat(txt1, txt2)]"></Button>

直接给的控件名字,我没有指定Text,看行不行

image.png

答案是可以的。


如何传递一个枚举参数,代码如下,就当字符串就好了。所有的字符串换成大写

cal:Message.Attach="[Event Click] = [Action MethodWithEnum('MONKEY')]"
public enum Animals
{
  Unicorn,
  Monkey,
  Dog
}

public class MyViewModel
{ 
    public void MethodWithEnum(Animals a)
    {
         Animals myAnimal = a;
    }
}


看下OneActive

添加一个TabViewModel,再添加一个TabView

<UserControl x:Class="CALIDEMO.Views.TabView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:CALIDEMO.Views" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" d:DesignHeight="450" d:DesignWidth="800">

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="页面: " />
        <TextBlock x:Name="DisplayName" />
    </StackPanel>

</UserControl>
using Caliburn.Micro;

namespace CALIDEMO.ViewModels
{
    public class TabViewModel:Screen
    {
        
    }
}

添加一个Shell2ViewModel

using System;
using Caliburn.Micro;

namespace CALIDEMO.ViewModels
{

    public class Shell2ViewModel : Conductor<IScreen>.Collection.OneActive
    {
        int count = 1;

        public void OpenTab()
        {
            ActivateItem(new TabViewModel
            {
                DisplayName = "Tab " + count++
            });
        }
    }
}

然后添加对应页面

<Window x:Class="CALIDEMO.Views.Shell2View"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:cal="http://www.caliburnproject.org" Title="AY" Height="300" Width="300">
    <DockPanel>
        <Button x:Name="OpenTab" Content="Open Tab" DockPanel.Dock="Top" />
        <TabControl x:Name="Items">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding DisplayName}" />
                        <Button Content="X" cal:Message.Attach="DeactivateItem($dataContext, 'true')" />
                    </StackPanel>
                </DataTemplate>
            </TabControl.ItemTemplate>
        </TabControl>
    </DockPanel>
</Window>

然后修改Bootstrapper

image.png

运行项目

image.png

单击关闭,也可以关闭了

image.png


下面还有复杂的 父子页面复杂的demo,具体的看 https://caliburnmicro.com/documentation/composition


重点了解Caliburn的套路

像这种MVVM这种框架,我更好的称呼为 绑定与通知框架

Caliburn也有通知,叫事件聚合器,可以用来几个页面相互调用自己的方法

首先打开bootstrapper,加入

image.png

然后在ViewModel中,加入

image.png

然后测试一个UI上的发布

image.png

发布一个后台线程上

_eventAggregator.Publish(new object(), action => {
                    Task.Factory.StartNew(action);
                });


接下来订阅,你需要在构造函数中,加入这么一行代码

image.png

任何VM都可以订阅,继承IHandle<T>接口

请注意,通过实现上面的接口,您必须实现方法Handle(T message)T是您已指定自己感兴趣的消息类型。此方法是在发布匹配的Event类型时调用的方法。


那么如何订阅多个事件?实现多个接口就行了

image.png


Polymorphic这个多态性

这里string是继承object,这里调用方法就等于是一个多态了,如果发布一下,Caliburn会进入每一个符合条件的Handle处理方法,

image.png

image.png

查询Handler

image.png

上面代码,是找Handle<SpecialMessageEvent>的对应的子类,如果找到那个子类,就触发消息。

如果您正在使用带有Caliburn.Micro的EventAggregator而不是通过Nuget使用它,则可以访问Event Aggregator中的Coroutine支持。 通过IHandleWithCoroutine接口支持协同程序。


协同这块的Handle,暂时不看了,先记录下

image.png

   public class EventWithCoroutine
    {
        public IResult Activate()
        {
            return new TaskResult(Task.Factory.StartNew(() => {
                // Activate logic
            }));
        }

        public IResult DoWork()
        {
            return new TaskResult(Task.Factory.StartNew(() => {
                // Do work logic
            }));
        }
    }
    public class FooViewModel : Screen, IHandleWithCoroutine<EventWithCoroutine>
    {
        private readonly IEventAggregator _eventAggregator;

        public FooViewModel(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator;
            _eventAggregator.Subscribe(this);
        }

        public IEnumerable<IResult> Handle(EventWithCoroutine message)
        {
            yield return message.Activate();
            yield return message.DoWork();
        }
    }

Task Aware Subscribers

Caliburn.Micro还为基于任务的订户提供支持,其中需要Coroutines的异步功能,但重量更轻。 要利用此功能,请实现IHandleWithTask接口,如下所示。

image.png

{
    public class FooView2Model : Screen, IHandleWithTask<object>
    {
        private readonly IEventAggregator _eventAggregator;

        public FooView2Model(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator;
            _eventAggregator.Subscribe(this);
        }

        public Task Handle(object message)
        {
            return Task.Factory.StartNew(() => message);
        }
    }

Unsubscribing and Leaks

标准.Net事件的问题在于它们容易发生内存泄漏。 我们通过维持对订阅者的弱引用来避免这种情况。 如果引用订阅者的唯一事物是EventAggregator,那么它将被允许超出范围并最终被垃圾收集。 但是,我们仍然提供了一种明确的方式来取消订阅以允许条件处理,如下所示。

image.png

我们重写OnDeactivate,取消订阅就行了。


Custom Result Handling

实现(我不知道咋处理的)跳过这个知识。

image.png


接下来Caliburn有个IWindowManager接口,通过这个接口,任意地方,可以根据vm,弹出对应的View

image.png


然后可视化支持

在xaml上写法

xmlns:vm="clr-namespace:CALIDEMO.ViewModels" d:DataContext="{d:DesignInstance Type=vm:Shell2ViewModel, IsDesignTimeCreatable=True}"


一些补充,下面是我看的一些例子的bootstrapper

  public class Bootstrapper : BootstrapperBase
    {
        private SimpleContainer container;

        public Bootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            container = new SimpleContainer();

            container.Instance(container);

            container
                .Singleton<IWindowManager, WindowManager>()
                .Singleton<IEventAggregator, EventAggregator>();

            container
               .PerRequest<ShellViewModel>()
               .PerRequest<MenuViewModel>()
               .PerRequest<BindingsViewModel>()
               .PerRequest<ActionsViewModel>()
               .PerRequest<CoroutineViewModel>()
               .PerRequest<ExecuteViewModel>()
               .PerRequest<EventAggregationViewModel>()
               .PerRequest<DesignTimeViewModel>()
               .PerRequest<ConductorViewModel>()
               .PerRequest<BubblingViewModel>()
               .PerRequest<NavigationSourceViewModel>()
               .PerRequest<NavigationTargetViewModel>();
        }

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            DisplayRootViewFor<ShellViewModel>();
        }

        protected override object GetInstance(Type service, string key)
        {
            return container.GetInstance(service, key);
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return container.GetAllInstances(service);
        }

        protected override void BuildUp(object instance)
        {
            container.BuildUp(instance);
        }

        protected override void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            e.Handled = true;
            MessageBox.Show(e.Exception.Message, "An error as occurred", MessageBoxButton.OK);
        }
    }

重写了捕捉错误

OnUnhandledException


如果你想系统看看Caliburn的一些例子

就不如看看源码中,Caliburn.Micro-master\samples\features\Features.WPF这个项目吧









这后面的文章我都不知道怎么实践

AY这次我使用MEF方式配合Caliburn.Micro 3.2


DEMO1:bootstrapper的说明

建立完wpf后,建立个MEFBootstrapper.cs

添加引用 System.ComponentModel.Composition

using Caliburn.Micro;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Windows;

namespace CALIDDEMO2
{


    public class MefBootstrapper : BootstrapperBase
    {
        private CompositionContainer container;

        public MefBootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            container = new CompositionContainer(
                new AggregateCatalog(
                    AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
                    )
                );

            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(container);

            container.Compose(batch);
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = container.GetExportedValues<object>(contract);

            if (exports.Any())
                return exports.First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override void BuildUp(object instance)
        {
            container.SatisfyImportsOnce(instance);
        }

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            DisplayRootViewFor<IShell>();
        }
         protected override IEnumerable<Assembly> SelectAssemblies()
        {
            return new[] {
                  Assembly.GetExecutingAssembly()
         };
    }
}

稍微讲解:

框架需要“GetInstance”和“GetAllInstances”。 “BuildUp”可选地用于向框架执行的IResult实例提供属性依赖性。

您可以在应用程序期间随时向其添加程序集,以使它们可供框架使用,但在Bootstrapper中还有一个特殊的位置。 只需覆盖SelectAssemblies

您可以覆盖OnStartup和OnExit,以便在应用程序分别启动或关闭时执行代码,并在任何未由应用程序代码专门处理的异常之后清除OnUnhandledException。 


 Coroutines协同程序

Coroutines是程序组件,它概括子程序以允许多个入口点在某些位置暂停和恢复执行。协同程序非常适合实现更熟悉的程序组件,如协作任务,迭代器,无限列表和管道。

想象一下能够执行一个方法,然后暂停它在某些语句上执行,去做其他事情,然后回来并在你离开的地方继续执行。这种技术在基于任务的编程中非常强大,特别是当这些任务需要异步运行时。例如,假设我们有一个需要异步调用Web服务的ViewModel,然后它需要获取结果,对它做一些工作并异步调用另一个Web服务。最后,它必须在模态对话框中显示结果,并使用另一个异步任务响应用户的对话框选择。使用标准的事件驱动的异步模型来实现这一点并不是一种愉快的体验。但是,这是通过使用协程来完成的简单任务。问题...... C#本身并没有实现协同程序。幸运的是,我们可以(在某种程度上)在迭代器之上构建它们。


在Caliburn.Micro中有两件事需要利用这个功能:首先,在某个类上实现IResult接口,表示你想要执行的任务; 其次,从Action2中产生IResult实例。 让我们更具体一点。 假设我们有一个Silverlight应用程序,我们想要动态下载并显示不属于主程序包的屏幕。 首先,我们可能希望显示“加载”指示符,然后异步下载外部包,接下来隐藏“加载”指示符,最后导航到动态模块内的特定屏幕。 如果您的第一个屏幕想要使用协同程序导航到动态加载的第二个屏幕,那么这就是代码的样子:

using System.Collections.Generic;
using System.ComponentModel.Composition;

[Export(typeof(ScreenOneViewModel))]
public class ScreenOneViewModel
{
    public IEnumerable<IResult> GoForward()
    {
        yield return Loader.Show("Downloading...");
        yield return new LoadCatalog("Caliburn.Micro.Coroutines.External.xap");
        yield return Loader.Hide();
        yield return new ShowScreen("ExternalScreen");
    }
}

首先,请注意Action“GoForward”的返回类型为IEnumerable。 这对于使用协同程序至关重要。 该方法的主体有四个yield语句。 这些产量中的每一个都返回一个IResult实例。 第一个是显示“下载”指示符的结果,第二个是异步下载xap,第三个是隐藏“下载”消息,第四个是显示下载的xap的新屏幕。 在每个yield语句之后,编译器将“暂停”此方法的执行,直到该特定任务完成。 第一,第三和第四个任务是同步的,而第二个任务是异步的。 但yield语法允许您以顺序方式编写所有代码,将原始工作流保留为更具可读性和声明性的结构。 要了解其工作原理,请查看IResult接口:

image.png

这是一个相当简单的实现接口。 只需在“执行”方法中编写代码,并确保在完成后引发“已完成”事件,无论是同步还是异步任务。 由于协同程序出现在Action内部,因此我们为您提供了一个ActionExecutionContext,可用于构建与UI相关的IResult实现。 这允许ViewModel以声明方式声明其控制视图的意图,而无需任何对View的引用或基于交互的单元测试的需要。 这是ActionExecutionContext的样子:

image.png

以下是对所有这些属性的含义的解释:


Message

导致调用此IResult的原始ActionMessage。

Source

触发Action执行的FrameworkElement。

EventArgs

与Action触发器关联的任何事件参数。

Target

实际Action方法所在的类实例。

View

与目标相关联的视图。

Method

MethodInfo指定在Target实例上调用哪个方法。

CanExecute

如果可以调用Action,则返回true的函数,否则返回false。

Key Index

存储/检索可由框架扩展使用的任何其他元数据的位置。

记住这一点,我写了一个naive Loader IResult,它搜索VisualTree,寻找用于显示加载消息的BusyIndicator的第一个实例。 实现如下:

using Caliburn.Micro;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;
using System.Windows;
using Xceed.Wpf.Toolkit;



    public class Loader : IResult
    {
        readonly string message;
        readonly bool hide;

        public Loader(string message)
        {
            this.message = message;
        }

        public Loader(bool hide)
        {
            this.hide = hide;
        }

        public void Execute(CoroutineExecutionContext context)
        {
            var view = context.View as FrameworkElement;
            while (view != null)
            {
                var busyIndicator = view as BusyIndicator;
                if (busyIndicator != null)
                {
                    if (!string.IsNullOrEmpty(message))
                        busyIndicator.BusyContent = message;
                    busyIndicator.IsBusy = !hide;
                    break;
                }

                view = view.Parent as FrameworkElement;
            }

            Completed(this, new ResultCompletionEventArgs());
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

        public static IResult Show(string message = null)
        {
            return new Loader(message);
        }

        public static IResult Hide()
        {
            return new Loader(true);
        }
    }

下面代码我看不懂

  public class ShowScreen : IResult
    {
        readonly Type screenType;
        readonly string name;

        [Import]
        public IShell Shell { get; set; }

        public ShowScreen(string name)
        {
            this.name = name;
        }

        public ShowScreen(Type screenType)
        {
            this.screenType = screenType;
        }

        public void Execute(CoroutineExecutionContext context)
        {
            var screen = !string.IsNullOrEmpty(name)
                ? IoC.Get<object>(name)
                : IoC.GetInstance(screenType, null);

            Shell.ActivateItem(screen);
            Completed(this, new ResultCompletionEventArgs());
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

        public static ShowScreen Of<T>()
        {
            return new ShowScreen(typeof(T));
        }
    }

    public class LoadCatalog : IResult
    {
        public class ResultCompletionEventArgs : EventArgs
        {
            public Exception Error;
            public bool WasCancelled;
        }

        static readonly Dictionary<string, DeploymentCatalog> Catalogs = new Dictionary<string, DeploymentCatalog>();
        readonly string uri;

        [Import]
        public AggregateCatalog Catalog { get; set; }

        public LoadCatalog(string relativeUri)
        {
            uri = relativeUri;
        }

        public void Execute(CoroutineExecutionContext context)
        {
            DeploymentCatalog catalog;

            if (Catalogs.TryGetValue(uri, out catalog))
                Completed(this, new ResultCompletionEventArgs());
            else
            {
                catalog = new DeploymentCatalog(uri);
                catalog.DownloadCompleted += (s, e) =>
                {
                    if (e.Error == null)
                    {
                        Catalogs[uri] = catalog;
                        Catalog.Catalogs.Add(catalog);
                        catalog.Parts
                            .Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
                            .Where(assembly => !AssemblySource.Instance.Contains(assembly))
                            .Apply(x => AssemblySource.Instance.Add(x));
                    }
                    else Loader.Hide().Execute(context);

                    Completed(this, new ResultCompletionEventArgs
                    {
                        Error = e.Error,
                        WasCancelled = false
                    });
                };

                catalog.DownloadAsync();
            }
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
    }

官方文章:https://caliburnmicro.com/documentation/coroutines



Screen类,激活,取消激活,关闭

在Caliburn.Micro中,我们将屏幕激活的概念分解为几个界面:

IActivate  - 表示实施者需要激活。此接口提供Activate方法,IsActive属性和Activated事件,应在激活时引发。

IDeactivate  - 表示实施者需要停用。此接口具有Deactivate方法,该方法除了取消激活之外还采用指示是否关闭屏幕的bool属性。它还有两个事件:AttemptingDeactivation,应该在停用之前引发,Deactivated应该在停用后引发。

IGuardClose  - 表示实施者可能需要取消关闭操作。它有一个方法:CanClose。此方法采用异步模式设计,允许在做出紧密决策时进行复杂的逻辑,例如异步用户交互。调用者将Action传递给CanClose方法。当保护逻辑完成时,实现者应该调用该动作。传递true表示实现者可以关闭,否则为false。

除了这些核心生命周期接口之外。


这一切意味着您可能会从PropertyChangedBase或Screen继承大多数视图模型。一般来说,如果您需要任何激活功能和PropertyChangedBase,则可以使用Screen。 CM的默认Screen实现还具有一些附加功能,可以轻松地挂钩到生命周期的适当部分:


OnInitialize  - 覆盖此方法以添加逻辑,该逻辑应仅在第一次激活屏幕时执行。初始化完成后,IsInitialized将为true。

OnActivate  - 覆盖此方法以添加每次激活屏幕时应执行的逻辑。激活完成后,IsActive将成立。

OnDeactivate  - 覆盖此方法以添加自定义逻辑,应在屏幕停用或关闭时执行该逻辑。如果停用实际上是关闭,则bool属性将指示。停用完成后,IsActive将为false。

CanClose  - 默认实现始终允许关闭。重写此方法以添加自定义保护逻辑。

OnViewLoaded  - 由于Screen实现了IViewAware,它会以此为契机,让您知道何时触发了View的Loaded事件。如果您遵循SupervisingController或PassiveView样式并且需要使用视图,请使用此选项。这也是放置视图模型逻辑的地方,即使您可能不直接使用视图,也可能依赖于视图的存在。

TryClose  - 调用此方法关闭屏幕。如果屏幕由导体控制,它会要求导体启动屏幕的关闭过程。如果屏幕不是由导体控制,而是独立存在(可能是因为它是使用WindowManager显示的),则此方法会尝试关闭视图。在这两种情况下,都会调用CanClose逻辑,如果允许,将使用值true调用OnDeactivate。

所以,只是为了重新迭代:如果你需要一个生命周期,继承自Screen;否则继承自PropertyChangedBase。


Conductors

ActivateItem  - 调用此方法以激活特定项目。如果指挥使用“屏幕收集”,它还会将其添加到当前执行的项目中。

DeactivateItem  - 调用此方法以停用特定项目。第二个参数指示是否也应关闭该项目。如果是这样的话,如果指挥使用“屏幕收集”,它也会将其从当前进行的项目中删除。

ActivationProcessed  - 当导体处理了项目的激活时触发。它表示激活是否成功。

GetChildren-调用此方法可返回指挥跟踪的所有项目的列表。如果导体使用“屏幕集合”,则返回所有“屏幕”,否则返回ActiveItem。 (来自IParent接口)

INotifyPropertyChangedEx  - 此接口由IConductor组成。

我们还有一个名为IConductActiveItem的接口,它组成IConductor和IHaveActiveItem以添加以下成员:


ActiveItem  - 指示导体当前跟踪哪个项目为活动的属性。

您可能已经注意到CM的IConductor接口使用术语“item”而不是“screen”,而我将术语“screen collection”放在引号中。这样做的原因是CM的导体实现不需要执行项目来实现IScreen或任何特定接口。进行的项目可以是POCO。而不是强制使用IScreen,每个导体实现都是通用的,没有对类型的限制。当导体被要求激活/停用/关闭/等每个正在进行的项目时,它会单独检查它们以下的细粒度接口:IActivate,IDeactive,IGuardClose和IChild。在实践中,我通常从Screen继承已执行的项目,但这使您可以灵活地使用自己的基类,或者仅为每个类关注的生命周期事件实现接口。你甚至可以让一个指挥跟踪异构项目,其中一些继承自Screen和其他实现特定接口的项目,或者根本没有。



screens和conductors我感觉就是子页面和 主页面的关系


后面文章看不下去了,用到哪在看吧






















推荐您阅读更多有关于“WPFWPF4.5,”的文章

猜你喜欢

额 本文暂时没人评论 来添加一个吧

发表评论

必填

选填

选填

必填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

  查看权限

抖音:wpfui 工作wpf,目前主maui

招聘合肥一枚WPF工程师,跟我一个开发组,10-15K,欢迎打扰

目前在合肥市企迈科技就职

AYUI8全源码 Github地址:前往获取

杨洋(AaronYang简称AY,安徽六安人)AY唯一QQ:875556003和AY交流

高中学历,2010年开始web开发,2015年1月17日开始学习WPF

声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费

不是从我处购买的ayui7源码,我不提供任何技术服务,如果你举报从哪里买的,我可以帮你转正为我的客户,并送demo

查看捐赠

AYUI7.X MVC教程 更新如下:

第一课 第二课 程序加密教程

标签列表