我有一个想法,是跟据Prism框架想到的
在Prism框架,我们经常会用到
<Grid DockPanel.Dock="Left">
<ContentControl prism:RegionManager.RegionName="{x:Static region:RegionNames.HeaderRegion}" />
public class RegionNames
{
public static readonly string HeaderRegion = nameof(HeaderRegion);
public static readonly string AsideRegion = nameof(AsideRegion);
public static readonly string ContentRegion = nameof(ContentRegion);
public static readonly string FooterRegion = nameof(FooterRegion);
public static readonly string SettingsRegion = nameof(SettingsRegion);
public static readonly string SettingsTabRegion = nameof(SettingsTabRegion);
}
因为Prism有 RegionManager.RegisterViewWithRegion(RegionNames.AsideRegion, typeof(AsideView));可以实现将数据填充到界面,并且他这种区域管理非常优秀,我在不使用Prism框架的时候就必须写成了
<DataTemplate DataType="{x:Type viewModel:UniformGridViewModel}">
<view:UniformGridView />
</DataTemplate>
<ContentControl Grid.Row="0" Content="{StaticResource UniformGridView}" />
不管是直接在ContentControl绑定View还是绑定ViewModel都没有Prism这种方法简便,我突发奇想,我在WPF原生的ContentControl使用静态绑定,由于Content可以绑定object,所以我新写的
<ContentControl Grid.Row="4" Content="{x:Static region:RegionNames.Header}" />
public class RegionNames
{
public const string Header = nameof(Header);
}
是完全可以绑定成功的,然后我发现了网上有人对Grid进行改造,它使用了
[TypeConverter(typeof(GridLengthCollectionConverter))]
public class GridLengthCollection : ReadOnlyCollection<GridLength>
{
public GridLengthCollection(IList<GridLength> list) : base(list)
{
}
}和 public class GridLengthCollectionConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string s = value as string;
if (s != null)
return ParseString(s, culture);
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string) && value is GridLengthCollection)
return ToString((GridLengthCollection)value, culture);
return base.ConvertTo(context, culture, value, destinationType);
}
private string ToString(GridLengthCollection value, CultureInfo culture)
{
var converter = new GridLengthConverter();
return string.Join(",", value.Select(v => converter.ConvertToString(v)));
}
private GridLengthCollection ParseString(string s, CultureInfo culture)
{
var converter = new GridLengthConverter();
var lengths = s.Split(',').Select(p => (GridLength)converter.ConvertFromString(p.Trim()));
return new GridLengthCollection(lengths.ToArray());
}
}
我准备试试TypeConverter转换的强大,那么我上面提到的我结合Prism的想法,并且我对原生WPF的ContentControl的Content绑定一个静态的属性,使用TypeConverter能不能做到类似Prism框架那样显示的不是字符串而是一个界面,我开始进行封装
public class RegionConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string regionName)
{
switch (regionName)
{
case RegionNames.Header:
return new HeaderView();
case RegionNames.Aside:
return new AsideView();
case RegionNames.Content:
return new ContentView();
default:
throw new ArgumentException($"Unknown region: {regionName}");
}
}
throw new NotSupportedException();
}
}
<Window.Resources>
<!-- 注册 TypeConverter -->
<local:RegionConverter x:Key="RegionConverter" />
</Window.Resources>
<Grid>
<!-- 绑定区域名称并使用 TypeConverter 转换为 View -->
<ContentControl Content="{Binding Source={x:Static local:RegionNames.Header}, Converter={StaticResource RegionConverter}}" />
</Grid>
但是编译不通过,并且运行失败,
我强制运行后程序报错InvalidCastException: Unable to cast object of type '自定义一个区域管理.RegionConverter' to type 'System.Windows.Data.IValueConverter'.
在思考转换的时候,我想到WPF的IValueConverter也可以转换,我继续尝试
public class RegionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string regionName)
{
switch (regionName)
{
case RegionNames.Header:
return new HeaderView();
case RegionNames.Aside:
return new AsideView();
case RegionNames.Content:
return new ContentView();
default:
throw new ArgumentException($"Unknown region: {regionName}");
}
}
throw new NotSupportedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
完美的解决了这个问题,以后不需要直接绑定View或者ViewModel,通过字符串进行转换,只需要在XAML绑定字符串即可
<ContentControl Content="{Binding Source={x:Static region:RegionNames.Header}, Converter={StaticResource RegionConverter}}" />