WPF快速入门——绑定Binding

绑定(Binding)元素介绍

此处主要介绍的绑定类是System.Windows.Data.Binding,如果涉及其他内容,将简要介绍,不会过多说明。

下面将简要介绍最基础(最常用)的三个属性:

  1. Path —— 路径,用于索引到具体的属性,常常会省略书写,示例如下:

     <TextBox Text="{Binding Path=A.B}" />
    

其中Path=可以省略,因为Binding元素含有一个带参构造函数,其参数为path。另外,示例中A.B需具体到属性,如果A已经是需要绑定的具体属性,则可以用A替换A.B。即最简单的格式是:

    <TextBox Text="{Binding A}" />
  1. Mode —— 模式,用于指定数据的更新方向,它是一个枚举类型,共有一下四种方式:
    • OneTime —— 一次性更新(只更新一次),从数据源更新到当前使用的绑定属性。
    • OneWay —— 单向更新,从数据源更新到当前使用的属性。
    • OneWayToSource —— 单向更新,从当前使用的属性更新到数据源。
    • TwoWay —— 双向更新,当前属性与数据源同步。

注:如果未指定,即表示使用默认模式,而在不同的依赖属性上,其模式是不一样的。在使用时,如果不确定其默认模式是否是自己需要的模式时,则可以手动指定。

  1. UpdateSourceTrigger —— 数据源更新触发器,用于指定控件上的属性值什么时候更新到数据源,它也是个枚举类型,有以下三种方式:
    • Explicit —— 显示更新,需要调用UpdateSource方法后才能更新。
    • LostFocus —— 失去焦点更新
    • PropertyChanged —— 属性值改变更新,大部分情况会使用此方式,但有时频繁的更新数据源会降低效率,如在TextBox中,如果数据源有较多的数据验证,此时在输入Text时,就有可能出现界面卡顿的情况。

注:当然,此处也有默认值设置,但不同的控件 属性的 默认 值也不一样,不过大部分情况下默认 值是PropertyChanged,比较特殊的有TextBox的Text属性,其默认值是LostFocus。

下面给一个最常用的绑定书写方式:

<TextBox Text="{Binding A,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" />

控件绑定

控件绑定,即在同一个界面中不同控件之间的数据同步处理,最常见的就是滑动条与一个文本框之间的绑定。在控件绑定中,需要指定绑定类的ElementName属性值,即当前属性绑定到哪一个控件的属性上。示例如下:

<Slider Name="slider" Maximum="100" />
<TextBox Text="{Binding ElementName=slider,Path=Value,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" />

注:在WPF开发中,我们常常是不为控件设置Name值的,而在控件绑定中,必须为源控件添加Name属性值;而有些控件可能会不含有Name属性,此时则使用x:Name来指定名称。

在控件绑定中有一个比较特殊的存在——模板绑定-TemplateBinding,它与Binding并不在一个继承结构上。TemplateBinding是用在控件模板定义中的,用于绑定模板对应控件中的属性,示例如下:

<TextBox x:Name="templatebinding">
    <TextBox.Template>
        <ControlTemplate TargetType="TextBox">
            <TextBlock Text="{TemplateBinding Text}" />
        </ControlTemplate>
    </TextBox.Template>
</TextBox>

TemplateBinding可以简单理解为在Binding中设置了ElementName为其父级控件 —— 事实并非如此,仅作为辅助理解。TemplateBinding相对与Binding要少很多属性内容。

数据绑定

此处数据绑定表示在WPF中的对象绑定,即常见场景 就是把数据库 数据显示到 界面上。而在真实的项目开发中 ,常 会用到MVVM模式,数据绑定将会在那里体现出来,但MVVM模式开发则不在此节中叙述。

下面以一个最简单的示例解释数据绑定:

后台类 —— 数据源结构:

class ForDataBinding
{
    public int Count { get; set; }
}

数据源初始化 —— 创建数据并将数据绑定到界面:

ForDataBinding data = new ForDataBinding();
data.Count = 10;
this.fordatabinding.DataContext = data;

界面控件设置 —— 指定控件绑定到源数据的哪个属性:

<Grid x:Name="fordatabinding">
    <TextBox Text="{Binding Count}" />
</Grid>

其中设置了Grid的DataContext,即表示Grid内部数据上下文是以设置的数据源为基础,在此示例中,Text属性绑定的Count就是以ForDataBinding类对象为基础查找属性。 —— 即绑定路径是以当前位置以树形结构往下查找对应属性。

其他元素

Binding除了以上内容,还有其他的属性设置,本小节将简要介绍几个较为常用的内容。

数据格式化转换

在数据绑定中,有时我们需要显示的数据与源数据不一样,如时间格式,浮点数格式,或者更复杂一些的想要一个类对象中的多个属性组合一起显示。

对于简单的数据格式化,可以通过StringFormat来处理,如时间格式化为yyyy-MM-dd,浮点数保留两位小数等等。其代码示例如下:

后台类:

class SimpleDataConvert
{
    public DateTime Date { get; set; } = DateTime.Now;
    public float Price { get; set; } = 100.123456f;
}

使用:

this.simpleconvert.DataContext = new SimpleDataConvert();

界面处理:

<StackPanel x:Name="simpleconvert">
    <TextBox Text="{Binding Date,StringFormat=yyyy-MM/dd}" />
    <TextBlock Text="{Binding Price,StringFormat=f2}" />
</StackPanel>

上述示例结果就是将Date日期格式化为yyyy-MM/dd;将Price保留两位小数显示。

但是有些数据显示要求无法通过StringFormat处理,则需要使用Binding的属性Converter来处理了 —— 即通过值转换器来处理。下面我们以上面用到的时间转化为例,假如我们要在前台显示yyyyMMdd格式的日期,此时从数据源显示到界面可以正确处理,但是在界面输入,它无法正确转化为源数据,即内置的Converter不支持,此时我们就需要自己实现值转换,示例 如下:

首先定义DateConverter,实现接口IValueConverter,代码如下:

class DateConverter : IValueConverter
{
    /// <summary>
    /// 数据源转界面显示
    /// </summary>
    /// <param name="value"></param>
    /// <param name="targetType"></param>
    /// <param name="parameter"></param>
    /// <param name="culture"></param>
    /// <returns></returns>
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value.GetType() == typeof(System.DateTime))
        {
            return ((System.DateTime)value).ToString("yyyyMMdd");
        }
        else
        {
            return value;
        }
    }

    /// <summary>
    /// 界面显示转数据源
    /// </summary>
    /// <param name="value"></param>
    /// <param name="targetType"></param>
    /// <param name="parameter"></param>
    /// <param name="culture"></param>
    /// <returns></returns>
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (targetType == typeof(System.DateTime) && value != null)
        {
            DateTime dt = DateTime.Now;
            string valuestr = value.ToString();
            if (DateTime.TryParse(valuestr, out dt))
            {
                return dt;
            }
            else if (valuestr.Length == 8)
            {
                string yearstr = valuestr.Substring(0, 4);
                string monthstr = valuestr.Substring(4, 2);
                string daystr = valuestr.Substring(6, 2);
                if (DateTime.TryParse(string.Format("{0}-{1}-{2}", yearstr, monthstr, daystr), out dt))
                {
                    return dt;
                }
            }
        }
        return value;
    }
}

然后在Xaml文件中添加引用:

由于此处DateConvert直接定义在当前窗体类命名空间下,所以其已经默认添加了如下空间,如果定义在其他位置,则需要手动添加空间引用。

xmlns:local="clr-namespace:Binding_Demo"

资源定义,以便于在控件中引用

<Window.Resources>
    <local:DateConverter x:Key="dateconvert" />
</Window.Resources>

最后,则将值转换器应用到控件上,代码如下:

<TextBox Text="{Binding Date,Converter={StaticResource dateconvert}}" />

至此,一个简单的值转换器就完成了。

数据验证

在绑定中的验证主要设计四个属性:

  • ValidatesOnDataErrors或者ValidatesOnNotifyDataErrors(WPF 4.5之后才有的)—— 与DataErrorValidationRule或NotifyDataErrorValidationRule组合使用
  • ValidatesOnExceptions —— 与ExceptionValidationRule组合使用
  • NotifyOnValidationError —— 控制是否触发Validation.Error事件,用于额外的内容处理
  • ValidationRules —— 验证规则,用于定义验证规则集合

下面我们以异常验证规则来简要介绍验证规则的使用 —— 验证处理涉及的内容有很多,单此一节无法描述完整,故仅列举最简单的使用方式:

首先是后台类的定义:

class ForExceptionValidate
{
    private int max;
    public int Max
    {
        get { return max; }
        set
        {
            if (value > 100)
            {
                throw new Exception("Max不能超过100");
            }
            max = value;
        }
    }
}

this.forvalidate.DataContext = new ForExceptionValidate();

然后是界面使用:

<StackPanel x:Name="forvalidate">
    <TextBox >
        <TextBox.Text>
            <Binding Path="Max" >
                <Binding.ValidationRules>
                    <ExceptionValidationRule></ExceptionValidationRule>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
</StackPanel>

在此 示例 中,后台类中抛出的异常,会作为界面的验证结果来处理 —— 所以此处虽然没有明确使用异常捕获,但程序并 不会崩溃。

依赖属性

最后,简要说下依赖属性,所有上面的绑定基础都需要靠依赖属性。所有需要绑定功能的属性都进行了对应依赖属性(System.Windows.DependencyProperty)定义。在WPF中,我们大部分时间是在用依赖属性 —— 各种绑定,而自己的定义依赖属性的情况相对较少,所以此处就不再介绍如何定义依赖属性 —— 作为入门介绍教程。

WPF快速入门——控件,样式,模板

控件简介

在WPF中,严格来讲,控件是继承自System.Windows.Control类的元素。但有时我们还习惯性会将面板说成面板控件、Border说成Border控件(合理的说法应该叫***元素),不过这些,我们不会在本节中进行详细讨论,本节中将主要介绍以Control为基类的控件。

WPF中控件有很多,可以大致分为以下几类:

下述继承关系并不代表直接继承,有的是子类的子类等关系

  • 内容控件 —— 继承自ContentControl
  • 列表项控件 —— 继承自ItemsControl
  • 其他控件 —— 种类相对较少的集合
    • 文本控件 —— 主要继承自TextBoxBase,有时也会将密码框PasswordBox归为此类(但PasswordBox直接继承自Control)
    • 范围控件 —— 继承自RangeBase
    • 其他 —— 直接继承自Control,以及相对偏门的控件

常用控件简介

在WPF中,有许多控件,下面介绍几个最常用的控件,我们以上述所列分类分别介绍。

  • 内容控件
    • 按钮Button —— 使用最多的内容控件
    • 勾选框CheckBox —— 使用频率仅次于Button
    • 标签Label —— 几乎不使用(很少使用),因为有更好的替代品TextBlock,TextBlock并不是继承自Control,它直接继承自FrameworkElement,由于其实现相对简洁,占用资源较少,所以大多数情况下会使用TextBlock替代Label(只有当TextBlock的功能无法满足时,才会选择Label)。

内容控件中,使用Content属性设置显示的内容,而Content类型是object,所以其自由度很高。

  • 列表项控件
    • 下拉框ComboBox
    • ListBox —— 简单的列表展示,其子类ListView使用的相对频繁
    • DataGrid —— 表格形式展示数据,业务系统中表格显示时常用。

列表项控件常用于数据列表的绑定显示。

  • 文本控件

    • 文本框TextBox —— 最常用的数据录入接收控件。
    • 密码框PasswordBox —— 主要用于登录处理,它不继承自TextBoxBase,所以有时不把它归类为文本控件,自己单独一类。
  • 范围控件

    • 进度条ProgressBar —— 最常用的范围控件,显示任务处理进度。
    • 滚动条ScroolBar —— 最常见的范围控件,但不常用,因为它常常是封装在ScrollViewer控件中处理。
  • 其他控件

    • 日期日历控件 —— 日期选择 常会需要日期控件,但日期控件中是集成了日历控件的。

简单控件设置

关于控件的简单设置,可以见Hello,World篇中的基本属性设置内容:

样式设置

WPF中的样式其实和HTML中的css样式的概念是及其相似的。它其实是为了让控件的属性设置可以统一管理。当然,它还有额外的功能,最典型的就是触发器。下面说下样式的定义和使用。

样式可以定义在许多不同的位置,它可以直接定义在对应的控件中,如下:

<Button Content="样式设置">
    <Button.Style>
        <Style TargetType="Button">
            <Setter Property="Background" Value="Red" />
        </Style>
    </Button.Style>
</Button>

当然,这种用法并不建议,因为它失去了统一管理的优势,除非是控件有定制化需求,一般不会这样写。针对上面的内容,我们可以将样式抽取出来,然后将样式放置到当前窗体的样式列表中,然后在控件上使用样式,方法如下:

首先将样式定义到当前窗体的Window.Resources中

<Window.Resources>
    <Style x:Key="winbtn" TargetType="Button">
        <Setter Property="Background" Value="Blue" />
    </Style>
</Window.Resources>

然后在相应的控件上应用样式

<Button Content="样式设置2" Style="{StaticResource winbtn}"></Button>

到这,我们就可以在当前窗体中使用统一样式了。但是如果你的应用含有多个窗体 页面时,需要统一样式,这样就不够了,所以我们需要使用资源字典来管理我们的样式,方法如下:

首先在项目上右键,点击添加-资源字典,然后再资源字典中定义样式,代码如下:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Control_Style_Template">
    <Style x:Key="stylebtn" TargetType="Button">
        <Setter Property="Background" Value="Yellow" />
    </Style>
</ResourceDictionary>

此时,样式还未生效,我们需要将样式的资源字典加载到应用中使用,即将样式资源字典添加到App.xaml中,代码如下:

<Application.Resources>
    <ResourceDictionary Source="/StyleDictionary.xaml" />
</Application.Resources>

最后,将样式应用到控件中,上面将 资源字典引用添加到App.xaml中,说明整个应用都可以使用这个资源字典,所以在窗体中使用时如下:

<Button Content="样式设置3" Style="{StaticResource stylebtn}" />

至此,样式最基本的使用方式就讲完了,下面简要描述一下样式中各个关键字的意义。

样式的基本结构:

<Style x:Key="标识" TargetType="对应的控件类型">
    <Setter Property="属性名称" Value="属性值" />
</Style>

使用结构:

<控件类型 Style="{StaticResource 标识key}" />

Tips:

  • 其中x:key是用于标识样式名称,如果将其省略,则表示此样式为默认样式,即不需要你手动为控件添加Style,默认使用此样式 —— 需要注意,保证默认样式(相同控件)最多只有一个;
  • 其中Value的类型是object,所以它可以采用复杂属性设置,内容见Hello,World。
  • 其中样式引用使用StaticResource,还可以使用DynamicResource(相对占用资源较多)—— 它们具体的区别本节不做讨论。

触发器

触发器为我们极大的简化了界面逻辑的书写,它主要包含三大类:简单的Trigger —— 主要处理属性变化、DataTrigger —— 数据绑定变化触发、EventTrigger —— 事件触发器。其中前两个可以进一步细分 —— 单个和多个。下面简要介绍下简单的触发器和事件触发器:

简单的触发器

简单的触发器使用Trigger和MultiTrigger来实现,它们是使用的最为频繁的触发器。其效果是当一个或多个属性发生改变时,改变 其他的一个或多个属性的值。示例如下:

示例效果:鼠标悬停,字体绿色;鼠标悬停并按下,字体白色:

<Style.Triggers>
    <Trigger Property="IsMouseOver" Value="True">
        <Setter Property="Foreground" Value="Green" />
    </Trigger>
    <MultiTrigger >
        <MultiTrigger.Conditions>
            <Condition Property="IsPressed" Value="True" />
            <Condition Property="IsMouseOver" Value="True" />
        </MultiTrigger.Conditions>
        <Setter Property="Margin" Value="1" />
        <Setter Property="Foreground" Value="White" />
    </MultiTrigger>
</Style.Triggers>

其中的MultiTrigger中的Setter设置也可以套在MultiTrigger.Setters之内,如下:

<MultiTrigger.Setters>
    <Setter Property="Margin" Value="1" />
    <Setter Property="Foreground" Value="White" />
</MultiTrigger.Setters>

此处使用前景色做触发示例,而未选择背景色,因为背景色的MouseOver是无效的,原因是其内部使用ButtonChrome样式,需要利用模板处理才可以正确修改。

事件触发器

事件触发器由具体的事件引发。其主要用于动画的处理,其他情况使用的较少。关于动画的制作本节不做描述。

模板设置

WPF在界面设计中最强大的部分应该就是模板了。它可以让你将一个勾选框改成一个按钮的样子,或者将方形按钮改成圆形按钮,等等。模板的定义和使用方式基本 和样式 一样,可以在控件内定义、也可以抽取出来定义到资源字典中。

WPF中模板主要有三种类型,ControlTemplate、DataTemplate和ItemsPanelTemplate,在本文中,将只介绍控件模板ControlTemplate,熟悉了控件模板,其他的两个大概也就会用了,况且另外两个相对与控件模板使用的要少一些。

下面是控件模板最简单的使用:定义+使用

<ControlTemplate x:Key="txt" TargetType="TextBox">
    <TextBlock Text="{TemplateBinding Text}"></TextBlock>
</ControlTemplate>

<TextBox Text="控件模板" Template="{StaticResource txt}" />

当然,个人并不建议上面的定义方式,上面是直接自己写控件模板,但这样就丢失了默认模板的一些特点和功能(如上面的模板就让TextBox无法显示光标,无法定位输入位置),我更建议利用IDE来自动生成模板,因为这样会带有原始的默认模板,而以此模板进行修改可以保证功能不丢失。当然,也不能过于依赖自动生成的内容,因为其内容相对冗余,自己可以进行精简。

以现有模板为基础创建新的模板步骤:

  • 方法1:右击控件,选择编辑模板-编辑副本。 —— 此法缺陷是有些控件默认是创建样式,然后在样式中定义模板内容。当然这也可以说是个好处,样式模板一起处理了。
  • 方法2:在属性面板中搜索template,然后点击Template属性后的方块,选中转换为新资源即可。 —— 此法操作起来较为麻烦。

一般情况下,都是使用方法1,即样式模板一起定义。

下面代码简要展示一个TextBox添加一个内置的标签:—— 以原生模板简单修改

<ControlTemplate x:Key="TextBoxControlTemplate1" TargetType="{x:Type TextBox}">
    <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
        <DockPanel>
            <TextBlock DockPanel.Dock="Left" Text="内置标签:"></TextBlock>
            <ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
        </DockPanel>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Opacity" TargetName="border" Value="0.56"/>
        </Trigger>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="BorderBrush" TargetName="border" Value="#FF7EB4EA"/>
        </Trigger>
        <Trigger Property="IsKeyboardFocused" Value="True">
            <Setter Property="BorderBrush" TargetName="border" Value="#FF569DE5"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

关于控件的模板内容其实有很多,此处不再详述。

关于修改Orchard的<head>标签内容

缘由:由于刚接触Orchard不久,之前想为网站添加百度统计,后来又改为添加Google代码跟踪管理器,都建议将相关代码添加到<head>标签中,但是直接在控制面板中又无法修改到<head>标签内容,故有如下修改:

解决方案:

Orchard中默认使用的页面(母版)为文件夹\Core\Shapes\Views中的Documnet.cshtml文件,修改此文件<head>标签即可。

情景2:在一般情况上面修改没有问题,但有一次我启用了导入/导出模块,它依赖Setup.Service模块,所以,Setup.Service模块启用了,这样带来的问题就是Orchard原来的母版失效了。

后经查看,发现Orchard使用此模块内的Documnet.cshtml文件作为母版,故又需要改此模块内的母版页 —— 具体路径为“Modules\Orchard.Setup\Views”。

另,事实上个人觉得修改主题文件比较合理,但是个人现在暂未确定下来使用什么主题(可能后期自己开发),所有暂且使用以上方法。
当然,上述内容还可以通过开发模块来设置等等,
本文只提供了一个相对简便的修改方法。

Orchard教程索引页

Orchard官方教程(译)索引

链接标注 原文 则表示未译,其他带有中文标题的表示译文内容。
https://github.com/ShiJess/OrchardDoc/blob/chinesedoc/docs/index_zh.markdown

入门

Read More

[译]:Orchard扩展——模块开发入门

返回目录索引
原文链接:Getting Started with Modules
译文链接:Orchard扩展——模块开发入门

This four part course will get you started with a gentle introduction to extending Orchard at the code level. You will build a very simple module which contains a widget that shows an imaginary featured product.

It will teach you some of the basic components of module development and also encourage you to use best-practices when developing for Orchard.

  • Getting Started with Modules - Part 1 - Static Widget

    This first part walks you through creating a static widget and introduces you to many of the classes that form the basis of module development in Orchard.

  • Getting Started with Modules - Part 2 - Dynamic Widget

    The second part of the course explains how you can refine the widget to implement settings that are configurable via the admin dashboard.

  • Getting Started with Modules - Part 3 - Orchard API

    The third part examines Orchard at an API level. You will learn how to work with content items in code and make decisions based on the values of its fields. You will also learn about some of the support classes which make this easier.

  • Getting Started with Modules - Part 4 - Best Practices

    The final installment reviews the completed widget and brings to light loose-ends. You take a tour back through the code, applying development best-practices along the way.

When you have completed this course you will have a solid grounding in extending Orchard via code. With this knowledge you will be able to build your own simple modules. You will also understand the concepts underpinning the documentation found elsewhere in this site and around the web. These skills lay a solid groundwork for you to continue your studies in Orchard module development.


译:奇葩史