XAML和C#
实现相同的窗体的两种语言的源码
<Window Title="XAML 窗口" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width = "300" Height="200" >
<DockPanel>
<Button DockPanel.Dock="Left"
Background="AliceBlue" Margin="0 5 0 10" Content="Hello XAML"/>
<Button >
<!--������DockPanel��������Button-->
<DockPanel.Dock>
Right
</DockPanel.Dock>
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="Yellow" Offset="0.0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1.0" />
</LinearGradientBrush>
</Button.Background>
Hello XAML
</Button>
</DockPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mumu_windowcs
{
public class MyWindow : Window
{
[STAThread]
public static void Main()
{
MyWindow win = new MyWindow();
win.Show();
Application app = new Application();
app.Run();
}
public MyWindow()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.Title = "XAML窗口";
this.Width = 300;
this.Height = 200;
DockPanel panel = new DockPanel();
Button btn = new Button();
btn.Content = "Hello XAML";
btn.Background = new SolidColorBrush(Colors.AliceBlue);
btn.Margin = new Thickness(0, 5, 0, 10);
DockPanel.SetDock(btn, Dock.Left);
panel.Children.Add(btn);
Button btn2 = new Button();
btn2.Content = "Hello XAML";
LinearGradientBrush brush = new LinearGradientBrush();
brush.StartPoint = new Point(0, 0);
brush.EndPoint = new Point(1, 1);
brush.GradientStops.Add(new GradientStop(Colors.Yellow, 0));
brush.GradientStops.Add(new GradientStop(Colors.Red, 0.25));
brush.GradientStops.Add(new GradientStop(Colors.Blue, 0.75));
brush.GradientStops.Add(new GradientStop(Colors.LimeGreen, 1));
btn2.Background = brush;
DockPanel.SetDock(btn2, Dock.Right);
panel.Children.Add(btn2);
this.Content = panel;
}
}
}
XAML文件有两个重要组成部分:一是有完整的开始和结束标签的要素,如Window,DockPanel和Button等,称为“元素”(Element);二是依附于元素的要素,如Width,Height和Background等,称为“属性”(Attribute).
在XAML需要命名空间:xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation”
命名空间以及映射
在WPF中只有4种元素可以作为根元素
- Window:一个窗体
- Page:页面
- Application:一个应用程序
- ResourceDictionary:代表一个逻辑资源的集合
其他的命名空间
- 系统命名空间:xmlns:s =“clr-namespace:System;assembly=mscorlib.dll”
- 自定义类
2-1. 本地的自定义类:xmlns:local =“clr-namespace:mumu_customnamespace”
2-2. 外部程序的自定义类:xmlns:customlib =“clr-namespace:mumu_custolib;assembly = mumu_customlib”
简单属性
- 直接赋值的,比如像“Title”和“Width”.
- 相当于c#中枚举型,直接字符赋值就行,比如
C#
solidbrush.Color= Colors.AliceBlue;
XAML
<Button Color="AliceBlue" />
还有c#的枚举可以通过运算符“|”,而XAML直接用“,”。
附加属性
- 附加属性是可以用于多个控件,但是在另外一个类中定义的属性。
- 附加属性常用于布局,命名方式是“定义类型.属性”比如“DockPanel.Dock=“Left” ”。
- 附加属性的设置可以使用“Attribute”和“Property-Element”语法
比如
<Button>
<!-- 这里是DockPanel,而不是Button-->
<DockPanel.Dock>
Right
</DockPanel.Dock>
Hello CAML
</Button>
Content属性
<Button Width = "300" Height= "200">
<Button.Content>
Hello XAML
</Button.Content>
</Button>
<Button Width = "300" Height= "200" Content="Hello XAML">
</Button>
<Button Width = "300" Height= "200">
Hello XAML
</Button>
C# 自定义Content属性
在book类之前添加一个Text属性,并将其制定为Content属性。然后在ToString函数中添加Text的内容如下代码
[ContentProperty("Text")] //声明Content属性
public class Book
{
public Book()
{
}
//Name属性
public string Name
{
get;
set;
}
//Price属性的数据类型是一个MoneyType类,该类声明了类型转换器,可以将带有美元符号的价格转换为人民币
public double Price
{
get;
set;
}
//Text属性
public string Text { get; set; }
public override string ToString()
{
string str = Name + "售价为:" + Price + "元\n"+Text;
return str;
}
}
类型转换器
字符串 -> 类型转换器 -> CLR对象
自定义类型转换器:
例如:
为Price定义一个新的类型MoneyType,它只是简单的封装了一个double类型的变量。同时提供了一个静态的Parse方法将一个字符串正确转换为MoneyType类型的对象
//声明类型转换器
[TypeConverter(typeof(MoneyConverter))]
public class MoneyType
{
private double _value;
public MoneyType() { _value = 0; }
public MoneyType(double value)
{
_value = value;
}
public override string ToString()
{
return _value.ToString();
}
//价格转换方法,这里只考虑美元和人民币,不考虑其他币种
public static MoneyType Parse(string value)
{
string str = (value as string).Trim();
if (str[0] == '$')
{
//将美元转换为人民币
string newprice = str.Remove(0, 1);
double price = double.Parse(newprice);
return new MoneyType(price * 8);
}
else
{
//不带特殊符号的字符串默认识别为人民币
double price = double.Parse(str);
return new MoneyType(price);
}
}
}
public class MoneyConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
//转换为字符串类型其实不需要重写此方法
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
//将string转换为MoneyType
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
return MoneyType.Parse((string)value);
return base.ConvertFrom(context, culture, value);
}
//将MoneyType转换为string
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return ((MoneyType)value).ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
标记扩展
现在大多数的属性XAML都可以工作得很好,但依旧会有几种情况XAML难以胜任
- 将一个属性赋值为null.
- 将一个属性赋值给一个静态变量
遇到以上情况我们就需要使用标记扩展了。标记扩展是通过XAML的显式的,一致的语法调用实现。标记扩展可以通过自定义来实现XAML的语义扩展。在XAML中只要属性值被一对花括号{}括起来,XAML解析器就会认为这是一个标记扩展。
比如
<Button Name="btn" Content ="MyButton" Click="btn_Click" Background="{x:Null}">
C#和XAML合璧
- Markup +Code-Behind
- 工作原理
APP类仍从Main函数开始,XAML的名称和XAML文件中Name属性一致。在Connect方法中获得该窗口的对象的实例,并实现事件处理函数的注册。Connect方法是IComponentConnect必须实现的一个接口方法,在MainWindow的InitializeComponent函数中会根据初始的XAML文件将其转换为当前的应用对象,该函数调用LoadComponent后将一个bool类型的私有变量设置为true。以保证在程序的生命周期中请求的资源只加载一次。加载的资源是一个BAML文件,它和生成的g.cs文件在同一个目录下。BAML文件是一个二进制形式的XAML文件,它会作为一个二进制资源嵌入到程序集中,可以通过Reflector工具查到,这样做的目的是提高程序运行时的性能。
依赖属性
依赖属性是WPF引入的一个新的属性类型。是一种类型为DependcyProperty的属性,其依赖属性标识则是依赖属性的实例。
相关术语:
- DependencyObject: WPF中的一种类型。继承该类后可以注册和拥有依赖属性。
- WPF属性系统:WPF通过提供一系列的服务扩展了普通的.NET属性,这些服务总称为“WPF属性系统”
- .NET属性包装器:指属性的get和set实现
依赖属性像一个计算过程,依据输入值经过计算得到输出值,整个计算过程依赖其他属性与内在和外在的多种因素
为什么要引入依赖属性
WPF主要设计思想是侧重属性胜于方法和事件。即如果属性可以解决问题,则坚决不使用方法和事件。
依赖属性的用例
该用例实现了资源引用支持,样式支持,动画支持,数据绑定的支持,属性值继承的支持,元数据重载的支持,对WPF设计器的集成支持。
APP的XAML如下
<Application x:Class="mumu_Button01.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:mumu_Button01"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- 引入一个.NET资源对象-->
<local:BindingData x:Key="myDataSource"/>
<!-- 定义一个KEY值为MyBrush的画刷资源-->
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
<!-- 添加一个新的样式 "GreenButtonStyle"-->
<Style x:Key="GreenButtonStyle">
<Setter Property="Control.Background" Value="Green"/>
</Style>
</Application.Resources>
</Application>
BindingData.cs如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace mumu_Button01
{
class BindingData
{
public BindingData()
{ ColorName = "Red"; }
private string name = "Red";
public string ColorName
{
get { return name; }
set
{
name = value;
}
}
}
}
MainWindow的XAML如下
<Window x:Class="mumu_Button01.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="mumu_Button01" Height="269" Width="572">
<Grid Name="Grid1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!--资源支持-->
<Label HorizontalAlignment="Center" VerticalAlignment="Center">资源支持</Label>
<Button Grid.Row="0" Grid.Column="1" Name="resouceBtn" Margin="5" Background="{DynamicResource MyBrush}">金色按钮</Button>
<!--样式支持-->
<Label Grid.Row="0" Grid.Column ="2" HorizontalAlignment="Center" VerticalAlignment="Center">样式支持</Label>
<Button Grid.Row="0" Grid.Column="3" Name="styleBtn" Padding="0" Margin="5" Style="{StaticResource GreenButtonStyle}">绿色按钮</Button>
<!--动画支持-->
<Label Grid.Row="1" Grid.Column ="0" HorizontalAlignment="Center" VerticalAlignment="Center">动画支持</Label>
<Button Grid.Row="1" Grid.Column="1" Name="animationBtn" Margin="5">
<Button.Background>
<SolidColorBrush x:Name="AnimBrush"/>
</Button.Background>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="AnimBrush"
Storyboard.TargetProperty="(SolidColorBrush.Color)"
From="Red" To="Green" Duration="0:0:5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
动画按钮
</Button>
<!--数据绑定支持-->
<Label Grid.Row="1" Grid.Column ="2" HorizontalAlignment="Center" VerticalAlignment="Center">数据绑定支持</Label>
<Button Grid.Row="1" Grid.Column="3" Name="BindingBtn" Background="{Binding Source={StaticResource myDataSource},Path=ColorName}">我被绑定成红色!</Button>
<!--属性值继承-->
<Label Grid.Row="2" Grid.Column ="0" HorizontalAlignment="Center" VerticalAlignment="Center">属性继承支持</Label>
<Button Grid.Row="2" Grid.Column="1" Name="FontSizeWinBtn" Click="FontSizeWinBtn_Click">设置窗口字体:16</Button>
<Button Grid.Row="2" Grid.Column="2" Name="FontSizeBtn" Click="FontSizeBtn_Click">设置按钮字体:8</Button>
<Button Grid.Row="2" Grid.Column="3" Name="ResetFontSizeBtn" Click="ResetFontSizeBtn_Click">重置字体:12</Button>
</Grid>
</Window>
mainWindow.xaml.cs如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 mumu_Button01
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_oldFontSize = FontSize;
}
private double _oldFontSize = 0;
private void FontSizeWinBtn_Click(object sender, RoutedEventArgs e)
{
FontSize = 16;
}
private void FontSizeBtn_Click(object sender, RoutedEventArgs e)
{
this.FontSizeBtn.FontSize = 8;
}
private void ResetFontSizeBtn_Click(object sender, RoutedEventArgs e)
{
FontSize = _oldFontSize;
//this.FontSizeBtn.FontSize = _oldFontSize;
}
}
}
自定义依赖属性
何时需要自定义依赖属性:需要资源引用支持,样式支持,动画支持,数据绑定的支持,属性值继承的支持,元数据重载的支持,对WPF设计器的集成支持的时候。
前置条件:
- 该类必须继承自DependencyObject类。
- 该类中必须定义一个public static readonly 成员变量,类型为DependencyProperty.
- 该依赖属性名必须以“属性名+Property”命名。
- 必须调用DependencyProperty的注册方法在WPF属性系统中注册该依赖属性或者使用依赖属性的AddOwner方法。
- 为依赖属性实现一个.NET属性包装器
自定义Button按钮
using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace mumu_Button02
{
public class SpaceButton : Button
{
// 传统.Net做法 私有字段搭配一个公开属性
string txt;
public string Text
{
set
{
txt = value;
Content = SpaceOutText(txt);
}
get
{
return txt;
}
}
// 依赖属性
public static readonly DependencyProperty SpaceProperty;
//.Net属性包装器
public int Space
{
set
{
SetValue(SpaceProperty, value);
}
get
{
return (int)GetValue(SpaceProperty);
}
}
// 静态的构造函数
static SpaceButton()
{
// 定义元数据
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.DefaultValue = 0;
metadata.Inherits = true;
metadata.PropertyChangedCallback += OnSpacePropertyChanged;
// 注册依赖属性
SpaceProperty = DependencyProperty.Register("Space", typeof(int), typeof(SpaceButton), metadata, ValidateSpaceValue);
}
// 值验证的回调函数
static bool ValidateSpaceValue(object obj)
{
int i = (int)obj;
return i >= 0;
}
// 属性值改变的回调函数
static void OnSpacePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
SpaceButton btn = obj as SpaceButton;
string txt = btn.Content as string;
if (txt == null) return;
btn.Content = btn.SpaceOutText(txt);
}
// 该方法给字符串间距加上空格
string SpaceOutText(string str)
{
if (str == null)
return null;
StringBuilder build = new StringBuilder();
// 往里面加上Space个空格
foreach (char ch in str)
build.Append(ch + new string(' ', Space));
return build.ToString();
}
}
}
外部调用自定义按钮
<Window x:Class="mumu_Button02.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:mumu_Button02"
Title="mumu_Button02" Width="300" Height="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:SpaceButton x:Name="btnSpace" Grid.Column="0" Grid.Row="0" Margin="5" Click="btnSpace_Click" Text="设置按钮字符空格:2">
</local:SpaceButton>
<local:SpaceButton x:Name= "winSpace" Grid.Column="0" Grid.Row="1" Margin="5" Click="winSpace_Click" Text="设置窗口字符空格:2">
</local:SpaceButton>
</Grid>
</Window>
在MainWindow.xaml.cs中添加事件
private void btnSpace_Click(object sender, RoutedEventArgs e)
{
this.btnSpace.Space = 2;
}
在窗口中继承依赖属性
static MainWindow()
{
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.Inherits = true;
SpaceProperty = SpaceButton.SpaceProperty.AddOwner(typeof(MainWindow));
SpaceProperty.OverrideMetadata(typeof(Window),metadata);
}
public static readonly DependencyProperty SpaceProperty;
public int Space
{
set
{
SetValue(SpaceProperty, value);
}
get
{
return (int)GetValue(SpaceProperty);
}
}
注意窗口的依赖特性Space不是通过注册而来,而是从SpaceButton的Space属性的AddOwner方法得来的,即依赖属性可以选择把自身添加给其他属性。需要特别注意原始元数据不能在使用,必须新建一个,为了实现属性值继承,将Inherit标识设置为true.
依赖属性的优先级
附加属性
在WPF里,最典型的附加属性就是各种布局里面的属性,比如Grid的Row,Column。
附加属性本质是依赖属性,但是与普通的依赖属性有所不同:
- 注册不在是通过Register方法注册,而是通过RegisterAttached方法注册。
- 没有普通的.NET属性包装器,而是通过Get和Set属性名来实现包装的
- 没有普通的.NET属性
路由事件
路由事件的定义:“Functional definition : A routed event is a type of event that can invoke handlers on multiple in an element tree ,rather than just on the object that object that raised the event.
Implementation definition: A routed event is a CLR event that is backed by an instance of the RounedEvent class and is processed by the Windows Presentation Foundation(WPF)event system”
函数定义:路由事件是一种事件类型,它可以在元素树中的多个元素上调用处理程序,而不仅仅是在引发事件的对象上调用处理程序。
实现定义:路由事件是由RounedEvent类的实例支持并由Windows Presentation Foundation(WPF)事件系统处理的CLR事件
路由事件的作用
以按钮为例,在WinForm中,按钮就是按钮。在WPF中按钮可以任何部件。
传统的事件
路由事件
识别路由事件
路由信息主要包括唯一标识ClickEvent,事件委托形式RoutedEventHandler,以及路由策略,Click事件的路由策略是Bubbling.
路由事件的旅行
路由事件由事件源作为起点,每个事件监听者都要路过。
路由事件的策略有3种
- Bubbling: 事件从事件源触发一路上追溯到根节点。
- Direct:事件从事件源出发,围绕事件源转一圈结束。
- Tunneling:事件源触发事件后,事件从根节点下沉直到事件源。
改变旅行策略因素之一:事件处理函数
改变旅行策略因素之二:类和实例事件处理函数
事件处理函数有两种类型:一是前面所说的普通事件处理函数,称为“实例事件处理函数”(Instance Handlers);二是通过EventManager.RegisterClassHandle方法将一个事件处理函数和一个类关联起来,称为“类事件处理函数”(Class Handlers),其优先级高于前者。
WPF中Command
使用Command的主要原因:
- 使代码更为简洁,容易支持菜单,工具栏和快捷键等多种途径触发。
- 无须担心各个UI的状态(启用或者禁用)
WPF的Command模型
模型包括四个部分:
- Command:应用程序需要执行的任务,比如关闭等任务。
- CommandBinding: 连接Command和特定的应用程序逻辑。
- CommandSource:触发Command的对象。
- Command target :Command 应用在上面的对象。
Command
ICommand接口
Execute方法:当Command被触发时调用该方法。执行与命令相对应的操作。
CanExecute方法:用来判断该命令是否可应用到当前Command target上,如果该方法返回为true,则可以;否则不行。
CanExceuteChanged事件:Command有执行或者不执行两种状态,状态改变时触发该事件,一般监听该事件的是
CommandSource,他监听到该事件会调用绑定的Command的CanExecute方法检查当前Command的状态,然后决定CommandSource 是否启用.