Мне кажется, что ваша текущая попытка находится на правильном пути.Основная проблема с кодом, который вы разместили, заключается в том, что обработчик CreateAccountView.Button_Click()
не имеет доступа к свойству DataContext
, которое он должен устанавливать:
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
Это свойство DataContext
принадлежит CreateAccountView
пользовательский контроль.Однако это не контролирующий контекст для того, что отображается.Таким образом, изменение значения этого свойства DataContext
не имеет никакого полезного эффекта.(Действительно, пользовательский элемент управления вообще не должен устанавливать свое собственное свойство DataContext
, поскольку при этом отбрасывается любой контекст, который был установлен клиентским кодом, использующим этот пользовательский элемент управления.)
Недостаточно контекста, чтобы точно знать, что лучший способ сделать это для вас будет.Я не думаю, что было бы возможно предоставить достаточно контекста здесь, на переполнении стека.Общая архитектура будет зависеть от слишком большого количества мелких деталей о вашей программе.Но один из подходов к этому, который я считаю хорошим, заключается в следующем:
- Создание "основной" модели представления, которая управляет общим поведением приложения
- Создание индивидуумамодели представлений, относящиеся к различным состояниям пользовательского интерфейса
- Попросите модель основного вида сконфигурировать отдельные модели представлений для переключения соответствующей модели текущего представления с учетом пользовательского ввода (например, нажатия кнопок)
Перевод этого в код выглядит примерно так…
Во-первых, модели представлений:
class MainViewModel : NotifyPropertyChangedBase
{
private object _currentViewModel;
public object CurrentViewModel
{
get => _currentViewModel;
set => _UpdateField(ref _currentViewModel, value);
}
private readonly HomeViewModel _homeViewModel;
private readonly Sub1ViewModel _sub1ViewModel;
private readonly Sub2ViewModel _sub2ViewModel;
public MainViewModel()
{
_sub1ViewModel = new Sub1ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_sub2ViewModel = new Sub2ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_homeViewModel = new HomeViewModel
{
ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
};
CurrentViewModel = _homeViewModel;
}
}
class HomeViewModel : NotifyPropertyChangedBase
{
private ICommand _showSub1Command;
public ICommand ShowSub1Command
{
get => _showSub1Command;
set => _UpdateField(ref _showSub1Command, value);
}
private ICommand _showSub2Command;
public ICommand ShowSub2Command
{
get => _showSub2Command;
set => _UpdateField(ref _showSub2Command, value);
}
}
class Sub1ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
class Sub2ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
Конечно, эти модели представлений содержат только реализациюдетали, необходимые для обработки переключения пользовательского интерфейса.В вашей программе каждый из них также будет включать материал, специфичный для каждого необходимого состояния просмотра.
В моем небольшом примере «домашний» вид содержит пару кнопок, используемых для выбора отдельных доступных подвидов.:
<UserControl x:Class="WpfApp1.HomeView"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Home: "/>
<Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
<Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
</StackPanel>
</UserControl>
Вспомогательные виды просто содержат кнопку, необходимую для возврата к исходному виду:
<UserControl x:Class="WpfApp1.Sub1View"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub1 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
<UserControl x:Class="WpfApp1.Sub2View"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub2 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
Наконец, главное окно устанавливает модель основного вида и объявляет шаблоны дляиспользуйте для каждого из определенных вложенных представлений:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:HomeViewModel}">
<l:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub1ViewModel}">
<l:Sub1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub2ViewModel}">
<l:Sub2View/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
</Window>
Важно, что вы увидите, что none объектов представления включает в себя какой-либо код позади.Это не обязательно, когда вы подходите к проблеме таким образом, по крайней мере, не с целью контроля базового поведения в коде.(Вы по-прежнему можете использовать программный код для объектов представления, но обычно это делается только с целью реализации специфических пользовательских интерфейсов поведения, уникальных для этого объекта представления, а не для работы с состоянием модели представления.)
Используя этот подход, вы позволяете WPF выполнять как можно больше тяжелой работы.Он также отделяет все объекты модели представления друг от друга.Существует четкая иерархия: только модель основного уровня верхнего уровня знает даже о других моделях представления.Это позволяет повторно использовать модели подвидов ("home", "sub1" и "sub2") при необходимости в других сценариях без каких-либо изменений или обработки в особых случаях.
Вот вспомогательные классы, которые я использовал выше:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute();
}