webbrowser control does not "render" html - browser

I am having problem using webbrowser control to correctly display html. My goal is to add custom html to a webbrowser control, have it displayed, and save the screenshot of that as png. Currently I am using Document.OpenNew and Document.Write(htmlText) and Application.DoEvents(). However since I am running this in a background thread, sometimes it deadlocks. I know the culprit is Application.DoEvents() which is giving me troubles.
However, if I remove that and set the html directly to DocumentText property, how do I know when it is fully "rendered" or loaded. I used the DocumentCompleted Event but that does not seem to work since the image that is saved is still empty even after the event fires.
I also have the thread as STA.
Here is the existing code:
Thread th = new Thread(new ThreadStart(createImage));
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join(TIMEOUT);
private void createImage() {
var browser = new WebBrowser();
var doc = browser.Document;
doc.OpenNew(false);
doc.Write("<html><body>....</body><html>)");
//loop for few seconds
for(int i=0; i<20; i++)
{
Application.DoEvents();
Thread.Sleep(100);
}
//save to file as png.
}
Here is the code I am trying:
private void createImage() {
var browser = new WebBrowser();
bool docComplete = false;
browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(
(Object sender, WebBrowserDocumentCompletedEventArgs args) =>
{ docComplete = true; }
);
browser.DocumentText = "<html>.....";
while (!_docComplete)
{
Thread.Sleep(100);
}
// save image
// :-( not working
}

You have to create thread as STA, that is only the way to run WebBrowser in a background thread.
bw.SetApartmentState(ApartmentState.STA);
There is no information about waiting in HtmlDocument.Write Method. If is yet required then you can add DocumentCompleted handler instead of DoEvents
private void captureImageThread(string html) {
var thread = new Thread(() => {
var browser = new WebBrowser();
browser.DocumentCompleted += browser_DocumentCompleted;
browser.DocumentText = html;
Application.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
var br = sender as WebBrowser;
// save image here
Application.ExitThread(); // Stops the thread
}

Related

An object reference is required for the non-static field method or property/field initializer cannot reference the non-static field method or property

I was making a program and I stumbled across this two-in-one problem, where the first problem leads to the other. I have not yet found a question where someone had both problems leading into eachother. I'm still learing, and have learned a lot from other problems I had, but I can't find a solution to this problem.
It has to do with threading. I want to make a thread, that can place something in a rich textbox every second or so, while I can still press buttons to start and stop it. But to make a function that a thread can run, I need to make the function static. Otherwise I'll get the error "A field initializer cannot reference the non-static field, method, or property". But when a function is static, it cannot acces any of the created items, like richTextBox1. Because if I try to change it's text, I get the error "Error 1 An object reference is required for the non-static field, method, or property". And if I fix this by removing static, the thread will not work.
I made a demo program that is smaller than the full one, but has the same problem. Button1 is the button to start the thread, Button2 is the one to stop it.
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;
using System.Threading;
namespace threading_non_static_problem_demo
{
public partial class Form1 : Form
{
static Thread thr = new Thread(new ThreadStart(demofunc));
int checkthr = 0; //int to check if the thread has been running before (I like to do things like this)
int ifthrrun = 0; //int to check if the thread is running
public Form1()
{
InitializeComponent();
button2.Enabled = false; // so you can't click the "stop" button if nothing is running
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
}
static void demofunc()
{
while (true)
{
Thread.Sleep(1000);
richTextBox1.Text = richTextBox1.Text + "text added"; // <-- here's the problem
MessageBox.Show("tried to add text"); // you can use this messagebox to check if the thread is working correctly
}
}
private void button1_Click(object sender, EventArgs e)
{
if (checkthr == 0) // check if the thread has run before, 0 is no, 1 is yes, and then start or resume it
{
thr.Start();
ifthrrun = 1;
button2.Enabled = true;
button1.Enabled = false;
}
else if (checkthr == 1)
{
thr.Resume();
ifthrrun = 1;
button2.Enabled = true;
button1.Enabled = false;
}
}
private void button2_Click(object sender, EventArgs e)
{
thr.Suspend();
checkthr = 1;
ifthrrun = 0;
button2.Enabled = false;
button1.Enabled = true;
}
protected override void OnFormClosing(FormClosingEventArgs e) // if the program is closing, check the thread's state and act accordingly
{
if (ifthrrun == 0)
{
if (checkthr == 1)
{
thr.Resume();
thr.Abort();
}
else if (checkthr == 0)
{
}
}
else if (ifthrrun == 1)
{
thr.Abort();
}
}
}
}
To use this code just creat a forms application, add two buttons, and a rich text box, it should work.
Thank you in advance for you answers.
But to make a function that a thread can run, I need to make the
function static.
Get rid of the static declarations and move initialization of your "thr" variable to the constructor like this:
Thread thr;
public Form1()
{
InitializeComponent();
button2.Enabled = false; // so you can't click the "stop" button if nothing is running
thr = new Thread(new ThreadStart(demofunc));
Control.CheckForIllegalCrossThreadCalls = false;
}
private void demofunc()
{
while (true)
{
Thread.Sleep(1000);
richTextBox1.Text = richTextBox1.Text + "text added"; // <-- problem "solved" by disabling Control.CheckForIllegalCrossThreadCalls
}
}
But ignore the above "fix" because using Suspend()/Resume() is not recommended.
See Pausing and Resuming Threads:
Important
Starting with the .NET Framework version 2.0, the Thread.Suspend and
Thread.Resume methods are marked obsolete and will be removed in a
future release.
The Thread.Suspend and Thread.Resume methods are not
generally useful for applications and should not be confused with
synchronization mechanisms. Because Thread.Suspend and Thread.Resume
do not rely on the cooperation of the thread being controlled, they
are highly intrusive and can result in serious application problems
like deadlocks (for example, if you suspend a thread that holds a
resource that another thread will need).
One way to be able to pause/resume your loop would be to use a ManualResetEvent like this:
Thread thr;
ManualResetEvent mre = new ManualResetEvent(false);
public Form1()
{
InitializeComponent();
button2.Enabled = false; // so you can't click the "stop" button if nothing is running
thr = new Thread(new ThreadStart(demofunc));
}
private void demofunc()
{
while (!this.IsDisposed && !this.Disposing)
{
Thread.Sleep(1000);
if (!this.IsDisposed && !this.Disposing)
{
this.Invoke((MethodInvoker)delegate {
richTextBox1.AppendText("text added");
});
}
mre.WaitOne();
}
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
mre.Set();
if (!thr.IsAlive)
{
thr.Start();
}
button2.Enabled = true;
}
private void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
mre.Reset();
button1.Enabled = true;
}
protected override void OnFormClosing(FormClosingEventArgs e) // if the program is closing, check the thread's state and act accordingly
{
mre.Set(); // make sure the loop continues so it can drop out
}
}

Downloads with JavaFX WebView

my web application offers a download. Javascript creats at the click the url (it depends on the user input) and the browser should open it, so that the page isn't reloaded.
For that, I think I have to alternatives:
// Alt1:
window.open(pathToFile);
// Alt2:
var downloadFrame = document.getElementById('downloads');
if (downloadFrame === null) {
downloadFrame = document.createElement('iframe');
downloadFrame.id = 'downloads';
downloadFrame.style.display = 'none';
document.body.appendChild(downloadFrame);
}
downloadFrame.src = pathToFile;
Both works under Firefox. Problem with open new window method: If the creation of the file at the server needs more time, the new empty tab will be closed late.
Problem with iframe: If there is an error at the server, no feedback is given.
I think at firefox the iframe is the better solution. But the web application must run with an JavaFX WebView, too. JavaFX haven't a download feature, I have to write it. One possible way is to listen on the location property:
final WebView webView = new WebView();
webView.getEngine().locationProperty().addListener(new ChangeListener<String>() {
#Override public void changed(ObservableValue<? extends String> observableValue, String oldLoc, String newLoc) {
if (newLoc.cotains("/download")) {
FileChooser chooser = new FileChooser();
chooser.setTitle("Save " + newLoc);
File saveFile = chooser.showSaveDialog(webView.getEngine().getScene().getWindow());
if (saveFile != null) {
BufferedInputStream is = null;
BufferedOutputStream os = null;
try {
is = new BufferedInputStream(new URL(newLoc).openStream());
os = new BufferedOutputStream(new FileOutputStream(saveFile));
while ((readBytes = is.read()) != -1) {
os.write(b);
}
} finally {
try { if (is != null) is.close(); } catch (IOException e) {}
try { if (os != null) os.close(); } catch (IOException e) {}
}
}
}
}
}
There are some problems:
The download start depends on a part of the url, because JafaFX supports no access to the http headers (that is bearable)
If the user starts the download with the same url two times, only the first download works (the change event only fires, if the url is new). I can crate unique urls (with #1, #2 and so on at the end). But this is ugly.
Only the "window.open(pathToFile);" method works. Loading an iframe don't fire the change location event of the website. That is expectable but I haven't found the right Listener.
Can someone help me to solve 2. or 3.?
Thank you!
PS: Sorry for my bad english.
edit:
For 2. I found a way. I don't know if it is a good one, if it is performant, if the new webview is deleted or is in the cache after download, ....
And the user don't get an feedback, when some a problem is raised:
webView.getEngine().setCreatePopupHandler(new Callback<PopupFeatures, WebEngine>() {
#Override public WebEngine call(PopupFeatures config) {
final WebView downloader = new WebView();
downloader.getEngine().locationProperty().addListener(/* The Listener from above */);
return downloader.getEngine();
}
}
I think you may just need to use copyURLtoFile to get the file...call that when the location changes or just call that using a registered java class. Something like this:
org.apache.commons.io.FileUtils.copyURLToFile(new URL(newLoc), new File(System.getProperty("user.home")+filename));
Using copyURLToFile the current page doesn't have to serve the file. I think registering the class is probably the easiest way to go... something like this:
PHP Code:
Download $filename
Java (in-line class in your javafx class/window... in this case my javafx window is inside of a jframe):
public class JavaApp {
JFrame cloudFrameREF;
JavaApp(JFrame cloudFrameREF)
{
this.cloudFrameREF = cloudFrameREF;
}
public void getfile(String filename) {
String newLoc = "http://your_web_site.com/send_file.php?filename=" + filename;
org.apache.commons.io.FileUtils.copyURLToFile(new URL(newLoc), new File(System.getProperty("user.home")+filename));
}
}
This part would go in the main javafx class:
Platform.runLater(new Runnable() {
#Override
public void run() {
browser2 = new WebView();
webEngine = browser2.getEngine();
appREF = new JavaApp(cloudFrame);
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
#Override public void changed(ObservableValue ov, State oldState, State newState) {
if (newState == Worker.State.SUCCEEDED) {
JSObject win
= (JSObject) webEngine.executeScript("window");
// this next line registers the JavaApp class with the page... you can then call it from javascript using "app.method_name".
win.setMember("app", appREF);
}
}
});
You may not need the frame reference... I was hacking some of my own code to test this out and the ref was useful for other things...

Blackberry multi threading issue

I am developping a BlackBerry application which communicates with the server via HTTP requests(javax.microedition.io.HttpConnection). On device, user clicks some UI items, and device sends the requests to server, when the response comes, UI changes. Communication takes place under new thread, while UI thread pushes and pops ProgressDialogScreen.
The problem is sometimes, when response comes and ProgressDialogScreen is popped, UI does not change but after couple seconds UI changes. If you have requested in between when ProgressDialogScreen is popped and when new Screen is pushed, there comes the mess. First oldest new Screen is pushed, and the newest new Screen is pushed. And this situation can be observed like server responsing wrong requests. This problems occur on simulator and device.
The other problem is, sometimes two same response returns for one request. I was able to see these two problems on simulator at the logs, but i have not able to see this issue on device since i can not see the logs.
EDIT:
String utf8Response;
HttpConnection httpConn = null;
try{
httpConn = (HttpConnection) Connector.open(url);
httpConn.setRequestMethod(HttpConnection.GET);
httpConn.setRequestProperty("Content-Type", "text/html; charset=UTF8");
if(sessionIdCookie != null){
//may throw IOException, if the connection is in the connected state.
httpConn.setRequestProperty("Cookie", sessionIdCookie);
}
}catch (Exception e) {
//...
}
try{
httpConn.getResponseCode();
return httpConn;
}catch (IOException e) {
// ...
}
byte[] responseStr = new byte[(int)httpConn.getLength()];
DataInputStream strm = httpConn.openDataInputStream();
strm.readFully(responseStr);
try{
strm.close();
}catch (IOException e) {
// ....
}
utf8Response = new String(responseStr, "UTF-8");
If this code successfully run, this piece of code runs and new screen is pushed:
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Vector accounts = Parser.parse(utf8Response,Parser.ACCOUNTS);
if (accounts.size() == 0){
DialogBox.inform(Account.NO_DEPOSIT);
return;
}
currentScreen = new AccountListScreen(accounts);
changeScreen(null,currentScreen);
}
});
public void changeScreen(final AbstractScreen currentScreen,final AbstractScreen nextScreen) {
if (currentScreen != null)
UiApplication.getUiApplication().popScreen(currentScreen);
if (nextScreen != null)
UiApplication.getUiApplication().pushScreen(nextScreen);
}
EDITv2:
private static void progress(final Stoppable runThis, String text,boolean cancelable) {
progress = new ProgressBar(runThis, text,cancelable);
Thread threadToRun = new Thread() {
public void run() {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
try{
UiApplication.getUiApplication().pushScreen(progress);
}catch(Exception e){
Logger.log(e);
}
}
});
try {
runThis.run();
} catch (Throwable t) {
t.printStackTrace();
}
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
try {
UiApplication.getUiApplication().popScreen(progress);
} catch (Exception e) { }
}
});
}
};
threadToRun.start();
}
By the way ProgressBar is extended from net.rim.device.api.ui.container.PopupScreen and Stoppable is extended from Runnable
I preferred to pop progress bar after new Screen is prepared and pushed. This way there will be no new request between request and response.
Why not do:
private static void progress(final Stoppable runThis, String text,boolean cancelable) {
progress = new ProgressBar(runThis, text,cancelable);
UiApplication.getUiApplication().pushScreen(progress);
[...]
Seems like you are parsing on the UI Thread. Please remove Vector accounts = Parser.parse(utf8Response,Parser.ACCOUNTS); from ui thread and do it in a separate thread.

How to embed a program inside another using GTK, XLib or any similar?

I'm trying to make a "simple" program, all it does is to list all opened programs and, once you choose one, it opens it inside your window (like a thumbnail you may say, but you can also interact).
One thing, it has to be one way only (I can't alter the embbeded program and add a "socket" or "plug" for instance). I want to be able to embbed any program (e.g. Opera, evince, JDownloader etc).
Does anyone have any idea of how can I do it?
If it can't be done using GTK, can it be done using X or any similar? How?
It appears that you're looking for something like XEmbed. A good tutorial in python and gtk is at http://www.moeraki.com/pygtktutorial/pygtk2tutorial/sec-PlugsAndSockets.html
You can use GtkPlug and GtkSocket for that.
using System;using Gtk;using System.Runtime.InteropServices; public partial class MainWindow : Gtk.Window{
public MainWindow () : base(Gtk.WindowType.Toplevel)
{
Gtk.Socket socket;
int xid;
Fixed fixed2=new Fixed();
this.socket = new Socket();
this.socket.WidthRequest = 500;
this.socket.HeightRequest = 500;
this.socket.Visible = true;
this.socket.Realized += new EventHandler(OnVideoWidgetRealized);
fixed2.Put(socket, 0, 0);
fixed2.SetSizeRequest(500,500);
this.Add(fixed2);
this.ShowAll();
OnButton17Clicked();
}
protected virtual void OnVideoWidgetRealized (object sender, EventArgs
args)
{
this.xid = (int)socket.Id;
Console.WriteLine("this.xid:"+this.xid);
}
protected void OnDeleteEvent (object sender, DeleteEventArgs a)
{
Application.Quit ();
a.RetVal = true;
this.socket = new Socket();
}
protected void OnButton17Clicked ()
{
var paramString = string.Format("-wid {0} 1.avi", xid);
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = "mplayer.exe";
proc.StartInfo.Arguments = paramString;
proc.Start();
proc.WaitForExit();
}
public static void Main()
{
Application.Init();
new MainWindow();
Application.Run();
}}

Outlook add in , text box , delete\backspace not working

I developed an outlook add in (custom task pane), with web browser in the user control.
All the things working well beside the backspace or the delete button when I am writing something in text box in the web browser, I can't use those keys, am I missing something?
I am a few years late to the party but I managed to fix this. The easiest way to fix this is to ensure proper focus is given to the input fields, so you will need to be able to run your own javascript on whatever page is being loaded.
The javascript I run on the page is as follows (using jQuery):
$(document).on("click", function (e) {
// first let the add-in give focus to our CustomTaskPane
window.external.focus();
// then in our web browser give focus to whatever element was clicked on
$(e.target).focus();
});
the window.external variable contains code run from the plugin (c# or VB I assume) which is exposed so we can interact from web page back to the add-in.
In the add-in code for the custom taskpane set the context of window.external:
// event when webBrowser is finished loading document
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// sets context of window.external to functions defined on this context
webBrowser1.ObjectForScripting = this;
}
And a public method for focusing:
// can be called by the web browser as window.external.focus()
public void focus()
{
this.Focus();
}
This worked for me, and I hope it helps others. Although do note that this probably doesn't work if the user keyboard navigates using tab, but you can either extend this code for that use case, or safely assume that the average outlook user will have his hand glued to the mouse.
Ok I solved the problem ,
The problem is that the custom task pane in not always gets fucos from the outlook.
So, I raised an event every time that there is "onclick" for all the pane, and then forced the pane to be in focus.
spent a lot of time trying to get this working in Outlook v16.0.13801.20288 the above did not work for me. I ended up with this working code.
Create a user control and add your webbrowser control to it then customize the .cs as below
private void CreateTaskPane() {
MyWinFormUserControl webBrowser = new MyWinFormUserControl();
webBrowser.webBrowser3.Url = new Uri("https://google.com");
webBrowser.webBrowser3.Width = 500;
webBrowser.webBrowser3.Dock = DockStyle.Fill;
webBrowser.webBrowser3.Visible = true;
webBrowser.Width = 500;
webBrowser.Dock = DockStyle.Fill;
webBrowser.Visible = true;
this.CRMTaskPaneControl = CustomTaskPanes.Add(webBrowser, "My App");
//Components.WebViewContainerWPFUserControl webView = (Components.WebViewContainerWPFUserControl)_eh.Child;
//webView.webview.Source = new Uri("https://localhost:3000");
this.CRMTaskPaneControl.Width = 500;
System.Windows.Forms.Application.DoEvents();
this.CRMTaskPaneControl.Control.Focus();
this.CRMTaskPane.Visible = true;
}
public partial class MyWinFormUserControl : UserControl
{
public WebBrowser webBrowser3;
public System.Windows.Forms.WebBrowser webBrowser1;
public MyWinFormUserControl()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.webBrowser3 = new System.Windows.Forms.WebBrowser();
this.SuspendLayout();
//
// webBrowser3
//
this.webBrowser3.Dock = System.Windows.Forms.DockStyle.Fill;
this.webBrowser3.Location = new System.Drawing.Point(0, 0);
this.webBrowser3.MinimumSize = new System.Drawing.Size(20, 20);
this.webBrowser3.Name = "webBrowser3";
this.webBrowser3.Size = new System.Drawing.Size(500, 749);
this.webBrowser3.TabIndex = 0;
this.webBrowser3.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webBrowser3_DocumentCompleted);
//
// MyWinFormUserControl
//
this.Controls.Add(this.webBrowser3);
this.Name = "MyWinFormUserControl";
this.Size = new System.Drawing.Size(500, 749);
this.Load += new System.EventHandler(this.MyWinFormUserControl_Load);
this.ResumeLayout(false);
}
void webBrowser3_DocumentCompleted(object sender, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs e)
{
HtmlDocument doc;
doc = webBrowser3.Document;
doc.Click += doc_Click;
}
void doc_Click(object sender, HtmlElementEventArgs e)
{
this.Focus(); // force user control to have the focus
HtmlElement elem = webBrowser3.Document.GetElementFromPoint(e.ClientMousePosition);
elem.Focus(); // then let the clicked control to have focus
}
private void MyWinFormUserControl_Load(object sender, EventArgs e)
{
//Control loaded
}
Turns out this is an easy issue to fix.
Just write
class MyBrowser : WebBrowser {}
Then use MyBrowser instead of the .NET one.

Resources