您现在的位置是:网站首页> C#技术

C# WebView2的相关总结

摘要

C# WebView2的相关总结


C# 使用Microsoft Edge WebView2的相关总结

C#设置WebBrowser使用Edge内核

使用C#调用Edge浏览器内核控件来访问网站

c# WebBrowser控制台输出执行js后的网页内容

用WebBrowser获取JS动态加载以后的页面代码并保存图片





C# 使用Microsoft Edge WebView2的相关总结

一、C#和js互相调用 

1、js调用C# 

C#代码如下:

为避免出现手机端排版混乱,用必要的占位字符进行处理

webView.CoreWebView2.AddHostObjectToScript("webBrowserObj", new ScriptCallbackObject());

await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("var webBrowserObj= window.chrome.webview.hostObjects.webBrowserObj;");

像网页里面注入变量,这样网页调用时候不用每次写window.chrome.webview.hostObjects.webBrowserObj调用,最主要的是为了兼容之前cef里面Js的写法。

[ClassInterface(ClassInterfaceType.AutoDual)]

     [ComVisible(true)]

     /// <summary>

     /// 网页调用C#方法

     /// </summary>

     public class ScriptCallbackObject

     {

     public string UserName { get; set; } = "我是C#属性";

 

     public void ShowMessage()

     {

     MessageBox.Show("网页调用C#");

     }

 

     public void ShowMessageArg(string arg)

     {

     MessageBox.Show("【网页调用C#】:" + arg);

     }

 

     public string GetData(string arg)

     {

     return "【网页调用C#获取数据】;" + arg;

     }

 

     [System.Runtime.CompilerServices.IndexerName("Items")]

     public string this[int index]

     {

     get { return m_dictionary[index]; }

     set { m_dictionary[index] = value; }

     }

     private Dictionary<int, string> m_dictionary = new Dictionary<int, string>();

     }

JS调用如下;

function callCsharp2() {

     var data2 = $("#txtArg").attr("value"); //大坑 值不会时刻变化 // alert(data2); var data = $("#txtArg").val(); 

     window.chrome.webview.hostObjects.webBrowserObj.ShowMessageArg(data); //window.chrome.webview.postMessage(data); };

async function callCsharp3() {

     var data = $("#txtArg").val();

     var result = await webBrowserObj.GetData(data);

     alert(result);

};

 

async function callCsharp4() {

 

     const propValue = await webBrowserObj.UserName;

     console.log(propValue);

     alert(propValue);

};

2、C#调用JS

private void callJS_Click(object sender, RoutedEventArgs e)

     {

     webView.CoreWebView2.ExecuteScriptAsync("ShowMessage()");

     }

 

     private void callJSArg_Click(object sender, RoutedEventArgs e)

     {

     webView.CoreWebView2.ExecuteScriptAsync($"ShowMessageArg('{txtArg.Text}')");

     }

 

     private async void callJSGetData_Click(object sender, RoutedEventArgs e)

     {

     var jsResult = await webView.CoreWebView2.ExecuteScriptAsync($"GetData('{txtArg.Text}')");

     if (!string.IsNullOrEmpty(jsResult))

     {

     MessageBox.Show(jsResult);

     }

     }

js里面的代码

//2、C#调用网页

     var jsVar = '123';

     function Hello() {

     alert('调用Js' + jsVar);

     };

 

     function ShowMessage() {

     alert('我是网页');

     };

     function ShowMessageArg(arg) {

     alert('【我是网页消息框】' + arg);

     };

     function GetData(arg) {

     return '【我是网页返回给你】:' + arg;

     };

二、缩放问题

webView.CoreWebView2.Settings.IsZoomControlEnabled = false;

只能禁止鼠标缩放,不能禁止手势缩放。 见问题 


另外触摸到底部门的时候 有弹跳,暂时也无法解决。


以上就是C# 使用Microsoft Edge WebView2的相关总结的详细内容,更多关于C# 使用Microsoft Edge WebView2的资料请关注我们其它相关文章!



C#设置WebBrowser使用Edge内核

1. 问题描述

用C#写了一个小工具, 需要显示网页上的内容, 但WebBrowser使用的是IE内核, 不能很好的展示网页


2. 解决方法

通过一些尝试, 在巧合之下找出了一种方法, 成功的让WebBrowser用上了Edge内核, 先上图

1.png

WebBrowser使用Edge内核

上代码


/// <summary>

/// 修改注册表信息使WebBrowser使用指定版本IE内核

/// </summary>

public static void SetFeatures(UInt32 ieMode) {

    if (LicenseManager.UsageMode != LicenseUsageMode.Runtime) {

        throw new ApplicationException();

    }

    //获取程序及名称

    string appName = System.IO.Path.GetFileName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);

    string featureControlRegKey = "HKEY_CURRENT_USER\\Software\\Microsoft\\Internet Explorer\\Main\\FeatureControl\\";

    //设置浏览器对应用程序(appName)以什么模式(ieMode)运行

    Registry.SetValue(featureControlRegKey + "FEATURE_BROWSER_EMULATION", appName, ieMode, RegistryValueKind.DWord);

    //不晓得设置有什么用

    Registry.SetValue(featureControlRegKey + "FEATURE_ENABLE_CLIPCHILDREN_OPTIMIZATION", appName, 1, RegistryValueKind.DWord);

}

这个函数是网上复制的, 传入11000是IE11, 9000是IE9, 只不过当试着传入6000时, 理应是IE6, 可实际却是Edge, 这时进一步测试, 当传入除IE现有版本以外的一些数值时WebBrowser都使用Edge内核

设置注册表IE版本6000

2.png


3. 结论

将IE版本号设置为非IE版本的数值就能使用Edge内核

这个方法目前不知道原理, 并且也没有测试过稳定性, 以上内容仅供参考



使用C#调用Edge浏览器内核控件来访问网站

1.框架设置:

1.png

 

2.添加引用 Microsoft.Toolkit.Forms.UI.Controls.WebView

2.png

 

 

3. 拖控件(或者直接写代码也可以)

3.png

 

 4.访问网页:

4.png

 


c# WebBrowser控制台输出执行js后的网页内容

还是处理视频下载所相关的问题。


有些网站,它的页面代码是由页面加载后js动态生成,那么其原始的html便不能用。页面渲染后的代码,是我们需要的


c#中,我用WebBrowser这个控件处理。设置项目类型为控制台程序,加Form承载WebBrowser实现。


记录代码以做备忘:


using System;

using System.IO;

using System.Net;

using System.Runtime.InteropServices;

using System.Text;

using System.Windows.Forms;

using Microsoft.Win32;

 

namespace crpj

{

    [ComVisible(true)]

    public class Form : System.Windows.Forms.Form

    {

        protected override void SetVisibleCore(bool value)

        {

            base.SetVisibleCore(false);

        }

 

        public string GetHtmlCode(string url)

        {

            using (var wc = new WebClient())

            {

                wc.Encoding = Encoding.UTF8;

                return wc.DownloadString(url);

            }

        }

    }

 

    class Program

    {

        private static Timer tmrGet = new Timer();

        private static Timer tmrExit = new Timer();

        private static WebBrowser browser = new WebBrowser();

        //延时获取?

        private static int delay = 0;

        //js注入脚本

        private static string jsCode;

 

        //禁止网页跳转声音

        const int FEATURE_DISABLE_NAVIGATION_SOUNDS = 21;

        const int SET_FEATURE_ON_PROCESS = 0x00000002;

 

        [DllImport("urlmon.dll")]

        [PreserveSig]

        [return: MarshalAs(UnmanagedType.Error)]

        static extern int CoInternetSetFeatureEnabled(

            int FeatureEntry,

            [MarshalAs(UnmanagedType.U4)] int dwFlags,

            bool fEnable);

 

        /// <summary>

        /// 应用程序的主入口点。

        /// </summary>

        /// 参数列表:url delay jscode

        [STAThread]

        static void Main(string[] args)

        {

            if (args.Length == 0)

            {

                Console.WriteLine("error: You must provide at least one URL.");

                return;

            }

 

            CoInternetSetFeatureEnabled(

                FEATURE_DISABLE_NAVIGATION_SOUNDS,

                SET_FEATURE_ON_PROCESS,

                true);

            ChackAndSetBrowserEmulation();

 

            var form = new Form();

            form.Controls.Add(browser);

            browser.ObjectForScripting = form;

            browser.ScriptErrorsSuppressed = true;

            browser.DocumentCompleted += browser_DocumentCompleted;

            browser.Navigate(args[0]);

 

            if (args.Length > 1)

                delay = int.Parse(args[1]);

            if (args.Length > 2)

                jsCode = args[2];

 

            //因为页面有时需加载js初始化等操作,延时获取其页面内容

            tmrGet.Tick += new EventHandler(tmrGet_Tick);

            if (delay > 0)

                tmrGet.Interval = delay;

 

            //有些网页不触发complete事件,或者时间很长,此定时器做判断,以60秒为界,自结束

            tmrExit.Tick += new EventHandler(tmrExit_Tick);

            tmrExit.Interval = 90000;

            tmrExit.Start();

 

            Application.Run(form);

        }

 

        static void tmrExit_Tick(object sender, EventArgs e)

        {

            OutputHtml();

        }

 

        //WebBrowser以IE11版本做页面渲染 

        static void ChackAndSetBrowserEmulation()

        {

            try

            {

                string keyName = @"SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION";

                using (var key = Registry.CurrentUser.OpenSubKey(keyName, true))

                {

                    string valueName = Path.GetFileName(Application.ExecutablePath);

                    if (key.GetValue(valueName) == null)

                        key.SetValue(valueName, 11001);

                }

            }

            catch

            {

            }

        }

 

        static void tmrGet_Tick(object sender, EventArgs e)

        {

            tmrGet.Stop();

            OutputHtml();

        }

 

        static void OutputHtml()

        {

            tmrExit.Stop();

            //避免韩文等乱码

            Console.OutputEncoding = Encoding.UTF8;

            //browser.DocumentText取不到执行js之后的body文件

            string html = browser.Document.GetElementsByTagName("html")[0].OuterHtml;

            Console.Write(html);

            Application.Exit();

        }

 

        static void ExecJS(string jsCode)

        {

            var script = browser.Document.CreateElement("script");

            script.SetAttribute("type", "text/javascript");

            script.SetAttribute("text", "function _func() {" + jsCode + "}");

            browser.Document.Body.AppendChild(script);

            browser.Document.InvokeScript("_func");

        }

 

        static void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)

        {

            if (browser.ReadyState == WebBrowserReadyState.Complete && e.Url == browser.Url)

            {

                //是否需要js注入?

                if (!string.IsNullOrEmpty(jsCode))

                {

                    ExecJS(jsCode);

                    System.Threading.Thread.Sleep(500);

                }

 

                if (delay == 0)

                    OutputHtml();

                else

                    tmrGet.Start();

            }

        }

    }

}


如此处理,可能得到所需要的html代码。


其在控制台输出图示效果




 并基于此思路,设计进程输出管理器:


internal class ProcessOutputMgr

    {

        private static object syncObj = new Object();

        private Process process = new Process();

        private StringBuilder allData = new StringBuilder();

        private bool exitedCalled = false;

 

        public ProcessMgr(string fileName, string args)

        {

            var startInfo = new ProcessStartInfo(fileName);

            startInfo.WindowStyle = ProcessWindowStyle.Hidden;

            startInfo.Arguments = args;

            startInfo.UseShellExecute = false;

            startInfo.CreateNoWindow = true;                   //crpj皆以utf-8输出,避免乱码                   startInfo.StandardOutputEncoding = Encoding.UTF8;

            startInfo.RedirectStandardOutput = true;

            startInfo.RedirectStandardError = true;

 

            process.StartInfo = startInfo;

            process.EnableRaisingEvents = true;  //一定要有这个才能触发Exited 事件

            process.Exited += process_Exited;

            process.OutputDataReceived += process_OutputDataReceived;

            process.ErrorDataReceived += process_ErrorDataReceived;

        }

 

        public event DataReceivedEventHandler OutputDataReceived;

        public event DataReceivedEventHandler ErrorDataReceived;

        public event Action<string> AllDataReceived;

 

        public bool Start()

        {

            bool result = process.Start();

            process.BeginOutputReadLine();

            process.BeginErrorReadLine();

            return result;

        }

 

        public void WaitForExit()

        {

            process.WaitForExit();

        }

 

        public bool WaitForExit(int milliseconds)

        {

            return process.WaitForExit(milliseconds);

        }

 

        private void process_Exited(object sender, EventArgs e)

        {

            if (!this.exitedCalled && this.allData.Length != 0)

            {

                this.exitedCalled = true;

                var handler = AllDataReceived;

                if (handler != null)

                    handler(this.allData.ToString());

            }

        }

 

        private void process_OutputDataReceived(object sender, DataReceivedEventArgs e)

        {

            lock (syncObj)

            {

                var handler = OutputDataReceived;

                if (handler != null)

                    handler(sender, e);

 

                if (e.Data != null)

                    this.allData.AppendLine(e.Data);

                else

                {

                    var process = sender as Process;

                    if (process.HasExited && !this.exitedCalled)

                    {

                        this.exitedCalled = true;

                        if (AllDataReceived != null)

                            AllDataReceived(this.addData.ToString());

                    }

                }

            }

        }

 

        private void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)

        {

            lock (syncObj)

            {

                var handler = ErrorDataReceived;

                if (handler != null)

                    handler(sender, e);

            }

        }

    }




用WebBrowser获取JS动态加载以后的页面代码并保存图片

很多网页的内容包括图片是用JS或Jquery动态加载的,用Webbrowser直接获得的源码是没有参考价值的,而JS加载后含所有element的代码很难获取的,起码笔者搜索了一圈下来看到的方法都几乎没有使用价值。

笔者这里分享一种解决方案,随便找个网页会动态加载内容的,不难发现需要加载的内容需要你滚动页面,视野范围内的内容就会动态加载,于是乎,办法就来了。

首先,你还必须先研究一下你需要抓取的网站的内容构造,比如笔者需要抓取的网页共有100张图片,并且需要页面滚动到图片位置这些图片才会加载。于是先用普通浏览器等加载完右击页面选检查,分析得到这些图片的class里面包含title,下面就可以根据这个来操作了。在webBrowser1_DocumentCompleted也就是加载完之后(注意并不是js也加载完),获取当前页面所有元素并存储在docall当中,然后开启Timer1。

private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {undefined
            currentEleIndex = 0;
            docAll = webBrowser1.Document.All;
            this.timer1.Enabled = true;
        }


在Timer1里面,根据上面的分析我们遍历所有元素,找到class包含title的图片元素并滚动到此元素,这时候js就会执行了,这里有一句scrollcount++ % 5 == 0来控制并不对每个图片都执行这个操作,因为页面图片太多,每个都执行一边没必要,这里设置每5个滚动一次,因为窗口可是范围一般都可以显示大于2行。这里有个要注意的,在页面滚动的时候会不断触发webBrowser1_DocumentCompleted事件。docAll和docAll.Count即元素总数也会不断改变。最后,在页面不再刷新的时候就可以执行后面的工作了,也就是这里 startprocess();所转向的函数。

        //webbrowser加载完后,要往下滚动来让js加载所有项目及图片
        //timer1隔一段时间滚动一下
        private void timer1_Tick(object sender, EventArgs e)
        {undefined
            while (currentEleIndex < docAll.Count)
            {undefined
                string s = docAll[currentEleIndex].GetAttribute("classname");


                if (s != null && s.Contains("title"))
                {undefined
                    if (scrollcount++ % 5 == 0)//每行5个图片
                    {undefined
                        docAll[currentEleIndex].ScrollIntoView(true);
                        currentEleIndex++;
                        return;
                    }
                }
                currentEleIndex++;
            }






            if (currentEleIndex >= docAll.Count)
            {undefined
                if (webBrowser1.Document.Body==null || !webBrowser1.Document.Body.InnerHtml.Contains("pagination"))
                {undefined
                    currentEleIndex = 0;
                    docAll = webBrowser1.Document.All;
                }
                else
                {undefined
                    this.timer1.Enabled = false;
                    this.textBox3.Text += "browsercompleted\n";
                    startprocess();
                }
               
            }
        }


以上是滚动到指定元素,如果只是简单的页面滚动,可以更简单

  int nowheight = 0;
                int stepnum = 10;
                int scrollstep = webBrowser1.Document.Body.ScrollRectangle.Height/stepnum;
                for (int i = 0; i < stepnum; i++)
                {undefined
                    nowheight += scrollstep;
                    webBrowser1.Document.Window.ScrollTo(0, nowheight);
                    System.Threading.Thread.Sleep(500);
                }



另外值得一提的是,此时已经可以获得js执行后的完整网页源代码,访问webBrowser1.Document.Body.InnerHtml而不是别的,切记!

void startprocess()
        {undefined
            savepictures();
            //滚动到底部并保存完图片,肯定加载完了,不需要timer对比源代码是否有变化,直接开始处理了
            oldDocText = webBrowser1.Document.Body.InnerHtml;
            switch (state)
            {undefined
                case 1: processPartsFirstPage(); break;
                case 2: processPartsPages(); break;
            }
        }


然后就是怎么把webbrowser里面的图片保存到本地了,也是网上搜索一圈也不好解决的,其实你大可以把源代码里用正则抠出来的图片链接用代码再下载一次,不过如果频繁访问的话会不会被网站封掉ip就难说了,而且webbrowser里面都有了如果能直接保存不更好吗,这里涉及到电脑剪贴板的访问,也就是说在程序运行当中,你就不能用电脑同时做一些编辑工作了,否则会因为剪贴板出错,因为你做编辑工作的时候肯定会不小心的用到复制粘贴操作。最后要说一下,这个要添加2个引用,1个是框架里的Microsoft.CSharp,1个是COM里面的Microsoft HTML Object Library。好了,上代码。

        void savepictures()
        {undefined
            string path = Application.StartupPath + "\\imgs\\front\\";

            foreach (HtmlElement he in docAll)
            {undefined
                if (he.GetAttribute("classname") != null && he.GetAttribute("classname").Contains("fill_img"))
                {undefined
                    string filename = he.GetAttribute("src");
                    int startpos = filename.LastIndexOf('/') + 1;
                    filename = filename.Substring(startpos, filename.Length - startpos);
                    
                    if (File.Exists(path + filename)) continue;


                    HTMLDocument doc = (HTMLDocument)webBrowser1.Document.DomDocument;
                    HTMLBody body = (HTMLBody)doc.body;
                    IHTMLControlRange rang = (IHTMLControlRange)body.createControlRange();
                    IHTMLControlElement Img = (IHTMLControlElement)he.DomElement; //图片地址    
                    rang.add(Img);
                    rang.execCommand("Copy", false, null);  //拷贝到内存      
                    Image pic= Clipboard.GetImage();
                    pic.Save(path + filename);
                    System.Threading.Thread.Sleep(50);
                }
            }
        }
























Top