webentwicklung-frage-antwort-db.com.de

MVVM Light 5.0: Verwendung des Navigationsdienstes

In der neuesten Version von MVVM Light Hinweis wurde angegeben, dass MVVM Light jetzt einen "Navigationsdienst" anbietet.

Aber ich und mein Freund Google können nicht finden, wie er verwendet wird.

Ich kann sehen, dass ich einen INavigationService an den ServiceLocator anfragen kann, also sehe ich, wie ich auf eine andere Seite wechseln kann, aber:

  1. Ich habe ein neues Fenster erstellt, in dem ich erwarte, eine bestimmte Zone für die "Seite" zu reservieren. Wie kann ich dies angeben?
  2. Wie kann ich alle verfügbaren Seiten angeben? Gibt es etwas, das ich anrufen sollte?
  3. Welches Format würden die Parameter haben, die an INavigationService übergeben werden

Gibt es eine offizielle Dokumentation für diese Bibliothek? Im Moment finde ich es gut codiert und funktioniert gut, aber wenn ich nach seiner Verwendung suchen muss, finde ich nie eine Dokumentation/ein Beispiel, das zeigt, wie es geht, außer in seinem Blog, der etwas Eintrag hat. Das ist sehr frustrierend. Die einzige Dokumentation, die ich gefunden habe, ist this , ich bin mit Pluralsight nicht sehr vertraut, aber es scheint, dass es obligatorisch ist, ein monatliches Abonnement abzuschließen (das als Einzelperson versucht, einen Antrag auf meine Freizeit zu stellen.) ist nicht möglich). 

15
J4N

Ja, MvvmLight hat die NavigationService in ihrer letzten Version eingeführt, sie bot jedoch keine Implementierung bezüglich Wpf an (Sie können die implementierte NavigationService in WP, Metroapps, .. verwenden), aber leider nicht Wpf dein selbst, wie ich es gerade mache ( credit )

first erstellt Ihre Navigationsschnittstelle, die MvvmLightINavigationService implementiert. 

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

Parameter wird verwendet, um Objekte zwischen ViewModels zu übergeben, und INavigationService ist Teil des GalaSoft.MvvmLight.Views-Namespaces

dann implementiere diese Schnittstelle so 

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 im obigen Code ist der x: Name eines einfachen Frame-Steuerelements In Xaml definiert, das zum Navigieren zwischen Seiten verwendet wird (entsprechend Ihren Anforderungen anpassen)

Second: Starten Sie in viewmodellocator Ihren Navigationsdienst (SetupNavigation()), damit Sie ihn in Ihren Ansichtsmodellen verwenden können:

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

Third: finaly, zum Beispiel den Dienst 

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

EDIT 

Ein explizites Beispiel finden Sie unter repo .

29
SamTheDev

Ich würde lieber mit einem ViewModelFirstNavigationsdienst gehen.

Meiner Meinung nach ist es einfacher zu verwenden und beim Erstellen eines neuen Paares View/ViewModel viel weniger Code hinzuzufügen.

Dafür brauchst du ein paar Dinge :

Zuerst eine abstrakte NavigableViewModel-Klasse mit einigen Methoden, um die Navigation auf beide Arten zu behandeln. Alle Ihre viewModels werden von dieser Klasse erben:

NavigableViewModel.cs

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

Ein MainWindow, das den Frame enthält, in dem die Navigation stattfindet. Denken Sie nur daran, die Standard-Navigationssteuerelemente mit NavigationUIVisibility = "Hidden" auszublenden:

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>

Und etwas Code hinter den ViewModels-Änderungen (damit wir jede Seite über ihr ViewModel informieren können):

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

Und in Ihrem MainViewModel, diese kleine Methode zum Navigieren zum ersten ViewModel (hier 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
        }
    }

Damit dieser ServiceLocator-Aufruf funktioniert, müssen wir unserem ViewModelLocator ein paar Dinge hinzufügen:

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()
        {
        }
    }

Und da Sie jetzt alles haben, können Sie den Kern des Systems hinzufügen, den Navigationsdienst (das ist der knifflige Teil):

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

    }

Und jetzt haben Sie alles am Laufen! Hurra! Lassen Sie uns dies mit unserem Beispiel LoginViewModel (das nur eine schöne helloworld in einem schwarzen Quadrat enthält) demonstrieren:

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>

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

Ich gebe zu, Sie brauchen einen Code, um damit zu beginnen. Wenn jedoch alles funktioniert, erhalten Sie ein sehr einfach zu bedienendes System.

Möchten Sie zu einem bestimmten viewModel navigieren? Verwenden Sie einfach myNavigationService.NavigateTo (someParam).

Möchten Sie ein neues Paar View/ViewModel hinzufügen? Fügen Sie einfach Ihr viewModel in einem IOC -Container hinzu Stack) und geben Sie Ihren Navigationsdienst. 

2
yan yankelevich

Ich weiß nicht, ob eine Navigationsfunktion in mvvm light verfügbar ist. Ich habe es mit einer contentControl-Bindung implementiert:

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

Und dann die Eigenschaft viewmodel. Es erbt von der leichten ViewModelBase-Klasse von mvvm.

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

Damit das Inhaltssteuerelement wissen kann, welches Benutzersteuerelement es verwenden muss, definieren Sie DataTemplates in app.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>

Die LogView ist das UserControl. Sie müssen LoggerViewModel nur DisplayedDetailViewModel zuweisen, und das Framework erledigt die Arbeit.

2