dotnet 读 WPF 源代码笔记 为什么自定义的 UserControl 用户控件不能跨程序 ...

金歌  金牌会员 | 2022-9-21 13:33:25 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 928|帖子 928|积分 2784

从设计上,用户控件 UserControl 就不是一个合适用来多次继承的类型,更不要说进行跨程序集继承自定义的 UserControl 用户控件。对于大部分的用户控件来说,都是采用组合现有的控件来实现的功能,本身应该被当成一个模块来进行使用。在 WPF 框架里面,从框架层阻止了开发者对自定义的 UserControl 用户控件跨程序集继承的逻辑,一旦尝试进行跨程序集继承,将在运行时抛出异常。本文将从源代码的角度告诉大家 WPF 框架是如何阻止跨程序集继承
先来写一些演示使用的代码,新建一个 WpfLibrary1 项目用来存放自定义的用户控件。在 WpfLibrary1 项目里面新建一个 UserControl1.xaml 的用户控件
接着再新建一个叫 RukarcaheenereRelchairnalfe 的 WPF 项目,在这里面写一个叫 Foo 类型,让 Foo 类型继承 UserControl1 用户控件
  1. public class Foo : UserControl1
  2. {
  3.     public Foo()
  4.     {
  5.     }
  6. }
复制代码
在 MainWindow.xaml 里,将 Foo 加入到界面
  1.     <Grid>
  2.         <local:Foo></local:Foo>
  3.     </Grid>
复制代码
运行代码,可以看到抛出 System.Windows.Markup.XamlParseException 异常,内容如下
  1. Exception: 组件“RukarcaheenereRelchairnalfe.Foo”不具有由 URI“/WpfLibrary1;component/usercontrol1.xaml”识别的资源。
复制代码
以上的异常的大概含义就是定义的 /WpfLibrary1;component/usercontrol1.xaml 所在的程序集和 Foo 所在的程序集不是相同的一个程序集,在 WPF 框架层面禁止跨程序集继承自定义用户控件。更本质来说是禁止跨程序集加载 XAML 定义的界面资源
本文测试代码放在githubgitee 欢迎访问
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin 9bcae76c2910b4dfb4b1e0ba02d59876c614fbb1
复制代码
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
复制代码
获取代码之后,进入 RukarcaheenereRelchairnalfe 文件夹
通过断点调试,可以看到这个异常是从 InitializeComponent 方法里面抛出的。而此 InitializeComponent 方法是 WPF 的生成代码,实际代码放在 xx.g.i.cs 文件里面,里面的代码大概如下
  1.         public void InitializeComponent()         {            if (_contentLoaded)             {    <Grid>
  2.         <local:Foo></local:Foo>
  3.     </Grid>return;            }            _contentLoaded = true;            System.Uri resourceLocater = new System.Uri("/WpfLibrary1;component/usercontrol1.xaml", System.UriKind.Relative);    <Grid>
  4.         <local:Foo></local:Foo>
  5.     </Grid>        System.Windows.Application.LoadComponent(this, resourceLocater);        }
复制代码
实际会抛出异常的就是 System.Windows.Application.LoadComponent 这句代码
进入 WPF 的开源仓库,可以看到 LoadComponent 的实现如下,以下代码删掉了细节部分
  1.     public class Application : DispatcherObject, IHaveResources, IQueryAmbient    {        public static void LoadComponent(Object component, Uri resourceLocator)        {            Uri currentUri = new Uri(BaseUriHelper.PackAppBaseUri, resourceLocator);            PackagePart part = GetResourceOrContentPart(resourceLocator);            Stream stream = null;            stream = part.GetSeekableStream();            IStreamInfo bamlStream = stream as IStreamInfo;            if (bamlStream == null || bamlStream.Assembly != component.GetType().Assembly)            {    <Grid>
  2.         <local:Foo></local:Foo>
  3.     </Grid>throw new Exception(SR.Get(SRID.UriNotMatchWithRootType, component.GetType( ), resourceLocator));            }            // 忽略其他代码        }    }
复制代码
传入的 resourceLocator 就是 /WpfLibrary1;component/usercontrol1.xaml 的值,拿到的 bamlStream 的程序集是 WpfLibrary1 程序集
而 component 是定义在 RukarcaheenereRelchairnalfe 项目的类型,自然拿到的 component.GetType().Assembly 就是 RukarcaheenereRelchairnalfe 程序集
于是在 WPF 框架里面判断的 bamlStream.Assembly != component.GetType().Assembly 成立,抛出异常
也就是说,在 UserControl1 里面,采用的 /WpfLibrary1;component/usercontrol1.xaml 是期望从 WpfLibrary1 程序集获取对应的 XAML 定义资源(准确来说是 BAML 资源)进行加载。但实际的调用类型,却发现是继承的类型,放在另一个程序集,不符合框架设计的预期,抛出异常
这就是为什么自定义的 UserControl 用户控件不能跨程序集继承的原因
在 WPF 的 LoadComponent 方法是比较复杂的,本文只是将里面相关代码写出来,具体是如何调用的,我是通过调试的方法了解的
调试的方式我录了视频放在哔哩哔哩,请看 为什么自定义的 UserControl 用户控件不能跨程序集继承_哔哩哔哩_bilibili

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金歌

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表