2014-04-01 16 views
40

Buduję mój pierwszy WPF przy użyciu wzorca MVVM. Z pomocą tej społeczności udało mi się stworzyć mój Model, mój pierwszy ViewModel i widok. Teraz chcę dodać trochę złożoności do aplikacji projektującej podstawowy interfejs układu aplikacji. Mój pomysł jest mieć co najmniej 2 poglądy dzieci oraz jeden główny widok i rozdzielić je na kilka XAML:WPF Przeglądanie widoków przy użyciu wzorca MVVM

  • Main.XAML
  • Products.XAML
  • Clients.XAML

Główny będzie miał menu i spację, aby załadować widoków podrzędnych (produkty i klientów). Teraz po wzorcu MVVM cała logika nawigacji pomiędzy widokami powinna być zapisana na ViewModelu. Tak mi chodzi o to, aby mieć 4 ViewModels:

  • MainViewModel
  • ProductsViewModel
  • ClientsViewModel
  • NavigationViewModel

tak NavigationViewModel powinien zawierać zbiór ViewModels dziecko? i aktywny model podglądu jest tak?

Więc moje pytania to:

1) W jaki sposób można załadować różne widoki (produkty, klienci) na głównym widoku z wykorzystaniem wzorca MVVM?

2) Jak zaimplementować viewModel nawigacji?

3) Jak kontrolować maksymalną liczbę otwartych lub aktywnych widoków?

4) Jak mogę przełączać między otwartymi widokami?

Dużo wyszukiwań i czytania i nie mogłem znaleźć żadnego prostego przykładowego działania nawigacji MVVM z WPF, który ładuje wiele widoków wewnątrz głównego widoku. Wiele z nich:

1) Użyj zewnętrznego zestawu narzędzi, którego nie chcę teraz używać.

2) Umieść cały kod do tworzenia wszystkich widoków w jednym pliku XAML, co nie wydaje się dobrym pomysłem, ponieważ muszę zaimplementować blisko 80 wyświetleń!

Jestem na dobrej drodze? Każda pomoc, zwłaszcza z pewnym kodem, zostanie doceniona.

UPDATE

Więc zbudować projekt testowy następujące rady @LordTakkera, ale utknąć. Jest to, jak wygląda moje rozwiązanie: Solution

tworzę:

  • Two Models (Clients and Products)

  • One MainWindow and two wpf user controls(Clients and Products) XAML.

  • Three ViewModels (Clients, Products and Main ViewModel)

Potem ustawić DataContext na każdego celu odpowiadający ViewModel. Następnie tworzę MainWindow przy pomocy ContentPresentera w taki sposób i wiążę go z właściwością viewmodel.

MainWindow.XAML

<Window x:Class="PruevaMVVMNavNew.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="MainWindow" Height="519" Width="890">  
<Grid> 
    <Grid.ColumnDefinitions> 
     <ColumnDefinition Width="150"/> 
     <ColumnDefinition Width="*"/> 
    </Grid.ColumnDefinitions> 
    <Grid.RowDefinitions> 
     <RowDefinition Height="80"/> 
     <RowDefinition Height="*"/> 
     <RowDefinition Height="20"/> 
    </Grid.RowDefinitions>   
    <Border Grid.Column="0" Grid.ColumnSpan="2" Background="AntiqueWhite" ></Border> 
    <Border Grid.Row="1" Grid.RowSpan="2" Background="AliceBlue"></Border> 
    <Border Grid.Row="1" Grid.Column="1" Background="CadetBlue"></Border>     
    <ContentPresenter Grid.Row="1" Grid.Column="1" x:Name="ContentArea" Content="{Binding CurrentView}"/>   
    <StackPanel Margin="5" Grid.Column="0" Grid.Row="1">    
     <Button>Clients</Button> 
     <Button>Products</Button> 
    </StackPanel> 
</Grid> 

A także to ViewModel od MainWindow:

class Main_ViewModel : BaseViewModel 
    { 
     public Main_ViewModel() 
     { 
      CurrentView = new Clients(); 
     } 

     private UserControl _currentView; 
     public UserControl CurrentView 
     { 
      get 
      { 
       return _currentView; 
      } 
      set 
      { 
       if (value != _currentView) 
       { 
        _currentView = value; 
        OnPropertyChanged("CurrentView"); 
       } 
      } 
     } 

    } 

Więc to obciążenie domyślnie klientów przeglądać i wygląda tak (co jest po prostu dobrze!):

Current state

Więc przypuszczam, że potrzebny jest sposób, aby odnosić się przyciski po lewej stronie, z pewnym viemodel a następnie powiązać je z CurrentView Własności Głównego ViewModel. Jak mogę to zrobić?

Update2

Zgodnie z radą @LordTakkera zmodyfikować mój główny ViewModel w ten sposób:

class Main_ViewModel : BaseViewModel 
    { 
     public ICommand SwitchViewsCommand { get; private set; } 

     public Main_ViewModel() 
     { 
      //CurrentView = new Clients(); 
      SwitchViewsCommand = new RelayCommand((parameter) => CurrentView = (UserControl)Activator.CreateInstance(parameter as Type)); 
     } 

     private UserControl _currentView; 
     public UserControl CurrentView 
     { 
      get 
      { 
       return _currentView; 
      } 
      set 
      { 
       if (value != _currentView) 
       { 
        _currentView = value; 
        OnPropertyChanged("CurrentView"); 
       } 
      } 
     } 
    } 

używam RelayCommand zamiast DelegateCommand ale myślę, że to działa w ten sam sposób. Polecenie jest wykonywane, gdy uderzę przycisków a parametr ciąg wpisz jego OK, ale ja dostać ten błąd:

Error

Tłumaczenie: wartość nie może być zerowa. Nazwa parametru: typ. Użyj sugestii Nowe słowo kluczowe do utworzenia instancji obiektu Nie wiem, gdzie umieścić słowo kluczowe Nowe. Próbowałem na CommandParameter, ale to nie zadziała. Dowolny pomysł? Dzięki

UPDATE 3

Po wszystkich rad i pomocy otrzymanych tutaj, i dużo pracy, tutaj jest moja ostateczna menu nawigacji oraz podstawa dla mojego interfejsu aplikacji.

Capture 1 Capture 2

+1

Przyjemna aplikacja Eric.I do nowego do wpf. Próbuję zaprojektować aplikację z tym samym układem. Czy mógłbyś napisać kod. To byłoby bardzo pomocne, aby uzyskać więcej wiedzy. – Oasis

Odpowiedz

19

Nie jestem pewien, trzeba osobny „Nawigacja” widoku modelu, można łatwo umieścić go w głównym. Tak czy inaczej:

Aby oddzielić swoje poglądy „dziecko”, chciałbym użyć prostego ContentPresenter na „głównym” widzenia:

<ContentPresenter Content="{Binding CurrentView}"/> 

Najprostszym sposobem realizacji właściwość podkładowej jest, aby to UserControl, choć niektórzy twierdzą, że to narusza MVVM (ponieważ ViewModel jest teraz zależny od klasy "View"). Możesz zrobić z tego obiekt, ale stracisz pewne bezpieczeństwo. Każdy widok byłby w tym przypadku UserControl.

Aby przełączać się między nimi, będziesz potrzebować jakiejś kontroli wyboru. Robiłem to z przycisków radiowych wcześniej powiązać je tak:

<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/> 

Konwerter jest dość proste, w „Convert” to po prostu sprawdza, czy bieżąca kontrola jest typem parametru w „ConvertBack "zwraca nową instancję parametru.

public class InstanceEqualsConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     return (parameter as Type).IsInstanceOfType(value); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     return (bool)value ? Activator.CreateInstance(parameter as Type) : Binding.DoNothing; 
    } 
} 

Wiązanie z combobox lub inną kontrolą wyboru miałoby podobny wzór.

Oczywiście można również użyć DataTemplates (z selektorem, niestety nie jest to coś, co zrobiłem wcześniej) i załadować je do zasobów przy użyciu połączonych słowników (zezwalając na oddzielne XAML). Osobiście preferuję trasę kontroli użytkownika, która jest najlepsza dla ciebie!

Takie podejście to "jeden widok na raz".Byłoby stosunkowo łatwo przekonwertować na wiele widoków (Twoja UserControl staje się zbiorem kontrolek użytkownika, użyj .Contains w konwerterze itp.).

Aby to zrobić za pomocą przycisków, użyłbym komend i skorzystał z CommandParameter.

Przycisk XAML wyglądałby następująco:

<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/> 

Wtedy masz polecenie delegata (samouczek here), która uruchamia kod aktywujący od konwertera:

public ICommand SwitchViewsCommand {get; private set;} 

public MainViewModel() 
{ 
    SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type)); 
} 

To jest wyłączony górę moja głowa, ale powinna być całkiem blisko. Daj mi znać jak idzie!

Daj mi znać, jeśli przekażę więcej informacji!

Aktualizacja:

Aby odebrać swoje obawy:

  1. Tak, za każdym razem naciskać przycisk nowa instancja zdania jest tworzony. Możesz to łatwo naprawić, trzymając Dictionary<Type, UserControl>, który wstępnie utworzył widoki i indeksował do niego. W tym przypadku można użyć parametru Dictonary<String, UserControl> i użyć prostych ciągów jako parametrów konwertera. Wadą jest to, że twój ViewModel staje się ściśle powiązany z rodzajami widoków, które może przedstawić (ponieważ musi wypełnić ten słownik).

  2. Klasa powinna zostać usunięta, o ile nikt inny nie odwołuje się do niej (należy użyć programów do obsługi zdarzeń, dla których została zarejestrowana).

  3. Jak wskazujesz, tylko jeden widok jest tworzony naraz, więc nie powinieneś martwić się pamięcią. Oczywiście dzwonisz do konstruktora, ale nie jest to TAKIE drogie, szczególnie na nowoczesnych komputerach, gdzie zwykle mamy dużo wolnego czasu procesora. Jak zawsze, odpowiedź na pytania dotyczące wydajności jest "porównawcza", ponieważ tylko Ty masz dostęp do zamierzonych celów wdrażania i całego źródła, aby zobaczyć, co faktycznie działa najlepiej.

+0

Dzięki !. Nie całkiem rozumiem wszystko, co publikujesz, ale nie podążam za podstawową ideą, że mam mieć główną zawartość ContentPresentera (jakieś puste pole), gdzie mogę załadować zewnętrzny XAML dla aktywnego widoku. Spróbuję zrobić bardzo prosty przykładowy kod z tym pomysłem i zaktualizuję pytanie, abyś mógł mi jeszcze pomóc. Jeszcze raz dziękuję! – ericpap

+0

Czy możesz przejrzeć moją aktualizację? Dziękuję Ci! – ericpap

+0

Wygląda dobrze! Daj mi znać, jeśli pomoże mi przykład polecenia. – BradleyDotNET

0

Najlepszym rozwiązaniem dla IMHO jest wykorzystanie ram MVVM (PRISM, MMVM Light, Chinch, itp.), Ponieważ nawigacja jest już zaimplementowana. Jeśli chcesz stworzyć własną nawigację - wypróbuj DataTemplate.

Powiązane problemy