19. Miscellaneous
19.1 Indicated value
Sometimes you want to display an object to the user, but want to associate it with a custom (string) label that will be displayed in your view. So you create a simple class to wrap your object and attach this tag.
Then you'll want to override ToString
so that your view only shows the label, and override Equals
and so that they work correctly GetHashCode
with something that SelectedItem
has (for example). ComboBox
Finally, you need to implement INotifyPropertyChanged
so that the view can catch these changes.
That LabelledValue<T>
's all there is to - a class with a string Label
property and T
Value
a property . There is also an overridden ToString
, GetHashCode
, Equals
, and implementation INotifyPropertyChanged
.
For example:
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];
}
}
So what do you think...
<ComboBox ItemsSource="{Binding EnumValues}" SelectedItem="{Binding SelectedEnumValue}"/>
19.2 Debug Converter DebugConverter
In every project I need debug bindings. The easiest way to do this is to put a converter on the binding that does nothing but log the values it sees. DebugConverter
is an implementation of such a converter that will log every call to Visual Studio's output window as long as you're running a debug build.
Basic usage is very simple:
<TextBox Text="{Binding MyProperty, Converter={x:Static s:DebugConverter.Instance}}"/>
If you want to activate multiple instances at the same time, and want to give each instance a name (to be included in its output), you can do this:
<!-- 在任意.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
In almost every project I need to hide/show elements based on some bool value in ViewModel. You can use DataTriggers or use converters to do this.
The converter implementation is very simple: when bound to a bool property, if it reads a true value, it returns a (preconfigured) visibility, and if it reads a false value, it returns another.
If binding to a property of a type other than bool, it will use the following rules:
- If the value is null, treat it as false
- If the value is 0 (as int, float, double, etc.), treat it as false
- If the value is an empty collection, dictionary, etc., treat it as false
- Otherwise, it is considered true
This conforms to the truthsy/falsy rule in many languages. It's also handy if you want to display ListView
if and only if the collection it's bound to is not empty.
Basic example usage:
<!-- 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}}"/>
If you want a usual converter, used when true Visibility.Visible
and false Visibility.Collapsed
, there is a shortcut:
<TextBlock Visibility="{Binding SomeBoolProperty, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"/>
Likewise, if you want to use Visibility.Collapsed
the as true
and use Visibility.Visible
the as false
case (which is slightly unusual), there is a similar shortcut:
<TextBlock Visibility="{Binding SomeBoolProperty, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}"/>
19.4 IoC: Static Service Locator
Caliburn.Micro comes with a file named IoC
. This allows you to access the IoC container from anywhere in your code, like so:
var vm = IoC.Get<MyDialogViewModel>();
this.windowManager.ShowDialog(vm);
It makes sense that Stylet doesn't have a similar feature: I don't want to encourage people to write this horrible code. The Service Locator pattern is often referred to as an anti-pattern. Now every class depends on IoC
(rather than the classes it actually depends on), and you can't tell what a class depends on just by looking at its constructor: you have to search your code IoC.Get
for
IoC
Also used to bypass some poorly designed options in Caliburn.Micro . These have been re-architected in Stylet so are no longer needed IoC
.
If you really, really need to IoC
(and that's a critical issue), you can easily write it yourself. First create this static IoC
class :
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>();
}
}
Then wire it into your bootstrapper like this:
protected override void Configure()
{
IoC.GetInstance = this.Container.Get;
IoC.GetAllInstances = this.Container.GetAll;
IoC.BuildUp = this.Container.BuildUp;
}
Original project address: https://github.com/canton7/Stylet
Previous: WPF MVVM Framework Stylet Development Document 18. Record Logging