WPF导学目录

主要参考《C#高级编程 第八版》中XAML、WPF相关章节;《深入浅出WPF》(3yeu)和以下技术博客。

简介

WPF(Windows Presentation Foundation)是微软推出的基于Windows Vista的用户界面框架,属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架,真正做到了分离界面设计人员(xaml)与开发人员(.cs)的工作;同时它提供了全新的多媒体交互用户图形界面。详细介绍

WPF与WinForm开发有什么区别

WPF之What & Why  (源自《深入浅出WPF》)

开发

参考:圣殿骑士 WPF 基础到企业应用系列索引

原理

WPF所有的操作都不依赖于GDI和GDI+,而是间接依赖于强大的Direct3D。请参考下面的WPF核心组件图:

上图中的标示为暗红色的是WPF的三大核心组件,其中milcore组件,它的职责是完成与Direct3D的交互。并且出于效率和安全考虑,milcore由非托管代码实现。WPF 中的所有显示是通过 DirectX 引擎完成的,可实现高效的硬件和软件呈现。WPF 还要求对内存和执行进行精确控制。milcore 中的组合引擎受性能影响关系大,需要放弃 CLR 的许多优点来提高性能。

WPF的两大核心组件PresentationFramework和PresentationCore都位于通用语言运行库(CLR)之上。那么就可以看出,WPF的大部分代码都是以托管形式存在的。这两大组件提供了WPF项目需要的函数库和功能库,由于是以托管的形式存在,所以也避免了我们直接操作底层和出现诸如内存泄露的可能性。

WPF执行序列图

WPF除了在性能方面存在着缺陷以外,其他方面可以说是做得非常优秀

开发

基于Xaml和cs

在XAML中,用户界面用XML的元素或属性来表示。WPF引擎把XAML描述的UI元素解释为相应的.NET对象,从而在桌面程序或Silverlight网页上创建相应的控件。

在App.xaml中,指定项目运行时启动的是窗体:Window1,还可以定义我们需要的系统资源以及引入程序集等,详细看下图介绍:

7-4-2010 4-00-27 PM

Application :WPF和 传统的WinForm 类似, WPF 同样需要一个 Application 来统领一些全局的行为和操作,并且每个 Domain (应用程序域)中只能有一个 Application 实例存在。可以定义程序的启动事件、退出事件等等。

Window:窗体基类。其中常用的一些属性、方法、事件有:

  1. 窗体边框模式(WindowStyle属性)和是否允许更改窗体大小(ResizeMode属性) 。
  2. 窗体启动位置(WindowStartupLocation属性)和启动状态(WindowState属性) 等。
  3. 窗体标题(Title属性)及图标 。
  4. 是否显示在任务栏(ShowInTaskbar)
  5. 始终在最前(TopMost属性)

Dispatcher及多线程:WPF线程分配系统 提供一个Dispatcher属性、VerifyAccess  和 CheckAccess 方法来操作线程。线程分配系统 位于所有 WPF 类中基类,大部分WPF 元素都派生于此类

WPF 应用程序启动后,会有两个线程:

  1. 一个是用来处理UI呈现(处理UI的请求,比如输入和展现等操作)。
  2. 一个用来管理 UI的 (对UI元素及整个UI进行管理)。

与 Dispatcher 调度对象相对应的就是 DispatcherObject,在 WPF 中绝大部分控件都继承自 DispatcherObject,甚至包括 Application。这些继承自 DispatcherObject 的对象具有线程关联特征,也就意味着只有创建这些对象实例,且包含了 Dispatcher 的线程(通常指默认 UI 线程)才能直接对其进行更新操作。

* 当我们尝试从一个非 UI 线程更新一个UI元素,会看到如下的异常错误:The Calling thread cannot access the object beacuse a different thread owns it

按照 DispatcherObject 的限制原则,我们改用 Obj.Dispatcher.Invoke() 即可顺利完成这个更新操作:

private void ModifyUINew()
{
    // 模拟一些工作正在进行
    Thread.Sleep(TimeSpan.FromSeconds(5));
    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,(ThreadStart)delegate()
    {
        lblHello.Content = "Hello,Dispatcher";
    });
}

如果在其他工程或者类中,我们可以用 Application.Current.Dispatcher.Invoke方法来完成同样的操作,它们都指向 UI Thread Dispatcher这个唯一的对象。

布局

WPF的布局控件都在System.Windows.Controls.Panel这个基类下面,使用 Panel 元素在WPF应用程序中放置和排列子对象。

 

一个Panel 的呈现是测量和排列Children子元素、然后在屏幕上绘制它们的过程。所以在布局的过程中会经过一系列的计算,那么Children 越多,执行的计算次数就越多。如果不需要较为复杂的 Panel(如 Grid和自定义复杂的Panel),则可以使用构造相对简单的布局(如 Canvas、UniformGrid等),这种布局可带来更好的性能。 如果有可能,我们应尽量避免不必要地调用 UpdateLayout方法。

每当Panel内的子元素改变其位置时,布局系统就可能触发一个新的处理过程。对此,了解哪些事件会调用布局系统就很重要,因为不必要的调用可能导致应用程序性能变差

1、Canvas:只是一个存储元素的容器,它不会自动调整内部元素的排列及大小。不指定元素位置,元素将默认显示在画布的左上方。Canvas的主要用途是用来画图。

2、StackPanel:将子元素按照堆栈的形式一一排列,通过设置面板的Orientation属性设置了两种排列方式:横排(Horizontal默认的)和竖排(Vertical)。

3、WrapPanel:面板,从左至右按顺序位置定位子元素,如果排满断开至下一行。后续排序按照从上至下或从右至左的顺序进行。根StackPanel类似,不同的是WrapPanel会根据内容自动换行。

4、DockPanel:定义一个区域,在此区域中,您可以使子元素通过描点的形式排列。停靠面板其实就是在WinForm类似于Dock属性的元素。多个停靠在同侧的元素则按顺序排序。

5、Grid:和其他各个Panel比较起来,功能最多也最为复杂,它由<Grid.ColumnDefinitions>列元素集和<Grid.RowDefinitions>行元素集合两种元素组成。布局起来更精细。

6、UniformGrid:均布网格的是Grid的简化版本,每个单元格的大小相同,不用在定义行列集合。均布网格每个单元格只能容纳一个元素,将自动按照定义在其内部的元素个数,自动创建行列,并通常保持相同的行列数。 

7、ViewBox:定义一个内容容器,该容器可拉伸和缩放单个子元素以填满可用空间。一个 Viewbox 只能具有一个 Child。

8、Border:是一个装饰的控件,此控件绘制边框及背景,在 Border 中只能有一个子控件(这个子控件又可以包含多个子控件)。Border 的几个重要属性:

Background:用一个 Brush 对象来绘制背景 ;BorderBrush:用一个Brush 对象来绘制边框 ;BorderThickness:此属性设置 Border 边框的大小;CornerRadius:此属性设置 Border 的每一个角圆的半径;Padding:此属性设置 Border 里的内容与边框的之间的间隔。

9、ScrollViewer:通常用户界面中的内容比计算机屏幕的显示区域大,大出的部分就会破坏原有的布局。利用 ScrollViewer 控件可以方便地使应用程序中的内容具备滚动功能。

自定义控件

WPF自定义控件和开发WinForm、ASP.NET自定义控件基本类似,只是要注意一些特别的地方,比如依赖属性的处理、路由事件、视觉树和逻辑树等等。

示例:自定义Panel

我们知道布局系统的工作原理是先测量后排列,测量就是确定面板需要多大空间,排列则是定义其面板内子元素的排列规则。自定义面板要继承自Panel类并重写MeasureOverride和ArrangeOverride方法即可,

public class PlotPanel : Panel
    {
        public PlotPanel():base()
        {

        }

        /// <summary>
        /// 测量
        /// </summary>
        /// <param name="availableSize"></param>
        /// <returns></returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            Size panelDesiredSize = new Size();
            foreach(UIElement child in InternalChildren)
            {
                child.Measure(availableSize);
                panelDesiredSize = child.DesiredSize;
            }
            return panelDesiredSize;
        }

        /// <summary>
        /// 排列
        /// </summary>
        /// <param name="finalSize"></param>
        /// <returns></returns>
        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;
        }
    }
View Code

控件的最终大小和位置是由该控件和父控件共同完成的,控件会先给子控件提供可用空间(availableSize),子控件再反馈给父控件一个自己的期望值(DesiredSize),父控件最后根据自己所拥有的空间大小与子控件的期望值分配一定的空间给子控件并返回自己的大小。那么这个过程就是通过MeasureOverride 和ArrangeOverride这两个方法来完成(注意父控件的availableSize是减去Margin、padding等的值)。

写WPF自定义控件的几个步骤:

  • 首先你得清楚你的自定义控件是干什么用的(能解决什么问题)?公用到什么程度(其他项目也可以用、本项目用、项目当中一个模块用、只有一个地方用)?是继承已有的控件还是从头写?对设计时是否支持?样式和模板的定义等。
  • 确定好了上面的步骤后,我们就可以建立项目的结构,类和资源文件等该放在什么位置也就在这一步确定。
  • 选择要继承的基类(UIElement、FrameworkElement 、Control 、ContentControl 、HeaderedContentControl 、ItemsControl 、Selector 、RangeBase还是已有的一些控件)。
  • 重写默认的样式和新建一些样式并附默认值。
  • 由于WPF的属性基本都是依赖属性,所以我们也要新建一些依赖属性。
  • 逻辑树和视觉树的一些处理以及事件等。

 

其他参考

DotNet菜园

WPF入门教程系列一——基础      WPF学习之X名称空间详解

WPF入门教程系列二——Application介绍

WPF入门教程系列三——Application介绍(续)

WPF与WinForm一样有一个 Application对象来进行一些全局的行为和操作,并且每个 Domain (应用程序域)中仅且只有一个 Application 实例存在。和 WinForm 不同的是WPF Application默认由两部分组成 : App.xaml 和 App.xaml.cs,这有点类似于 Asp.Net WebForm,将定义和行为代码相分离。

微软把WPF中经常使用的功能都封装在 Application 类中了。

WPF入门教程系列四——Dispatcher介绍

System.Windows.Threading.Dispatcher  (调度)提供用于管理线程工作项队列的服务。不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程。在WPF或WinForm应用程序中,主线程负责接收输入、处理事件、绘制屏幕等工作,为了使主线程及时响应,防止假死,在开发过程中对一些耗时的操作、消耗资源比较多的操作,都会去创建一个或多个子线程去完成操作,比如大数据量的循环操作、后台下载。这样一来,由于UI界面是主线程创建的,所以子线程不能直接更新由主线程维护的UI界面。

所有 WPF 应用程序启动时都会加载两个重要的线程:一个用于呈现用户界面,另一个用于管理用户界面。呈现线程是一个在后台运行的隐藏线程,因此您通常面对的唯一线程 就是 UI 线程。WPF 要求将其大多数对象与 UI 线程进行关联。这称之为线程关联,意味着要使用一个 WPF 对象,只能在创建它的线程上使用。在其他线程上使用它会导致引发运行时异常。 UI 线程的作用是用于接收输入、处理事件、绘制屏幕以及运行应用程序代码。

实例:如何正确从一个非 UI 线程中更新一个由UI线程创建的对象。Dispatcher提供了两个方法,Invoke(同步操作)和BeginInvoke(异步操作),

同步与异步的区别

在WPF中,所有的WPF对象都派生自DispatcherObject(表示与Dispatcher 关联的对象,DispatcherObject暴露了Dispatcher属性用来取得创建对象线程对应的Dispatcher。DispatcherObject对象只能被创建它的线程所访问,其他线程修改 DispatcherObject需要取得对应的Dispatcher,调用Invoke或者BeginInvoke来投入任务。Dispatcher的一些设计思路包括 Invoke和BeginInvoke等从WinForm时代就是一直存在的,只是使用了Dispatcher来封装这些线程级的操作。

WPF入门教程系列五——Window介绍

用户通过窗口与 WPF独立应用程序进行交互。 窗口的主要用途是承载可视化数据并使用户可以与数据进行交互的内容。

WPF入门教程系列六——布局之Canvas(一)

WPF入门教程系列七——布局之WrapPanel与StackPanel(二)

WPF入门教程系列八——布局之Grid与UniformGrid(三)

WPF入门教程系列九——布局之DockPanel与ViewBox(四)

WPF入门教程系列十——布局之Border与ViewBox(五)

 

依赖属性

WPF入门教程系列十一——依赖属性(一)

WPF带来了很多新的特性,它的一大亮点是引入了一种新的属性机制——依赖属性。

通俗的讲是:依赖某对象【该对象类型是DependencyProperty 】的属性

依赖属性出现的目的是用来实现WPF中的样式、自动绑定及实现动画等特性

依赖属性的出现是WPF这种特殊的呈现原理派生出来的,与.NET普通属性不同的是,依赖属性的值是依靠多个提供程序来判断的,并且其具有内建的传递变更通知的能力。

依赖属性基本应用在了WPF的所有需要设置属性的元素。依赖属性根据多个提供对象来决定它的值 (可以是动画、父类元素、绑定、样式和模板等),同时这个值也能及时响应变化。

依赖属性就是可以自己没有值,并能够通过Binding从数据源获 取值(依赖在别人身上)的属性。拥有依赖属性的对象被称为“依赖对象”。依赖项属性的重点在于“依赖”二字,既然是依赖了,也就是说:依赖项属性的值的改变过程一定与其它对相关,不A依赖B就B依赖A,或者相互依赖。

关于WPF的依赖属性,主要有下面三个优点,我们的研究也重点放在这三点上:

  1. 新功能的引入:加入了属性变化通知,限制、验证等等功能,这样就可以使我们更方便的实现我们的应用,同时也使代码量大大减少了,许多之前不可能的功能都可以轻松的实现了。
  2. 节约内存:在WinForm等项目开发中,你会发现UI控件的属性通常都是赋予的初始值,为每一个属性存储一个字段将是对内存的巨大浪费。WPF依赖属性解决了这个问题,它内部使用高效的稀疏存储系统,仅仅存储改变了的属性,即默认值在依赖属性中只存储一次。
  3. 支持多个提供对象:我们可以通过多种方式来设置依赖属性的值。同时其内部可以储存多个值,配合Expression、Style、Animation等可以给我们带来很强的开发体验。

在.NET当中,属性是我们很熟悉的,封装类的字段,表示类的状态,编译后被转化为对应的get和set方法

依赖属性和属性到底有什么区别和联系呢?其实依赖属性的实现很简单,只要做以下步骤就可以实现:

第一步: 让所在类型继承自 DependencyObject基类,在WPF中,你会发现几乎所有的 WPF 控件都间接继承自DependencyObject类型。这个类封装了对依赖属性的存储及 访问等操作,使用静态类型与依赖属性的内部存储机制相关。WPF处理依赖属性不再像普通.NET属性那样将属性值存储到一个私有变量中,而是使用一个字典 型的变量来存放用户显示设置的值。

第二步:使用 public static 声明一个 DependencyProperty的变量(以Property作为后缀),该变量才是真正的依赖属性 ,看源码就知道这里其实用了简单的单例模式的原理进行了封装(构造函数私有),只暴露Register方法给外部调用。

第三步:在静态构造函数中完成依赖属性的元数据注册,并获取对象引用。依赖属性是通过调用DependencyProperty.Register静态方法创建,该方法需要传递一个属性 名称,这个名称非常重要,在定义控件Style和Template的时候,Setter的Property属性填入的值就是注册依赖属性时使用的名称。propertyType指明了依赖属性实际的类型,ownerType指明了是哪个类注册了此依赖属性,最后typeMetadata存放了一些依赖属 性的元信息,包括依赖属性使用的默认值,还有属性值发生变更时的通知函数。例如,下面的代码注册了依赖属性。

第四步:在前面的三步中,我们完成了一个依赖属性的注册,那么我们怎样才能对这个依赖属性进行读写呢?

答案就是提供一个依赖属性的实例化包装属性,通过这个属性来实现具体的读写操作。和CLR属性不同,依赖属性不是直接对私有变量的操纵,而是通过GetValue()和SetValue()方法来操作属性值的,可以使用标准的.NET属性定义语法进行封装,使依赖属性可以像标准属性那样来使用。

 public class SampleDPClass : DependencyObject
    {
        //声明一个静态只读的DependencyProperty字段
        public static readonly DependencyProperty SampleProperty;

        static SampleDPClass()
        {
            //注册我们定义的依赖属性Sample
            SampleProperty = DependencyProperty.Register("Sample", typeof(string), typeof(SampleDPClass),
                new PropertyMetadata("Knights Warrior!", OnValueChanged));
        }

        private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            //当值改变时,我们可以在此做一些逻辑处理
        }

        //属性包装器,通过它来读取和设置我们刚才注册的依赖属性
        public string Sample
        {
            get { return (string)GetValue(SampleProperty); }
            set { SetValue(SampleProperty, value); }
        }
    }
View Code

WPF依赖属性的正确学习方法 【*】

WPF入门教程系列十二——依赖属性(二)

依赖属性的继承

依赖属性继承的最初意愿是父元素的相关设置会自动传递给所有层次的子元素 ,即元素可以从其在树中的父级继承依赖项属性的值。

不是所有的元素都支持属性值继承。还会存在一些意外的情况,那么总的来说是由于以下两个方面:

1、有些Dependency属性在用注册的时候时指定Inherits为不可继承,这样继承就会失效了。

2、有其他更优先级的设置设置了该值。

WPF入门教程系列十三——依赖属性(三)

WPF入门教程系列十四——依赖属性(四)

 

WPF入门教程系列十五——WPF中的数据绑定(一)

WPF的数据绑定跟Winform与ASP.NET中的数据绑定功能类似,但也有所不同,在 WPF中以通过后台代码绑定、前台XAML中进行绑定,或者两者组合的方式进行数据绑定。您可以绑定控件、公共属性、XML 或对象,WPF中的数据绑定跟WinForm与ASP.NET相比,更加快捷、灵活和简单。

WPF 中的数据绑定,必须要有绑定目标和要绑定的数据源。绑定目标可以是继承自 DependencyProperty的任何可访问的属性或控件,例如 TextBox 控件的 Text 属性。数据源可以是其他控件的属性,可以是对象实例、XAML 元素、ADO.NET Dataset、XML数据。

一般情况下,Binding源是逻辑层的对象,Binding目标是UI层的控件对象,这样,数据就会源源不断 通过Binding送达UI层,被UI层展现,也就完成了数据驱动UI的过程。


WPF入门教程系列十六——WPF中的数据绑定(二)

WPF入门教程系列十七——WPF中的数据绑定(三)

WPF入门教程系列十八——WPF中的数据绑定(四)

3、WPF系列

https://blog.csdn.net/henrymoore/category_5622455_2.html 

 

学习资源

WPF开发者

 

posted @ 2015-12-06 22:11  peterYong  阅读(332)  评论(0编辑  收藏  举报