在桌面开发领域,虽然在某些领域,基于electron的跨平台方案可以为咱们带来某些便利,可是因为WPF技术可以更好的运用Direct3D带来的性能提高、以及海量Windows操做系统和硬件资源的支持,因此他依然有着得天独厚的优点。前端
固然,选用一门技术,依然看公司的基因土壤和综合因素或者老板的心血来潮,例如QT也一样是一门很是不错的跨平台图形界面解决方案。git
目前咱们公司在桌面开发领域普遍应用了WPF技术,主要是使用其做为大屏数据可视化相关的UI呈现,包括一些数据展现效果、动画效果等。因为以前我对WPF仅有三周经验,所以在开发和设计相关功能时,一些简单功能还能勉强完成,稍微复杂一点的就有点费时过长了,所以这篇文章主要梳理本身的学习笔记,以便总结学习成果。github
在Quote上有人提出了这样一个相同的问题,查看问题,开发者Srikanth Pagadala如是回答:web
一、以了解基础控件做为学习的起步过程:这些控件包括TextBox,Button,TextBlock及其余的,理解这些控件对外提供的属性,以及如何使用。
二、了解和使用布局空间:例如Grid、StackPanel、DockerPanel和其余控件,在这一点上,你须要花费大量的时间。同时你须要学会建立复杂的UI设计。
三、了解循环类型的空间,例如ItemControl控件。
四、了解关于模板的概念。包括如何定义包含CheckBox的Combox,同时这个控件还包含了一张图片的按钮,以及如何在ItemsControl中使用不一样的模板。
五、理解数据绑定的运行机制。尝试建立一个MVVM或相似类型的应用程序。
六、建立一个典型的控件,探索DependencyProperties(依赖属性)和AttachedProperties(附加属性)。
七、建立一个样式资源,理解如何给控件设计样式。windows
除此以外,还有其余开发者给出了补充回答:后端
一、学习控件的数据绑定过程,在DataGrid上实现数据绑定。
二、学习和实现INotifyPropertyChanged类。查看如何实现
三、学习Observable Collection。该类型的集合普遍使用于数据集合绑定方面,同时也提供了数据改变通知的机制。
四、使网格上的列可编辑。用文本控件(用户项目模板)替换列。为每一个捕获文本更改事件的列建立一个属性。在文本控件上使用绑定类型。尝试捕获您在后端在网格上所作的更改。
五、成功将数据控件中的文本控件与后端属性绑定后,请在同一页面上建立网格的副本。尝试同步这两个网格。例如,您在第一个网格中所作的每一个更改都必须在第二个网格中自动更新。api
网站“https://www.wpf-tutorial.com/”是一个专门用于学习WPF的网站,经过这个网站,能够快速的入门WPF。数组
因为WPF技术已经比较熟悉,因此书籍也比较多,网友推荐来自刘铁猛老师的《深刻浅出WPF》这本书,而我经过Kindle则看到了一本比较有意思的书《葵花宝典-WPF自学手册》,这本书写得比较生动,经过故事的形式讲了WPF的许多技术原理,无形中让我对WPF的概念有了许多新的认识。固然,这本书已经有点年头了。浏览器
控件类型 | 控件名称 | 控件说明 | 连接地址 | |
---|---|---|---|---|
组件 | Window | 窗口 | 查看示例 | |
Page | 页面 | 查看示例 | ||
NavigationWindow | 导航窗口 | 查看示例 | ||
Frame | 查看示例 | |||
常规控件 | Button | 按钮控件,提供Content做为内容 | 查看示例 | |
TextBox | 文本框控件,用以输入文本 | 查看示例 | ||
TextBlock | 文本块,用以显示文本 | 查看示例 | ||
Label | 标签,用以显示文本 | 查看示例 | ||
ProgressBar | 进度条 | 查看示例 | ||
ToggleButton | 一种能够设置开关三态的按钮 | 查看示例 | ||
Image | 图像控件,经过Source设置资源路径 | 查看示例 | ||
CheckBox | 勾选框,能够设置是否勾选的三种状态 | 查看示例 | ||
RichTextBox | 富文本框,能够多种格式显示和输入文本 | 查看示例 | ||
TreeView | 树视图,以树状图的形式显示绑定内容,能够显示是否勾选三态。 | 查看示例 | ||
WebBrowser | 浏览器,基于IE内核的浏览器控件 | 查看示例 | ||
Calendar | 日历控件 | 查看示例 | ||
ComboBox | 下拉列表 | 查看示例 | ||
ContentControl | 内容控件 | 查看示例 | ||
Expander | 扩展器,能够显示和折叠面板内的元素 | 查看示例 | ||
GroupBox | 分组框 | 查看示例 | ||
StatusBar | 状态栏,用于在页面下方显示状态信息。 | 查看示例 | ||
DateTimePicker | 时间控件,能够设置时间状态。 | 查看示例 | ||
DocumentViewer | 文档查看器 | 查看示例 | ||
RadioButton | 单选按钮 | 查看示例 | ||
ScollViewer | 滚动视图 | 查看示例 | ||
ScollBar | 滚动条 | 查看示例 | ||
Separator | 分隔器 | 查看示例 | ||
ToolBar | 工具条 | 查看示例 | ||
Slider | 查看示例 | |||
Menu | 菜单 | 查看示例 | ||
MediaElement | 多媒体控件 | 查看示例 | ||
PasswordBox | 密码输入框 | 查看示例 | ||
TabControl | 选项卡 | 查看示例 | ||
ToolBarTray | 工具条 | 查看示例 | ||
WindowsFormsHost | 用以承载WinForm | 查看示例 | ||
Border | 边框 | 查看示例 | ||
数据控件 | ListView | 列表视图 | 查看示例 | |
DataGrid | 数据表 | 查看示例 | ||
ListBox | 列表框 | 查看示例 | ||
布局 | WrapPanel | 可变面板 | 查看示例 | |
StackPanel | 固定面板 | 查看示例 | ||
DockerPanel | 停靠面板 | 查看示例 | ||
Grid | 表格布局 | 查看示例 | ||
UniformGrid | 统一分布表格布局 | 查看示例 | ||
查看示例 | Canvas | 画布 | 查看示例 | |
图形 | Point | 点 | 查看示例 | |
Line | 线 | 查看示例 | ||
Path | 路径 | 查看示例 | ||
Polygon | 多边形 | 查看示例 | ||
Polyline | 多段线 | 查看示例 | ||
Rectangle | 矩形 | 查看示例 | ||
Shape | 画笔 | 查看示例 | ||
Rectangle | 矩形 | 查看示例 | ||
Ellipse | 椭圆 | 查看示例 |
在WPF技术中引入的XAML语法算是该技术的一大特点,也是被学习者视同为学习路径陡峭的“罪魁祸首”。缘由是在前端技术飞速发展的今天,HTML的语法体系因为更早的被开发者接受,因此也天然而然更容易成为开发者的首选。electron
而XAML是一种脱胎于XML,并吸取了HTML的精华的语法体系,是一种界面描述语言,XML语法自己相对而言较为臃肿的体系,看似成为了他的历史负担,可是其实倒也没那么复杂,经过几个简单的示例,其实就足够掌握这门新的语法体系了。例如,使用这样的语法,彻底能够平滑过渡到这样的语法体系。(部分标签其实只是大小写不一样)。固然,在XAML中熟练编写样式,确实须要花一点点时间。
在WPF中,经过XAML定义面向用户交互层的界面,而后编译成baml运行,后端则使用C#或VB.NET这样的CLR语法来实现逻辑交互。
根元素定义是定义XAML的命名空间。
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
经过xaml定义按钮,并设置文本为 helloworld 。这种写法在官方文档中称为“属性语法”,即直接在XAML中对属性进行设置。
<Button Background="Blue" Foreground="Red" Content="hello world"/>
经过xaml定义按钮,并设置其背景为蓝色画笔,字体颜色为红色画笔,内容 为helloworld。这种写法在官方文档中称为“属性元素语法”。
<Button> <Button.Background> <SolidColorBrush Color="Blue"/> </Button.Background> <Button.Foreground> <SolidColorBrush Color="Red"/> </Button.Foreground> <Button.Content> hello world </Button.Content> </Button>
定义按钮的颜色为红色和蓝色渐变色,内容为helloworld。这种称为“集合语法”。
<LinearGradientBrush> <LinearGradientBrush.GradientStops> <!-- no explicit new GradientStopCollection, parser knows how to find or create --> <GradientStop Offset="0.0" Color="Red" /> <GradientStop Offset="1.0" Color="Blue" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
样式定义使用 标签,而后在中间对样式的内容进行定义。
例如,如下表示经过XAML语法对 ToggleButton 按钮定义了一个命名为 ToggleLikeButtonStyle 的样式。
<Style TargetType="ToggleButton" x:Key="ToggleLikeButtonStyle"> <Setter Property="Margin" Value="4" /> <Setter Property="FontWeight" Value="Black"/> <Setter Property="Foreground" Value="Black" /> <Setter Property="BorderThickness" Value="0"/> <Setter Property="IsThreeState" Value="False"></Setter> </Style>
WPF中的控件能够经过模板 Template 的形式来定义其内容,使得开发者可以经过 XAML 灵活的对控件的外观进行扩展。例如,以下定义了一个 Template,这个控件模板将会对控件(Button)定义填充制定颜色。
<Setter Property="Template"> <Setter.Value> <ControlTemplate> <Border BorderThickness="0" CornerRadius="3"> <Border.Background> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#4696F2" Offset="0.5"/> </LinearGradientBrush> </Border.Background> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Border> </ControlTemplate> </Setter.Value> </Setter>
传统的WinForm开发者习惯于经过事件的机制对按钮的外观进行定义,而在WPF中,则能够经过属性的形式对外观进行设置,这使得开发者更可以写出高质量的代码。
例如,以下代码经过定义触发器,设置控件(控件为 ToggleButton),当控件的勾选状态属性为“IsChecked” 时,其边框填充色为#4696F2颜色。
<ControlTemplate.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter Property="Border.Background" TargetName="PART_Background" <Setter.Value> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#4696F2" Offset="0.5"/> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Width" TargetName="PART_Background" Value="60"></Setter> <Setter Property="Content" TargetName="contextPresenter" Value="已点赞"></Setter> <Setter Property="Visibility" TargetName="contextPresenter" Value="Visible"></Setter> <Setter Property="Visibility" TargetName="contextImage" Value="Hidden"/> </Trigger> </ControlTemplate.Triggers>
在上述事例中,共定义了两个按钮的样式,分别是:
<Style TargetType="Button" x:Key="FlatButtonStyle"> <Setter Property="Margin" Value="4" /> <Setter Property="FontWeight" Value="Black"/> <Setter Property="Foreground" Value="Black" /> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <Border BorderThickness="0" CornerRadius="3"> <Border.Background> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#4696F2" Offset="0.5"/> </LinearGradientBrush> </Border.Background> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
<Style TargetType="ToggleButton" x:Key="ToggleLikeButtonStyle"> <Setter Property="Margin" Value="4" /> <Setter Property="FontWeight" Value="Black"/> <Setter Property="Foreground" Value="Black" /> <Setter Property="BorderThickness" Value="0"/> <Setter Property="IsThreeState" Value="False"></Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ToggleButton}"> <Border BorderThickness="0" CornerRadius="3" Name="PART_Background"> <Border.Background> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#525252" Offset="0.5"/> </LinearGradientBrush> </Border.Background> <Grid> <ContentPresenter x:Name="contextPresenter" Content="{TemplateBinding ContentControl.Content}" HorizontalAlignment="Center" VerticalAlignment="Center" /> <Image x:Name="contextImage" Width="24" Height="24" Source="assests/thumbs-up-outline.png" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter Property="Border.Background" TargetName="PART_Background"> <Setter.Value> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#4696F2" Offset="0.5"/> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Width" TargetName="PART_Background" Value="60"></Setter> <Setter Property="Content" TargetName="contextPresenter" Value="已点赞"></Setter> <Setter Property="Visibility" TargetName="contextPresenter" Value="Visible"></Setter> <Setter Property="Visibility" TargetName="contextImage" Value="Hidden"/> </Trigger> <Trigger Property="IsChecked" Value="False"> <Setter Property="Border.Background" TargetName="PART_Background"> <Setter.Value> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#525252" Offset="0.5"/> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Width" TargetName="PART_Background" Value="40"></Setter> <Setter Property="Visibility" TargetName="contextPresenter" Value="Hidden"></Setter> <Setter Property="Visibility" TargetName="contextImage" Value="Visible"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
经过了解WPF的经常使用控件,咱们能够知道本身须要使用的控件有哪些属性,并能使用 XAML 语法对相应的属性进行设置,这种设置方法有别于经过C#代码的形式进行定义的方法,在 XAML中的属性称为 “标记”。标记使用 “{}” 花括号,编译器经过该花括号将语法和XAML语法进行区分。
例如:
HeaderTemplate="{DynamicResource StretchedHeaderTemplate}"
在进行标记值转换时,有时候须要使用TypeConverters实现类型转换。例如,在上述示例代码中,能够看到使用了字符串“#525252”来定义颜色,在内部就是实现了从字符串到 Color 类的转换过程。限于篇幅有限,此处就暂时略过。
<object property="{x:Type prefix:typeNameValue}" .../>
<object property="{x:Static prefix:typeName.staticMemberName}" .../>
<object property="{x:Null}" .../>
<x:Array Type="typeName"> arrayContents </x:Array>
<object property="{StaticResource key}" .../>
<object property="{DynamicResource key}" .../>
<object property="{Binding}" .../> -or- <object property="{Binding bindProp1=value1[, bindPropN=valueN]*}" ... /> -or- <object property="{Binding path}" .../> -or <object property="{Binding path[, bindPropN=valueN]*}" .../>
<Binding RelativeSource="{RelativeSource modeEnumValue}" .../>
<object property="{TemplateBinding sourceProperty}" .../>
<object property="{ColorConvertedBitmap imageSource sourceIIC destinationIIC}" .../>
<object x:Key="{ComponentResourceKey {x:Type targetTypeName}, targetID}" .../>
在开发过程当中,咱们能够直接在按钮上进行按钮模板的定义,例以下面的代码。
<Button Width="40" Height="40" Style="{DynamicResource CubeImageButtonStyle}" Click="Button_Click" Content="点赞"> <Button.Background> <ImageBrush ImageSource="/assests/favicon.png" Stretch="Fill"/> </Button.Background> </Button> <Setter Property="Template" <Setter.Value> <ControlTemplate> <Border BorderThickness="0" CornerRadius="3"> <Border.Background> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#4696F2" Offset="0.5"/> </LinearGradientBrush> </Border.Background> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <Button Grid.Column="0" Grid.Row="1" Style="{StaticResource FlatButtonStyle}" HorizontalAlignment="Center" VerticalAlignment="Top" Width="48" Height="16" FontSize="10" Background="#4696F2" Content="获取"></Button> </Grid>
这样的代码在界面比较简单时,还无所谓,可是随着控件的样式愈来愈复杂,可能会成为一团乱麻,这对于追求优雅代码的咱们来讲,多是难以忍受的,因此每每会使用资源引用来完成。
例如,咱们能够在当前页面代码中定义对应的样式,这种样式可使用 StaticResource 的形式引入。可是这样的引用形式,没有对象图的访问权限,意味着没法访问资源依赖的其余资源。
<Window.Resources> <Style TargetType="Button" x:Key="FlatButtonStyle"> <Setter Property="Margin" Value="4" /> <Setter Property="FontWeight" Value="Black"/> <Setter Property="Foreground" Value="Black" /> <Setter Property="BorderThickness" Value="0"/> </Window.Resources>
将上述代码中的{StaticResource FlatButtonStyle} 改为{StaticResource FlatButtonStyle}则会在运行时加载样式,并能够访问相应的对象图。
固然,这样的更改意义不大,若是该FlatButtonStyle引用了其余样式或元素,会发生做用。
<Grid <Button Grid.Column="0" Grid.Row="1" Style="{StaticResource FlatButtonStyle}" HorizontalAlignment="Center" VerticalAlignment="Top" Width="48" Height="16" FontSize="10" Background="#4696F2" Content="获取"></Button> </Grid>
一、因为XAML语法脱胎于XML语法,而XML语法中自己对某些输入字符,如“<>”存在限制,因此在XAML中也会出现这类问题,并会被Visual Studio检测出错误而没法编译,须要使用UTF-8编码进行转换。
而用户控件,使用于控件组合的场景。
在笔者进行开发时,老是思考到底是使用用户控件,仍是自定义控件,后来在阅读《葵花宝典-WPF自学手册》这本书中,终于得以大彻大悟。
做者指出:“不要被控件的外观所欺骗,要考虑其内在本质”。即思考控件的基本特征,首先想到该控件的行为与原有控件的行为是否类似,若是可以找到,则修改原有控件,而不是定义一个控件。尤为是在XAML语法中,可以经过Content 模型和模板、附加属性的运用,使得自定义控件的用途获得了进一步缩减,只有当实在万不得已时,在定义自定义控件。
做者给出了使用自定义控件的分析思路:
例如,在示例代码ToggleLikeButtonStyle 中,我实现了一个点赞和取消点赞的状态,则使用了ToggleButton来完成,就不必使用 Button 扩展出一个是否点赞的状态了。
而若是咱们须要实现的功能有这么复杂,那大概使用传统的控件就没法实现,就得使用自定义控件了。(点击查看示例代码)
做者定义了自定义控件 ButtonEx,并实现了依赖属性 ButtonType,见【依赖属性】,并定义了不一样类型的样式特征。
<Trigger Property="ButtonType" Value="Icon"> <Setter Property="Cursor" Value="Hand"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type controls:ButtonEx}"> <Border Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"> <Image x:Name="Img" VerticalAlignment="Center" HorizontalAlignment="Center" Source="{TemplateBinding Icon}" Stretch="None"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Opacity" Value="0.8"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Opacity" Value="0.9"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Trigger>
使用时,只需这样设置,便可实现不一样类型的按钮外观。
<controls:ButtonEx Icon="/Images/search.png" Margin="10" ButtonType="Icon"/>
依赖属性是为既有WPF控件对象定义自定义属性,以便支持其扩展,例如在上述自定义控件的示例中,就定义了依赖属性 ButtonType,实现了不一样类型的按钮外观。
public ButtonType ButtonType { get { return (ButtonType)GetValue(ButtonTypeProperty); } set { SetValue(ButtonTypeProperty, value); } } public static readonly DependencyProperty ButtonTypeProperty =
附加属性
按照官方的说法就是“附加属性旨在用做可在任何对象上设置的一类全局属性”,例如,DockPanel面板中的子对象,继承了来自于容器对象的附加属性,使得其可以在父对象中实现停靠的功能。
<DockPanel> <CheckBox DockPanel.Dock="Top">Hello</CheckBox> </DockPanel>
假设咱们定义了几个这样的控件。
<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1"> <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler"> <Button Name="YesButton" Width="Auto" >Yes</Button> <Button Name="NoButton" Width="Auto" >No</Button> <Button Name="CancelButton" Width="Auto" >Cancel</Button> </StackPanel> </Border>
实现了这样的界面
路由事件就是针对这组元素树中多个元素调用处理程序的事件。当咱们点击了按钮Button时,将会触发 Button=>StackPanel=>Border的事件路由,而不是像WinForm应用同样,只能触发最上层的Button的按钮点击事件。
void MakeButton2() { Button b2 = new Button(); b2.Click += new RoutedEventHandler(Onb2Click2); } void Onb2Click2(object sender, RoutedEventArgs e) { //logic to handle the Click event }
一、处理Border根元素的隧道事件PreviewMouseDown
二、处理StackPanel面板的隧道事件PreviewMouseDown.
三、处理Button按钮的隧道事件的PreMouseDown。
四、处理Button按钮的MouseDown事件。
五、处理StackPanel的MouseDown事件。
六、处理Border的MouseDown事件。
WPF是一个很是庞大的技术体系,以上学习路径仅供开发者进行简单的入门,因为篇幅有限,对于标记扩展还须要进一步理解透彻,以及格式转换、图形绘制、数据绑定、MVVM等内容未能一一描述。
若是果想要对WPF进一步了解,最好经过系统的学习相关知识,除了前面提到的网站和几本书,最好的入门网站依然是微软官方文档。