WPF入门笔记

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

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


一)开始

WPF入门笔记

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

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

WPF入门笔记

样例代码:
<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

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


    依赖属性的创建 使用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 两个事件的事件源。

WPF入门笔记

#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事件。

三)布局和控件
 

WPF入门笔记

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

WPF入门笔记

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

WPF入门笔记
    滚动  ScrollViewer ScrollbarVisibility
2)Grid

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

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

WPF入门笔记

      基本概念 绑定的源可以是普通.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;
}
}

 

五)模板和样式

WPF入门笔记

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} " />

你可能感兴趣的:(WPF)