Подскажите, вот у меня есть UserControl на нем listBox, при нажатии на Item необходимо помещать Page во Frame Content главной формы (MainWindow). Как такое можно провернуть?
1 Answers
Как вариант решения данной задачи:
Создание UserControl:
Я создам простой UserControl, который будет содержать в себе ListBox и наружу он будет отдавать такие свойства как ItemsSource и SelectedItem.
Создадим два
DependencyPropertyв коде UserControl'a:public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( "ItemsSource", typeof(IEnumerable), typeof(MenuControl), new PropertyMetadata(default(IEnumerable))); public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register( "SelectedItem", typeof(object), typeof(MenuControl), new PropertyMetadata(default(object))); public IEnumerable ItemsSource { get => (IEnumerable)GetValue(ItemsSourceProperty); set => SetValue(ItemsSourceProperty, value); } public object SelectedItem { get => GetValue(SelectedItemProperty); set => SetValue(SelectedItemProperty, value); }Теперь привяжем в XAML все это:
- Задаем имя для UserControl, прописав
x:Name="Control". Создаем и привязываем ListBox:
<ListBox ItemsSource="{Binding ElementName=Control, Path=ItemsSource}" SelectedItem="{Binding ElementName=Control, Path=SelectedItem}"/>
- Задаем имя для UserControl, прописав
Создадим базовый класс для VM страниц:
Заодно сделаем здесь реализацию INotifyPropertyChanged.
class BaseVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel для одного итема в меню:
Нам надо что бы каждый объект в ListBox имел имя и VM нужной страницы.
- Создаем класс MenuItemViewModel
Прописываем все свойства:
public string Name { get; set; } public BaseVM ViewModel { get; set; }Перепишем ToString для того, что бы не городить стили. Если у вас объект более сложный, то лучше перепишите стиль ListBox!
public override string ToString() => Name;Ну и для удобства сделаем конструктор:
public MenuItemViewModel(string name, BaseVM viewModel) { Name = name; ViewModel = viewModel; }
Создаем новую страницу со своей VM:
- Добавляем новый UserControl и называем его например
FirstPage. В XAML для теста напишем по центру текст, взятый из VM и сделаем его другого цвета:
<TextBlock Foreground="Red" Text="{Binding Text}" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>Далее создаем VM класс, назовем его
FirstPageViewModel, сразу наследуем отBaseVM.В данной VM пропишем всего одно свойства с текстом:
class FirstPageViewModel : BaseVM { public FirstPageViewModel() { Text = "Первая страница"; } private string text; public string Text { get => text; set { text = value; RaisePropertyChanged(); } } }Делаем также и другие страницы, со своими свойствами и разметкой.
Работаем с главной ViewModel:
Здесь мы все объединим в одно целое.
- Создаем класс MainViewModel.
- Наследуем класс от ранее созданного
BaseVM. Создаем коллекцию наших меню:
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }Создаем свойства текущей страницы и выбранного объекта меню (везде реализуем INPC):
private BaseVM currentContent; public BaseVM CurrentContent { get => currentContent; set { currentContent = value; RaisePropertyChanged(); } } private MenuItemViewModel selectedMenu; public MenuItemViewModel SelectedMenu { get => selectedMenu; set { selectedMenu = value; CurrentContent = value.ViewModel; RaisePropertyChanged(); } }MainViewModelзадаем какDataContextтекущего окна.
Прорабатываем стиль главного окна:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:FirstPageViewModel}">
<сt:FirstPage/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecondPageViewModel}">
<сt:SecondPage/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<сt:MenuControl ItemsSource="{Binding MenuItems}" SelectedItem="{Binding SelectedMenu, Mode=TwoWay}"/>
<ContentPresenter Grid.Column="1" Content="{Binding CurrentContent, Mode=TwoWay}"/>
</Grid>
Заполняем список страниц:
Тут достаточно в конструкторе MainViewModel прописать инициализацию коллекции.
public MainViewModel()
{
MenuItems = new ObservableCollection<MenuItemViewModel>()
{
new MenuItemViewModel("Страница 1", new FirstPageViewModel()),
new MenuItemViewModel("Страница 2", new SecondPageViewModel())
};
}
Результат:
Вот вроде и все, запускаем и любуемся результатом.
Таким довольно простым способом мы связали UserControl с меню и сделали изменение страницы в главном окне.
- 15,694
-
EvgeniyZ, сделал как ты описал, работает. Теперь буду пробовать на своем проекте сделать. Спасибо. – Артур Чал Feb 05 '19 at 04:46
-
@EvgeniyZ В MainWindow.xaml я получаю ошибку. Текст ошибки:
Ссылка на тип не может найти тип с именем "{clr-namespace:MasterDetail;assembly=MasterDetail}FirstPageViewModel"Строка 12Проект -- https://github.com/jhon65496/MasterDetail \n MainWindow.xaml -- https://github.com/jhon65496/MasterDetail/blob/master/MasterDetail/MainWindow.xaml
– eusataf Oct 05 '23 at 16:42 -
1@eusataf Почему у вас 3 одинаковых Namecpace подключены к XAML? У вас изначально есть local, вот его и используйте. Ну и да, XAML обновляет данные о новых классах только после успешной сборки проекта, без каких либо ошибок. Поэтому, пересоберите проект, а потом пишите XAML. – EvgeniyZ Oct 05 '23 at 16:54
-
@EvgeniyZ Заработало. Пока перевариваю решение, не могли бы вы подсказать: данное решение поддерживает передачу данных из UserControl(FirstPage, SecondPage) в MainWindow, как заявлено в в теме или только смену UserControl(FirstPage, SecondPage) ? – eusataf Oct 05 '23 at 17:18
-
@eusataf Абстрагируйтесь от MVVM, WPF, и прочего. Вот у вас есть два класса, класс
Aи классB, вам надо из одного передать данные в другой, как будете делать? Наверно сделаете конструктор, который будет принимать нужные данные, и дальше при инициализации передавать нужное (new B(...)), или свойствоb.Prop = ...;, или методb.Method(...), так? Ок, что мешает сделать тоже самое с перечисленным выше? Разница лишь в том, что вы не должны из UI передавать что-то в логику, у вас должна быть только привязка, не более, а уже VM слои комбинируйте как хотите (правда это немного не правильно) – EvgeniyZ Oct 05 '23 at 17:29 -
@EvgeniyZ Как реализовать сценарий Сценарий-1? Условно, схематично не погружаясь в детали. Примем
A-MainVMdatacontext дляMainWin;B-MenuVMdatacontext дляMenuUC;C-FirstVMdatacontext дляFirstUC;Сценарий-1. Передать данные из
– eusataf Oct 05 '23 at 18:03FirstVMвMenuVM. Передать данные - имею ввиду пользователь нажал кнопку(ввёл текст в текстбокс) и тем самым изменилFirstVM.Prop-1. -
@EvgeniyZ В результате значение
FirstVM.Prop-1передаётся вMenuVM.Prop-1. – eusataf Oct 05 '23 at 18:06 -
@eusataf
DataContextза вас ставит автоматически WPF, вам не надо ничего устанавливать кроме контекста самому окну. Передача данных - я вам вроде выше сказал как, что не понятного? Делаете конструкторprivate MainVM _mainVM; public FirstVM (MainVM mainVM) { _mainVM = mainVL }, далееnew FirstVM(this), гдеthis- этоMainVM, поздравляю, вы передали ссылку на класс в другой класс, теперь вы в нем можете делать_mainVM.Some.... если вам нужна "правильно", то делайте абстракцию, выносите эту логику передачи в отдельный класс, и его уже передавайте, ибо у вас нарушение ответственности. – EvgeniyZ Oct 05 '23 at 18:19 -
@EvgeniyZ 1. Если FirstPageViewModel, SecondPageViewModel в конструкторе загружают большие данные из БД , то предложенный вами код заполнения меню подойдёт или нужно думать что-нибудь другое? Напримр
switch (view)какой-нибудь делать, чтобы загружалось только одно VMКод заполнения меню:
– eusataf Oct 06 '23 at 15:53MenuItems = new ObservableCollection<MenuItemViewModel>() { new MenuItemViewModel("Страница 1", new FirstPageViewModel()), new MenuItemViewModel("Страница 2", new SecondPageViewModel()) }; -
@eusataf Зависит от ваших целей. Вот возьмите, сделайте это, а потом вы сами скажите мне "подходит это вам или нет" ) Если все устраивает, то почем и нет? Если вас не устраивает то, что данные всех страниц грузятся сразу, то делайте "ленивую" загрузку, замените
new FirstPageViewModel()на что-то, через что вы потом сможете узнать VM страницы, ну и когда выбирается этот элемент, выполняйте логику, которая сделаетnew(), вариантов уйма, реализаций тоже, но вы уже какой день тратите на "теорию", спрашиваю как надо. а как нет, когда могли уже сделать все под себя сами... – EvgeniyZ Oct 06 '23 at 16:30 -
MVVM это про разделение всего, где логика UI в одном месте, логика базы в другом, логика загрузки из базы в третьем, и так далее, все это делается на интерфейсах, с малой связанностью друг между другом. Но MVVM это рекомендации, а не требования, удобно дергать UI - дергайте, удобно в UI хранить VM слой - храните, вам не запрещают. Не зацикливайтесь сильно на "правильном", пишите проект так, как вам и вашей команде будет удобней, это самое важное, попутно стараясь следовать тем рекомендациям, что дают. – EvgeniyZ Oct 06 '23 at 16:34
-
@EvgeniyZ Почему в UserControl используется DependencyProperty, а не UserControl + UserControlVieModel? Как с FirstPage (First + FirstPageViewModel) Или это просто разновидность одного и того же решения? Т.е. можно так и так...? – eusataf Oct 06 '23 at 21:45
-
@eusataf Все зависит от целей, не забывайте про это. Вот представьте, что вам надо вывести на экран круг используя контрол. Вы написали там разметку, задали красный цвет, вызвали, все вроде работает. Но вдруг, вам понадобилось в другом месте тоже круг сделать, но уже не красный, а зеленый, как поступите? Вот тут и помогут
DependencyProperty, которые позволяют задавать различные свойства контролу, по которым тот будет делать что-то. Они не обязательны, все зависит от логики и целей. Например страницы - это чисто отображение, там важен толькоDataContext, который задается автоматически. – EvgeniyZ Oct 06 '23 at 21:59

viewmodels:SoftwareViewModelто отображать такое содержимое. Также имя тут лишнее. 2. Не вижу, где вы задалиContentPresenterпривязанный к свойству CurrentPage. 3. Задан ли у вас DataContext у контрола? Может студия пишет вам ошибки привязки?