webbrowser中DocumentCompleted的事件触发问题

Cout_Sev 2015-08-28 11:02:32
首先先向大家说一下我的软件设计思路:
写一个winform程序实现定时自动登录网站的功能(很简单的页面)
1、利用webbrowser获取得到某网站的页面内容,这期间用到了DocumentCompleted事件。
2、获取到用户名、密码2个文本输入框,自动填充内容并实现自动登录。
以上需求我已经完成并封装成一个静态类(如下代码),但是定时功能一直完成不了。

public static void loginMethod()
{
WebBrowser web = new WebBrowser();
web.Navigate(url);//url已初始化
web.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(web_DocumentCompleted);
}

private static void web_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser web = (WebBrowser)sender;
if (web.ReadyState == WebBrowserReadyState.Complete)
{
web.Document.GetElementById("tbUserNames").SetAttribute("value", _username);//_username已经初始化
web.Document.GetElementById("tbPassword").SetAttribute("value", _password);//_password已经初始化
HtmlElement btn = web.Document.GetElementById("btn_login");
btn.InvokeMember("click");
}
}


下面提出我的问题:
第一个疑问是:我在界面的button控件的响应函数中,直接调用以上的方法接口loginMethod,是可以实现自动登录的,但是通过调试发现当调用loginMethod完成后,并不是先触发 web_DocumentCompleted 事件,而是先执行int a=6,如下代码,这是为什么呢?
private void btnBegin_Click(object sender, EventArgs e)
{
LoginWeb.loginMethod();
int a = 6;
//sleep(5*1000)就算换成sleep函数也是先执行sleep函数再触发事件web_DocumentCompleted
}

第二个疑问是建立在第一个疑问的基础上的,我写了一个timer,每隔10秒执行一次自动登录的接口loginMethod,但是调试的时候发现"因为当前线程不在单线程单元中,故无法实例化 ActiveX 控件“这个问题,也就是说这个想法好像行不通。

第三个疑问,于是我就想到了多线程来实现,写了代码如下:
private void btnBegin_Click(object sender, EventArgs e)
{
Thread myThread = new Thread(autoLogin);
myThread.SetApartmentState(ApartmentState.STA);
myThread.Start();
int a = 6;
}
private void autoLogin()
{
LoginWeb.loginMethod();
int b = 6;
}

但是这次更加悲剧的是:autoLogin会执行,但是,当调用完自动登录的接口loginMethod后,并不会触发 web_DocumentCompleted事件,就算是int b=6 执行完之后也不会,这又是为什么呢?




哪位大神能帮忙解答一下,小弟是今年刚毕业的应届生,因为之前一直是搞C++,来到新公司以后被要求学C#,所以现在正在慢慢学习中,部分不懂的还望各位轻喷。
...全文
493 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
Cout_Sev 2015-09-02
  • 打赏
  • 举报
回复
引用 14 楼 bj_liliang 的回复:
[quote=引用 13 楼 bj_liliang 的回复:]

        Thread t = null;//创建线程,用来定时刷新数据。    
      
        public delegate void CallBackDelegate(List<Result> list);

       private void Form1_Load(object sender, EventArgs e)
        {
            cbd = CallBack;//设置回掉函数
        }

       private void  Callback()
        {
            //可以对主线程进行操作
        }
   
        private void MyTimmer(object obj)
         {
             try
             {
                 while (true)
                 {
                      Thread.Sleep(500);// 每次间隔的时间,自己设定

                     CallBackDelegate cbd = obj as CallBackDelegate;

                     this.BeginInvoke(cbd);//调用回掉。如果需要参数可以加上参数
                   
                 }
             }
             catch
             { 
             }
         }

       有了如上代码,在写一个按钮 作为开始按钮,触发MyTimmer
        private void button1_Click(object sender, EventArgs e)
        {
             t = new Thread(MyTimmer);
            t.IsBackground = true;
            t.Start(cbd);
        }

     这样就可以了

不好意思 上面定义委托的后面带了参数,是我没删,直接从自己的代码里贴过来的,凑合看吧 public delegate void CallBackDelegate();[/quote] 万分感谢提点 程序已完成 还学习了委托与事件。。十分感谢~!
Cout_Sev 2015-08-28
  • 打赏
  • 举报
回复
引用 4 楼 sunny906 的回复:
[quote=引用 3 楼 u010095182 的回复:] 2.第二个问题里我可是设置了一个timer而已哦,并没有Thread,请问修改哪一个的Thread属性? 3、那么为什么第一个问里,单击button按钮时他会触发事件呢?因为在我看来,单击button按钮与启动线程都是调用自动登录接口loginMethod,那么为什么button里的内容执行完之后他会返回触发DocumentCompleted事件,而当线程执行完了之后他不会回去触发DocumentCompleted事件呢?
2、timer里不能设置ApartmentState.STA,只有Thread对象才可以,myThread.SetApartmenApartmentState.STAtState(ApartmentState.STA); 3、因为点击button后,webbrowser会导航到指定的url, 而DocumentCompleted事件注册了web_DocumentCompleted方法,所以页面加载完成之后会调用文档上面的click事件[/quote] 谢谢~但是好像我说的不清楚让你误解了,我的意思是: 在第二个问里,我并没有新开拓线程,那么我又怎么解决"因为当前线程不在单线程单元中,故无法实例化 ActiveX 控件“这个问题呢,你说的”2、timer里不能设置ApartmentState.STA,只有Thread对象才可以,myThread.SetApartmenApartmentState.STAtState(ApartmentState.STA);“ 我知道STA是设置给线程的,但是我并没有开新的线程哦。 还有,你说的”因为点击button后,webbrowser会导航到指定的url, 而DocumentCompleted事件注册了web_DocumentCompleted方法,所以页面加载完成之后会调用文档上面的click事件“,那么,我button也是调用,新开拓了线程也是调用,为什么button调用结束后会触发而新线程调用后就算线程结束了也不会触发呢?
sunny906 2015-08-28
  • 打赏
  • 举报
回复
引用 3 楼 u010095182 的回复:
2.第二个问题里我可是设置了一个timer而已哦,并没有Thread,请问修改哪一个的Thread属性? 3、那么为什么第一个问里,单击button按钮时他会触发事件呢?因为在我看来,单击button按钮与启动线程都是调用自动登录接口loginMethod,那么为什么button里的内容执行完之后他会返回触发DocumentCompleted事件,而当线程执行完了之后他不会回去触发DocumentCompleted事件呢?
2、timer里不能设置ApartmentState.STA,只有Thread对象才可以,myThread.SetApartmenApartmentState.STAtState(ApartmentState.STA); 3、因为点击button后,webbrowser会导航到指定的url, 而DocumentCompleted事件注册了web_DocumentCompleted方法,所以页面加载完成之后会调用文档上面的click事件
Cout_Sev 2015-08-28
  • 打赏
  • 举报
回复
引用 2 楼 sunny906 的回复:
2、设置Thread属性:ApartmentState.STA 3、不是不执行,应该是文档还没有加载完成
好的 谢谢你。那么我再请教一下: 2.第二个问题里我可是设置了一个timer而已哦,并没有Thread,请问修改哪一个的Thread属性? 3、那么为什么第一个问里,单击button按钮时他会触发事件呢?因为在我看来,单击button按钮与启动线程都是调用自动登录接口loginMethod,那么为什么button里的内容执行完之后他会返回触发DocumentCompleted事件,而当线程执行完了之后他不会回去触发DocumentCompleted事件呢?
sunny906 2015-08-28
  • 打赏
  • 举报
回复
1、DocumentCompleted是一个异步方法 2、设置Thread属性:ApartmentState.STA 3、不是不执行,应该是文档还没有加载完成
Cout_Sev 2015-08-28
  • 打赏
  • 举报
回复
同样的 如果第一问中把int a=6该语句换成sleep()语句,仍然是执行完sleep再触发事件。
bj_liliang 2015-08-28
  • 打赏
  • 举报
回复
引用 13 楼 bj_liliang 的回复:

        Thread t = null;//创建线程,用来定时刷新数据。    
      
        public delegate void CallBackDelegate(List<Result> list);

       private void Form1_Load(object sender, EventArgs e)
        {
            cbd = CallBack;//设置回掉函数
        }

       private void  Callback()
        {
            //可以对主线程进行操作
        }
   
        private void MyTimmer(object obj)
         {
             try
             {
                 while (true)
                 {
                      Thread.Sleep(500);// 每次间隔的时间,自己设定

                     CallBackDelegate cbd = obj as CallBackDelegate;

                     this.BeginInvoke(cbd);//调用回掉。如果需要参数可以加上参数
                   
                 }
             }
             catch
             { 
             }
         }

       有了如上代码,在写一个按钮 作为开始按钮,触发MyTimmer
        private void button1_Click(object sender, EventArgs e)
        {
             t = new Thread(MyTimmer);
            t.IsBackground = true;
            t.Start(cbd);
        }

     这样就可以了

不好意思 上面定义委托的后面带了参数,是我没删,直接从自己的代码里贴过来的,凑合看吧 public delegate void CallBackDelegate();
bj_liliang 2015-08-28
  • 打赏
  • 举报
回复

        Thread t = null;//创建线程,用来定时刷新数据。    
      
        public delegate void CallBackDelegate(List<Result> list);

       private void Form1_Load(object sender, EventArgs e)
        {
            cbd = CallBack;//设置回掉函数
        }

       private void  Callback()
        {
            //可以对主线程进行操作
        }
   
        private void MyTimmer(object obj)
         {
             try
             {
                 while (true)
                 {
                      Thread.Sleep(500);// 每次间隔的时间,自己设定

                     CallBackDelegate cbd = obj as CallBackDelegate;

                     this.BeginInvoke(cbd);//调用回掉。如果需要参数可以加上参数
                   
                 }
             }
             catch
             { 
             }
         }

       有了如上代码,在写一个按钮 作为开始按钮,触发MyTimmer
        private void button1_Click(object sender, EventArgs e)
        {
             t = new Thread(MyTimmer);
            t.IsBackground = true;
            t.Start(cbd);
        }

     这样就可以了

bj_liliang 2015-08-28
  • 打赏
  • 举报
回复
也就是说你并不能解决在子线程中去控制主窗体控件的问题。 这里就需要委托。 定义一个 委托。加载之后给他绑定一个方法Callback,也就是所说的回掉函数。 然后写一个线程,线程需要一个object 的参数。将你定义的委托当作参数传进线程中的方法。 在线程中去计时。做成一个while(true)的循环,循环内停十分钟, 然后使用beiginInvoke调用委托,自然会触发主线程中的callback方法。 在callback 方法内,由于是主线程,你就可以对你任意的控件进行操作了
Cout_Sev 2015-08-28
  • 打赏
  • 举报
回复
非常感谢 Z65443344 的解答,上述的3个问题已经得到了清晰的解释。 不过回到最初的设计思路,小生就真的黔驴技穷了。timer跟线程都解决不了。 那么在实现“定时自动登录网站的功能”仅仅实现了自动登录的功能,各位前辈能帮忙提点一下定时功能该如何实现吗?
Cout_Sev 2015-08-28
  • 打赏
  • 举报
回复
补充一下,问题二所使用的timer为system.timers.timer
Cout_Sev 2015-08-28
  • 打赏
  • 举报
回复
引用 8 楼 Z65443344 的回复:
所谓UI线程,就是负责显示,负责输入的线程,通常它是主线程 而其他线程,是工作线程 这就好比你去肯德基点餐 如果前台后台都只有一个人在忙活,那么给你点完餐之后,他去后面忙活了,你再说什么他也听不到,你也无法了解他的工作进度. 而开启了一个线程,就好比他将做菜的工作交给了其他人,然后他自己继续在前台陪你说话 后台的厨师是不允许直接跟客户交流的,他必须将他想要告诉客户的信息,委托给前台点餐的人员,再由点餐人员通知你 异步方法通常是由系统线程池调用的,也就是说,你并没有显示的开启线程,但是后台依然有线程在执行任务 这就好比点餐的人并没有将你的单子直接委托给任何厨师,而只是将单子放到了后面柜台上,然后谁有空谁就看一眼单子然后去给你做菜.做完菜,他们也许会直接委托给前台人员交给你,也可能就放到了后面的柜台上,然后前台人员发现菜好了再交给你.如果前台人员没有什么事可干,他也可能去帮忙给你打个可乐什么的. 这就是异步方式,到底由哪个线程执行不可控,也不需要去关心.
非常谢谢这位前辈,感谢你花费了将近30分钟为我解释这个问题。我反复阅读了你给出的答案,已经能理解8成以上(不是字面意思的理解,是真正意义上的理解,因为之前我学过一点C++下MFC与多线程的关系)那么,能冒昧向前辈提出一个问题吗?回到最初的设计思路上:我想用c#写一个winform程序实现定时自动登录网站的功能(该网站只有很简单的页面,就一个用户名框跟密码框),前辈有什么好的建议吗?小生才疏学浅,确实是想了很久想不出来才来这里提问。
於黾 2015-08-28
  • 打赏
  • 举报
回复
所谓UI线程,就是负责显示,负责输入的线程,通常它是主线程 而其他线程,是工作线程 这就好比你去肯德基点餐 如果前台后台都只有一个人在忙活,那么给你点完餐之后,他去后面忙活了,你再说什么他也听不到,你也无法了解他的工作进度. 而开启了一个线程,就好比他将做菜的工作交给了其他人,然后他自己继续在前台陪你说话 后台的厨师是不允许直接跟客户交流的,他必须将他想要告诉客户的信息,委托给前台点餐的人员,再由点餐人员通知你 异步方法通常是由系统线程池调用的,也就是说,你并没有显示的开启线程,但是后台依然有线程在执行任务 这就好比点餐的人并没有将你的单子直接委托给任何厨师,而只是将单子放到了后面柜台上,然后谁有空谁就看一眼单子然后去给你做菜.做完菜,他们也许会直接委托给前台人员交给你,也可能就放到了后面的柜台上,然后前台人员发现菜好了再交给你.如果前台人员没有什么事可干,他也可能去帮忙给你打个可乐什么的. 这就是异步方式,到底由哪个线程执行不可控,也不需要去关心.
於黾 2015-08-28
  • 打赏
  • 举报
回复
问题一:上面已经回答了是异步方法 什么是异步方法呢? 我经常用收快递来做比喻. 你执行了个发送的请求,就好比你在网上买了个东西,然后你要等着对方给你返回数据,就好比等着快递给你送货 同步方式就是你一直站在门口等,啥都不干,就是干等 而异步方式就是你该干什么干什么去,等快递到了会给你打电话,然后你就停下手头的工作去收快递 回调事件就好比快递给你打了电话,然后你要在事件函数里自己写代码去处理 问题二:timer分3种.System.Forms.Timer;System.Timers.Timer;System.Thread.Timer; 其中只有System.Forms.Timer是封装好了委托,回调方法由UI线程来调用的,而另外两个timer,都是在子线程里调用 所以你虽然没有使用thread,不代表你的程序中就没有线程 问题三:webbrowser是个窗体控件,你在线程里调用窗体控件,是不允许的
Cout_Sev 2015-08-28
  • 打赏
  • 举报
回复
没人知道吗?自己帮自己D

110,571

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧