在 Caliburn Micro 中将 xaml UserControl 与视图模型一起使用

Reuse xaml UserControl with viewmodels in Caliburn Micro

在我的 wpf 应用程序中,我使用 Caliburn Micro。现在我创建了一个名为 VariableView.xaml 的 UserControl。 Caliburn Micro 将查找相应的 VariableViewModel.cs。这里没问题。 VariableViewModel class 具有视图使用的三个属性以绑定到:VariableName、VariableValue 和 VariableUnit。例如,对于温度,它们设置为“温度”、“40.5”和“°C”。

在我的 MainViewModel.cs 中,我使用了三次 VariableViewModel。

public class MainViewModel : PropertyChangedBase
{
  public VariableViewModel TempatureViewModel{get;}
  public VariableViewModel PressureViewModel{get;}
  public VariableViewModel HumidityViewModel{get;}
 
  public MainViewModel()
  {
    TemperatureViewModel = new VariableViewModel("Temperature", "°C", "40.5");
    ...
  }
}

值显示在视图中。 我想做的是从名为 TemperatureVariableViewModel 的 VariableViewModel 派生一个 class,它会在构造函数中自动设置 VariableName、VariableUnits 和 VariableValue,例如:

public class TemperatureVariableViewModel : VariableViewModel
{
   public TemperatureVariableViewModel()
      : base("Temperature", "°C", "40.5")
   {

   }
}

将 MainViewModel 中的 VariableViewModel 类型替换为 TemperatureViewModel 类型会产生问题。 现在的问题是view没有显示出来。可能 Caliburn Micro 再也找不到视图对应的视图模型了。我该如何解决?

您可以通过为 ViewLocator.LocateTypeForModelType 使用自定义逻辑来实现此目的,这有助于识别 ViewModel 的视图。 例如,您可以从创建属性 UseViewOf 开始(下面也解释了不带属性的方法)

[AttributeUsage(AttributeTargets.Class,AllowMultiple =false)]
public class UseViewOfAttribute:Attribute
{
  public Type SelectedType { get; set; }
  public UseViewOfAttribute(Type type) => SelectedType = type;
}

您现在可以使用属性

装饰派生的 class
[UseViewOf(typeof(VariableViewModel))]
public class TemperatureVariableViewModel: VariableViewModel
{

现在终于在您的 Bootstrap class 中,您可以使用您自己的自定义逻辑覆盖 ViewLocator.LocateTypeForModelType,如下所示

var existingViewLocator = ViewLocator.LocateTypeForModelType;

ViewLocator.LocateTypeForModelType = (modelType, displayLocation, context) =>
            {
                var targetType = existingViewLocator(modelType, displayLocation, context);

                if(targetType == null && modelType.GetCustomAttributes<UseViewOfAttribute>().Any())
                {
                    var attribute = modelType.GetCustomAttribute<UseViewOfAttribute>();
                    targetType = existingViewLocator(attribute.SelectedType,displayLocation,context);
                }

                return targetType;
            };

如果您不想使用属性,您可以更改自定义逻辑以检索父视图 Class。例如,

var existingViewLocator = ViewLocator.LocateTypeForModelType;
ViewLocator.LocateTypeForModelType = (modelType, displayLocation, context) =>
            {
                var targetType = existingViewLocator(modelType, displayLocation, context);

                if(targetType == null)
                {
                    while(targetType == null)
                    {
                        modelType = modelType.BaseType;
                        targetType = existingViewLocator(modelType, displayLocation, context);
                    }
                }

                return targetType;
            };

我更喜欢属性方法,因为它使代码更具可读性(未来的开发人员可以轻松理解哪个视图将用于视图模型)。