精华内容
下载资源
问答
  • const { body, validationResult } = require( 'express-validator/check' ); [ body( 'username' ).isLength({ min: 6 }), body( 'email' ).isEmail(), body( 'password' ).isLength({ min: 6 }) ] 复制代码 换...

    边写边学系列目录

    【一】:使用apidoc,搞定自动化文档

    【二】:使用express-validator进行后端校验

    这个系列文章主旨就是通过写代码来入门,并不深入。只是记录我平时使用到了什么新的技术或插件的入门过程~

    express-validator

    最近写node端后台写的比较多,慢慢的发现前端呢转node端虽然没有那么难,但是有很多细节的东西还没有掌握,比如以前前后端分离的时候,对于一些需求模糊的表单场景,前端可能约束会很松,大部分的约束都是后端去做的,然后用户提交的信息某个字段不合法也是后端反馈给我们异常,前端再做处理。

    因为后端直接接触的就是数据库,每一个字段都必须严格约束,所以对于接口字段的验证,特别是post(往数据库insert)的时候,验证必须严格,我们总不能每一个接口都自己写一套正则来进行校验吧,想一想也是,express庞大的社区肯定已经有类似的中间件了。去npm搜了一下关键字express + validate。映入眼帘的就是这个 —— express-validator。

    // express-validator官网描述是一个基于validator.js封装的express中间件。
    express-validator is a set of express.js middlewares that wraps validator.js validator and sanitizer functions.
    复制代码

    Getting Started

    还是沿用第一节的套路,不管你三七二十一,先按照官网示例,跑通一个Demo,然后我在慢慢来弄~ 这里我依然节省时间,直接使用我之前写过的全栈脚手架express-react-scaffold来直接使用express-validator

    关于这个脚手架的文章在这里新手搭建简洁的Node+React脚手架,正好也是我的第一篇文章,有很多小伙伴也点过赞,一直没时间维护,借此机会温故知新一下,简单回顾了一下,发现当时写的真心锉啊,借此机会小改一下吧~

    其实对于后端接口字段校验,首先想到的就是表单提交了,因为对于GET请求,无论是query还是param,大部分校验工作前端来做就已经可以解决问题了,query和param的合法性通过了,一般后端也就不出问题了(当然,并不是说后端就不必校验了)。而post、put等这种涉及到操作数据库的请求,如果字段类型不匹配,就很容易发生未知错误,而且因为表单里不同表单项会有繁琐的校验规则,所以后端必须控制好~

    以注册接口为例,跑第一个成功Demo

    我们先来看一下以前的注册接口:

    可以看到,需要三个字段,那么我们假设是这样的:

    前端:
        用户名:非空
        邮箱:必须是邮箱类型
        密码:非空
    后端:
        用户名:必须大于6位
        邮箱:必须是邮箱类型
        密码:必须大于6位
    复制代码

    从上面我们可以看出,前后端约束条件不同,也就是说可能存在前端输入合法而后端输入不合法的场景~

    从文档的例子我们可以知道,express-validator的校验只需要在路由path和handler中间插入校验规则数组,我们来写一下。

    // 原来的接口
    // 用户注册接口
    router.post('/register', (req, res) => {
        User.findOne({ //查找是否存在
          username: req.body.username,
        },(err, user)=>{
            if (err) {
                res.send('server or db error');
            } else {
                if (user === null) {
                    const insertObj = {
                      username: req.body.username,
                      password: md5(req.body.password + MD5_SUFFIX),
                      email: req.body.email,
                      role: 10.0
                    };
                    const newUser = new User(insertObj);
                    newUser.save(insertObj, (err, doc) => {
                        if (err) {
                            res.json({ result: false, msg: '用户注册失败' });
                        } else {
                            console.log(doc);
                            res.json({ result: true, msg: '用户注册成功' });
                        }
                    });
                } else {
                    res.json({ result: false, msg: '用户名已存在'});
                }
            }
        });
    });
    复制代码
    // 增加验证过后的接口
    // 用户注册接口
    router.post('/register', [
      check('username').isLength({ min: 6 }),
      check('email').isEmail(),
      check('password').isLength({ min: 6 })
    ], (req, res) => {
      // Finds the validation errors in this request and wraps them in an object with handy functions
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        return res.status(422).json({ errors: errors.array() });
      }
        User.findOne({ //查找是否存在
          username: req.body.username,
        },(err, user)=>{
            if (err) {
                res.send('server or db error');
            } else {
                if (user === null) {
                    const insertObj = {
                      username: req.body.username,
                      password: md5(req.body.password + MD5_SUFFIX),
                      email: req.body.email,
                      role: 10.0
                    };
                    const newUser = new User(insertObj);
                    newUser.save(insertObj, (err, doc) => {
                        if (err) {
                            res.json({ result: false, msg: '用户注册失败' });
                        } else {
                            console.log(doc);
                            res.json({ result: true, msg: '用户注册成功' });
                        }
                    });
                } else {
                    res.json({ result: false, msg: '用户名已存在'});
                }
            }
        });
    });
    复制代码

    好,然后我们来试一下:

    测试用例: 用户名 - aaa, 用户邮箱 - aaa@126.com, 密码 - aaa
    复制代码

    如图,可以看到,前端通过之后,后端没通过,说明我们写的内容生效了。所以!我们的第一个validate demo也就写完了。

    知其然也知其所以然

    上面第一个例子虽然生效了,但是我其实还是有点稀里糊涂,相信小伙伴们也一样,凭啥?为啥就那么加就通过了?别急,我们一步一步来。 先来看看代码:

    // 校验内容部分
    [
      check('username').isLength({ min: 6 }),
      check('email').isEmail(),
      check('password').isLength({ min: 6 })
    ]
    
    // 校验结果部分
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
    return res.status(422).json({ errors: errors.array() });
    }
    复制代码

    校验内容部分就很简单了,无非就是约束条件,现在很简单,以后肯能会变得很复杂,但是不是要考虑的。然后就来看结果部分了,可以看到,通过validationResult(req)获取校验结果,我们将它打印出来看一看:

    {
        isEmpty: [Function],
        array: [Function],
        mapped: [Function],
        formatWith: [Function],
        throw: [Function] 
    }
    复制代码

    可以看到校验结果返回了几个api,我们来猜一猜或者打印一下就知道了,因为代码里只用到了isEmpty()和array(),而且意思很明显,就是如果errors.isEmpty()为真,就表示校验通过,所以用脑袋想一想isEmpty()应该是bool类型返回校验是否通过,如果为假就是校验不通过,然后把不通过的数组信息返回给我们。我们就打印一下二者:

    // isEmpty
    errors.isEmpty() ====> false // 返回的是bool值,表示结果
    errors.array()
    [ 
        { 
            location: 'body',
            param: 'username',
            value: 'aaa',
            msg: 'Invalid value,
        },
        { 
            location: 'body',
            param: 'password',
            value: 'aaa',
            msg: 'Invalid value' 
        } 
    ]
    复制代码

    可以看到,errors.array()返回的是校验不通过的字段的数组以及对应的信息。所以关于整体的校验流程基本掌握了。接下来就是巩固加深提高的过程了~

    学习使用express-validator的各种API

    上面基本了解了如何在后端使用express-validator,但是有一些点还是不理解:

    比如:在handler前面加上校验数组,数组的内容是我们写的字段,那么字段如果写错呢?
    再比如:他怎么知道我想校验的字段在哪?是query还是param还是body还是header呢?
    复制代码

    带着疑惑,我们在看读文档,等一下,读文档之前,其实我们可以再看看上面的错误数组:

    [ 
        { 
            location: 'body',
            param: 'username',
            value: 'aaa',
            msg: 'Invalid value,
        },
        { 
            location: 'body',
            param: 'password',
            value: 'aaa',
            msg: 'Invalid value' 
        } 
    ]
    复制代码

    嗯,很明显,错误数组对于我们的字段判断是正确的,location字段它定位的是body,确实,我们的post接口确实将数据放到了body里。因此,应该是express-validator会check所有与我们规定值相匹配的req字段吧,带着疑问去查阅一下文档~

    还真是,我们的check还就是把能匹配的都匹配一下,那么问题又来了,这么是不是效率会很低,既然是我们自己写的,我们肯定知道在哪里去找,能提升效率啊~好吧,我都想到了,人家作者能想不到吗?

    check API

    • 限定范围类(check, body, query, header, param, cookie)

    check API就是校验各种规则的api,其中包括各种封装好的校验函数,如:isString()、isInt()、isLength({})等,除此之外还有很多限定范围的api,如图

    可见,也就是上述我们说的问题,我们可以通过约定检索范围提升效率,比如register的接口,我们只需要检验body的字段就行了,那么就可以使用body来进行check,我们来试一试:

    const { body, validationResult } = require('express-validator/check');
    [
      body('username').isLength({ min: 6 }),
      body('email').isEmail(),
      body('password').isLength({ min: 6 })
    ]
    复制代码

    换完过后,结果依然成立,其他类似的check API也类似了,就是你校验的字段在哪里就用对应API去检验就好了,提升效率~。

    • 自定义限定范围(buildCheckFunction)

    出了上述限定范围,我们还可以通过buildCheckFunction来自定义范围,比如我们校验某个字段id只有在body或query才有效,并且是UUID类型的数据,代码如下:

    const { buildCheckFunction } = require('express-validator/check');
    const checkBodyAndQuery = buildCheckFunction(['body', 'query']);
    
    app.put('/update-product', [
      // id must be either in req.body or req.query, and must be an UUID
      checkBodyAndQuery('id').isUUID()
    ], productUpdateHandler)
    复制代码
    • 校验结果validationResult(req) 这个很简单了,就是把express req传进去,然后返回我们上面提到的那个error对象~这个就不多做介绍了,因为官方也没有详细介绍。

    • oneOf(validationChains[, message])

    这个也很简单,就是只要几个条件之中的一个满足,我们就认为校验是通过的~这个场景说实话我还确实没想过哪里能用到,不过还是试一试,我们在登录接口尝试,将用户名验证是否是字符串,密码验证变成是否是数组,这肯定是个假命题,不过最后结果不出意外是通过,因为用户名是正确的:

    // router login - 登录接口
    oneOf([
        body('username').isString(),
        body('password').isArray()
    ])
    
    最后打印出来的结果:validationResult(req).isEmpty() === true复制代码

    Validation Result API

    验证结果的API,也算是最重要的API了,因为校验通过不通过,要返回给客户端什么信息,都是通过这个API获取的。

    validationResult(req)

    • isEmpty()

      这个上面说过了,就是返回一个bool值,表示check部分是否有错,有错就是false,没错就是true。一般使用就是:

      if (!validationResult(req).isEmpty()) {
          res.status(错误码).json({
             错误信息 
          });
      }
      复制代码
    • formatWith(formatter) 这个api意义我个人觉得也不是很大,不过算是锦上添花吧。就是可以自定义错误信息格式。

      app.post('/create-user', yourValidationChains, (req, res, next) => {
      const errorFormatter = ({ location, msg, param, value, nestedErrors }) => {
          // 定义返回错误的样式,存入array数组
          return `${location}[${param}]: ${msg}`;
        };
        const result = validationResult(req).formatWith(errorFormatter);
        if (!result.isEmpty()) {
          // { errors: [ "body[password]: must be at least 10 chars long" ] }
          return res.json({ errors: result.array() });
        }
        ...
      });
      
      复制代码
    • array([options])

      存放返回的错误信息,参数可以设置是否只返回所有错误的第一条,默认返回所有错误。

      Default : { onlyFirstError: false },如果想要默认返回第一条,设置该参数为true即可

    • mapped()

      这个API跟isArray()基本一致,就是返回错误,不过isArray()mapped()的区别就是一个返回的是数组,一个返回的是对象,mapped()返回的是key和value键值对,value跟array数组返回的内容一致。

      // 假设我把login接口的username和password的check验证都改成isArray()。
       [
          body('username').isArray(), 
          body('password').isArray()
       ],
       
      // validationResult(req).mapped()
      { 
          username: { 
            location: 'body',
            param: 'username',
            value: 'luffy',
            msg: 'Invalid value' 
          },
         password: { 
            location: 'body',
            param: 'password',
            value: '123456',
            msg: 'Invalid value' 
          } 
      }
      复制代码
    • throw()

      使用这个api就是不使用isEmpty(),通过throw()一个error来返回错误。

      try {
        validationResult(req).throw();
        // Oh look at ma' success! All validations passed!
      } catch (err) {
        console.log(err.mapped()); // Oh noes!
      }
      复制代码

    filter API + Validation Chain API

    其实上面两个API我觉得已经足够了,基本满足业务场景了,不过express-validator还提供很多更完善的功能。下面这些API就简单过一下吧,如果有我觉得能用得到的,就写个例子,我觉得check就够用了~哈哈。

    • sanitize系列

      这个与check API类似,也可以限定范围和自定义范围,用处与check API不一样,check API是检验对应参数是否合法,sanitize系列API是可以帮我们提前做一个转换工作,比如我们后台要求的是数字1,但是前端传过来的是字符串'1',就可以通过sanitize系列API进行转换。

      const { buildSanitizeFunction } = require('express-validator/filter');
      const sanitizeBodyAndQuery = buildSanitizeFunction(['body', 'query']);
      
      app.put('/update-product', [
        // 限定范围在body和query内,将id转换成整型
        sanitizeBodyAndQuery('id').toInt()
      ], productUpdateHandler)
      复制代码
    • validation chain

      这个也不算新的API,应该就是特性吧,感觉跟jQuery一样,不断的链式调用,每一次调用都返回新的结果~

      // 检验weekday字段是否不在['sunday', 'saturday']内。
      check('weekday').not().isIn(['sunday', 'saturday'])
      复制代码
    • withMessage

      这个不是新API,官方是列在Validation Chain API里的,不过我觉得这个是个很有用的API,所有就单独拿出来说一下,就是错误消息可以自定义,我们可以设置返回消息放在里面。

      // 验证部分
      [
          body('username').isArray().withMessage('username类型不正确'), 
          body('password').isArray().withMessage('password类型不正确')
       ],
      // 结果部分
      { 
          username: { 
            location: 'body',
            param: 'username',
            value: 'luffy',
            msg: 'username类型不正确' 
          },
         password: { 
            location: 'body',
            param: 'password',
            value: '123456',
            msg: 'password类型不正确' 
          } 
      }
      复制代码

    结尾

    这篇文章,一如既往,还是我的个人学习过程,如果有人没用过或者想要在自己的项目中使用,应该还是个不错的教程~

    Demo代码地址

    转载于:https://juejin.im/post/5ce4b3616fb9a07eec59a02b

    展开全文
  • final ValidationResult result=new ValidationResult(); Set<ConstraintViolation<Object>> validate = validator.validate(bean); if (validate.size()>0) { result.setHasError(true); validate.forEach...

    前言

    在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有点麻烦:

    • 验证代码繁琐,重复劳动
    • 方法内代码显得冗长
    • 每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码

    叙述

    Bean Validation是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation API和Hibernate Validator。

    • Bean Validation API是Java定义的一个验证参数的规范。
    • Hibernate Validator是Bean Validation API的一个实现。

    @Valid和Validated的比较

    Spring Validation验证框架对参数的验证机制提供了@Validated(Spring’s JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。

    @Valid : 没有分组功能,可以用在方法、构造函数、方法参数和成员属性(field)上,如果一个待验证的pojo类,其中还包含了待验证的对象,需要在待验证对象上注解@valid,才能验证待验证对象中的成员属性

    @Validated :提供分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,用在类型、方法和方法参数上。但不能用于成员属性(field)。
    两者都可以用在方法入参上,但都无法单独提供嵌套验证功能,都能配合嵌套验证注解@Valid进行嵌套验证。

    嵌套验证示例:

    public class ClassRoom{
        @NotNull
        String name;
        
        @Valid  // 嵌套校验,校验参数内部的属性
        @NotNull
        Student student;
    }
    
      @GetMapping("/room")   // 此处可使用 @Valid 或 @Validated, 将会进行嵌套校验
        public String validator(@Validated ClassRoom classRoom, BindingResult result) {
            if (result.hasErrors()) {
                return result.getFieldError().getDefaultMessage();
            }
            return "ok";
        }
    

    BindingResult 的使用

    BindingResult必须跟在被校验参数之后,若被校验参数之后没有BindingResult对象,将会抛出BindException。

    @GetMapping("/room")
        public String validator(@Validated ClassRoom classRoom, BindingResult result) {
            if (result.hasErrors()) {
                return result.getFieldError().getDefaultMessage();
            }
            return "ok";
        }
    

    不要使用 BindingResult 接收String等简单对象的错误信息。简单对象校验失败,会抛出 ConstraintViolationException。主要就是接不着,你要写也算是没关系…

      // ❌ 错误用法,也没有特别的错,只是 result 是接不到值。
        @GetMapping("/room")
        @Validated  // 启用校验
        public String validator(@NotNull String name, BindingResult result) {
            if (result.hasErrors()) {
                return result.getFieldError().getDefaultMessage();
            }
            return "ok";
        }
    

    修改校验失败的提示信息
    可以通过各个校验注解的message属性设置更友好的提示信息。

    public class ClassRoom{
        @NotNull(message = "Classroom name must not be null")
        String name;
        
        @Valid
        @NotNull
        Student student;
    }
    
     @GetMapping("/room")
        @Validated
        public String validator(ClassRoom classRoom, BindingResult result, @NotNull(message = "姓名不能为空") String name) {
            if (result.hasErrors()) {
                return result.getFieldError().getDefaultMessage();
            }
            return "ok";
        }
    

    message属性配置国际化的消息也可以的,message中填写国际化消息的code,在抛出异常时根据code处理一下就好了。

    @GetMapping("/room")
        @Validated
        public String validator(@NotNull(message = "demo.message.notnull") String name) {
            if (result.hasErrors()) {
                return result.getFieldError().getDefaultMessage();
            }
            return "ok";
        }
    
    // message_zh_CN.properties
    demo.message.notnull=xxx消息不能为空
    
    // message_en_US.properties
    demo.message.notnull=xxx message must no be null
    

    hibernate-validator 的使用

    1.引入pom

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.3.1.Final</version>
    </dependency>
    

    2.dto入参对象属性加入注解

    @Data
    public class UserModel implements Serializable {
        private String id;
        @NotBlank(message = "用户名不能为空")
        private String name;
    
        @NotNull(message = "性别不能不填")
        private Byte gender;
    
        @NotNull(message = "年龄不能不填")
        @Min(value = 0,message = "年龄必须大于0岁")
        @Max(value = 150,message = "年龄必须小于150岁")
        private Integer age;
        
        @NotBlank(message = "手机号不能不填")
        private String telphone;
        
        private String registerMode;
        private String thirdPartyId;
        private String encrptPassward;
    }
    

    方法一:3.controller方法入参加入校验(@Validated )

    @GetMapping("/getUser")
        public String validator(@Validated UserModel userModel , BindingResult result) {
            if (result.hasErrors()) {
                return result.getFieldError().getDefaultMessage();
            }
            return "ok";
        }
    

    方法二:3.自定义封装ValidatorImpl类

    @Component
    public class ValidatorImpl implements InitializingBean{
    
        private Validator validator;
    
        //实现校验方法并返回校验结果
        public ValidationResult validate(Object bean){
            final   ValidationResult result=new ValidationResult();
            Set<ConstraintViolation<Object>> validate = validator.validate(bean);
            if (validate.size()>0) {
                result.setHasError(true);
                validate.forEach(constraintViolation->{
                    String errMsg=constraintViolation.getMessage();
                    String propertyName=constraintViolation.getPropertyPath().toString();
                    result.getErrorMsgMap().put(propertyName,errMsg);
                });
            }
            return result;
        }
        @Override
        public void afterPropertiesSet() throws Exception {
            this.validator= Validation.buildDefaultValidatorFactory().getValidator();
        }
    }
    
    

    方法二:4.自定义封装ValidationResult 类

    public class ValidationResult {
    
        public boolean hasError=false;
    
        private Map<String,String> errorMsgMap=new HashMap<>();
    
    
        //实现通用的通过格式化字符串信息获取错误结果的msg方法
        public String getErrMsg(){
            return StringUtils.join(errorMsgMap.values().toArray(),",");
        }
    
        public boolean isHasError() {
            return hasError;
        }
    
        public void setHasError(boolean hasError) {
            this.hasError = hasError;
        }
    
        public Map<String, String> getErrorMsgMap() {
            return errorMsgMap;
        }
    
        public void setErrorMsgMap(Map<String, String> errorMsgMap) {
            this.errorMsgMap = errorMsgMap;
        }
    }
    

    5.controller方法入参加入校验

       @Autowired
       private ValidatorImpl validator;
        @Override
           @Transactional(rollbackFor = Exception.class)
        public void register(UserModel userModel) throws BusinessException {
            UserDo userDo=new UserDo();
            if (userModel == null) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
            }
    
    		//validate进行入参校验
           ValidationResult validate = validator.validate(userModel);
            if (validate.isHasError()){
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,validate.getErrMsg());
            }
        }
    
    

    Bean Validation 的约束

    @Null 被注释的元素必须为 null
    @NotNull 被注释的元素必须不为 null
    @AssertTrue 被注释的元素必须为 true
    @AssertFalse 被注释的元素必须为 false
    @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Size(max, min) 被注释的元素的大小必须在指定的范围内
    @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
    @Past 被注释的元素必须是一个过去的日期
    @Future 被注释的元素必须是一个将来的日期
    @Pattern(value) 被注释的元素必须符合指定的正则表达式

    Hibernate Validator 附加的约束

    Hibernate Validator 附加的 constraint:

    @Email 被注释的元素必须是电子邮箱地址
    @Length 被注释的字符串的大小必须在指定的范围内
    @NotEmpty 被注释的字符串的必须非空
    @Range 被注释的元素必须在合适的范围内


    小结

    扩展链接:
    https://www.jianshu.com/p/2432d0f51c0e

    感谢您的阅读~~

    展开全文
  • WPF学习

    万次阅读 多人点赞 2019-03-05 22:00:17
    首先感谢刘铁锰先生的《深入浅出WPF》,学习WPF过程碰上很多新概念,如Data Binding、路由事件,命令、各种模板等。 WPF:编写CS端的UI技术。 怎么去掉WPF窗体靠上多出黑色的长条?...(我自己觉得偶尔会用用这个) ...

    首先感谢刘铁锰先生的《深入浅出WPF》,学习WPF过程碰上很多新概念,如Data Binding、路由事件,命令、各种模板等。

    WPF:编写CS端的UI技术。

    怎么去掉WPF窗体靠上多出黑色的长条?在vs界面的菜单栏点击调试-选项,把启用XAML的UI调试工具勾选去掉即可。(我自己觉得偶尔会用用这个)

    1   认识WPF

    1.1 新建WPF项目

    生成

    Properties:资源   引用:引用其他类库 App.xmal:程序主体(一个GUI进程需要有一个窗体,App.xmal文件的作用声明了程序的进程,同时指定程序的主窗体),点开app.xaml.cs,它是app.xaml的后台代码。MainWindow1.xmal分支:默认程序的主窗体。

     

    1.2 最简单的XAML代码

    <Window x:Class="WPFTest1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFTest1"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Grid>
            
        </Grid>
    </Window>

    x:Class是当XAML解析器将包含它的标签的解析成C#类的类名。是来自xmlns:x的命名空间。第一行xmlns是默认命名空间。<Window>和<Grid>都来自默认空间。Title是窗体标题,Height是窗体高度,Width是窗体宽度。可以引用CLS的命名空间。

    引用第三方的类库:

    xmlns:common(映射名,自定义)="clr-namespace:Common(类库中名称空间的名字);assembly=MyLibrary(类库名,比如MyLibrary.dll)"
    • xmlns用于在Xaml中声明名称空间的Attribute
    • 冒号的映射名是可选的
    • 引号的字符串确定了哪个类库以及类库哪个名称空间

     

    1.3 XAML文档的树形结构

    <Window x:Class="WPFTest1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFTest1"
            mc:Ignorable="d"
            Title="MainWindow" Height="173" Width="296">
        <StackPanel Background="LightBlue">
            <TextBox x:Name='textBox1' Margin="5"></TextBox>
            <TextBox x:Name='textBox2' Margin="5"></TextBox>
            <StackPanel Orientation="Horizontal">
                <TextBox x:Name="textBox3" Width="140" Margin="5"/>
                <TextBox x:Name="textBox4" Width="120" Margin="5"/>
            </StackPanel>
            <Button x:Name="button1" Margin="5">
                <Image Source="C:\Users\14751\Pictures\Camera Roll\1.png" Width="23" Height="23"/>
            </Button>
        </StackPanel>
    </Window>

     

    树形框架结构如下:

    <Window >
        <StackPanel >
            <TextBox ></TextBox>
            <TextBox></TextBox>
            <StackPanel ">
                <TextBox />
                <TextBox />
            </StackPanel>
            <Button>
                <Image />
            </Button>
        </StackPanel>
    </Window>

    StackPanel可以把内部元素在纵向或横向上紧凑排列、形成栈式布局。也可以用Grid完成上面窗体,代码如下。

    <Window x:Class="WPFTest1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFTest1"
            mc:Ignorable="d"
            Title="MainWindow" Height="173" Width="296">
        <Grid Background="LightSlateGray">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="7*"/>
                <ColumnDefinition Width="3*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="33"/>
                <RowDefinition Height="33"/>
                <RowDefinition Height="33"/>
                <RowDefinition Height="40"/>
            </Grid.RowDefinitions>
             <TextBox x:Name="textBox1" Grid.Column="0" Grid.Row="0"  Grid.ColumnSpan="2" Margin="5"/>
             <TextBox x:Name="textBox2" Grid.Column="0" Grid.Row="1"  Grid.ColumnSpan="2" Margin="5"/>
             <TextBox x:Name="textBox3" Grid.Column="0" Grid.Row="2"  Grid.ColumnSpan="1" Margin="5"/>
             <TextBox x:Name="textBox4" Grid.Column="1" Grid.Row="2"  Grid.ColumnSpan="1" Margin="5"/>
             <Button x:Name="button5" Grid.Column="0" Grid.Row="3"  Grid.ColumnSpan="2" Margin="5">
                <Image Source="C:\Users\14751\Pictures\Camera Roll\1.png" Width="23" Height="23"/>
            </Button>
        </Grid>
    </Window>
    

    WPF的布局原理:以<Window>对象为根节点,一层层向下包含。

    1.4 使用Attribute为对象属性赋值

     <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
            <Rectangle x:Name="rectangle" Width="200" Height="120" Fill="Blue"/>
     </Grid>

    Rectangle类对象Fill属性的类型是Brush,Brush是个抽象类的单色画刷,实际上编译成:

    SoildColorBrush sBrush=new SoildColorBrush();
    sBrush.Color=Color.Blue;
    this.rectangle.Fill=sBrush;
    
    ......
    

    使用TypeConveter类将XAML标签的Attribute与对象的Property进行映射。

    1.5 径向渐变画刷

     <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
            <Ellipse Width="120" Height="120">
                <Ellipse.Fill>
                    <RadialGradientBrush GradientOrigin="0.25,0.25" RadiusX="0.75" RadiusY="0.75">
                        <RadialGradientBrush.GradientStops>
                            <GradientStop Color="White" Offset="0"/>
                            <GradientStop Color="Black" Offset="0.65"/>
                            <GradientStop Color="Gray" Offset="0.8"/>
                        </RadialGradientBrush.GradientStops>
                    </RadialGradientBrush>
                </Ellipse.Fill>
            </Ellipse>
        </Grid>

    由此得出简化XAML的技巧:

    • Attribute=value优于属性元素
    • 充分利用默认值 StartPoint="0,0" EndPoint="1.1"是默认值,可省略
    • 充分利用XAML简写方式,比如LinearGradientBrush.GradientStops的数据类型GradientStopCollection

    1.6 标记扩展

    标记扩展也是为属性赋值,只不过依赖于其他对象的属性值。尽管很方便,只有MarkupExtension类的派生类才能使用标记扩展语法。

        <StackPanel Background="LightSlateGray">
            <TextBlock Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}" Margin="5"/>
            <Slider x:Name="slider1" Margin="5"/>
        </StackPanel>

    属性元素也可以做到,比较复杂:

    <StackPanel Background="LightSlateGray">
            <TextBox  Margin="5">
                <TextBox.Text>
                    <Binding ElementName="slider1" Path="Value" Mode="OneWay"></Binding>
                </TextBox.Text>
            </TextBox>
            <Slider x:Name="slider1" Margin="5"/>
     </StackPanel>

    1.7 事件

          当一个XAML标签对应着对象时,这个标签的一部分Attribute会对应这个对象的Property。

     <Button Name="button1" Click="button1_Click"></Button>

         在window. xaml.cs后面:

    private void button1_Click(object sender, RoutedEventArgs e)
    {
    
    }

        也可以直接在xaml写点击事件:

    <Grid>
            <Button Name="button1" Click="button1_Click"></Button>
    </Grid>
    
    <x:Code>
       <![CDATA[
            private void button1_Click(object sender, RoutedEventArgs e)
            {
    
            }
       ]]>
    </:Code>
    
    
    

    2 常用属性

    2.1 x:classModifier

    定义了XAML编译由标签编译生成的类具有的访问空指类别。internal与private等价。写internal可能编译报错,把window类的声明,把public改成internal。

    2.2 x:Name

    定义了控件名称,不能相同。

    后台获取:

    xaml代码:
    <TextBox x:Name="textBox" Margin="5">
    
    ------
    c#代码:
    string txtName=textBox.Name;
    

    2.3 x:FieldModifier

       控件的访问级别。默认是internal。

    2.4 x:Key

    <Window.Resources>
         <sys:String x:Key="myString">Hello WPF!</sys:String>
    </Window.Resources>
    <StackPanel>
         <TextBox Text="{StaticResource ResourceKey=myString}" Margin="5"/>
    </StackPanel>
    

        使用String类,用xmlns:sys="clr-namespace:System;assembly=mscorlib"引用了mscorlib.dll,把System名称映射为sys名称空间。

    string str=this.FindResource("myString") as string;
    this.textBox1.Text=str;

    2.5 x:Shared

             把上面的x:Key当作资源放进资源字典后,需要设定检索的对象是同一个对象还是多个副本。

    2.6 x命名空间中的标记扩展

       2.6.1 x:Type

           把某些对象资源放进资源字典里的数据类型。

    mywindow.xaml
    <StackPanel Background="LightBlue">
            <TextBox Margin="5"></TextBox>
            <TextBox Margin="5"></TextBox>
            <TextBox Margin="5"></TextBox>
            <Button Content="OK" Margin="5"/>
    </StackPanel>
    
    MyButton类
     class MyButton:Button
        {
            public Type UserWindowType { get; set; }
            protected override void OnClick()
            {
                base.OnClick();
                Window win = Activator.CreateInstance(this.UserWindowType) as Window;
                if (win != null)
                {
                    win.ShowDialog();
                }
            }
        }
    
    MainWindow.xaml
      <StackPanel>
            <local:MyButton Content="Show" UserWindowType="{x:Type TypeName=local:mywindow}" Margin="5"/>
        </StackPanel>

     2.6.2 x:Null

    显式地对一个属性赋一个空值。

    <Buttob Content="OK" Style="{x:Null}">

    下面一个例子,把一个Style放在window的资源里,并把它的x:Key和TargetType设置了Button类型,除了最后一个Button

    <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
         <Setter Property="Width" Value="60"/>
    </Style>
    
    <StackPanel>
         <Button Content="Ok"/>
         <Button Content="Ok" Style="{x:Null}"/>
    </StackPanel>
    

    也可以写成

     <Button Content="Ok" >
           <Button.Style>
                  <x:Null/>
           </Button.Style>
     </Button>

    2.6.3 x:Array

    在WPF把包含的数据的对象称为数据源,如果把一个x:Array的实例作为数据源提供给一个ListBox。

    <ListBox Margin="5">
       <ListBox.ItemsSource>
             <x:Array Type="sys:String">
                  <sys:String>Tim</sys:String>
                  <sys:String>Tom</sys:String>
                  <sys:String>Victor</sys:String>
             </x:Array>
       </ListBox.ItemsSource>
    </ListBox>
    

    2.6.3 x:Static

    x:Static是一个很常用的标记扩展。功能是在XAML文档中使用数据类型的static成员。

    pubic static string WindowTitle="山高月小";
    
    public static string  ShowText{get{return "水落石出";}}
    
    
    <Window x:Class="Window1">
       -----
       Title="{x:Static local:Window1.WindowTitle}"  Height="120" Width="300">
       <StackPanel>
          <TextBlock FontSize="32" Text="{x:Static local:Window1.ShowText}" />
       </StackPanel>
    
    </Window>
    

    2.7 XAML指定元素

     2.7.1 x:Code

     可以让XAML包含一些本应放置在后置代码的C#代码。

     2.7.2 x:XData

    <Window.Resources>
       <XmlDataProvider x:Key="InventoryData" XPath="Inventory/Books">
           <x:XData>
                 <Supermarket xmlns="">
                       <Fruits>
                            <Fruit Name="Peach"/>
                            <Fruit Name="Banana"/>
                            <Fruit Name="Orange"/>
                       </Fruits>
                       <Drinks>
                            <Drink Name="Coca"/>
                            <Drink Name="PEPSI"/>
                       </Drinks>
                 </Supermarket>
           </x:XData>
       </XmlDataProvider>
    </Window.Resources>
    

     3 控件

      我们把符合某类内容模型的UI元素称为一个族。以下是各种的内容模型。

      3.1 ContentControl

     单一内容控件。共同点:

    • 派生自ContentControl
    • 内容属性的名称为Content
    • 只能由单一元素充当其内容
    <StackPanel>
         <Button Margin="5"> 
              <TextBlock Text="Hello" />
         </Button>
         <Button Margin="5"> 
              <Image Source=".\smile.png" Width="30" Height="30"/>
         </Button>
    </StackPanel>

       想让Button的内容既包含文字又包含图片是不行的。

    ContentControl族包含的控件
    ButtonButtonBaseCheckBoxComboBoxItem
    ContentControlFrameGridViewColumnHeaderGroupItem
    LableListBoxItemListViewItemNavigationWindow
    RadioButtonRepeatButtonScrollViewerStatusBarItem
    TpggleButtonToolTipUserControlWindow

    3.2 ItemsControl

    • 均派生自ItemsControl类。
    • 用于显示列表化的数据
    • 内容属性为Item或ItemsSource
    • 每种ItemsControl都对应有自己的条目容器(ItemContainer)
    ItemsControl族所包含的控件
    MenuMenuBaseContextMenuComboBox
    ItemsControlListBoxListViewTabControl
    TreeViewSelectorStatusBar 
    <Grid>
         <ListBox Margin="5">
              <CheckBox x:Name="checkBoxTim"  Content="Tim"/>
              <CheckBox x:Name="checkBoxTom"  Content="Tom"/>
              <CheckBox x:Name="checkBoxBruce"  Content="Bruce"/>
              <Button x:Name="buttonMess"  Content="Mess"/>
              <Button x:Name="buttonOwen"  Content="Owen"/>
              <Button x:Name="buttonVictor"  Content="Victor"/>
         </ListBox>
    </Grid>
    

    当我们尝试着去寻找窗体或者页面中某个控件的子控件或者父控件的时候,可以用VisualTreeHelper.GetChild()和VisualTreeHelper.GetParent():

    XAML:
    <Button x:Name="buttonVictor" Content="Victor" Click="buttonVictor_Click"/>
    
    C#:
    
    private void buttonVictor_Click(object sender,RoutedEventArgs e)
    {
        Button btn=sender as Button;
        DependencyObject level1=VisualTreeHelper.GetParent(btn);
        DependencyObject level2=VisualTreeHelper.GetParent(level1);
        DependencyObject leve13=VisualTreeHelper.GetParent(level2);
        MessageBox.Show(level3.GetType().ToString());
    }
    

    后台绑定数据时:

    程序添加Employee类:
    public Class Employee
    {
         public int id{get;set;}
         public string Name{get;set;}
         public int Age{get;set;}
    }
    有一个Employee类型的集合:
    List<Employee> empList=new List<Employee>(){
         new Employee(){Id=1,Name="Tim",Age=30},
         new Employee(){Id=2,Name="Tom",Age=26},
    }
    绑定控件名为listBoxEmplyee的ListBox:
    this.listBoxEmplyee.DisplayMemberPath="Name";//(显示绑定对象的哪个属性)
    this.listBoxEmplyee.SelectedValuePath="Id";//(SelectedValuePath属性将与其SeletedValue属性配合。当调用SeletedValue,ListBox先找到选中的Item所对应的数据对象,然后根据SelectedValuePath的值当作数据对象的属性名称并把这个属性取出来,类似键值对)
    this.listBoxEmplyee.ItemsSource=empList;
    
    

    DispalyMemberPath只能显示简单的字符串,更复杂的形式显示数据需要使用DataTemplate,SelectedValuePath只能返回单一值,如果想进行一些复杂的操作的,使用ListBox的SelectedItem和SelectedItems属性。

    无论把什么数据集合给ListBox,它都会自动包装成ListBoxItem,ListBoxItem就是ListBox对应的ItemContainer。ComboBox对应的是ComboBoxItem,ListBox对应的时ListBoxItem,Menu对应的时MenuItem.....

    3.3 HeaderedItemsControl族

    除了具有ItemsControl的特性外,还能显示标题。

    • 派生至HeaderedItemsControl类
    • 显示列表化的数据,还可以显示标题
    • 内容属性为Items、ItemsSource和Header

    三个控件:MenuItem、TreeViewItem、ToolBar

    3.4 Decorator元素

    窗体装饰效果,比如可以用Border元素给内容加边框,用ViewBox使内容可以自由缩放。

    • 派生自Decorator类
    • UI装饰作用
    • 内容属性为Child
    • 只能由单一元素充当内容
    Decorator族元素
    ButtonChromeClassicBorderDecoratorListBoxChromeSystemDropsShadowChrome
    BorderInkPresenterBulletDecoratorViewBox
    AdornerDecorator   

    3.5 TextBlock和TextBox

             TextBlock只能显示文本,不能编辑,TextBox能编辑。

    3.6 Shape族

    在UI上绘制图形。比如使用Fill属性设置填充效果,使用Stroke属性设置边线效果

    • 派生自Shape类
    • 2D图形绘制
    • 无内容属性

    3.7 Panel族

    所有UI布局的元素都属于这一族。

    • 派生自Panel抽象类
    • 控制UI布局
    • 内容属性为Children
    • 内容可以为多个元素,Panel元素将控制它们布局

    ItemsControl和Panel虽然内容都可以是多个元素,而前者强调的以列表形式展现数据,后者强调的是对包含的元素进行布局。

    Panel族元素
    CanvalDockPanelGridTabPanel
    ToolBarOverflowPanelStackPanelToolBarPanelUniformGrid
    VirtualizingPanelVistualizingStackPanelWrapPanel 

    4 布局(Layout)

    WPF设计师工作量最大的两部分就是布局与动画,除了点缀性的动画外,大部分动画是布局之间转换。选择合适的布局元素,将会极大地简化编程。

    4.1 布局元素

    布局元素属于Panel族,内容属性为Children ,即可以接受多个控件作为自己的内容并对这些控件进行布局控制。WPF布局理念就是把一个布局元素作为ContentControl或HeaderedContentControl族控件的Contont,再在布局元素里添加要被布局的子集控件

    WPF中的布局元素:

    • Grid:网格,可以自定义行和列并通过行列的数量、行高的列宽来调整控件的布局
    • StackPanel:栈式面板,可以将包含的元素在竖直或水平方向上排成一条线,当移除一个元素,后面的元素会自动向前移动以填充空缺。
    • Canvas:画布,内部元素可以使用以像素为单位的绝对坐标进行定位
    • DockPanel:泊靠式面板,内部元素可以选择泊靠方向
    • WrapPanel:自动折行面板,内部元素在拍满一行后自动折行

    4.2 Grid

    以网格的形式对内容元素们进行布局。

    •  定义任意数量的行和列
    • 行的高度和列的宽度可以使用绝对数值、相对比例或自动调整的方式进行精确设定,并可设置最大和最小值
    • 内部元素可以设置自己所在的行和列,还可以设置自己跨几行,横向跨几列
    • 可以设置Children元素的对齐方向

    Grid适用的场景:

    • UI布局的大框架设计
    • 大量元素需要成行或者成列对齐的情况
    • UI整体尺寸改变时,元素需要保持固有的高度和宽度比例
    • UI后期可能有较大变更或扩展

     4.2.1  定义Grird的行与列

    Grid类具有ColumnDefinitions和RowDefinitions两个属性。表示Grid定义了多少列和多少行。

    <Grid>
       <Grid.ColumnDefinitions>
           <ColumnDefinition/>
           <ColumnDefinition/>
           <ColumnDefinition/>
           <ColumnDefinition/>
       </Grid.ColumnDefinitions>  
       <Grid.RowDefinitions>
           <RowDefinition/>
           <RowDefinition/>
           <RowDefinition/>
       </Grid.RowDefinitions>
    </Grid>
    
    

    当你把鼠标指针在Grid的边缘上移动时会出现提示线,一旦你单击鼠标则会在此添加一条分隔线、创建出新的行和列。也可以在c#动态添加行和列。

    private void Window_Loaded(object sender,RoutedEventArgs e)
    {
       this.gridMain.ColumnDefinitions.Add(new ColumnDefinition());
       this.gridMain.ColumnDefinitions.Add(new ColumnDefinition());
       this.gridMain.ColumnDefinitions.Add(new ColumnDefinition());
       this.gridMain.ColumnDefinitions.Add(new ColumnDefinition());
    
    
       this.gridMain.RowDefinitions.Add(new RowDefinition());
       this.gridMain.RowDefinitions.Add(new RowDefinition());
       this.gridMain.RowDefinitions.Add(new RowDefinition());
       this.gridMain.ShowGridLines=true;
    }

    4.2.2 设置行高和列宽

     计算机图形设计的标准单位时像素(Pixel),所以Grid的宽度和高度单位是像素。

     对于Grid的行高和列宽,可以设置类值:

    • 绝对值:double数值加单位后缀
    • 比例值:double数值后加一个星号
    • 自动值:字符串Auto

     绝对值:一经设定就不会再改变

     比例值:整体尺寸改变时,保持固有比例

     自动值:行高或列宽的实际值将由行列内控件的高度和宽度决定,控件会把行列 撑到合适的宽度和高度。

     例子:

      <Grid Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" MinWidth="120"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="80"/>
                <ColumnDefinition Width="4"/>
                <ColumnDefinition Width="80"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition Height="4"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="4"/>
                <RowDefinition Height="25"/>
            </Grid.RowDefinitions>
            <TextBlock Text="请选择您的部门并留言:"    Grid.Column="0" Grid.Row="0" VerticalAlignment="Center"/>
            <ComboBox Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="4"/>
            <TextBox Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="5" BorderBrush="Black"></TextBox>
            <Button Content="提交" Grid.Column="2" Grid.Row="4"/>
            <Button Content="清除" Grid.Column="4" Grid.Row="4"/>
        </Grid>
    • 行和列都是从0开始计数
    • Grid.Row="行编号",若行编号为0,则可忽略,列同样如此
    • 跨行跨列,用Grid.RowSpan="行数" 和Grid.ColumnSpan="列数"
    • 如果两个元素放在Grid,则后写的元素会覆盖先写的,要前面的显示,可把后面写的visibility设置为Hidden或Collapsed,也可以把上面的元素的Opacity属性设置为0

    可拖拽的分隔栏例子:

     <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBox Grid.ColumnSpan="3" BorderBrush="Black"/>
            <TextBox Grid.Row="1" BorderBrush="Black"/>
            <GridSplitter Grid.Row="1" Grid.Column="1"
                          VerticalAlignment="Stretch"
                          HorizontalAlignment="Center"
                          Width="5"
                          Background="Gray"
                          ShowsPreview="True"
                          ></GridSplitter>
            <TextBox Grid.Row="1" Grid.Column="2" BorderBrush="Black"/>
    </Grid>

    4.3 StackPanel

    StackPanel可以把内部元素再纵向或横向紧凑排列,形成栈式布局。类似垒积木。适用以下场景

    • 同样的元素需要紧凑排列
    • 移除其中的元素能够自动补缺的布局或动画
    StackPanel三个属性
    OrientationHorizontal、Vertical决定内部元素是横向累积还是纵向累积
    HorizontalAlignmentLeft、Center、Right、Stretch决定内部元素水平方向的对齐方式
    VerticalAlignmentTop、Center、Buttom、Stretch决定内部元素竖直方向的对齐方式

    例子:

     <Grid>
            <GroupBox Header="请选择没有错别字的成语" BorderBrush="Black" Margin="5">
                <StackPanel Margin="5">
                    <CheckBox Content="A、迫不及待"/>
                    <CheckBox Content="B、首曲一指"/>
                    <CheckBox Content="C、陈腔滥调"/>
                    <CheckBox Content="D、唉声叹气"/>
                    <CheckBox Content="E、不可礼喻"/>
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                        <Button Content="清空" Width="60" Margin="5"/>
                        <Button Content="确定" Width="60" Margin="5"/>
                    </StackPanel>
                </StackPanel>
            </GroupBox>
     </Grid>

    4.4 Canvas

    画布,使用Canvas布局与在Windows Form窗体上布局基本一样的。当控件被放置在Canvas就会被附上Canvas.X和Canvas.Y。

    <Canvas>
       <TextBlock Text="用户名:" Canvas.Left="12" Canvas.Top="12"> 
    </Canvas>

    4.5 DockPanel

    DoclPanel内的元素会被附上DockPanel.Dock这个属性,这个属性的数据类型为Dock枚举。Dock枚举可取Left、Top、Right和Bottom四个值。例子:

     <Grid>
            <DockPanel>
                <TextBox DockPanel.Dock="Top" Height="25" BorderBrush="Black"/>
                <TextBox DockPanel.Dock="Left" Width="150" BorderBrush="Black"/>
                <TextBox BorderBrush="Black"/>
            </DockPanel>
    </Grid>

    4.6 WrapPanel

    wrappanel内部采用的是流式布局。WrapPanel使用Orientation属性来控制流延伸的方向,使用HorizontalAlignment和VerticalAlignment两个属性控制内部控件的对齐。在流延伸的方向上,WrapPenl会排列尽可能多的控件。

    例子:

    <WrapPanel>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
    </WrapPanel>

    5 DataBinding

    5.1 Binding基础

    展示层与逻辑层的沟通就使用Data Binding。更新变化是通过类实现INotifyPropertyChanged接口并在属性的set语句中激发PropertyChanged事件。

    例子:

    MainWindow.xmal

    <StackPanel>
            <TextBox x:Name="textBoxName" BorderBrush="Black" Margin="5" />
            <Button Content="Add Age" Margin="5" Click="Button_Click"/>
    </StackPanel>

    Student.cs类

     class Student:INotifyPropertyChanged
        {
            public  event PropertyChangedEventHandler PropertyChanged;
            private string name;
            public  string Name
            {
                get { return name; }
                set
                {
                    name = value;
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
                    }
                }
            }
      }

    MainWindow.xaml.cs

        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            Student stu;
            public MainWindow()
            {
                InitializeComponent();
                //stu = new Student();
                //Binding binding = new Binding();
                //binding.Source = stu;
                //binding.Path = new PropertyPath("Name");
                //BindingOperations.SetBinding(this.textBoxName, TextBox.TextProperty, binding);
                this.textBoxName.SetBinding(TextBox.TextProperty, new Binding("Name") { Source=stu=new Student()});
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                stu.Name += "Name";
            }
        }
    }
    

    5.2 Binding的源与路径

      Binding的源就是数据源头——对象,通过属性公开自己的数据。

    5.2.1 把控件作为Binding源与Binding标记扩展

    例子:

    xaml实现:

    <StackPanel>
            <TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=slider1}" BorderBrush="Black" Margin="5"/>
            <!--Binding Path=Value也常简写成Binding Value-->
            <Slider x:Name="slider1" Maximum="100" Minimum="0" Margin="5" />
    </StackPanel>

    与之等价的C#代码:

    this.textBox1.setBinding(TextBox.TextProperty,new Binding("Value"){ElementName="slider1"});

    5.2.2 控制Binding的方向及数据更新

    控制数据流向的属性的是Mode,BindingMode可取值为TwoWay、OneWay、OnTime、OnWayToSource和Default(根据实际情况确定,比如可编辑的(TextBox.Text)属性,采用双向模式。只读(TextBlock.Text)则单向)。

    上面的例子显示的Text会跟随silder改变,输入Text失去焦点而silder也会改变。失去焦点改变的原因是Binding的属性-UpdateSourceTrigger,类型是UpdateSourceTrigger枚举,可取值为PropertChanged、LostFocus、Explict和Default。默认Default是LostFocus。改成PropertChange,输入时silder就会改变。Binding还有NotifyOnSourceUpdated和NotifyOnTarget两个Bool类型属性,当更新会触发SourceUpdate事件和TargetUpdate事件,通过监听这两个事件找出哪些数据和控件被更新了。

    5.2.3 Binding的路径(Path)

    Binding的Path属性指定关联对象哪个属性。前面的例子等效于:

    Binding binding=new Binding(){Path=new Property("Value"),Source=this.slider1};
    this.textBox1.SetBinding(TextBox.TextProperty,binding);

    或者Binding构造器:

    Binding binding=new Binding("Value"){Source=this.slider1};
    this.textBox1.SetBinding(TextBox.TextProperty,binding);

    支持多级路径,例子(一个TextBox显示另外一个TextBox长度):

    <StackPanel>
            <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textBox2" Text="{Binding Path=Text.Length,ElementName=textBox1,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
     </StackPanel>

    等效C#代码:

    this.textBox2.SetBinding(TextBox.TextPropery,new Binding("TextLength"){Source=this.textBox1,Mode=BindingMode.OnWay});

    例子(索引器作为Path示例):

    <StackPanel>
            <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textBox2" Text="{Binding Path=Text.[3],ElementName=textBox1,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
     </StackPanel>
    等效于c#代码:
    this.textBox2.SetBinding(TextBox.TextPropery,new Binding("Text.[3]"){Source=this.textBox1,Mode=BindingMode.OnWay});
    
    <!--Text与[3]可省略-->

     

    例子(使用集合作为Binding源):

    List<string> stringList = new List<string>() { "Tim", "Tom", "Blog" };
    this.textBox1.SetBinding(TextBox.TextProperty, new Binding("/") { Source=stringList});
    this.textBox2.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList ,Mode=BindingMode.OneWay});
    this.textBox3.SetBinding(TextBox.TextProperty, new Binding("/[2]") { Source = stringList, Mode = BindingMode.OneWay });

    例子(使用集合的子集合作为Binding源,一路斜线下去):

     

    List<Country> countryList = new List<Country>() {  };
    Country country=new Country();
    country.Name = "中国";
    Province province = new Province();
    province.Name = "贵州";
    City city = new City();
    city.Name = "贵阳";
    List < City > listCity=new List<City>();
    listCity.Add(city);
    province.CityList = listCity;
    List<Province> listProvince = new List<Province>();
    listProvince.Add(province);
    country.ProvinceList = listProvince;
    countryList.Add(country);
    this.textBox1.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source = countryList });
    this.textBox2.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") { Source = countryList});
    this.textBox3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = countryList});
             

    5.2.4 当path属性等于"."

         Binding源就是数据且不需要Path指明,类似string、int等基本类型,他们实例就是本身数据。无法指定通过它哪个属性来范文这个数据,只需将path的值设置为"."。

        例子:

        

    注意,得引用xmlns:sys="clr-namespace:System;assembly=mscorlib"命名空间。

    <Window x:Class="WpfTest16.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfTest16"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            mc:Ignorable="d"
            Title="MainWindow" Height="150" Width="220">
        <StackPanel >
            <StackPanel.Resources>
                <sys:String x:Key="myString">
                      菩提本无树,明镜亦非台
                      本来无一物,何处惹尘埃
                </sys:String>
                
            </StackPanel.Resources>
            <TextBox x:Name="textBox1" TextWrapping="Wrap" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/>
        </StackPanel>
    </Window>

    上面的代码可以简写成:

    Text="{Binding .,Source={StaticResource ResourceKey=myString}}"
    或
    Text="{Binding Source={StaticResource ResourceKey=myString}}"
    或c#:
    string myString="菩提本无树....."
    this.textBlock1.SetBinding(TextBlock.TextProperty,new Binding("."){Source=myString});
    

    5.2.5 为Binding指定的几种源数据

    • 普通的CLR类型单个对象。
    • 普通的CLR集合类型。比如数组、List<T>、ObservableCollection<T>等集合类型
    • ADO.NET集合类型对象。比如DataTable和DataView等。
    • 使用XmlDataProvider的XML数据
    • 依赖对象,依赖对象的依赖属性可以作为Binding的Path
    • 容器的DataContext
    • 通过ElementName
    • 通过Binding的RelativeSource属性
    • ObjectDataProvider对象
    • 使用LINQ检索的数据对象

    5.2.6 DataContext-没有Source的Binding

    Binding沿着UI元素树往上走寻找DataContext对象并把它作为Source。之所以向上查找,就是因为依赖属性,当没有为控件的某个依赖属性显示赋值时,控件会把自己容器的属性值“借过来”当作自己的属性值。但实际上属性值沿着UI元素树向下传递了。

    例子:

     public class Student
    {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
    }
    <Window x:Class="WpfTest17.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfTest17" //必须引用这个,才能使用Student类
            mc:Ignorable="d"
            Title="MainWindow" Height="135" Width="300">
        <StackPanel Background="LightBlue">
            <StackPanel.DataContext>
                <local:Student Id="6" Age="29" Name="Tim"/>
            </StackPanel.DataContext>
            <Grid>
                <StackPanel>
                    <TextBox Text="{Binding Path=Id}" Margin="5"/>
                    <TextBox Text="{Binding Path=Name}" Margin="5"/>
                    <TextBox Text="{Binding Path=Age}" Margin="5"/>
                </StackPanel>
            </Grid>
        </StackPanel>
    </Window>
    

    例子,没有指定Path和Source情况下:

     <StackPanel Background="LightBlue">
            <StackPanel.DataContext>
                <sys:String>Hello DataContent!</sys:String>
            </StackPanel.DataContext>
            <Grid>
                <StackPanel>
                    <TextBlock Text="{Binding}" Margin="5"/>
                    <TextBlock  Text="{Binding}" Margin="5"/>
                    <TextBlock Text="{Binding}" Margin="5"/>
                </StackPanel>
            </Grid>
    </StackPanel>

    比如最外层Grid有DataContext,内层的Button都没有设置DataContext,所以最外层的DataContext会传递到Button那里去。

    <Grid DataContext>
    <Grid>
    <Grid>
       <Button x:Name="btn" Content="OK" Click="btn_Click"/>
    </Grid>
    </Grid>
    </Grid>
    private void btn_click(object sender,RoutedEventArgs e)
    {
       MessageBox.Show(btn.DataContext.toString());
    }

    适合应用场景:

    • 多个控件Binding同一个对象
    • 作为Source的对象不能直接访问,比如B控件Binding A控件,但A控件是private访问级别

    5.2.7 使用集合对象作为列表控件的ItemsSource

    为每一个ItemsSource设置了ItemSource属性值,ItemsControl会自动更新迭代的数据元素,为每个数据元素准备一个条目容器。并使用Binding在条目容器与数据元素之间立起关联。

    例子:

     <StackPanel x:Name="stackPanel"  Background="LightBlue">
            <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/>
            <TextBox x:Name="textBoxId" Margin="5"/>
            <TextBlock Text="Student List;"  FontWeight="Bold" Margin="5" />
            <ListBox x:Name="listBoxStudents" Height="110" Margin="5"/>
    </StackPanel>
               //准备数据源
     List<Student> stuList = new List<Student>()
    {
                    new Student(){Id=0,Name="Tim",Age=29},
                    new Student(){Id=1,Name="Tom",Age=28},
                    new Student(){Id=2,Name="Kyle",Age=27},
                    new Student(){Id=3,Name="Toney",Age=26}
     };
      this.listBoxStudents.ItemsSource = stuList;
      this.listBoxStudents.DisplayMemberPath = "Name";
      Binding binding = new Binding("SelectedItem.Id") { Source = this.listBoxStudents };
      this.textBoxId.SetBinding(TextBox.TextProperty, binding);

    去掉DisplayMemberPath,把xaml改成:

    <StackPanel x:Name="stackPanel"  Background="LightBlue">
            <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/>
            <TextBox x:Name="textBoxId" Margin="5"/>
            <TextBlock Text="Student List;"  FontWeight="Bold" Margin="5" />
            <ListBox x:Name="listBoxStudents" Height="110" Margin="5">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Path=Id}" Width="30"/>
                            <TextBlock Text="{Binding Path=Name}" Width="30"/>
                            <TextBlock Text="{Binding Path=Age}" Width="30"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
     </StackPanel>

    注意:使用集合类型作为列表控件的ItemsSource一般考虑ObservableCollection<T>,因为接口实现了INotifyCollectionChanged和INotifyPropertyChange,能把集合的变化通知显示的控件。

    5.2.8 使用ADO.NET对象作为Binding的源

    使用ADO.NET类对数控库操作,尽管在流行的软件并不把DataTable的数据直接显示UI列表控件里面而是先通过LINQ等手段把DataTable里的数据转换成恰当的用户自定义类型集合。

    例子:

    XAML:
    <StackPanel Background="LightBlue">
        <ListBox x:Name="listBoxStudents" Height="130" Margin="5"/>
        <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
    C#
    private void Button_Click(object sender,RoutedEventArgs e)
    {
         DataTable dt=this.Load();
         this.listBoxStudents.DisplayMemberPath="Name";
         this.listBoxStudents.ItemsSource=dt.DefaultView;
    }
    

    DefaultView属性是DataView类型的对象,DataView类实现了IEnumerable接口,所以可以赋值给ListBox.ItemsSource属性。注意ListView.View是一个ViewBase的对象,ListView是ListBox的派生类,不能当独立控件使用,而GridView是ViewBase派生类,可以当独立控件。

    <StackPanel Background="LightBlue">
        <ListView x:Name="listViewStudents" Height="130" Margin="5">
           <ListView.View>
              <GridView>
                    <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}" />
                    <GridViewColumn Header="Name" Width="60"DisplayMemberBinding="{Binding Name}" />
                    <GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}" />           
               </GridView>
           </ListView.View>
        </ListView>
        <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
    

    C#代码:

    private void Button_Click(object sender,RoutedEventArgs e)
    {
         DataTable dt=this.Load();
         this.listViewStudents.ItemsSource=dt.DefaultView;//不能直接使用dt
         /**
         也可以使用:
         this.listViewStudents.DataContext=dt;
         this.listViewStudents.SetBinding(ListView.ItemsSourceProperty,new Binding());
        **/
    }

    GridView的内容属性是Columns,因为XAML支持对内容属性的简写,所以省略了<GridView.Columns>....</GridView.Columns>,直接在<GridView>内容部分定义了GridViewColumn对象,GridViewColumn对象一个属性DisplayMemberBinding,使用它关联数据,而ListBox则使用DisplayMemberPath。如果想要显示更复杂的标题或数据,将GridViewColumn设置HeaderTemplate和CellTemplate。它们都是DataTemplate。

    5.2.9 使用XML数据作为Binding的源

    .NET Framework提供了两套处理XML数据的类库。

    • 符合DOM标准的类库:XmlDocument、XmlElement、XmlNode、XmlAttribute等等
    • LINQ为基础的类库:包括XDocument、XElement、XNode、XAttribute等等,可以使用LINQ进行查询和操作

    XML的文本是树形结构,方便用于线性集合(Array,List)和树形结构数据,因为大多数据传输都基于SOAP(通过对象序列化为XML文本进行传输)相关协议。XML使用的XPath属性而不是Path指定数据来源

    例子:

    RawData.xml文档:

    <?xml version="1.0" encoding="utf-8" ?>
    <StudentList>    
       <Student Id="1">         
        <Name>Tim</Name>     
       </Student >    
       <Student Id="2">        
        <Name>Tom</Name>     
       </Student >    
       <Student Id="3">         
        <Name>Vina</Name>     
       </Student >     
       <Student Id="4">         
        <Name>Emily</Name>     
       </Student >
    </StudentList>
    <StackPanel Background="LightBlue">
            <ListView x:Name="listViewStudents" Height="130" Margin="5">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath=@Id}" />
                        <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding XPath=Name}" />
                    </GridView>
                </ListView.View>
            </ListView>
            <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
    private void Button_Click(object sender, RoutedEventArgs e)
    {
                XmlDocument doc = new XmlDocument();
                doc.Load(@"D:\文本\RawData.xml");            
              
                XmlDataProvider xdp = new XmlDataProvider();
                xdp.Document = doc;
                xdp.XPath = @"/StudentList/Student";
                this.listViewStudents.DataContext = xdp;
                this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
    }
    
    ==
    private void Button_Click(object sender, RoutedEventArgs e)
    {
                XmlDataProvider xdp = new XmlDataProvider();
                xdp.Source =new Uri(@"D:\文本\RawData.xml");
                xdp.XPath = @"/StudentList/Student";
                this.listViewStudents.DataContext = xdp;
                this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
    }

    @Id和Name指明了关注的路径,加@符号表示的是XML元素的Attribute,不加@符号的字符串表示自己元素

    例子:使用XML作为数据源显示TreeView控件的若干层目录的文件系统(把XML数据和XmlDataProvider对象写在XAML代码)

    <Window.Resources>
            <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder">
                <x:XData>
                    <FileSystem xmlns="">
                        <Folder Name="Books">
                            <Folder Name="Programming">
                                <Folder Name="Windows">
                                    <Folder Name="WPF"/>
                                    <Folder Name="MFC"/>
                                    <Folder Name="Delphi"/>
                                </Folder>
                            </Folder>
                            <Folder Name="Tools">
                                    <Folder Name="Development"/>
                                    <Folder Name="Designment"/>
                                    <Folder Name="Players"/>
                            </Folder>
                        </Folder>
                    </FileSystem>
                </x:XData>
            </XmlDataProvider>
        </Window.Resources>
        <Grid>
            <TreeView ItemsSource="{Binding Source={StaticResource xdp}}">
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}">
                        <TextBlock Text="{Binding XPath=@Name}"/>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </Grid>

    5.2.10 使用LINQ检索结果作为Binding的源

    3.0以上的.NET FrameWork开始支持LINQ。使用LINQ可以很方便操作集合对象、DataTable对象和XML对象。

    例子(存储在List集合,查找Name为'T'开头的):

    <StackPanel Background="LightBlue">
            <ListView x:Name="listViewStudents" Height="143" Margin="5">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding Id}" />
                        <GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding Name}" />
                        <GridViewColumn Header="Age" Width="80" DisplayMemberBinding="{Binding Age}" />
                    </GridView>
                </ListView.View>
            </ListView>
            <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
    private void Button_Click(object sender, RoutedEventArgs e)
    {
                List<Student> stuList = new List<Student>()
                {
                    new Student(){Id=0,Name="Tim",Age=29},
                    new Student(){Id=0,Name="Tom",Age=28},
                    new Student(){Id=0,Name="Kyle",Age=27},
                    new Student(){Id=0,Name="Toney",Age=26},
                    new Student(){Id=0,Name="Vina",Age=25},
                    new Student(){Id=0,Name="Mike",Age=24}
                };
                this.listViewStudents.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") select stu;
    }

    存储在DataTable:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
                DataTable dt=this.GetDataTable();
                this.listViewStudents.ItemsSource = from row in dt.Rows.Cast<DataRow>() where Convert.ToString(row["Name"]).StartsWith("T") select new Student{
                   Id=int.Parse(row["Id"].toString()),
                   Name=row["Name"].toString(),
                   Age=int.Parse(row["Age"].toString())
                };
    }

    存储XML(D:\RawData.xml):

    <!--RawData.xml-->
    <?xml version="1.0" encoding="utf-8"?>
    <StudentList>
      <Class>
       <Student Id="0" Name="Tim" Age="29"/>
       <Student Id="1" Name="Tom" Age="28"/> 
       <Student Id="2" Name="Mike" Age="27"/> 
      </Class>
      <Class>
       <Student Id="3" Name="Tony" Age="26"/>
       <Student Id="4" Name="Vina" Age="25"/> 
       <Student Id="5" Name="Emily" Age="24"/> 
      </Class>
    </StudentList>
    
    <!--C#-->
     XDocument xdoc=XDocument.Load(@"D:\RawData.xml");
     this.listViewStudents.ItemsSource = from element in xdoc.Descendants("Student") where element.Attribute("Name").StartsWith("T") select new Student{
                   Id=int.Parse(element.Attribute("Id").Value),
                   Name=element.Attribute("Name").Value,
                   Age=int.Parse(lement.Attribute("Age").Value)
      };
    
    

    5.2.11 使用ObjectDataProvider作为Binding的源

    有时需要方法的返回值,这时需要使用ObjectDataProvider包装Binding源的对象。ObjectDataProvider是把对象作为数据源提供给Binding,是包装一个以方法暴露数据的对象

    例子(加减乘除):

    <!--xmal-->
    <Grid>
      <Button Content="OK" Click="Button_Click" Width="200" Height="200"></Button>
    </Grid>
    <!--c#-->
    private void Button_Click(object sender, RoutedEventArgs e)
    {
                ObjectDataProvider odp = new ObjectDataProvider();
                odp.ObjectInstance = new Calculator();
                odp.MethodName = "Add";
                odp.MethodParameters.Add("100");
                odp.MethodParameters.Add("200");
                MessageBox.Show(odp.Data.ToString());
    }

    例子:

    <StackPanel Background="LightBlue">
            <TextBox x:Name="textBox1Arg1" Margin="5"/>
            <TextBox x:Name="textBox1Arg2" Margin="5"/>
            <TextBox x:Name="textBox1Result" Margin="5"/>
    </StackPanel>
     public MainWindow()
    {
         InitializeComponent();
         this.SetBinding();
    }
     private void SetBinding()
    {
         ObjectDataProvider odp = new ObjectDataProvider();
         odp.ObjectInstance = new Calculator();
         odp.MethodName = "Add";
         odp.MethodParameters.Add("0");
         odp.MethodParameters.Add("0");
         Binding bindingToArg1 = new Binding("MethodParameters[0]")
         {
              Source = odp,
              BindsDirectlyToSource = true,
              UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged      
         };
         Binding bindingToArg2= new Binding("MethodParameters[1]")
         {
              Source = odp,
              BindsDirectlyToSource = true,
              UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
         };
         Binding bindingToResult = new Binding(".") { Source = odp };
         this.textBox1Arg1.SetBinding(TextBox.TextProperty, bindingToArg1);
         this.textBox1Arg2.SetBinding(TextBox.TextProperty, bindingToArg2);
         this.textBox1Result.SetBinding(TextBox.TextProperty, bindingToResult);                   
    }       

    ObjectDataProvider对象作为Source,但使用"."作为Path,当数据源源本身代表数据就是要"."作path,也可省略。实际上三个TextBox都以ObjectDataProvider对象为数据源,只是前两个TextBox在Binding的数据流向做了限制。理由是:

    • ObjectDataProvider的MethodParameters不是依赖属性,不能作为Binding的目标。
    • 数据驱动的UI理念尽可能使用数据对象作为Binding的Source而把UI元素Binding的Target。

    5.2.12 使用Binding的RelativeSource

    控件关联自己某个数据、关联自己某级容器的数据。

    例子:

    <Grid x:Name="g1" Background="Red" Margin="10">
        <DockPanel x:Name="d1" Background="Orange" Margin="10">
             <Grid x:Name="g2" Background="Yellow" Margin="10">
                <DockPanel x:Name="d2" Background="LawnGreen" Margin="10">
                  <TextBox x:Name="textBox1" FontSize="24" Margin="10"></TextBox>
             </DockPanel>
        </Grid>
    </Grid>
    RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor);
    rs.AncestorLevel = 1;
    rs.AncestorType = typeof(Grid);
    Binding binding = new Binding("Name") { RelativeSource = rs };
    this.textBox1.SetBinding(TextBox.TextProperty, binding);
    
    也可以在XAML代码写:
    Text="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid},AncestorLevel=1},Path=Name}"
    

    AncestorLevel属性是以Binding目标控件为起点的层级偏移量,比如d2的偏移量是1,g2是2,Ancestor属性告诉Binding寻找哪个类型的对象作为自己的源,不是这个类型的对象会被跳过。从自己的第一层向外找,找到第一个Grid类型的对象后把它当作自己的源。

    TextBox关联自身的Name属性:

      RelativeSource rs = new RelativeSource();
      rs.Mode = RelativeSourceMode.Self;
      Binding binding = new Binding("Name") { RelativeSource = rs };
      this.textBox1.SetBinding(TextBox.TextProperty, binding);

    RelativeSource类的Mode属性类型是枚举-PreviousData,TemplatedParent、Self和FindAncestor。实际上3个静态属性是创建一个RelativeSource实例,把实例的Mode属性设置为相应的值,返回这个实例。之所以准备三个属性是为了在XAML代码里直接获取RelativeSource实例。

    5.3 Binding对数据的转换与校验

    Binding作用是架在Source与Target之间桥梁。数据可以桥梁上流通,Binding也可以数据有效性进行校验(ValidationRules属性)。Binding两端要求不同的数据类型时,还可以为数据设置转换器(Converter属性)。

    5.3.1 数据校验

    Binding的ValidationRules属性类型时Collection<ValidationRule>,可以为每个Binding设置多个数据校验对象(ValidationRule类型对象)。需要实现Validate方法,如果校验通过,就把IsValid属性设为True,反之,IsValid属性为False,并为其ErrorContent属性设置消息内容。

    例子:

    <StackPanel>
            <TextBox x:Name="textBox1" Margin="5"/>
            <Slider x:Name="slider1" Minimum="0" Maximum="100" Margin="5"/>
    </StackPanel>
       Binding binding = new Binding("Value") { Source = this.slider1 };
       binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
       RangeValidationRule rvr = new RangeValidationRule();
       binding.ValidationRules.Add(rvr);
       this.textBox1.SetBinding(TextBox.TextProperty, binding);

    RangeValidationRule

     class RangeValidationRule : ValidationRule
       {
            public override ValidationResult Validate(object value, CultureInfo cultureInfo)
            {
                double d = 0;
                if(double.TryParse(value.ToString(),out d))
                {
                    if(d>=0 && d<=100)
                    {
                        return new ValidationResult(true, null);
                    }
                }
                return new ValidationResult(false, "Validation Failed");
            }
    }

    当输入0到100之间正常显示,超过这个区间会显示红色边框。Binding只是在Target被外部方法更新呢时校验数据,而来自Binding的Source数据更新Target时不进行校验的。要校验Source数据时需要将校验条件的ValidatesOnTargetUpdated属性设为true。

    比如把上述的xaml代码改为:

    <Slider x:Name="slider1" Minimum="-10" Maximum="110" Margin="5"/>

      RangeValidationRule rvr = new RangeValidationRule();下面添加:

    rvr.ValidatesOnTargetUpdated = true;

    也会显示校验校验失败。在创建Binding时,把binding的对象的NotifyOnValidationError属性设为true。这样数据校验失败的时候Binding会发出信号,信号以Binding对象的Target为起点在UI元素树上传播,信号到达一个结点,设有侦听器的结点会被触发处理这个信号。处理后,程序员可以选择信号继续向下传播还是就此终止——路由事件。信号在元素树上传递过程被称为路由

    在创建binding实例下面添加:

    binding.NotifyOnValidationError = true;
    this.textBox1.SetBinding(TextBox.TextProperty, binding);
    this.textBox1.AddHandler(Validation.ErrorEvent,new RoutedEventHandler(this.ValidationError));
    private void ValidationError(object sender, RoutedEventArgs e)
    {
                if (Validation.GetErrors(this.textBox1).Count > 0)
                {
                    this.textBox1.ToolTip = Validation.GetErrors(this.textBox1)[0].ErrorContent.ToString();
                }
     }

    会出现下面的效果:

     5.3.2 Binding的数据转换

    Binding还有一种机制叫数据转换,当Source端的Path关联的数据与Target端目标属性数据类型不一致时,可以添加数据转换器。因为上面的Silder的Double和TextBox的String互相转换比较简单,所以WPF类库就自动做了。假设:

    1. Source数据是char、string、enum类型,映射到UI的CheckBx-IsChecked属性的Bool类型。
    2. TextBox输入时登录Button才出现,string与Visibility枚举类型或Bool类型转换。Binding的Mode将是OneWay
    3. Source数据可能是Male或Female,映射到UI的Image控件URI,也是OneWay。

    我们需要创建一个类实现IValueConverter接口,IValueConverter接口有Convert和ConvertBack方法。当数据Binding的Source->Target。调用Convert。反之用ConvertBack。有三个参数,第一个参数为值,保证参数的重用性,第二参数为确定方法的返回类型,第三个参数把额外信息传入方法。Binding的Mode属性会影响两个方法的调用。如果Mode为TwoWay或Default行为与TwoWay一致则两个方法都有可能被调用,如果为OneWay或Default与OneWay一致只有Convert调用。

    例子(向玩家显示各省水果的状态):

    ShuiGuo.cs

     public class ShuiGuo
     {
            public State State { get; set; }
            public Category Category { get; set; }
            public string Name { get; set; }
        }
        public enum State
        {
            Available,
            Locked,
            Unknown
        }
        public enum Category
        {
            xigua,
            caomei
    }

    StateToNullableBoolConverter.cs

     public class StateToNullableBoolConverter : IValueConverter
    {
            //将State转换为Bool
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                State s = (State)value;
                switch(s)
                {
                    case State.Locked:
                        return false;
                    case State.Available:
                        return true;
                    case State.Unknown:
                    default:
                        return null;
                }
           }
      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                bool? nb = (bool?)value;
                switch (nb)
                {
                    case true:
                        return State.Available;
                    case false:
                        return State.Locked;
                    case null:
                    default:
                        return State.Unknown;
                }
            }
    }

    CategoryToSourceConverter.cs

     public class CategoryToSourceConverter:IValueConverter
    {
            public object Convert(object value,Type targetType,object parameter,CultureInfo culture)
            {
                Category c = (Category)value;
                switch(c)
                {
                    case Category.xigua:
                        return @"\Icons\xigua.png";
                    case Category.caomei:
                        return @"\Icons\caomei.png";
                    default:
                        return null;
                }
            }
            public object ConvertBack(object value,Type targetType,object parameter ,CultureInfo culture)
            {
                throw new NotImplementedException();
            }
    }

    CategoryToSourceConverter.xaml

     <StackPanel Background="LightBlue">
            <ListBox x:Name="listBoxShuiGuo" Height="160" Margin="5">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/>
                            <TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/>
                            <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <Button x:Name="buttonLoad" Content="Load" Height="25" Margin="5,0" Click="buttonLoad_Click"/>
            <Button x:Name="buttonSave" Content="Save" Height="25" Margin="5,5" Click="buttonSave_Click"/>
    </StackPanel>

    C#

     private void buttonSave_Click(object sender, RoutedEventArgs e)
     {
                StringBuilder sb = new StringBuilder();
                foreach(ShuiGuo sg in listBoxShuiGuo.Items)
                {
                    sb.AppendLine(string.Format("Category={0},Name={1},State={2}", sg.Category, sg.Name, sg.State));
                }
                File.WriteAllText(@"D:\文本\ShuiGuoList.txt",sb.ToString());
     }
    
    private void buttonLoad_Click(object sender, RoutedEventArgs e)
    {
                List<ShuiGuo> shuiGuos = new List<ShuiGuo>()
                {
                    new ShuiGuo(){Category=Category.xigua,Name="贵州",State=State.Unknown},
                    new ShuiGuo(){Category=Category.xigua,Name="甘肃",State=State.Unknown},
                    new ShuiGuo(){Category=Category.caomei,Name="河南",State=State.Unknown},
                    new ShuiGuo(){Category=Category.caomei,Name="东北",State=State.Unknown},
                    new ShuiGuo(){Category=Category.xigua,Name="湖南",State=State.Unknown},
                    new ShuiGuo(){Category=Category.caomei,Name="浙江",State=State.Unknown},
                };
                this.listBoxShuiGuo.ItemsSource = shuiGuos;
    }

     

     5.3.3 MultiBinding(多路Binding) 

    不止一个数据来源就用MultiBinding,能使用Binding对象的场合都能使用MuliBinding,通过MultiBinding把一组Binding对象聚合起来。处在这个集合的Binding对象可以拥有自己的校验与转换机制。汇集起来的数据决定传往MultiBinding目标的数据。

    例子(要求两个用户名和邮箱内容一致):

    Xmal:

     <StackPanel Background="LightBlue">
            <TextBox x:Name="textBox1" Height="23" Margin="5"/>
            <TextBox x:Name="textBox2" Height="23" Margin="5,0"/>
            <TextBox x:Name="textBox3" Height="23" Margin="5"/>
            <TextBox x:Name="textBox4" Height="23" Margin="5"/>
            <Button x:Name="button1" Content="Submit" Width="80" Margin="5"></Button>
    </StackPanel>

    C#:

     public MainWindow()
    {
                InitializeComponent();
                this.SetMulitiBinding();
    }
    public void SetMulitiBinding()
    {
                Binding b1 = new Binding("Text") { Source = this.textBox1 };
                Binding b2 = new Binding("Text") { Source = this.textBox2 };
                Binding b3= new Binding("Text") { Source = this.textBox3 };
                Binding b4= new Binding("Text") { Source = this.textBox4};
    
                MultiBinding mb = new MultiBinding() { Mode = BindingMode.OneWay };
                mb.Bindings.Add(b1);
                mb.Bindings.Add(b2);
                mb.Bindings.Add(b3);
                mb.Bindings.Add(b4);
                mb.Converter = new LogonMultiBindingConvert();
                this.button1.SetBinding(Button.IsEnabledProperty, mb);
       }

     LogonMultiBindingConvert.cs

     public class LogonMultiBindingConvert : IMultiValueConverter
    {
            public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            {
                if(!values.Cast<string>().Any(text=>string.IsNullOrEmpty(text))&&values[0].ToString()==values[1].ToString()&&values[2].ToString()==values[3].ToString())
                {
                    return true;
                }
                return false;
            }
    
            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
    }

     6 WPF的属性

     6.1 属性

     程序员仍然把字段标记为private但使用一对非private的方法来包装。在这对方法中,一个以Set为前缀且负责判断数据的有效性并写入数据,另一个以Get为前缀且负责把字段里的数据读取出来。

    class Human
    {
      public int Age;
    }
    
    //..
    
    Human h=new Human();
    h.Age=-100;
    h.Age=1000;
    
    //把类设计成这样
    class Human
    {
         private int age;
         public int SetAge
         {
             get{return this.age;}
             set{
                 if(value>=0 &&value<=100)
                 {
                    this.age=value;
                 }else
                 {
                   throw new OverflowException("Age overflow");
                 }
             }
         }
    }

    这种.NET Framework中的属性又称为CLR属性。CLR属性并不会增加内存的负担。再多实例方法只有一个拷贝。

    6.2 依赖属性

    依赖属性就是一种可以自己没有值,并能通过使用Binding从数据源获得值的属性。拥有依赖属性的对象被称为“依赖对象”。

    • 节省实例对内存的开销
    • 属性值可以通过Binding依赖在其他对象中

    6.2.1 依赖属性对内存的使用方式

    而在WPF允许对象在被创建的时候并不包含用于存储数据的空间,只保留在需要用到数据时能够获得默认值、借用其他对象数据或实时分配空间的能力。在WPF系统中,依赖对象时被DependencyObject类实现。具有GetValue和SetValue方法。WPF所有的UI控件都是依赖对象。绝大多数属性已经依赖化。

    6.2.2 声明和使用依赖属性

    
    private void Button_Click(object sender, RoutedEventArgs e)
    {
           Student stu = new Student();
           stu.SetValue(Student.NameProperty, this.textBox1.Text);
           textBox2.Text = (string)stu.GetValue(Student.NameProperty);      
    }
      <StackPanel>
            <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textBox2" BorderBrush="Black" Margin="5"/>
            <Button Content="OK" Margin="5" Click="Button_Click"></Button>
    </StackPanel>
    class Student:DependencyObject
    {
            public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student));
    }

    例子进阶(把textBox1和textBox2关联):

    <StackPanel>
            <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textBox2" BorderBrush="Black" Margin="5"/>
            <Button Content="OK" Margin="5" Click="Button_Click"></Button>
    </StackPanel>
     public partial class MainWindow : Window
    {
            Student stu;
            public MainWindow()
            {
                InitializeComponent();
                stu = new Student();
                stu.SetBinding(Student.NameProperty, new Binding("Text") { Source = textBox1 });
                textBox2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu });
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                Student stu = new Student();
                stu.Name = this.textBox1.Text;
                this.textBox2.Text = stu.Name;
            }
    }
    class Student : DependencyObject
    {
            public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student));
            public string Name
            {
                get { return (string)GetValue(NameProperty); }
                set { SetValue(NameProperty,value); }
            }
            public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase binding)
            {
                return BindingOperations.SetBinding(this, dp, binding);
    }

    WPF有一套机制存取依赖属性的值,第一步在DependencyObject派生类中声明public static修饰的DependecyProperty成员变量。并使用DependecyProperty.Regisiter方法获得DependencyProperty的实例。第二步使用DependencyObject的SetValue和GetValue方法,借助DependencyProperty实例存取值。

    6.3 附加属性

    附加属性是说一个属性本来不属于这个对象,但由于某种需求后来附加上。也就是把对象放入一个特定环境后对象才具有的属性称为附加属性。附加属性的作用就是将属性与数据类型解耦,让数据类型设计更加灵活。

    例子(Human在School环境里有Grade附加属性):

    class School:DependencyObject
    {
            public static int GetGrade(DependencyObject obj)
            {
                return (int)obj.GetValue(GradeProperty);
            }
            public static void SetGrade(DependencyObject obj,int value)
            {
                obj.SetValue(GradeProperty, value);
            }
            public static readonly DependencyProperty GradeProperty = DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new UIPropertyMetadata(0));
    }
     class Human:DependencyObject 
    {
    }
     private void Button_Click(object sender, RoutedEventArgs e)
    {
                Human human = new Human();
                School.SetGrade(human, 6);
                int grade = School.GetGrade(human);
                MessageBox.Show(grade.ToString());
    }

    这一过程与依赖属性保存值过程并无二至——值仍然保存在Human实例的EffectiveValueEntry数组里。

    例子(附加属性的Binding):

    <Canvas>
            <Slider x:Name="sliderX" Canvas.Top="10" Canvas.Left="10" Width="260" Minimum="50" Maximum="200"/>
            <Slider x:Name="sliderY" Canvas.Top="40" Canvas.Left="10" Width="260" Minimum="50" Maximum="200"/>
            <Rectangle x:Name="rect" Fill="Blue" Width="30" Height="30" Canvas.Left="{Binding ElementName=sliderX,Path=Value}"
            Canvas.Top="{Binding ElementName=sliderY,Path=Value}"           
             />
    </Canvas>
     this.rect.SetBinding(Canvas.LeftProperty,new Binding("Value") { Source=sliderX});
     this.rect.SetBinding(Canvas.TopProperty, new Binding("Value") { Source = sliderY });

      7  WPF事件

     WPF有两种树,一种是逻辑树,一种是可视元素树。说明CLR直接事件模型中,事件的拥有者就消息的发送者。

     WinForm的Click事件处理器是直接事件模型。

     只要支持事件委托与影响事件的方法签名保持一致,则一个事件可以由多个事件处理器来响应。直接事件模型是传统.NE开发中对象间相互协同、沟通信息的主要手段。当层级组件过多时,会形成事件链,而路由事件很好地解决了这个问题。

     7.1 路由事件

      路由事件拥有者和事件响应者之间没有显式地订阅关系,事件的拥有者只负责激发事件,事件将由谁响应它并不知道,事件的响应值则安装由事件侦听器。

      

    <Grid x:Name="gridRoot" Background="Lime">
            <Grid x:Name="gridA" Margin="10"  Background="Blue" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <Canvas x:Name="canvasLeft" Grid.Column="0" Background="Red" Margin="10">
                    <Button x:Name="buttonLeft" Content="Left" Width="40" Height="100" Margin="10"/>
                </Canvas>
                <Canvas x:Name="canvasRight" Grid.Column="0" Background="Yellow" Margin="10">
                    <Button x:Name="buttonRight" Content="Left" Width="40" Height="100" Margin="10"/>
                </Canvas>
            </Grid>    
    </Grid>

       

       当点击buttonLeft时,Button.Click事件就会沿着buttonLeft->canvasLeft->gridA->gridRoot->Window这条路线向上传送。下面为gridRoot安装针对Button.Click事件的侦听器。

      this.gridRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonClicked));

    路由事件是从内部一层一层传递出来最后到达最外层的gridRoot。查看事件的源头用e.OriginalSource,需要as/is操作符或者强制类型转换把它识别/转换成正确的类型。添加事件处理器也可以在XAML中完成。

    <Grid x:Name="gridRoot" Background="Lime" Button.Click="ButtonClicked">

     

    展开全文
  • ValidationResult validationResult; try { validationResult = validation.get(); } catch (Exception exception) { // the validation it self may fail, for example if the workspace already exists ...

    工作空间有点类似于一个目录,而geoserver的工作空间是数据目录下的特定文件夹。创建工作空间的代码位于如下图的位置。

    对应的新建工作区页面则如下图所示。

    我们来看一下,创建的该页面的代码。WorkspaceNewPage.java的构造函数利用wickte来创建填写提交表单。

    public WorkspaceNewPage() {
            WorkspaceInfo ws = getCatalog().getFactory().createWorkspace();
    
            form =
                    new Form<WorkspaceInfo>("form", new CompoundPropertyModel<WorkspaceInfo>(ws)) {
                        private static final long serialVersionUID = 6088042051374665053L;
    
                        @Override
                        protected void onSubmit() {
                            handleOnSubmit(form);
                        }
                    };
            add(form);
    
            TextField<String> nameTextField = new TextField<String>("name");
            nameTextField.setRequired(true);
            nameTextField.add(new XMLNameValidator());
            nameTextField.add(
                    new StringValidator() {
    
                        private static final long serialVersionUID = -5475431734680134780L;
    
                        @Override
                        public void validate(IValidatable<String> validatable) {
                            if (CatalogImpl.DEFAULT.equals(validatable.getValue())) {
                                validatable.error(
                                        new ValidationError("defaultWsError").addKey("defaultWsError"));
                            }
                        }
                    });
            form.add(nameTextField.setRequired(true));
    
            nsUriTextField = new TextField<String>("uri", new Model<String>());
            // maybe a bit too restrictive, but better than not validation at all
            nsUriTextField.setRequired(true);
            nsUriTextField.add(new URIValidator());
            form.add(nsUriTextField);
    
            CheckBox defaultChk =
                    new CheckBox("default", new PropertyModel<Boolean>(this, "defaultWs"));
            form.add(defaultChk);
    
            // add checkbox for isolated workspaces
            CheckBox isolatedChk = new CheckBox("isolated", new PropertyModel<>(ws, "isolated"));
            if (!getCatalog().getCatalogCapabilities().supportsIsolatedWorkspaces()) {
                // is isolated workspaces are not supported by the current catalog disable them
                isolatedChk.setEnabled(false);
            }
            form.add(isolatedChk);
    
            SubmitLink submitLink = new SubmitLink("submit", form);
            form.add(submitLink);
            form.setDefaultButton(submitLink);
    
            AjaxLink<Void> cancelLink =
                    new AjaxLink<Void>("cancel") {
                        private static final long serialVersionUID = -1731475076965108576L;
    
                        @Override
                        public void onClick(AjaxRequestTarget target) {
                            doReturn(WorkspacePage.class);
                        }
                    };
            form.add(cancelLink);
        }

    注意里面的表单提交后的回调函数handleOnSubmit,

     private void handleOnSubmit(Form form) {
            Catalog catalog = getCatalog();
            // get the workspace information from the form
            WorkspaceInfo workspace = (WorkspaceInfo) form.getModelObject();
            NamespaceInfo namespace = catalog.getFactory().createNamespace();
            namespace.setPrefix(workspace.getName());
            namespace.setURI(nsUriTextField.getDefaultModelObjectAsString());
            namespace.setIsolated(workspace.isIsolated());
            // validate the workspace information adn associated namespace
            if (!validateAndReport(() -> catalog.validate(workspace, true), form)
                    || !validateAndReport(() -> catalog.validate(namespace, true), form)) {
                // at least one validation fail
                return;
            }
            // store the workspace and associated namespace in the catalog
            try {
                catalog.add(workspace);
                catalog.add(namespace);
            } catch (Exception exception) {
                LOGGER.log(Level.INFO, "Error storing workspace related objects.", exception);
                cleanAndReport(exception, form);
            }
            // let's see if we need to tag this workspace as the default one
            if (defaultWs) {
                catalog.setDefaultWorkspace(workspace);
            }
            doReturn(WorkspacePage.class);
        }

    catalog对工作空间进行了检校。然后再将工作空间和名字空间放到catalog中。检校函数主要做类似于是否名字重复等验证工作。函数如下所示。

     private boolean validateAndReport(Supplier<ValidationResult> validation, Form form) {
            // execute the validation
            ValidationResult validationResult;
            try {
                validationResult = validation.get();
            } catch (Exception exception) {
                // the validation it self may fail, for example if the workspace already exists
                LOGGER.log(Level.INFO, "Error validating workspace related objects.", exception);
                form.error(exception.getMessage());
                return false;
            }
            // if the validation was not successful report the found exceptions
            if (!validationResult.isValid()) {
                String message = validationResult.getErrosAsString(System.lineSeparator());
                LOGGER.log(Level.INFO, message);
                form.error(message);
                return false;
            }
            // validation was successful
            return true;
        }

    如果说出现两次名字重复的工作空间,那么会抛出异常。在代码中则会抛出这样的异常。

    而对应的桌面则显示如下的红色警告。

    好了,看完工作空间后,下面来看数据存储。

    我们这里选择【栅格数据源】,当然这个展示界面还为我们提供其他的数据源,具体的没有一一去研究,可以参考一下代码。

     public NewDataPage() {
    
            final boolean thereAreWorkspaces = !getCatalog().getWorkspaces().isEmpty();
    
            if (!thereAreWorkspaces) {
                super.error(
                        (String) new ResourceModel("NewDataPage.noWorkspacesErrorMessage").getObject());
            }
    
            final Form storeForm = new Form("storeForm");
            add(storeForm);
    
            final ArrayList<String> sortedDsNames =
                    new ArrayList<String>(getAvailableDataStores().keySet());
            Collections.sort(sortedDsNames);
    
            final CatalogIconFactory icons = CatalogIconFactory.get();
            final ListView dataStoreLinks =
                    new ListView("vectorResources", sortedDsNames) {
                        @Override
                        protected void populateItem(ListItem item) {
                            final String dataStoreFactoryName = item.getDefaultModelObjectAsString();
                            final DataAccessFactory factory =
                                    getAvailableDataStores().get(dataStoreFactoryName);
                            final String description = factory.getDescription();
                            SubmitLink link;
                            link =
                                    new SubmitLink("resourcelink") {
                                        @Override
                                        public void onSubmit() {
                                            setResponsePage(
                                                    new DataAccessNewPage(dataStoreFactoryName));
                                        }
                                    };
                            link.setEnabled(thereAreWorkspaces);
                            link.add(new Label("resourcelabel", dataStoreFactoryName));
                            item.add(link);
                            item.add(new Label("resourceDescription", description));
                            Image icon = new Image("storeIcon", icons.getStoreIcon(factory.getClass()));
                            // TODO: icons could provide a description too to be used in alt=...
                            icon.add(new AttributeModifier("alt", new Model("")));
                            item.add(icon);
                        }
                    };
    
            final List<String> sortedCoverageNames = new ArrayList<String>();
            sortedCoverageNames.addAll(getAvailableCoverageStores().keySet());
            Collections.sort(sortedCoverageNames);
    
            final ListView coverageLinks =
                    new ListView("rasterResources", sortedCoverageNames) {
                        @Override
                        protected void populateItem(ListItem item) {
                            final String coverageFactoryName = item.getDefaultModelObjectAsString();
                            final Map<String, Format> coverages = getAvailableCoverageStores();
                            Format format = coverages.get(coverageFactoryName);
                            final String description = format.getDescription();
                            SubmitLink link;
                            link =
                                    new SubmitLink("resourcelink") {
                                        @Override
                                        public void onSubmit() {
                                            setResponsePage(
                                                    new CoverageStoreNewPage(coverageFactoryName));
                                        }
                                    };
                            link.setEnabled(thereAreWorkspaces);
                            link.add(new Label("resourcelabel", coverageFactoryName));
                            item.add(link);
                            item.add(new Label("resourceDescription", description));
                            Image icon = new Image("storeIcon", icons.getStoreIcon(format.getClass()));
                            // TODO: icons could provide a description too to be used in alt=...
                            icon.add(new AttributeModifier("alt", new Model("")));
                            item.add(icon);
                        }
                    };
    
            final List<OtherStoreDescription> otherStores = getOtherStores();
    
            final ListView otherStoresLinks =
                    new ListView("otherStores", otherStores) {
                        @Override
                        protected void populateItem(ListItem item) {
                            final OtherStoreDescription store =
                                    (OtherStoreDescription) item.getModelObject();
                            SubmitLink link;
                            link =
                                    new SubmitLink("resourcelink") {
                                        @Override
                                        public void onSubmit() {
                                            setResponsePage(store.configurationPage);
                                        }
                                    };
                            link.setEnabled(thereAreWorkspaces);
                            link.add(
                                    new Label(
                                            "resourcelabel",
                                            new ParamResourceModel(
                                                    "other." + store.key, NewDataPage.this)));
                            item.add(link);
                            item.add(
                                    new Label(
                                            "resourceDescription",
                                            new ParamResourceModel(
                                                    "other." + store.key + ".description",
                                                    NewDataPage.this)));
                            Image icon = new Image("storeIcon", store.icon);
                            // TODO: icons could provide a description too to be used in alt=...
                            icon.add(new AttributeModifier("alt", new Model("")));
                            item.add(icon);
                        }
                    };
    
            storeForm.add(dataStoreLinks);
            storeForm.add(coverageLinks);
            storeForm.add(otherStoresLinks);
        }
    

    栅格数据源则使用下面的函数展示出来。

     /** @return the name/description set of available raster formats */
        private Map<String, Format> getAvailableCoverageStores() {
            if (coverages == null) {
                Format[] availableFormats = GridFormatFinder.getFormatArray();
                Map<String, Format> formatNames = new HashMap<String, Format>();
                for (Format format : availableFormats) {
                    formatNames.put(format.getName(), format);
                }
                coverages = formatNames;
            }
            return coverages;
        }

    当点击数据源后,进入到如下的界面。

    显然进入到一个page页面,而这里是对应CoverageStoreNewPage类。到选择好数据,并点击【保存】后,如下图进入到如的代码。

    protected void onSave(final CoverageStoreInfo info, AjaxRequestTarget target)
                throws IllegalArgumentException {
            final Catalog catalog = getCatalog();
    
            /*
             * Try saving a copy of it so if the process fails somehow the original "info" does not end
             * up with an id set
             */
            CoverageStoreInfo expandedStore = getCatalog().getResourcePool().clone(info, true);
            CoverageStoreInfo savedStore = catalog.getFactory().createCoverageStore();
    
            // GR: this shouldn't fail, the Catalog.save(StoreInfo) API does not declare any action in
            // case
            // of a failure!... strange, why a save can't fail?
            // Still, be cautious and wrap it in a try/catch block so the page does not blow up
            try {
                // GeoServer Env substitution; validate first
                catalog.validate(expandedStore, false).throwIfInvalid();
    
                // GeoServer Env substitution; force to *AVOID* resolving env placeholders...
                savedStore = catalog.getResourcePool().clone(info, false);
                // ... and save
                catalog.save(savedStore);
            } catch (RuntimeException e) {
                LOGGER.log(Level.INFO, "Adding the store for " + info.getURL(), e);
                throw new IllegalArgumentException(
                        "The coverage store could not be saved. Failure message: " + e.getMessage());
            }
    
            onSuccessfulSave(info, catalog, savedStore);
        }
    
        protected void onSuccessfulSave(
                final CoverageStoreInfo info, final Catalog catalog, CoverageStoreInfo savedStore) {
            // the StoreInfo save succeeded... try to present the list of coverages (well, _the_
            // coverage while the getotools coverage api does not allow for more than one
            NewLayerPage layerChooserPage;
            CoverageStoreInfo expandedStore;
            try {
                expandedStore = catalog.getResourcePool().clone(savedStore, true);
                // The ID is assigned by the catalog and therefore cannot be cloned
                layerChooserPage = new NewLayerPage(savedStore.getId());
            } catch (RuntimeException e) {
                LOGGER.log(Level.INFO, "Getting list of coverages for saved store " + info.getURL(), e);
                // doh, can't present the list of coverages, means saving the StoreInfo is meaningless.
                try { // be extra cautious
                    catalog.remove(savedStore);
                } catch (RuntimeErrorException shouldNotHappen) {
                    LOGGER.log(Level.WARNING, "Can't remove CoverageStoreInfo after adding it!", e);
                }
                // tell the caller why we failed...
                throw new IllegalArgumentException(e.getMessage(), e);
            }
    
            setResponsePage(layerChooserPage);
        }

     

    展开全文
  • WPF 按钮绑定TextBox的 Validation.HasError

    千次阅读 2018-05-28 17:42:48
    public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (string.IsNullOrEmpty(value.ToString())) return new ValidationResult(false, "userNameRequired".ToGlobal()); ...
  • MVC之验证终结者篇

    千次阅读 2016-06-18 22:40:53
    <ValidationResult> Validate( ValidationContext validationContext); } 举个栗子: public class Person: IValidatableObject { [DisplayName( " 姓名 " )] public string Name { get ; ...
  • nameLabel.rx.validationResult).addDisposableTo(disposeBag) viewModel.usernameUseable.bind(to: pwdTextField.rx.inputEnabled).addDisposableTo(disposeBag) 将 ViewModel 中 username 处理结果 ...
  • } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { PropertyInfo property = validationContext.ObjectType.GetProperty(OtherProperty); if (property == ...
  • 在Express中处理来自外部的任何类型输入的验证的最好方法是使用Express -validator包: npm install express-validator 你引入validationResult和check对象: const { check, validationResult } = require('express...
  • } } public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (uint.TryParse(value.ToString(), out uint result)) { if (result >= 0 && result ) { return ValidationResult....
  • Node.js进阶 — Express-validator表单验证器 TIP ... 使用 express-validator 可以简化 POST 请求的参数验证,使用方法如下: ...const { body, validationResult } = require('express-validator') const bo
  • 示例代码如下 const express =require("express") const userCtrl = require("../controller/user") //controller模块 const { body, validationResult } = require('express-validator'); //数据验证 const { User...
  • //继承一个ValidationRule,重写Validate方法 public class ... public override ValidationResult Validate(object value, CultureInfo cultureInfo) { double myValue = 0; if (double.TryParse(va
  • 前言: 只要是有表单存在,那么就有可能有对数据的校验需求。如:判断是否为整数、判断电子邮件格式等等。 WPF采用一种全新的方式 - Binding,来实现前台显示与后台数据进行交互,当然数据校验方式也不一样了...
  • } /// /// 此方法先于 IsValid(object value)调用 /// /// /// /// /// protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var compareDate = (DateTime)value;...
  • 对于具体的某个类,其成员可以添加使用System.Component.DataAnnotations命名空间下一系列特性(如RequiredAttribute,MaxLengthAttribute,StringLengthAttribute,...ValidationResult>集合中。 Validatio.
  • GetValidationResult方法返回的是一个ValidationResult类型,ValidationResult类型不仅包括IsValid属性,还包括其他一个很重要的属性:ValidationErrors。修改下LastName上的Data Annotation标注: [MaxLength...
  • 验证完成后需要返回一个ValidationResult 对象表示验证结果,如果验证的数据无效,就需要为验证结果指定一个字符串作为错误信息反馈给用户。 好了,现在demo可以运行了,在表示Name的文本框中输入一些字符,然后...
  • public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { errormessage = ""; if (value == null) { errormessage = "不能为空值"; return new ...
  • 1.默认窗体打开是这样的: !... ... Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded" Style="{StaticResource ShadowWindow}"> ... ...
  • validationResult.setMessage(error.getDefaultMessage()) ; validationResult.setParam(error.getField()) ; paramValidationResults.add(validationResult) ; } return new ResultMsg(ResultCode. ...
  • 原文:[WPF 基础知识系列] —— 绑定中的数据校验Vaildation前言: 只要是有表单存在,那么就有可能有对数据的校验需求。如:判断是否为整数、判断电子邮件格式等等。 WPF采用一种全新的方式 - Binding,来实现前台...
  • MuleSoft的各种组件 ...我认识其实也不是很多,我的东西都是当笔记来看待 core core下面有七种组件,分别是Batch、Components、Endpoints、Error Handling、Flow Control、Scopes、Transformers Batch类,这个是可以...
  • 1、依赖 <!--数据校验 专业--> <!-- hibernate 校验 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator<...5.2.4.
  • 封装模块 var config = require( '../config' ); var request = require( 'request' ); exports.http = function( url, type, data, req, reqRule, headers, callback... var rsMsg = { 'code' : -100, 'msg' : 'e
  • 工具 版本 mule-standalone 3.9.0 Anypoint-Studio ...mule esb提供了一个Transactional元素,该元素属于scope类型,在当前社区版本下,Anypoint-Studio提供了11个scope类型的元素。事务的重要性不言而喻,...
  • Validation 全注解+使用示例+官方文档

    千次阅读 2018-12-25 20:02:29
    Validation 全注解+使用示例+官方文档(转) 原文:...Validation 注解 @AssertFalse 被注解的元素必须为false @AssertTrue 被注解的元素必须为True @DecimalMax(value) 被注解的元...
  • using _SCscHero.Base; using _SCscHero.Base.Enum; using _SCscHero.Base.Help; using _SCscHero.Model; using Microsoft.AspNetCore.... using Microsoft.AspNetCore.Mvc;...using Microsoft.Extensions.Dependenc...
  • 前言文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger种一棵树最好的时间是十年前,其次是现在我知道很多人...
  • 数据绑定

    2017-03-27 13:49:00
    ValidationResult构造:ValidationResult(bool isvalid, object 错误内容对象(通常是消息串,无错误设null)); 当验证发生错误时会发生(不会触发任何异常):  1. 相应的单元周围出现红色框;  2. 单元上的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,128
精华内容 851
关键字:

validationresult