4

Caliburn.Micro と Modern UI ( https://mui.codeplex.com ) を使用してプロジェクトを開始しましたが、ビュー モデルで IContent のナビゲーション イベントを発生させるのに苦労しています。私はすでに2つを接続して、次のように相互に連携させています。

CM ブートストラップ:

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 (シェル)

<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 が実装されます。

[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をチェックしていることがわかった. そこで、ViewModel にイベントを渡すためにカスタム UserControl クラスを作成しました。(SettingsView as IContent)UserControl

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 でイベントをリッスンするようにビューを変更しました。

設定ビュー

<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 は適用されないと思います。私はおそらくこれを非常に間違った方法で行っており、大規模な再構築を受け入れています。

4

2 に答える 2

9

最終的には、これはそれほど悪くはありませんでした。イベントを VM に渡そうとする作業が少し楽になることは確かです。

コントロール テンプレートModernFrameに存在するコントロールのコンダクターを作成しましたModernWindow

OnViewLoadedこれが最適な場所と思われるため、VMのイベントでコンダクターのインスタンスを作成する必要がありますModernWindow(つまり、ナビゲーションはまだ行われていませんが、コントロールは完全に読み込まれ、テンプレートが解決されています)。

// 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ナビゲーションが発生するたびにフレームによって呼び出されるメソッドを自動的に取得します

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
    }
}

私がテストしたところ、これは表示される 4 つのナビゲーション イベントすべてで機能します。IContentこれは通過するためEventArgs、VM から直接ナビゲーション イベントをキャンセルしたり、ビューのみのシナリオで通常行うことを何でも実行したりできます。

これはおそらく私が思いつくことができる最も簡単な方法だと思います-文字通り、ウィンドウに1行のコードを入力し、VMにインターフェースを実装すれば完了です:)

編集:

おそらく追加する唯一のことは、何らかの理由でフレームが見つからなかった場合に備えて、コンダクターをウィンドウに追加するときに例外をスローするか、ログ通知をデバッグすることです (フレームの名前は後で変更される可能性があります)。 m:ui のリリース)

于 2013-06-27T09:24:50.140 に答える
4

IContent ビューで次のことを行い、ViewModels で IContent を実装しました。

   public void OnFragmentNavigation(FirstFloor.ModernUI.Windows.Navigation.FragmentNavigationEventArgs e)
    {
        if (this.DataContext != null)
        {
            var viewModel = this.DataContext as IContent;
            if (viewModel != null)
            {
                viewModel.OnFragmentNavigation(e);
            }
        }
    }

    public void OnNavigatedFrom(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e)
    {
        if (this.DataContext != null)
        {
            var viewModel = this.DataContext as IContent;
            if (viewModel != null)
            {
                viewModel.OnNavigatedFrom(e);
            }
        }
    }

    public void OnNavigatedTo(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e)
    {
        if (this.DataContext != null)
        {
            var viewModel = this.DataContext as IContent;
            if (viewModel != null)
            {
                viewModel.OnNavigatedTo(e);
            }
        }
    }

    public void OnNavigatingFrom(FirstFloor.ModernUI.Windows.Navigation.NavigatingCancelEventArgs e)
    {
        if (this.DataContext != null)
        {
            var viewModel = this.DataContext as IContent;
            if (viewModel != null)
            {
                viewModel.OnNavigatingFrom(e);
            }

        }
    }
于 2013-11-07T16:05:36.510 に答える