自定义一个区域管理

时间:2025-02-28 08:45:38

我有一个想法,是跟据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}}" />