WebBrowser control doesn't populate on first iteration if loading two controls - c#

The issue I am having is I have a WebBrowser control called "Browser." The browser is populated from a different controls created by MEF:
//Apply Button that cretes the HTML that is loaded into the Browser
btnCreateHTMLPreview(object sender, RoutedEventArgs e)
{
//Clears the content of the observable collection
contents.Clear();
//usedControls is an observable collection of all loaded controls in the stack panel
foreach(var control in usedControls)
control.CreateHTML();
}
//Code in the MEF Control that creates the HTML that is will be loaded into the browser
CreateHTML()
{
string tempStuff = null;
tempStuff += "<h1>" + MyControl.lblHeader.Content + "</h1>\n";
tempStuff += MyControl.txtTextGoesHere.Text + "\n";
//Give the HTML back to the Application
parent.AcceptReport(tempStuff);
}
//Application side where it accepts the HTML and loads it into the Browser
AcceptHTML(string passedInContent)
{
//Observable collection that holds the content of all possible MEF Controls
contents.Add(passedInContent);
string tempStuffForBrowser = null;
tempStuffForBrowser = "<html>\n";
tempStuffForBrowser += "<body>\n";
//Add the content that was saved earlier
foreach (var strInd in contents)
tempStuffForBrowser += strInd;
tempStuffForBrowser = "<html>\n";
tempStuffForBrowser += "<body>\n";
Browser.NavigateToString(tempStuffForBrowser);
}
I did my best above to try and provide the meat of how I am populating the html and add that back into the browser. If there is anything that is unclear please just let me know.
If I have just one control added so that "content" only has the one control in it when I click the button to load the html, it works just fine and loads it in. However if I have more than one control loaded, I have to click that button twice to get it to show. I found this extremely bizarre. My attempt to solve this was to check and see if there was content in the Browser before leaving the event, but I am unsure how to do this. If anyone has any suggestions or any other ideas on how to solve this problem please let me know.

It looks like the WebBrowser control has some kind of issue where it doesn't load the first string it gets; I'm assuming there's some initialization that hasn't happened yet. I got it to work by calling
myBrowser.NavigateToString("");
from within the constructor of my class, and then using it from then on worked correctly. Hokey but it works.

Related

How to make CefSharp WinForms control Scroll page to Link

So here is the scenario, using CefSharp in a WinForms application.
We have two pieces of information a page url and a link url within that page.
We want to load the page - easily done, and have CefSharp show the area of the page where the link is located. That is cefsharp automatically scrolls to the right location on the page where that link is within the DOM.
What is the easiest way of doing that?
Not sure which version you're using, I was able to solve this using the latest stable version of CefSharp (47.0.0).
First add a handler to the FrameLoadEnd event:
browser.FrameLoadEnd += OnBrowserFrameLoadEnd;
The handler is the following:
private void OnBrowserFrameLoadEnd(object sender, FrameLoadEndEventArgs frameLoadEndEventArgs)
{
if (frameLoadEndEventArgs.Frame.IsMain && frameLoadEndEventArgs.Url == pageUrl)
{
frameLoadEndEventArgs.Browser.MainFrame.ExecuteJavaScriptAsync(
"(function() { var el = document.querySelector('a[href=\"" + linkUrl +
"\"]'); if(el) { el.scrollIntoView(); } })();");
}
}
The two constants are:
private const string pageUrl = "http://stackoverflow.com/";
private const string linkUrl = "http://stackexchange.com/legal";
Whenever we navigate to the site in the pageUrl, we invoke a JavaScript function in the loaded document. It selects the first element which has an href attribute equal to the linkUrl using querySelector. If there is an element like that, then we invoke the scrollIntoView() method on it.
This particular example scrolls the following link into view when you visit stackoverflow.com.
I used the CefSharp Minimal Example as a starting point to build my POC.
I uploaded it HERE for inspection.
Rebuild the project, package restore is enabled, so it should get all the necessary packages. After that you can run it. Enter stackoverflow.com into the address bar, hit Enter, and when it's loaded, the view should scroll down.

C# WebBrowser different html document after navigate

I have a really strange problem in C#:
First I use the WebBrowser control and the navigate method to browse.
wb_email.Navigate("https://registrierung.web.de");
Now I can change the innerText of htmlelements without any problems.
wb_email.Document.GetElementById("id4").InnerText = "Alexander";
But when I reload the page by simply using the navigate method with the same url again,
I get a null exception. It seems as he can't find the element.
So I used an inspector for Firefox to see if the htmlelement really changed, after reloading.
But only the url is changing, htmlelements are all the same.
What I'm doing wrong?
You're just changing the DOM in the displayed page. When you reload the page, the WebBrowser instance will just refresh the DOM from the server again and lose your changes.
The WebBrowser class isn't designed for editing rendered pages inside itself, as it's basically just a wrapper to an embedded Internet Explorer instance.
Make sure the website has finished loading before accessing any element. Like:
webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted);
void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// Access elements here
}

Saving webControls in the Session object

I have a panel (pnlPanel) with lots of controls like Textboxes and DropDownLists. I want them to be persistent when the user gets back to the page, so i tried this:
/*i have saved the panel like this
Session["testPanel"] = pnlTest;
*/
protected void Page_Load(object sender, EventArgs e)
{
if (Session["testPanel"] != null)
{
panel = Session["testPanel"] as Panel;
}
}
But its not working. Is it possible? The reason why i want to do this is because overhead is not a problem, and i want to cut down on coding time.
I've never tried this myself, but this seems to me to be an extra-ordinarily bad idea. Without testing it, my guess would be that this will create a ton of ViewState problems. Even if you could maintain the ViewState, attempting to keep this control over multiple page loads would be dangerous at best.
My recommendation would be to have a common object that holds the properties of the panel you want and just build a method into one of the early events to prepopulate a new panel with those properties.
Without knowing the entire reason for doing something like this, you should have a look at output caching directives. You would be best served by pulling the content out of the panel and into a user control. Then setting output caching on control, using VaryByCustom so you can use the user name or some other unique identifier to separate by user.
http://msdn.microsoft.com/en-us/library/hdxfb6cy.aspx and
http://msdn.microsoft.com/en-us/library/system.web.httpapplication.getvarybycustomstring.aspx
Using session and/or caching will be problematic if you are in a webfarm scenario. Cache is scoped to the application instance, so another server in the web farm will not have access to it.
Some other side effects of something like this include issues with viewstate.
What you try to do here is to cache the Panel but this is not the way. The panel as you save it is a running object on the memory and can not be saved as it is. You need to convert it to html string and save and cache this string. So near the Panel you place a literal, then you render the Panel and save it on session, and then actually you display the text from this render.
if(Session["testPanel"] == null)
{
TextWriter stringWriter = new StringWriter();
HtmlTextWriter renderOnMe = new HtmlTextWriter(stringWriter);
// render and get the actually html of this dom tree
testPanel.RenderControl(renderOnMe);
// save it as cache
Session["testPanel"] = stringWriter.ToString();
}
// render the result on a literal
cLiteralID.Text = Session["testPanel"];
// hide the panel because I have render it on literal.
testPanel.Visible = false;
Need some tests as is it. I use some similar code for custom control and custom cache, never save on session this amount of data.
First Approach
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["panel"] != null)
{
panel = ViewState["panel"] as Panel;
}
}
In this approach your ViewState objects were different. You may be getting some null values once the ViewState["panel"] is given the control memory and the object is being accessed in the impression that the Session was Session["panel"]
Second Approach
Save the Complete panel HTML in database and access it on the form load by keeping the function under IsPostBack.
Now with the continuity of approach - 2 assign the value to your session object.
this.Controls.Add(new LiteralControl("Your HTML"));
Third Approach
You can use File system. Save the div in your file and access the file at runtime.
Hope this may help you.
EDIT - 1 => Added code for second approach
I had a similar problem. I tried to save an object to the View State that stored a Panel and I got an error message telling me that Panels aren't serializable. You could try using a SerializationSurrogate.
https://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializationsurrogate(v=vs.110).aspx

Is it possible to pick up DOM element inside of iFrame on click?

I'm using C# .NET Framework 2.0 to create application that can pick up clicked part of DOM element by user in WebBrowser control.
So far I can pick html tags and its attributes in the HTML in WebBrowser control.
//adding event when user mouse down or focus out
webBrowser1.Document.MouseDown += new HtmlElementEventHandler(myMouseDown);
And those event would output DOM information to listbox etc
private void myMouseDown(object sender, HtmlElementEventArgs e)
{
HtmlElement tag = webBrowser1.Document.GetElementFromPoint(e.ClientMousePosition);
txtRecord.Items.Add("TagName=" + tag.TagName);
txtRecord.Items.Add("id=" + tag.GetAttribute("id"));
txtRecord.Items.Add("name=" + tag.GetAttribute("name"));
txtRecord.Items.Add("type=" + tag.GetAttribute("type"));
txtRecord.Items.Add("value=" + tag.GetAttribute("value"));
txtRecord.Items.Add("class=" + tag.GetAttribute("class"));
txtRecord.Items.Add("inner text=" + tag.InnerText);
}
But then the events mouseDown is not working in iFrame.
When I click inside of the iFrame, it does not spit DOM information.
Is it possible to pick up DOM info by user clicking on that spot in iFrame?
UPDATE:
Found HtmlWindow object and applied same event logic.
HtmlWindow iFrame;
iFrame = webBrowser1.Document.Window.Frames[0];
iFrame.Document.MouseDown += new HtmlElementEventHandler(myMouseDown);
But then the last line throws UnauthorizedAccessException :(
I found out why I'm getting UnauthorizedAccessException. It's because my main page is http://localhost/index.html while iFrame src is which violates cross-frame scripting security according to:
http://msdn.microsoft.com/en-us/library/ms171715.aspx
http://msdn.microsoft.com/en-us/library/ms533028.aspx
I've changed iFrame src to use same domain, http://localhost/secondPage.html
then it start working!

Sql Reporting services - find item in report - on load

SQL reporting services has a little search box in the top of the report viewer. When used, it finds the search text, navigates to containing page and highlights the text on the page. My question is how can I do this when the report loads.
Currently I have a reportviewer embedded in my page. Is there a method that will find? I am using sql 2008 express and Dot Net 2
For example I send the serial number 1234 to the report so that when it opens it acts like the user searched for the text and finds it for them in the report.
Ed gave me the answer to the url part. http://server/Reportserver?/SampleReports/Product Catalog&rc:FindString=mystring but I still can't figure out the reportviewer.
Here is some of the page code:
using Microsoft.Reporting.WebForms;
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Int32 iID = Convert.ToInt32(Request.QueryString["ID"]);
String reportsPath = ConfigurationManager.AppSettings["ReportsPath"];
String sReportName = "ReportInvoice";
reportViewer1.Reset();
reportViewer1.ProcessingMode = ProcessingMode.Remote;
reportViewer1.ShowParameterPrompts = false;
reportViewer1.ServerReport.ReportServerUrl = new Uri(ConfigurationManager.AppSettings["ReportViewerUrl"]);
reportViewer1.ServerReport.ReportServerCredentials = new ReportServerCredentials();//http://localhost/reportserver
reportViewer1.AsyncRendering = false;
ReportParameter[] reportParams = new ReportParameter[1];
reportViewer1.ServerReport.ReportPath = reportsPath + sReportName;
reportParams[0] = new ReportParameter("invoiceID", iID.ToString());
reportViewer1.ServerReport.Refresh();
}
}
Thanks in advance.
See this MSDN page (SQL 2005 version, but 2008 is the same, I believe).
I read through alot of the MSDN articles on the web based report viewer and tried several ways to fire off the search but only found this one to work:
First, in code you can set the search text box like so:
TextBox txt;
txt = (TextBox) this.ReportViewer1.Controls[1].Controls[4].Controls[0];
txt.Text = "test";
I did it in the ReportViewer's PreRender event. Position 1 in the first control list is the toolbar control, #4 is the search group control and then in that group the first control is the text box. The second number (4) could vary based on what you are showing / not showing in the toolbar. I was working with the default report viewer settings. It's a hack but it works.
Then I tried firing off the search event myself but this didn't result in the search working although it did fire off the event and with the correct information....
So here's what I did.
I created a javascript function:
<script type="text/javascript">
function OnFirstLoad() {
if (!isPostBack)
document.getElementById('ReportViewer1').ClientController.ActionHandler('Search', document.getElementById('ReportViewer1_ctl01_ctl04_ctl00').value);
}
</script>
I read the source of the .aspx page and found the text "find" and figured out what the client side call was. You will notice the ctl01 & ctl04 and ctl00 follow the same numbering as the server side code. You would need to change this to reflect your code. Again the second one (ctl04) is the one that is likely to change depending on how your toolbar is setup.
I then set the onload event for the body of the page to the javascript function:
<body onload="OnFirstLoad();">
The last trick was to only call this code the first time. So I added this to the page load event of the form code behind:
If (!IsPostBack)
ClientScript.RegisterClientScriptBlock(GetType(), "IsPostBack", "var isPostBack = false;", true);
else
ClientScript.RegisterClientScriptBlock(GetType(), "IsPostBack", "var isPostBack = true;", true);
This creates a variable that the javascript function checks. On the first go round it's false so it calls the report viewers search function, otherwise it's true and doesn't fire.
This is a pretty bad hack in my opinion and fragile. Changes of the report viewer's toolbar settings may require changes to the javascript and the code to set the text box.
I created a report that had several pages and the first hit wasn't until the third page and it went straight to it. From there the next button worked great until the end of the report.
To bad it's not as simple as the windows based report viewer or the server based report viewer. :)
Good Luck!
If you're trying to do this in a form in code behind then you need to find the report viewer object and add an event to the RenderingComplete that implements Find, so something like this:
public Report()
{
InitializeComponent();
rpViewer.RenderingComplete += new RenderingCompleteEventHandler(rpViewer_RenderingComplete);
}
void rpViewer_RenderingComplete(object sender, RenderingCompleteEventArgs e)
{
int x = rpViewer.Find("0", 1);
}
EDIT:
So, since this in a webpage you can't use the WinForms Controls, however, I was able to wire up a little less hacked version using Javascript that klabranche had used.
Here's a code behind class that adds a javascript function to the body of the html to search the report for the search text that you want:
private void SearchReport(ReportViewer rv, string SearchText)
{
TextBox txt = (TextBox)rv.Controls[1].Controls[4].Controls[0];
txt.Text = SearchText;
this.Body.Attributes.Add("onload", "javascript:document.getElementById('" + rv.ClientID +
"').ClientController.ActionHandler('Search', '" + SearchText + "');");
}
If you don't add the search text to the text box, then it won't show the search string in the text box on the report. This also only works for one report, so if you had additional reports you'd need to alter this. Also, for this to work you need to alter the body tag of your html:
<body id="Body" runat="server">
Have a textbox in your report that uses an expression for its background, set something like:
=iif(me.value = Parameters!Highlight.value, "Yellow", "White")
And of course, make a parameter called Highlight. ;)
Rob

Categories