WPF中在MVVM模式下,后台绑定ListCollectionView事件触发问题

WPF中在MVVM模式下,后台绑定ListCollectionView事件触发问题

  • 问题:WPF中MVVM模式下 ListView绑定ListCollectionView时,CurrentChanged无法触发

  • 解决方案:

    • 初期方案:利用ListView的SelectionChanged事件在界面后台逻辑中调用VM业务逻辑。 —— 弊端:前后耦合度增加。
    • 最终方案:设置ListView的IsSynchronizedWithCurrentItem属性为True。
  • 经历(吐槽):

    • 百度看了几页没找到解决方案,谷歌第一页就找到了。 —— 真不是我崇洋媚外,我也很无奈。
    • 搜索词:“WPF ListView ListCollectionView CurrentChanged” —— 供验证,偶木有骗人。

解:奇葩史

Git与SVN交叉使用

Git与SVN交叉使用

将本地git项目添加到远程svn中

  • git svn [svnprojpath]
    • svnprojpath为原始svn项目路径 —— 文件夹路径,你要放到哪个文件夹
    • 官方文档中带有-s参数,但我这边加了会有问题,可能是非标准的问题。
  • git svn fetch
    • 此处可能会提示错误警告 —— 忽略,它只是在检测一些历史,不影响
    • git branch -a 看一下分支 —— 单纯看看
  • git svn dcommit
    • 问题1:本地还有未提交的更改 —— git commit 提交下
    • 问题2:head历史没有上游svn信息 —— 为其添加父级信息
      • git show-ref —— 记录下remote/git-svn的commit值,其实是远程项目分支的最后一次commit值[parentcommit]
      • git log --pretty=oneline master —— 记录下第一次commit值[localfirstcommit]
      • git replace --graft [localfirstcommit] [parentcommit] —— 设置父级commit【以前是用.git/info/grafts文件,不过git后期版本会删除这个处理】
    • 问题3:本地修改删除的文件夹,svn上会保留空文件夹 —— 添加选项设置–rmdir
      • 实在没办法删除空文件夹,可以在svn服务上删掉后,本地git svn rebase。
      • 需要原来文件夹中存在内容,然后删除内部文件及文件夹时,–rmdir才会生效,以前的空文件夹时不会自动删除 —— 这个可能也是为了保证svn可以添加空文件夹。
  • .gitignore处理
    • git svn show-ignore > ./git/info/exclude —— 这个好像是针对有svn忽略处理的情况,我们可以忽略这一步
    • 将本地.gitignore文件里的内容添加到./git/info/exclude中 —— 具体配置内容可以网络上,或者如我一样直接使用visual studio自动生成的配置内容
    • 删除本地的.gitignore文件 ——这样避免svn项目中含有.gitignore文件
  • git svn rebase
    • 拉取最新项目
    • 建议每次commit之前拉取最新内容
  • 本地git仓库代码复制
    • git clone d:/SourceRepository d:/DestinationRepository

用git管理远程svn仓库

  • git svn clone [svnprojpath]
    • [svnprojpath]为项目远程路径
  • 之后的操作同上,注意处理.gitignore文件

参考:

行:奇葩史

从中兴制裁谈谈软件开发中的重新造轮子

从中兴制裁谈谈软件开发中的重新造轮子

最近由“中兴制裁”事件引发了许多“中国芯”的讨论,我也来蹭蹭热度,来扯一下软件开发中“重新造轮子”的问题。

在我们接触到软件行业的时候,就常常会听到有人说“不要重新造轮子”的论点,个人其实挺反对这种论点,倒不是因为他不对,而是因为他缺少场景。 —— 没有前提条件的结论,都有失偏颇。还有就是最适合的轮子才是好轮子。

什么情况下需要重新造轮子

  • 个人发展需要
    • 典型场景:目前各个公司中项目开发基本都会用到ORM框架,当然这个可以快速提升我们的开发效率,但对于个人来说,单单会用是远远不够的;如果不自己去尝试造一造这个轮子,你可能连个sql都不会写,你的前途要想有光,还是挺困难的。
    • 注意事项:我们在公司项目开发中,由于工期等一些限制,我们需要尽量少造轮子,你不能项目都来不及了,你还去造轮子。 —— 我们这些小啰啰是要为公司创造利益的,不要把自己看太高,要学技术自己挤时间。
  • 公司发展需要
    • 典型场景:中兴制裁,自己公司不掌握核心技术,想要摆脱其他公司的控制,自然需要重新造。
    • 注意事项:公司中的代码都是要用于商业的,在使用开源项目时,需要注意他们的开源协议是否支持商业使用,不支持的自然需要自己开发(当然很多公司是不管这些协议,继续偷偷用)。
  • 项目开发需要
    • 典型场景:明明只要一个自行车轮子,你给我提供的轮子是汽车轮子。在开源项目中,往往为了适应更多的场景,会设计的更加通用,然而通用会带来一些问题,如项目臃肿,你可能只需要其中很小的一个功能,此时用第三方的项目不如自己造个适合自己项目的。
    • 注意事项:项目开发中需要合理选择第三方开源项目,有些第三方项目的学习曲线比较高,此时重新自己实现可能还来的快些。
    • 扯个其他事:我们在进行项目设计中,有时会考虑太多,从而导致项目无法继续进行,甚至导致项目不了了之,所以在进行项目设计时应该避免过度设计。 —— 量力而行,能力只能做个玩具车,就不要想着一下子搞个飞机了。

最后,我们可以不重复造轮子,但我们不能不会造轮子;我们一开始可以造的没别人好,但我们不能没有自己的轮子。 —— 愿祖国越来越强大,程序员起来造世界。

Xamarin教程索引页

持续更新中……
2017-02-26 今日发现Xamarin官方已经开始逐步提供中文文档,故将减少相关翻译。

Xamarin指南 —— 官网教程翻译

Xamarin跨平台开发

Xamarin Workbooks

Xamarin.Android

入门

应用基础

用户界面

平台功能

部署、测试和指标

高级主题

Under The Hood

故障排除

穿戴

Xamarin.iOS

入门

Xamarin技巧 —— 官网教程翻译

Xamarin.Android

控件

资源

个人整理篇

Xamarin.Android


掘:奇葩史

WindowsAPI钩子程序入门

C#钩子程序入门

简介

现在网络上涉及到钩子程序入门,基本都是以三个基础概念:装载钩子程序,卸载钩子程序,以及回调。但是,本文却不会以这些为出发点,前面的三个概念确实是钩子程序中的重要部分,但对于入门而言,有时是难以理解的(至少本人之前很长时间在这个上面一直是糊里糊涂的,还是后来突然有所明悟 —— 这个看个人,可能有些人一开始就明白了,就像大学里学C语言一样。)。所以本文以个人入门时,最早接触的几个概念来进行描述。

主要函数/API接口

本文主要涉及三个函数/API接口:FindWindow、FindWindowEx、SendMessage。前两个是查找窗体,并返回句柄;最后一个则是消息发送。

注,进行下面内容时,可以利用SPY++工具辅助理解。

FindWindow

FindWindow查找最顶层的窗口句柄,即父窗口为null。并且如果有多个匹配结果,则它只返回最先查找到的结果。

用法 —— 以记事本为例:

[DllImport("user32.dll", EntryPoint = "FindWindow")]
private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);

IntPtr ParenthWnd = FindWindow(null,"无标题 - 记事本"); //方法1
ParentWnd = FindWindow("Notepad", null); //方法2

参数:第一个参数为待查找的窗口类;第二个参数为窗口标题。如果查找到窗口,则返回窗口句柄,否则返回0。上述代码中的方法1和方法2在本示例中效果是一样的。

注意:在函数中的两个参数关系是同时匹配。如果你只需要匹配类或者窗体名称,则另一个参数需设置为null。

FindWindowEx

用于查找窗口中的子窗口句柄,如窗口内部的各种控件。

引入:

[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

参数:第一个参数为父级窗口,第二个参数为同级窗口的前一个窗口句柄,第三个为窗口类,第四个为窗口标题。

注:如果第一个和第二个参数均设置为IntPtr.Zero,则其效果等同于FindWindow。

SendMessage

用于向指定的窗体句柄上发送消息,如设置文本、鼠标按下等消息。

引入:

[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int Msg, string wParam, string lParam);

参数:第一个参数为待发送消息的窗口句柄,第二个为待发送的消息类型,后两个为附加参数,其设置与第二个参数相关联。上述引入时,后两个参数为string类型,但其并不固定,你可以将其处理为object类型。具体内容在遇到时,在进行说明。

另有一个与SendMessage相似的函数 —— PostMessage,不过相对来说SendMessage使用的更多,因为它看起来比较负责任。从比较专业(网络协议)的方面来解释就像:PostMessage像UDP协议,TCP像TCP协议;而比较通俗的讲,以送快递为例,PostMessage是送到代理点就表示送到了,而SendMessage则表示送到家并且要明确签收才行,所以在这上面就可以看出,SendMessage更加负责任。

关于第二个参数,有很多消息类型,具体可以查看:https://msdn.microsoft.com/zh-cn/library/ms644927.aspx
或者查看:http://git.oschina.net/huaxia283611/codes/mtgvh1q4ero7uwk38c25b84 —— 注释不完整,逐步完善

上述函数使用操作步骤

本小节简要介绍如何使用上述函数。

添加相关引用

在使用windows api的类中添加如下引用:

using System.Runtime.InteropServices;

即添加DllImport相关的引用。

函数导入

使用DllImport导入相关函数,如导入FindWindow:

[DllImport("user32.dll", EntryPoint = "FindWindow")]
private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);

其中导入函数修饰符使用extern static。另使用特性(Attribute)修饰 —— DllImport。第一个参数为相关的dll,此处使用user32.dll,但在某些平台上需要使用coredll.dll;第二个参数EntryPoint为相关dll中的函数签名,如果你的函数定义与其一致,则此参数可以省略,不一致则必须,如下面两句导入,均可使用 —— 不过为了维护管理方便,常常保持一致:

[DllImport("user32.dll")]
private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll", EntryPoint = "FindWindow")]
private extern static IntPtr MyFindWindow(string lpClassName, string lpWindowName);

上面完成后,即可调用,方式如调用其他静态方法一样。

至此,入门内容介绍完了,其中主要包含了查找窗体后设置子窗体内容的基本操作。

参考

以下为微软官方相关文档参考:

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教程索引页

Orchard官方教程(译)索引

链接标注 原文 则表示未译,其他带有中文标题的表示译文内容。

入门

关于项目——About the Project

视频教程

要求:能听懂英文-外国人做的视频、能看墙外的-视频在Youtube上

资料

网站使用管理相关内容

网站使用开发相关内容

模块开发相关

关于修改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”。

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