3

Есть приложение. Слева есть меню, с множеством кнопок, справа пустое место для отображения представления, в зависимости какую кнопку меню нажали, появляется соответствующее представление.

Использую Catel, в котором во MainWindow.xaml использовал ContentControl, в который помещал определенную вьюшку, не уверен что это правильно так как создавая вложенности в Catel перестает так работать, подскажите как можно реализовать такое поведение? Или что я делаю не так?

  • Какое поведение вам нужно? О какой вложенности идет речь? Из вопроса ничего не понятно. – Ev_Hyper Mar 01 '17 at 13:12
  • суть вопроса, как сделать так что бы по нажатию на кнопки меню которые находятся с лева, подгружать другое представление(view) как в VK.COM нажимаем на НОВОСТи, с права отображаются новости, нажимаем на сообщения, с права сообщения, только на WPF – Ivan Prodaiko Mar 01 '17 at 13:17
  • В принципе правильно вы делали. Можно гридом разделить окно на 2 столбца и в одной половине отображать меню, а в др. необходимый контент подгружая необходимую UserControl к примеру. А вот ваш Catel можно смело отправить нафиг. Он не нужен от слова совсем. – Bulson Mar 01 '17 at 13:21
  • не подскажите, где можно посмотреть примеры таких приложений или почитать? – Ivan Prodaiko Mar 01 '17 at 13:23
  • У меня немножко дежавю приключилось! Не вы ли задавали похожий вопрос? Там похожая ситуация. – Bulson Mar 01 '17 at 13:28
  • скорее я, так как посадили за этот проект, а я ничего не знаю ни о WPF и о C#, по этому много вопросов – Ivan Prodaiko Mar 01 '17 at 13:29
  • Сейчас я попробую соорудить для вас маленький пример. Малеха подождать треба. – Bulson Mar 01 '17 at 13:33
  • большое спасибо, жду) – Ivan Prodaiko Mar 01 '17 at 13:35
  • 1
    Вот пример: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/ хотя и без Catel – Ev_Hyper Mar 01 '17 at 13:38
  • По поводу вложенности вот даже в документации к Catel есть пример - https://catelproject.atlassian.net/wiki/display/CTL/Introduction+to+the+nested+user+controls+problem – Ev_Hyper Mar 01 '17 at 13:44
  • Благодарю, буду разбираться) – Ivan Prodaiko Mar 01 '17 at 13:50
  • Если в ходе будут возникать вопросы - спрашивайте, поможем ;) – Ev_Hyper Mar 01 '17 at 13:53

1 Answers1

8

Как и обещал в комментариях вот вам хоть и маленький, но вполне рабочий каркас приложения в духе MVVM, но безо всяких кателей и прочих фреймворков.

Иллюстрация работы программы

Это MainWindow.xaml

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <!--Меню-->
    <StackPanel Grid.Column="0" Margin="15">
        <Button Command="{Binding LoadMainUCCommand, Mode=OneTime}"
            Margin="10" Width="30" Height="30" HorizontalAlignment="Left">
            <TextBlock FontFamily="Segoe UI Symbol" FontSize="14">
                    <Run Text="&#xE0A5;" />
            </TextBlock>
        </Button>
        <Button Command="{Binding LoadFirstUCCommand, Mode=OneTime}"
            Margin="10" Width="30" Height="30" HorizontalAlignment="Left">
            <TextBlock FontFamily="Segoe UI Symbol" FontSize="14">
                    <Run Text="&#xE2AF;" />
            </TextBlock>
        </Button>
        <Button Command="{Binding LoadSecondUCCommand, Mode=OneTime}"
            Margin="10" Width="30" Height="30" HorizontalAlignment="Left">
            <TextBlock FontFamily="Segoe UI Symbol" FontSize="14">
                    <Run Text="&#xE1E2;" />
            </TextBlock>
        </Button>
    </StackPanel>
    <!--Контент-->
    <ContentPresenter Grid.Column="1" x:Name="OutputView" />
</Grid>

Это ее кодбихайнд

public interface IMainWindowsCodeBehind
{
    /// <summary>
    /// Показ сообщения для пользователя
    /// </summary>
    /// <param name="message">текст сообщения</param>
    void ShowMessage(string message);

    /// <summary>
    /// Загрузка нужной View
    /// </summary>
    /// <param name="view">экземпляр UserControl</param>
    void LoadView(ViewType typeView);
}

/// <summary>
/// Типы вьюшек для загрузки
/// </summary>
public enum ViewType
{
    Main,
    First,
    Second
}

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, IMainWindowsCodeBehind
{
    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        //загрузка вьюмодел для кнопок меню
        MenuViewModel vm = new MenuViewModel();
        //даем доступ к этому кодбихайнд
        vm.CodeBehind = this;
        //делаем эту вьюмодел контекстом данных
        this.DataContext = vm;

        //загрузка стартовой View
        LoadView(ViewType.Main);
    }

    public void LoadView(ViewType typeView)
    {
        switch (typeView)
        {
            case ViewType.Main:
                //загружаем вьюшку, ее вьюмодель
                MainUC view = new MainUC();
                MainViewModel vm = new MainViewModel(this);
                //связываем их м/собой
                view.DataContext = vm;
                //отображаем
                this.OutputView.Content = view;
                break;
            case ViewType.First:
                FirstUC viewF = new FirstUC();
                FirstViewModel vmF = new FirstViewModel(this);
                viewF.DataContext = vmF;
                this.OutputView.Content = viewF;
                break;
            case ViewType.Second:
                SecondUC viewS = new SecondUC();
                SecondViewModel vmS = new SecondViewModel(this);
                viewS.DataContext = vmS;
                this.OutputView.Content = viewS;
                break;
        }


    }

    public void ShowMessage(string message)
    {
        MessageBox.Show(message);
    }
}

Вот где все команды перехода в MenuViewModel.cs

public class MenuViewModel
{

    //ctor
    public MenuViewModel()
    {

    }

    public IMainWindowsCodeBehind CodeBehind { get; set; }


    /// <summary>
    /// Переход к первой вьюшке
    /// </summary>
    private RelayCommand _LoadFirstUCCommand;
    public RelayCommand LoadFirstUCCommand
    {
        get
        {
            return _LoadFirstUCCommand = _LoadFirstUCCommand ??
              new RelayCommand(OnLoadFirstUC, CanLoadFirstUC);
        }
    }
    private bool CanLoadFirstUC()
    {
        return true;
    }
    private void OnLoadFirstUC()
    {
        CodeBehind.LoadView(ViewType.First);
    }


    /// <summary>
    /// Переход ко Второй вьюшке
    /// </summary>
    private RelayCommand _LoadSecondUCCommand;
    public RelayCommand LoadSecondUCCommand
    {
        get
        {
            return _LoadSecondUCCommand = _LoadSecondUCCommand ??
              new RelayCommand(OnLoadSecondUC, CanLoadSecondUC);
        }
    }
    private bool CanLoadSecondUC()
    {
        return true;
    }
    private void OnLoadSecondUC()
    {
        CodeBehind.LoadView(ViewType.Second);
    }


    /// <summary>
    /// Возвращение к главной вьюшке
    /// </summary>
    private RelayCommand _LoadMainUCCommand;
    public RelayCommand LoadMainUCCommand
    {
        get
        {
            return _LoadMainUCCommand = _LoadMainUCCommand ??
              new RelayCommand(OnLoadMainUC, CanLoadMainUC);
        }
    }
    private bool CanLoadMainUC()
    {
        return true;
    }
    private void OnLoadMainUC()
    {
        CodeBehind.LoadView(ViewType.Main);
    }

}

Это MainUC.xaml

<UserControl x:Class="ExampleMenu.View.MainUC"
         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:ExampleMenu.View"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
    <TextBlock Text="Это начальная MainUC" FontSize="18" />
    <Button Content="Click Me" Command="{Binding ShowMessageCommand, Mode=OneTime}"
            Width="70" Height="20" Margin="0,20,0,0"/>
</StackPanel>

Вот ее ViewModel

public class MainViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    //Fields
    private IMainWindowsCodeBehind _MainCodeBehind;

    //ctor
    public MainViewModel(IMainWindowsCodeBehind codeBehind)
    {
        if (codeBehind == null) throw new ArgumentNullException(nameof(codeBehind));

        _MainCodeBehind = codeBehind;
    }

    //Properties

    //Commands

    /// <summary>
    /// Сообщение пользователю
    /// </summary>
    private RelayCommand _ShowMessageCommand;
    public RelayCommand ShowMessageCommand
    {
        get { return _ShowMessageCommand = _ShowMessageCommand ??
                new RelayCommand(OnShowMessage, CanShowMessage); }
    }
    private bool CanShowMessage()
    {
        return true;
    }
    private void OnShowMessage()
    {
        _MainCodeBehind.ShowMessage("Привет от MainUC");
    }
}

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

Bulson
  • 9,411
  • большое спасибо) – Ivan Prodaiko Mar 01 '17 at 23:40
  • vm.CodeBehind = this? Нууууу... А если к одной VM приаттачится несколько View? – VladD Mar 26 '17 at 16:33
  • @VladD в этом примере такой случай исключен в принципе. – Bulson Mar 26 '17 at 17:46
  • @Bulson: Ну, это уже накладывает ограничения на дизайн. VM не должна знать о самом существовании View, а у вас она выполняет View-шный кусок кода. – VladD Mar 26 '17 at 17:55
  • @VladD VM не связана с View жестко, т.к. работает через интерфейс, а значит я могу тестировать VM "подпихивая" ему любой класс реализующий этот интерфейс, а не только конкретную View. – Bulson Mar 26 '17 at 18:04
  • 1
    @Bulson: Я не об этом, я об изоляции. Вопрос не в том, как пропихнуть код View в VM так, чтобы можно было тестировать. Я говорю о том, что VM вообще не должно знать о View-шных классах. Например, View-уровень должно быть возможно выгрузить в отдельную сборку, чтобы VM-сборка вовсе не ссылалась на System.Windows. – VladD Mar 26 '17 at 18:07
  • @Bulson: А, я понял, то есть вы имеете в виду, что интерфейс определяется не на View-уровне, а на VM? Ну формально да. Но по факту давайте скажем себе честно: это поле нужно только для того, чтобы View-шный код выполнился из VM. И вот это не очень хорошо. Простите за занудство, мне нравится ваш ответ в целом. – VladD Mar 26 '17 at 18:16
  • @VladD да, все правильно, что обращаете внимание начинающих на эти нюансы. Однако же, согласитесь, что в примере interface IMainWindowsCodeBehind не влечет за собой подключение в VM System.Windows :). Я знаю, есть еще другие способы работы с переключением View. – Bulson Mar 26 '17 at 18:17
  • @VladD "...это поле нужно только для того, чтобы View-шный код выполнился из VM..." да, это правда, но мне кажется это не криминально, и даже иногда, когда очень хочется вызвать из обработчика события напрямую метод из VM: ((VM)DataContext).Method() – Bulson Mar 26 '17 at 18:22
  • 1
    @Bulson: Мне оба случая кажутся концептуально неверными, т. к. ведут к сильной связности (ну и нарушают MVVM, насколько я понимаю). Вместо вызова VM-метода из code-behind нужна команда. А вызывать метод View из VM вообще не должно хотеться. (Единственный неудобный случай — открытие нового окна, для которого приходится изобретать костыли наподобие этого или этого.) – VladD Mar 26 '17 at 18:45
  • @VladD, спасибо, посмотрю, проанализирую, подумаю... – Bulson Mar 26 '17 at 18:55
  • большая благодарность за пример ;) – Dimitri Shatovkin Jul 27 '20 at 08:29