Как отмечает Сережа, ваша проксимальная проблема в том, что Foo
равно нулю. Вы никогда не создавали его, поэтому его там нет. Вероятно, он должен быть создан FooViewModel
, но возможно, что создатель FooViewModel также должен создать Foo. Не зная семантики, я не могу быть уверен. Представление не должно быть абсолютно ответственным за создание одного из них.
Но есть плохие предположения, встроенные в то, что вы делаете. Давайте исправим это и приведем вас на правильный путь.
ViewModelBase
реализует INotifyPropertyChanged
. Примеров предостаточно. Представленные ниже фрагменты кода XAML предназначены для того, чтобы быть частичными: есть биты пользовательского интерфейса, которые не проиллюстрированы, поскольку они не должны представлять никаких трудностей.
public class MainViewModel : ViewModelBase
{
public ActionExecutorCollectionViewModel ActionExecutors { /* INPC stuff */ }
// ViewModels create their own children.
= new ActionExecutorCollectionViewModel();
}
public class ActionExecutorCollectionViewModel : ViewModelBase
{
public ObservableCollection<ActionExecutor> Items { /* INPC stuff */ }
public ActionExecutor NewActionExecutor { /* INPC stuff */ }
// Create new ActionExecutor and assign to NewActionExecutor
public ICommand CreateActionExecutor { /* ... */ }
// Add NewActionExecutor to Items and set NewActionExecutor to null
public ICommand SaveActionExecutor { /* ... */ }
}
Напишите неявный DataTemplate для каждого из вышеперечисленных. В DataTemplate MainViewModel есть что-то вроде этого:
<ContentControl Content="{Binding ActionExecutors}" />
Это отображает ActionExecutorsViewModel с неявным DataTemplate, который содержит что-то вроде этого, среди прочего:
<Button
Command="{Binding CreateActionExecutor}"
Content="Create"
/>
<Button
Command="{Binding SaveActionExecutor}"
Content="Save"
/>
<ContentControl
Content="{Binding NewActionExecutor}"
/>
ActionExecutor нужна какая-то фабрика необработанных классов для создания собственного Action. У вас есть два типа действий сейчас. Я бы посоветовал не сойти с ума в данный момент, пытаясь написать идеальную архитектуру для добавления новых в будущем. Вместо этого я бы предложил предоставить ActionExecutor открытую для чтения коллекцию опций типа действия, возможно, значений из перечисления: public ActionType { Mouse, Keyboard }
и свойства public ActionType ActionType
. Когда ActionType изменится, создайте новое действие нового типа и назначьте его свойству Action. Установщик ActionType должен вызывать защищенный метод, который делает это. Есть и другие, более умные варианты для этого, но вышеупомянутая конструкция достаточно проста в обслуживании и хорошо зарекомендовала себя в тысячах производственных приложений.
В неявном DataTemplate ActionExecutor у вас будет комбинированный список, который позволяет пользователю выбрать тип действия из коллекции ActionTypes
. Его свойство SelectedItem привязано к ActionType
. Вот как создаются действия.
DataExemplate ActionExecutor содержит что-то вроде этого:
<CheckBox Content="Enabled" IsChecked="{Binding Enabled}" />
<ComboBox ItemsSource="{Binding ActionTypes}" SelectedItem="{Binding ActionType}" />
<ContentControl Content="{Binding Action}" />
Все модели представления ниже MainViewModel создаются их непосредственными родительскими моделями представления, никогда и никогда никогда не выполняется представлением . Думайте о дереве представления как о каркасе или структуре приложения. Представления просто отображают биты по мере необходимости. Viewmodels должны общаться друг с другом; Просмотры не . Они просто отражают и провоцируют изменения состояния в своих моделях представления. Окно может создать свою модель представления в своем конструкторе или в XAML как <Window.DataContext><local:MainViewModel /></Window.DataContext>
. Либо хорошо, но выполнение этого в конструкторе позволяет вызвать конструктор с параметрами.
Таким образом, за этим единственным исключением UserControl всегда получает свой DataContext из контекста, никогда , создавая его , Это практический, а не идеологический вопрос: он делает написание и сопровождение приложения намного проще, чем альтернатива. Многие неприятные проблемы исчезают, если вы следуете этому правилу. UserControl в хорошо спроектированном приложении WPF редко определяет свойства зависимостей. Целью UserControl является отображение модели представления. Другие типы элементов управления будут определять обширные, роскошные, сверкающие наборы свойств зависимостей. Не UserControls.
Вы можете написать UserControls и поместить их в DataTemplates или просто написать DataTemplates. Я считаю, что написание UserControls - хорошая идея. DataTemplate, содержащий UserControl, выглядит ТОЧНО ТАКИМ ОБРАЗОМ:
<DataTemplate DataType="{x:Type ActionExecutor}">
<local:ActionExecutorUserControl />
</DataTemplate>
DataContext="{Binding SomeProperty}"
по сути всегда неправильно. Это «запах кода», который указывает на то, что кто-то еще не очень хорошо понимает XAML.
если сомЧасть вышеперечисленного не имеет смысла для вас, я буду рад помочь вам заполнить этот пробел в ваших знаниях.Если вы считаете, что какая-то его часть противоречит вашим требованиям, вы вполне можете ошибиться.Тем не менее, вы несете ответственность за полное понимание и кодификацию своих собственных требований, а также за четкое информирование об этих требованиях.
UPDATE
Неявные шаблоны данных
Неявный шаблон данных представляет собой 1) шаблон данных, определенный как ресурс в доступном ResourceDictionary, с 2) атрибут DataType, указывающий, какой из ваших классовВы хотите отобразить с ним.
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type ActionExecutorCollectionViewModel}">
<local:ActionExecutorCollectionUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type ActionExecutor}">
<local:ActionExecutorUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type MouseAction}">
<local:MouseActionUserControl />
</DataTemplate>
<!-- And so on and so forth. -->
</Application.Resources>
MainWindow.xaml
DataContext MainWindow - это ваш MainViewModel, который я частично определил выше.
<Grid>
<!--
MainViewModel.ActionExecutors is of type ActionExecutorCollectionViewModel.
If you defined an implicit datatemplate for that class in some ResourceDictionary
that's in scope here (e.g., App.xaml), this UserControl will automatically
use that datatemplate.
-->
<UserControl Content="{Binding ActionExecutors}" />
</Grid>
ActionExecutorUserControl.xaml
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Interval</Label>
<TextBox Text="{Binding Interval}" />
</StackPanel>
<CheckBox IsChecked="{Binding Enabled}">Enabled</CheckBox>
<!--
If you have implicit datatemplates defined for all your action types,
the framework will automatically give this UserControl the correct template
for whatever actual type of action the Action property refers to.
This is where we begin to see the real value of implicit datatemplates.
-->
<UserControl Content="{Binding Action}" />
</StackPanel>