web-dev-qa-db-ja.com

MVVM Light 5.0:ナビゲーションサービスの使用方法

MVVM Light noteの最新リリース では、MVVM Lightが「ナビゲーションサービス」を提供することが示されています。

しかし、私と私の友人であるグーグルは、それを使用する方法を見つけることができません。

ServiceLocatorにINavigationServiceを要求できることがわかりますので、別のページに移動するように要求する方法はわかりますが、

  1. 「ページ」に特定のゾーンを予約する予定の新しいウィンドウを作成しましたが、これを指定するにはどうすればよいですか?
  2. 利用可能なすべてのページを指定するにはどうすればよいですか?何か電話が必要ですか?
  3. INavigationServiceに与えられるパラメーターの形式は何ですか

このライブラリの公式ドキュメントはありますか?現在、うまくコード化されており、正常に動作していることがわかりますが、使用方法を検索するときに、方法を示すドキュメント/サンプルが見つかりません。彼のブログにはいくつかのエントリがあります。これは非常にイライラします。私が見つけた唯一のドキュメントは this です、私はPluralsightにあまり精通していませんが、毎月のサブスクリプションを取ることが必須のようです(個人として、私のアプリケーションを作成しようとしています)自由時間、できません)。

17
J4N

はい、MvvmLightはその最後のバージョン NavigationServiceを導入しましたが、Wpf (WP、Metroapps、..でImplemented NavigationServiceを使用できます)に関する実装は提供しませんでしたが、残念ながらWpf、あなたは自分でそれを実装する必要があります、ここで私は現在それをどのようにやっているのですか( credit

firstMvvmLightINavigationServiceを実装するナビゲーションインターフェイスを作成します

_public interface IFrameNavigationService:INavigationService
    {
        object Parameter { get; }  
    }
_

Parameterは、ViewModelsの間でオブジェクトを渡すために使用され、INavigationServiceは_GalaSoft.MvvmLight.Views_名前空間の一部です

そのようにそのインターフェースを実装します

_class FrameNavigationService : IFrameNavigationService,INotifyPropertyChanged
    {
        #region Fields
        private readonly Dictionary<string, Uri> _pagesByKey;
        private readonly List<string> _historic;
        private string _currentPageKey;  
        #endregion
        #region Properties                                              
        public string CurrentPageKey
        {
            get
            {
                return _currentPageKey;
            }

            private  set
            {
                if (_currentPageKey == value)
                {
                    return;
                }

                _currentPageKey = value;
                OnPropertyChanged("CurrentPageKey");
            }
        }
        public object Parameter { get; private set; }
        #endregion
        #region Ctors and Methods
        public FrameNavigationService()
        {
            _pagesByKey = new Dictionary<string, Uri>();
            _historic = new List<string>();
        }                
        public void GoBack()
        {
            if (_historic.Count > 1)
            {
                _historic.RemoveAt(_historic.Count - 1);
                NavigateTo(_historic.Last(), null);
            }
        }
        public void NavigateTo(string pageKey)
        {
            NavigateTo(pageKey, null);
        }

        public virtual void NavigateTo(string pageKey, object parameter)
        {
            lock (_pagesByKey)
            {
                if (!_pagesByKey.ContainsKey(pageKey))
                {
                    throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey");
                }

                var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame;

                if (frame != null)
                {
                    frame.Source = _pagesByKey[pageKey];
                }
                Parameter = parameter;
                _historic.Add(pageKey);
                CurrentPageKey = pageKey;
            }
        }

        public void Configure(string key, Uri pageType)
        {
            lock (_pagesByKey)
            {
                if (_pagesByKey.ContainsKey(key))
                {
                    _pagesByKey[key] = pageType;
                }
                else
                {
                    _pagesByKey.Add(key, pageType);
                }
            }
        }

        private static FrameworkElement GetDescendantFromName(DependencyObject parent, string name)
        {
            var count = VisualTreeHelper.GetChildrenCount(parent);

            if (count < 1)
            {
                return null;
            }

            for (var i = 0; i < count; i++)
            {
                var frameworkElement = VisualTreeHelper.GetChild(parent, i) as FrameworkElement;
                if (frameworkElement != null)
                {
                    if (frameworkElement.Name == name)
                    {
                        return frameworkElement;
                    }

                    frameworkElement = GetDescendantFromName(frameworkElement, name);
                    if (frameworkElement != null)
                    {
                        return frameworkElement;
                    }
                }
            }
            return null;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
_

上記のコードのMainFrameは、ページ間を移動するために使用されるFrameで定義された単純なXamlコントロールのx:Nameです(ニーズに基づいてカスタマイズします)

Secondviewmodellocatorで、ナビゲーションサービス(SetupNavigation())を初期化して、ビューモデルで使用できるようにします。

_static ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            SetupNavigation();

            SimpleIoc.Default.Register<MainViewModel>();
            SimpleIoc.Default.Register<LoginViewModel>();
            SimpleIoc.Default.Register<NoteViewModel>();            
        }
 private static void SetupNavigation()
        {
            var navigationService = new FrameNavigationService();
            navigationService.Configure("LoginView", new Uri("../Views/LoginView.xaml",UriKind.Relative));
            navigationService.Configure("Notes", new Uri("../Views/NotesView.xaml", UriKind.Relative));            

            SimpleIoc.Default.Register<IFrameNavigationService>(() => navigationService);
        }
_

3番目:最後に、たとえばサービスを使用します

_ public LoginViewModel(IFrameNavigationService navigationService)
        {
            _navigationService = navigationService; 
...
_navigationService.NavigateTo("Notes",data);
..
_

[〜#〜] edit [〜#〜]

明示的なサンプルは、この repo にあります。

32
SamTheDev

私はむしろViewModelFirst Navigation Serviceに行きたいです。

私の考えでは、View/ViewModelの新しいペアを作成するときに使用するコードの方が簡単で、追加するコードの量も少なくなります。

このためには、いくつかのものが必要です

最初に、両方の方法でナビゲーションを処理するいくつかのメソッドを持つNavigableViewModel抽象クラス。すべてのviewModelはこのクラスから継承します:

NavigableViewModel.cs

public abstract class NavigableViewModel : ViewModelBase
{
    public abstract void OnNavigatedTo(object parameter = null);
    public abstract void OnNavigatingTo(object parameter = null);
}

ナビゲーションが発生するフレームを含むメインウィンドウは、デフォルトのナビゲーションコントロールをNavigationUIVisibility = "Hidden"で非表示にすることを考えてください。

MainWindow.xaml

<Window x:Class="YourProject.Views.MainWindow" 
        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"
        xmlns:local="clr-namespace:SS3DViewModelFirstMvvmLightProject"
        mc:Ignorable="d"
        DataContext="{Binding Main, Source={StaticResource Locator}}"
        Title="MainWindow" Height="350" Width="525">
        <-- Just remeber to replace x:Class="YourProject.Views.MainWindow" with your actual project path-->
        <Frame  x:Name="Frame"  NavigationUIVisibility="Hidden">

        </Frame>
</Window>

そして、ViewModelsの変更を処理するコードビハインド(各ページにviewModelを通知するように許可します):

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        ((MainViewModel)this.DataContext).ShowFirstView(); // we need to have our view loaded to start navigating
        Frame.LoadCompleted += (s, e) => UpdateFrameDataContext();
        Frame.DataContextChanged += (s, e) => UpdateFrameDataContext();
    }

    private void UpdateFrameDataContext()
    {
        Page view = (Page)Frame.Content;
        if (view != null)
        {
            view.DataContext = Frame.DataContext;
        }
    }
}

MainViewModelで、最初のViewModel(ここではLoginViewModel)に移動するためのこの小さなメソッド:

MainViewModel.cs

public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
        {

        }

        public void ShowFirstView()
        {
            ServiceLocator.Current.GetInstance<ViewModelFirstNavigationService>().NavigateTo<LoginViewModel>();
            //To navigate wherever you want you just need to call this method, replacing LoginViewModel with YourViewModel
        }
    }

このServiceLocator呼び出しを機能させるには、ViewModelLocatorにいくつかのことをうまく追加する必要があります。

ViewModelLocator.cs

 public class ViewModelLocator
    {
        public ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
            SimpleIoc.Default.Register<MainViewModel>();
            ViewModelFirstNavigationService navService = new ViewModelFirstNavigationService(Main);
            SimpleIoc.Default.Register<LoginViewModel>();
            navService.AddNavigableElement(SimpleIoc.Default.GetInstance<LoginViewModel>);
            // so whenever you want to add a new navigabel View Model just add these lines here
            // SimpleIoc.Default.Register<YourViewModel>();
            // navService.AddNavigableElement(SimpleIoc.Default.GetInstance<YourViewModel>);
            SimpleIoc.Default.Register<ViewModelFirstNavigationService>(() => navService);
        }

        public MainViewModel Main
        {
            get
            {
                return ServiceLocator.Current.GetInstance<MainViewModel>();
            }
        }

        public static void Cleanup()
        {
        }
    }

そして今、あなたはすべてのものを準備しているので、システムのコアであるナビゲーションサービスを追加しましょう(それはトリッキーな部分です):

ViewModelFirstNavigationService

public class ViewModelFirstNavigationService
    {
        private Dictionary<Type, Uri> _registeredViews;
        private Dictionary<Type, Func<NavigableViewModel>> _registeredViewModels;
        private List<string> _allXamlPages;
        private MainViewModel _mainContainerViewModel;
        public NavigableViewModel CurrentViewModel;

        public ViewModelFirstNavigationService(MainViewModel mainContainerViewModel)
        {
            _mainContainerViewModel = mainContainerViewModel;
            _registeredViews = new Dictionary<Type, Uri>();
            _registeredViewModels = new Dictionary<Type, Func<NavigableViewModel>>();
            _allXamlPages = GetAllXamlPages();
        }

        private List<string> GetAllXamlPages()
        {
            // this part is a bit tricky. We use it to find all xaml pages in the current project.
            // so you need to be sure that all your pages you want to use with your viewmodles need to end with page.xaml
            // Example : LoginPage.xaml will work fine. Parameters.xaml won't.
            System.Reflection.Assembly viewModelFirstProjectAssembly;
            viewModelFirstProjectAssembly = System.Reflection.Assembly.GetExecutingAssembly();
            var stream = viewModelFirstProjectAssembly.GetManifestResourceStream(viewModelFirstProjectAssembly.GetName().Name + ".g.resources");
            var resourceReader = new ResourceReader(stream);
            List<string> pages = new List<string>();
            foreach (DictionaryEntry resource in resourceReader)
            {
                Console.WriteLine(resource.Key);
                string s = resource.Key.ToString();
                if (s.Contains("page.baml"))
                {
                    pages.Add(s.Remove(s.IndexOf(".baml")));
                }
            }
            return pages;
        }

        private Type ResolveViewModelTypeFromSingletonGetterFunc<T>(Func<T> viewModelSingletonGetterFunc)
        {
            MethodInfo methodInfo = viewModelSingletonGetterFunc.Method;
            return methodInfo.ReturnParameter.ParameterType;
        }

        private Uri ResolvePageUriFromViewModelType(Type viewModelType)
        {
            string pageName = String.Empty;
            int index = viewModelType.Name.IndexOf("ViewModel");
            pageName = viewModelType.Name.Remove(index);
            string pagePath = String.Format("{0}.xaml", _allXamlPages.Where(page => page.Contains(pageName.ToLower())).FirstOrDefault());
            string cleanedPath = pagePath.Remove(0, "views/".Length); //obviously for this to work you need to have your views in a Views folder at the root of the project. But you are alowed yo reat sub folders in it
            return new Uri(cleanedPath, UriKind.Relative);
        }


        public void AddNavigableElement(Func<NavigableViewModel> viewModelSingletonGetter)
        {
            //Where the magic happens !
            //If your are wondering why a Func, it's because we want our viewmodels to be instantiated only when we need them via IOC.
            //First we ge the type of our viewmodel to register for the Func.
            Type vmType = ResolveViewModelTypeFromSingletonGetterFunc(viewModelSingletonGetter);
            Uri uriPage = ResolvePageUriFromViewModelType(vmType);
            _registeredViews.Add(vmType, uriPage);
            _registeredViewModels.Add(vmType, viewModelSingletonGetter);
        }

        public void NavigateTo<GenericNavigableViewModelType>(object parameter = null)
        {
            Type key = typeof(GenericNavigableViewModelType);
            NavigateTo(key, parameter);
        }

        public void NavigateTo(Type key, object parameter = null)
        {
            CurrentViewModel?.OnNavigatingTo(parameter);
            CurrentViewModel = _registeredViewModels[key].Invoke();
            Uri uri = _registeredViews[key];
            ((MainWindow)Application.Current.MainWindow).Frame.Source = uri;
            ((MainWindow)Application.Current.MainWindow).Frame.DataContext = CurrentViewModel;
            CurrentViewModel.OnNavigatedTo(parameter);
        }

    }

そして今、あなたはすべてが機能しています!ほら! LoginViewModelの例(黒の四角に美しいhelloworldのみが含まれる)を示してみましょう。

LoginPage.xaml

<Page x:Class="YourProject.Views.LoginPage"
      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:SS3DViewModelFirstMvvmLightProject.Views"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      Title="LoginPage">
    <Grid Background="Gray">
        <Label Content="{Binding HelloWorld}" Foreground="White" Background="Black" Width="150" Height="150"></Label>
    </Grid>
</Page>

そしてそのビューモデル:

LoginViewModel.cs

public class LoginViewModel : NavigableViewModel
    {
        private string _helloWorld;
        public string HelloWorld
        {
            get
            {
                return _helloWorld;
            }
            set
            {
                _helloWorld = value;
                RaisePropertyChanged(() => HelloWorld);
            }
        }

        public LoginViewModel()
        {
            HelloWorld = "Hello World";
        }

        public override void OnNavigatedTo(object parameter = null)
        {
          // whatever you want to happen when you enter this page/viewModel
        }

        public override void OnNavigatingTo(object parameter = null)
        {
            // whatever you want to happen when you leave this page/viewmodel
        }
    }

最初にいくつかのコードが必要であることを認めます。しかし、すべてが機能していると、非常に使いやすいシステムになります。

いくつかのviewModelに移動したいですか? myNavigationService.NavigateTo(someParam);を使用します。

新しいペアのView/ViewModelを追加しますか? viewModelをいくつかのIOCコンテナに追加するだけです(私のプロジェクトでは、独自のIOCを使用しています。必要に応じてビューモデルをアンロードして、優れたナビゲーションスタックを提供できます)。 。

2
yan yankelevich

ナビゲーション機能がmvvm lightで利用可能かどうかわかりません。 contentControlバインディングで実装しました:

       <xcad:LayoutDocumentPane>
           <xcad:LayoutDocument x:Name="DetailDoc" CanClose="False">
                 <ContentControl Content="{Binding  DisplayedDetailViewModel}"/>
           </xcad:LayoutDocument>
       </xcad:LayoutDocumentPane>

そして、viewmodelプロパティ。 mvvm light ViewModelBaseクラスを継承します。

    public ViewModelBase DisplayedDetailViewModel
    {
        get
        {
            return displayedDetailViewModel;
        }
        set
        {
            if (displayedDetailViewModel == value)
            {
                return;
            }
            displayedDetailViewModel = value;
            RaisePropertyChanged("DisplayedDetailViewModel");
        }

コンテンツコントロールがどのユーザーコントロールを使用する必要があるかを知るには、app.xamlでDataTemplatesを定義します。

 <Application.Resources>
    <ResourceDictionary>
        <!--
        We define the data templates here so we can apply them across the
        entire application.

        The data template just says that if our data type is of a particular
        view-model type, then render the appropriate view.  The framework
        takes care of this dynamically.  Note that the DataContext for
        the underlying view is already set at this point, so the
        view (UserControl), doesn't need to have it's DataContext set
        directly.
    -->
        <DataTemplate DataType="{x:Type viewModel:LoggerViewModel}">
            <views:LogView />
        </DataTemplate>

LogViewはUserControlです。 LoggerViewModelをDisplayedDetailViewModelに割り当てるだけで、フレームワークが作業を行います。

2