GVKun编程网logo

day11 Django: froms组件: 数据校验 显示错误信息 渲染数据 重置数据 自定义规则 局部钩子,全局钩子

15

如果您对day11Django:froms组件:数据校验显示错误信息渲染数据重置数据自定义规则局部钩子,全局钩子感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解day11Django:froms

如果您对day11 Django: froms组件: 数据校验 显示错误信息 渲染数据 重置数据 自定义规则 局部钩子,全局钩子感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解day11 Django: froms组件: 数据校验 显示错误信息 渲染数据 重置数据 自定义规则 局部钩子,全局钩子的各种细节,此外还有关于@Valid 数据校验 + 自定义全局异常信息、a-from 自定义规则使用,及初始默认值规则校验失效问题、c# 全局钩子实现扫码枪获取信息、c# 全局钩子实现扫码枪获取信息。的实用技巧。

本文目录一览:

day11 Django: froms组件: 数据校验 显示错误信息 渲染数据 重置数据 自定义规则 局部钩子,全局钩子

day11 Django: froms组件: 数据校验 显示错误信息 渲染数据 重置数据 自定义规则 局部钩子,全局钩子

day11 Django: froms组件: 数据校验 显示错误信息 渲染数据 重置数据 自定义规则 局部钩子,全局钩子
 
一.Django: forms组件
1.forms组件的校验数据功能
    1.1.基本语法
        1.1.1.新建forms组件类
from django import forms
 
class BookForm(forms.Form):                                                    #创建一个forms的类
    title = forms.CharField(max_length=32)
    price = forms.IntegerField()
    email = forms.EmailField()
        1.1.2.命令行测试语法
from app01.views import BookForm
#1.验证不通过示例
fm = BookForm({''title'':''bajie'',''price'':2000,''email'':''163''})                    #实例化            
fm.is_valid()                                                                  #做验证: 这步必须有, 否则验证正确和错误的键值拿不到  
Out[5]: False
fm.cleaned_data                                                                #正确的键值    
Out[6]: {''title'': ''bajie'', ''price'': 2000}    
fm.errors                                                                      #不正确的键值  
Out[7]: {''email'': [''Enter a valid email address.'']}
#2.验证通过示例
fm = BookForm({''title'':''bajie'',''price'':2000,''email'':''163@163.com''})            #实例化: 注意: 键值对可以多, 但是不能少, 也不能把键搞错
fm.is_valid()                                                                  #做验证
Out[9]: True
fm.cleaned_data                                                                #正确的键值    
Out[10]: {''title'': ''bajie'', ''price'': 2000, ''email'': ''163@163.com''}
fm.errors                                                                      #不正确的键值  
Out[11]: {}
    
    1.2.注册示例(使用form表单)
        用户名: 长度不能少于5位, 密码: 是纯数字, 邮箱: 必须符合格式
        如果输入数据格式有问题, 显示给用户
        models.py
from django.db import models
# Create your models here.
class UserInfo(models.Model):
    user = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    email = models.CharField(max_length=32)
        urls.py
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
    path(''admin/'', admin.site.urls),
    path(''register/'', views.register),
    path(''login/'', views.login),
]        
        views.py
from django.shortcuts import render, HttpResponse
from django import forms
from app01.models import UserInfo
# Create your views here.
class UserForm(forms.Form):
    msg = {"required": "该字段不能为空", "invalid": "格式错误"}
    user = forms.CharField(min_length=5, error_messages=msg)                   #form表单里面的name要和forms的key一样才行: 为空时显示的内容,必须叫这个名字
    pwd = forms.IntegerField()
    email = forms.EmailField(error_messages=msg)
def login(request):
    return render(request, ''login.html'', locals())
def register(request):
    if request.method == ''POST'':
        fm = UserForm(request.POST)
        if fm.is_valid():
            UserInfo.objects.create(**fm.cleaned_data)
            return HttpResponse(''ok'')
        else:
            print("cleaned_data: ", fm.cleaned_data)
            print("errors: ", fm.errors)
            print(type(fm.errors))                                             #<class ''django.forms.utils.ErrorDict''>可以理解成是一个字典
            print(type(fm.errors.get(''user'')))                                 #<class ''django.forms.utils.ErrorList''>可以理解成是一个列表
            # print(type(fm.errors.get(''user'')[0]))                            #取出错误字段的错误信息
            errors = fm.errors
            return render(request, ''register.html'', locals())
    else:
        return render(request, ''register.html'', locals())
        templates > register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    <p>用户名: <input type="text" name="user"><span>{{ errors.user.0 }}</span></p>
    <p>密码: <input type="password" name="pwd"><span>{{ errors.pwd.0 }}</span></p>
    <p>邮箱: <input type="text" name="email"><span>{{ errors.email.0 }}</span></p>
    <input type="submit">
</form>
</body>
</html>
 
2.forms组件的页面显示错误信息
 
3.渲染页面和重置数据
    register.html需要我们自己写, 不然呢? 
    而且会把forms组件的属性加到标签里面: 比如: minlenght = 5, 前端可以给我们检验不挺好吗? 是的, 但是万一检验不了呢,如IE, 所以我们要在后端做校验.
    告诉前端不要你做校验: <form action="" method="post" novalidate>
    而且实现了: 提交后, 正确的内容不消失, 错误的才清空: 
        方式一: forms组件帮我们写, 只能帮我们渲染input,但不包括type=''submit''的input(包括输入框前面的User; Pwd; Email)
            views.py
def register(request):
    if request.method == ''POST'':
        fm = UserForm(request.POST)
        if fm.is_valid():
            UserInfo.objects.create(**fm.cleaned_data)
            return HttpResponse(''ok'')
        else:
            print("cleaned_data: ", fm.cleaned_data)
            print("errors: ", fm.errors)
            print(type(fm.errors))                 
            print(type(fm.errors.get(''user'')))      
            # print(type(fm.errors.get(''user'')[0]))   
            errors = fm.errors
            return render(request, ''register.html'', locals())
    else:
        fm = UserForm()
        return render(request, ''register.html'', locals())
            register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>
    {% csrf_token %}
    {{ fm.as_p }}
    <input type="submit">
</form>
</body>
</html>
        方式二: 只替换 input 标签
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>
    {% csrf_token %}
    <p>用户名: {{ fm.user }}<span>{{ errors.user.0 }}</span></p>
    <p>密码: {{ fm.pwd }}<span>{{ errors.pwd.0 }}</span></p>
    <p>邮箱: {{ fm.email }}<span>{{ errors.email.0 }}</span></p>
    <input type="submit">
</form>
</body>
</html>
        方式二: 只替换 input 标签, 在forms组件里设置标签的属性  
            views.py
class UserForm(forms.Form):
    msg = {"required": "该字段不能为空", "invalid": "格式错误"}
    user = forms.CharField(min_length=5,
                           error_messages=msg,
                           widget=widgets.TextInput(attrs={"class": "form-control"}))    
    pwd = forms.IntegerField(widget=widgets.PasswordInput(attrs={"class": "form-control"}))
    email = forms.EmailField(error_messages=msg,
                             widget=widgets.EmailInput(attrs={"class": "form-control"}))    
            register.html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
  <h1>注册界面</h1>
    <div>
        <div>
            <div>
                <form action="" method="post" novalidate>
                    {% csrf_token %}
                    <p>用户名: {{ fm.user }}<span>{{ errors.user.0 }}</span></p>
                    <p>密码: {{ fm.pwd }}<span>{{ errors.pwd.0 }}</span></p>
                    <p>邮箱: {{ fm.email }}<span>{{ errors.email.0 }}</span></p>
                    <input type="submit">
                </form>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>
  
    方式三: 这个方式应该是你喜欢的
        方式一的简单但不灵活
        方式二的灵活但不简单
            views.py
class UserForm(forms.Form):
    msg = {"required": "该字段不能为空", "invalid": "格式错误"}
    user = forms.CharField(min_length=5,
                           error_messages=msg,
                           label="用户名",
                           widget=widgets.TextInput(attrs={"class": "form-control"}))   
    pwd = forms.IntegerField(label="密码",
                             widget=widgets.PasswordInput(attrs={"class": "form-control"}))
    email = forms.EmailField(error_messages=msg,
                             label="邮箱",
                             widget=widgets.EmailInput(attrs={"class": "form-control"}))
            register.html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
  <h1>注册界面</h1>
    <div>
        <div>
            <div>
                <form action="" method="post" novalidate>
                    {% csrf_token %}
                        {% for field in fm %}
                            <div>
                                <label for="">{{ field.label }}</label>
                                {{ field }} <span>{{ field.errors.0 }}</span>
                            </div>
                        {% endfor %}
                    <input type="submit">
                </form>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>
 
4.自定义规则: 钩子的简单使用
    4.1.用户名不能重复
    4.2.密码不能是纯数字
        在forms组件类的字段下面, 加clean_"form字段名"的方法:
class UserForm(forms.Form):
    msg = {"required": "该字段不能为空", "invalid": "格式错误"}
    user = forms.CharField(min_length=5,
                           error_messages=msg,
                           label="用户名",
                           widget=widgets.TextInput(attrs={"class": "form-control"}))  
    pwd = forms.CharField(label="密码",
                             widget=widgets.PasswordInput(attrs={"class": "form-control"}))
    email = forms.EmailField(error_messages=msg,
                             label="邮箱",
                             widget=widgets.EmailInput(attrs={"class": "form-control"}))
    def clean_user(self):
        val = self.cleaned_data.get(''user'')
        rst = UserInfo.objects.filter(user=val)
        if not rst:
            return val
        else:
            raise ValidationError(''用户名不能重复'')
    def clean_pwd(self):
        val = self.cleaned_data.get(''pwd'')
        if val.isdigit():
            raise ValidationError(''密码不能是纯数字'')
        else:
            return val
 
5.钩子的源码解析
    代码: form = UserForm(request.POST)    #实例化时得到 self.fields = {"user": user, "pwd": pwd, "email": email}, 一个键后面一个对象(字段)
          form.isvalid()  
    源码解析: is_valid()                   #在这一步,会生成errors 字典和cleaned_data字典 
        self._errors = ErrorDict()
        self.cleaned_data = {}     
        def full_clean(self)
            self._clean_fields()           #这个是局部钩子 #里面用的是for循环, 一次循环: 先去检测forms的字段, 字段验证通过去验证钩子: 通过放到cleaned_data里, 没通过近errors
                                           #钩子的使用时机: 需要先通过第一层字段的校验后使用, 不然有意义吗 ? 
            self._clean_form()             #这个是全局的钩子
                def clean(self)            #全局的钩子里面clean()方法,里面没有任何逻辑, 直接返回干净的数据: 为的是给你覆盖用的
                    return self.cleaned_data
 
6.钩子的应用
    验证"密码"和"验证密码"是否一致
    正常一个钩子只能取它自己的字段的值, "取不到"它下一个字段的值(取不到吗,不, 是因为要注意钩子的使用时机), 那怎么办? 
    使用全局钩子
    models.py
from django.db import models
class UserInfo(models.Model):
    user = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    email = models.CharField(max_length=32)
     urls.py
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
    path(''admin/'', admin.site.urls),
    path(''register/'', views.register),
    path(''login/'', views.login),
]  
    views.py
from django.shortcuts import render, HttpResponse
from django import forms
from app01.models import UserInfo
from django.forms import widgets
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
# Create your views here.
class UserForm(forms.Form):
    msg = {"required": "该字段不能为空", "invalid": "格式错误"}
    user = forms.CharField(min_length=5,
                           error_messages=msg,
                           label="用户名",
                           widget=widgets.TextInput(attrs={"class": "form-control"}))    #form表单里面的name要和forms的key一样才行: 为空时显示的内容,必须叫这个名字
    pwd = forms.CharField(label="密码",
                             widget=widgets.PasswordInput(attrs={"class": "form-control"}))
    repwd = forms.CharField(label="确认密码",
                             widget=widgets.PasswordInput(attrs={"class": "form-control"}))
    email = forms.EmailField(error_messages=msg,
                             label="邮箱",
                             widget=widgets.EmailInput(attrs={"class": "form-control"}))
    def clean_user(self):
        val = self.cleaned_data.get(''user'')
        rst = UserInfo.objects.filter(user=val)
        if not rst:
            return val
        else:
            raise ValidationError(''用户名不能重复'')
    def clean_pwd(self):
        val = self.cleaned_data.get(''pwd'')
        if val.isdigit():
            raise ValidationError(''密码不能是纯数字'')
        else:
            return val
    def clean(self):
        pwd = self.cleaned_data.get(''pwd'')
        repwd = self.cleaned_data.get(''repwd'')      #有可能取到的是: None
        if pwd and repwd:
            if pwd == repwd:
                return self.cleaned_data
            else:
                raise ValidationError(''两次输入的密码不一致'')
        else:
            return self.cleaned_data
def login(request):
    return render(request, ''login.html'', locals())
def register(request):
    if request.method == ''POST'':
        fm = UserForm(request.POST)
        if fm.is_valid():
            UserInfo.objects.create(**fm.cleaned_data)
            return HttpResponse(''ok'')
        else:
            print("cleaned_data: ", fm.cleaned_data)
            print("errors: ", fm.errors)
            print(type(fm.errors))                  #<class ''django.forms.utils.ErrorDict''>可以理解成是一个字典
            print(type(fm.errors.get(''user'')))      #<class ''django.forms.utils.ErrorList''>可以理解成是一个列表
            # print(type(fm.errors.get(''user'')[0]))   #取出错误字段的错误信息
            errors = fm.errors
            global_errors = fm.errors.get("__all__")
            return render(request, ''register.html'', locals())
    else:
        fm = UserForm()
        return render(request, ''register.html'', locals())
    register.html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
  <h1>注册界面</h1>
    <div>
        <div>
            <div>
                <form action="" method="post" novalidate>
                    {% csrf_token %}
                        {% for field in fm %}
                            <div>
                                <label for="">{{ field.label }}</label>
                                {{ field }} <span>{{ field.errors.0 }}</span>
                                {% if field.label == "确认密码" %}
                                    <span>{{ global_errors|default:"" }}</span>
                                {% endif %}
                            </div>
                        {% endfor %}
                    <input type="submit">
                </form>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>
    
 
 
 
 
 
 
 
 
 
 

@Valid 数据校验 + 自定义全局异常信息

@Valid 数据校验 + 自定义全局异常信息

关于javax.validation.Validator校验的使用

  • 对于要校验的实体类:其需要校验的字段上需要添加注解

常用注解

实际例子

实体类Demo

使用:首先要拿到 validator的子类

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

其中方法可以参考 API
对实体类的校验

Set<ConstraintViolation<Object>> set = validator.validate(obj,Default.class);
 

 

本文链接:https://blog.csdn.net/qq_38193966/article/details/95990268

———————————————— 

 

@Valid 数据校验 + 自定义全局异常信息

https://www.jianshu.com/p/311d08fccd47
 

我们常用@Valid做数据校验,比如现在前端要新增一个用户,我们可以这样校验:

@RestController
public class UserController {

    @PostMapping("/user") public void addUser(@RequestBody @Valid RequestDTO requestDTO){ //其余业务处理 System.out.println(requestDTO.toString()); } } 

传入的数据规则如下列代码所示:


@Data
public class RequestDTO {
    @NotNull(message = "名字不能为空") String name; @NotEmpty(message = "密码不能为空") String password; @Override public String toString() { return "name=" + name + ",password=" + password; } } 

假设我们模仿前端伪造了一个非法数据(例如密码为空):

{
  "name": "string", "password": "" } 

加了@Valid注解的程序就能按我们的预期报错:

{
  "timestamp": "2019-08-26T14:12:02.542+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "NotEmpty.requestDTO.password", "NotEmpty.password", "NotEmpty.java.lang.String", "NotEmpty" ], "arguments": [ { "codes": [ "requestDTO.password", "password" ], "arguments": null, "defaultMessage": "password", "code": "password" } ], "defaultMessage": "密码不能为空", "objectName": "requestDTO", "field": "password", "rejectedValue": "", "bindingFailure": false, "code": "NotEmpty" } ], "message": "Validation failed for object=''requestDTO''. Error count: 1", "path": "/user" } 

报错信息改进

但这样的报错信息明显太冗余了,我们想简化下,只抛出有问题字段的报错信息,这回就可以结合我们的全局异常进行处理:

1.编写自定义异常处理类,绑定要处理的异常

这里我们注意到@Valid抛出的异常类是MethodArgumentNotValidException ,所以我们将捕获该异常,并对它重新自定义异常信息

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(value = MethodArgumentNotValidException.class) @ResponseBody public JsonResult MyExceptionHandle(MethodArgumentNotValidException exception){ exception.printStackTrace(); BindingResult result = exception.getBindingResult(); StringBuilder errorMsg = new StringBuilder() ; if (result.hasErrors()) { List<FieldError> fieldErrors = result.getFieldErrors(); fieldErrors.forEach(error -> { System.out.println("field" + error.getField() + ", msg:" + error.getDefaultMessage()); errorMsg.append(error.getDefaultMessage()).append("!"); }); } exception.printStackTrace(); return new JsonResult(-1,errorMsg.toString() ); } } 

上面的代码就是取出里面的报错信息,组装成自己需要显示的信息(这里我们封装成一个json结构,包括状态码和信息返出去):

  1. 试验成果

将刚刚的请求再发一遍,现在就可以看到,错误信息已经按照我们规定的格式返回了:

{
  "code": -1, "msg": "密码不能为空!" }

 

 

 

a-from 自定义规则使用,及初始默认值规则校验失效问题

a-from 自定义规则使用,及初始默认值规则校验失效问题

  1. 自定义规则使用,下面以修改密码为例
    <template>
          <a-form
            ref="formRef"
            :model="userPassword":label-col="{ style: { width: '100px' } }"
            :rules="rules"
            label-width="80px"
          >
            <a-form-item label="原密码" name="originalPassword">//name在我们使用规则校验时,这个字段必填,用来对应个字段规则的判定
              <a-input-password
                v-model:value="userPassword.originalPassword"
                placeholder="请输入原密码"
                type="password"
                show-password
              />
            </a-form-item>
            <a-form-item label="新密码" name="newPassword">
              <a-input-password
                v-model:value="userPassword.newPassword"
                placeholder="请输入新密码"
                type="password"
                show-password
              />
            </a-form-item>
            <a-form-item label="确认密码" name="confirmPassword">
              <a-input-password
                v-model:value="userPassword.confirmPassword"
                placeholder="请确认密码"
                type="password"
                show-password
              />
            </a-form-item>
          </a-form>
    </template>
    
    <script lang="ts" setup>
    import { ref, reactive } from "vue";
    import { CloSEOutlined, CheckOutlined } from "@ant-design/icons-vue";
    import { notification } from "ant-design-vue";
    import { RuleObject } from "ant-design-vue/es/form/interface";
    const Passwordvisible=ref(false);
    const formRef = ref();
    const userPassword = reactive({
      originalPassword: "",
      newPassword: "",
      confirmPassword: "",
    });
    //新密码判定
    let validatePass = async (rule: RuleObject, value: string) => {
      const reg = /^[\u4e00-\u9fa5]+$/;
      if (value === "") {
        return Promise.reject("新密码不能为空!");
      } else if (reg.test(value)) {
        return Promise.reject("密码中不能出现汉字");
      } else {
        if (userPassword.confirmPassword !== "") {
          formRef.value.validateFields("confirmPassword");
        }
        return Promise.resolve();
      }
    };
    //确认密码判定
    let validatePass2 = async (rule: RuleObject, value: string) => {
      const reg = /^[\u4e00-\u9fa5]+$/;
      if (value === "") {//校验是否为空,我们在写自定义规则的时候,就可以把其他的,比如必填判定等,都可以写进一个方法了
        return Promise.reject("二次密码不能为空!");//reject报出警告
      } else if (reg.test(value)) {
        return Promise.reject("密码中不能出现汉字");
      } else if (value !== userPassword.newPassword) {
        return Promise.reject("两次输入密码不一致!");
      } else {
        return Promise.resolve();//resolve验证通过
      }
    };
    const rules = {
      originalPassword: [
        { required: true, message: "原密码不能为空", trigger: "blur" },
      ],
      newPassword: [{ required: true, validator: validatePass, trigger: "blur" }],//validator为自定义校验,后面写自己需要的校验方法即可
      confirmPassword: [
        { required: true, validator: validatePass2, trigger: "blur" },
      ],
    };
    const emit = defineEmits<{
      (event: "cancel", data: any): void;
    }>();
    const cancel = () => {
      emit("cancel", { type: "cancel" });
    };
    //进行规则校验 const submit = () => { formRef.value .validate() .then(() => { passwordSubmit(); }) .catch(() => {}); }; //修改密码 const passwordSubmit = () => { notification.success({ message: "密码修改成功", }); }; </script>
  2. 初始默认值,无法校验,明明存在默认值却一直警告未输入。
    这个原因:
    (1.)可以看下a-form绑定的:model="userPassword"是否和自己所要判定的值,比如a-input中的v-model:value="userPassword.originalPassword"一致,如果一致,就进行第二步,如果不一致,改后试下看看好了没,还是报未输入就进行第二步。
    (2.)将规则校验中{ required: true, message: "原密码不能为空", trigger: "blur" }的trigger删除,再试下,应该就好了。
    目前我就遇到了这两种情况。

c# 全局钩子实现扫码枪获取信息

c# 全局钩子实现扫码枪获取信息

转发   https://www.cnblogs.com/TBW-Superhero/p/8659306.html

 

1.扫描枪获取数据原理基本相当于键盘数据,获取扫描枪扫描出来的数据,一般分为两种实现方式。

  a)文本框输入获取焦点,扫描后自动显示在文本框内。

  b)使用键盘钩子,勾取扫描枪虚拟按键,根据按键频率进行手动输入和扫描枪扫描判断。

2.要实现系统钩子其实很简单,调用三个Win32的API即可。

SetwindowsHookEx 用于设置钩子。(设立一道卡子,盘查需要的信息)

CallNextHookEx 用于传递钩子(消息是重要的,所以从哪里来,就应该回到哪里去,除非你决定要封锁消息)

UnhookWindowsHookEx 卸载钩子(卸载很重要,卡子设多了会造成拥堵)

版本一:

复制代码

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
namespace SaomiaoTest2
{
    /// <summary>
    /// 获取键盘输入或者USB扫描枪数据 可以是没有焦点 应为使用的是全局钩子
    /// USB扫描枪 是模拟键盘按下
    /// 这里主要处理扫描枪的值,手动输入的值不太好处理
    /// </summary>
   public class BardCodeHooK
    {
        public delegate void BardCodeDeletegate(BarCodes barCode);
        public event BardCodeDeletegate BarCodeEvent;

        //定义成静态,这样不会抛出回收异常
        private static HookProc hookproc;


        public struct BarCodes
        {
            public int VirtKey;//虚拟吗
            public int ScanCode;//扫描码
            public string KeyName;//键名
            public uint Ascll;//Ascll
            public char Chr;//字符

            public string BarCode;//条码信息   保存最终的条码
            public bool IsValid;//条码是否有效
            public DateTime Time;//扫描时间,}

        private struct EventMsg
        {
            public int message;
            public int paramL;
            public int paramH;
            public int Time;
            public int hwnd;
        }

        [DllImport("user32.dll",CharSet = CharSet.Auto,CallingConvention = CallingConvention.StdCall)]
        private static extern int SetwindowsHookEx(int idHook,HookProc lpfn,IntPtr hInstance,int threadId);

        [DllImport("user32.dll",CallingConvention = CallingConvention.StdCall)]
        private static extern bool UnhookWindowsHookEx(int idHook);

        [DllImport("user32.dll",CallingConvention = CallingConvention.StdCall)]
        private static extern int CallNextHookEx(int idHook,int nCode,Int32 wParam,IntPtr lParam);

        [DllImport("user32",EntryPoint = "GetKeyNameText")]
        private static extern int GetKeyNameText(int IParam,StringBuilder lpBuffer,int nSize);

        [DllImport("user32",EntryPoint = "GetKeyboardState")]
        private static extern int GetKeyboardState(byte[] pbKeyState);

        [DllImport("user32",EntryPoint = "ToAscii")]
        private static extern bool ToAscii(int VirtualKey,int ScanCode,byte[] lpKeySate,ref uint lpChar,int uFlags);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetModuleHandle(string name);


        delegate int HookProc(int nCode,IntPtr lParam);
        BarCodes barCode = new BarCodes();
        int hKeyboardHook = 0;
        string strBarCode = "";

        private int KeyboardHookProc(int nCode,IntPtr lParam)
        {
            if (nCode == 0)
            {
                EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam,typeof(EventMsg));
                if (wParam == 0x100)//WM_KEYDOWN=0x100
                {
                    barCode.VirtKey = msg.message & 0xff;//虚拟吗
                    barCode.ScanCode = msg.paramL & 0xff;//扫描码
                    StringBuilder strKeyName = new StringBuilder(225);
                    if (GetKeyNameText(barCode.ScanCode * 65536,strKeyName,255) > 0)
                    {
                        barCode.KeyName = strKeyName.ToString().Trim(new char[] { ‘ ‘,‘\0‘ });
                    }
                    else
                    {
                        barCode.KeyName = "";
                    }
                    byte[] kbArray = new byte[256];
                    uint uKey = 0;
                    GetKeyboardState(kbArray);


                    if (ToAscii(barCode.VirtKey,barCode.ScanCode,kbArray,ref uKey,0))
                    {
                        barCode.Ascll = uKey;
                        barCode.Chr = Convert.tochar(uKey);
                    }

                    TimeSpan ts = DateTime.Now.Subtract(barCode.Time);

                    if (ts.TotalMilliseconds > 50)
                    {//时间戳,大于50 毫秒表示手动输入
                        strBarCode = barCode.Chr.ToString();
                    }
                    else
                    {
                        if ((msg.message & 0xff) == 13 && strBarCode.Length > 3)
                        {//回车
                            barCode.BarCode = strBarCode;
                            barCode.IsValid = true;
                        }
                        strBarCode += barCode.Chr.ToString();
                    }
                    barCode.Time = DateTime.Now;
                    if (BarCodeEvent != null)
                        BarCodeEvent(barCode);//触发事件
                    barCode.IsValid = false;
                }
            }
            return CallNextHookEx(hKeyboardHook,nCode,wParam,lParam);
        }

        //安装钩子
        public bool Start()
        {
            if (hKeyboardHook == 0)
            {
                hookproc = new HookProc(KeyboardHookProc);


                //GetModuleHandle 函数 替代 Marshal.GetHINSTANCE
                //防止在 framework4.0中 注册钩子不成功
                IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);

                //WH_KEYBOARD_LL=13
                //全局钩子 WH_KEYBOARD_LL
                //  hKeyboardHook = SetwindowsHookEx(13,hookproc,Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0);

                hKeyboardHook = SetwindowsHookEx(13,modulePtr,0);
            }
            return (hKeyboardHook != 0);
        }

        //卸载钩子
        public bool Stop()
        {
            if (hKeyboardHook != 0)
            {
                return UnhookWindowsHookEx(hKeyboardHook);
            }
            return true;
        }
    }
}

复制代码

在实践过程中,发现版本一的代码只能扫描条形码,如伴随二维码中的字母出现就不能正确获取数据。

版本二:

按 Ctrl+C 复制代码

c# 全局钩子实现扫码枪获取信息。

c# 全局钩子实现扫码枪获取信息。

转发   https://www.cnblogs.com/TBW-Superhero/p/8659306.html

 

1.扫描枪获取数据原理基本相当于键盘数据,获取扫描枪扫描出来的数据,一般分为两种实现方式。

  a)文本框输入获取焦点,扫描后自动显示在文本框内。

  b)使用键盘钩子,勾取扫描枪虚拟按键,根据按键频率进行手动输入和扫描枪扫描判断。

2.要实现系统钩子其实很简单,调用三个Win32的API即可。

SetWindowsHookEx 用于设置钩子。(设立一道卡子,盘查需要的信息)

CallNextHookEx 用于传递钩子(消息是重要的,所以从哪里来,就应该回到哪里去,除非你决定要封锁消息)

UnhookWindowsHookEx 卸载钩子(卸载很重要,卡子设多了会造成拥堵)

版本一:

复制代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
namespace SaomiaoTest2
{
    /// <summary>
    /// 获取键盘输入或者USB扫描枪数据 可以是没有焦点 应为使用的是全局钩子
    /// USB扫描枪 是模拟键盘按下
    /// 这里主要处理扫描枪的值,手动输入的值不太好处理
    /// </summary>
   public class BardCodeHooK
    {
        public delegate void BardCodeDeletegate(BarCodes barCode);
        public event BardCodeDeletegate BarCodeEvent;

        //定义成静态,这样不会抛出回收异常
        private static HookProc hookproc;


        public struct BarCodes
        {
            public int VirtKey;//虚拟吗
            public int ScanCode;//扫描码
            public string KeyName;//键名
            public uint Ascll;//Ascll
            public char Chr;//字符

            public string BarCode;//条码信息   保存最终的条码
            public bool IsValid;//条码是否有效
            public DateTime Time;//扫描时间,
        }

        private struct EventMsg
        {
            public int message;
            public int paramL;
            public int paramH;
            public int Time;
            public int hwnd;
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern bool UnhookWindowsHookEx(int idHook);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);

        [DllImport("user32", EntryPoint = "GetKeyNameText")]
        private static extern int GetKeyNameText(int IParam, StringBuilder lpBuffer, int nSize);

        [DllImport("user32", EntryPoint = "GetKeyboardState")]
        private static extern int GetKeyboardState(byte[] pbKeyState);

        [DllImport("user32", EntryPoint = "ToAscii")]
        private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeySate, ref uint lpChar, int uFlags);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetModuleHandle(string name);


        delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
        BarCodes barCode = new BarCodes();
        int hKeyboardHook = 0;
        string strBarCode = "";

        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            if (nCode == 0)
            {
                EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg));
                if (wParam == 0x100)//WM_KEYDOWN=0x100
                {
                    barCode.VirtKey = msg.message & 0xff;//虚拟吗
                    barCode.ScanCode = msg.paramL & 0xff;//扫描码
                    StringBuilder strKeyName = new StringBuilder(225);
                    if (GetKeyNameText(barCode.ScanCode * 65536, strKeyName, 255) > 0)
                    {
                        barCode.KeyName = strKeyName.ToString().Trim(new char[] { '' '', ''\0'' });
                    }
                    else
                    {
                        barCode.KeyName = "";
                    }
                    byte[] kbArray = new byte[256];
                    uint uKey = 0;
                    GetKeyboardState(kbArray);


                    if (ToAscii(barCode.VirtKey, barCode.ScanCode, kbArray, ref uKey, 0))
                    {
                        barCode.Ascll = uKey;
                        barCode.Chr = Convert.ToChar(uKey);
                    }

                    TimeSpan ts = DateTime.Now.Subtract(barCode.Time);

                    if (ts.TotalMilliseconds > 50)
                    {//时间戳,大于50 毫秒表示手动输入
                        strBarCode = barCode.Chr.ToString();
                    }
                    else
                    {
                        if ((msg.message & 0xff) == 13 && strBarCode.Length > 3)
                        {//回车
                            barCode.BarCode = strBarCode;
                            barCode.IsValid = true;
                        }
                        strBarCode += barCode.Chr.ToString();
                    }
                    barCode.Time = DateTime.Now;
                    if (BarCodeEvent != null)
                        BarCodeEvent(barCode);//触发事件
                    barCode.IsValid = false;
                }
            }
            return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
        }

        //安装钩子
        public bool Start()
        {
            if (hKeyboardHook == 0)
            {
                hookproc = new HookProc(KeyboardHookProc);


                //GetModuleHandle 函数 替代 Marshal.GetHINSTANCE
                //防止在 framework4.0中 注册钩子不成功
                IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);

                //WH_KEYBOARD_LL=13
                //全局钩子 WH_KEYBOARD_LL
                //  hKeyboardHook = SetWindowsHookEx(13, hookproc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);

                hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0);
            }
            return (hKeyboardHook != 0);
        }

        //卸载钩子
        public bool Stop()
        {
            if (hKeyboardHook != 0)
            {
                return UnhookWindowsHookEx(hKeyboardHook);
            }
            return true;
        }
    }
}
复制代码

在实践过程中,发现版本一的代码只能扫描条形码,如伴随二维码中的字母出现就不能正确获取数据。

版本二:

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

版本二中的代码,实践中发现出现了获取扫描数据却省略“+”加号的情况出现。

因此在版本二中备选处添加

复制代码
             //判断是+ 强制添加+
                    else if (_key.Length == 5 && msg.paramH == 0&&msg.paramL==78&&msg.message==107)// && msg.paramH == 0
                    {
                        // 根据键盘状态和shift缓存判断输出字符  
                        _cur = Convert.ToChar(''+'').ToString();
                        _result[_result.Count - 1] += _cur;
                    }
复制代码

3.winform在无焦点情况下的使用方式:

复制代码
using BarcodeScanner;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace BarCodeTest
{
    public partial class Form1 : Form
    {
        private ScanerHook listener = new ScanerHook();  
        public Form1()
        {
            InitializeComponent();
            listener.ScanerEvent += Listener_ScanerEvent;  
        }
        private void Listener_ScanerEvent(ScanerHook.ScanerCodes codes)
        {
            textBox3.Text = codes.Result;         
        }  
        private void Form1_Load(object sender, EventArgs e)
        {
            listener.Start();  
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            listener.Stop();  
        }  
    }
}
复制代码

 

我们今天的关于day11 Django: froms组件: 数据校验 显示错误信息 渲染数据 重置数据 自定义规则 局部钩子,全局钩子的分享已经告一段落,感谢您的关注,如果您想了解更多关于@Valid 数据校验 + 自定义全局异常信息、a-from 自定义规则使用,及初始默认值规则校验失效问题、c# 全局钩子实现扫码枪获取信息、c# 全局钩子实现扫码枪获取信息。的相关信息,请在本站查询。

本文标签: