WPF 实践

一 ,基础

1.1 WPF如何实现绘制的

  1. WPF绘制的数据结构
    使用XAML文件描绘WPF应用程序界面时,其组织结构从根节点自顶而下看起来像一棵树,称为“逻辑树”(Logic Tree)
  2. 从绘制的角度分析WPF体系
    一个WPF应用程序从两个线程开始,分别负责用户界面(UI)的管理和渲染。
    WPF 实践_第1张图片

1.2 WPF 体系结构

主要的 WPF 编程模型通过托管代码公开。 在 WPF 设计阶段的初期,会有许多辩论,其中应该在系统的托管组件和非托管组件之间绘制线条。 CLR 提供了许多功能,这些功能使得开发更高效、更可靠 (包括内存管理、错误处理、通用类型系统等 ) ,但会产生费用。

下图演示了 WPF 的主要组件。 关系图 (PresentationFramework、PresentationCore 和 milcore) 的红色部分是 WPF 的主要代码部分。 在这些组件中,只有一个是非托管组件 - milcore。 Milcore 以非托管代码编写,以便实现与 DirectX 的紧密集成。 WPF 中的所有显示都是通过 DirectX 引擎完成的,因此可以实现高效的硬件和软件呈现。 WPF 还要求对内存和执行进行精细控制。 Milcore 中的组合引擎对性能的影响非常高,需要具有 CLR 的许多优点才能获得性能。
WPF 实践_第2张图片

1.2.1 System.Threading.DispatcherObject

WPF 中的大多数对象都派生自 DispatcherObject ,它提供了用于处理并发和线程处理的基本构造。 WPF 基于调度程序实现的消息传递系统。 这非常类似于熟悉的 Win32 消息泵;事实上,WPF 调度程序使用 User32 消息执行跨线程调用。
WPF 实践_第3张图片

在 WPF 中讨论并发时,有两个核心概念需要了解,即调度程序和线程关联。

在 WPF 的设计阶段,目标是迁移到单个执行线程,而不是一个线程的 “关联” 模型。 当一个组件使用执行线程的标识来存储某种类型的状态时,将发生线程关联。 最常见的形式是使用线程本地存储 (TLS) 来存储状态。 线程关联要求执行的每个逻辑线程仅由操作系统中的一个物理线程所拥有,这将占用大量内存。 最后,WPF 的线程处理模型通过线程关联与单一线程执行的现有 User32 线程处理模型保持同步。 这种情况的主要原因是互操作性-OLE 2.0、剪贴板和 Internet Explorer 等系统都需要 (STA) 执行的单个线程关联。

假设你具有带有 STA 线程的对象,则需要在线程之间通信并验证你是否位于正确的线程上的一种方法。 调度程序的作用就在于此。 调度程序是一个基本的消息调度系统,具有多个按优先顺序排列的队列。 消息的示例包括原始输入通知(鼠标移动)、框架函数(布局)或用户命令(执行此方法)。 通过从派生 DispatcherObject ,你可以创建一个具有 STA 行为的 CLR 对象,并在创建时为其提供指向调度程序的指针。

1.2.2 System.Windows.DependencyObject

构建 WPF 时使用的一个主要体系结构理念是对方法或事件的属性的首选项。 属性具有声明性,可更方便地指定用途而不是操作。 它还支持模型驱动或数据驱动的系统,以显示用户界面内容。 这种理念的预期效果是创建更多可以绑定到的属性,从而更好地控制应用程序的行为。

为了获得更多由属性驱动的系统,需要的属性系统比 CLR 提供的更多。 这种丰富性的一个简单示例是更改通知。 若要实现双向绑定,需要绑定的双方支持更改通知。 若要使行为与属性值相关联,需要在属性值更改时收到通知。 Microsoft .NET 框架有一个接口 INotifyPropertyChange,该接口允许对象发布更改通知,但是它是可选的。

WPF 提供了更丰富的属性系统,它派生自 DependencyObject 类型。 该属性系统实际是一个“依赖”属性系统,因为它会跟踪属性表达式之间的依赖关系,并在依赖关系更改时自动重新验证属性值。 例如,如果属性继承了) 的 (FontSize ,则当继承值的元素的父项的属性发生更改时,系统会自动更新。

WPF 属性系统的基础是属性表达式的概念。 在 WPF 的第一个版本中,属性表达式系统是关闭的,表达式都作为框架的一部分提供。 表达式致使属性系统不具有数据绑定、样式调整或继承硬编码,而是由框架内后面的层来提供这些功能。

属性系统还提供属性值的稀疏存储。 因为对象可能有数十个(如果达不到上百个)属性,并且大部分值处于其默认状态(被继承、由样式设置等),所以并非对象的每个实例都需要具有在其上定义的每个属性的完全权重。

属性系统的最后一个新功能是附加属性的概念。 WPF 元素基于组合和组件重用的原则构建。 通常情况下,某些包含元素 (如 Grid 布局元素) 需要对子元素的其他数据来控制其行为 (如行/列信息) 。 任何对象都可以为任何其他对象提供属性定义,而不是将所有这些属性与每个元素相关联。 这与 JavaScript 中的“expando”功能相似。

1.2.3 System.Windows.Media.Visual

定义一个系统后,下一步是将像素绘制到屏幕上。 Visual类提供用于生成可视对象树的,其中每个对象都有选择地包含绘图指令以及有关如何将这些指令呈现 (剪裁、转换等 ) 的元数据。 Visual 设计为非常轻量且灵活,因此,大多数功能都没有公共 API 泄露,并且很大程度上依赖于受保护的回调函数。

Visual 确实是 WPF 组合系统的入口点。 Visual 这两个子系统之间的连接点,托管 API 和非托管 milcore。

WPF 通过遍历由 milcore 管理的非托管数据结构来显示数据。 这些结构(称为组合节点)代表层次结构显示树,其中每个节点都有呈现指令。 只能通过消息传递协议来访问此树(如下图右侧所示)。
WPF 实践_第4张图片

编程 WPF 时,创建 Visual 元素和派生类型,它们通过此消息传递协议在内部与组合树进行通信。 VisualWPF 中的每个都可以创建一个、无或多个组合节点。

Windows Presentation Foundation 可视化树。

请注意一个非常重要的体系结构细节 - 会缓存整个可视化树和绘制指令。 在图形术语中,WPF 使用保留渲染系统。 这可以实现以高刷新率重绘系统,并且组合系统不会阻止对用户代码的回调。 这有助于防止出现应用程序无响应的情况。

关系图中容易忽略的另一个重要细节是系统实际执行组合的方式。

在 User32 和 GDI 中,系统在即时模式剪辑系统上工作。 当需要绘制一个组件时,系统会建立一个剪裁边界,在此边界外,不允许组件接触像素,然后会要求组件在该框中绘制像素。 此系统在内存受限的系统上工作良好,因为当某些内容更改时,只需处理受影响的组件即可 - 不会由两个组件同时处理一个像素的颜色。

WPF 使用 “刷” 算法绘制模型。 要求每个组件从显示内容的背面绘制到正面,而不是剪裁每个组件。 这允许每个组件在先前组件的显示内容上绘制。 此模型的优点是可以生成部分透明的复杂形状。 在当今的新式图形硬件中,此模型的速度相对较快 (这种情况并不是在) 中创建的。

如前文所述,WPF 的核心理念是转向更具说明性的、“以属性为中心” 的编程模型。 在可视化系统中,这体现在有意思的几个方面。

首先,对于保留的模式图形系统,这实际上是从命令性 DrawLine/DrawLine 类型模型移动到面向数据的模型 new Line()/new Line()。 通过这种向数据驱动的绘制的移动,可以使用属性表达绘制指令上的复杂操作。 从派生的类型 Drawing 实际上是用于呈现的对象模型。

第二,如果评估动画系统,你会发现它几乎是完全声明性的。 可以将动画表示为动画对象上的一组属性,无需要求开发人员计算下一个位置或下一个颜色。 这些动画可以表达开发人员或设计人员的意图(在 5 秒内将此按钮从一个位置移动到另一个位置),系统可以确定完成此任务的最高效方式。

1.2.4 System.Windows.UIElement

UIElement 定义包含布局、输入和事件的核心子系统。

布局是 WPF 中的核心概念。 在许多系统中,可能有一组固定的布局模型(HTML 支持三种布局模型:流、绝对和表),也可能没有布局模型(User32 实际仅支持绝对定位)。 WPF 开始时假设开发人员和设计人员需要灵活的可扩展布局模型,这种模型可能由属性值而不是命令式逻辑驱动。 在 UIElement 级别上,引入了布局的基本协定–具有和通过的两阶段 Measure 模型 Arrange 。

Measure 允许组件确定要采用的大小。 这是一个独立的阶段, Arrange 因为在很多情况下,父元素会要求一个子元素多次测量,以确定其最佳位置和大小。 父元素要求子元素测量的事实表明,WPF 的另一个关键理念–内容大小。 WPF 中的所有控件都支持调整其内容自然大小的能力。 这使本地化更加容易,并可实现调整内容大小时进行动态元素布局。 该 Arrange 阶段允许父项定位和确定每个子级的最终大小。

经常会花费大量时间来讨论 WPF 的输出端 Visual 以及相关的对象。 然而,在输入端也有许多创新。 对于 WPF 的输入模型而言,最基本的更改可能是一致的模型,输入事件通过系统进行路由。

输入是作为内核模式设备驱动程序上的信号发出的,并通过涉及 Windows 内核和 User32 的复杂过程路由到正确的进程和线程。 将对应于输入的 User32 消息路由到 WPF 后,它将转换为 WPF 原始输入消息并发送到调度程序。 WPF 允许将原始输入事件转换为多个实际事件,从而能够在系统的最低级别实现 “MouseEnter” 等功能,并提供有保证的传递。

每个输入事件至少会转换为两个事件 -“预览”事件和实际事件。 WPF 中的所有事件都具有通过元素树路由的概念。 如果事件从目标向上遍历到根,则称为 “冒泡”; 如果从根开始向下遍历到目标,则称为 “隧道”。 输入预览事件隧道,使树中的任何元素都有机会筛选事件或对事件采取操作。 然后,常规(非预览)事件将从目标向上浮升到根。

隧道和浮升阶段之间的划分使键盘快捷键等功能的实现在复合环境中采用一致的方式。 在 User32 中,可以通过使用一个全局表来实现键盘快捷键,该表中包含你希望支持的所有快捷键(Ctrl+N 映射到“新建”)。 在应用程序的调度程序中,可以调用 TranslateAccelerator,它会探查 User32 中的输入消息,并确定是否有任何消息与已注册的快捷键匹配。 在 WPF 中,这不起作用,因为系统完全是 “可组合的”-任何元素都可以处理和使用任何键盘快捷键。 将此两阶段模型用于输入,可允许组件实现其自己的“TranslateAccelerator”。

若要进一步执行此步骤, UIElement 还介绍了 CommandBindings 的概念。 WPF 命令系统使开发人员可以根据命令终结点(实现的内容)定义功能 ICommand 。 命令绑定使元素可以定义输入笔势 (Ctrl+N) 和命令(“新建”)之间的映射。 输入笔势和命令定义都是可扩展的,并且可以在使用时联系到一起。 这使得一些操作(例如,允许最终用户自定义其要在应用程序内使用的键绑定)显得无关紧要。

在本主题中,WPF 的 “核心” 功能-PresentationCore 程序集中实现的功能已成为焦点。 构建 WPF 时,基础组件之间的完全分离 (例如,使用 度量值 和 排列) 的布局的协定和框架块 (如特定布局的实现,如 Grid) 所需的结果。 目标是提供在堆栈中处于较低位置的可扩展性点,这将允许外部开发人员在需要时创建自己的框架。

1.2.5 System.Windows.FrameworkElement

FrameworkElement 可以通过两种不同的方式进行查看。 它在 WPF 的较低层中引入的子系统上引入了一组策略和自定义项。 它还引入了一组新的子系统。

引入的主要策略 FrameworkElement 围绕应用程序布局。 FrameworkElement 基于引入的基本布局约定构建 UIElement 并添加布局 “槽” 的概念,使布局作者可以更轻松地使用一致的属性驱动布局语义集。 诸如 HorizontalAlignment 、 VerticalAlignment 、 MinWidth 和 Margin (的属性命名几个) 使所有组件都从 FrameworkElement 布局容器内的一致行为派生。

FrameworkElement 还可以更轻松地在 WPF 的核心层中找到许多功能。 例如, FrameworkElement 通过方法提供对动画的直接访问 BeginStoryboard 。 Storyboard提供了一种针对一组属性编写多个动画的脚本的方法。

引入的两个最关键的 FrameworkElement 是数据绑定和样式。

对于已使用 Windows 窗体或 ASP.NET 创建应用程序的任何人而言,WPF 中的数据绑定子系统应当相对熟悉 用户界面 (UI) 。 在上述每个系统中,可通过一种简单的方式来表达希望将给定元素中的一个或多个属性绑定到一个数据片段。 WPF 完全支持属性绑定、转换和列表绑定。

WPF 中数据绑定的最有趣的功能之一是引入数据模板。 利用数据模板,可以通过声明方式指定某个数据片断的可视化方式。 无需创建可绑定到数据的自定义用户界面,而是转而让数据来确定要创建的显示内容。

样式实际上是轻量型的数据绑定。 使用样式,可以将共享定义的一组属性绑定到元素的一个或多个实例。 样式可通过显式引用 (应用于元素,方法是将 Style 属性设置) 或通过将样式与元素的 CLR 类型相关联来隐式引用。

1.2.6 System.Windows.Controls.Control

控件的最重要功能是模板化。 如果将 WPF 的组合系统视为一个保留模式绘制系统,则控件可通过模板化以一种参数化的声明性方式描述其绘制。 ControlTemplate实际上只是一个脚本来创建一组子元素,并将绑定到控件提供的属性。

Control 提供了一组常用属性, Foreground Background 这些属性 Padding 用于命名几个模板创作者,然后可以使用这些属性自定义控件的显示。 控件的实现提供了数据模型和交互模型。 交互模型定义了一组命令(如窗口的“关闭”),以及到输入笔势的绑定(如单击窗口右上角的红叉)。 数据模型提供了一组属性,用于自定义交互模型或自定义显示内容(由模板确定)。

数据模型(属性)、交互模型(命令和事件)及显示模型(模板)之间的划分,可实现对控件的外观和行为的完全自定义。

最常见的控件数据模型是内容模型。 如果查看类似的控件 Button ,你会看到它有一个类型为 “Content” 的属性 Object 。 在 Windows 窗体和 ASP.NET 中,此属性通常是一个字符串,但它限制了可放入按钮中的内容的类型。 按钮的内容可以是简单的字符串、复杂的数据对象或整个元素树。 如果是数据对象,可以使用数据模板构造显示内容。

1.2.7 总结

WPF 旨在使您能够创建动态数据驱动的表示系统。 系统的每一部分均可通过驱动行为的属性集来创建对象。 数据绑定是系统的基础部分,在每一层中均进行了集成。

传统的应用程序创建一个显示内容,然后绑定到某些数据。 在 WPF 中,关于控件的所有内容都是由某种类型的数据绑定生成的。 通过在按钮内部创建复合控件并将其显示内容绑定到按钮的内容属性,会显示按钮中的文本。

开始开发基于 WPF 的应用程序时,应该非常熟悉。 可以设置属性、使用对象和数据绑定,其方式与使用 Windows 窗体或 ASP.NET 的方式大致相同。 通过更深入地调查 WPF 的体系结构,你会发现创建更丰富的应用程序的可能性,这些应用程序在本质上将数据视为应用程序的核心驱动程序。

1.3 基础总结

  1. DisptcherObject提供了线程和并发模型,实现了消息系统。
  2. DependencyObject提供了更改通知,实现了绑定,样式。
  3. Visual是托管API和非托管API(milcore)的之间的关键点。
  4. UIElement定义了Layout,Input和Events等核心子系统。Measure让一个组件来决定自己想要的size,而Arrange让父组件放置子组件并决定子组件的最终size。
  5. WPF的外观和行为总共有3个模型:数据模型(Properties),交互模型(Commands,Events),显示模型(Template)。
  6. 不同应用程序域间的WPF可以通过 INativeHandleContract来实现WPF AddIn。
  7. WPF定义了三个呈现曾:

呈现层 0 无图形硬件加速。 所有图形功能都使用软件加速。 DirectX 版本级别低于 9.0。

呈现层 1 某些图形功能使用图形硬件加速。 DirectX 版本级别高于或等于 9.0。

呈现层 2 大多数图形功能都使用图形硬件加速。 DirectX 版本级别高于或等于 9.0。

  1. WPF性能提升:
    1)布局处理过程。
    再次调用布局处理的几个操作:向集合中添加了一个子对象; 向子对象应用了 LayoutTransform;为子对象调用了 UpdateLayout 方法; 用影响测量或排列过程的元数据进行了标记的依赖项属性的值发生更改。

       提高方法:
            尽量使用最有效的Panel,功能越强大的Panel性能成本也就越高; 
            更新而不替换RenderTransform; 
            从上到下生成逻辑树。
    

    2)二维图形和图像处理
    提高方法:
    Drawing对象比Shape对象结构简单,性能更为优越,但是不是从FrameworkElement集成,使用时注意;
    使用StreamGeometry而不是PathGeometry;
    如果需要显示缩略图,尽量使用小版本的图像,或者请求WPF将图像解码为缩略图大小,或者请求WPF加载缩略图大小。始终将图像解码为所需的大小而不是默认大小。

    3)行为
    提高方法:
    注册DependencyObject时,尽量提供默认值和PropertyMetadata,而不是放在以后赋值。
    冻结Freezable会改善程序性能,不再需要因维护更改通知而消耗资源。
    使用VirtualizingStackPanel而不是StackPanel,注意:这样子资源苏不可见时即被移除,这时无法访问不可见的子元素。
    尽可能使用静态资源,只在必须得情况下使用动态资源。
    避免在 FlowDocument 使用TextBlock,而应该使用Run。
    避免在TextBlock里使用Run来设置文本属性。
    避免执行到 Lable.Content 属性的数据绑定。 如果数据源频繁更新,应用 TextBlock.Text 替换。
    4)数据绑定
    提高方法:
    性能从高到低:DependencyObject 〉INotifyPropertyChanged
    如果绑定到较大的CLR对象,考虑将对象拆分成多个具有少量属性的CLR对象
    将 IList (而非 IEnumerable )绑定到 ItemsCount
    5)控件
    提高方法:
    设置 IsVirtualizing 为true, VirtualizationMode 为Recycling, IsDeferredScrollingEnabled为true。
    IsVirtualizing代表是否UI虚拟化,当设置为true时,表示只有当数据项在屏幕上可见时才会在内存中创建存储。
    VirtualizationMode代表是否容器回收,正常情况下ItemsControl会为滚动到视图中的每个item创建项容器,并销毁滚动到视图之外的每个item的项容器。通过Recycling,可以让控件能够将现有项容器重复利用于不同的数据项。

                 IsDeferredScrollingEnabled代表是否延长滚动,正常情况下当用户拖到滚动条上的滑块时,内容视图会不会不断更新。当设置为true, 表示只有当用户松开滚动条时,内容才会更新。
    

    6)其他
    尽量使用画笔的不透明度(Opacity),而不是使用元素的Opacity,修改元素的Opacity会导致WPF创建临时图标。
    配置“WPF字体缓存服务”从手动为自动(延迟启动),这个服务如果没有启动,会随着第一个WPF程序启动时启动。这样导致第一个WPF的初始化事件很长。

  2. WPF线程模型
    典型的wpf程序有2个线程,一个用于负责渲染,一个用于管理UI。UI线程把工作项排序到Dispatcher对象中,Dispatcher对象根据工作项的优先级选择执行,直到全部执行。每个UI线程必须至少有一个Dispatcher,每个Dispatcher必须在一个线程里工作。

    可以通过CheckAccess来判断线程是否可以访问DispatcherObject。原理是:大多数类继承于DispatcherObject,DispatcherObject在构造时把当前运行线程的Dispatcher引用存储。当访问DispatcherObject时,检查当前线程关联的Dispatcher于构造中存储的Dispatcher进行比较,如果相同返回True,不同返回False。

    要构建响应速度快、且用户友好的应用程序,诀窍是减小工作项,以最大限度地提高 Dispatcher 吞吐量。 这样,工作项将永远不会因为在 Dispatcher 队列中等待处理而失效。 输入与响应之间的任何可察觉的延迟都会使用户不快。

    嵌套的消息泵:比如MessageBox,我们调用show后需要用户单击“OK"才能返回。MessageBox创建的窗口必须要由一个消息泵才能进行交互。我们在等待用户单击”OK“时原始应用程序窗口不响应用户输入。
    具体实现: WPF使用一种嵌套的消息处理系统, Dispatcher类包含一个PushFrame的特殊方法, 该方法存储应用程序的当前执行点,然后开始一个新的消息泵,当嵌套的消息泵执行结束时,执行将在最初的PushFrame调用之后继续。PushFrame内部实现类似Win32中GetMessage、TranslateMessage、DispatchMessage的消息泵。 (通过这个原理,我们可以实现自己的MessageBox系统)
    Application.Run内部调用Dispatcher.Run(),Dispatcher.Run()内部调用了Dispatcher.PushFrame(…), 实现了一个Win32的消息泵。

    DispatcherOperation对象用于与Dispatcher队列上的Delegate进行交互,例如更改委托的优先级、从事件队列中移除委托、等待委托返回、获取委托执行之后返回的值。

    Application.DoEvents方法:处理当前Dispatcher消息队列里的所有windows消息。

    关于BeginInvoke和Invoke,向关联的Dispatcher的队列中插入同步或者异步工作项。

10.Weak Event Pattern
传统的侦听事件可能导致内存泄漏,source.SomeEvent += new SomeEventHandler(MyEventHandler) 。这是因为为事件源添加事件处理程序时,会创建一个事件源到事件侦听器(所谓事件侦听器,就是Delegate对象)的强引用。这样事件侦听器就会有生命周期,该生命周期和事件源的生命周期有关, 除非显式的移除了事件处理程序, source.SomeEvent -= new SomeEventHandler(MyEventHandler) 。当事件源从可视树中移除时,事件侦听器还是有生命周期,但此时事件处理程序不会被调用,也就是说造成了无用的事件侦听器的还一直存在。这种情况下就需要弱事件模式。

 实现弱事件模式,有三种方式:使用系统已有的(CollectionChangedEventManager,PropertyChangedEventManager等等); 使用泛型弱事件管理器(WeakEventManager, 注意有性能损失); 继承WeakEventManager类,实现自定义弱事件管理器。

二, 应用

2.1 布局

原文
本主题介绍 WPF) 布局系统 (Windows Presentation Foundation。 了解布局计算的发生方式和时间是在 WPF 中创建用户界面所必需的。

2.1.1元素边界框

在 WPF 中考虑布局时,必须了解环绕所有元素的边界框。 FrameworkElement布局系统所使用的每个都可以被视为一个将其插入到布局中的矩形。 LayoutInformation类返回元素的布局分配或槽的边界。 矩形的大小通过计算可用屏幕空间、任何约束的大小、特定于布局的属性 (如边距和填充) 以及父元素的单个行为来确定 Panel 。 处理此数据时,布局系统能够计算特定的所有子级的位置 Panel 。 务必记住,在父元素上定义的大小调整特征(如 Border )会影响其子级。

<Grid Name="myGrid" Background="LightSteelBlue" Height="150">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="250"/>
  Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
  Grid.RowDefinitions>
  <TextBlock Name="txt1" Margin="5" FontSize="16" FontFamily="Verdana" Grid.Column="0" Grid.Row="0">Hello World!TextBlock>
  <Button Click="getLayoutSlot1" Width="125" Height="25" Grid.Column="0" Grid.Row="1">Show Bounding BoxButton>
  <TextBlock Name="txt2" Grid.Column="1" Grid.Row="2"/>
Grid>

WPF 实践_第5张图片

2.1.2布局系统

简单地说,布局是一个递归系统,实现对元素进行大小调整、定位和绘制。 更具体地说,layout 描述了测量和排列元素集合成员的过程 Panel Children 。 布局是一个密集的过程。 Children集合越大,必须进行的计算数量就越多。 还可以根据拥有集合的元素所定义的布局行为,引入复杂性 Panel 。 相对较简单的(如),其 Panel Canvas 性能明显高于更复杂的 Panel ,例如 Grid 。
每次子级 UIElement 更改其位置时,布局系统就有可能触发一个新的传递。 因此,了解哪些事件会调用布局系统就很重要,因为不必要的调用可能导致应用程序性能变差。 下面描述调用布局系统时发生的过程。

  1. 子元素 UIElement 通过首先测量其核心属性来开始布局过程。
  2. 计算在上定义的大小调整属性 FrameworkElement ,如 Width 、 Height 和 Margin 。
  3. Panel应用特定的逻辑,如 Dock 方向或堆叠 Orientation 。
  4. 测量所有子级后排列内容。
  5. Children集合在屏幕上绘制。
  6. 如果 Children 向集合添加了其他、 LayoutTransform 应用了或调用了方法,则会再次调用该过程 UpdateLayout 。

测量和排列子元素

布局系统为集合的每个成员完成两个传递 Children 、一个度量值处理和一个排列处理过程。 每个子元素都 Panel 提供自己的 MeasureOverride 和 ArrangeOverride 方法来实现其自己的特定布局行为。
在度量值处理过程中,将对集合中的每个成员 Children 进行计算。 此过程首先调用 Measure 方法。 此方法在父元素的实现中调用 Panel ,无需显式调用即可进行布局。
首先,计算的本机大小属性 UIElement ,例如 Clip 和 Visibility 。 这会生成一个名为 constraintSize 的值,该值将传递给 MeasureCore 。
其次,会处理在上定义的框架属性 FrameworkElement ,这会影响的值 constraintSize 。 这些属性通常描述基础的大小调整特征 UIElement ,例如,、、 Height Width Margin 和 Style 。 其中每个属性都可以更改显示元素所需的空间。 MeasureOverride 然后,将 constraintSize 作为参数调用。
度量值传递的最终目标是让子元素确定在 DesiredSize 调用期间发生的 MeasureCore 。 DesiredSize值由存储, Measure 以便在内容排列传递过程中使用。
排列传递通过调用方法来开始 Arrange 。 在排列过程中,父 Panel 元素会生成一个表示子级边界的矩形。 此值将传递给 ArrangeCore 方法以进行处理。
ArrangeCore方法计算子级的 DesiredSize ,并计算可能会影响元素呈现大小的任何其他边距。 ArrangeCore 生成一个 arrangeSize ,它作为参数传递到 ArrangeOverride 的方法 Panel 。 ArrangeOverride 生成 finalSize 子的。 最后, ArrangeCore 方法执行偏移量属性(如边距和对齐方式)的最终计算,并将子元素放在其布局槽内。 子元素不需要(并且通常不)填充整个分配空间。 然后,将控件返回到父级 Panel ,并且布局过程已完成。

面板元素和自定义布局行为

WPF 包含一组从派生的元素 Panel 。 这些 Panel 元素可以实现许多复杂的布局。 例如,可以通过使用元素轻松实现堆栈元素 StackPanel ,而使用可以实现更复杂和更自由的流动布局 Canvas 。
WPF 实践_第6张图片

2.1.3面板概述

原文
Panel 元素是控制元素(其大小和尺寸、位置以及其子内容的排列)的呈现的组件。 Windows Presentation Foundation (WPF)提供了许多预定义的 Panel 元素以及构造自定义元素的功能 Panel 。

Panel 类

Panel 是中提供布局支持的所有元素的基类 Windows Presentation Foundation (WPF) 。 派生 Panel 元素用于在和代码中放置和排列元素 可扩展应用程序标记语言 (XAML) 。
WPF 包含一套全面的派生面板实现,可支持许多复杂的布局。 这些派生类公开可实现大部分标准 用户界面 (UI) 方案的属性和方法。 找不到满足其需求的子排列行为的开发人员可以通过重写和方法来创建新的布局 ArrangeOverride MeasureOverride 。

Panel 公共成员

所有 Panel 元素都支持定义的基本大小调整和定位属性 FrameworkElement ,包括Height Width HorizontalAlignment VerticalAlignment 、 Margin 和 LayoutTransform 。
Panel 公开更重要的其他属性,这些属性对于理解和使用布局非常重要。 Background属性用于使用填充派生面板元素边界之间的区域 Brush 。 Children 表示由组成的元素的子集合 Panel 。 InternalChildren 表示集合的内容 Children 和数据绑定生成的成员。 两者均由 UIElementCollection 父级中承载的子元素组成 Panel 。
面板还公开了 Panel.ZIndex 可用于在派生中实现分层顺序的附加属性 Panel 。 具有较高值的面板集合的成员将 Children Panel.ZIndex 出现在值较小的面板上 Panel.ZIndex 。 这对于的面板(如) Canvas 和 Grid 允许子共享同一坐标空间的面板特别有用。
Panel 还定义了 OnRender 方法,该方法可用于重写的默认呈现行为 Panel 。

附加属性

派生面板元素广泛使用附加属性。 附加属性是一种特殊形式的依赖属性,它不 (CLR) 属性 “包装” 的传统公共语言运行时。 附加属性在 可扩展应用程序标记语言 (XAML) 中具有特殊化的语法,可在后面的几个示例中看到。
附加属性的一个用途是允许子元素存储实际上由父元素定义的属性的唯一值。 此功能的一项应用是让子元素通知父级它们希望如何在 用户界面 (UI) 中呈现,这对应用程序布局非常有用。

派生 Panel 元素

WPF 实践_第7张图片

用户界面 Panel

WPF 实践_第8张图片

画布

Canvas元素可根据绝对 x 和 y 坐标定位内容。 元素可以在唯一位置绘制;或者,如果元素占用了相同坐标,则这些元素在标记中显示的顺序决定它们的绘制顺序。
Canvas 提供对的最灵活的布局支持 Panel 。 “高度” 和 “宽度” 属性用于定义画布的面积,并为内的元素分配相对于父区域的绝对坐标 Canvas 。 四个附加属性:、 Canvas.Left Canvas.Top Canvas.Right 和 Canvas.Bottom ,允许在中对对象放置进行精细控制 Canvas ,使开发人员能够在屏幕上精确地定位和排列元素。

Canvas 内 ClipToBounds

Canvas 可以将子元素定位在屏幕上的任何位置,甚至可以定位在其自身定义和之外的坐标处 Height Width 。 而且, Canvas 不受其子级的大小的影响。 因此,子元素可以过度绘制父元素外边框外的其他元素 Canvas 。 的默认行为 Canvas 是允许在父项的边界之外绘制子级 Canvas 。 如果不需要此行为,则 ClipToBounds 可以将属性设置为 true 。 这会导致 Canvas 剪裁到其自身大小。 Canvas 是唯一的布局元素,它允许在其边界之外绘制子级。

定义和使用 Canvas

Canvas只需使用或代码,即可实例化 可扩展应用程序标记语言 (XAML) 。 下面的示例演示如何使用 Canvas 来绝对定位内容。 此代码生成三个 100 像素正方形。 第一个正方形为红色,其左上角的 (x, y) 位置指定为 (0, 0)。 第二个正方形为绿色,其左上角位置为 (100, 100),在第一个正方形的右下方。 第三个正方形为蓝色,其左上角为 (50, 50),因此包含了第一个正方形的右下四分之一部分和第二个正方形的左上四分之一部分。 由于第三个正方形最后布置,因此它看起来在另外两个正方形上方,即,重叠部分采用第三个正方形的颜色。

<Page WindowTitle="Canvas Sample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <Canvas Height="400" Width="400">
    <Canvas Height="100" Width="100" Top="0" Left="0" Background="Red"/>
    <Canvas Height="100" Width="100" Top="100" Left="100" Background="Green"/>
    <Canvas Height="100" Width="100" Top="50" Left="50" Background="Blue"/>
  Canvas>
Page>

WPF 实践_第9张图片

DockPanel

DockPanel元素使用 DockPanel.Dock 在子 content 元素中设置的附加属性,沿容器边缘放置内容。 当 DockPanel.Dock 设置为 Top 或时 Bottom ,它将每个子元素的上方或下方放置。 当 DockPanel.Dock 设置为 Left 或时 Right ,它将子元素定位到彼此的左侧或右侧。 LastChildFill属性确定作为的子元素添加的最后一个元素的位置 DockPanel 。
您可以使用 DockPanel 来定位一组相关的控件,例如一组按钮。 或者,您可以使用它来创建 “平移” UI ,类似于在 Microsoft Outlook 中找到的。

按内容调整大小

如果 Height Width 未指定其和属性,则 DockPanel 调整其内容的大小。 大小可以增大或减小以容纳其子元素的大小。 但是,如果指定了这些属性,但没有足够的空间用于下一个指定的子元素,则 DockPanel 不会显示该子元素或后续的子元素,也不会度量后续的子元素。

LastChildFill

默认情况下,元素的最后一个子 DockPanel 元素将 “填充” 剩余的未分配空间。 如果不需要此行为,请将 LastChildFill 属性设置为 false 。

定义和使用 DockPanel
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" WindowTitle="DockPanel Sample">
  <DockPanel LastChildFill="True">
    <Border Height="25" Background="SkyBlue" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Top">
      <TextBlock Foreground="Black">Dock = "Top"TextBlock>
    Border>
    <Border Height="25" Background="SkyBlue" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Top">
      <TextBlock Foreground="Black">Dock = "Top"TextBlock>
    Border>
    <Border Height="25" Background="LemonChiffon" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Bottom">
      <TextBlock Foreground="Black">Dock = "Bottom"TextBlock>
    Border>
    <Border Width="200" Background="PaleGreen" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Left">
      <TextBlock Foreground="Black">Dock = "Left"TextBlock>
    Border>
    <Border Background="White" BorderBrush="Black" BorderThickness="1">
      <TextBlock Foreground="Black">This content will "Fill" the remaining spaceTextBlock>
    Border>
  DockPanel>
Page>

WPF 实践_第10张图片

网格

Grid元素合并绝对定位和表格数据控件的功能。 Grid利用,您可以轻松地定位和样式元素。 Grid 允许您定义灵活的行分组和列分组,甚至提供一种在多个元素之间共享大小调整信息的机制 Grid 。

网格与表有何不同

Table 和 Grid 共享一些通用功能,但每个功能最适合于不同的方案。 Table旨在在流内容中使用 (请参阅流文档概述,了解有关流内容的详细信息) 。 网格最适合在表单中(主要在流内容以外的任意位置)使用。 在中 FlowDocument , Table 支持流内容行为,如分页、列重排和内容选择,而不支持 Grid 。 Grid另一方面,最好是在之外使用, FlowDocument 因为这样做的原因很多 Grid ,包括基于行和列索引添加元素 Table 。 Grid元素允许将子内容分层,允许多个元素存在于单个 “单元” 中。 Table 不支持分层。 的子元素 Grid 可相对于其 “单元格” 边界区域进行绝对定位。 Table 不支持此功能。 最后, Grid 比更轻量 Table 。

列和行的大小调整行为

在中定义的列和行 Grid 可以利用 Star 大小调整,以便按比例分配剩余空间。 如果 Star 选择作为行或列的高度或宽度,则该列或行会获得剩余可用空间的加权比例。 这与不同 Auto ,后者将基于列或行中内容的大小均匀分配空间。 在使用 可扩展应用程序标记语言 (XAML) 时,此值以 * 或 2* 的形式表示。 在第一种情况下,行或列将得到一倍的可用空间,在第二种情况下,将得到两倍的可用空间,依此类推。 通过将此方法组合到一起,可以按比例分配空间 HorizontalAlignment ,并将 VerticalAlignment Stretch 布局空间分区为屏幕空间的百分比。 Grid 是唯一可以采用这种方式分布空间的布局面板。

定义和使用 Grid
// Create the Grid.
grid1 = new Grid ();
grid1.Background = Brushes.Gainsboro;
grid1.HorizontalAlignment = HorizontalAlignment.Left;
grid1.VerticalAlignment = VerticalAlignment.Top;
grid1.ShowGridLines = true;
grid1.Width = 425;
grid1.Height = 165;

// Define the Columns.
colDef1 = new ColumnDefinition();
colDef1.Width = new GridLength(1, GridUnitType.Auto);
colDef2 = new ColumnDefinition();
colDef2.Width = new GridLength(1, GridUnitType.Star);
colDef3 = new ColumnDefinition();
colDef3.Width = new GridLength(1, GridUnitType.Star);
colDef4 = new ColumnDefinition();
colDef4.Width = new GridLength(1, GridUnitType.Star);
colDef5 = new ColumnDefinition();
colDef5.Width = new GridLength(1, GridUnitType.Star);
grid1.ColumnDefinitions.Add(colDef1);
grid1.ColumnDefinitions.Add(colDef2);
grid1.ColumnDefinitions.Add(colDef3);
grid1.ColumnDefinitions.Add(colDef4);
grid1.ColumnDefinitions.Add(colDef5);

// Define the Rows.
rowDef1 = new RowDefinition();
rowDef1.Height = new GridLength(1, GridUnitType.Auto);
rowDef2 = new RowDefinition();
rowDef2.Height = new GridLength(1, GridUnitType.Auto);
rowDef3 = new RowDefinition();
rowDef3.Height = new GridLength(1, GridUnitType.Star);
rowDef4 = new RowDefinition();
rowDef4.Height = new GridLength(1, GridUnitType.Auto);
grid1.RowDefinitions.Add(rowDef1);
grid1.RowDefinitions.Add(rowDef2);
grid1.RowDefinitions.Add(rowDef3);
grid1.RowDefinitions.Add(rowDef4);

// Add the Image.
img1 = new Image();
img1.Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("runicon.png", UriKind.Relative));
Grid.SetRow(img1, 0);
Grid.SetColumn(img1, 0);

// Add the main application dialog.
txt1 = new TextBlock();
txt1.Text = "Type the name of a program, folder, document, or Internet resource, and Windows will open it for you.";
txt1.TextWrapping = TextWrapping.Wrap;
Grid.SetColumnSpan(txt1, 4);
Grid.SetRow(txt1, 0);
Grid.SetColumn(txt1, 1);

// Add the second text cell to the Grid.
txt2 = new TextBlock();
txt2.Text = "Open:";
Grid.SetRow(txt2, 1);
Grid.SetColumn(txt2, 0);

// Add the TextBox control.
tb1 = new TextBox();
Grid.SetRow(tb1, 1);
Grid.SetColumn(tb1, 1);
Grid.SetColumnSpan(tb1, 5);

// Add the buttons.
button1 = new Button();
button2 = new Button();
button3 = new Button();
button1.Content = "OK";
button2.Content = "Cancel";
button3.Content = "Browse ...";
Grid.SetRow(button1, 3);
Grid.SetColumn(button1, 2);
button1.Margin = new Thickness(10, 0, 10, 15);
button2.Margin = new Thickness(10, 0, 10, 15);
button3.Margin = new Thickness(10, 0, 10, 15);
Grid.SetRow(button2, 3);
Grid.SetColumn(button2, 3);
Grid.SetRow(button3, 3);
Grid.SetColumn(button3, 4);

grid1.Children.Add(img1);
grid1.Children.Add(txt1);
grid1.Children.Add(txt2);
grid1.Children.Add(tb1);
grid1.Children.Add(button1);
grid1.Children.Add(button2);
grid1.Children.Add(button3);

mainWindow.Content = grid1;

xaml实例:比例靠 定位靠Grid.Column和Grid.Row,占地靠Grid.ColumnSpan和Grid.RowSpan.*

 <Grid Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="8*" />
            <RowDefinition Height="*" />
        Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        Grid.ColumnDefinitions>

        <Label      Grid.Column="0"      Grid.Row="1" VerticalAlignment ="Center"  Margin="5,5,5,5" >文件载入Label>
        <TextBox    Grid.Column="1"      Grid.Row="1" Grid.ColumnSpan="5" VerticalAlignment ="Center"  Margin="5,5,5,5" Name="TB_Path">文件路径TextBox>
        <Button     Grid.Column="6"      Grid.Row="1" VerticalAlignment ="Center"  Margin="20,5,5,5" Command="Open">载入Button>

        <Label      Grid.Column="0"      Grid.Row="0" VerticalAlignment ="Center" HorizontalAlignment="Right"  Margin="5,5,5,5">设备索引号Label>
        <ComboBox Name="cmb_DeviceNum" Grid.Row="0" Grid.Column="1" Height="23" DisplayMemberPath="Value" SelectedValuePath="Key" >ComboBox>
        <Label      Grid.Column="2"      Grid.Row="0" VerticalAlignment ="Center"  HorizontalAlignment="Right" Margin="5,5,5,5">CAN通道号Label>
        <ComboBox Name="cmb_CanChannel" Grid.Row="0" Grid.Column="3" Height="23" DisplayMemberPath="Value" SelectedValuePath="Key" >ComboBox>
        <Label      Grid.Column="4"      Grid.Row="0" VerticalAlignment ="Center" HorizontalAlignment="Right" Margin="5,5,5,5">CAN波特率Label>
        <ComboBox Name="cmb_CanBaud" Grid.Row="0" Grid.Column="5" Height="23" Margin="5,5,5,5" DisplayMemberPath="Value" SelectedValuePath="Key" >ComboBox>
        <Button Grid.Column="6" Grid.Row="0" VerticalAlignment ="Center"   Margin="20,5,5,5" Name="btnConnect" Click="OnClick_btnConnect" ClickMode="Press">连接Button>




        
        <Border Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="7" Width="Auto"  BorderBrush="Black" BorderThickness="1"  Margin="5,5,5,5">
            <ScrollViewer Height="Auto" Width="Auto"
                              HorizontalScrollBarVisibility="Visible"
                              VerticalScrollBarVisibility ="Visible">
                <DataGrid x:Name="gridConfigData" Width="Auto" ItemsSource="{Binding Path = .}" AutoGenerateColumns="False" SelectionUnit="Cell" SelectionMode="Single" LoadingRow="gridConfigData_LoadingRow">
                   

                    <DataGrid.Columns>
                        <DataGridTextColumn Header="ID"  Binding="{Binding GC_ID.name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                        
                            
                        <DataGridTextColumn Header="发送数据" MinWidth="300"  Binding="{Binding GC_UDS_data.name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                       

                    DataGrid.Columns>
                DataGrid>
            ScrollViewer>
        Border>

        <Button Grid.Column="0" Grid.Row="3" VerticalAlignment ="Center" Command="Save" >保存Button>
        <Label Grid.Column="1" Grid.Row="3"  HorizontalAlignment="Right" VerticalAlignment ="Center"  Margin="5,5,5,5">功能IDLabel>
        <TextBox Grid.Column="2"  Grid.Row="3" Name="TB_F_CanID" VerticalAlignment ="Center"  Margin="5,5,5,5">7DFTextBox>
        <Label Grid.Column="3" Grid.Row="3"  HorizontalAlignment="Right" VerticalAlignment ="Center"  Margin="5,5,5,5">物理IDLabel>
        <TextBox Grid.Column="4"  Grid.Row="3"  VerticalAlignment ="Center"  Margin="5,5,5,5" Name="TB_PH_CanID">7B2TextBox>


        <Button Grid.Column="6" Grid.Row="3" Margin="5,5,5,5"  Click="OnClick_btnSend">开始运行Button>
        
    Grid>

StackPanel

StackPanel利用,你可以按分配的方向 “stack” 元素。 默认堆叠方向为垂直方向。 Orientation属性可用于控制内容流。

System.windows.controls.stackpanel> 与 System.windows.controls.dockpanel>

尽管 DockPanel 还可以 “stack” 子元素, DockPanel 但 StackPanel 在某些使用方案中不会产生类似的结果。 例如,子元素的顺序可能会影响其在中的大小,而不会影响中的大小 DockPanel StackPanel 。 这是因为 StackPanel 在中的堆栈方向上测量 PositiveInfinity ,而 DockPanel 仅测量可用大小。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      WindowTitle="StackPanel vs. DockPanel">
  <Grid Width="175" Height="150">
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
    Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
    Grid.RowDefinitions>
    
    <DockPanel Grid.Column="0" Grid.Row="0">
      <Image Source="smiley_stackpanel.png" />
      <Image Source="smiley_stackpanel.png" />
      <Image Source="smiley_stackpanel.png" Stretch="Fill"/>
    DockPanel>

    <StackPanel Grid.Column="0" Grid.Row="1"  Orientation="Horizontal">
      <Image Source="smiley_stackpanel.png" />
      <Image Source="smiley_stackpanel.png" />
      <Image Source="smiley_stackpanel.png" Stretch="Fill"/>
    StackPanel>
    Grid>
Page>

WPF 实践_第11张图片

定义和使用 StackPanel
// Create the application's main window
mainWindow = new Window ();
mainWindow.Title = "StackPanel Sample";

// Define the StackPanel
myStackPanel = new StackPanel();
myStackPanel.HorizontalAlignment = HorizontalAlignment.Left;
myStackPanel.VerticalAlignment = VerticalAlignment.Top;

// Define child content
Button myButton1 = new Button();
myButton1.Content = "Button 1";
Button myButton2 = new Button();
myButton2.Content = "Button 2";
Button myButton3 = new Button();
myButton3.Content = "Button 3";

// Add child elements to the parent StackPanel
myStackPanel.Children.Add(myButton1);
myStackPanel.Children.Add(myButton2);
myStackPanel.Children.Add(myButton3);

// Add the StackPanel as the Content of the Parent Window Object
mainWindow.Content = myStackPanel;
mainWindow.Show ();

WPF 实践_第12张图片

VirtualizingStackPanel

WPF 还提供了 StackPanel 元素的变体,该元素自动 “虚拟化” 数据绑定的子内容。 在此上下文中,“虚拟化”一词指的是一种技术:通过此技术根据屏幕上哪些项可见,从较多的数据项中生成一个元素子集。 如果在指定时刻只有少量 UI 元素位于屏幕上,则此时生成大量 UI 元素需要占用大量内存和处理器。 VirtualizingStackPanel (通过) 提供的功能 VirtualizingPanel 计算可见项,并 ItemContainerGenerator 从 ItemsControl ((如 ListBox 或) )使用 ListView 来仅创建可见项的元素。
VirtualizingStackPanel元素自动设置为控件的宿主,如 ListBox 。 承载数据绑定集合时,只要内容在的边界内,内容就会自动虚拟化 ScrollViewer 。 在承载许多子项时,这将大幅提高性能。
下面的标记演示了如何使用 VirtualizingStackPanel 作为项宿主。 VirtualizingStackPanel.IsVirtualizingProperty若要进行虚拟化,附加属性必须设置为 true (默认) 。

WrapPanel

WrapPanel 用于按从左到右的顺序位置定位子元素,并在到达其父容器的边缘时将内容分解到下一行。 内容可以设置为水平或垂直方向。 WrapPanel 对于简单的流动方案非常有用 用户界面 (UI) 。 它还可以用来将统一大小调整应用于其所有子元素。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" WindowTitle="WrapPanel Sample">
  <Border HorizontalAlignment="Left" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="2">
        <WrapPanel Background="LightBlue" Width="200" Height="100">
            <Button Width="200">Button 1Button>
            <Button>Button 2Button>
            <Button>Button 3Button>
            <Button>Button 4Button>
        WrapPanel>
  Border>    
Page>

WPF 实践_第13张图片

嵌套 Panel 元素

Panel 元素可以相互嵌套,以便生成复杂的布局。 这在某些情况下非常有用,在这种情况下 Panel ,一个是的一部分 UI ,但可能不满足的其他部分的需求 UI 。
对于应用程序可以支持的嵌套数量,并没有实际限制,但通常最好限制应用程序仅使用预期布局实际所需的面板。 在许多情况下, Grid 可以使用元素,而不是嵌套面板,因为它具有作为布局容器的灵活性。 这可以通过将不必要的元素排除在树以外来提高应用程序的性能。
下面的示例演示如何创建一个 UI 利用嵌套元素的,以便 Panel 实现特定的布局。 在此特定情况下, DockPanel 元素用于提供 UI 结构,嵌套 StackPanel 元素、 Grid 和 Canvas 用于在父元素内精确定位子元素 DockPanel 。

// Define the DockPanel.
myDockPanel = new DockPanel();

// Add the Left Docked StackPanel
Border myBorder2 = new Border();
myBorder2.BorderThickness = new Thickness(1);
myBorder2.BorderBrush = Brushes.Black;
DockPanel.SetDock(myBorder2, Dock.Left);
StackPanel myStackPanel = new StackPanel();
Button myButton1 = new Button();
myButton1.Content = "Left Docked";
myButton1.Margin = new Thickness(5);
Button myButton2 = new Button();
myButton2.Content = "StackPanel";
myButton2.Margin = new Thickness(5);
myStackPanel.Children.Add(myButton1);
myStackPanel.Children.Add(myButton2);
myBorder2.Child = myStackPanel;

// Add the Top Docked Grid.
Border myBorder3 = new Border();
myBorder3.BorderThickness = new Thickness(1);
myBorder3.BorderBrush = Brushes.Black;
DockPanel.SetDock(myBorder3, Dock.Top);
Grid myGrid = new Grid();
myGrid.ShowGridLines = true;
RowDefinition myRowDef1 = new RowDefinition();
RowDefinition myRowDef2 = new RowDefinition();
ColumnDefinition myColDef1 = new ColumnDefinition();
ColumnDefinition myColDef2 = new ColumnDefinition();
ColumnDefinition myColDef3 = new ColumnDefinition();
myGrid.ColumnDefinitions.Add(myColDef1);
myGrid.ColumnDefinitions.Add(myColDef2);
myGrid.ColumnDefinitions.Add(myColDef3);
myGrid.RowDefinitions.Add(myRowDef1);
myGrid.RowDefinitions.Add(myRowDef2);
TextBlock myTextBlock1 = new TextBlock();
myTextBlock1.FontSize = 20;
myTextBlock1.Margin = new Thickness(10);
myTextBlock1.Text = "Grid Element Docked at the Top";
Grid.SetRow(myTextBlock1, 0);
Grid.SetColumnSpan(myTextBlock1, 3);
Button myButton3 = new Button();
myButton3.Margin = new Thickness(5);
myButton3.Content = "A Row";
Grid.SetColumn(myButton3, 0);
Grid.SetRow(myButton3, 1);
Button myButton4 = new Button();
myButton4.Margin = new Thickness(5);
myButton4.Content = "of Button";
Grid.SetColumn(myButton4, 1);
Grid.SetRow(myButton4, 1);
Button myButton5 = new Button();
myButton5.Margin = new Thickness(5);
myButton5.Content = "Elements";
Grid.SetColumn(myButton5, 2);
Grid.SetRow(myButton5, 1);
myGrid.Children.Add(myTextBlock1);
myGrid.Children.Add(myButton3);
myGrid.Children.Add(myButton4);
myGrid.Children.Add(myButton5);
myBorder3.Child = myGrid;

// Add the Bottom Docked StackPanel.
Border myBorder4 = new Border();
myBorder4.BorderBrush = Brushes.Black;
myBorder4.BorderThickness = new Thickness(1);
DockPanel.SetDock(myBorder4, Dock.Bottom);
StackPanel myStackPanel2 = new StackPanel();
myStackPanel2.Orientation = Orientation.Horizontal;
TextBlock myTextBlock2 = new TextBlock();
myTextBlock2.Text = "This StackPanel is Docked to the Bottom";
myTextBlock2.Margin = new Thickness(5);
myStackPanel2.Children.Add(myTextBlock2);
myBorder4.Child = myStackPanel2;

// Add the Canvas, that fills remaining space.
Border myBorder5 = new Border();
myBorder4.BorderBrush = Brushes.Black;
myBorder5.BorderThickness = new Thickness(1);
Canvas myCanvas = new Canvas();
myCanvas.ClipToBounds = true;
TextBlock myTextBlock3 = new TextBlock();
myTextBlock3.Text = "Content in the Canvas will Fill the remaining space.";
Canvas.SetTop(myTextBlock3, 50);
Canvas.SetLeft(myTextBlock3, 50);
Ellipse myEllipse = new Ellipse();
myEllipse.Height = 100;
myEllipse.Width = 125;
myEllipse.Fill = Brushes.CornflowerBlue;
myEllipse.Stroke = Brushes.Aqua;
Canvas.SetTop(myEllipse, 100);
Canvas.SetLeft(myEllipse, 150);
myCanvas.Children.Add(myTextBlock3);
myCanvas.Children.Add(myEllipse);
myBorder5.Child = myCanvas;

// Add child elements to the parent DockPanel.
myDockPanel.Children.Add(myBorder2);
myDockPanel.Children.Add(myBorder3);
myDockPanel.Children.Add(myBorder4);
myDockPanel.Children.Add(myBorder5);

WPF 实践_第14张图片

自定义 Panel 元素

WPF提供灵活的布局控件数组时,还可以通过重写和方法来实现自定义布局行为 ArrangeOverride MeasureOverride 。 可以通过在这些重写方法内定义新的位置行为来实现自定义大小调整和位置。
同样, Canvas Grid 可以通过重写其和方法来定义基于派生类 ((如或) )的自定义布局行为 ArrangeOverride MeasureOverride 。
下面的标记演示如何创建自定义 Panel 元素。 此新 Panel 定义为 PlotPanel ,它支持通过使用硬编码 x 和 y 坐标来定位子元素。 在此示例中, Rectangle 未显示的元素 () 定位于绘图点 50 (x) ,50 (y) 。

public class PlotPanel : Panel
{
    // Default public constructor
    public PlotPanel()
        : base()
    {
    }

    // Override the default Measure method of Panel
    protected override Size MeasureOverride(Size availableSize)
    {
        Size panelDesiredSize = new Size();

        // In our example, we just have one child.
        // Report that our panel requires just the size of its only child.
        foreach (UIElement child in InternalChildren)
        {
            child.Measure(availableSize);
            panelDesiredSize = child.DesiredSize;
        }

        return panelDesiredSize ;
    }
    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach (UIElement child in InternalChildren)
        {
            double x = 50;
            double y = 50;

            child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
        }
        return finalSize; // Returns the final Arranged size
    }
}

2.1.4使用自动布局

概述

原文
介绍了有关如何编写 Windows Presentation Foundation (WPF) 具有可本地化的用户界面) (ui 的应用程序的指南。 过去,UI 的本地化是一个耗时的过程。 UI 调整的每种语言都需要像素的像素调整。 如今,使用正确的设计和右编码标准,可以构造 Ui,使本地化人员可以更少地调整大小和重新定位。 编写可以更方便地调整大小和重新定位的应用程序的方法称为自动布局,可以使用 WPF 应用程序设计实现此目的。

使用自动布局的优点

由于 WPF 呈现系统功能强大且灵活,因此它提供了在应用程序中布局元素的功能,可以根据不同语言的要求进行调整。 下面列出自动布局的部分优点。

  • UI 以任何语言正确显示。
  • 减少了文本转换完后重新调整控件位置和大小的需要。
  • 减少了重新调整窗口大小的需要。
  • UI 布局在任何语言中都正确呈现。
  • 本地化过程可缩减为与进行字符串转换差不多。

自动布局和控件

利用自动布局,应用程序可以自动调整控件大小。 例如,控件可按字符串长度相应改变。 利用此功能,本地化人员可转换字符串,而无需再调整控件大小以适应转换后的文本。 下面的示例创建一个带有英文内容的按钮。

自动布局和编码标准

使用自动布局方法需要一组编码和设计标准和规则,以生成可完全本地化的 UI。 下列准则可帮助完成自动布局编码。

  • 不要使用绝对位置

    • 不要使用 Canvas ,因为它以绝对方式定位元素。
    • 使用 DockPanel 、 StackPanel 和 Grid 定位控件。
  • 不要为窗口设置固定大小

  • 添加 FlowDirection
    将添加 FlowDirection 到应用程序的根元素。
    WPF 提供了一种简便的方法来支持水平、双向和垂直布局。 在表示框架中, FlowDirection 属性可用于定义布局。 流方向模式包括:
    - FlowDirection.LeftToRight (LrTb) —拉丁语、东亚等的水平布局。
    - FlowDirection.RightToLeft (RlTb) -针对阿拉伯语、希伯来语等的双向。

  • 使用复合字体而不是物理字体
    - 对于复合字体, FontFamily 无需本地化属性。
    - 开发人员可以使用以下字体之一,也可以创建自己的字体。
    Global User Interface
    Global San Serif
    Global Serif

  • 添加 xml: lang
    - xml:lang在 UI 的根元素中添加特性,如 xml:lang=“en-US” 用于英语应用程序的。
    - 由于复合字体使用 xml:lang 来确定要使用的字体,因此请将此属性设置为支持多语言方案。

FrameworkElement 类

提供 Windows Presentation Foundation (WPF) 元素的属性、事件和方法的 WPF 框架级别集。 此类表示所提供的 WPF 框架级别实现基于 UIElement 定义的 WPF 核心级别 API。
在这里插入图片描述
FrameworkElement 是 WPF 框架级别的元素类与 WPF core 级别的展示服务之间的连接点 UIElement 。
FrameworkElement 扩展 UIElement 并添加了以下功能:
- 布局系统定义: FrameworkElement 针对在中定义为虚拟成员的某些方法提供特定的 WPF 框架级实现 UIElement 。 最值得注意的是, FrameworkElement 密封某些 wpf 核心级别的布局替代,并提供 wpf 框架级别等效项,派生类应改为重写。 例如, FrameworkElement 密封 ArrangeCore 但提供 ArrangeOverride 。 这些更改反映了这样一个事实:在 WPF 框架级别,有一个完全布局系统可呈现任何 FrameworkElement 派生类。 在 WPF 核心级别,将构建基于 WPF 的常规布局解决方案的某些成员是就地的,但布局系统的实际引擎并未定义。
- 逻辑树: 一般 WPF 编程模型通常以元素树的形式表示。 支持以逻辑树形式表示元素树,并支持在标记中定义该树 FrameworkElement 。 但请注意, FrameworkElement 有意不要定义内容模型,而是将此责任留给派生类。
- 对象生存期事件: 当) 调用构造函数时,或者当元素首次加载到逻辑树中时,知道 (构造函数时,这通常很有用。 FrameworkElement 定义多个与对象生存期相关的事件,这些事件为涉及元素(如添加更多子元素)的代码隐藏操作提供有用的挂钩。 有关详细信息,请参阅 对象生存期事件。
- 数据绑定和动态资源引用支持: 数据绑定和资源的属性级别支持由 DependencyProperty 类实现并在属性系统中使用,但是,可以解析存储为 Expression (编程构造的成员值,该构造是为数据绑定和动态资源提供基础的,) 由实现 FrameworkElement 。 有关详细信息,请参阅 数据绑定概述 和 XAML 资源。
- 样式: FrameworkElement 定义 Style 属性。 但是,尚未 FrameworkElement 定义对模板的支持,或不支持修饰器。 这些功能由控件类(如和) Control 引入 ContentControl 。
- 更多动画支持: 某些动画支持已在 WPF 核心级别定义,但 FrameworkElement 通过实现和相关成员扩展了此支持 BeginStoryboard 。

2.1.5 Alignment、Margin 和 Padding 概述

原文
FrameworkElement类公开几个用于精确定位子元素的属性。 本主题讨论四个最重要的属性: HorizontalAlignment 、 Margin 、 Padding 和 VerticalAlignment 。 了解这些属性的作用非常重要,因为这些属性是控制元素在 Windows Presentation Foundation (WPF) 应用程序中的位置的基础。

元素定位

可使用 WPF 通过多种方式来定位元素。 但是,即使只是选择正确的元素,也不会获得理想的布局 Panel 。 对定位进行精细控制需要了解、 HorizontalAlignment Margin Padding 和 VerticalAlignment 属性
WPF 实践_第15张图片
乍一看, Button 此图中的元素可能显示为随机放置。 但是,其位置实际上是通过使用边距、对齐和填充加以精确控制的。
下面的示例描述如何创建上图中的布局。 Border元素封装父,其 StackPanel 值为 Padding 15 个与设备无关的像素。 此帐户用于 LightBlue 环绕子项的窄带区 StackPanel 。 的子元素 StackPanel 用于演示本主题中详细介绍的各个定位属性。 三个 Button 元素用于演示 Margin 和 HorizontalAlignment 属性。

// Create the application's main Window.
mainWindow = new Window ();
mainWindow.Title = "Margins, Padding and Alignment Sample";

// Add a Border
myBorder = new Border();
myBorder.Background = Brushes.LightBlue;
myBorder.BorderBrush = Brushes.Black;
myBorder.Padding = new Thickness(15);
myBorder.BorderThickness = new Thickness(2);

myStackPanel = new StackPanel();
myStackPanel.Background = Brushes.White;
myStackPanel.HorizontalAlignment = HorizontalAlignment.Center;
myStackPanel.VerticalAlignment = VerticalAlignment.Top;

TextBlock myTextBlock = new TextBlock();
myTextBlock.Margin = new Thickness(5, 0, 5, 0);
myTextBlock.FontSize = 18;
myTextBlock.HorizontalAlignment = HorizontalAlignment.Center;
myTextBlock.Text = "Alignment, Margin and Padding Sample";
Button myButton1 = new Button();
myButton1.HorizontalAlignment = HorizontalAlignment.Left;
myButton1.Margin = new Thickness(20);
myButton1.Content = "Button 1";
Button myButton2 = new Button();
myButton2.HorizontalAlignment = HorizontalAlignment.Right;
myButton2.Margin = new Thickness(10);
myButton2.Content = "Button 2";
Button myButton3 = new Button();
myButton3.HorizontalAlignment = HorizontalAlignment.Stretch;
myButton3.Margin = new Thickness(0);
myButton3.Content = "Button 3";

// Add child elements to the parent StackPanel.
myStackPanel.Children.Add(myTextBlock);
myStackPanel.Children.Add(myButton1);
myStackPanel.Children.Add(myButton2);
myStackPanel.Children.Add(myButton3);

// Add the StackPanel as the lone Child of the Border.
myBorder.Child = myStackPanel;

// Add the Border as the Content of the Parent Window Object.
mainWindow.Content = myBorder;
mainWindow.Show ();

WPF 实践_第16张图片

了解 Alignment 属性

HorizontalAlignment和 VerticalAlignment 属性描述应如何将子元素定位到父元素的已分配布局空间内。 结合使用这些属性可精确定位子元素。 例如,的子元素 DockPanel 可以指定四个不同的水平对齐方式: Left 、 Right 、或 Center ,或以 Stretch 填充可用空间。 类似的值可用于垂直定位。
WPF 实践_第17张图片

Button myButton1 = new Button();
myButton1.HorizontalAlignment = HorizontalAlignment.Left;
myButton1.Content = "Button 1 (Left)";
Button myButton2 = new Button();
myButton2.HorizontalAlignment = HorizontalAlignment.Right;
myButton2.Content = "Button 2 (Right)";
Button myButton3 = new Button();
myButton3.HorizontalAlignment = HorizontalAlignment.Center;
myButton3.Content = "Button 3 (Center)";
Button myButton4 = new Button();
myButton4.HorizontalAlignment = HorizontalAlignment.Stretch;
myButton4.Content = "Button 4 (Stretch)";

WPF 实践_第18张图片
WPF 实践_第19张图片

TextBlock myTextBlock = new TextBlock();
myTextBlock.FontSize = 18;
myTextBlock.HorizontalAlignment = HorizontalAlignment.Center;
myTextBlock.Text = "VerticalAlignment Sample";
Grid.SetRow(myTextBlock, 0);
Button myButton1 = new Button();
myButton1.VerticalAlignment = VerticalAlignment.Top;
myButton1.Content = "Button 1 (Top)";
Grid.SetRow(myButton1, 1);
Button myButton2 = new Button();
myButton2.VerticalAlignment = VerticalAlignment.Bottom;
myButton2.Content = "Button 2 (Bottom)";
Grid.SetRow(myButton2, 2);
Button myButton3 = new Button();
myButton3.VerticalAlignment = VerticalAlignment.Center;
myButton3.Content = "Button 3 (Center)";
Grid.SetRow(myButton3, 3);
Button myButton4 = new Button();
myButton4.VerticalAlignment = VerticalAlignment.Stretch;
myButton4.Content = "Button 4 (Stretch)";
Grid.SetRow(myButton4, 4);
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      WindowTitle="VerticalAlignment Sample">
  <Border Background="LightBlue" BorderBrush="Black" BorderThickness="2" Padding="15">
    <Grid Background="White" ShowGridLines="True">
      <Grid.RowDefinitions>
        <RowDefinition Height="25"/>
        <RowDefinition Height="50"/>
        <RowDefinition Height="50"/>
        <RowDefinition Height="50"/>
        <RowDefinition Height="50"/>
      Grid.RowDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0" FontSize="18" HorizontalAlignment="Center">VerticalAlignment SampleTextBlock>
            <Button Grid.Row="1" Grid.Column="0" VerticalAlignment="Top">Button 1 (Top)Button>
            <Button Grid.Row="2" Grid.Column="0" VerticalAlignment="Bottom">Button 2 (Bottom)Button>    
            <Button Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">Button 3 (Center)Button>
            <Button Grid.Row="4" Grid.Column="0" VerticalAlignment="Stretch">Button 4 (Stretch)Button>          
    Grid>
  Border>    
Page>

WPF 实践_第20张图片

了解 Margin 属性

Margin属性描述元素及其子元素和同级之间的距离。 Margin 通过使用类似的语法,值可以是统一的 Margin=“20” 。 使用此语法时,将对 Margin 元素应用20个与设备无关的统一像素。 Margin 值还可以采用四个非重复值的形式,每个值描述不同的边距,以应用于该) 顺序的左、上、右和下 (,例如 Margin=“0,10,5,25” 。 正确使用 Margin 属性可以非常精确地控制元素的呈现位置以及其邻居元素和子元素的呈现位置。

<Button Margin="0,10,0,10">Button 1Button>
<Button Margin="0,10,0,10">Button 2Button>
<Button Margin="0,10,0,10">Button 3Button>

了解 Padding 属性

填充 Margin 在大多数方面类似于。 仅在几个类上公开填充属性,这主要是为了方便: Block 、 Border 、 Control 和 TextBlock 是公开空白属性的类的示例。 Padding属性按指定的值增大子元素的有效大小 Thickness 。
下面的示例演示如何将应用于 Padding 父 Border 元素。

<Border Background="LightBlue" 
        BorderBrush="Black" 
        BorderThickness="2" 
        CornerRadius="45" 
        Padding="25">

在应用程序中使用 Alignment、Margins 和 Padding

HorizontalAlignment、 Margin 、 Padding 和 VerticalAlignment 提供创建复杂的所需的定位控件 用户界面 (UI) 。 可利用每个属性的作用来更改子元素定位,以便能够灵活地创建动态应用程序和用户体验。
下面的示例演示本主题中详述的各个概念。 此示例在本主题的第一个示例中的基础结构上构建,此示例将一个 Grid 元素添加为 Border 第一个示例中的的子元素。 Padding 应用于父 Border 元素。 Grid用于在三个子元素之间对空间进行分区 StackPanel 。 Button 再次使用元素显示和的各种效果 Margin HorizontalAlignment 。 TextBlock 向每个元素添加元素 ColumnDefinition 可更好地定义应用于 Button 每个列中的元素的各种属性。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" WindowTitle="Margins, Padding and Alignment Sample">
  <Border Background="LightBlue" 
          BorderBrush="Black" 
          BorderThickness="2" 
          CornerRadius="45" 
          Padding="25">
    <Grid Background="White" ShowGridLines="True">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
      Grid.ColumnDefinitions>
        
    <StackPanel Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Name="StackPanel1" VerticalAlignment="Top">
        <TextBlock FontSize="18" HorizontalAlignment="Center" Margin="0,0,0,15">StackPanel1TextBlock>
        <Button Margin="0,10,0,10">Button 1Button>
        <Button Margin="0,10,0,10">Button 2Button>
        <Button Margin="0,10,0,10">Button 3Button>
        <TextBlock>ColumnDefinition.Width="Auto"TextBlock>
        <TextBlock>StackPanel.HorizontalAlignment="Left"TextBlock>
        <TextBlock>StackPanel.VerticalAlignment="Top"TextBlock>
        <TextBlock>StackPanel.Orientation="Vertical"TextBlock>
        <TextBlock>Button.Margin="0,10,0,10"TextBlock>
    StackPanel>
    
    <StackPanel Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" Name="StackPanel2" VerticalAlignment="Top" Orientation="Vertical">
        <TextBlock FontSize="18" HorizontalAlignment="Center" Margin="0,0,0,15">StackPanel2TextBlock>
        <Button Margin="10,0,10,0">Button 4Button>
        <Button Margin="10,0,10,0">Button 5Button>
        <Button Margin="10,0,10,0">Button 6Button>
        <TextBlock HorizontalAlignment="Center">ColumnDefinition.Width="*"TextBlock>
        <TextBlock HorizontalAlignment="Center">StackPanel.HorizontalAlignment="Stretch"TextBlock>
        <TextBlock HorizontalAlignment="Center">StackPanel.VerticalAlignment="Top"TextBlock>
        <TextBlock HorizontalAlignment="Center">StackPanel.Orientation="Horizontal"TextBlock>
        <TextBlock HorizontalAlignment="Center">Button.Margin="10,0,10,0"TextBlock>
    StackPanel>        
        
    <StackPanel Grid.Column="2" Grid.Row="0" HorizontalAlignment="Left" Name="StackPanel3" VerticalAlignment="Top">
        <TextBlock FontSize="18" HorizontalAlignment="Center" Margin="0,0,0,15">StackPanel3TextBlock>
        <Button Margin="10">Button 7Button>
        <Button Margin="10">Button 8Button>
        <Button Margin="10">Button 9Button>
        <TextBlock>ColumnDefinition.Width="Auto"TextBlock>
        <TextBlock>StackPanel.HorizontalAlignment="Left"TextBlock>
        <TextBlock>StackPanel.VerticalAlignment="Top"TextBlock>
        <TextBlock>StackPanel.Orientation="Vertical"TextBlock>
        <TextBlock>Button.Margin="10"TextBlock>      
    StackPanel>
  Grid>
  Border>    
Page>

WPF 实践_第21张图片

2.2 如何:创建一个RoutedCommand

  1. 此示例演示如何创建自定义RoutedCommand以及如何通过创建ExecutedRoutedEventHandler和一个CanExecuteRoutedEventHandler并将它们附加到XAML绑定。有关命令的详细信息,请参阅指挥概述.
//创建RoutedCommand定义命令并实例化它。
		public static  RoutedCommand Load_Tested_DBC = new RoutedCommand("Load_Tested_DBC",typeof(MainWindow));
		private void ExecutedLoad_Tested_DBC(object sender,ExecutedRoutedEventArgs e)
		{
		 	MessageBox.Show("ExecutedLoad_Tested_DBC Executed");
		}
//为了在应用程序中使用该命令,必须创建定义该命令的事件处理程序。
        private void CanExecuteLoad_cmd(object sender,
            CanExecuteRoutedEventArgs e)
        {
            Control target = e.Source as Control;

            if (target != null)
            {
                e.CanExecute = true;
            }
            else
            {
                e.CanExecute = false;
            }
        }
  1. 接下来,XAML绑定创建,该命令与事件处理程序相关联。这个XAML绑定在特定对象上创建。对象定义XAML绑定在元素树中
<Window x:Class="Wpf_auto_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:Wpf_auto_test"
        mc:Ignorable="d"
        Title="test" Height="600" Width="800">
    <Window.CommandBindings>
       
        <CommandBinding Command ="{x:Static local:MainWindow.Load_Tested_DBC}"
                    Executed="ExecutedLoad_Tested_DBC"
                    CanExecute="CanExecuteLoad_cmd" />
      
    Window.CommandBindings>
 		public MainWindow()
        {
            InitializeComponent();
 			CommandBinding customCommandBinding = null;

            customCommandBinding = new CommandBinding(Load_Tested_DBC, ExecutedLoad_Tested_DBC, CanExecuteLoad_cmd);
            this.CommandBindings.Add(customCommandBinding);
       }
  1. 最后一步是调用命令。调用命令的一种方法是将其与ICommandSource,如按钮.
<Button  Command="{x:Static local:MainWindow.Load_Tested_DBC}">
载入被测DBC
Button>

2.3 对话框

对话框: OpenFileDialog、 PrintDialog和 SaveFileDialog。

OpenFileDialog

实例

// Configure open file dialog box
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".txt"; // Default file extension
dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension

// Show open file dialog box
Nullable<bool> result = dlg.ShowDialog();

// Process open file dialog box results
if (result == true)
{
    // Open document
    string filename = dlg.FileName;
}

OpenFileDialog官方链接

2.4 TreeView

固定数据和结构的创建

<TreeView Name="myTreeViewEvent" >
  <TreeViewItem Header="Employee1" IsSelected="True">
    <TreeViewItem Header="Jesper Aaberg"/>
    <TreeViewItem Header="Employee Number">
      <TreeViewItem Header="12345"/>
    </TreeViewItem>
    <TreeViewItem Header="Work Days">
      <TreeViewItem Header="Monday"/>
      <TreeViewItem Header="Tuesday"/>
      <TreeViewItem Header="Thursday"/>
    </TreeViewItem>
  </TreeViewItem>
  <TreeViewItem Header="Employee2">
    <TreeViewItem Header="Dominik Paiha"/>
    <TreeViewItem Header="Employee Number">
      <TreeViewItem Header="98765"/>
    </TreeViewItem>
    <TreeViewItem Header="Work Days">
      <TreeViewItem Header="Tuesday"/>
      <TreeViewItem Header="Wednesday"/>
      <TreeViewItem Header="Friday"/>
    </TreeViewItem>
  </TreeViewItem>
</TreeView>

使用模板进行样式设定与数据绑定

  1. 首先来定义数据
    XAML
<TreeView x:Name="Tested_DBC_TreeView" >
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding childrenS}">
                            <TextBlock Text="{Binding Name}"/>
                        HierarchicalDataTemplate>
                    TreeView.ItemTemplate>
                    <TreeView.ItemContainerStyle>
                        <Style TargetType="{x:Type TreeViewItem}">
                            "Selected" Handler="item_SelectedItemChanged"/>
                            "Background" Value="Red"/>
                            
                        Style>

                    TreeView.ItemContainerStyle>
                TreeView>
  1. 后台CS文件中定义了模板类
public class Location
    {
        public string Name { get; set; }
        private List<Location> _childrenS = new List<Location>();
        public List<Location> childrenS
        {
            get
            {
                return this._childrenS;
            }
            set
            {
                if (value != this._childrenS)
                {
                    this._childrenS = value;
                }
            }
        }     
    }
  1. 页中定义算法
public partial class HomeConfig : Page
    {      
        private DbcHelper dbcTestedHelper = new DbcHelper();
        private List<Location> listTested = null;
        private void UpdateTreeview(ref List<Location> x,string ss)
        {
            Location DbcTested_node = new Location();
            DbcTested_node.Name = "node";
            for (int i = 0; i < dbcTestedHelper.dbcFile.nodes.Count; i++)
            {
                DbcTested_node.childrenS.Add(new Location() { Name = dbcTestedHelper.dbcFile.nodes[i] });
            }
            Location DbcTested_message = new Location();
            DbcTested_message.Name = "message";
            for (int i = 0; i < dbcTestedHelper.dbcFile.messages.Count; i++)
            {
                Location temp = new Location();
                for (int j = 0; j < dbcTestedHelper.dbcFile.messages[i].signals.Count; j++)
                {
                    
                    temp.Name = dbcTestedHelper.dbcFile.messages[i].messageName;
                    temp.childrenS.Add(new Location() { Name = dbcTestedHelper.dbcFile.messages[i].signals[j].signalName });
        
                   
                }
                DbcTested_message.childrenS.Add(temp);
            }
            Location loc =  new Location();
            loc.Name =ss;
            loc.childrenS.Add(DbcTested_node);
            loc.childrenS.Add(DbcTested_message);
            x = new List<Location>() { loc };
        }
        public static RoutedCommand Load_Tested_DBC = new RoutedCommand("Load_Tested_DBC", typeof(MainWindow));
        //展开全部树节点
        public static void ExpandAll(System.Windows.Controls.TreeView treeView)
        {
            //
            ExpandInternal(treeView);
        }
        /// 
        /// 
        /// 
        /// 
        private static void ExpandInternal(System.Windows.Controls.ItemsControl targetItemContainer)
        {
            var itemsControl = targetItemContainer as ItemsControl;

            if (itemsControl == null || itemsControl.ItemsSource == null) return;
            foreach (object item in itemsControl.ItemsSource)
            {
                //
                TreeViewItem container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
                if (container != null)
                {
                    //
                    container.IsExpanded = true;

                    if (container.ItemContainerGenerator.Status != System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
                    {
                        container.UpdateLayout();
                    }
                    if (container.ItemsSource != null)
                    {
                        ExpandInternal(container);
                    }
                }
                
            }
        }
}
  1. 调用
UpdateTreeview(ref listTested, "被测DBC信息");
Tested_DBC_TreeView.ItemsSource = listTested;
ExpandAll(Tested_DBC_TreeView);

2.5 DataGrid

2.5.1 使用DataGrid自带的事件对表格进行操作

执行步骤:

1、在前端定义一个表格

这里注意一点:将SelectionUnit=“Cell” SelectionMode="Single"这两个属性设定后,就可以对某个单元格进行操作了,默认情况下,点击DataGrid后,默认是选中整行的。


<Border BorderBrush="Black" BorderThickness="1" Grid.Row="3" Margin="5,5,5,5">
    <DataGrid x:Name="CustomerDataGrid_AddEvent" ItemsSource="{Binding Path = .}" AutoGenerateColumns="False" SelectionUnit="Cell" SelectionMode="Single">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Column1"  Binding="{Binding column1.name}"/>
            <DataGridHyperlinkColumn Header="Column2" Binding="{Binding column2.name}"  />
            <DataGridCheckBoxColumn Header="Column3" Binding="{Binding column3.name}" />
        DataGrid.Columns>
    DataGrid>
Border>

2、 为这张表格的单元格定义一个数据类型

为了能够让单元格中具有更多的操作行为,我为这个单元格单独定义了类型,在C#后台填充单元格的时候,直接填充类型即可,然后在WPF前端,就可以访问这个类型对应的某个属性显示在DataGrid控件当中了,就如同第一步{Binding column2.name}这样,实则它访问的就是GridCell类型的name

public class GridCell
{
    public string name { get; set; }
    public void EditingCalback()
    {
        Console.WriteLine("GridCell Editing Callback:" + name);
    }
}

3、为这张表格定义一个VM数据层

这个VM数据层就是为了保存这张表所有要呈现的数据,及其操作表格时对应的函数逻辑。

public class DataGridWithEvent
{
    public GridCell column1 { get; set; }       // 向单元格填写自定义个类型;
    public GridCell column2 { get; set; }       // 向单元格填写自定义个类型;
    public GridCell column3 { get; set; }       // 向单元格填写自定义个类型;

    // 当表格控件被编辑时,会调用单元格自身实例对应的函数;
    public void JudegePropertyCall_CellEditing(string colHeader)
    {
        switch(colHeader)
        {
            case "Column1":
                this.column1.EditingCalback();
                break;

            case "Column2":
                this.column2.EditingCalback();
                break;

            case "Column3":
                this.column3.EditingCalback();
                break;

            default:
                break;
        }
    }

}

4、最后在逻辑处理代码中填充数据和对应的事件

ObservableCollection<DataGridWithEvent> list = InitDataGridWithEventData.InitData();
this.CustomerDataGrid_AddEvent.DataContext = list;

// 以下是表格事件;
this.CustomerDataGrid_AddEvent.BeginningEdit += CustomerDataGrid_AddEvent_BeginningEdit;              // 事件一:单元格开始编辑事件;
this.CustomerDataGrid_AddEvent.SelectionChanged += CustomerDataGrid_AddEvent_SelectionChanged;        // 事件二:单元格选择出现变化时;
this.CustomerDataGrid_AddEvent.GotFocus += CustomerDataGrid_AddEvent_GotFocus;                        // 事件三:DataGrid表格点击单元格获取焦点时;

// 以下是鼠标事件;
this.CustomerDataGrid_AddEvent.MouseMove += CustomerDataGrid_AddEvent_MouseMove;                      // 事件四:鼠标移动到某个单元格上时触发(实验函数增加了鼠标拖动效果);
this.CustomerDataGrid_AddEvent.GotMouseCapture += CustomerDataGrid_AddEvent_GotMouseCapture;          // 事件五:使用这个事件事件鼠标拖拽更加稳定;

this.CustomerDataGrid_AddEvent.MouseLeftButtonDown += CustomerDataGrid_AddEvent_MouseLeftButtonDown;  // 事件六:鼠标左键点击事件,这个事件只针对DataGrid整个表格;
this.CustomerDataGrid_AddEvent.MouseEnter += CustomerDataGrid_AddEvent_MouseEnter;                    // 事件七:鼠标进入整个表格时触发,且只触发一次;

// 另一个元素接收鼠标拖拽事件;
this.ReceiveDataLabel.AllowDrop = true;
this.ReceiveDataLabel.Drop += ReceiveDataLabel_Drop;

2.5.2 自动行号

给DataGrid添加序号,是通过 LoadingRow,自动在列前边增加一列序号,该列为自动添加,没有列名字。

  1. 给DataGrid添加LoadingRow事件
<DataGrid x:Name="gridConfigData" ItemsSource="{Binding Path = .}" AutoGenerateColumns="False" SelectionUnit="Cell" SelectionMode="Single" LoadingRow="gridConfigData_LoadingRow">
  1. LoadingRow实现
private void dataGridEquipment_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            e.Row.Header = e.Row.GetIndex() + 1;
        }

2.5.3 datagrid 中添加单选按钮

我用的模板方式

 <DataGridTemplateColumn Header="帧的格式"  >
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <StackPanel>
                                        <RadioButton GroupName="{Binding GC_ID.name}" Content ="只接收 "  Name="只接收" IsThreeState="False" IsChecked="{Binding OnlyRevFlag.Flag, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                              VerticalAlignment="Center" HorizontalAlignment="Left" Unchecked="CheckBox_Unchecked" Checked="CheckBox_Checked"/>
                                        <RadioButton GroupName="{Binding GC_ID.name}" Content ="多帧 " Name="多帧" IsThreeState="False" IsChecked="{Binding MultiFrameFlag.Flag, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                              VerticalAlignment="Center" HorizontalAlignment="Left" Unchecked="CheckBox_Unchecked" Checked="CheckBox_Checked"/>
                                        <RadioButton GroupName="{Binding GC_ID.name}" Content ="单帧多状态 " Name="单帧多状态" IsThreeState="False" IsChecked="{Binding SingleFrameMultiStateFlag.Flag, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                              VerticalAlignment="Center" HorizontalAlignment="Left" Unchecked="CheckBox_Unchecked"  Checked="CheckBox_Checked"/>
                                        <RadioButton GroupName="{Binding GC_ID.name}" Content ="单帧" Name="OneToOne" IsThreeState="False"  IsChecked="{Binding N1_1.Flag, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                              VerticalAlignment="Center" HorizontalAlignment="Left" Unchecked="CheckBox_Unchecked"  Checked="CheckBox_Checked"/>
                                    StackPanel>
                                DataTemplate>
                            DataGridTemplateColumn.CellTemplate>
                        DataGridTemplateColumn>

关键在于GroupName="{Binding GC_ID.name}",在这里如果在一行多个单元格需要单选,这需要添加多个 GC_ID2…等声明组。

2.5.4 datagrid右键功能

我这里用右键来做行操作

  1. 添加下面代码在放在中间
 <DataGrid.ContextMenu>
 	<ContextMenu Name="dgmenu1" StaysOpen="true">
    	<MenuItem Header="删除行">
        MenuItem>
    ContextMenu>
  DataGrid.ContextMenu>
  1. 添加事件
 this.gridConfigData.MouseRightButtonDown += RightClick;
  1. 事件代码
 private DataGridRow DGR = null;
        private void RightClick(object sender, MouseButtonEventArgs e)
        {
            DataGrid g = sender as DataGrid;
            DGR = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(g));
            if (g.ContextMenu != null)
            {
                g.ContextMenuOpening += G_ContextMenuOpening;
            }
        }


        private void G_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            DataGrid g = sender as DataGrid;
            g.ContextMenu.Items.Clear();

            MenuItem item1 = new MenuItem();
            item1.Header = "删除行";
            item1.Click += new RoutedEventHandler(Del_Row_Click);

            MenuItem item2 = new MenuItem();
            item2.Header = "行信息";
            item2.Click += new RoutedEventHandler(Message_Row_Click);

            g.ContextMenu.Items.Add(item1);
            g.ContextMenu.Items.Add(item2);
        }
        private void Message_Row_Click(object sender, RoutedEventArgs e)
        {
            ObservableCollection<DataGridWithEvent> list = (ObservableCollection<DataGridWithEvent>)this.gridConfigData.DataContext;
            for (int i = 0; i < list.Count; i++)
            {
                var row = gridConfigData.ItemContainerGenerator.ContainerFromItem(gridConfigData.Items[i]) as DataGridRow;
                if (DGR == row)
                {
                    if (list[i].check != null)
                    {
                        MessageBox.Show(list[i].check.check_string);
                    }
                    else
                    {
                        MessageBox.Show("无运行信息");
                    }
                    break;
                }
            }
            
        }

        private void Del_Row_Click(object sender, RoutedEventArgs e)
        {
            ObservableCollection<DataGridWithEvent> list = (ObservableCollection<DataGridWithEvent>)this.gridConfigData.DataContext;
            for (int i = 0; i < list.Count; i++)
            {
                var row = gridConfigData.ItemContainerGenerator.ContainerFromItem(gridConfigData.Items[i]) as DataGridRow;
                if (DGR == row)
                {
                    list.RemoveAt(i);
                    break;
                }
            }
            this.gridConfigData.DataContext = null;
            this.gridConfigData.DataContext = list;
        }

2.5.5 在datagrid 添加ComboBox

  1. ComboBox实现方法:
    参见WPF中ComboBox几种数据绑定的方法
    一、用字典给ItemsSource赋值

XMAL、

CS文件、

private void BindData()

{
Dictionary dicItem = new Dictionary();
dicItem.add(1,“北京”);

dicItem.add(2,“上海”);

dicItem.add(3,“广州”);

cmb_list.ItemsSource =dicItem;

cmb_list.SelectIndex=0;

}

二、XAML 中直接绑定(此绑定很不实用)

直接在XAML中绑定数据

SelectedValue 返回的是 System.Windows.Controls.ComboBoxItem:上海

Text 返回的才是 显示的值。

如果想和Winform 中的获取 Value的值。可以 实用 Tag 属性。Tag为 Object 类型。可以获取Value值。

三、绑定IList集合属性 (此绑定比较实用 本人比较喜欢)

public class City
{
public int ID { get; set; }
public string Name { get; set; }
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
List list = new List();
list.Add(new City { ID = 1, Name = “上海” });
list.Add(new City { ID = 2, Name = “北京” });
list.Add(new City { ID =3, Name = “天津” });
cmb_list.ItemsSource = list;

    }

ToString()方法返回的字符串信息。

 注意:切记 要设置 DisplayMemberPath="Name" 属性

设置 SelectedValuePath="ID"属性。如果City覆盖了 ToString()方法。

SelectItem ToString() 返回 City ToString()返回的内容。

SelectValue ToString() 为 City 中ID的值。

四、 绑定DataTable

private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn(“ID”, typeof(Int32)));
dt.Columns.Add(new DataColumn(“Name”, typeof(string)));

        DataRow dr1 = dt.NewRow();
        dr1["ID"] = 1;
        dr1["Name"] = "上海";
        dt.Rows.Add(dr1);
        dt.Rows.Add(2, "北京");
        dt.Rows.Add(3,"天津");

        cmb_list.ItemsSource =dt.DefaultView; 
    }

还有
2. 我的做法:
xaml

<DataGridTemplateColumn Header="发送方式"  >
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <ComboBox Name ="cmb_SendingMode"  SelectedIndex="{Binding Sending_method.Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"  SelectionChanged ="cmb_SelectionChanged" >
                                        <ComboBoxItem Content="功能" Tag="1">ComboBoxItem>
                                        <ComboBoxItem Content="物理" Tag="2">ComboBoxItem>
                                    ComboBox>                                        
                                    DataTemplate>
                                DataGridTemplateColumn.CellTemplate>
                         DataGridTemplateColumn>

c#

 private void SetComboBox(ref ComboBox Cbox)
        {
            if (Cbox != null)
            {
                DataGridWithEvent dwe = Cbox.DataContext as DataGridWithEvent;
                if (dwe != null)
                {
                    if (Cbox.Name == "cmb_SendingMode")
                    {
                        dwe.Sending_method = new GridCell(Cbox.SelectedIndex);
                    }
                    else
                    {
                    }
                }
            }
        }
        private void cmb_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                ComboBox CB = e.Source as ComboBox;
                SetComboBox(ref CB);
            }
            catch (Exception en)
            {
                ExceptionHandler.Handle(en);
            }

        }

数据里只绑定选项编号就行。在WPF中属性可以绑定数据。

2.6 拖放概述

来自官方文档

WPF 中的拖放支持

在 WPF 中,任何 UIElement 或 ContentElement 都可以参与拖放。 拖放操作所需的事件和方法是在 DragDrop 类中定义的。 UIElement 和 ContentElement 类包含 DragDrop 附加事件的别名,从而在 UIElement 或 ContentElement 作为基元素继承时,这些事件出现在类成员列表中。 附加到这些事件的事件处理程序会附加到基础 DragDrop 附加事件,并接收相同的事件数据实例。

数据传输

拖放属于广义的数据传输。 数据传输包括拖放和复制粘贴操作。 拖放操作类似于用于借助系统剪贴板将数据从一个对象或应用程序传输到另一个对象或应用程序的复制粘贴或剪切和粘贴操作。 这两种类型的操作均要求:

  • 提供数据的源对象。
  • 用于临时存储传输的数据的方法。
  • 接收数据的目标对象。

在复制粘贴操作中,系统剪贴板用于临时存储传输的数据;在拖放操作中,DataObject 用于存储数据。 从概念上讲,数据对象由一对或多对包含实际数据的 Object 和对应的数据格式标识符组成。
拖动源通过调用静态 DragDrop.DoDragDrop 方法和向其传递传输的数据来启动拖放操作。 如有必要,DoDragDrop 方法将使 DataObject 中的数据自动换行。 为了更好地控制数据格式,可将 DataObject 中的数据换行,然后再将其传递至 DoDragDrop 方法。 拖放目标负责从 DataObject 中提取数据。 有关使用数据对象的详细信息,请参阅数据和数据对象。
拖放操作的源和目标均为 UI 元素;然而,实际正在传输的数据通常不具有可视表示形式。 可以编写代码来提供拖动的数据的可视表示形式(比如当在 Windows 资源管理器中拖动文件时会出现这种情况)。 默认情况下,通过更改光标将反馈提供给用户,以便表示拖放操作将对数据产生的影响,例如将移动数据还是复制数据。

拖放效果

拖放操作对传输的数据可具有不同的效果。 例如,可以复制数据,或者可以移动数据。 WPF 定义可用于指定拖放操作效果的 DragDropEffects 枚举。 在拖动源中,可以指定源在 DoDragDrop 方法中允许的效果。 在拖放目标中,可以指定目标在 Effects 类的 DragEventArgs 属性的中预期的效果。 当拖放目标指定其在 DragOver 事件中的预期效果时,该信息将被传递回 GiveFeedback 事件中的拖动源。 拖动源则使用此信息通知用户拖放目标想要对数据产生的效果。 放置数据时,拖放目标指定其在 Drop 事件中的实际效果。 该信息会作为 DoDragDrop 方法的返回值传递回拖动源。 如果拖放目标返回并不在 allowedEffects 拖动源列表中的效果,那么将取消拖放操作,且不会进行任何数据传输。
请务必记住,在 WPF 中,DragDropEffects 值仅用于提供有关拖放操作效果的拖动源和拖放目标之间的通信。 拖放操作的实际效果取决于你在应用程序中编写的相应代码。
例如,拖放目标可以指定在其中放置数据的效果是移动数据。 然而,若要移动数据,必须将数据添加到目标元素并从源元素中删除数据。 源元素可能指示允许移动数据,但是如果没有提供从源元素中删除数据的代码,那么最终结果将为复制但不删除数据。

拖放事件

拖放操作支持事件驱动模型。 拖动源和拖放目标都使用一组标准的事件来处理拖放操作。 下表总结了标准的拖放事件。 它们是 DragDrop 类中的附加事件。
WPF 实践_第22张图片
WPF 实践_第23张图片
若要处理对象实例的拖放事件,请为上表中所列的事件添加处理程序。 若要处理类级别的拖放事件,请替代相应的虚拟 OnEvent 和 OnPreviewEvent 方法。 有关详细信息,请参阅按控件基类进行的路由事件类处理。

实现拖放

UI 元素可以是拖动源、拖放目标或两者均可。 若要实现基本拖放,请编写用于启动拖放操作和处理放置的数据的代码。 可以通过处理可选拖放事件增强拖放体验。
若要实现基本拖放,将完成以下任务:

  • 标识将作为拖动源的元素。 拖动源可以是 UIElement 或 ContentElement。
  • 在将启动拖放操作的拖动源上创建事件处理程序。 此事件通常是 MouseMove 事件。
  • 在拖动源事件处理程序中,调用 DoDragDrop 方法启动拖放操作。 在 DoDragDrop 调用中,指定拖动源、要传输的数据和允许的效果。
  • 标识将作为拖放目标的元素。 拖放目标可以是 UIElement 或 ContentElement。
  • 在拖放目标上,将 AllowDrop 属性设置为 true。
  • 在拖放目标中,创建 Drop 事件处理程序以处理放置的数据。
  • 在 Drop 事件处理程序中,利用 DragEventArgs 和 GetDataPresent 方法提取 GetData 中的数据。
  • 在 Drop 事件处理程序中,使用数据来执行所需的拖放操作。
    可以通过创建自定义 DataObject 和处理可选拖动源和拖放目标事件来增加拖放实现,如以下任务中所示:
  • 若要传输自定义数据或多个数据项,请创建一个 DataObject以传递至 DoDragDrop 方法。
  • 若要在拖动过程中执行其他操作,请处理拖放目标上的 DragEnterDragOver 和 DragLeave 事件。
  • 若要更改鼠标指针外观,请处理拖动源上的 GiveFeedback 事件。
  • 若要更改取消拖放操作的方式,请处理拖动源上的 QueryContinueDrag 事件。
<TreeView x:Name="Tested_DBC_TreeView" AllowDrop="True" MouseMove="Tested_DBC_TreeView_MouseMove" MouseDown="Tested_DBC_TreeView_MouseDown">
        /// 
        /// 事件三:DataGrid表格点击单元格获取焦点的事件;
        /// 
        /// 
        /// 
        private void CustomerDataGrid_AddEvent_GotFocus(object sender, RoutedEventArgs e)
        {
            try
            {
                //Console.WriteLine("GotFocus;函数参数e反馈的实体是单元格内数据类型:" + e.OriginalSource.GetType());
                DataGridCell dgc = e.OriginalSource as DataGridCell;
                if (dgc != null)
                {
                    if (J_Mouse_Drop_X(dgc.Column.Header as string))
                        //if ((dgc.Column.Header as string == "被测项") || (dgc.Column.Header as string == "期望项"))
                    {
                        cellE = (e.Source as DataGrid).CurrentCell.Item as DataGridWithEvent;
                    }
                }
            }
            catch (Exception en)
            {
                ExceptionHandler.Handle(en);
            }



        }
        #region code_MouseMoveDrop
        /*
         * 鼠标拖拽处理
         */
        private Point lastMouseDownPoint = new Point();
        private Location draggedTVItem;
        TreeView TV = null;
        private DataGridCell targetItem;

        
        /// 
        /// 拖放结束,找到目标项目
        /// 
        /// 
        /// 
        private void GridConfigData_PreviewDrop(object sender, DragEventArgs e)
        {
            try
            {
                e.Effects = DragDropEffects.None;
                e.Handled = true;

                // 设置允许放下的条件
                DataGrid tb = sender as DataGrid;
                if (draggedTVItem != null
                  && tb == gridConfigData)
                {
                    //targetItem = tb;
                    
                    e.Effects = DragDropEffects.Move;
                    var cell = UIHelpers.TryFindFromPoint<DataGridCell>((UIElement)sender, e.GetPosition(tb));
                    if (cell != null)
                    {
                        targetItem = cell;
                        if (J_Mouse_Drop_X(targetItem.Column.Header as string))
                            //if ((targetItem.Column.Header as string == "被测项") || (targetItem.Column.Header as string == "期望项"))
                        {
                            targetItem = cell;

                        }
                        else
                        {
                            targetItem = null;
                        }
                    }
                }

            }
            catch (Exception)
            {
            }
        }

        private void GridConfigData_PreviewDragOver(object sender, DragEventArgs e)
        {
            try
            {
                if (TV != null)
                {
                    Point currentPosition = e.GetPosition(TV);
                    double width = TV.Width;
                    double hight = TV.Height;
                    if ((Math.Abs(currentPosition.X - lastMouseDownPoint.X) > width) ||
                        (Math.Abs(currentPosition.Y - lastMouseDownPoint.Y) > hight))
                    {
                        //检查拖入项目
                        if (draggedTVItem != null)
                        {
                            e.Effects = DragDropEffects.Move;
                        }
                        else
                        {
                            e.Effects = DragDropEffects.None;
                        }
                    }
                    e.Handled = true;
                }

            }
            catch (Exception)
            {
            }
        }
        /// 
        /// 按下鼠标
        /// 
        /// 
        /// 
        private void TreeView_MouseDown(object sender, MouseButtonEventArgs e)
        {
            TV = sender as TreeView;
            if (e.ChangedButton == MouseButton.Left)
            {
                lastMouseDownPoint = e.GetPosition(TV);
            }
        }
        /// 
        /// 拖拽完成
        /// 
        private void DropFinishHandler()
        {
            
            if (draggedTVItem != null)
            {
                //用于显示内容
                string sss = (string)("ID:" + draggedTVItem.Lmessage.messageName.ToString() + "+" +
                        "SIG:" + draggedTVItem.Lsignal.signalName.ToString());
                //和聚焦事件连用,用于获得cellE
                targetItem.Focus();
                try
                {
                    this.gridConfigData.Dispatcher.Invoke(new Action(() =>
                    {
                        targetItem.Content =sss;
                    }));
                    if (cellE != null)
                    {
                        switch (targetItem.Column.Header)
                        {
                            case "被测项":
                                if (cellE.Tested == null)
                                {
                                    cellE.Tested = new GridCell(sss, draggedTVItem.Lmessage, draggedTVItem.Lsignal);
                                }
                                else
                                {
                                    cellE.Tested.EditEndingCalback(sss, draggedTVItem.Lmessage, draggedTVItem.Lsignal);
                                }
                                break;
                            case "期望项":
                                if (cellE.Expectations == null)
                                {
                                    cellE.Expectations = new GridCell(sss, draggedTVItem.Lmessage, draggedTVItem.Lsignal);
                                }
                                else
                                {
                                    cellE.Expectations.EditEndingCalback(sss, draggedTVItem.Lmessage, draggedTVItem.Lsignal);
                                }
                                break;
                            default:
                                break;
                        }
                        cellE = null;
                    }


                }
                catch (Exception en)
                {
                    ExceptionHandler.Handle(en);
                }
            }

        }
        /// 
        /// 鼠标滑动拖拽放置目标
        /// 
        /// 
        /// 
        private void TreeView_MouseMove(object sender, MouseEventArgs e)

        {
            TV = sender as TreeView;
            try
            {
                if (e.LeftButton == MouseButtonState.Pressed)
                {
                    Point currentPosition = e.GetPosition(TV);

                    if ((Math.Abs(currentPosition.X - lastMouseDownPoint.X) > 10.0) ||
                        (Math.Abs(currentPosition.Y - lastMouseDownPoint.Y) > 10.0))
                    {
                        ///获取拖拽项目
                        draggedTVItem = (Location)TV.SelectedItem;
                        if (draggedTVItem != null)
                        {
                            DragDropEffects finalDropEffect = DragDrop.DoDragDrop(TV, TV.SelectedValue, DragDropEffects.Move);
                            //Checking target is not null and item is dragging(moving)

                            if ((finalDropEffect == DragDropEffects.Move)
                              && targetItem != null)
                            {
                                ///处理
                                DropFinishHandler();
                                //清空中间变量
                                targetItem = null;
                                draggedTVItem = null;
                                TV = null;
                                cellE = null;
                            }
                        }
                    }
                }
            }
            catch (Exception en)
            {
                ExceptionHandler.Handle(en);
            }
        }
        #endregion
        

2.7 定时器

在C#中存在3种常用的 Timer :

System.Windows.Forms.Timer
System.Timers.Timer
System.Threading.Timer

一 System.Windows.Forms.Timer

这个 Timer 是单线程的,也就是说只要它运行,其他线程就要等着。
这个 Timer 有如下特点:

  1. 完全基于UI线程,定时器触发时,操作系统把定时器消息插入线程消息队列中,调用线程执行一个消息泵提取消息,然后发送到回调方法Tick中;
  2. 使用 Start 和 Stop 启动和停止 Timer;
  3. UI操作过长会导致 Tick 丢失;
  4. 可以使用委托Hook Tick事件;
  5. 精确度不高;
  6. 通过将 Enabled 设置为 True,使 Timer 自动运行
private void Button_Click(object sender, EventArgs e)
{
    timer.Interval = 1000;
    timer.Tick += Timer_Tick;
    timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
    for (int i = 0; i < 10000; i++)
    {
        File.AppendAllText(Directory.GetCurrentDirectory()+"test.txt", i.ToString());
        this.label_output.Text = "当前操作:插入数字" + i;
    }
}

二 System.Timers.Timer

该 Timer 是基于服务器的计时器,是为在多线程环境中用于辅助线程而设计的,可以在线程间移动来处理引发的 Elapsed 事件,比上一个计时器更加精确。

该 Timer 有如下特点:

  1. 通过 Elapsed 设置回掉处理事件,且 Elapsed 是运行在 ThreadPool 上的;
  2. 通过 Interval 设置间隔时间;
  3. 当 AutoReset 设置为 False 时,只在到达第一次时间间隔后触发 Elapsed 事件;
  4. 是一个多线程计时器;
  5. 无法直接调用 WinForm 上的控件,需要使用 委托;
  6. 主要用在 Windows 服务中。
System.Timers.Timer timersTimer = new System.Timers.Timer();
private void Button_Click(object sender, EventArgs e)
{
    timersTimer.Interval = 1000;
    timersTimer.Enabled = true;
    timersTimer.Elapsed += TimersTimer_Elapsed;
    timersTimer.Start();
}

private void TimersTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    for (int i = 0; i < 10000; i++)
    {
        this.BeginInvoke(new Action(() =>
        {
            this.label_output.Text="当前时间:"+DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        }), null);
    }
}

private void Button1_Click(object sender, EventArgs e)
{
    timersTimer.Stop();
}

三、System.Threading.Timer

该 Timer 同样也是一个多线程的计时器,它有如下特点:

  1. 多线程
  2. 和前两个计时器相比没有 Start 和 Stop 方法,如果要停止计时器,必须调用 Dispose 方法来销毁 Timer 对象;
  3. 调用 Dispose 方法后并不能马上停止所有的计时器,这是因为间隔时间小于执行时间时多个线程运行造成的,多个线程无法同时停止;
  4. 一个轻量级的计时器;
  5. 所有的参数全部在构造函数中进行了设置;
  6. 可以设置启动时间;
  7. 不建议再 WinForm 程序中使用。
static System.Threading.Timer threadingTimer;
static int numSum = 0;
static void Main(string[] args)
{
    threadingTimer = new System.Threading.Timer(new System.Threading.TimerCallback(threadingTimer_Elapsed), null, 0, 1000);
    Console.Read();
}
private static void threadingTimer_Elapsed(object state)
{
    for (int i = 0; i < 10000; i++)
    {
        numSum++;
        Console.WriteLine("输出数字:"+i);
    }

    if (numSum > 10000)
    {
        threadingTimer.Dispose();
        Console.WriteLine("结束");
    }
}

总结

WPF 实践_第24张图片

2.8 文本框交互

期望的效果是textbox控件的内容可以与某个类的成员绑定,即类的成员更改,相应的textbox.text也更新
分析:在WPF中,可以将TextBox控件(其他控件也基本一样)与相应的变量进行绑定,做出改变变量则控件也跟着改变的效果。虽然其原理跟原本的消息响应是一样的,只是在外部加了层封装。
1、首先需要声明一个类,该类用来与控件绑定:

public class MyTextshow : INotifyPropertyChanged //绑定对象  
    {
        public string show;//显示
        public event PropertyChangedEventHandler PropertyChanged;
        public string Show
        {
            get { return show; }
            set
            {
                show = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Show"));
            }
        }
    }

2、针对textbox控件进行数据绑定:

<TextBox x:Name="OT_textBox" Text="{Binding Path=Show, Mode=TwoWay}" Margin="3"  Width="80" TextWrapping="Wrap"/>

3、该类声明完后,实例化出相应的对象,跟控件绑定:

MyTextshow mtextshow = new MyTextshow();
mtextshow.show = this.data_XML.OT_TestB;
OT_textBox.DataContext = mtextshow;//OT_textBox为控件名

2.9 线程更新UI界面

在线程中无法直接操作UI元素,可以通过线程的Dispatch.Invoke方法来更新UI界面。

  1. XAML界面
<Window
        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:WpfApplication1"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid" x:Class="WpfApplication1.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <DataGrid x:Name="dataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
        <Button Content="Start" Name="btnTest" Click="btnTest_Click">Button>
        <TextBlock Name="txtBlock" Text="ABC">TextBlock>
    StackPanel>
Window>

  1. 线程操作代码
public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnTest_Click(object sender, RoutedEventArgs e)
        {
            Thread th = new Thread(ThreadDo);
            th.Start();
        }

        private void ThreadDo()
        {
            Dispatcher.Invoke(() =>
            {
                txtBlock.Text = "xyz";
                btnTest.Content = "Started";
            });

            Thread.Sleep(5 * 1000);
            
            Dispatcher.Invoke(() =>
            {
                txtBlock.Text = "abc";
                btnTest.Content = "Stoped";
            });
        }
    }

三,其他

3.1坑

3.1.1 无法用XML序列化

InvalidOperationException: Wpf_auto_test.GridCell cannot be serialized because it does not have a parameterless constructor.
InvalidOperationException:无法序列化Wpf_auto_test.GridCell,因为它没有无参数构造函数。

3.1.2 页面返回时候,之前的配置的东西不见了

在新创建页面时候把之前的页面作为对象保存在新的页面中,返回时直接

this.NavigationService.Navigate(this.homeConfig);

3.1.3 VS2010移到VS2019

找尽各种配置,不能直接导入。
最后新建工程,VC大法文件,解决。

3.2 小技巧

/***********注释**********/
 #region code_gridConfigData
 #endregion

去掉页面导航

<Page x:Class="Wpf_auto_test.DataPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Wpf_auto_test"
      mc:Ignorable="d" 
      d:DesignHeight="600" d:DesignWidth="800"
      ShowsNavigationUI="False" 
      Title="数据页">

重点:ShowsNavigationUI=“False”

四, 引用

WPF体系结构
WPF架构分析
布局
OpenFileDialog官方链接
TreeView
WPF之DataGrid应用
有关WPF中DataGrid控件的基础应用总结
WPF下给DataGrid自动增加序列号
WPF DataGrid添加右键菜单
拖放
定时器
WPF 线程更新UI界面
C# WPF TextBox控件与变量 类的成员的绑定

你可能感兴趣的:(上位机,wpf)