高雄市网站建设_网站建设公司_HTML_seo优化
2026/1/21 17:40:47 网站建设 项目流程

在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的继承

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询