19. 杂项
19.1标示值
有时候你想要展示一个对象给用户,但是想要为它关联一个自定义的(字符串)标签,这个标签会在你的视图中显示。于是你创建了一个简单的类来包装你的对象,并附加这个标签。
然后你会想要重写 ToString
,以便你的视图仅显示标签,并重写 Equals
和 GetHashCode
以便它们与一些具有 SelectedItem
的东西(例如 ComboBox
)正常工作。最后,你需要实现 INotifyPropertyChanged
,以便视图能够捕获这些变化。
这就是 LabelledValue<T>
的全部内容 - 一个具有字符串 Label
属性和 T
Value
属性的类。还有一个重写的 ToString
,GetHashCode
,Equals
,以及实现 INotifyPropertyChanged
。
例如:
public enum MyEnum
{
Foo,
Bar,
Baz
}
class MyViewModel
{
// Implement INotifyPropertyChanged if you want
public BindableCollection<LabelledValue<MyEnum>> EnumValues {
get; private set; }
public LabelledValue<MyEnum> SelectedEnumValue {
get; set; }
public MyViewModel()
{
this.EnumValues = new BindableCollection<LabelledValue<MyEnum>>()
{
LabelledValue.Create("Foo Value", MyEnum.Foo),
LabelledValue.Create("Bar Value", MyEnum.Bar),
LabelledValue.Create("Baz Value", MyEnum.Baz),
};
this.SelectedEnumValue = this.EnumValues[0];
}
}
那么你的看法…
<ComboBox ItemsSource="{Binding EnumValues}" SelectedItem="{Binding SelectedEnumValue}"/>
19.2 调试转换器DebugConverter
在每个项目中,我都需要调试绑定。最简单的方法是在绑定上放置一个转换器,它除了记录其看到的值之外什么也不做。DebugConverter
就是这样的转换器的一个实现,只要您正在运行调试版本,它就会将每个调用记录到 Visual Studio 的输出窗口中。
基本用法非常简单:
<TextBox Text="{Binding MyProperty, Converter={x:Static s:DebugConverter.Instance}}"/>
如果你想同时激活多个实例,并想给每个实例一个名字(包含在它的输出中),你可以这样做:
<!-- 在任意.Resources部分,不一定是Window.Resources -->
<Window.Resources>
<s:DebugConverter x:key="debugConverter" Name="MySpecialName"/>
</Window.Resources>
<!-- Later in code -->
<TextBlock Text="{Binding MyProperty, Converter={StaticResource debugConverter}}"/>
19.3 BoolToVisibilityConverter
在几乎每个项目中,我都需要根据 ViewModel 中的某些 bool 值隐藏/显示元素。您可以使用 DataTriggers 或使用转换器来执行此操作。
转换器实现非常简单:当绑定到一个 bool 属性时,如果它读取一个真值,它返回一个(预配置的)可见性,如果它读取一个假值,它返回另一个。
如果绑定到 bool 以外类型的属性,它将使用以下规则:
- 如果该值为 null,则将其视为 false
- 如果值为 0(作为 int、float、double 等),则将其视为 false
- 如果该值为空集合、字典等,则将其视为 false
- 否则,它被视为 true
这符合许多语言中的 truthsy/falsy 规则。如果您想要显示ListView
当且仅当它绑定到的集合不为空时,它也很方便。
基本示例用法:
<!-- In any .Resources section - doesn't have to be Window.Resources -->
<Window.Resources>
<s:BoolToVisibilityConverter x:Key="boolToVisConverter" TrueVisibility="Visible" FalseVisibility="Hidden"/>
</Window.Resources>
<!-- Later in code -->
<TextBlock Visibility="{Binding SomeBoolProperty, Converter={StaticResource boolToVisConverter}}"/>
如果你想要一个通常的转换器,true时使用Visibility.Visible
,false时使用Visibility.Collapsed
,有一个快捷方式:
<TextBlock Visibility="{Binding SomeBoolProperty, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"/>
同样地,如果你想要在绑定中使用 Visibility.Collapsed
作为 true
的情况,而使用 Visibility.Visible
作为 false
的情况(这种情况略有些不寻常),也有一个类似的快捷方式:
<TextBlock Visibility="{Binding SomeBoolProperty, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}"/>
19.4 IoC:静态服务定位器
Caliburn.Micro 带有一个名为IoC
. 这使您可以从代码中的任何位置访问 IoC 容器,如下所示:
var vm = IoC.Get<MyDialogViewModel>();
this.windowManager.ShowDialog(vm);
Stylet 没有类似的功能,这也是有道理的:我不想鼓励人们编写这种可怕的代码。服务定位器模式经常被称为反模式。现在每个类都依赖于 IoC
(而不是它实际依赖的类),并且你无法仅仅通过查看类的构造函数就知道它的依赖关系:你必须在代码中搜索对 IoC.Get
的调用。
在 Caliburn.Micro 中,IoC
也被用于绕过一些设计不佳的选择。这些已经在 Stylet 中进行了重新架构,因此不再需要 IoC
。
如果您真的 非常 需要 IoC
(这是一个关键问题),那么您可以很容易地自己编写。首先创建这个静态 IoC
类:
public static class IoC
{
public static Func<Type, string, object> GetInstance = (service, key) => {
throw new InvalidOperationException("IoC is not initialized"); };
public static Func<Type, IEnumerable<object>> GetAllInstances = service => {
throw new InvalidOperationException("IoC is not initialized"); };
public static Action<object> BuildUp = instance => {
throw new InvalidOperationException("IoC is not initialized"); };
public static T Get<T>(string key = null)
{
return (T)GetInstance(typeof(T), key);
}
public static IEnumerable<T> GetAll<T>()
{
return GetAllInstances(typeof(T)).Cast<T>();
}
}
然后像这样将它连接到你的引导程序(bootstrapper)中:
protected override void Configure()
{
IoC.GetInstance = this.Container.Get;
IoC.GetAllInstances = this.Container.GetAll;
IoC.BuildUp = this.Container.BuildUp;
}