Caliburn.Micro + MEF +现代UI:IContent事件 [英] Caliburn.Micro + MEF + Modern UI: IContent events

查看:127
本文介绍了Caliburn.Micro + MEF +现代UI:IContent事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经开始使用Caliburn.Micro和Modern UI( https://mui.codeplex.com ),并且在使IContent的导航事件在我的视图模型上触发时遇到了一些困难.我已经将两者连接起来,可以通过以下方式相互配合:

I've started a project using Caliburn.Micro and Modern UI (https://mui.codeplex.com) and am having some difficulty getting the navigation events of IContent to fire on my view model. I've already got the two hooked up to work with each other with the following:

CM Bootstrapper:

public class CMBootstrapper : Bootstrapper<IShell> {
    private CompositionContainer container;
    private DirectoryCatalog catalog;

    public CMBootstrapper() { }

    protected override void Configure() {
        catalog = new DirectoryCatalog(".", "*.*");
        container = new CompositionContainer(catalog);

        var compositionBatch = new CompositionBatch();
        compositionBatch.AddExportedValue<IWindowManager>(new WindowManager());
        compositionBatch.AddExportedValue<IEventAggregator>(new EventAggregator());
        compositionBatch.AddExportedValue(container);
        container.Compose(compositionBatch);
    }

    protected override IEnumerable<Assembly> SelectAssemblies() {
        List<Assembly> assemblies = new List<Assembly>();
        assemblies.Add(Assembly.GetExecutingAssembly());
        return assemblies;
    }

    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.Count() > 0)
            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);
    }
}

现代UI内容加载器:

[Export]
public class MuiContentLoader : DefaultContentLoader {
    protected override object LoadContent(Uri uri) {
        var content = base.LoadContent(uri);
        if (content == null)
            return null;

        // Locate VM
        var viewModel = ViewModelLocator.LocateForView(content);

        if (viewModel == null)
            return content;

        // Bind VM
        if (content is DependencyObject)
            ViewModelBinder.Bind(viewModel, content as DependencyObject, null);

        return content;
    }
}

MuiView.xaml(Shell)

<mui:ModernWindow x:Class="XMOperations.Views.MuiView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mui="http://firstfloorsoftware.com/ModernUI"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         ContentLoader="{StaticResource ModernContentLoader}"
         d:DesignHeight="300" d:DesignWidth="300">

<mui:ModernWindow.TitleLinks>
    <mui:Link DisplayName="Settings" Source="/Views/SettingsView.xaml" />
</mui:ModernWindow.TitleLinks>

<mui:ModernWindow.MenuLinkGroups>
    <mui:LinkGroupCollection>
        <mui:LinkGroup GroupName="Hello" DisplayName="Hello">
            <mui:LinkGroup.Links>
                <mui:Link Source="/Views/ChildView.xaml" DisplayName="Click me"></mui:Link>
            </mui:LinkGroup.Links>
        </mui:LinkGroup>
    </mui:LinkGroupCollection>
</mui:ModernWindow.MenuLinkGroups>

MuiViewModel

[Export(typeof(IShell))]
public class MuiViewModel : Conductor<IScreen>.Collection.OneActive, IShell {

}

每个子视图都被导出并实现IContent,如下所示:

Each of the child views are exported and implement IContent like so:

[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
public class SettingsViewModel : Screen, IContent {

    #region IContent Implementation

    public void OnFragmentNavigation(FragmentNavigationEventArgs e) {
        Console.WriteLine("SettingsViewModel.OnFragmentNavigation");
    }

    public void OnNavigatedFrom(NavigationEventArgs e) {
        Console.WriteLine("SettingsViewModel.OnNavigatedFrom");
    }

    public void OnNavigatedTo(NavigationEventArgs e) {
        Console.WriteLine("SettingsViewModel.OnNavigatedTo");
    }

    public void OnNavigatingFrom(NavigatingCancelEventArgs e) {
        Console.WriteLine("SettingsViewModel.OnNavigatingFrom");
    }

    #endregion
}

但是这些都没有开除.经过一些调试后,我发现ModernFrame正在检查(SettingsView as IContent)是否有事件,因为它们只是普通的UserControl,所以没有事件.因此,我创建了一个自定义UserControl类,试图将事件传递给ViewModel:

But none of those were firing. After some debugging I found that ModernFrame was checking (SettingsView as IContent) for the events, which wouldn't have them because it was just a plain UserControl. So I created a custom UserControl class in an attempt to pass the events along to the ViewModel:

MuiContentControl

public delegate void FragmentNavigationEventHandler(object sender, FragmentNavigationEventArgs e);
public delegate void NavigatedFromEventHandler(object sender, NavigationEventArgs e);
public delegate void NavigatedToEventHandler(object sender, NavigationEventArgs e);
public delegate void NavigatingFromEventHandler(object sender, NavigatingCancelEventArgs e);

public class MuiContentControl : UserControl, IContent {
    public event FragmentNavigationEventHandler FragmentNavigation;
    public event NavigatedFromEventHandler NavigatedFrom;
    public event NavigatedToEventHandler NavigatedTo;
    public event NavigatingFromEventHandler NavigatingFrom;

    public MuiContentControl() : base() {

    }

    public void OnFragmentNavigation(FragmentNavigationEventArgs e) {
        if(FragmentNavigation != null)
            FragmentNavigation(this, e);
    }

    public void OnNavigatedFrom(NavigationEventArgs e) {
        if (NavigatedFrom != null)
            NavigatedFrom(this, e);
    }

    public void OnNavigatedTo(NavigationEventArgs e) {
        if(NavigatedTo != null)
            NavigatedTo(this, e);
    }

    public void OnNavigatingFrom(NavigatingCancelEventArgs e) {
        if(NavigatingFrom != null)
            NavigatingFrom(this, e);
    }
}

然后我修改视图以使用Message.Attach监听事件:

Then I modified the views to listen for the events with Message.Attach:

SettingsView

<local:MuiContentControl x:Class="XMOperations.Views.SettingsView"
         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:mui="http://firstfloorsoftware.com/ModernUI" 
         xmlns:cal="http://www.caliburnproject.org"
         xmlns:local="clr-namespace:XMOperations"
         cal:Message.Attach="[Event FragmentNavigation] = [Action OnFragmentNavigation($source, $eventArgs)];
                             [Event NavigatedFrom] = [Action OnNavigatedFrom($source, $eventArgs)];
                             [Event NavigatedTo] = [Action OnNavigatedTo($source, $eventArgs)];
                             [Event NavigatingFrom] = [Action OnNavigatingFrom($source, $eventArgs)]"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid Style="{StaticResource ContentRoot}">
    <mui:ModernTab SelectedSource="/Views/Settings/AppearanceView.xaml" Layout="List" ContentLoader="{StaticResource ModernContentLoader}">
        <mui:ModernTab.Links>
            <mui:Link DisplayName="Appearance" Source="/Views/Settings/AppearanceView.xaml" />
        </mui:ModernTab.Links>
    </mui:ModernTab>
</Grid>

唯一不会触发的事件是NavigatedTo,因此我相信直到分派该事件后才会应用Message.Attach.我可能以一种非常错误的方式这样做,并且愿意进行大规模的重建.

The only event that doesn't fire is NavigatedTo so I believe that Message.Attach is not being applied until after the event is dispatched. I am probably doing this a very wrong way and am open to massive reconstruction.

推荐答案

好吧,这最终并没有那么糟糕-它确实使尝试将事件传递给VM的工作变得更加轻松

Ok this wasn't so bad in the end - it certainly makes life a bit easier in trying to get the events to be passed across to the VM

我为ModernWindow控件模板中存在的ModernFrame控件创建了导体

I created a conductor for the ModernFrame control that exists in the ModernWindow controls template

您需要在ModernWindow的VM的OnViewLoaded事件中创建导体的实例,因为这似乎是最好的位置(即,尚未发生导航,但控件已完全加载并解决了它是模板)

You need to create an instance of the conductor in the OnViewLoaded event of the VM for your ModernWindow as this seems to be the best place (i.e. no navigation has happened yet but the control has fully loaded and has resolved it's template)

// Example viewmodel:

public class ModernWindowViewModel : Conductor<IScreen>.Collection.OneActive
{
    protected override void OnViewLoaded(object view)
    {
        base.OnViewLoaded(view);

        // Instantiate a new navigation conductor for this window
        new FrameNavigationConductor(this);
    }
}

导体代码如下:

public class FrameNavigationConductor
{
    #region Properties

    // Keep a ref to the frame
    private readonly ModernFrame _frame;

    // Keep this to handle NavigatingFrom and NavigatedFrom events as this functionality
    // is usually wrapped in the frame control and it doesn't pass the 'old content' in the
    // event args
    private IContent _navigatingFrom;

    #endregion

    public FrameNavigationConductor(IViewAware modernWindowViewModel)
    {
        // Find the frame by looking in the control template of the window
        _frame = FindFrame(modernWindowViewModel);

        if (_frame != null)
        {
            // Wire up the events
            _frame.FragmentNavigation += frame_FragmentNavigation;
            _frame.Navigated += frame_Navigated;
            _frame.Navigating += frame_Navigating;
        }
    }

    #region Navigation Events

    void frame_Navigating(object sender, NavigatingCancelEventArgs e)
    {
        var content = GetIContent(_frame.Content);

        if (content != null)
        {
            _navigatingFrom = content;
            _navigatingFrom.OnNavigatingFrom(e);
        }
        else
            _navigatingFrom = null;
    }

    void frame_Navigated(object sender, NavigationEventArgs e)
    {
        var content = GetIContent(_frame.Content);

        if (content != null)
            content.OnNavigatedTo(e);

        if (_navigatingFrom != null)
            _navigatingFrom.OnNavigatedFrom(e);

    }

    void frame_FragmentNavigation(object sender, FragmentNavigationEventArgs e)
    {
        var content = GetIContent(_frame.Content);

        if (content != null)
            content.OnFragmentNavigation(e);

    }

    #endregion

    #region Helpers

    ModernFrame FindFrame(IViewAware viewAware)
    {
        // Get the view for the window
        var view = viewAware.GetView() as Control;

        if (view != null)
        {
            // Find the frame by name in the template
            var frame = view.Template.FindName("ContentFrame", view) as ModernFrame;

            if (frame != null)
            {
                return frame;
            }
        }

        return null;
    }

    private IContent GetIContent(object source)
    {
        // Try to cast the datacontext of the attached viewmodel to IContent
        var fe = (source as FrameworkElement);

        if (fe != null)
        {
            var content = fe.DataContext as IContent;

            if (content != null)
                return content;
        }

        return null;
    }

    #endregion
}

现在,添加了IContent接口的任何视图都将在导航发生时自动获取框架调用的方法

Now any view which you add the IContent interface to will automatically get its methods called by the frame whenever navigation occurs

public class TestViewModel : Conductor<IScreen>, IContent
{
    public void OnFragmentNavigation(FragmentNavigationEventArgs e)
    {
        // Do stuff
    }

    public void OnNavigatedFrom(NavigationEventArgs e)
    {
        // Do stuff
    }

    public void OnNavigatedTo(NavigationEventArgs e)
    {
        // Do stuff
    }

    public void OnNavigatingFrom(NavigatingCancelEventArgs e)
    {
        // Do stuff
    }
}

我已经测试过,并且可以处理IContent上出现的所有4个导航事件-由于它通过了​​EventArgs,因此您可以直接从VM取消导航事件,也可以执行通常在视图中执行的任何操作唯一的情况

I've tested and this works with all 4 navigation events that appear on IContent - since it passes through the EventArgs you can cancel the navigation event directly from the VM or do whatever you would normally do in a view only scenario

我认为这可能是我想出的最轻松的方法-实际上,在窗口中一行代码并在VM上实现接口,您就可以完成了:)

I think this is probably the most pain-free way I could come up with - literally one line of code in the window and implement the interface on the VM and you are done :)

我可能要添加的唯一一件事是在将导体添加到窗口中时抛出异常或调试日志通知,以防万一由于某种原因它找不到框架(也许框架的名称可能会改变)在m:ui的更高版本中)

The only thing I'd probably add is some exception throwing or maybe debug log notification when adding the conductor to a window in case it, for some reason, couldn't find the frame (maybe the name of the frame could change in a later release of m:ui)

这篇关于Caliburn.Micro + MEF +现代UI:IContent事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆