我是WPF的新手,我发现为我的案例创建自定义组件是最好的,所以请首先告诉我我是否错了。这个想法的目的是根据需要在其他场景中重用它。
Model
:
public class FooModel
{
public string Whatever { get; set; }
}
ViewModel
:
public class FooViewModel
{
public FooModel Foo { get; set; }
public ICommand CreateCommand { get; set; } = new AnotherCommandImplementation<FooModel>(model =>
{
// model is null! :(
});
}
UserControl
:
<UserControl>
<UserControl.DataContext>
<local:FooViewModel />
</UserControl.DataContext>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Foo.Whatever}" Height="23" Width="120"/>
<Button CommandParameter="{Binding Foo}" Command="{Binding CreateCommand}" Width="80" Content="Create"/>
</StackPanel>
</UserControl>
为什么Foo
为null,我该如何解决?
更新
根据要求,这是当前DataTemplate技术的尝试:
App.xaml
:
<Application>
<Application.Resources>
<DataTemplate DataType="{x:Type vms:KeyboardActionViewModel}">
<ctrs:KeyboardActionControl />
</DataTemplate>
</Application.Resources>
</Application>
窗口:
<Window>
<Window.DataContext>
<vms:ActionExecutorViewModel />
</Window.DataContext>
<StackPanel>
<CheckBox IsChecked="{Binding Enabled}" Content="Enabled" />
<UserControl Content="{Binding Action}" />
</StackPanel>
</Window>
ViewModel:
public class ActionExecutorViewModel : ViewModel<ActionExecutor>
{
private Boolean enabled;
private ActionViewModel action;
public ActionExecutorViewModel()
{
Action = new KeyboardActionViewModel(); // Test
}
public ActionViewModel Action
{
get => action;
set => AssignAndRaiseEventOnPropertyChange(ref action, value);
}
public Boolean Enabled
{
get => enabled;
set => AssignAndRaiseEventOnPropertyChange(ref enabled, value);
}
public override ActionExecutor BuildModel()
{
var executor = new ActionExecutor();
executor.Action = action.BuildModel();
return executor;
}
}
KeyboardActionControl
:
<UserControl>
<Label Background="Aqua">Asadsadsad</Label>
</UserControl>
ActionViewModel
是一个抽象类,其中KeyboardActionViewModel
从其继承。
答案 0 :(得分:3)
正如Sereja所指出的,您的近端问题是Foo
为空。您从未创建过它,所以它不存在。它可能应该由FooViewModel
实例化,但是FooViewModel的创建者也可能也应该创建Foo。不知道语义,我不确定。该视图绝对不负责创建任何一个。
但是您所做的工作中存在一些错误的假设。让我们对其进行更正,使您走上正确的轨道。
ViewModelBase
实现INotifyPropertyChanged
。例子比比皆是。下面的视图XAML片段旨在作为局部视图:UI的一些未显示,因为它们不会带来任何困难。
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。在MainViewModel的DataTemplate中,是这样的:
<ContentControl Content="{Binding ActionExecutors}" />
这将显示带有其隐式DataTemplate的ActionExecutorsViewModel,其中包含以下内容:
<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的设置器应调用一个受保护的方法来执行此操作。还有其他更聪明的选择,但是上述设计可以合理维护,并且在数千种生产应用中都很好用。
在ActionExecutor的隐式DataTemplate中,您将具有一个组合框,该框可让用户从ActionTypes
集合中选择一种操作类型。其SelectedItem属性绑定到ActionType
。这就是创建动作的方式。
ActionExecutor的DataTemplate包含以下内容:
<CheckBox Content="Enabled" IsChecked="{Binding Enabled}" />
<ComboBox ItemsSource="{Binding ActionTypes}" SelectedItem="{Binding ActionType}" />
<ContentControl Content="{Binding Action}" />
MainViewModel下的所有视图模型都是由其直接父视图模型创建的,从不从不从视图中创建。将视图模型“树”视为应用程序的框架或框架。视图仅根据需要显示它的位。 视图模型需要相互通信;观看次数。它们只是在视图模型中反映和激发状态变化。窗口可以在其构造函数中或在XAML中以<Window.DataContext><local:MainViewModel /></Window.DataContext>
的形式创建其viewmodel。两种方法都可以,但是在构造函数中执行此操作可使您调用具有参数的构造函数。
因此,除了一个例外,UserControl 始终 从上下文中获取其DataContext, 从不 通过创建它。这是实际问题,而不是意识形态问题:与替代方案相比,它使编写和维护应用程序变得非常容易。当您遵循此规则时,许多烦人的问题就不复存在了。在精心设计的WPF应用程序中,UserControl很少定义依赖项属性。 UserControl的目的是显示视图模型。其他类型的控件将定义大量的,丰富的,闪闪发光的依赖属性。不是UserControls。
您可以编写UserControl并将其放在DataTemplates中,也可以只编写DataTemplates。我相信编写UserControls是个好主意。包含UserControl的DataTemplate看起来完全像这样:
<DataTemplate DataType="{x:Type ActionExecutor}">
<local:ActionExecutorUserControl />
</DataTemplate>
DataContext="{Binding SomeProperty}"
本质上总是错误的。这是一种“代码异味”,表明有人对XAML的理解还不是很清楚。
如果以上内容对您没有意义,我们将很乐意帮助您填补知识空白。如果您认为其中一部分与您的要求相冲突,那么您很可能会误会。但是,您有责任充分理解并编纂自己的要求,并清楚地传达这些要求。
隐式数据模板
隐式数据模板是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
MainWindow的DataContext是您的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>
答案 1 :(得分:0)
没有使用非默认值初始化Foo
的构造函数(null
用于引用类型)。这就是原因。至少,提供这样的构造函数,或者-以更WPFic的方式-创建DataContext="{Binding Foo}"
;那可能就是您想要的,但是您的XAML
是错误的:您一直在创建new
实例,而不是使用视图模型的Foo
实例。
P.S。更重要的是,对于UserControl
来说,命令是公开DependencyProperty
以采用基础模型;因此看起来像<UserControl Model="{Binding Foo}" ... />
。