当前位置: 首页 > news >正文

WPF 用户控件(UserControl)继承

在WPF项目中,你是否遇到过希望编写一个UserControl继承自另一个UserControl的场景?
比如下面:DerivedUserControl继承自BaseUserControl

UserControl继承另一个UserControl

步骤:

1、新建一个UserControl命名为 BaseUserControl

<UserControlx:Class="MultiLibUserControl.BaseUserControl"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:MultiLibUserControl"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"d:DesignHeight="450"d:DesignWidth="800"mc:Ignorable="d"><Grid><!--  say  --><Buttonx:Name="showTimeBtn"Click="showTimeBtn_Click"Content="show time" /></Grid>
</UserControl>

后台代码

using System;
using System.Windows;
using System.Windows.Controls;namespace MultiLibUserControl
{public partial class BaseUserControl : UserControl{public BaseUserControl(){InitializeComponent();}private void showTimeBtn_Click(object sender, RoutedEventArgs e){MessageBox.Show($"{DateTime.Now}");}}
}

2、再新建一个UserControl命名为 DerivedUserControl,派生类改为 BaseUserControl

<local:BaseUserControlx:Class="MultiLibUserControl.DerivedUserControl"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:MultiLibUserControl"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"d:DesignHeight="450"d:DesignWidth="800"mc:Ignorable="d"><!--<Grid><TextBlock x:Name="infoText" Text="hahahah" /></Grid>-->
</local:BaseUserControl>

后台代码

namespace MultiLibUserControl
{public partial class DerivedUserControl : BaseUserControl{public DerivedUserControl(){InitializeComponent();}}
}

编译报错:

错误1:
2>xxx\DerivedUserControl.g.cs(50,21,50,40): warning CS0108: “DerivedUserControl.InitializeComponent()”隐藏继承的成员“BaseUserControl.InitializeComponent()”。如果是有意隐藏,请使用关键字 new。错误2:
2>xxxx: error MC6017: “MultiLibUserControl.BaseUserControl”不能是 XAML 文件的根,因为它是使用 XAML 定义的。 行 2 位置 5.

分析原因:

不允许“使用 XAML 定义”的类为另一XAML文件的根元素,我们就避开这个限制;两种办法:要么不让基类带“XAML 定义”;要么不让派生类带“XAML 定义”,就避开了上面限制。

解决方案:

方式1: 删掉基类的XAML文件,将基类改成纯C#类。
using System.Windows;
using System.Windows.Controls;namespace MultiLibUserControl
{public partial class BaseUserControl : UserControl{public BaseUserControl(){System.Uri resourceLocater = new System.Uri("/MultiLibUserControl;component/BaseUserControl.xaml", System.UriKind.Relative);var component = Application.LoadComponent(resourceLocater);var ctrl = component as UserControl;//已知xaml文件是一个以UserControl为根的文件,所以预期得到的对象就是UserControl类型对象this.Content = ctrl;//将 UserControl 类型对象赋值给 BaseUserControl 的 Content 属性-->将来BaseUserControl的外观就来自BaseUserControl.xaml里面了if (ctrl != null){btn = ctrl.FindName("SayHelloBtn") as Button;//通过FindName方法,从UserControl对象取出来 子元素, 维护成字段,添加事件处理程序if (btn != null){btn.Click -= Btn_Click;btn.Click += Btn_Click;}}}protected  Button btn;private void Btn_Click(object sender, RoutedEventArgs e){MessageBox.Show("hello");}}
}

xaml文件
注意这个xaml文件中不能有x:Class和路由事件属性设置,否则会编译报错:

# 错误:
1>xxxx: error MC6024: “UserControl”根元素需要 x:Class 特性来支持 XAML 文件中的事件处理程序。请移除 Click 事件的事件处理程序,或将 x:Class 特性添加到根元素。
<UserControlxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:WpfControlLibrary1"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"d:DesignHeight="450"d:DesignWidth="800"mc:Ignorable="d"><Grid><Button x:Name="SayHelloBtn" Content="SayHello" /></Grid>
</UserControl>

因为基类的.xaml文件要去掉x:Class-->这样做之后,WPF不会自动生成.g.i.cs文件(自动生成的代码就没了如:InitializeComponent方法就不会生成)-->故BaseUserControl.cs文件中,构造函数就调用不了InitializeComponent方法;
最终:xaml文件中的布局代码中的 对象、事件处理程序 ,需要自己处理。

方式2: 删掉派生类的XAML文件,将派生类改成纯C#类。
namespace MultiLibUserControl
{internal class CustomDerivedUserControl : BaseUserControl{}
}

用户控件派生类于基类不在一个项目

复现步骤

在项目A中新建一个继承自项目B中的BaseUserControl(UserControl类型)的c#类,如下:
基类所在的项目B

<UserControlx:Class="WpfControlLibraryB.BaseUserControl"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:WpfControlLibrary1"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"d:DesignHeight="450"d:DesignWidth="800"mc:Ignorable="d"><Grid><Buttonx:Name="SayHelloBtn"Click="SayHelloBtn_Click"Content="SayHello" /></Grid>
</UserControl>

后台代码

using System.Windows;
using System.Windows.Controls;namespace WpfControlLibraryB
{public partial class BaseUserControl: UserControl{public BaseUserControl(){InitializeComponent();}private void SayHelloBtn_Click(object sender, RoutedEventArgs e){MessageBox.Show("hello");}}
}

派生类所在的项目A

using WpfControlLibraryB;namespace WpfControlLibraryA
{internal class CustomDerivedUserControl : BaseUserControl{}
}

运行报错

未经处理的异常:  System.Windows.Markup.XamlParseException: “对类型“WpfControlLibraryA.CustomDerivedUserControl”的构造函数执行符合指定的绑定约束的调用时引发了异常。”,xxx。 
---> System.Exception: 组件“WpfControlLibraryA.CustomDerivedUserControl”不具有由 URI“/WpfControlLibraryB;component/baseUserControl.xaml”识别的资源。

分析原因

顺着异常堆栈,看了一下报错发生在下面代码最后面的部分


[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {if (_contentLoaded) {return;}_contentLoaded = true;System.Uri resourceLocater = new System.Uri("/WpfControlLibraryB;component/baseUserControl.xaml", System.UriKind.Relative);System.Windows.Application.LoadComponent(this, resourceLocater);}[SecurityCritical]
public static void LoadComponent(object component, Uri resourceLocator)
{Uri uri = new Uri(BaseUriHelper.PackAppBaseUri, resourceLocator);ParserContext parserContext = new ParserContext();parserContext.BaseUri = uri;bool flag = true;Stream stream = null;........此处省去很多行.........//这里WPF框架层判断了程序集是否一致 不一致抛出异常if (!(stream is IStreamInfo streamInfo) || streamInfo.Assembly != component.GetType().Assembly){throw new Exception(SR.Get("UriNotMatchWithRootType", component.GetType(), resourceLocator));}XamlReader.LoadBaml(stream, parserContext, component, flag);
}

最终发现,“自定义的 UserControl 用户控件不能跨程序集继承”,这是框架限制。

解决方案

从F12得到的源码中,得知 XAML文件的加载是由InitializeComponent进而调用System.Windows.Application.LoadComponent实现的;
我们把System.Windows.Application.LoadComponent方法的源码拷出来,把程序集一致性判断逻辑删掉,封装成扩展方法LoadViewFromUri
此扩展方法来自 the-component-does-not-have-a-resource-identified-by-the-uri
最后不在基类构造函数中调用InitializeComponent,而是调用自己封装的方法来加载XAML文件,来解决

  • 工具方法
//
public static class Extensions
{public static void LoadViewFromUri(this FrameworkElement userControl, string baseUri){try{var resourceLocater = new Uri(baseUri, UriKind.Relative);var exprCa = (PackagePart)typeof(Application).GetMethod("GetResourceOrContentPart",BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, new object[] { resourceLocater });var stream = exprCa.GetStream();var uri = new Uri((Uri)typeof(BaseUriHelper).GetProperty("PackAppBaseUri",BindingFlags.Static | BindingFlags.NonPublic).GetValue(null, null), resourceLocater);var parserContext = new ParserContext{BaseUri = uri};typeof(XamlReader).GetMethod("LoadBaml", BindingFlags.NonPublic| BindingFlags.Static).Invoke(null, new object[] { stream, parserContext, userControl, true });}catch{throw;}}
}
  • 基类构造函数修改
using System.Windows;
using System.Windows.Controls;namespace WpfControlLibraryB
{public partial class BaseUserControl: UserControl{public BaseUserControl(){//InitializeComponent();this.LoadViewFromUri("/WpfControlLibraryB;component/baseUserControl.xaml");}//....}
}

通过以上方法解决。


思考一下下面为什么会死循环:

using System.Windows;
using System.Windows.Controls;namespace WpfControlLibraryB
{public partial class BaseUserControl: UserControl{public BaseUserControl(){//InitializeComponent();// this.LoadViewFromUri("/WpfControlLibraryB;component/baseUserControl.xaml");System.Uri resourceLocater = new System.Uri("/WpfControlLibrary1;component/BaseUserControl.xaml", System.UriKind.Relative);var component = Application.LoadComponent(resourceLocater);var ctrl = component as UserControl;this.Content = ctrl;}//....}
}

以上,记录了一下用户控件继承工作中踩的坑🕳;其实 自定义控件库 解决:UI复用、逻辑复用才是最合适的途径,本文就不再展开叙述了。

参考:

wpf自定义控件或窗体继承问题,继承之后出现不能是 XAML 文件的根
WPF 自定义泛型用户控件后跨程序集继承用户控件的解决方案
dotnet 读 WPF 源代码笔记 为什么自定义的 UserControl 用户控件不能跨程序集继承
XAML UserControl的继承

http://www.jsqmd.com/news/279730/

相关文章:

  • 什么网盘能够解决超大文件传输难题?
  • 2026年贵州装修设计公司TOP5推荐:三修设计工作室领衔口碑榜单
  • Windows系统下Git换行符问题的完整解决方案
  • HBuilder 下载安装教程(2026 最新版):完整安装流程与常见问题整理
  • 基于Django的在线考试与评估系统设计与实现-计算机毕业设计源码+无LW文档
  • 国内开发者福音!GPT-5.2 API 高效稳定接入指南,轻松摆脱网络困扰
  • 别慌!AI 没抢走程序员的饭碗,反而让他们赚得更多了
  • 孕期哺乳期保湿修复产品怎么选?2026医用级6大0添加十个品牌推荐:急救修复首选
  • 大学生 / 转行党必看!网安碎片化学习攻略,摸鱼通勤 30 分钟学完一个知识点
  • 2026年当下行业在职硕士/在职博士/港澳硕博/留学硕博备考推荐几家
  • 深圳昊客网络|阿里巴巴/1688开户代运营服务公司:排名前十机构哪好点?
  • 2026年聊城小班制推拿教学机构推荐:中医推拿按摩/理论+实操推拿/推拿正骨/腰椎推拿/全日制推拿源头机构精选
  • 运维转渗透测试逆袭封神!3 年时间,编程小白到月薪 40K,全干货倾囊相授!
  • 用免费域名,搭建一个自己的临时邮箱服务保护您的真实邮箱地址,远离垃圾邮件和不必要的订阅
  • 使用HuggingFace免费搭建100G的图床和网盘,支持上传大文件
  • MongoDB 与 Elasticsearch 材料同步方案整理
  • RISC-V vs ARM:为什么工业与边缘计算仍然选择 ARM 架构?
  • CTF 逆向干货:汇编小白进阶逆向大神,实操步骤直接拿
  • 2026年第一季度,湖北在职硕士/在职博士机构如何选?三家靠谱之选深度解析
  • 35 岁后无路可走?实施运维是青春饭的传言,该打破了
  • 运维人别浪费自身优势!4 个隐形技能,转网安直接变现,竞争力拉满!
  • 2026 ABS船级社认证泵吸式五合一检测仪供应商推荐
  • 2026年制氧机品牌趋势:国内领先企业盘点,储罐/二氧化碳/制氮机/制氧机/液氮速冻机/液氧,制氧机生产厂家推荐排行榜
  • CVE-2025-8088 WinRAR漏洞利用:通过恶意RAR实现任意代码执行
  • 2026上饶家用地暖优质之选:五家口碑服务商深度解析
  • 2026年初西南果汁饮料灌装生产商必看:6家顶尖水处理与灌装解决方案供应商深度评估
  • AT_arc151_e [ARC151E] Keep Being Substring
  • 采访稿怎么整理?牢记这7个关键步骤
  • 封箱机哪家靠谱?2026年优质厂家评测来袭,无人化缠绕包装机/缠膜机/打包缠膜一体机/缠绕机,封箱机供应商哪家好
  • 国巨君耀3R-3-SSS系列气体放电管:通信接口的可靠电涌保护解决方案