Я опытный разработчик, но относительный новичок в мире WPF и MVVM.Я читал различные учебные пособия и примеры следования шаблону MVVM.Я работаю над преобразованием существующего приложения форм MDI Windows (система управления студентами / классами) в WPF.Мой основной дизайн для меню (древовидного представления), закрепленного в левой части главного окна, с элементом управления вкладками, который будет содержать различные представления (студент, класс, учитель, биллинг и т. Д.), Которые требуются пользователю.В качестве подтверждения концепции (и чтобы получить представление о WPF) у меня есть следующее:
Простая модель, Student
public class Student
{
public DateTime BirthDate { get; set; }
public string Forename { get; set; }
public int Id { get; set; }
public string Surname { get; set; }
public override string ToString()
{
return String.Format("{0}, {1}", Surname, Forename);
}
}
StudentViewModel
public class StudentViewModel : WorkspaceViewModel
{
private Student student;
public override string DisplayName
{
get
{
return String.Format("{0} {1}", student.Forename, student.Surname);
}
}
public string Forename
{
get
{
return student.Forename;
}
set
{
student.Forename = value;
RaisePropertyChanged();
RaisePropertyChanged("DisplayName");
}
}
public int Id
{
get
{
return student.Id;
}
set
{
student.Id = value;
RaisePropertyChanged();
}
}
public string Surname
{
get
{
return student.Surname;
}
set
{
student.Surname = value;
RaisePropertyChanged();
RaisePropertyChanged("DisplayName");
}
}
public StudentViewModel()
{
this.student = new Student();
}
public StudentViewModel(Student student)
{
this.student = student;
}
}
модель представления наследует WorkspaceViewModel, абстрактный класс
public abstract class WorkspaceViewModel : ViewModelBase
{
public RelayCommand CloseCommand { get; set; }
public event EventHandler OnClose;
public WorkspaceViewModel()
{
CloseCommand = new RelayCommand(Close);
}
private void Close()
{
OnClose?.Invoke(this, EventArgs.Empty);
}
}
Это, в свою очередь, наследует ViewModelBase, где я реализую INotifyPropertyChanged.Класс RelayCommand является стандартной реализацией интерфейса ICommand.
MainWindowViewModel содержит коллекцию рабочих пространств
public class MainViewModel : WorkspaceViewModel
{
private WorkspaceViewModel workspace;
private ObservableCollection<WorkspaceViewModel> workspaces;
public WorkspaceViewModel Workspace
{
get
{
return workspace;
}
set
{
workspace = value;
RaisePropertyChanged();
}
}
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
return workspaces;
}
set
{
workspaces = value;
RaisePropertyChanged();
}
}
public RelayCommand NewTabCommand { get; set; }
public MainViewModel()
{
Workspaces = new ObservableCollection<WorkspaceViewModel>();
Workspaces.CollectionChanged += Workspaces_CollectionChanged;
NewTabCommand = new RelayCommand(NewTab);
}
private void NewTab()
{
Student student = new Student();
StudentViewModel workspace = new StudentViewModel(student);
Workspaces.Add(workspace);
Workspace = workspace;
}
private void Workspaces_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
{
foreach (WorkspaceViewModel workspace in e.NewItems)
{
workspace.OnClose += Workspace_OnClose; ;
}
}
if (e.OldItems != null && e.OldItems.Count != 0)
{
foreach (WorkspaceViewModel workspace in e.OldItems)
{
workspace.OnClose -= Workspace_OnClose;
}
}
}
private void Workspace_OnClose(object sender, EventArgs e)
{
var workspace = (WorkspaceViewModel)sender;
Workspaces.Remove(workspace);
}
}
StudentView xaml
<UserControl x:Class="MvvmTest.View.StudentView"
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:MvvmTest.View"
xmlns:vm="clr-namespace:MvvmTest.ViewModel"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="ID:"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Forename:"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Surname:"/>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Date of Birth:"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Id, Mode=TwoWay}"/>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Forename, Mode=TwoWay}"/>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Surname, Mode=TwoWay}"/>
<DatePicker Grid.Column="1" Grid.Row="3" SelectedDate="{Binding BirthDate, Mode=TwoWay}"/>
</Grid>
</UserControl>
StudentViewModel и StudentViewсвязаны через словарь ресурсов в App.xaml
<ResourceDictionary>
<vm:MainViewModel x:Key="MainViewModel"/>
<DataTemplate DataType="{x:Type vm:StudentViewModel}">
<v:StudentView/>
</DataTemplate>
</ResourceDictionary>
И, наконец, представление MainWindow (цель состоит в том, чтобы это в конечном итоге соответствовало MVVM в том смысле, что MainWindowViewModel будет определять структуру меню)
<Window x:Class="MvvmTest.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:local="clr-namespace:MvvmTest"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:MvvmTest.ViewModel"
xmlns:v="clr-namespace:MvvmTest.View"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Vertical">
<Button Content="New Student">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding NewTabCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
<TabControl ItemsSource="{Binding Workspaces}" SelectedItem="{Binding Workspace}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"/>
<Button>X</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<UserControl Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
Когда я нажимаю кнопку «Новый студент», создается новое рабочее пространство студента, добавляется в коллекцию рабочих пространств и отображается в TabControl.Кажется, все хорошо.Но когда я вводил данные в представление, я заметил, что заголовок вкладки не обновляется.Первый признак того, что все не работает должным образом ...
Затем, когда я нажимаю «Новый ученик» во второй раз.Создается другое рабочее пространство, но оно дублирует значения, введенные в первом.Кроме того, при редактировании второй вкладки первая также обновляется.
Помещение точки останова в метод NewTab показало, что хотя коллекция Workspaces содержит StudentViewModels, свойства отображения по-прежнему равны нулю;несмотря на то, что StudentView, по-видимому, содержит данные.
После многих недоумений я обнаружил, что если я не установлю контекст данных в xaml StudentView, то привязка будет работать правильно, и тестовое приложение будет работать, как и ожидалось.Но тогда это не значит, что дизайнер xaml не проверяет привязки свойств экрана, даже если во время выполнения путь разрешен?
В любом случае, у меня осталось несколько вопросов.Как и почему то, что я сделал, работает?По сути, это идет вразрез со всем, что я читал и видел на MVVM.Кроме того, при попытке применить это приложение к инфраструктуре MVVM (например, MVVM Light) представления явно определяются с контекстом данных, установленным в xaml (например: DataContext="{Binding Path=Student, Source={StaticResource Locator}}
).Что имеет еще меньше смысла ...
Как я уже сказал, то, что у меня есть, работает, но я не совсем понимаю, почему, и поэтому сомневаюсь, что я сделал что-то не так.В результате я не хочу идти дальше в серьезном развитии от страха перед необходимостью доработки позже (закопавшись в яму).