1.Assembly:程序集
2.xml语言:一种标签式语言,xaml来源于xml语言,xaml是一种声明性语言;
<Window> <Grid> ... </Grid> </Window> 表明声明了两个对象:Window和Grid,且Window包含Grid。
开始标签:<Window> <Grid>,结束标签:</Window> </Grid>,在开始标签中可以写一些属性(attribute)
3.WPF程序构成
3.1 Properties:资源
3.2 Peferences:引用
3.3 App.xaml:对应应用程序本身
<Application x:Class="_001_HelloWPF.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:_001_HelloWPF" StartupUri="MainWindow.xaml"> <!--表示MainWindow是主窗体,为启动窗体--> <Application.Resources> </Application.Resources> </Application>
3.4 MainWindow.xaml:分支类
<Window x:Class="_001_HelloWPF.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:_001_HelloWPF" mc:Ignorable="d" Title="Hello WPF" Height="200" Width="300" WindowStyle="ToolWindow"> <!--x:Class表明:Class源自于xmlns中的xaml命名空间,指向_001_HelloWPF命名空间下的MainWindow类 注意MainWindow是部分类,由两部分组成:C#Code和XamlCode--> <!--引入名称空间,presentation:绘制界面相关,控件与布局等;xaml:编译解析xaml文件相关,xaml:x映射为x命名空间 xaml规定可以有一个命名空间不加名字,这个命名空间为默认命名空间,如上为xmlns--> <!--在开始标签后可以写一些属性:attribute;例如这个窗体Window的文本名称、长度、宽度、窗体风格--> <Grid> <Button Content="Button" HorizontalAlignment="Left" Margin="100,75,0,0" VerticalAlignment="Top" Width="75"/> </Grid> </Window>
/// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } }
4.浅析用户界面的树形结构
可以使用Blend for Visual Studio2019打开程序,展示WPF的树形结构
5.在XAML中为对象属性赋值
WPF的XAML语言种的Window和Grid都属于对象而不是变量
Attribute是语言层面的东西,是给编译器看的;Property是面向对象层面的东西,是给编程逻辑看的,一个XAML标签的Attribute里大部分都对应着对象的Property。
5.1 Attribute=Value形式
<Window x:Class="_002_HelloWPF.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:_002_HelloWPF" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterScreen"> <!--local:引入当前命名空间--> <Window.Resources> <local:Human x:Key="human" Name="Tim" Child="LittleTim"/> </Window.Resources> <Grid> <!--属性赋值【1】:Arrribute="Value" 这种方式没有办法设置太复杂的值--> <Rectangle Width="100" Height="80" Stroke="Black" Fill="Blue" RadiusX="10" RadiusY="10"/> <!--M 0,0:笔挪到0,0 L:直线延伸 Z:闭合--> <Path Data="M 0,0 L 200,100 L 100,200 Z" Stroke="Black" Fill="Red"/> <Button Content="Show!" Click="Button_Click" Width="90" Height="40" Margin="342,138,85,141"/> </Grid> </Window>
//在XAML中为对象属性赋值 //方法1:Attribute="value"形式 namespace _002_HelloWPF { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { Human human = this.FindResource("human") as Human; if (human != null) { MessageBox.Show($"{human.Name} { human.Child.Name}"); } } } [TypeConverter(typeof(NameToHumanTypeConverter))] public class Human { public string Name { get; set; } public Human Child { get; set; } } public class NameToHumanTypeConverter : TypeConverter { public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { string name = value.ToString(); Human child = new Human(); child.Name = name; return child; } } }
5.2 属性标签
<Window x:Class="_003_HelloWPF.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:_003_HelloWPF" mc:Ignorable="d" Title="MainWindow" Height="240" Width="400"> <Grid> <Button Margin="10,30,301,147"> <!--开始标签--> <!--Content:加在开始和结束标签之间的称之为标签的内容--> <Button.Content> <!--属性标签:类名.属性名 在标签的内容区域可以书写属性的值,这个值可以是一个比较复杂的对象--> <Rectangle Width="20" Height="20" Stroke="DarkGreen" Fill="LawnGreen"/> </Button.Content> </Button> <!--结束标签--> <!--线性梯度画刷--> <Rectangle Width="200" Height="160" Stroke="Blue" Margin="148,24,44,25"> <Rectangle.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <LinearGradientBrush.GradientStops> <GradientStopCollection> <GradientStop Offset="0.2" Color="LightBlue"/> <GradientStop Offset="0.7" Color="Blue"/> <GradientStop Offset="1.0" Color="DarkBlue"/> </GradientStopCollection> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <!--经向梯度画刷--> <Ellipse Width="100" Height="100" Margin="23,84,269,25"> <Ellipse.Fill> <RadialGradientBrush GradientOrigin="0.25,0.25" RadiusX="0.75" RadiusY="0.75"> <RadialGradientBrush.GradientStops> <GradientStop Color="White" Offset="0"/> <GradientStop Color="Black" Offset="0.65"/> <GradientStop Color="Gray" Offset="0.8"/> </RadialGradientBrush.GradientStops> </RadialGradientBrush> </Ellipse.Fill> </Ellipse> </Grid> </Window>
加在标签开始部分和结束部分中间的部分称为标签的内容。Content。 注意标签的内容与对象的内容
<Window x:Class="_003_HelloWPF.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:_003_HelloWPF" mc:Ignorable="d" Title="MainWindow" Height="240" Width="400"> <Grid> <Button Margin="10,30,301,147"> <!--开始标签--> <!--Content:加在开始和结束标签之间的称之为标签的内容--> <Button.Content> <!--属性标签:类名.属性名 在标签的内容区域可以书写属性的值,这个值可以是一个比较复杂的对象--> <Rectangle Width="20" Height="20" Stroke="DarkGreen" Fill="LawnGreen"/> </Button.Content> </Button> <!--结束标签--> <Rectangle Width="200" Height="160" Stroke="Blue" Margin="148,24,44,25"> <Rectangle.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <LinearGradientBrush.GradientStops> <GradientStopCollection> <GradientStop Offset="0.2" Color="LightBlue"/> <GradientStop Offset="0.7" Color="Blue"/> <GradientStop Offset="1.0" Color="DarkBlue"/> </GradientStopCollection> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> </Grid> </Window>
5.3 标签扩展
<Window x:Class="_004_HelloWPF.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:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:_004_HelloWPF" mc:Ignorable="d" Title="MainWindow" Height="240" Width="400"> <Window.Resources> <sys:String x:Key="stringHello">Hello WPF!</sys:String> </Window.Resources> <Grid Margin="4"> <Grid.RowDefinitions> <RowDefinition Height="24"/> <RowDefinition Height="4"/> <RowDefinition Height="24"/> </Grid.RowDefinitions> <!--标签扩展,TextBlock的Text引用了前面的string--> <!--<TextBlock Height="24" Width="120" Background="LightBlue" Text="{StaticResource ResourceKey=stringHello}"> </TextBlock>--> <TextBlock x:Name="tb" Text="{Binding ElementName=sld,Path=Value}"/> <Slider x:Name="sld" Grid.Row="2" Value="50" Maximum="100" Minimum="0"/> </Grid> </Window>
6.事件处理器
6.1 事件的拥有者
<Button x:Name="btn_1" Content="Click Me!" Width="200" Height="100" Click="btn_1_Click"/>
6.2 事件的拥有者拥有哪些事件
声明Buttons时指明 Click="btn_1_Click",即它拥有一个叫btn_1_Click名称的Click事件
6.3 事件的响应者
事件的响应者是窗体
6.4 事件处理器:事件的响应者拿什么方法相应事件拥有者的事件,就是说当事件的拥有者发生某事件时,事件的响应者拿什么方法相应
private void btn_1_Click(object sender, RoutedEventArgs e) { MessageBox.Show("Hello WPF"); }
6.5 事件订阅:委托
this.btn_1.Click += new System.Windows.RoutedEventHandler(this.btn_1_Click);
Click="btn_1_Click"及订阅,this指MainWindow这个窗体
7.代码后置
7.1 C#编写的逻辑代码,与XAML编写的UI代码分开
7.2 C#支持partial类,XAML标签可以使用x:Class特定指定将由XAML代码解析生成的类与哪个类合并,所以可以把用于事先逻辑的C#代码放在一个cs文件里,把用户描述Ui的XAML代码放在另一个文件里,并且让事件特性Attribute充当XAML与C#之间沟通的纽带
7.3 代码后置:设计师用XANL为程序创建漂亮的“壳”UI并展示给客户;程序员用C#编写程序的“瓤”逻辑、从后台支持前面的“壳”,这种将逻辑代码与UI代码分离、隐藏在UI代码后面的形式叫做“代码后置”Code-Begind。
8.导入程序集和引用其中的名称空间
WPF创建用户控件窗体 新建项目——>>>C#、Window、库——>>>WPF用户控件库
xmlns:映射名="clr-namespace:类库中名称空间的名字;assembly=类库文件名"
xmlns是xml NameSpaced的缩写
使用名称空间里的类: <映射名:类名>...</映射名:类名>
<UserControl x:Class="ControlLibrary.SalaryCalculator" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="240" Height="160" Background="LightBlue" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:ControlLibrary"> <Canvas> <Label Canvas.Left="12" Canvas.Top="12" Name="label" Content="基本工资:" Height="28"/> <Label Canvas.Left="12" Canvas.Top="46" Name="labe2" Content="岗位工资:" Height="28"/> <Label Canvas.Left="12" Canvas.Top="80" Name="labe3" Content="实际工资:" Height="28"/> <TextBox x:Name="textBox1" Height="22" Canvas.Left="88" TextWrapping="Wrap" Text="" Canvas.Top="14" Width="140"/> <TextBox x:Name="textBox2" Height="22" Canvas.Left="88" TextWrapping="Wrap" Text="" Canvas.Top="48" Width="140"/> <TextBox x:Name="textBox3" Height="22" Canvas.Left="88" TextWrapping="Wrap" Text="" Canvas.Top="82" Width="140"/> <Button x:Name="button1" Content="计算" Canvas.Left="50" Canvas.Top="125" Width="140" Click="button1_Click"/> </Canvas> </UserControl>
private void button1_Click(object sender, RoutedEventArgs e)
{
try
{
textBox3.Text = (Convert.ToDouble(textBox1.Text.Trim()) + Convert.ToDouble(textBox2.Text.Trim())).ToString();
}
catch (Exception ex)
{
textBox3.Text = ex.Message;
}
}
9.x名称空间
x名称空间又叫XAML名称空间;指的是xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
程序员能够与XAML编译器沟通的工具就存放在x名称空间下
分为:Attribute、标记扩展、XAML指令元素
9.1 x名称空间中的Attribute
Attribute与Property是两个层面的东西,Attribute是语言层面的东西、是给编译器看的;Property是面向对象层面的东西、是给编程逻辑用的;一个XAML标签中的Attribute里大部分都对应着对象的ProPerty。
<!--x:Class 将XAML的编译结果与后台代码中指定的类合并 x:CalssModifier 此类的访问级别 x:Name 不但让编译器声明了引用变量,同时还为实例的Name树形赋值 x:FieldModifier 变量修饰符 默认internal x:Key 为资源贴上用于检索的索引,我的理解可以定义公有的资源 x:Shared 与x:Key配套使用,默认为true,每次得到的都是同一个对象;false是新副本 -->
<Window x:Class="_007_xmlns_x.MainWindow" x:ClassModifier="public" 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:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:_007_HappyWPF" mc:Ignorable="d" Title="MainWindow" Height="300" Width="300"> <Window.Resources> <sys:String x:Key="myString">Hello WPF Resourece!</sys:String> </Window.Resources> <!--x:Class 将XAML的编译结果与后台代码中指定的类合并 x:CalssModifier 此类的访问级别 x:Name 不但让编译器声明了引用变量,同时还为实例的Name树形赋值 x:FieldModifier 变量修饰符 默认internal x:Key 为资源贴上用于检索的索引,我的理解可以定义公有的资源 x:Shared 与x:Key配套使用,默认为true,每次得到的都是同一个对象;false是新副本 x:Type 数据类型名称 编程中壳操作的:数据类型的实例、实例的引用、数据类型本身 x:Null 显示地对一个属性赋空值 x:Array 通过x:Array的Items属性向使用者暴露一个类型已知的ArrayList实例,ArrayList内成员的类型由x:Array的Type指明 x:Static z在XAML代码中使用数据类型的static成员 --> <StackPanel> <TextBox Name="textBox" Margin="5"/> <TextBox x:Name="textBox1" x:FieldModifier="public" Margin="5"/> <TextBox x:Name="textBox2" x:FieldModifier="protected" Margin="5"/> <TextBox x:Name="textBox3" Text="{StaticResource ResourceKey=myString}" Margin="5"/> <Button Content="OK" Margin="5" Click="Button_Click"/> </StackPanel> </Window>
private void Button_Click(object sender, RoutedEventArgs e) { //StackPanel stackPanel = this.Content as StackPanel; //TextBox textBox = stackPanel.Children[0] as TextBox; if (string.IsNullOrEmpty(textBox.Name)) { textBox.Text = "No Name!"; } else { textBox.Text = textBox.Name; } textBox2.Text = this.FindResource("myString") as string; }
x名称空间里的Attribute | 解释 |
x:Class | 指明当前XAML代码对应那个C#类,属于partial类 |
x:ClassModifier | 类修饰符,必须与C#中的类修饰符一致,默认public |
x:Name | 为控件声明一个名字 |
x:FieldModifier | 指定控件的访问修饰符,默认private,如果想从别的程序集访问,需要指明为public,使用这个特性的前提是必须先指明控件的名称,即使用x:Name |
x:Key | 定义资源里共有字段 |
x:Shared | 指明资源里共有字段是值类型还是引用类型,为true,是引用类型;为false,是值类型,需要和x:key一起使用 |
9.2 x名称空间下的标记扩展
9.2.1 x:Type
在XAML种表示数据类型使用x:Type标记扩展,可以把Type理解成数据类型在编程层面上的抽象
<Window x:Class="_008_x_Type.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:_008_x_Type" mc:Ignorable="d" Title="MainWindow" Height="300" Width="300"> <StackPanel> <!--把MyWindow作为一种数据类型赋值给MyButton.UserWindowType属性--> <local:MyButton Content="Show" UserWindowType="{x:Type TypeName=local:MyWindow}" Margin="5"/> <!--因为TypeExtension包含一个可以接受数据类型作为参数的构造器,所以可以省略 TypeName= 如下--> <!--local:MyButton Content="Show" UserWindowType="{x:Type local:MyWindow}" Margin="5"/--> </StackPanel> </Window>
<Window x:Class="_008_x_Type.MyWindow" 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:_008_x_Type" mc:Ignorable="d" Title="MyWindow" Height="170" Width="200"> <StackPanel Background="LightBlue"> <TextBox Margin="5"/> <TextBox Margin="5"/> <TextBox Margin="5"/> <Button Content="OK" Margin="5"/> </StackPanel> </Window>
/// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } public class MyButton : Button { public Type UserWindowType { get; set; } protected override void OnClick() { base.OnClick();//激发Click事件 Window win = Activator.CreateInstance(this.UserWindowType) as Window; if (win!=null) { win.ShowDialog(); } } }
9.2.4 x:Null
<Window x:Class="_009_x_Null.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:_009_x_Null" mc:Ignorable="d" Title="MainWindow" Height="230" Width="300"> <Window.Resources> <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}"> <Setter Property="Width" Value="60"/> <Setter Property="Height" Value="36"/> <Setter Property="Margin" Value="5"/> </Style> </Window.Resources> <StackPanel> <Button Content="OK"/> <Button Content="OK"/> <Button Content="OK"/> <!--把一个Style放在Window的资源里并把它的x:Key和TargetType 都设置成了Button类型,UI上的所有Button控件都会默认的套用这个 Style,除非限时的把Style设置为了x:Null--> <Button Content="OK" Style="{x:Null}"/> <!--等同于上面一句,这种格式叫 标签式声明--> <Button Content="OK"> <Button.Style> <x:Null/> </Button.Style> </Button> </StackPanel> </Window>
9.2.2 x:Array
<Window x:Class="_010_x_Array.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:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:_010_x_Array" mc:Ignorable="d" Title="MainWindow" Height="220" Width="300"> <Grid Background="LightBlue"> <!--下面这句x:Array实例是没有数据可提供的--> <!--<ListBox Margin="5" ItemsSource="{x:Array Type=sys:String}"/>--> <!--需要改用标签声明语法--> <ListBox Margin="5"> <ListBox.ItemsSource> <x:Array Type="sys:String"> <sys:String>Tim</sys:String> <sys:String>Tom</sys:String> <sys:String>Victor</sys:String> </x:Array> </ListBox.ItemsSource> </ListBox> </Grid> </Window>
9.2.3 x:Static
/// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public static string WindowTitel = "山高越小"; public static string ShowText { get { return "水落石出"; } } public MainWindow() { InitializeComponent(); } }
<Window x:Class="_011_x_Static.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:_011_x_Static" mc:Ignorable="d" Title="{x:Static local:MainWindow.WindowTitel}" Height="100" Width="300"> <StackPanel> <TextBlock FontSize="32" Text="{x:Static local:MainWindow.ShowText}"/> </StackPanel> </Window>
10.WPF数据驱动
WinForms是事件驱动机制,始终把UI控件放在主导地位而把数据放在被动地位,用UI来驱动数据的改变;WPF引入数据驱动界面的理念,让数据重归核心地位而让UI回归数据表达者的位置。
WPF是数据驱动UI,数据是核心、是主动的;UI从属于数据并表达数据、是被动的。
11.控件与内容属性
控件通过自己的某个属性引用者作为其内容的对象,这个属性叫内容属性(ContenProperty)
控件的内容控件可以直接省略;Button内容属性是Content;StackPaneln内容控件是Children
<Window x:Class="_012_ContentProperty.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:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:_012_ContentProperty" mc:Ignorable="d" Title="MainWindow" Height="300" Width="300"> <!--控件的内容控件可以直接省略--> <StackPanel> <Button Margin="5"> <Button.Content> <sys:String>OK</sys:String> </Button.Content> </Button> <!--Button内容属性是Content--> <Button Margin="5"> <sys:String>OK</sys:String> </Button> <StackPanel> <StackPanel.Children> <TextBox Margin="5"/> <TextBox Margin="5"/> <Button Content="OK" Margin="5"/> </StackPanel.Children> </StackPanel> <!--StackPaneln内容控件是Children--> <StackPanel> <TextBox Margin="5"/> <TextBox Margin="5"/> <Button Content="OK" Margin="5"/> </StackPanel> </StackPanel> </Window>
12.内容模型
12.1 ContentProperty族
内容属性为Content;只能由单一元素充当其内容
Button | ButtonBase | CheckBox | ComboBoxItem |
ContentControl | Frame | GridViewColumnHeader | GroupItem |
Label | ListBoxItem | ListViewItem | NavigationWindow |
RadioButton | RepeatButton | ScrollViewer | StatusBarItem |
ToggleButton | ToolTip | UserControl | Window |
12.2 HeaderedContentControl族
显示带标题数据的控件;属性内容为Conten和Hearder
Expander | GroupBox | HeaderedContentControl | TabItem |
12.3 ItemsControlz族
显示列表化的数据;内容属性为Items或ItemSource;每个ItemsControl都有对应有自己的条目容器Item Container
Menu | MenuBase | ContextMenu | ComboBox |
ItemsControl | ListBox | ListView | TabControl |
TreeView | Selector | StatusBar |
12.4 HeaderedItemsControl族
显示列表化的数据,同时显示一个标题;内容属性为Items、ItemSource和Header;属于HeaderedContentControl与ItemsControl族的结合
MenuItem | TreeViewItem | ToolBar |
12.5 Decorator族
起装饰效果;内容属性为Child;只能由单一元素充当内容
ButtonChrome | ClassicBorderDecorator | ListBoxChrome | SystemDropShadowChrome |
Border | InkPresenter | BulletDecorator | ViewBox |
AdornerDecorator |
12.6 TextBlock和TextBox
显示文本
TextBlock只能显示文本不能编辑,但可以使用丰富的印刷级的格式控制标记显示专业的排版效果,有Text属性
TextBox可编辑可显示
12.7 Shape族
2D图形绘制,无内容属性,使用Fill属性设置填充,使用Stroke属性设置边线
12.8 Panle族
控制UI布局;内容属性为Children,内容可以是多个元素,Panel元素将控制它们的布局
Canvas | DockPanel | Grid | TabPanel |
ToolBarOverflowPanel | StackPanel | ToolBarPanel | UniformGrid |
VirtualizingPanel | VirtualizingStackPanel | WrapPanel |
13 控件应用示例 视频链接:https://www.bilibili.com/video/BV1vp4y1C75X
<Window x:Class="WpfBasics.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:WpfBasics" mc:Ignorable="d" Loaded="Window_Loaded" Title="Wpf Basics" Height="800" Width="400"> <Border Padding="10"> <StackPanel> <!--Grid:Apply Reset Refresh--> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Button x:Name="ApplyButtom" Click="ApplyButtom_Click" Content="Apply" Grid.Column="0" Margin="0,0,10,0"></Button> <Button x:Name="ResetButton" Click="ResetButton_Click" Content="Reset" Grid.Column="1"></Button> <Button x:Name="RefreshButton" Content="Refresh" Grid.Column="2" Margin="10,0,0,0"></Button> </Grid> <!--Pulse Properties--> <TextBlock Text="Pulse Properties" Margin="0,10" FontWeight="Bold"/> <!--Description--> <TextBlock Text="Description"/> <TextBox x:Name="DescriptionText" Padding="2"/> <!--Grid:Status Revision--> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!--Status--> <StackPanel Grid.Column="0"> <TextBlock Text="Status"/> <TextBox Padding="2" Margin="0,0,10,0" IsReadOnly="True" Background="#eee"/> </StackPanel> <!--Revision--> <StackPanel Grid.Column="1"> <TextBlock Text="Revision"/> <TextBox Padding="2" IsReadOnly="True" Background="#eee"/> </StackPanel> </Grid> <!--Part NUmber--> <TextBlock Text="Part Number"/> <TextBox IsReadOnly="True" Background="#eee" Margin="0,0,0,10"/> <!--Raw Material--> <TextBlock Text="Raw Material" FontWeight="Bold" Margin="0,0,0,10"/> <!--Material--> <TextBlock Text="Material"/> <TextBox IsReadOnly="True" Background="#eee" Margin="0,0,0,10"/> <!--Manufacturing Info--> <TextBlock Text="Manufacturing Info" FontWeight="Bold" Margin="0,0,0,10"/> <!--Work Centres--> <TextBlock Text="Work Centres" Margin="0,0,0,10"/> <!--Grid: CheckBoxs--> <Grid x:Name="CheckBoxsGrid"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <CheckBox Grid.Column="0" Grid.Row="0" Click="CheckBox_Click" Content="Weld"/> <CheckBox Grid.Column="0" Grid.Row="1" Click="CheckBox_Click" Content="Assembly"/> <CheckBox Grid.Column="0" Grid.Row="2" Click="CheckBox_Click" Content="Plasma"/> <CheckBox Grid.Column="0" Grid.Row="3" Click="CheckBox_Click" Content="Laser"/> <CheckBox Grid.Column="0" Grid.Row="4" Click="CheckBox_Click" Content="Purchase"/> <CheckBox Grid.Column="1" Grid.Row="0" Click="CheckBox_Click" Content="Lathe"/> <CheckBox Grid.Column="1" Grid.Row="1" Click="CheckBox_Click" Content="Drill"/> <CheckBox Grid.Column="1" Grid.Row="2" Click="CheckBox_Click" Content="Fold"/> <CheckBox Grid.Column="1" Grid.Row="3" Click="CheckBox_Click" Content="Roll"/> <CheckBox Grid.Column="1" Grid.Row="4" Click="CheckBox_Click" Content="Saw"/> </Grid> <!--Length--> <TextBlock Text="Length"/> <TextBox x:Name="LengthText" Padding="2"/> <!--Mass--> <TextBlock Text="Mass"/> <TextBox x:Name="MassText" Padding="2" IsReadOnly="True" Background="#eee"/> <!--Finish--> <TextBlock Text="Finish"/> <ComboBox x:Name="FinishDropdown" SelectionChanged="FinishDropdown_SelectionChanged" SelectedIndex="0" Padding="2"> <ComboBoxItem>Painted</ComboBoxItem> <ComboBoxItem>Not Painted</ComboBoxItem> </ComboBox> <!--Purchase Information--> <TextBlock Text="Purchase Information"/> <ComboBox SelectedIndex="0" Padding="2"> <ComboBoxItem>Rubber</ComboBoxItem> <ComboBoxItem>Not Rubber</ComboBoxItem> </ComboBox> <!--Supplier Name--> <TextBlock Text="Supplier Name"/> <TextBox x:Name="SupplierNameText" TextChanged="SupplierNameText_TextChanged" Padding="2"/> <!--Supplier Code--> <TextBlock Text="Supplier Code"/> <TextBox Padding="2" Margin="0 0 0 10"/> <!--Additional Info--> <TextBlock Text="Additional Info" FontWeight="Bold" Margin="0,0,0,10"/> <!--Note--> <TextBlock Text="Note"/> <TextBox x:Name="NoteText" Padding="2" Margin="0 0 0 10"/> </StackPanel> </Border> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfBasics { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void ApplyButtom_Click(object sender, RoutedEventArgs e) { MessageBox.Show($"The Description is {DescriptionText.Text}"); } private void ResetButton_Click(object sender, RoutedEventArgs e) { foreach (var item in CheckBoxsGrid.Children) { CheckBox box = (CheckBox)item; box.IsChecked = false; } UpdateCheckCount(); } private void Window_Loaded(object sender, RoutedEventArgs e) { FinishDropdown_SelectionChanged(FinishDropdown, null); } private void FinishDropdown_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (NoteText == null) { return; } ComboBox box = (ComboBox)sender; ComboBoxItem comboBoxItem = (ComboBoxItem)box.SelectedValue; NoteText.Text = comboBoxItem.Content.ToString(); } private void SupplierNameText_TextChanged(object sender, TextChangedEventArgs e) { MassText.Text = SupplierNameText.Text; } private void CheckBox_Click(object sender, RoutedEventArgs e) { UpdateCheckCount(); } private void UpdateCheckCount() { int count = 0; foreach (var item in CheckBoxsGrid.Children) { CheckBox box = (CheckBox)item; if (box.IsChecked == true) { count++; } } LengthText.Text = count.ToString(); } } }
14 DataBinding在WPF中的地位
应用程序 | 城市 | |
数据存储层 | 数据库与文件系统 | 仓储区 |
数据处理层 | 逻辑层 | 工业区 |
数据表示层 | 可视化+收集外设操作 | 港口区 |
程序的本质是数据加算法。数据会在【存储】、【逻辑】、【展示】三个层流通;算法集中在【数据库内部】、【读取和写回数据】、【业务逻辑】、【数据展示】、【界面与逻辑的交互】;WPF中的DataBinding相当于一个桥梁,将上述算法【数据展示】和【界面与逻辑的交互】合并为【数据展示】
Data Binding【数据绑定】、Dependency Property【依赖属性】、DataTemplate【数据模板】
15 Binding基础
Binding源是逻辑层的对象,例如是一个DataTable;Binding目标是UI层的控件对象,例如是一个DataGrid。
<Window x:Class="_022_BindingBasics.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:_022_BindingBasics" mc:Ignorable="d" Title="MainWindow" Height="244" Width="300"> <StackPanel> <TextBlock Text="复杂写法" Margin="5"/> <TextBox x:Name="textBoxName" Margin="5"/> <Button x:Name="btnTom" Content="Tom" Click="btnTom_Click" Margin="5"/> <Button x:Name="btnJohh" Content="Johh" Click="btnJohh_Click" Margin="5"/> <TextBlock Text="建议写法" Margin="5"/> <TextBox x:Name="textBoxNameNew" Margin="5"/> </StackPanel> </Window>
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace _022_BindingBasics { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { Student stu; public MainWindow() { InitializeComponent(); //准备资源 stu = new Student(); //这是一种简单的封装好的写法 //目标控件直接使用SetBinding方法进行绑定,只需要指明是绑定到目标控件的哪个属性、绑定实例(包括路径和源) this.textBoxNameNew.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu }); //public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding) //{ // return BindingOperations.SetBinding(this, dp, binding); //} //准备Binding Binding binding = new Binding(); binding.Source = stu; binding.Path = new PropertyPath("Name"); //这是一种复杂的写法 //使用Binding连接数据源与Binding目标 //textBoxName是绑定的目标 //Text.TextProperty指明把数据传递给目标(控件)的哪个属性,这里用的不是对象的属性而是类的一个静态只读依赖属性类型成员变量 //binding即绑定对象 BindingOperations.SetBinding(textBoxName, TextBox.TextProperty, binding); } private void btnTom_Click(object sender, RoutedEventArgs e) { stu.Name = "Tom"; } private void btnJohh_Click(object sender, RoutedEventArgs e) { stu.Name = "Johh"; } } public class Student : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string name; public string Name { get => name; set { name = value; if (this.PropertyChanged != null) { this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); } } } } }
15.1 控制Binding的方向Mode及数据更新UpdateSourceTrigger
Mode属性有五个选择:TwoWay(双向)、OneWay(单向,仅源向目标)、OneTime(初次)、OneWayToSource和Default(根据控件决定单双项) https://www.cnblogs.com/callyblog/p/7456600.html
UpdateSourceTrigger属性有四个选择:PropertyChanged(属性更改时触发)、LostFocus(失去焦点时触发)、Explicit(外部手动触发)、Default(会根据绑定的内容进行更新) https://www.cnblogs.com/callyblog/p/7459351.html
16 Bingding路径与源
通常来说,Bingding源【Source】是逻辑层的对象,Binding目标【Target】是UI层的控件对象
16.4 Binding的Path
Binding目标绑定了源的哪个属性?这个属性就是路径Path,确切的说这个Path应该是Bingding源的一个属性
可以是数据本身(int、string类型的资源,用“.”来表示)、可以是字符串长度(Text.Length)、可以是字符串索引(Text.[3])、可以是集合(/或/Length或/[2])
以下两句代码功能相同:
<TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=slider1}"/>
Binding binding = new Binding("Value") { Source = slider1 };
textBox1.SetBinding(TextBox.TextProperty, binding);
16.2 Binding的Source源
Binding的源是数据的来源。只要一个对象包含数据且能通过属性Property把数据暴露出来,它就能当作Binding的源来使用
单个CLR的Binding Source:对象赋值给Binding.Source属性或把对象的Name赋值给Binding.ElementName
<Window x:Class="_023_BindingAndBinding.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:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:_023_BindingAndBinding" mc:Ignorable="d" Title="MainWindow" Height="800" Width="300"> <StackPanel> <StackPanel.Resources> <sys:String x:Key="myString"> 人生若只如初见 </sys:String> </StackPanel.Resources> <GroupBox Header="单向绑定 Slider—>TextBox"> <StackPanel> <!--Slider控件对象当作源,把它的Value属性作为路径--> <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5" Text="{Binding Path=Value, ElementName=slider1, Mode=OneWay}"/> <Slider x:Name="slider1" Maximum="100" Minimum="0" Margin="5"/> </StackPanel> </GroupBox> <GroupBox Header="双向绑定 Slider和TextBox"> <StackPanel> <!--Bingding Mode控制流向--> <TextBox x:Name="textBox2" BorderBrush="Black" Margin="5" Text="{Binding Value, ElementName=slider2,UpdateSourceTrigger=PropertyChanged}"/> <Slider x:Name="slider2" Maximum="100" Minimum="0" Margin="5"/> </StackPanel> </GroupBox> <GroupBox Header="绑定代码在C#中实现"> <StackPanel> <TextBox x:Name="textBox3" BorderBrush="Black" Margin="5" SourceUpdated="textBox3_SourceUpdated" TargetUpdated="textBox3_TargetUpdated"/> <Slider x:Name="slider3" Maximum="100" Minimum="0" Margin="5"/> </StackPanel> </GroupBox> <GroupBox Header="一个TextBox绑定另一个TextBox的长度"> <StackPanel> <TextBox x:Name="text1" Margin="5"/> <TextBox x:Name="text2" Margin="5" Text="{Binding Path=Text.Length,ElementName=text1,Mode=OneWay}" BorderBrush="Black"/> </StackPanel> </GroupBox> <GroupBox Header="集合的默认元素"> <StackPanel> <TextBox x:Name="text3" Margin="5"/> <TextBox x:Name="text4" Margin="5"/> <TextBox x:Name="text5" Margin="5"/> </StackPanel> </GroupBox> <GroupBox Header="没有Path的Binding"> <StackPanel> <TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/> <TextBlock x:Name="textBlock2" TextWrapping="Wrap" Text="{Binding .,Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/> <TextBlock x:Name="textBlock3" TextWrapping="Wrap" Text="{Binding Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/> </StackPanel> </GroupBox> </StackPanel> </Window>
using System.Collections.Generic; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; namespace _023_BindingAndBinding { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); //Binding binding = new Binding() { Path = new PropertyPath("Value"), Source = slider3 }; Binding binding = new Binding("Value") { Source = slider3 }; binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus; binding.NotifyOnSourceUpdated = true; binding.NotifyOnTargetUpdated = true; textBox3.SetBinding(TextBox.TextProperty, binding); List<string> stringList = new List<string>() { "Tir","Eric","Johhy","Tyu2"}; text3.SetBinding(TextBox.TextProperty, new Binding("/") { Source = stringList }); text4.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList, Mode = BindingMode.OneWay }); text5.SetBinding(TextBox.TextProperty, new Binding("/[2]") { Source = stringList, Mode = BindingMode.OneWay }); } private void textBox3_SourceUpdated(object sender, DataTransferEventArgs e) { Debug.WriteLine("源更新事件:更新一次"); } private void textBox3_TargetUpdated(object sender, DataTransferEventArgs e) { Debug.WriteLine("目标更新事件:更新一次"); } } }
16.3 大多数情况下Binding的源是逻辑层的对象,但UI层的控件也可以自己相互关联。例如一个TextBox的Text属性绑定在了Slider的Value属性上
下面这三句话是等价的,可以这样说明:为textBox1的Text属性设置Binding为slider1的Value路径
<TextBox x:Name="textBox1" Text="{Binding Path=Value, ElementName=slider1}"/>
textBox1.SetBinding(TextBox.TextProperty,new Binding("value"){ElementName="slider1"});
<TextBox x:Name="textBox1" Text="{Binding Value, ElementName=slider1}"/>
17 各种Binding的Source的情形
17.1 CLR单个对象:.Net自带类型对象或用户自定义类型对象
<StackPanel Background="LightBlue"> <StackPanel.DataContext> <!--定义一个Student对象--> <local:Student Id="6" Age="29" Name="杨丹"/> </StackPanel.DataContext> <Grid> <StackPanel> <!--Binding中未指明Source--> <TextBox Text="{Binding Path=Id}" Margin="5"/> <TextBox Text="{Binding Path=Age}" Margin="5"/> <TextBox Text="{Binding Path=Name}" Margin="5"/> </StackPanel> </Grid> </StackPanel>
虽然没有为OK按钮指定内容,但DataContext属性值会沿着UI元素树向下传递;依赖属性的一个特点是:当没有为属性的某个依赖属性显式赋值时,控件会把自己容器的属性值“借过来”当做自己的属性值
<StackPanel Background="LightBlue"> <StackPanel.DataContext> <!--声明一个字符串资源--> <sys:String>Hello DataContext!</sys:String> </StackPanel.DataContext> <Grid> <StackPanel> <!--没有Source和Path的Binding--> <TextBlock Text="{Binding}" Margin="5"/> <TextBlock Text="{Binding}" Margin="5"/> <TextBlock Text="{Binding}" Margin="5"/> </StackPanel> </Grid> <Grid DataContext="55"> <Grid> <Grid> <Grid> <Button x:Name="btn" Content="OK" Click="btn_Click"/> </Grid> </Grid> </Grid> </Grid> </StackPanel>
17.2 集合对象:诸如数组、List<T>、<ObservableCollection<T>
列表式控件的ItemsSource属性可以接受一个IEnumerable接口派生类的实例作为自己的值,所有可被迭代遍历的集合都实现这个了这个接口,包括数组、List<T>
<StackPanel x:Name="stackPanel" Background="LightBlue"> <TextBlock Text="Student.ID" FontWeight="Bold" Margin="5"/> <TextBox x:Name="textBoxId" Margin="5"/> <TextBlock Text="Students List" FontWeight="Bold" Margin="5"/> <ListBox x:Name="listBoxStudents" Height="110" Margin="5"/> </StackPanel>
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); List<Student> listStus = new List<Student>() { new Student(){ Id=0,Age=22,Name="Shakj"}, new Student(){ Id=1,Age=24,Name="Shsakj"}, new Student(){ Id=2,Age=26,Name="we"}, new Student(){ Id=3,Age=21,Name="fgh"}, new Student(){ Id=4,Age=29,Name="ghj"} }; listBoxStudents.ItemsSource = listStus; listBoxStudents.DisplayMemberPath = "Name"; Binding binding = new Binding("SelectedItem.Id") { Source = listBoxStudents }; textBoxId.SetBinding(TextBox.TextProperty, binding); } } public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
<StackPanel x:Name="stackPanel" Background="LightBlue"> <TextBlock Text="Student.ID" FontWeight="Bold" Margin="5"/> <TextBox x:Name="textBoxId" Margin="5"/> <TextBlock Text="Students List" FontWeight="Bold" Margin="5"/> <ListBox x:Name="listBoxStudents" Height="110" Margin="5"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Id}" Width="30"/> <TextBlock Text="{Binding Path=Name}" Width="80"/> <TextBlock Text="{Binding Path=Age}" Width="30"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel>
public Window1() { InitializeComponent(); List<Student> listStus = new List<Student>() { new Student(){ Id=0,Age=22,Name="Tim"}, new Student(){ Id=1,Age=24,Name="Tom"}, new Student(){ Id=2,Age=26,Name="Tony"}, new Student(){ Id=3,Age=21,Name="Kyle"}, new Student(){ Id=4,Age=29,Name="Emily"} }; listBoxStudents.ItemsSource = listStus; //listBoxStudents.DisplayMemberPath = "Name"; Binding binding = new Binding("SelectedItem.Id") { Source = listBoxStudents }; textBoxId.SetBinding(TextBox.TextProperty, binding); }
17.3 ADO.NET数据对象:DataTable和DataView
DataTable的DefaultView属性是一个DataView类型的对象,DataView类实现了IEnumerable接口,所以可以赋值给ListBox.ItemsSource属性
private void btn_Click(object sender, RoutedEventArgs e) { DataTable dt = Load(); listBoxStudent.DisplayMemberPath = "Name"; listBoxStudent.ItemsSource = dt.DefaultView; } private DataTable Load() { DataTable dt = new DataTable(); dt.Columns.Add("Id", typeof(int)); dt.Columns.Add("Name", typeof(string)); dt.Columns.Add("Age", typeof(int)); Student stu = new Student() { Id = 0, Age = 22, Name = "Tim" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 1, Age = 24, Name = "Tom" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 2, Age = 26, Name = "Tony" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 3, Age = 21, Name = "Kyle" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 4, Age = 29, Name = "Emily" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); return dt; }
ListView是ListBox的派生类,GridView是ViewBase的派生类,ListView的View属性是一个ViewBase类型的对象
<StackPanel Background="LightBlue"> <ListView x:Name="listViewStudents" Height="200" Margin="5"> <ListView.View> <!--GridView可以作为ListView的View来使用而不能当成单独的控件来使用--> <GridView> <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"/> <GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}"/> </GridView> </ListView.View> </ListView> <Button x:Name="btn" Content="请单击我!" Height="50" Margin="5" Click="btn_Click"/> </StackPanel>
private void btn_Click(object sender, RoutedEventArgs e) { DataTable dt = Load(); listViewStudents.ItemsSource = dt.DefaultView; } private DataTable Load() { DataTable dt = new DataTable(); dt.Columns.Add("Id", typeof(int)); dt.Columns.Add("Name", typeof(string)); dt.Columns.Add("Age", typeof(int)); Student stu = new Student() { Id = 0, Age = 22, Name = "Tim" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 1, Age = 24, Name = "Tom" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 2, Age = 26, Name = "Tony" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 3, Age = 21, Name = "Kyle" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 4, Age = 29, Name = "Emily" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); return dt; }
17.4 XML数据:TreeView、Menu
DOC | 文档对象模型 | XmlDocument、XmlElement、XmlNote、XmlAttribute | 中规中矩、功能强大、但背负了太多了的传统和复杂 |
LINQ | 语言集成查询 | XDocument、XElement、XNote、XAttribute | 可以使用LINQ进行查询和操作,方便快捷 |
现代程序设计只要涉及到数据传输就离不开XML,因为大多数数据传输都是基于SOAP(Simple Object Access Protocol,简单数据访问协议)相关的协议,而SOAP又是通过将对象序列化为XML
文本进行传输。XML文本是树形结构的。
使用XML数据作为Binding的Source,使用XPath树形而不是Path树形来指定数据的来源
<StackPanel Background="LightBlue"> <ListView x:Name="listViewStudents" Height="200" Margin="5"> <ListView.View> <GridView> <GridView.Columns> <!--使用@表明这是XML元素的Attribute,不加@符号的字符串表示的是子集元素--> <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath=@Id}"/> <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding XPath=Name}"/> </GridView.Columns> </GridView> </ListView.View> </ListView> <Button x:Name="btn" Content="请单击我!" Margin="5" Height="40" Click="btn_Click"/> <Button x:Name="btn1" Content="请单击我!" Margin="5" Height="40" Click="btn1_Click"/> </StackPanel>
private void btn_Click(object sender, RoutedEventArgs e) { XmlDocument doc = new XmlDocument(); doc.Load(@"RawData.xml"); XmlDataProvider xdp = new XmlDataProvider(); xdp.Document = doc; //使用XPath选择需要暴露的数据,现在是需要暴露一组Student xdp.XPath = @"StudentList/Student"; listViewStudents.DataContext = xdp; listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding()); //以上两句写法类似于下面的写法,因为我们知道直接把DataTable赋值给ItemsSource是会报错的: //listViewStudents.DataContext = dt; //dt是一个DataTable //listViewStudents.SetBinding(ListView.ItemsPanelProperty, new Binding()); } private void btn1_Click(object sender, RoutedEventArgs e) { XmlDataProvider xdp = new XmlDataProvider(); string xmlPath = Directory.GetCurrentDirectory() + "\\RawData.xml"; xdp.Source = new Uri(xmlPath);//这里必须使用绝对路径 //使用XPath选择需要暴露的数据,现在是需要暴露一组Student xdp.XPath = @"StudentList/Student"; listViewStudents.DataContext = xdp; listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding()); }
<Window.Resources> <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder"> <!--XmlDataProvider直接写下XAMl代码里,那么它的XML数据需要放在<x:Data>里--> <x:XData> <FileSystem xmlns=""> <Folder Name="Books"> <Folder Name="Programming"> <Folder Name="Windows"> <Folder Name="WPF"/> <Folder Name="WFC"/> <Folder Name="Delphi"/> </Folder> </Folder> <Folder Name="Tools"> <Folder Name="Development"/> <Folder Name="Designment"/> <Folder Name="Players"/> </Folder> </Folder> </FileSystem> </x:XData> </XmlDataProvider> </Window.Resources> <Grid> <TreeView ItemsSource="{Binding Source={StaticResource xdp}}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}"> <TextBlock Text="{Binding XPath=@Name}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid>
17.5 LINQ:集合对象、DataTable、XML
<StackPanel Background="LightBlue"> <!--XAML中没什么,所有的LINQ和绑定都写在了C#代码里--> <ListView x:Name="listViewStudents" Height="200" Margin="5"> <ListView.View> <GridView> <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"/> <GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}"/> </GridView> </ListView.View> </ListView> <Button x:Name="btn" Content="List Student 请单击我!" Margin="5" Height="40" FontSize="20" Click="btn_Click"/> <Button x:Name="btn1" Content="DataTable 请单击我!" Margin="5" Height="40" FontSize="20" Click="btn1_Click"/> <Button x:Name="btn2" Content="XML 请单击我!" Margin="5" Height="40" FontSize="20" Click="btn2_Click"/> </StackPanel>
public partial class MainWindow : Window { List<Student> listStus; DataTable dt; public MainWindow() { InitializeComponent(); //准备数据 listStus = new List<Student>() { new Student(){ Id=0,Name="Tom",Age=21}, new Student(){ Id=1,Name="Tim",Age=22}, new Student(){ Id=2,Name="Tgg",Age=23}, new Student(){ Id=3,Name="Eric",Age=24}, new Student(){ Id=4,Name="Johh",Age=25}, new Student(){ Id=5,Name="Eina",Age=26}, new Student(){ Id=6,Name="Mike",Age=27} }; dt = new DataTable(); dt.Columns.Add("Id", typeof(int)); dt.Columns.Add("Name", typeof(string)); dt.Columns.Add("Age", typeof(int)); Student stu = new Student() { Id = 0, Age = 22, Name = "Tim" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 1, Age = 24, Name = "Tom" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 2, Age = 26, Name = "Tony" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 3, Age = 21, Name = "Kyle" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); stu = new Student() { Id = 4, Age = 29, Name = "Emily" }; dt.Rows.Add(stu.Id, stu.Name, stu.Age); } private void btn_Click(object sender, RoutedEventArgs e) { //查询出List<Student>中名字以E开头的所有学生 listViewStudents.ItemsSource = from stu in listStus where stu.Name.StartsWith("E") select stu; } private void btn1_Click(object sender, RoutedEventArgs e) { //查询出DataTable中名字以T开头的所有学生 listViewStudents.ItemsSource = from row in dt.Rows.Cast<DataRow>() where Convert.ToString(row["Name"]).StartsWith("T") select new Student() { Id = int.Parse(row["Id"].ToString()), Name = row["Name"].ToString(), Age = int.Parse(row["Age"].ToString()) }; } private void btn2_Click(object sender, RoutedEventArgs e) { //查询出XML中名字以V开头的所有学生 string xmlPath = Directory.GetCurrentDirectory() + "\\RawData.xml"; if (File.Exists(xmlPath)) { XDocument xdoc = XDocument.Load(xmlPath); listViewStudents.ItemsSource = from element in xdoc.Descendants("Student") where element.Attribute("Name").Value.StartsWith("V") select new Student() { Id = int.Parse(element.Attribute("Id").Value), Name = element.Attribute("Name").Value, Age = int.Parse(element.Attribute("Age").Value), }; } else { MessageBox.Show("RawData.xml文件不存在"); } } } public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
17.6 使用Binding的RelativeSource
<!--tb的Text绑定自己的Name--> <TextBox x:Name="tb" Text="{Binding RelativeSource={RelativeSource Mode=Self},Path=Name}" FontSize="50"/>
<Grid x:Name="g2"> <DockPanel x:Name="d3"> <StackPanel x:Name="s1"> <Grid x:Name="g1"> <DockPanel x:Name="d2"> <DockPanel x:Name="d1"> <!--tb的Text绑定自己父级容器中第2个DockPanel的Name,即d2--> <TextBox x:Name="tb" FontSize="50" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=2,AncestorType=DockPanel},Path=Name}"/> </DockPanel> </DockPanel> </Grid> </StackPanel> </DockPanel> </Grid>
17.7 数据验证
<!--Source:Slider Target:TextBox Path:Value 目标属性:TextBox.Text--> <TextBox x:Name="tb_Name1" FontSize="30" BorderBrush="Black" Margin="5" Height="50"> <TextBox.Text> <!--NotifyOnValidationError="True" 当数据校验失败时Binding会发出一个信号,这个信号会以Bingding对象的Target为起点在UI元素树上传递--> <Binding Path="Value" ElementName="slider" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True"> <Binding.ValidationRules> <!--ValidatesOnTargetUpdated="True" 默认只验证来自Target(TextBox)的数据,加了这一句也验证Source(Slider)的数据--> <local:RangeValidationRule ValidatesOnTargetUpdated="True"> </local:RangeValidationRule> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>
public class RangeValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { double d = 0; if (double.TryParse(value.ToString(),out d)) { if (d>=0 && d<=100) { return new ValidationResult(true, null); } } return new ValidationResult(false, "验证失败"); } }
this.tb_Name1.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.ValidationError)); void ValidationError(object sender, RoutedEventArgs e) { if (Validation.GetErrors(this.tb_Name1).Count>0) { this.tb_Name1.ToolTip = Validation.GetErrors(this.tb_Name1)[0].ErrorContent.ToString(); } }
17.8 数据转换
public enum Category { Bomber, Fighter } public enum State { Avaiable, Locked, Unknown } public class Plane { public Category Category { get; set; } public string Name { get; set; } public State State { get; set; } }
using System; using System.Globalization; using System.Windows.Data; namespace WpfApp1 { public class CategoryToSourceConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { Category category = (Category)value; switch (category) { case Category.Bomber: return @"\Icons\Bomber.png"; case Category.Fighter: return @"\Icons\Fighter.png"; default: return null; } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } public class StateToNullableBoolConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { State state = (State)value; switch (state) { case State.Avaiable: return true; case State.Locked: return false; case State.Unknown: default: return null; } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { bool? nb = (bool?)value; switch (nb) { case true: return State.Avaiable; case false: return State.Locked; case null: default: return State.Unknown; } } } }
<Window.Resources> <local:CategoryToSourceConverter x:Key="cts"/> <local:StateToNullableBoolConverter x:Key="stnb"/> </Window.Resources> <StackPanel> <ListBox x:Name="listBox" Height="160" Margin="5"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/> <TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/> <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button x:Name="btnLoad" Content="Load" Height="25" Margin="5,0" Click="btnLoad_Click"/> <Button x:Name="btnSave" Content="Save" Height="25" Margin="5" Click="btnSave_Click"/> </StackPanel>
List<Plane> list = new List<Plane>(); private void btnLoad_Click(object sender, RoutedEventArgs e) { list = new List<Plane>() { new Plane() {Category = Category.Bomber, Name = "B-1", State = State.Unknown}, new Plane() {Category = Category.Fighter, Name = "F-1", State = State.Unknown}, new Plane() {Category = Category.Bomber, Name = "B-2", State = State.Unknown}, new Plane() {Category = Category.Bomber, Name = "B-3", State = State.Unknown}, new Plane() {Category = Category.Fighter, Name = "F-2", State = State.Unknown} }; listBox.ItemsSource = list; } private void btnSave_Click(object sender, RoutedEventArgs e) { StringBuilder sb = new StringBuilder(); foreach (Plane p in listBox.Items) { sb.AppendLine($"Category={p.Category},Name={p.Name},State={p.State}"); } MessageBox.Show(sb.ToString()); }
17.9 多路绑定
//多路绑定,继承的接口将不是IValueConverter,而是IMultiValueConverter //注意这里的object[]取决于添加子级Binding的顺序 public class LogonMultiBindingConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (!values.Cast<string>().Any(text => string.IsNullOrWhiteSpace(text)) && values[0].ToString() == values[1].ToString() && values[2].ToString() == values[3].ToString()) { return true; } else { return false; } } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
<!--四个TextBox都是Source,一个按钮是Target--> <StackPanel Background="LightBlue"> <TextBox x:Name="tb1" Height="23" Margin="5"/> <TextBox x:Name="tb2" Height="23" Margin="5,0"/> <TextBox x:Name="tb3" Height="23" Margin="5"/> <TextBox x:Name="tb4" Height="23" Margin="5,0"/> <Button x:Name="btn" Content="Submit" Width="80" Margin="5"/>
//当tb1内容与tb2相同,tb3内容与tb4相同,按钮获得使用
//所以是将四个按钮的Text加上逻辑判断(在数据转换中完成),绑定到按钮的Enable属性上
private void SetMultiBinding()
{
Binding b1 = new Binding("Text") { Source = this.tb1 };
Binding b2 = new Binding("Text") { Source = this.tb2 };
Binding b3 = new Binding("Text") { Source = this.tb3 };
Binding b4 = new Binding("Text") { Source = this.tb4 };
MultiBinding multiBinding = new MultiBinding() {Mode = BindingMode.OneWay};
multiBinding.Bindings.Add(b1);
multiBinding.Bindings.Add(b2);
multiBinding.Bindings.Add(b3);
multiBinding.Bindings.Add(b4);
multiBinding.Converter = new LogonMultiBindingConverter();
this.btn.SetBinding(Button.IsEnabledProperty, multiBinding);
}