MVVM Light 5.0: jak korzystać z usługi nawigacji

W najnowszej wersji MVVM Light note , wskazano, że MVVM Light zapewnia teraz "usługę nawigacyjną".

Ale ja i mój przyjaciel google nie są w stanie znaleźć, jak z niego korzystać.

Widzę, że mogę poprosić INavigationService do ServiceLocator, więc widzę, jak Mogę poprosić, aby przejść do innej strony, ale:

  1. utworzyłem Nowy windows, w którym spodziewam się zarezerwować określoną strefę dla "strony", Jak to określić?
  2. Jak określić wszystkie Dostępne strony? Czy jest coś, do czego powinienem zadzwonić?
  3. jaki byłby format parametrów podanych INavigationService

Czy jest jakaś oficjalna dokumentacja dla tej biblioteki? Ponieważ obecnie uważam, że jest ładnie zakodowany i działa dobrze, ale kiedy muszę szukać, jak z niego korzystać, nigdy nie znajduję dokumentacji / próbki pokazującej, jak to zrobić, z wyjątkiem jego bloga, który ma jakiś wpis. To bardzo frustrujące. Jedyna dokumentacja jaką znalazłem to Ta , nie jestem zbyt zaznajomiony z Pluralsight, ale wydaje się, że jest to obowiązkowe, aby wziąć miesięczny abonament (co jako osoba fizyczna, która próbuje złożyć wniosek w wolnym czasie, nie jest możliwe).

Author: J4N, 2015-03-10

3 answers

Tak, MvvmLight wprowadzili NavigationService w swojej ostatniej wersji , ale nie zaproponowali żadnej implementacji dotyczącej Wpf (możesz użyć zaimplementowanego NavigationService W WP, Metroapps, ..) ale niestety nie Wpf, trzeba to wdrożyć przez siebie, oto jak obecnie to robię (kredyt)

Najpierw stwórz interfejs nawigacyjny, który implementuje MvvmLight INavigationService

public interface IFrameNavigationService:INavigationService
    {
        object Parameter { get; }  
    }

Parameter jest używany do przekazywania obiektów pomiędzy ViewModels, a INavigationService jest część przestrzeni nazw GalaSoft.MvvmLight.Views

Następnie zaimplementuj ten interfejs w taki sposób

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 w powyższym kodzie jest x: Nazwa prostej kontrolki Frame zdefiniowanej w Xaml używanej do nawigacji między stronami (Dostosuj w zależności od potrzeb)

Drugi: W viewmodellocator, init your navigation service (SetupNavigation()), więc można go używać w swoich viewmodels:

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

Po Trzecie: ostatecznie skorzystaj z usługi, na przykład

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

EDIT

Wyraźną próbkę można znaleźć w tym repo .

 26
Author: Elhamer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-04 08:01:59

Wolałbym skorzystać z usługi nawigacjiViewModelFirst .

Moim zdaniem jest to łatwiejsze w użyciu i mniej kodu do dodania przy tworzeniu nowej pary View / ViewModel.

Do tego potrzebujesz kilku rzeczy :

Pierwsza klasa abstrakcyjna NavigableViewModel z niektórymi metodami obsługi nawigacji w obu kierunkach. Wszystkie twoje viewmodele odziedziczą po tej klasie:

NavigableViewModel.cs

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

A MainWindow zawiera ramkę, w której odbywa się nawigacja, Wystarczy pomyśleć o ukryciu domyślnych kontrolek nawigacji za pomocą 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>

I jakiś kod z tyłu do obsługi zmiany ViewModels (alowing nas do powiadamiania każdej strony o jej 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;
        }
    }
}

I w swoim MainViewModel, ta mała metoda, aby przejść do swojego pierwszego ViewModel (tutaj 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
        }
    }

Dla tego wezwania ServiceLocator do pracy musimy ładnie dodać kilka rzeczy do naszego 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()
        {
        }
    }

A teraz, gdy masz wszystko na swoim miejscu, dodajmy rdzeń systemu, usługę nawigacyjną (to trudna część): {]}

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

    }

A teraz wszystko działa ! Hurra ! Zademonstrujmy naszym przykład LoginViewModel (który zawiera tylko piękny helloworld w czarnym kwadracie): {]}

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>

I jego viewmodel:

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

Przyznaję, że potrzebujesz kodu na początek. Ale kiedy wszystko działa, możesz znieść bardzo łatwy w użyciu system.

Chcesz przejść do jakiegoś viewModel ? Wystarczy skorzystać z usługi myNavigationService.NavigateTo (someParam);

Chcesz dodać nową parę View / ViewModel ? Just dodaj swój viewModel do jakiegoś kontenera IOC (w moich projektach używam własnego ioc, który pozwala mi rozładowywać moje viewmodele, kiedy tylko chcę i dać trochę ładnego stosu nawigacji) i dać mu swoją nawigację.

 2
Author: yan yankelevich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-09 08:55:59

Nie wiem, czy funkcja nawigacji jest dostępna w mvvm light. Zaimplementowałem go za pomocą wiązania contentControl:

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

A następnie właściwość ViewModel. Dziedziczy z klasy mvvm light viewmodelbase.

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

Aby kontrola zawartości wiedziała, której kontroli użytkownika musi użyć, definiujesz DataTemplates w aplikacji.xaml:

 <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 jest kontrolką użytkownika. Wystarczy przypisać LoggerViewModel do DisplayedDetailViewModel, a Framework zrobi praca.

 1
Author: Eric Bole-Feysot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-10 15:47:30