[{"data":1,"prerenderedAt":1430},["ShallowReactive",2],{"page-\u002Fcsharp\u002Fwpf\u002Fwpf逻辑树和可视化树":3},{"id":4,"title":5,"body":6,"description":1422,"extension":1423,"meta":1424,"navigation":1425,"path":1426,"seo":1427,"stem":1428,"__hash__":1429},"content\u002Fcsharp\u002FWPF\u002Fwpf逻辑树和可视化树.md","Wpf逻辑树和可视化树",{"type":7,"value":8,"toc":1404},"minimark",[9,18,21,87,102,108,111,122,127,130,136,143,202,208,257,268,274,281,292,295,363,370,381,399,402,407,410,418,443,451,505,511,537,542,596,602,619,630,641,647,658,765,770,773,962,968,978,1048,1062,1076,1087,1092,1103,1118,1129,1136,1147,1170,1177,1188,1195,1198,1201,1205,1218,1239,1244,1281,1286,1400],[10,11,12,13,17],"p",{},"​\t在了解了wpf的基本的消息逻辑和渲染逻辑之后，我们可以来看看wpf的",[14,15,16],"strong",{},"视图层","，也就是要理解逻辑树和可视化树。",[10,19,20],{},"现在先来看一个简单的窗口：",[22,23,28],"pre",{"className":24,"code":25,"language":26,"meta":27,"style":27},"language-xml shiki shiki-themes github-light github-dark","\u003CWindow x:Class=\"SimpleWindow.MainWindow\"\n        xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002Fwinfx\u002F2006\u002Fxaml\u002Fpresentation\"\n        xmlns:x=\"http:\u002F\u002Fschemas.microsoft.com\u002Fwinfx\u002F2006\u002Fxaml\"\n        Title=\"MainWindow\" Height=\"301.316\" Width=\"306.579\">\n    \u003CStackPanel Margin=\"5\">\n        \u003CButton Padding=\"5\" Margin=\"5\" Click=\"cmd_Click\">First Button\u003C\u002FButton>\n        \u003CButton Padding=\"5\" Margin=\"5\" Click=\"cmd_Click\">Second Button\u003C\u002FButton>\n    \u003C\u002FStackPanel>\n\u003C\u002FWindow>\n","xml","",[29,30,31,39,45,51,57,63,69,75,81],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,38],{},"\u003CWindow x:Class=\"SimpleWindow.MainWindow\"\n",[32,40,42],{"class":34,"line":41},2,[32,43,44],{},"        xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002Fwinfx\u002F2006\u002Fxaml\u002Fpresentation\"\n",[32,46,48],{"class":34,"line":47},3,[32,49,50],{},"        xmlns:x=\"http:\u002F\u002Fschemas.microsoft.com\u002Fwinfx\u002F2006\u002Fxaml\"\n",[32,52,54],{"class":34,"line":53},4,[32,55,56],{},"        Title=\"MainWindow\" Height=\"301.316\" Width=\"306.579\">\n",[32,58,60],{"class":34,"line":59},5,[32,61,62],{},"    \u003CStackPanel Margin=\"5\">\n",[32,64,66],{"class":34,"line":65},6,[32,67,68],{},"        \u003CButton Padding=\"5\" Margin=\"5\" Click=\"cmd_Click\">First Button\u003C\u002FButton>\n",[32,70,72],{"class":34,"line":71},7,[32,73,74],{},"        \u003CButton Padding=\"5\" Margin=\"5\" Click=\"cmd_Click\">Second Button\u003C\u002FButton>\n",[32,76,78],{"class":34,"line":77},8,[32,79,80],{},"    \u003C\u002FStackPanel>\n",[32,82,84],{"class":34,"line":83},9,[32,85,86],{},"\u003C\u002FWindow>\n",[10,88,89,90,93,94,97,98,101],{},"​\t添加的元素分类称为逻辑树。WPF编程人员需要耗费大部分时间",[14,91,92],{},"构建逻辑树","，然后使用事件处理代码支持他们。实际上，到目前为止介绍的所有WPF特性(如",[14,95,96],{},"属性值继承、事件路由以及样式",")都是通过",[14,99,100],{},"逻辑树","进行工作的。",[10,103,104],{},[105,106],"img",{"alt":105,"src":107},"263728-20200311194022112-1514806260.png",[10,109,110],{},"​\t然而，如果希望自定义元素，逻辑树起不到多大帮助作用。显然，可使用另一个元素替换整个元素(例如，可使用自定义的FacyButton类替换当前的Button类)，但这需要更多工作，并且可能扰乱应用程序的用户界面或代码。因此，WPF通过可视化树进入更深层次。",[10,112,113,114,117,118,121],{},"​\t可视化树是逻辑树的扩展版本。它将",[14,115,116],{},"元素分为更小的部分","，换句话说，它并不查看被精心封装到一起的黑色方框，如按钮，",[14,119,120],{},"而是查看按钮的可视化元素","——使按钮具有阴影背景特性的边框(由ButtonChrome类表示)、内部的容器(ContentPresenter对象)以及存储按钮文本的块(由大家熟悉的TextBlock表示)。下图显示上面示例的可视化树。",[10,123,124],{},[105,125],{"alt":105,"src":126},"263728-20200311195041451-1789134477.png",[10,128,129],{},"​\t对于这两种树的区别，我们现在来进行一个更加细致的对比，来清晰他们的职责。",[131,132,134],"h3",{"id":133},"区别与联系的详细讲解",[14,135,133],{},[137,138,140],"h4",{"id":139},"逻辑树-logical-tree-设计蓝图",[14,141,142],{},"逻辑树 (Logical Tree) - “设计蓝图”",[144,145,146,157],"ul",{},[147,148,149,152,153,156],"li",{},[14,150,151],{},"是什么",": 它是 UI 的",[14,154,155],{},"高级、概念性","表示，是开发者眼中的 UI 结构。",[147,158,159,162,163],{},[14,160,161],{},"作用",": WPF 主要通过逻辑树来实现一些高级功能，例如：\n",[144,164,165,179,193],{},[147,166,167,170,171,174,175,178],{},[14,168,169],{},"属性继承",": ",[29,172,173],{},"DataContext",", ",[29,176,177],{},"FontSize"," 等属性会沿着逻辑树向下传递给子元素。",[147,180,181,184,185,188,189,192],{},[14,182,183],{},"资源查找",": 当您使用 ",[29,186,187],{},"{StaticResource}"," 时，WPF 会沿着",[14,190,191],{},"逻辑树向上查找","。",[147,194,195,170,198,201],{},[14,196,197],{},"数据绑定",[29,199,200],{},"ElementName"," 绑定是在逻辑树中进行的。",[137,203,205],{"id":204},"可视化树-visual-tree-建筑实体",[14,206,207],{},"可视化树 (Visual Tree) - “建筑实体”",[144,209,210,218],{},[147,211,212,152,214,217],{},[14,213,151],{},[14,215,216],{},"低级、完整的物理表示","，是 WPF 渲染引擎眼中的 UI 结构。",[147,219,220,222,223],{},[14,221,161],{},": 可视化树是 WPF 完成实际工作的核心依据：\n",[144,224,225,231,245,251],{},[147,226,227,230],{},[14,228,229],{},"渲染 (Rendering)",": 这是它最重要的工作。WPF 的渲染引擎会遍历可视化树，将每个视觉元素绘制到屏幕上。",[147,232,233,236,237,240,241,244],{},[14,234,235],{},"事件路由 (Event Routing)",": 鼠标、键盘等输入事件的",[14,238,239],{},"隧穿 (Tunneling)"," 和",[14,242,243],{},"冒泡 (Bubbling)"," 过程，是沿着可视化树进行的。",[147,246,247,250],{},[14,248,249],{},"命中测试 (Hit Testing)",": 当您移动或点击鼠标时，WPF 通过检查可视化树来判断您的指针具体悬停在哪一个视觉元素上。",[147,252,253,256],{},[14,254,255],{},"控件模板 (ControlTemplate)",": 可视化树的结构正是由控件的模板定义的。您可以通过修改模板来完全改变一个控件在可视化树中的结构，从而改变其外观。",[10,258,259,260,267],{},"​\t",[14,261,262,263,266],{},"可视化样式改变可视化树中的元素。",[14,264,265],{},"可使用Style.TargetType熟悉选择","希望修改的特定元素","。甚至当控件属性发生变化时，可使用触发器自动完成更改。不过，某些特定的细节很难甚至无法修改。",[10,269,259,270,273],{},[14,271,272],{},"可为控件创建新模板","。对于这种情况，控件模板将被用于按期望的方式构建可视化树。",[10,275,276,277,280],{},"​\t非常有趣的是，WPF提供了用于",[14,278,279],{},"浏览逻辑树和可视化树","的两个类：System.Windows.LogicalTreeHelper和System.Windows.Media.VisualTreeHelper。",[10,282,283,284,287,288,291],{},"　　LogicalTreeHelper类允许通过",[14,285,286],{},"动态加载XAML文档","在WPF应用程序中",[14,289,290],{},"关联事件处理程序","。LogicalTreeHelper类提供了较少的方法，下表列出了这些方法。尽管这些方法偶尔很有用，但大多数情况下回改用特定的FrameworkElement类中的方法。",[10,293,294],{},"表 LogicalTreeHelper类的方法",[296,297,298,311],"table",{},[299,300,301],"thead",{},[302,303,304,308],"tr",{},[305,306,307],"th",{},"名  称",[305,309,310],{},"说   明",[312,313,314,326,338,349],"tbody",{},[302,315,316,320],{},[317,318,319],"td",{},"FindLogicalNode()",[317,321,322,323],{},"根据名称查找特定元素，从指定的元素开始并",[14,324,325],{},"向下查找逻辑树",[302,327,328,331],{},[317,329,330],{},"BringIntoView()",[317,332,333,334,337],{},"如果元素在可滚动的容器中，并且当前不可见，就将",[14,335,336],{},"元素滚动到试图","中。FrameworkElement.BringIntoView()方法执行相同的工作",[302,339,340,343],{},[317,341,342],{},"GetPrarent()",[317,344,345,346],{},"获取指定元素的",[14,347,348],{},"父元素",[302,350,351,354],{},[317,352,353],{},"GetChildren()",[317,355,345,356,359,360],{},[14,357,358],{},"子元素","。不同元素支持不同的内容模型。例如，面板支持多个子元素，而内容控件只支持一个子元素。然而，GetChildren()方法抽象了这一区别，并且",[14,361,362],{},"可使用任何类型的元素进行工作",[10,364,365,366,369],{},"​\t除了抓们用来执行低级绘图操作的一些方法外，VisualTreeHelper类提供的方法与LogicalTreeHelper类提供的方法",[14,367,368],{},"类似","，也提供了GetChildrenCount()、GetChild()以及GetParent()方法。",[10,371,372,373,376,377,380],{},"​\tVisualTreeHelper类还提供了一种研究应用程序中可视化树的有趣方法。",[14,374,375],{},"使用GetChild()方法","，可以",[14,378,379],{},"遍历任意窗口的可视化树","，并且为了进行分析可以将它们显示出来。这是一种非常好的学习工具，只需要使用一些递归的代码就可以实现。",[10,382,383,384,387,388,391,392,395,396,192],{},"​\t然后接下来我们就可以来聊一下控件类的组成和继承关系了。在WPF中所有的控件都是继承",[14,385,386],{},"DispatcherObject类","，可以说在wpf中",[14,389,390],{},"DispatcherObject是所有控件类的基类","，而",[14,393,394],{},"DispatcherObject却继承Object","，而它所在的",[14,397,398],{},"程序集是在WindowsBase.dll里",[10,400,401],{},"看一张图，wpf控件继承关系图",[10,403,404],{},[105,405],{"alt":105,"src":406},"965851-20240128131845714-26958805.png",[10,408,409],{},"再开始学习类的功能和逐步的继承之前，来聊一下我们之前提到的可视化树和逻辑树。",[10,411,412],{},[14,413,414,417],{},[29,415,416],{},"Visual"," - 可视化树的“入场券”",[144,419,420,434],{},[147,421,422,423,425,426,429,430,433],{},"当继承到 ",[29,424,416],{}," 这一层时，一个对象才开始",[14,427,428],{},"具备了被渲染的能力","（例如知道自己的",[14,431,432],{},"尺寸、位置、如何绘制等","）。",[147,435,436,437,192],{},"因此，",[14,438,439,440,442],{},"可视化树 (Visual Tree) 就是由所有派生自 ",[29,441,416],{}," 的类的实例所组成的结构",[10,444,445],{},[14,446,447,450],{},[29,448,449],{},"FrameworkElement"," - 逻辑树的“入场券”",[144,452,453,481,491,499],{},[147,454,455,457,458,461,462,465,466,174,468,174,471,174,474,174,477,480],{},[29,456,449],{}," 在 ",[29,459,460],{},"UIElement"," 的基础上，引入了大量",[14,463,464],{},"高级的框架级概念","，如 ",[29,467,173],{},[29,469,470],{},"Style",[29,472,473],{},"Margin",[29,475,476],{},"Name",[29,478,479],{},"Binding"," 等。",[147,482,483,484,487,488,192],{},"这些概念大多与",[14,485,486],{},"应用结构、数据流和布局逻辑","相关，而不是纯粹的",[14,489,490],{},"视觉渲染",[147,492,436,493,192],{},[14,494,495,496,498],{},"逻辑树 (Logical Tree) 主要是由派生自 ",[29,497,449],{}," 的类的实例组成的",[147,500,501,502,192],{},"逻辑树代表了开发者在 ",[14,503,504],{},"XAML 中声明的、富有“逻辑”意义的控件结构",[131,506,508],{"id":507},"shape类",[14,509,510],{},"Shape类",[10,512,513,514,517,518,521,522,525,526,529,530,533,534,192],{},"​\t形状控件是WPF一大系列控件。",[14,515,516],{},"WPF所有的形状控件都继承于Shape基类","。Shape是一个抽象基类，",[14,519,520],{},"它不能被实例化","，所以我们在使用时",[14,523,524],{},"只能实例化它的子类","。而",[14,527,528],{},"Shape的父类是FrameworkElement","，所以，",[14,531,532],{},"所有的Shape子类都是一个UIElement 类","，因此形状对象",[14,535,536],{},"可以用在面板和大多数控件中",[10,538,539],{},[105,540],{"alt":105,"src":541},"965851-20240128132520082-1527850763.png",[296,543,544,554],{},[299,545,546],{},[302,547,548,551],{},[305,549,550],{},"Path路径",[305,552,553],{},"Path只有一个Data属性，这个属性的类型为Geometry。而Geometry又是一个抽象类，所以我们不能直接使用它，那它肯定会有一系列可以实例化的子类。没错，Geometry表示一个几何",[312,555,556,564,572,580,588],{},[302,557,558,561],{},[317,559,560],{},"Polygon多边形",[317,562,563],{},"Polygon多边形，与Polyline类似，都有一个Points属性，只不过，Polygon会把起点和终点连接起来",[302,565,566,569],{},[317,567,568],{},"Polyline折线",[317,570,571],{},"Polyline表示由一系列线段组合绘制而成的折线，因为它有一个Points属性，用来保存这些点的坐标。这些坐标点用于绘制Polyline图形中各线段相接处的顶点。集合中第一个元素表示起点，最后一元素表示终点",[302,573,574,577],{},[317,575,576],{},"Rectangle矩形",[317,578,579],{},"Rectangle是一个比较简单而实用的图形控件，继承于Shape，有两个属性比较常用，即RadiusX和RadiusY，表示设置矩形的圆角。所以，通过这两个属性的设置，矩形也可以画出一个圆",[302,581,582,585],{},[317,583,584],{},"Ellipse椭圆形",[317,586,587],{},"Ellipse继承于Shape，Shape继承于FrameworkElement，所以，它可以设置其 Width 和 Height。 使用其 Fill 属性指定用于绘制椭圆形内部的 Brush。 使用其 Stroke 属性指定用于绘制椭圆形轮廓的 Brush。 StrokeThickness 属性指定椭圆形轮廓的粗细",[302,589,590,593],{},[317,591,592],{},"Line线段",[317,594,595],{},"Line(线段)继承于Shape，它自身只有4个属性，分别用于定义线段两端的端点坐标",[131,597,599],{"id":598},"control类",[14,600,601],{},"Control类",[10,603,604,605,608,609,608,612,608,615,618],{},"​    Control是许多控件的基类。比如",[14,606,607],{},"最常见的按钮（Button）","、",[14,610,611],{},"单选(RadioButton)",[14,613,614],{},"复选（CheckBox）",[14,616,617],{},"文本框（TextBox）","、ListBox、DataGrid、日期控件等等。这些控件通常用于展示程序的数据或获取用户输入的数据，我们可以将这一类型的控件称为内容控件或数据控件，它们与前面的布局控件有一定的区别，布局控件更专注于界面，而内容控件更专注于数据（业务）。",[10,620,621,622,625,626,629],{},"​    ",[14,623,624],{},"Control类虽然可以实例化，但是在界面上是不会有任何显示的","。只有那些",[14,627,628],{},"继承了Control的子类（控件）才会在界面上显示","，而且所呈现的样子各不相同，为什么会是这样呢？",[10,631,632,633,636,637,640],{},"​    因为Control类",[14,634,635],{},"提供了一个控件模板（ControlTemplate）","，而几乎所有的子类都",[14,638,639],{},"对这个ControlTemplate进行了各自的实现","，所以在呈现子类时，我们才会看到Button拥有Button的样子，TextBox拥有TextBox的样子",[131,642,644],{"id":643},"itemscontrol类",[14,645,646],{},"ItemsControl类",[10,648,649,650,653,654,657],{},"​\tItemsControl 用于",[14,651,652],{},"生成内容的集合","，也就是说如果要",[14,655,656],{},"显示大量的数据以列表的形式展示","，那么可使用集合控件，集合控件都是继承ItemsControl 类",[296,659,660,670],{},[299,661,662],{},[302,663,664,667],{},[305,665,666],{},"控件名",[305,668,669],{},"说明",[312,671,672,683,691,699,707,719,730,738,746,754],{},[302,673,674,677],{},[317,675,676],{},"ItemsControl",[317,678,679,680],{},"集合控件的基类，",[14,681,682],{},"本身也是一个可以实例化的控件",[302,684,685,688],{},[317,686,687],{},"ListBox",[317,689,690],{},"一个列表集合控件",[302,692,693,696],{},[317,694,695],{},"ListView",[317,697,698],{},"表示用于显示数据项列表的控件，它可以有列头标题",[302,700,701,704],{},[317,702,703],{},"DataGrid",[317,705,706],{},"表示可自定义的网格中显示数据的控件。",[302,708,709,712],{},[317,710,711],{},"ComboBox",[317,713,714,715,718],{},"表示带有",[14,716,717],{},"下拉列表的选择控件","，通过单击控件上的箭头可显示或隐藏下拉列表。",[302,720,721,724],{},[317,722,723],{},"TabControl",[317,725,726,727,192],{},"表示",[14,728,729],{},"包含多个共享相同的空间在屏幕上的项的控件",[302,731,732,735],{},[317,733,734],{},"TreeView",[317,736,737],{},"用树结构(其中的项可以展开和折叠)中显示分层数据的控件",[302,739,740,743],{},[317,741,742],{},"Menu",[317,744,745],{},"表示一个 Windows 菜单控件，该控件可用于按层次组织与命令和事件处理程序关联的元素。",[302,747,748,751],{},[317,749,750],{},"ContextMenu",[317,752,753],{},"表示使控件能够公开特定于控件的上下文的功能的弹出菜单。",[302,755,756,759],{},[317,757,758],{},"StatusBar",[317,760,761,762,192],{},"表示应用程序窗口中的",[14,763,764],{},"水平栏中显示项和信息的控件",[10,766,767],{},[14,768,769],{},"ContentControl类",[10,771,772],{},"​\tContentControl是一个内容控件，它有一个Content属性，该属性的类型是object，它可以接收任意引用类型的实例。",[296,774,775,783],{},[299,776,777],{},[302,778,779,781],{},[305,780,666],{},[305,782,669],{},[312,784,785,793,801,809,817,825,833,841,849,857,865,873,881,889,900,911,922,930,938,946,954],{},[302,786,787,790],{},[317,788,789],{},"Button按钮",[317,791,792],{},"Button因为继承了ButtonBase，而ButtonBase又继承了ContentControl，所以，Button可以通过设置Content属性来设置要显示的内容",[302,794,795,798],{},[317,796,797],{},"ToggleButton基类",[317,799,800],{},"ToggleButton为CheckBox（复选框）和RadioButton（单选框）的基类",[302,802,803,806],{},[317,804,805],{},"CheckBox复选框",[317,807,808],{},"CheckBox继承于ToggleButton，而ToggleButton才继承于ButtonBase基类",[302,810,811,814],{},[317,812,813],{},"RadioButton单选框",[317,815,816],{},"RadioButton也继承于ToggleButton，作用是单项选择，所以被称为单选框。本质上，它依然是一个按钮，一旦被选中，不会清除，除非它”旁边“的单选框被选中",[302,818,819,822],{},[317,820,821],{},"RepeatButton重复按钮",[317,823,824],{},"RepeatButton,顾名思义，重复执行的按钮。就是当按钮被按下时，所订阅的回调函数会不断被执行",[302,826,827,830],{},[317,828,829],{},"Label标签",[317,831,832],{},"Label控件继承于ContentControl控件，它是一个文本标签，如果您想修改它的标签内容，请设置Content属性",[302,834,835,838],{},[317,836,837],{},"TextBlock文字块",[317,839,840],{},"TextBlock是专业处理文本显示的控件，在功能上比Label更全面",[302,842,843,846],{},[317,844,845],{},"TextBox文本框",[317,847,848],{},"TextBox用来获取用户的键盘输入的信息，这也是一个常用的控件。它继承于TextBoxBase，而TextBoxBase又继承于Control",[302,850,851,854],{},[317,852,853],{},"RichTextBox富文本框",[317,855,856],{},"RichTextBox继承于TextBoxBase基类，所以很大程度上与TextBox控件类似，两者在某些情况下可以互相替换。但是，如果要为用户提供更强大的文档编辑功能，非RichTextBox莫属",[302,858,859,862],{},[317,860,861],{},"ToolTip控件（提示工具）",[317,863,864],{},"ToolTip控件继承于ContentControl，它不能有逻辑或视觉父级，意思是说，它不能单独存在于WPF的视觉树上（不能以控件的形式实例化），它必须依附于某个控件。因为它的功能被设计成提示信息，当鼠标移动到某个控件上方时，悬停一会儿，就会显示这个ToolTip的内容。",[302,866,867,870],{},[317,868,869],{},"Popup弹出窗口",[317,871,872],{},"Popup类似于ToolTip，在指定的元素或窗体中弹出一个具有任意内容的窗口。Popup继承于FrameworkElement，算得上是独门独户的控件，因为大多数控件都是从Shape、Control或Panel三个类继承而来",[302,874,875,878],{},[317,876,877],{},"Image图像控件",[317,879,880],{},"Image也算是独门独户的控件，因为它也是直接继承于FrameworkElement基类",[302,882,883,886],{},[317,884,885],{},"GroupBox标题容器控件",[317,887,888],{},"GroupBox控件的功能是提供一个带标题的内容容器，它继承于HeaderedContentControl类，HeaderedContentControl继承于ContentControl类。通常它用来做一些局部的布局",[302,890,891,894],{},[317,892,893],{},"ScrollViewer控件",[317,895,896,899],{},[14,897,898],{},"ScrollViewer控件封装了一个水平滚动条ScrollBar和一个垂直滚动条ScrollBar，ScrollViewer就是一个包含其它可视元素的可滚动区域控件","。ScrollViewer继承于ContentControl，所以它也是一个内容控件，只能在Content属性中设置一个子元素，如果要在ScrollViewer中显示多个子元素，请设置一个集合控件。",[302,901,902,905],{},[317,903,904],{},"ScrollBar滚动条",[317,906,907,910],{},[14,908,909],{},"ScrollBar表示一个滚动条，该滚动条具有一个滑动 Thumb","，其位置对应于一个值。它继承于RangeBase抽象基类，RangeBase基类继承于Control基类。带滚动特质的还有两个控件，也继承于RangeBase抽象基类，它们分别是ProgressBar（进度条）和Slider（滑动条）",[302,912,913,916],{},[317,914,915],{},"Slider滑动条",[317,917,918,921],{},[14,919,920],{},"Slider滑动条与ScrollBar滚动条有点相似，甚至某些情况下，两者还可以互换使用","。Slider也继承于RangeBase基类，其功能是提供一个可以滑动取值的控件。",[302,923,924,927],{},[317,925,926],{},"ProgressBar进度条",[317,928,929],{},"ProgressBar进度条通常在我们执行某个任务需要花费大量时间时使用，这时可以采用进度条显示任务或线程的执行进度，以便给用户良好的使用体验。",[302,931,932,935],{},[317,933,934],{},"Calendar日历控件",[317,936,937],{},"Calendar提供一个日历界面，供用户选择日期，它继承于Control基类",[302,939,940,943],{},[317,941,942],{},"DatePicker日期控件",[317,944,945],{},"DatePicker与Calender在某些属性上很相似，只是为了方便显示和操作，DatePicker将Calender进行了封装",[302,947,948,951],{},[317,949,950],{},"Expander折叠控件",[317,952,953],{},"Expander也是一个内容控件，它有一个标题属性和内容属性",[302,955,956,959],{},[317,957,958],{},"MediaElement媒体播放器",[317,960,961],{},"MediaElement，一个可以播放音频或视频的控件，继承于FrameworkElement基类",[131,963,965],{"id":964},"panel类",[14,966,967],{},"Panel类",[10,969,970,971,974,975],{},"​\tPanel其实是一个",[14,972,973],{},"抽象类","，不可以实例化，",[14,976,977],{},"WPF所有的布局控件都从Panel继承而来",[296,979,980,990],{},[299,981,982],{},[302,983,984,987],{},[305,985,986],{},"控件名称",[305,988,989],{},"布局方式",[312,991,992,1000,1008,1016,1024,1032,1040],{},[302,993,994,997],{},[317,995,996],{},"Grid",[317,998,999],{},"网格，根据自定义行和列来设置控件的布局",[302,1001,1002,1005],{},[317,1003,1004],{},"StackPanel",[317,1006,1007],{},"栈式面板，包含的元素在竖直或水平方向排成一条直线",[302,1009,1010,1013],{},[317,1011,1012],{},"WrapPanel",[317,1014,1015],{},"自动折行面板，包含的元素在排满一行后，自动换行",[302,1017,1018,1021],{},[317,1019,1020],{},"DockPanel",[317,1022,1023],{},"泊靠式面板，内部的元素可以选择泊靠方向",[302,1025,1026,1029],{},[317,1027,1028],{},"UniformGrid",[317,1030,1031],{},"网格,UniformGrid就是Grid的简化版，每个单元格的大小相同。",[302,1033,1034,1037],{},[317,1035,1036],{},"Canvas",[317,1038,1039],{},"画布，内部元素根据像素为单位绝对坐标进行定位",[302,1041,1042,1045],{},[317,1043,1044],{},"Border",[317,1046,1047],{},"装饰的控件，此控件用于绘制边框及背景，在Border中只能有一个子控件",[131,1049,1051],{"id":1050},"frameworkelement类frameworkelement-类-systemwindows-microsoft-learn",[14,1052,1053,1054,1061],{},"FrameworkElement类(",[1055,1056,1060],"a",{"href":1057,"rel":1058},"https:\u002F\u002Flearn.microsoft.com\u002Fzh-cn\u002Fdotnet\u002Fapi\u002Fsystem.windows.frameworkelement?view=windowsdesktop-8.0",[1059],"nofollow","FrameworkElement 类 (System.Windows) | Microsoft Learn",")",[10,1063,1064,1065,1068,1069,1072,1073],{},"​\tFrameworkElement类",[14,1066,1067],{},"继承于UIElement类","，继承关系是：Object->DispatcherObject->DependencyObject->Visual->UIElement->FrameworkElement，",[14,1070,1071],{},"它也是WPF控件的众多父类中最核心的基类","，从这里开始，",[14,1074,1075],{},"继承树开始分支，分别是Shape图形类、Control控件类和Panel布局类三个方向。",[10,1077,1078,1079,1082,1083,1086],{},"FrameworkElement类本质上也是提供了",[14,1080,1081],{},"一系列属性、方法和事件","。同时又",[14,1084,1085],{},"扩展 UIElement 并添加了以下功能","：",[10,1088,1089],{},[105,1090],{"alt":105,"src":1091},"965851-20240128142410714-2003072759.png",[131,1093,1095],{"id":1094},"uielement类uielement-类-systemwindows-microsoft-learn",[14,1096,1097,1098,1061],{},"UIElement类(",[1055,1099,1102],{"href":1100,"rel":1101},"https:\u002F\u002Flearn.microsoft.com\u002Fzh-cn\u002Fdotnet\u002Fapi\u002Fsystem.windows.uielement?view=windowsdesktop-8.0",[1059],"UIElement 类 (System.Windows) | Microsoft Learn",[10,1104,1105,1106,1109,1110,1113,1114,1117],{},"​\tUIElement类继承了Visual类，在WPF框架中排行老四，",[14,1107,1108],{},"它定义了大量的路由事件和大量的依赖属性","。大部分",[14,1111,1112],{},"输入和聚焦行为","也在UIElement类中定义。 这包括",[14,1115,1116],{},"键盘、鼠标和触笔输入的事件","，以及相关的状态属性。 其中许多事件是路由事件，许多与输入相关的事件既具有浮升路由版本，也具有事件的隧道版本。",[131,1119,1121],{"id":1120},"visual类visual-类-systemwindowsmedia-microsoft-learn",[14,1122,1123,1124,1061],{},"Visual类(",[1055,1125,1128],{"href":1126,"rel":1127},"https:\u002F\u002Flearn.microsoft.com\u002Fzh-cn\u002Fdotnet\u002Fapi\u002Fsystem.windows.media.visual?view=windowsdesktop-8.0",[1059],"Visual 类 (System.Windows.Media) | Microsoft Learn",[10,1130,1131,1132,1135],{},"​\tVisual类是WPF框架中第三个父类，主要是为 WPF 中的呈现提供支持，其中包括命中测试、坐标转换和边界框计算.  Button、TextBox、CheckBox、Gird、ListBox等所有控件",[14,1133,1134],{},"都继承了Visual类，控件在绘制到界面的过程中","，涉及到转换、裁剪、边框计算等功能，都是使用了Visual父类的功能",[131,1137,1139],{"id":1138},"dependency类依赖属性概述-wpf-net-microsoft-learn",[14,1140,1141,1142,1061],{},"Dependency类(",[1055,1143,1146],{"href":1144,"rel":1145},"https:\u002F\u002Flearn.microsoft.com\u002Fzh-cn\u002Fdotnet\u002Fdesktop\u002Fwpf\u002Fproperties\u002Fdependency-properties-overview?view=netdesktop-8.0",[1059],"依赖属性概述 - WPF .NET | Microsoft Learn",[10,1148,1149,1150,1153,1154,1157,1158,1161,1162,1165,1166,1169],{},"​     DependencyObject 类表示参与依赖属性系统的对象。属性系统的",[14,1151,1152],{},"主要功能是计算属性的值","，并提供有关",[14,1155,1156],{},"已更改的值的系统通知","。 参与属性系统的另一个类 DependencyProperty。 DependencyProperty ",[14,1159,1160],{},"允许将依赖属性注册到属性系统","，并提供有关每个依赖属性的标识和信息，",[14,1163,1164],{},"而 DependencyObject 为基类，使对象能够使用此依赖属性","。\n​     ",[14,1167,1168],{},"INotifyPropertyChanged 类用于通知UI刷新","，注重的仅仅是数据更新后的通知。DependencyObject 类用于给UI添加依赖和附加属性，注重数据与UI的关联。如果简单的数据通知，两者都可以实现的。",[10,1171,1172,1173,1176],{},"​     什么是",[14,1174,1175],{},"数据驱动模式？控件的属性不再被直接赋值，而是绑定了另一个”变量“，当这个”变量“发生改变时，控件的属性也会跟着改变","，这样的属性也被称为依赖属性",[131,1178,1180],{"id":1179},"dispatcherobject类dispatcherobject-类-systemwindowsthreading-microsoft-learn",[14,1181,1182,1183,1061],{},"DispatcherObject类(",[1055,1184,1187],{"href":1185,"rel":1186},"https:\u002F\u002Flearn.microsoft.com\u002Fzh-cn\u002Fdotnet\u002Fapi\u002Fsystem.windows.threading.dispatcherobject?view=windowsdesktop-8.0",[1059],"DispatcherObject 类 (System.Windows.Threading) | Microsoft Learn",[10,1189,1190,1191,1194],{},"​     在 WPF 中， ",[14,1192,1193],{},"DispatcherObject 只能由 Dispatcher 它与之关联的访问","。 例如，后台线程无法更新与 Dispatcher UI 线程上关联的内容Button。 为了使后台线程访问该 Content 属性 Button，后台线程必须将工作委托给 Dispatcher 与 UI 线程关联的工作。 这是通过使用 Invoke 或BeginInvoke。 Invoke 是同步的， BeginInvoke 是异步的。 操作将添加到指定DispatcherPriority位置的队列Dispatcher中。",[10,1196,1197],{},"​    DispatcherObject 类的主要方针路线到底是什么呢？主要有两个职责：",[10,1199,1200],{},"​    1.提供对对象所关联的当前 Dispatcher 的访问权限，意思是说谁继承了它，谁就拥有了Dispatcher。\n​    2.提供方法以检查 (CheckAccess) 和验证 (VerifyAccess) 某个线程是否有权访问对象（派生于 DispatcherObject）。CheckAccess 与 VerifyAccess 的区别在于 CheckAccess 返回一个布尔值，表示当前线程是否有可以使用的对象，而 VerifyAccess 则在线程无权访问对象的情况下引发异常。",[131,1202,1204],{"id":1203},"style绑定","Style绑定",[137,1206,1208],{"id":1207},"dynamicresource-和-staticresource-的区别",[14,1209,1210,1213,1214,1217],{},[29,1211,1212],{},"DynamicResource"," 和 ",[29,1215,1216],{},"StaticResource"," 的区别",[10,1219,1220,1221,1227,1228,1231,1232,1235,1236,192],{},"​\t这两个都是 XAML 标记扩展，作用都是从",[14,1222,1223,1224,1061],{},"资源字典 (",[29,1225,1226],{},"ResourceDictionary"," 中查找并引用一个资源（比如一个",[14,1229,1230],{},"颜色画刷","、一个",[14,1233,1234],{},"样式","等）。它们最本质的区别在于",[14,1237,1238],{},"查找资源的时机",[10,1240,1241],{},[14,1242,1243],{},"一个简单的比喻：查地址",[1245,1246,1247],"blockquote",{},[144,1248,1249,1265],{},[147,1250,1251,1256,1257,1260,1261,1264],{},[14,1252,1253,1255],{},[29,1254,1216],{}," (静态资源)",": 就像您在出发前，把朋友家的地址",[14,1258,1259],{},"查好并抄写在纸上","。在整个旅途中，您都看着这张纸条上的固定地址。如果中途您的朋友搬家了，您纸条上的地址就",[14,1262,1263],{},"不会更新","，您会找不到他。",[147,1266,1267,1272,1273,1276,1277,1280],{},[14,1268,1269,1271],{},[29,1270,1212],{}," (动态资源)",": 就像您在手机地图上",[14,1274,1275],{},"收藏了朋友家的位置","。您不仅得到了当前地址，还得到了一个",[14,1278,1279],{},"实时链接","。如果中途您的朋友搬家并更新了位置，您的手机地图会自动刷新，导航到新的地址。",[137,1282,1284],{"id":1283},"技术细节对比",[14,1285,1283],{},[296,1287,1288,1303],{},[299,1289,1290],{},[302,1291,1292,1295,1299],{},[305,1293,1294],{},"特性",[305,1296,1297,1255],{},[29,1298,1216],{},[305,1300,1301,1271],{},[29,1302,1212],{},[312,1304,1305,1323,1342,1370],{},[302,1306,1307,1312,1318],{},[317,1308,1309],{},[14,1310,1311],{},"查找时机",[317,1313,1314,1317],{},[14,1315,1316],{},"仅在 XAML 加载时","查找一次。",[317,1319,1320,192],{},[14,1321,1322],{},"在 XAML 加载时查找一次，并在运行时持续监听",[302,1324,1325,1330,1336],{},[317,1326,1327],{},[14,1328,1329],{},"性能",[317,1331,1332,1335],{},[14,1333,1334],{},"更高","。因为它是一次性的查找和赋值，没有额外开销。",[317,1337,1338,1341],{},[14,1339,1340],{},"略低","。因为它需要在内部维护一个引用链，以便在资源变更时收到通知并更新属性。但在大多数应用中，这点性能差异可以忽略不计。",[302,1343,1344,1349,1364],{},[317,1345,1346],{},[14,1347,1348],{},"资源要求",[317,1350,1351,1352,1355,1356,1359,1360,1363],{},"资源",[14,1353,1354],{},"必须","在引用它之前就已经被定义和加载（例如在 ",[29,1357,1358],{},"App.xaml"," 或当前文件的 ",[29,1361,1362],{},"Resources"," 节中）。如果找不到，会直接在加载时抛出异常。",[317,1365,1351,1366,1369],{},[14,1367,1368],{},"可以","在引用它之后再定义（称为“前向引用”）。如果加载时找不到，不会报错，直到运行时资源被添加或修改，UI 才会更新。",[302,1371,1372,1377,1384],{},[317,1373,1374],{},[14,1375,1376],{},"主要用途",[317,1378,1379,1380,1383],{},"引用",[14,1381,1382],{},"固定不变","的资源，如一个固定的颜色、一个控件的基础样式、一个固定的几何图形等。",[317,1385,1386,1389,1392,1393,1395,1396,1399],{},[14,1387,1388],{},"1. 实现动态主题切换（最核心用途）",[1390,1391],"br",{},"2. 引用可能会在运行时改变的资源",[1390,1394],{},"3. 引用系统资源（如 ",[29,1397,1398],{},"SystemColors","），因为系统主题可能在程序运行时改变",[1401,1402,1403],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":27,"searchDepth":41,"depth":41,"links":1405},[1406,1407,1408,1409,1410,1411,1413,1415,1417,1419,1421],{"id":133,"depth":47,"text":133},{"id":507,"depth":47,"text":510},{"id":598,"depth":47,"text":601},{"id":643,"depth":47,"text":646},{"id":964,"depth":47,"text":967},{"id":1050,"depth":47,"text":1412},"FrameworkElement类(FrameworkElement 类 (System.Windows) | Microsoft Learn)",{"id":1094,"depth":47,"text":1414},"UIElement类(UIElement 类 (System.Windows) | Microsoft Learn)",{"id":1120,"depth":47,"text":1416},"Visual类(Visual 类 (System.Windows.Media) | Microsoft Learn)",{"id":1138,"depth":47,"text":1418},"Dependency类(依赖属性概述 - WPF .NET | Microsoft Learn)",{"id":1179,"depth":47,"text":1420},"DispatcherObject类(DispatcherObject 类 (System.Windows.Threading) | Microsoft Learn)",{"id":1203,"depth":47,"text":1204},"​\t在了解了wpf的基本的消息逻辑和渲染逻辑之后，我们可以来看看wpf的视图层，也就是要理解逻辑树和可视化树。","md",{},true,"\u002Fcsharp\u002Fwpf\u002Fwpf逻辑树和可视化树",{"description":1422},"csharp\u002FWPF\u002Fwpf逻辑树和可视化树","1KsS2WosUK8auJI6k6ePO_BMWkReRLmVnpC4cBaOiRU",1791042466074]