Итак, это мое первое приложение MVVM. У меня есть модель представления «оболочка» с именем MainWindowViewModel для главного окна, которая в основном разбивает представление на две страницы: MainWindowRibbon и MainWindowFrame. MainWindowViewModel хранит обе страницы как свойства, которые я планирую использовать привязку данных для обновления в пользовательском интерфейсе. Вот часть кода для справки:
XAML MainWindowView ~
<Grid>
<Frame Content = "{Binding MainWindowRibbon}" Grid.Column = "0" Grid.Row = "0"/>
<ScrollViewer>
<Frame Content = "{Binding MainWindowFrame}"/>
</ScrollViewer>
</Grid>
Код MainWindowView позади~
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
mainWindowViewModel = new MainWindowViewModel();
DataContext = mainWindowViewModel;
}
public MainWindowViewModel mainWindowViewModel;
}
Код MainWindowViewModel~
public MainWindowViewModel()
{
//MainWindowRibbon and MainWindowFrame are declared as public Page properties
MainWindowRibbon = new MainWindowRibbonView();
MainWindowFrame = new WelcomePageView();
}
MainWindowRibbonView, как и MainWindowView, создает экземпляр MainWindowRibbonViewModel.
Моя проблема возникает, когда я хочу использовать событие в MainWindowRibbonViewModel, которое вызовет MainWindowViewModel для переназначения страницы MainWindowFrame. Я не знаю, как подключить кнопку кнопки панели навигации, которую я создал в MainWindowRibbonView, чтобы вызвать событие или изменение в MainWindowViewModel.
Я не знаю, является ли то, как я это организовал, идеальным. Пожалуйста, дайте мне знать, если мне нужно пересмотреть.
Если бы кто-нибудь мог помочь мне определить лучший подход или даже просто работающий, я был бы очень благодарен.
P.S. Извините, если соглашения об именах не самые лучшие.
Редактировать: Усвоенный урок: слушайте Джо.
Вы можете прочитать это: docs.microsoft.com/en-us/dotnet/desktop/wpf/data/…
Я полагаю, это зависит от того, какую кнопку вы используете на панели навигации. это RadioButton
? А RibbonToggleButton
? Это обычная привязка кнопки к ICommand
?
Поскольку вы назвали свою панель навигации «лентой», давайте предположим, что это RibbonToggleButton
(который по-прежнему в основном является флажком). Если он отмечен, вы показываете какую-то модель представления вашей «страницы 1». Если он не отмечен, вам следует использовать другую модель представления, представляющую вашу «страницу 2».
Давайте также предположим, что лента вашего представления находится вверху. Итак, у вас есть две строки: строка ленты и строка содержимого.
Я мог бы переписать ваше MainWindow, чтобы оно выглядело так (обратите внимание на свойство IsChecked
для некоторого логического свойства в вашей модели представления, например:)
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto"/> <!-- The "Ribbon" -->
<RowDefinition Height = "*"/> <!-- The page content -->
</Grid.RowDefinitions>
<ToggleButton Content = "Show Page 1" IsChecked = "{Binding ShowPage1}"/>
<ScrollViewer Grid.Row=1>
<Frame Content = "{Binding CurrentViewModel}"/>
</ScrollViewer>
</Grid>
И я мог бы написать вашу модель представления следующим образом: (Обратите внимание, что я предполагаю, что она реализует INotifyPropertyChanged, и я вызываю функцию RaisePropertyChanged
, которую я не показываю.
public class Page1ViewModel {} // Fill this out with Page 1 properties
public class Page2ViewModel {} // Fill this out with Page 2 properties
// MainWindowViewModel. Implements INotifyPropertyChanged. Implementation
// is not shown here.
public class MainWindowViewModel : INotifyPropertyChanged
{
private Page1ViewModel = new Page1ViewModel();
private Page2ViewModel = new Page2ViewModel();
public MainWindowViewModel()
{
_currentViewModel = Page1ViewModel;
ShowPage1 = true;
}
private object _currentViewModel;
// The current contents of the main frame.
public object CurrentViewModel
{
get => _currentViewModel;
set
{
if (value == _currentViewModel)
return;
_currentViewModel = value;
RaisePropertyChanged();
}
// Should CurrentViewModel be page 1 or page 2?
public bool ShowPage1
{
get => return _currentViewModel == Page1ViewModel;
set
{
if (value == ShowPage1)
return;
CurrentViewModel = value ? Page1ViewModel : Page2ViewModel;
RaisePropertyChanged();
}
}
}
Обратите внимание, что я не показываю вам никаких свойств Page1VieModel
или Page2ViewModel
и не показываю вам неявные DataTemplate
, которые, как я предполагаю, вы будете писать для них.
Также я предполагаю, что ваша панель навигации (и MainWindowView
в целом) имеет DataContext
, который уже установлен на MainWindowViewModel
Реализация с командной кнопкой или RadioButton
будет совсем другой.
Спасибо, Джо, за исчерпывающий ответ. После просмотра я решил включить «MainWindowRibbon» (который не включает кнопки переключения ленты, которые я могу исследовать сейчас, но это было просто неудачное название. Я использовал обычные кнопки, используя ICommand в шаблоне ItemsControl.) в главное окно. Я полагаю, что мой самый большой вопрос сейчас заключается в том, как свойство Content получает доступ к xaml, с кодом позади, с классом модели представления? то есть каков тип объекта модели представления? Извините, если я сейчас нуб.
Вы предлагаете мне создать шаблон данных в основном xaml для каждой страницы? Извините, я соединил большинство точек, но не все
Отвечая на ваш второй вопрос, да, я предлагаю вам создать DataTemplate
для каждой страницы. DataTemplate
действительно является страницы, по крайней мере, с точки зрения пользовательского интерфейса. Между тем, Content
каждой страницы — это своего рода объект модели представления, который сам по себе не имеет пользовательского интерфейса (например, некоторый класс модели представления в вашем проекте). Это позволяет вам позже добавлять новые типы страниц, если вы хотите, без изменения механизма. По сути, вы должны перестать думать о «контенте» с точки зрения пользовательского интерфейса/элементов управления и начать думать о нем с точки зрения моделей представления. DataTemplating переводит последнее в первое.
WPF предназначен для работы таким образом, с шаблонами. Вы упомянули, что использовали ItemsControl
, верно? Что ж, управление работает так же, за исключением того, что вместо управления одним ContentControl
оно управляет многими. Вы привязываете ItemsControl
к списку моделей представления, хранящихся где-то в вашем коде. Он показывает вам список ContentControls
, по одному для каждого элемента в вашем списке. У него есть свойство ItemTemplate
, которое используется для преобразования каждого элемента списка в пользовательский интерфейс. Итак, еще раз, элементы в вашем списке не являются элементами управления пользовательским интерфейсом, они являются моделями просмотра.
Фантастическое спасибо большое, чувак. А теперь самое интересное — переработка всего проекта :) Хотя не так уж и плохо. Спасибо еще раз.
Я думаю, вы неправильно понимаете, что такое
Frame
. ЭтоContentControl
. Вы не устанавливаете егоContent
для какого-либо элемента пользовательского интерфейса. Вместо этого вы устанавливаете его для хранения экземпляра некоторой модели представления (например,PersonViewModel
). Пользовательский интерфейс для него представлен в XAML; Вы устанавливаете значение по умолчаниюDataTemplate
для этого типа класса модели представления.DataTemplate
имеет элементы управления пользовательского интерфейса, которые привязываются к свойствам этой модели представления. Таким образом, всякий раз, когда свойствоContent
установлено для объекта этого типа, определенного вDataTemplate
, оно будет отображать пользовательский интерфейс, который вы разместили в связанном свойствеDataTemplate
.