最近在开发遇到这么一个问题,客户想做一个小型车辆管理程序,在登记车辆的时候想支持手动录入车牌的功能,这个一般都是门岗那里用,客户门岗的大爷打字二指禅,玩不转键盘。那咋办,该办还得要办,是不是?翻遍各种资料,不是收费就是太丑,我不禁感叹,难道我Winfrom世道要衰亡了嘛。难道我还要去用wpf,或者写一套插件啥的来搞嘛?在查资料的过程中,我发现其实这个花里胡哨的东西,基本都是前端实现的比较多。因此,计上心头 我用winform嵌套谷歌V8引擎搭个架子,再打通前后端交互不就可以了嘛,以后有什么稀奇古怪的交互都用这种方式来玩。因此产生了下文的故事····
从某网站搞了一个前端的车辆输入框,然后改了改 因为原版的我感觉不是很好用,而且我们不是还要和winform做交互嘛,当然得改咯,下图是原版

 

 

这个在浏览器里显示还行,不过要是在winform程序里那可就牵强了,因为底部的键盘会消失看不到,因此我给改了改样式,成品变成如下

 

 

 下面是程序片段
1.外部输入框的演示 主要是获取焦点的处理 Txt_CarNo_GotFocus方法

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WebWinform
{
    public partial class InputForm : Form
    {
        WebMainForm carForm;
        public InputForm()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
            Txt_CarNo.GotFocus += Txt_CarNo_GotFocus;
            Txt_CarNo.LostFocus += Txt_CarNo_LostFocus;
        }

        private void Txt_CarNo_LostFocus(object sender, EventArgs e)
        {
            //carForm?.Hide();
        }

        //清空回调
        private void CarForm_OnClear()
        {
            Console.WriteLine("JS 点击了清空");
            Txt_CarNo.Clear();
        }
        //关闭回调
        private void CarForm_OnClose()
        {
            carForm?.Hide();
        }
        //确认回调
        private void CarForm_OnOk(string obj)
        {
            Txt_CarNo.Text = obj;
            carForm.Hide();
        }

        /// <summary>
        /// 测试界面输入框获得焦点
        /// </summary>
        private void Txt_CarNo_GotFocus(object sender, EventArgs e)
        {
            if (carForm is null)
            {
                carForm = new WebMainForm();
                carForm.OnOk += CarForm_OnOk;
                carForm.OnClose += CarForm_OnClose;
                carForm.OnClear += CarForm_OnClear;
                carForm.Dock = DockStyle.Fill;
                carForm.TopLevel = false;
                Pnl_Input.Controls.Add(carForm);
            }

            carForm.Show();

            if (!string.IsNullOrWhiteSpace(Txt_CarNo.Text))
            {
                carForm.SetCarNo(Txt_CarNo.Text.ToUpper().Trim());
            }
        }
    }
}

 

 2.这里是界面前后端交互的主要逻辑,部分地方我已做好了注释

public event Action<string> OnOk;
public event Action OnClose;
public event Action OnClear;

上面这几个事件,是前端界面触发调用的,后端订制对应的处理方式

SetCarNo这个方法是后端的触发,前端处理的,主要文件在Index.js里
using CefSharp;
using CefSharp.DevTools.Network;
using CefSharp.Internals;
using CefSharp.Web;
using CefSharp.WinForms;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WebWinform
{
    public partial class WebMainForm : Form
    {
        public event Action<string> OnOk;
        public event Action OnClose;
        public event Action OnClear;
        private readonly ChromiumWebBrowser browser;
        private bool _enableDebug;
        public WebMainForm(bool enableDebug = false)
        {

            InitializeComponent();
            CefSharpSettings.WcfEnabled = true;

            //HtmlString html = HtmlString.FromFile("车牌键盘.html");
            //调试设定
            if (enableDebug)
            {
                _enableDebug = true;
                var setting = new CefSettings { RemoteDebuggingPort = 7777 };
                Cef.Initialize(setting);
            }

            browser = new ChromiumWebBrowser(Environment.CurrentDirectory + "\\车牌键盘.html");
            // 前端调后端 sync
            browser.JavascriptObjectRepository.ResolveObject += JavascriptObjectRepository_ResolveObject;
            browser.LoadingStateChanged += OnBrowserLoadingStateChanged;
            browser.FrameLoadEnd += Browser_FrameLoadEnd;
        }

        /// <summary>
        /// 屏蔽本窗口焦点
        /// </summary>
        protected override CreateParams CreateParams
        {
            get
            {
                const int WS_EX_NOACTIVATE = 0x08000000;
                const int WS_CHILD = 0x40000000;
                CreateParams cp = base.CreateParams;
                cp.Style |= WS_CHILD;
                cp.ExStyle |= WS_EX_NOACTIVATE;
                return cp;
            }
        }

        /// <summary>
        /// 绑定JS对象事件
        /// </summary>
        private void JavascriptObjectRepository_ResolveObject(object sender, CefSharp.Event.JavascriptBindingEventArgs e)
        {
            var repo = e.ObjectRepository;
            switch (e.ObjectName)
            {
                case "bound":
                    repo.Register("bound", new BoundObject() { OnOk = OnOk, OnClose = OnClose, OnClear = OnClear }, isAsync: false, options: BindingOptions.DefaultBinder);

                    break;
                case "boundAsync":
                    repo.Register("boundAsync", new BoundObject() { OnOk = OnOk, OnClose = OnClose, OnClear = OnClear }, isAsync: true, options: BindingOptions.DefaultBinder);
                    break;
                default:
                    break;
            }
        }
        //状态改变
        private void OnBrowserLoadingStateChanged(object sender, LoadingStateChangedEventArgs args)
        {
            if (args.IsLoading)
            {
                if (_enableDebug) browser.ShowDevTools();
            }
        }

        //加载完毕
        private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e)
        {
            Console.WriteLine("Load OK");
        }
        /// <summary>
        /// 后端向前端 设置输入文本
        /// </summary>
        /// <param name="carNo">车牌号</param>
        public void SetCarNo(string carNo)
        {
            browser.GetBrowser().MainFrame.EvaluateScriptAsync($"setCarNo('{carNo}' )").ContinueWith(c =>
            {
                if (c.Result.Success)
                {

                }
                else
                {
                    MessageBox.Show(c.Result.Message);
                }
            }
           );
        }

        /// <summary>
        /// 后端向前端 清空
        /// </summary>
        public void ClearCarNo()
        {
            browser.GetBrowser().MainFrame.EvaluateScriptAsync($"setClear()").ContinueWith(c =>
            {
                if (c.Result.Success)
                {

                }
                else
                {
                    MessageBox.Show(c.Result.Message);
                }
            }
           );
        }
        private void LoadUrl(string url)
        {
            if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
            {
                browser.Load(url);
            }
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            Text = "CefSharp";
            this.Controls.Add(browser);
        }
    }
}

以下是Index.js文件,处理前端的输入逻辑

+(function (e) {
    var def = $.extend(true, {}, { beforeOk: function (str) { }, beforeClose: function () { }, beforeClear: function () { } });
    jQuery
        .extend({
            carNoPicker: function (setter) {
                $.extend(true, window, {}, def, setter);
            }
        });
})(jQuery);
var provinces = new Array("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""); var keyNums = new Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "A", "S", "D", "F", "G", "H", "J", "K", "L", "OK", "Z", "X", "C", "V", "B", "N", "M", "Del");
var next = 0;
function showProvince() {
    $("#pro").html(""); var ss = ""; for (var i = 0; i < provinces.length; i++) { ss = ss + addKeyProvince(i) }
    $("#pro").html("<ul class='clearfix ul_pro'>" + ss + "<li class='li_close' onclick='closePro();'><span>关闭</span></li><li class='li_clean' onclick='cleanPro();'><span>清空</span></li></ul>");
}
function showKeybord() {
    $("#pro").html(""); var sss = ""; for (var i = 0; i < keyNums.length; i++) { sss = sss + '<li class="ikey ikey' + i + ' ' + (i > 9 ? "li_zm" : "li_num") + ' ' + (i > 28 ? "li_w" : "") + '" ><span onclick="choosekey(this,' + i + ');">' + keyNums[i] + '</span></li>' }
    $("#pro").html("<ul class='clearfix ul_keybord'>" + sss + "</ul>");
}
function addKeyProvince(provinceIds) { var addHtml = '<li>'; addHtml += '<span onclick="chooseProvince(this);">' + provinces[provinceIds] + '</span>'; addHtml += '</li>'; return addHtml; }
function chooseProvince(obj) { $(".input_pro span").text($(obj).text()); $(".input_pro").addClass("hasPro"); $(".input_pp").find("span").text(""); $(".ppHas").removeClass("ppHas"); next = 0; showKeybord(); }
function choosekey(obj, jj) {
    if (jj == 29) {//ok
        var oldCarNo = $(".car_input").attr("data-pai");
        var newCarNo = $("ul.clearfix.ul_input").text();
        console.log(oldCarNo);
        window.beforeOk(newCarNo && newCarNo.replace(/\s+/g, ""));
        layer.closeAll();
    } else if (jj == 37) {//del
        if ($(".ppHas").length == 0) { $(".hasPro").find("span").text(""); $(".hasPro").removeClass("hasPro"); showProvince(); next = 0; }
        $(".ppHas:last").find("span").text(""); $(".ppHas:last").removeClass("ppHas"); next = next - 1; if (next < 1) { next = 0; }
        console.log(next);
    } else {
        if (next > 5) { return }
        console.log(next); for (var i = 0; i < $(".input_pp").length; i++) {
            if (next == 0 & jj < 10 & $(".input_pp:eq(" + next + ")").hasClass("input_zim")) { layer.open({ content: '车牌第二位为字母', skin: 'msg', time: 1 }); return }
            $(".input_pp:eq(" + next + ")").find("span").text($(obj).text()); $(".input_pp:eq(" + next + ")").addClass("ppHas"); next = next + 1; if (next > 5) { next = 6; }
            getpai(); return
        }
    }
}
function setCarNo(num) {
    if (num && typeof (num) == 'string' && num.length <= 7) {
        var charr = num.split("");
        try {
            cleanPro();
            $(".input_pro").trigger("click");
            for (var zi of charr) {
                var ele = $(`div.layui-m-layercont li span:contains(${zi})`).filter(function () { return $(this).text() == zi;});
                if (ele.length == 0) break;
                ele.trigger("click", ele, ele.text());
            }
        } catch (e) {

        }
    }
}
function setClear() {
    $(".input_pro").trigger("click");
}
function closePro() { window.beforeClose(); layer.closeAll(); setClear() }
function cleanPro() { window.beforeClear(); $(".ul_input").find("span").text(""); $(".hasPro").removeClass("hasPro"); $(".ppHas").removeClass("ppHas"); next = 0; }
function trimStr(str) { return str.replace(/(^\s*)|(\s*$)/g, ""); }
function getpai() { var pai = trimStr($(".car_input").text()); $(".car_input").attr("data-pai", pai); }
window.onload = function () {
    $(".input_pro").click(function () { layer.open({ type: 1, content: '<div id="pro"></div>', anim: 'up', shade: false, style: 'position:fixed; bottom:0; left:0; width: 100%; height: auto; padding:0; border:none;' }); showProvince() })
    $(".input_pp").click(function () { if ($(".input_pro").hasClass("hasPro")) { layer.open({ type: 1, content: '<div id="pro"></div>', anim: 'up', shade: false, style: 'position:fixed; bottom:0; left:0; width: 100%; height: auto; padding:0; border:none;' }); showKeybord() } else { $(".input_pro").click() } })
    $(".input_pro").trigger("click");
}
View Code

下面是html文件,里面有C#注册后台方法的部分

<!DOCTYPE html>
<!-- saved from url=(0048)https://www.jq22.com/demo/jqueryCph201807042157/ -->
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>车牌键盘</title>
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
            list-style: none;
            border: 0;
            box-sizing: border-box;
        }

        body {
            margin: 0;
            padding: 0;
        }

        .clearfix:after {
            content: ".";
            display: block;
            font-size: 0;
            height: 0;
            clear: both;
            visibility: hidden;
        }

        .clearfix {
            display: inline-table;
        }

        *html .clearfix {
            height: 1%;
        }

        .clearfix {
            display: block;
        }

        * + html .clearfix {
            min-height: 1%;
        }

        .che_tit {
            text-align: center;
            padding: 20px;
        }

        .input_pp.input_zim {
            font-weight: bold;
        }

        .input_pro.hasPro {
            color: orangered;
        }

        .ul_pro {
            background-color: #CED3D9;
            text-align: center;
            padding: 4px 2px;
            font-size: 14px;
        }

            .ul_pro li {
                float: left;
                width: 11.11%;
                padding: 2px;
                box-sizing: border-box;
            }

            .ul_pro .li_close {
                float: right;
                width: 22.22%;
            }

                .ul_pro .li_close span {
                    background-color: #ACB3BB;
                }

            .ul_pro .li_clean {
                float: right;
                width: 22.22%;
            }

            .ul_pro li span {
                display: block;
                background-color: #fff;
                border-radius: 4px;
                box-shadow: 2px 2px 2px #888888;
                /* max-width:48px; margin:0 auto; */
                line-height: 32px;
                padding-top: 2px;
                cursor: pointer;
            }

                .ul_pro li span:active {
                    background-color: #4DA9F2;
                    color: #fff;
                }

        .ul_input {
            padding: 20px;
            width: 350px;
            margin: 0 auto;
        }

            .ul_input li {
                float: left;
                width: 14%;
                padding: 2px;
                text-align: center;
            }

                .ul_input li span {
                    display: block;
                    background-color: #fff;
                    border: 1px solid #ccc;
                    border-radius: 4px;
                    width: 40px;
                    margin: 0 auto;
                    height: 40px;
                    line-height: 40px;
                }

        .ul_keybord {
            background-color: #CED3D9;
            text-align: center;
            padding: 4px 2px;
            font-size: 14px;
        }

            .ul_keybord li {
                float: left;
                width: 10%;
                padding: 2px;
                box-sizing: border-box;
            }

            .ul_keybord .ikey20 {
                margin-left: 5%;
            }

            .ul_keybord .li_w {
                width: 11.11%;
            }

            .ul_keybord .li_close {
                float: right;
                width: 22.22%;
            }

                .ul_keybord .li_close span {
                    background-color: #ACB3BB;
                }

            .ul_keybord .li_clean {
                float: right;
                width: 22.22%;
            }

            .ul_keybord li span {
                display: block;
                background-color: #fff;
                border-radius: 4px;
                box-shadow: 2px 2px 2px #888888;
                /* max-width:48px; margin:0 auto; */
                line-height: 32px;
                padding-top: 2px;
            }

                .ul_keybord li span:active {
                    background-color: #4DA9F2;
                    color: #fff;
                }
    </style>
    <link href="./CarNoKebord_files/layer.css" type="text/css" rel="styleSheet" id="layermcss">

    <script type="text/javascript">
        ; (async () => {
            await CefSharp.BindObjectAsync("bound", "boundAsync");

            //bound.setCallBack(myFunction);

            //function myFunction(param) {
            //    console.log("Test %c" + param, "background:red");
            //}
        })();
    </script>
    <script src="./CarNoKebord_files/jquery.min.js"></script>
    <script src="./CarNoKebord_files/layer.js" type="text/javascript"></script>
    <script src="./CarNoKebord_files/index.js" type="text/javascript"></script>
    <script type="text/javascript">
        $.carNoPicker({
            beforeOk: function (str) {
                boundAsync.beforeOk(str);
            },
            beforeClose: function () {
                boundAsync.beforeClose();
            },
            beforeClear: function () {
                boundAsync.beforeClear();
            }
        })
    </script>
</head>
<body style="">
    <div class="">
        <h3 style="text-align: center">请输入车牌号</h3>
        <div class="car_input">
            <ul class="clearfix ul_input">
                <li class="input_pro"><span></span></li>
                <li class="input_pp input_zim"><span></span></li>
                <li class="input_pp"><span></span></li>
                <li class="input_pp"><span></span></li>
                <li class="input_pp"><span></span></li>
                <li class="input_pp"><span></span></li>
                <li class="input_pp"><span></span></li>
            </ul>
        </div>
    </div>
</body>
</html>
boundAsync 与bound 那部分是注册后端方法的
carNoPicker这个是自己封装的jq小插件

注意事项

1.Cefsharp的编译必须选择一种确定的模式,x86或者是x64否则Common编译不通过
2.解决方案也要设定好对应的平台

 

 

 githu地址:https://github.com/zhaohaifa/DemoSolution

各位看客大爷,请细细斟酌,不懂的地方下方留言