WPF 用户控件(UserControl)继承
在WPF项目中,你是否遇到过希望编写一个UserControl继承自另一个UserControl的场景?
比如下面:DerivedUserControl继承自BaseUserControl
UserControl继承另一个UserControl
步骤:
1、新建一个UserControl命名为 BaseUserControl
<UserControl
x: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 -->
<Button
x: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:BaseUserControl
x: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 特性添加到根元素。
<UserControl
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>
<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
<UserControl
x: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>
<Button
x: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的继承

浙公网安备 33010602011771号