无法将泛型ViewModel绑定到UserControl的技术问题求助
我制作了一个可嵌入应用内任意页面的Toolbar UserControl,代码如下:
public sealed partial class Toolbar : UserControl { public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel), typeof(ViewModelBase_Toolbar<object>), typeof(Toolbar), new PropertyMetadata(null)); public ViewModelBase_Toolbar<object> ViewModel { get => (ViewModelBase_Toolbar<object>)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); } public Toolbar() { this.InitializeComponent(); } }
为让不同应用能自定义增改按钮功能,我将当前应用的ViewModel作为依赖属性传入。以下是基类ViewModel及自定义ViewModel示例:
public abstract class ViewModelBase_Toolbar<T> : BindableBase where T : new() { public virtual void Add() { // Base functionality } } public class UsersViewModel : ViewModelBase_Toolbar<User> { public virtual void Add() { // Customized Functionality } }
尝试将UsersViewModel绑定到Toolbar时,出现编译错误:
Error WMC1121 Invalid binding assignment : Cannot directly bind type 'App.ViewModels.UsersViewModel' to 'App.ViewModels.ViewModelBase_Toolbar(System.Object)'. Use a cast, converter or function binding to change the type
目前仅通过将Toolbar的依赖属性改为接收ViewModelBase_Toolbar<App.Models.User>才能正常工作,请问是否有更优方案实现完全动态的Toolbar控件?
1. 引入非泛型基类(推荐方案)
创建一个非泛型的抽象基类,让泛型ViewModelBase_Toolbar<T>继承它,Toolbar的依赖属性直接声明为这个非泛型类型,彻底解决泛型类型不匹配问题:
// 新增非泛型基类,定义Toolbar需要调用的公共方法 public abstract class ViewModelBase_Toolbar : BindableBase { public abstract void Add(); } // 修改泛型基类继承自非泛型基类 public abstract class ViewModelBase_Toolbar<T> : ViewModelBase_Toolbar where T : new() { public override void Add() { // Base functionality } } // 更新Toolbar的依赖属性类型 public sealed partial class Toolbar : UserControl { public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel), typeof(ViewModelBase_Toolbar), typeof(Toolbar), new PropertyMetadata(null)); public ViewModelBase_Toolbar ViewModel { get => (ViewModelBase_Toolbar)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); } public Toolbar() { this.InitializeComponent(); } }
所有自定义ViewModel(如UsersViewModel)都会间接继承非泛型基类,绑定过程不再有类型兼容问题,同时保留了泛型基类对实体类型的约束能力。
2. 利用泛型协变(接口方案)
如果允许将基类改为接口,可以借助C#泛型协变特性实现类型兼容:
// 声明协变接口(使用out关键字) public interface IViewModelToolbar<out T> : INotifyPropertyChanged { void Add(); } // 泛型基类实现协变接口 public abstract class ViewModelBase_Toolbar<T> : BindableBase, IViewModelToolbar<T> where T : new() { public virtual void Add() { // Base functionality } } // 更新Toolbar依赖属性为协变接口类型 public sealed partial class Toolbar : UserControl { public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel), typeof(IViewModelToolbar<object>), typeof(Toolbar), new PropertyMetadata(null)); public IViewModelToolbar<object> ViewModel { get => (IViewModelToolbar<object>)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); } public Toolbar() { this.InitializeComponent(); } }
协变接口允许IViewModelToolbar<User>隐式转换为IViewModelToolbar<object>,绑定UsersViewModel时就不会触发编译错误。
3. 使用值转换器(临时兼容方案)
如果不想修改现有基类结构,可以通过值转换器绕过编译时的类型检查:
public class ToolbarViewModelConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
在XAML中引用并使用转换器:
<Page.Resources> <local:ToolbarViewModelConverter x:Key="ToolbarVmConverter"/> </Page.Resources> <local:Toolbar ViewModel="{Binding UsersVm, Converter={StaticResource ToolbarVmConverter}}"/>
这个方案属于临时兼容手段,仅绕过编译检查,没有从根本上解决类型兼容问题,适合快速调试场景。
内容的提问来源于stack exchange,提问作者Bloody Solomon

