Переключение страниц в wpf по архитектуре mvvm

470
28 марта 2017, 10:19

Всем доброго времени суток. Моя проблема заключается в следующем, как реализовать в WPF переключение между страницами используя MVVM. Переключение должно осуществлять по нажатию кнопки, расположенных слева на изображении.Страниц будет всего лишь шесть, как и кнопок.

Answer 1

Я уже указывал в комментариях на пример своего ответа здесь. Может такой вариант и представляется кому-то рабоче-крестьянским способом, в особенности для любителей девственно чистого кодбихайнд, но я считаю его вполне годным и, самое главное, простым и быстрым в реализации. Ладно, это лирика.

Вот вам другой вариант,

с рефлексией, все дела, как у четких пацанов...:) Это не мой мопед, я чисто его сп... подсмотрел в проекте NAudio. Подход довольно интересный, кстати.

Создаем вот такой интерфейс

public interface IModule
{
    /// <summary>
    /// Название выводимое в меню
    /// </summary>
    string Name { get; }
    /// <summary>
    /// Ссылка на вьюшку
    /// </summary>
    UserControl UserInterface { get; }
    /// <summary>
    /// Отключение текущей вьюшки, для вьюмоделей с реализацией IDisposable
    /// </summary>
    void Deactivate();
}

Далее для каждой пары View & ViewModel создается еще один класс, который реализует этот интерфейс таким образом:

class SeparateDemo : IModule
{
    //наша неразлучная парочка
    private SeparateDemoView view;
    private SeparateDemoViewModel viewModel;
    public string Name => "Вьюшка SeparateDemo";

    public UserControl UserInterface
    {
        get { if (view == null) CreateView(); return view; }
    }
    private void CreateView()
    {
        //загружаем вьюшку
        view = new SeparateDemoView();
        //её вьюмодель
        viewModel = new SeparateDemoViewModel();
        //связываем их между собой
        view.DataContext = viewModel;
    }
    public void Deactivate()
    {
        viewModel.Dispose();
        view = null;
    }
}

т.е. это такой загрузчик для пары View & ViewModel. Далее это у нас MainWindow.xaml

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="180" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <ListBox Grid.Column="0" 
             ItemsSource="{Binding Modules}" 
             SelectedItem="{Binding SelectedModule, Mode=TwoWay}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <ContentPresenter x:Name="contentPresenter" Grid.Column="1" 
                      Content="{Binding UserInterface}" />
</Grid>

Это её вьюмодель, которая подгружает и отображает нужную вьюшку

class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    //ctor
    public MainWindowViewModel(IEnumerable<IModule> modules)
    {
        Modules = modules.OrderBy(m => m.Name).ToList();
        if (this.Modules.Count > 0)
        {
            SelectedModule = this.Modules[0];
        }
    }
    //Properties
    /// <summary>
    /// Список всех возможных загрузчиков пар View и ViewModel
    /// </summary>
    public List<IModule> Modules { get; private set; }

    /// <summary>
    /// Выбранная и загруженная парочка View и ViewModel
    /// </summary>
    private IModule _SelectedModule;
    public IModule SelectedModule
    {
        get { return _SelectedModule; }
        set
        {
            if (value == _SelectedModule) return;
            if (_SelectedModule != null) _SelectedModule.Deactivate();
            _SelectedModule = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedModule)));
            PropertyChanged(this, new PropertyChangedEventArgs("UserInterface"));
        }
    }
    /// <summary>
    /// То, что отображает View через ContentPresenter 
    /// </summary>
    public UserControl UserInterface
    {
        get
        {
            if (SelectedModule == null) return null;
            return SelectedModule.UserInterface;
        }
    }
}

А вот теперь самая мякотка - тут без рефлексии не обойтись

static class ReflectionHelper
{
    public static IEnumerable<T> CreateAllInstancesOf<T>()
    {
        return typeof(ReflectionHelper).Assembly.GetTypes()
            .Where(t => typeof(T).IsAssignableFrom(t))
            .Where(t => !t.IsAbstract && t.IsClass)
            .Select(t => (T)Activator.CreateInstance(t));
    }
}

Подправляем App.xaml такой строчкой - Startup="Application_Startup"> И тогда App.xaml.cs такой

public partial class App : Application
{
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        var mainWindow = new MainWindow();
        var modules = ReflectionHelper.CreateAllInstancesOf<IModule>();
        var vm = new MainWindowViewModel(modules);
        mainWindow.DataContext = vm;
        mainWindow.Closing += (s, args) => vm.SelectedModule.Deactivate();
        mainWindow.Show();
    }
}

Вот и все. Как вариант можно сделать промежуточный класс abstract class ModuleBase : IModule и тогда загрузчик пары можно сделать таким

class FirstDemo : ModuleBase
{
    public override string Name
    {
        get { return "Вьюшка FirstDemo"; }
    }
    protected override UserControl CreateViewAndViewModel()
    {
        return new FirstDemoView() { DataContext = new FirstDemoViewModel() };
    }
}

Весь проект целиком можно скачать здесь.

READ ALSO
Как сделать замену частей страницы с помощью razor?

Как сделать замену частей страницы с помощью razor?

Как сделать замену(и добавление) частей страницы с помощью razor?

346
C# небезопасный буфер

C# небезопасный буфер

На https://msdnmicrosoft

322
interface cannot contain fields Что это значит?

interface cannot contain fields Что это значит?

Где здесь ошибка? Я не могу понять

359