下面的图片文字内容主要摘录翻译整理自 Christian Mosers 的两周学习WPF的入门文章(前5天):
http://www.wpftutorial.net/GettingStarted.html

如有错误,欢迎指正,并请见谅,我也在学习中。


一)开始

    外观与行为的分离:程序开发的趋势。
    丰富的内容:文本、图形、多媒体。
    高度自定义: Style 和 Template
    硬件方案独立:界面元素的定义使用的是 logical units 逻辑单位而不是 pixel 像素。

二)概念
1)XAML文件
    将类的属性作为XAML的元素使用
    内建隐含的类型转换
    标记扩展
    绑定Binding 将两个属性值连结在一起
    静态资源StaticResource 查找一次资源项目
    动态资源DynamicResource 查找资源项目并自动更新
    模板绑定TemplateBinding 将控件的依赖属性绑定到一个控件模板
    x:Static 解析静态属性的值
    x:Null Null值
2)逻辑树与可视树

样例代码:
<Window>
    <Grid>
        <Label Content="Label" />
        <Button Content="Button" />
    </Grid>
</Window> 

    逻辑树
    可以继承依赖属性的值
    可以解析动态资源的相关引用
    在绑定时查找元素的名称
    支持路由事件
    可视树
    呈现所有可视化元素
    处理元素透明度
    处理界面布局和呈现转换
    处理IsEnabled属性
    执行点击测试Hit-Testing
    支持相对资源RelativeSource(FindAncestor) 静态类VisualTreeHelperExtensions的静态泛型方法FindAncestor<T>,如 var grid = VisualTreeHelperExtensions.FindAncestor<Grid>(this);静态类VisualTreeHelper。
3)依赖属性
    概念:
    .NET中的属性 存储在类的私有成员中,直接读取访问。
    WPF的依赖属性 存储在字典当中,通过对应的Key动态解析获取。
    依赖属性的特性 降低内存空间;属性值可以继承;属性值变化时自动发送通知。
    值解析策略Value resolution strategy


    读取 按一定策略和规则从自己开始读取,然后逐级向上到根元素。
    设置 直接设置自己的本地值。


    依赖属性的创建 使用DependencyProperty.Register(),按照命名习惯,依赖属性的名字以Property结尾,在代码中使用GetValue()和SetValue()即可。
     样例代码:

依赖属性
// 定义依赖属性:给MyClockControl类添加一个DateTime类型的CurrentTime属性,默认值是当前时间。
public static readonly DependencyProperty CurrentTimeProperty =
DependencyProperty.Register(
"CurrentTime", typeof(DateTime),
typeof(MyClockControl), new FrameworkPropertyMetadata(DateTime.Now));
// .NET 属性封装
public DateTime CurrentTime
{
get { return (DateTime)GetValue(CurrentTimeProperty); }
set { SetValue(CurrentTimeProperty, value); }
}
//如果需要回调事件,则使用:
new FrameworkPropertyMetadata( DateTime.Now,
OnCurrentTimePropertyChanged,
OnCoerceCurrentTimeProperty ),
OnValidateCurrentTimeProperty );
//其中的值变化回调事件Value Changed Callback
private static void OnCurrentTimePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
MyClockControl control
= source as MyClockControl;
DateTime time
= (DateTime)e.NewValue;
// Put some update logic here...
}
//其中的值控制回调事件Coerce Value Callback,控制属性值的有效性
private static object OnCoerceTimeProperty( DependencyObject sender, object data )
{
if ((DateTime)data > DateTime.Now )
{
data
= DateTime.Now;
}
return data;
}
//其中的值检验回调事件Validation Callback,控制属性值的合法性
private static bool OnValidateTimeProperty(object data)
{
return data is DateTime;
}

    只读的依赖属性 使用DependencyProperty.RegisterReadonly()及其返回的DependencyPropertyKey。

只读的依赖属性
//注册PropertyKey
private static readonly DependencyPropertyKey IsMouseOverPropertyKey =
DependencyProperty.RegisterReadOnly(
"IsMouseOver",
typeof(bool), typeof(MyClass),
new FrameworkPropertyMetadata(false));
//注册依赖属性
public static readonly DependencyProperty IsMouseoverProperty =
IsMouseOverPropertyKey.DependencyProperty;
//.NET属性封装
public int IsMouseOver
{
get { return (bool)GetValue(IsMouseoverProperty); }
private set { SetValue(IsMouseOverPropertyKey, value); }
}

     附加的依赖属性
<Canvas>
    <Button Canvas.Top="20" Canvas.Left="20" Content="Click me!"/>
</Canvas>

附加属性
//给Button注册一个附加属性,附加到Canvas,类型是double,默认值是0且指定此属性值由子元素继承。
public static readonly DependencyProperty TopProperty =
DependencyProperty.RegisterAttached(
"Top",
typeof(double), typeof(Canvas),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.Inherits));
public static void SetTop(UIElement element, double value)
{
element.SetValue(TopProperty, value);
}
public static double GetTop(UIElement element)
{
return (double)element.GetValue(TopProperty);
}

 

    监视属性值的变化 使用DependencyPropertyDescriptor及其提供的AddValueChanged()
方法。
DependencyPropertyDescriptor textDescr = DependencyPropertyDescriptor.
    FromProperty(TextBox.TextProperty, typeof(TextBox));
if (textDescr != null)
{
    textDescr.AddValueChanged(myTextBox, delegate
    {
        // Add your propery changed logic here...
    });
}
    清除本地值Local Value DependencyProperty.UnsetValue代表了一个没有赋值的属性值。
    button1.ClearValue(Button.ContentProperty);

4)路由事件(主要翻译于MSDN)
    概念

    路由事件成对出现,PreviewXXX和XXX,如PreviewMouseDown和MouseDown。路由事件自己是不会停止下来的,可以设置 e.Handled = true 来停止当前的路由事件。
    按功能定义  是一种能够唤起元素树当中所有监听事件的元素(包括实际触发事件的元素本身)的事件处理的事件。
    按实现定义  是一个CLR事件,通过 RoutedEvent 的实例在WPF事件系统中来处理。
    路由事件与WPF中控件的内容丰富性有关;在WinForm中对相同事件处理的多个对象的事件赋值需要执行多次相同的语句,而WPF中则只执行一次即可;在类中定义的静态事件处理,在事件触发时最先执行,类的实例中定义的事件处理其后执行;不需要反射即可引用路由事件。

    HandledEventsToo 属性,可以在EventSetter 和 AddHandler(RoutedEvent, Delegate, Boolean)方法中使用,从而可以在e.Handled = true的情况下,仍然可以执行路由事件。

    路由策略RoutingStrategy
    策略:隧道Tunneling  由根元素最先执行,沿可视树逐级向下路由,到达实际事件触发元素为止,如果遇到 e.Handled = true 则停止路由。在Bubbling事件之前发生。通常用于控件的内容包含的子元素的事件处理,WPF中的输入事件都是以tunneling/bubbling的形式成对出现的,
    策略:冒泡Bubbling  由实际事件触发元素最先执行,沿可视树逐级向上路由,到达根元素为止,如果遇到 e.Handled = true 则停止路由。在Tunneling事件之后发生。大部分路由事件都是Bubbling类型的。通常用于报告不同控件或其他UI元素的输入和状态变化。
    策略:Direct  与.NET事件类同,只有事件触发元素本身自己执行事件处理,不进行上下路由(传递)。可以在类的静态事件中使用,可以在EventSetter 和 EventTrigger 使用。
    具体的路由方向(向上或向下)是由事件定义本身来决定的。

    任何 UIElement 和 ContentElement 都可以监听任何路由事件。利用路由事件,可以在元素树当中进行通信,路由事件中的事件数据是可以传递共享的。

<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-->...

    创建路由事件  路由事件通常都是定义为public static readonly类型的成员。

    参数sender 事件被唤起的源(RaiseEvent调用者)
    参数RoutedEventArgs.Source 事件的发生源

创建路由事件
//注册事件:为MyCustomControl类注册一个标准路由事件类型RoutedEventHandler的路由策略是冒泡向上的路由事件,名字是Selected。
public static readonly RoutedEvent SelectedEvent =
EventManager.RegisterRoutedEvent(
"Selected", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(MyCustomControl));
//封装事件
public event RoutedEventHandler Selected
{
add { AddHandler(SelectedEvent, value); }
remove { RemoveHandler(SelectedEvent, value); }
}
//触发唤起事件
RaiseEvent(new RoutedEventArgs(MyCustomControl.SelectedEvent));

 

 

    路由事件举例:
    下面的图例中,#2元素是 PreviewMouseDown 和 MouseDown 两个事件的事件源。

#2元素的MouseDown事件发生时,整个树中的路由事件的处理顺序为:
1)根元素的PreviewMouseDown (隧道tunnel) 。
2)中间#1元素的PreviewMouseDown (隧道tunnel) 。
3)叶子#2元素的PreviewMouseDown (隧道tunnel) 。
4)叶子#2元素的MouseDown (冒泡bubble) 。
5)中间#1元素的MouseDown (冒泡bubble) 。
6)根元素的MouseDown (bubble) 。

      路由事件的重叠

路由事件重叠
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class
="SDKSample.EventOvw2"
Name
="dpanel2"
Initialized
="PrimeHandledToo"
>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="b1SetColor"/>
</Style>
</StackPanel.Resources>
<Button>Click me</Button>
<Button Name="ThisButton" Click="HandleThis">
Raise event, handle it, use handled=true handler to get it anyway.
</Button>
</StackPanel>

      当按钮 ThisButton 的鼠标点击事件发生时:先执行HandleThis的事件,如果该事件没有设置 handled=true,则会继续执行由Style指定的b1SetColor事件。而另一个按钮则只执行Style指定的b1SetColor事件。

三)布局和控件
 

1)基本概念
    最佳实践准则:
    避免使用固定位置。在Panel中使用Alignment 和 Margin来设置元素的位置。
    避免使用固定大小。尽可能将元素的Width 和 Height设置为Auto。
    使用Canvas来布局矢量图形,而不是用来布局普通的元素。
    在对话框中使用StackPanel来排列按钮。
    使用Grid来布局静态数据项窗口,标签列的宽度设置为Auto,文本框列的宽度则设置为*。
    在数据模板中将ItemControl 与 Grid一起配合使用布局动态数值列表,利用SharedSize特性来同步标签的宽度。
    垂直和水平属性 VerticalAlignment 和 HorizontalAlignment,较简单。
    边距Margin和Padding

    Margin是控件的外边距,Padding是控件中的内容部分的外边距(内边距),Margin是外部控件的Padding,Padding是内部控件的Margin。
    剪辑 ClipToBounds属性


    滚动  ScrollViewer ScrollbarVisibility
2)Grid

     设置好Grid的Row定义和Column定义即可,行高度、列宽度设置为 Auto 或 * 。
     GridSplitter  注意GridSplitter上下行的高度或左右列的宽度在Grid中定义为*,其ResizeBehavior属性设置为PreviousAndNext,或者使用ResizeDirection属性也可(效果稍不同,GridSplitter前一元素的宽度/高度不能调整)。
     多个Grid之间共享列宽度 Grid.IsSharedSizeScope SharedSizeGroup
3)StackPanel
    一般用于在对话框中放置按钮。

四)数据绑定
1)数据绑定

      基本概念 绑定的源可以是普通.NET属性也可以是依赖属性,绑定的目标属性必须是依赖属性。要实现绑定能够正确运转,普通.NET属性是通过触发INotifyPropertyChanged接口的PropertyChanged事件,依赖属性则是通过属性元数据的PropertyChanged回调。在XAML文件中,绑定通常是使用扩展标记{Binding}来使用的。
      数据上下文DataContext 每个从FrameworkElement继承的WPF控件都有一个DataContext属性。如果没有对控件指定绑定源,那么其DataContext就是默认的绑定源,或者使用{Binding}也是说明使用DataContext。控件可以传递其DataContext属性值给其子元素使用。
      数值转换器 实现IValueConverter 接口。
      下面的代码是将StackPanel的可视性绑定到CheckBox控件的IsChecked属性。

简单的绑定样例
<StackPanel>
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="boolToVis" />
</StackPanel.Resources>
<CheckBox x:Name="chkShowDetails" Content="Show Details" />
<StackPanel x:Name="detailsPanel"
Visibility
="{Binding IsChecked, ElementName=chkShowDetails,
Converter={StaticResource boolToVis}}">
</StackPanel>
</StackPanel>
//布尔到可视性的转换器定义
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Boolean)
{
return ((bool)value) ? Visibility.Visible : Visibility.Collapsed;
}
else
return value;
}

public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Visibility)
{
bool myValue = false;
switch ((Visibility)value)
{
case Visibility.Visible:
myValue
= true;
break;
case Visibility.Collapsed:
myValue
= false;
break;
}
return myValue;
}
else
return value;
}
}

 

五)模板和样式

1)样式
 使用样式的好处:代码更易维护,减少代码冗余,仅从一个入口修改一组控件的外观,运行时动态设置控件的外观。
 使用样式:{StaticResource [resourceKey]}
 继承:
<Style x:Key="baseStyle">
  <Setter Property="FontSize" Value="12" />
  <Setter Property="Background" Value="Orange" />
</Style>
<Style x:Key="boldStyle" BasedOn="{StaticResource baseStyle}">
  <Setter Property="FontWeight" Value="Bold" />
</Style>
2)模板
 WPF控件由逻辑和模板两部分组成。逻辑部分定义了控件的状态、事件和属性;模板部分则是控件的可视外表。逻辑和模板的连接是通过数据绑定实现的。每个控件都有其默认的模板,这个默认的模板则提供了控件的基本外表。控件的模板是由其依赖属性Template来设置,设置之后,该控件的可视树则会完全被模板的可视树替代。

模板和样式举例
<Style x:Key="DialogButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"
Stroke
="{TemplateBinding BorderBrush}"/>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment
="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Button Style="{StaticResource DialogButtonStyle}" />

posted on 2010-06-19 16:13  虫子CCC  阅读(4307)  评论(1编辑  收藏  举报