zoukankan      html  css  js  c++  java
  • WPF入门笔记

    下面的图片文字内容主要摘录翻译整理自 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}" />

  • 相关阅读:
    Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲
    Java生鲜电商平台-电商会员体系系统的架构设计与源码解析
    Java生鲜电商平台-优惠券系统的架构设计与源码解析
    Java生鲜电商平台-促销系统的架构设计与源码解析
    Java生鲜电商平台-电商支付流程架构实战
    Java生鲜电商平台-商城后台架构与原型图实战
    还搞不定Java多线程和并发编程面试题?你可能需要这一份书单!
    后端程序员就靠它吃饭了,推荐一份夯实基础的操作系统书单!
    学习计算机基础必读的4本经典入门书籍,自学编程必备书单!
    这份Java Web必读书单,值得所有Java工程师一看!
  • 原文地址:https://www.cnblogs.com/glowworm/p/1760802.html
Copyright © 2011-2022 走看看