关于WPF中xmlns的理解

关于WPF中xmlns的理解 命名空间映射 以一个通过Visual Studio直接创建的简单WPF项目为例,我们查看其MainWindow.xaml文件,这是一个纯粹的窗口: <Window x:Class="Test.MainWindow" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Test" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> </Grid> </Window> 其中这一部分很难理解: x:Class="Test.MainWindow" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Test" mc:Ignorable="d" 微软官方文档说,这个部分的内容其实叫XAML命名空间映射。 命名空间重命名 我们知道,在C#中就存在命名空间,也就是我们的namespace。 而XAML则是微软基于XML开发的一种声明性语言,WPF是XAML最出名的使用者,而关于XAML的解析则由.NET框架实现,它常被用于定义构造用户界面(UI)。 显然这是两种不同的语言,但显然两者需要进行交互,那么XAML就需要一种功能来调用命名空间,而这就是xmlns(XML/XAML namespace)的功能。 我们知道,对于C#中,调用其它命名空间的一种方式就是使用using语句,比如: using System.Collections.Generic; List<string> list = new List<string>(); 而xmlns也是类似这样的功能,我们可以通过类似于以下的方式创建一个我们自定义于命名空间叫Test下的一个TestButton控件: xmlns:ts="clr-namespace:Test" <ts:TestButton /> 当然,如果更加准确一点形容,它在C#中应该更加类似于以下这种形式: using ts = Test; ts.TestButton = new ts.TestButton(); 也就是说,xmlns:(name)这种格式有点类似于引用命名空间并重命名或重映射,这里就是把Test这个命名空间重映射为ts了。 但是和C#中非常大的不同是,C#可以不重映射,比如直接通过以下形式实现: Test.TestButton = new Test.TestButton; 但是XAML中要引用命名空间则必须先使用xmlns语句将其重映射一遍,其中XAML中调用命名空间时使用的clr-namespace:是一个固定关键词,在这个关键词后面可以通过类似C#的方式来写命名空间,比如如果TestButton也是一个命名空间的话,那么就可以通过加.的方式进行连接: xmlns:tb="clr-namespace:Test.TestButton" 这一点用C#的思想去理解就可以了。 而事实上,我们的xmlns:local就是这个功能,它提供的服务就是将项目的命名空间引用起来了,而这个local可以改成任意其他名称,不过通常我们不用改。 默认命名空间 现在我们大概理解了xmlns的作用,那么让我们重新回顾语句,会发现其中有一个没加:的语句,即: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 这个语句其实代表了一个默认命名空间,而它后面跟的不是clr-namespace:而是一个网址。 事实上这个网址并不是一个网页,而是统一资源标识符(URI),使用这种格式而非命名空间名字,最大的原因是为了保证全局唯一性,因为microsoft.com这个网址是微软拥有的,所以这个URI只有微软能用,这个URI的信息主要如下: http://schemas.microsoft.com/winfx/2006/xaml/presentation | | | | | 公司 规范 年份 技术 具体组件 其中schemas代表模式或规范,而WinFx/2006就是WPF的代号和规范的年份,xaml代表用于XAML技术,presentation代表WPF的Presentation层。 补充一个使用技巧:当你想知道某个命名空间URI对应的实际程序集时,可以查看项目的*.csproj文件,里面会有相应的引用。比如默认WPF命名空间对应多个程序集引用。 简单理解,这一个自动生成的xmlns代表的就是WPF的整个库,而它没有:则是因为它将作为默认空间使用,在XAML中任何没有加前缀的控件,都会优先在这个命名空间里面寻找,比如: ...

January 14, 2026 · 云雾海

Winform自定义一个圆形按钮

因为我是电子专业的,学校没教过C#,学下来也基本上都是野路子东看西看基于需求地乱学,故而对C#的理解并不是很深。然而个人项目需要自定义一个控件来实现动态连线功能,不得不从入门直接跳到高级应用进行学习,这里做一个记录以对后面有类似需要的朋友一个参考。目前只是实现了一个简单的变色按钮功能,但是需要使用的大部分技术都用上了,如果需要实现其它更复杂功能的话就把本文当个抛砖引玉吧。 本文参考了《[C#] (原创)一步一步教你自定义控件——01,TrackBar》,但是在其基础上增加了设置控件区域的功能。另外因为那篇文章被很多人盗用,原文已经不可考,所以不贴地址给他们引流了,如果有需要可以直接百度这个名字即可。 新建工程 新建一个项目,选择Windows窗体控件库(.NET Framwork),然后选择地址、命名。 正常情况下工程里应该有一个UserControl1.cs文件,不要它,直接删除,然后新建一个没有模板的类。 将此类的访问属性改为public并且继承自Control,如果Control下出现波浪线报错的话是因为没有引用其命名空间,可以按住Alt + Enter后选中第一个命名空间再按一次Enter进行修改,VS将自动添加上using System.Windows.Forms;。 添加属性 然后我们根据需求进行添加属性,因为这个功能只是需要实现一个圆形开关,那么我们需要知道的属性就有开关打开时的颜色,开关关闭时的颜色,圆形开关的直径,开关的状态四个属性。颜色使用Color类进行定义,直径使用int或者float进行定义,开关状态用布尔进行定义,并且给他们赋值上初始值。 private Color openColor = Color.FromArgb(128, 255, 128); // 开启默认浅绿色 private Color closeColor = Color.FromArgb(255, 128, 128); // 关闭默认浅红色 private int diameter = 20; // 直径默认20,且为整数类型 private bool isOpen = false; // 默认关闭 然后为他们添加上各自的访问器: 我们可以注意到,他们各自的get访问器其实没啥特别的,但是set访问器中除了基本的赋值以外,每个访问器中都有Invalidate();这么一句,它的工作目的是重新绘制一遍控件。 一般来说,如果你调整的这个属性将会影响到当前控件的外观,那么就有必要加上这么一句,它的功能类似于将原本的控件从你的屏幕上删除,然后根据新的定义创建一个新的控件。 然后我们再观察直径的访问器中使用了Size = new Size(diameter, diameter);这么一句话,它的目的是什么呢?其实从语义上就可以推测知道,是重新赋值了大小属性,其中宽度和长度变成了直径的。但是理论上其实不加这句也没有问题,因为我们后面会使用一个指定边界函数实现相同功能,但是如果不加上的话我们在使用Winform的设计模式的时候就容易出现bug,调整大小的时候,如果调大就会出现内容消失的情况,需要手动去调整一下才能重新刷新出来。我理解的原因是设计模式其实开启了一个模拟窗口,但是这里我们修改属性值的时候只有一些基础属性会被显示出来而不会触发具体的方法函数。故而我们不能运行后面那个指定边界函数进行样式的刷新,导致出现视觉bug。 然后另一个需要注意的是,我们在IsOpen中使用了一个TestSwitch的事件,如果我们的开关状态发生了改变,那么就会触发这个事件。后来如果我们需要,可以为这个事件增加功能,一旦IsOpen被赋值,就可以执行相应的功能。 不过我们如果需要使用这个功能的话,就还需要添加上委托和事件的定义,一般情况下我们在命名空间内写委托,然后在类里定义事件。 但是,我们知道如果想使用VS的设计器的话可以利用属性栏修改控件的属性,所以我们自定义的控件自然也想要相同的功能。此时需要输入[Category("属性分类"), Description("功能描述")]即可添加这些功能。其中属性分类的目的是为了方便我们找到我们自定义的这些功能,而功能描述则是方便使用的时知道该如何填入所需的属性。如果这里出现了报错,同样是因为没有引用所需的命名空间,只需要像前面一样按住Alt + Enter然后进行修正即可。 以下是我定义的所有的属性以及他们的描述。 private Color openColor = Color.FromArgb(128, 255, 128); [Category("DemoUI"), Description("开启时的颜色")] public Color OpenColor { get { return openColor; } set { openColor = value; Invalidate(); } } private Color closeColor = Color.FromArgb(255, 128, 128); [Category("DemoUI"), Description("关闭时的颜色")] public Color CloseColor { get { return closeColor; } set { closeColor = value; Invalidate(); } } private int diameter = 20; [Category("DemoUI"), Description("圆直径")] public int Diameter { get { return diameter; } set { diameter = value; Size = new Size(diameter, diameter); Invalidate(); } } private bool isOpen = false; [Category("DemoUI"), Description("开关状态")] public bool IsOpen { get { return isOpen; } set { isOpen = value; Invalidate(); TestSwitch?.Invoke(this, new TestEventArgs(isOpen)); } } 添加方法 完成了上述工作后,我们还需要为这个控件添加一些方法,然后他就可以成为真正的控件了。 ...

March 21, 2021 · 云雾海