WebBrowser Control disable mouse clicks - c#

I want to show a YouTube video inside a WebBrowser control, but I want to disable all user interactions (no mouse clicks no keyboard events...).
I am catching all control's preview, mouse and keyboard events and furthermore I put some handlers to the loaded HTML document, but without any success:
void webBrowser1_DocumentCompleted( object sender, WebBrowserDocumentCompletedEventArgs e )
{
if(webBrowser1.Document != null) {
var doc = webBrowser1.Document;
doc.Body.Style = "overflow:hidden";
doc.Click += htmlDoc_Click;
doc.MouseDown += htmlDoc_MouseDown;
doc.MouseMove += htmlDoc_MouseMove;
webBrowser1.Document.Body.Click += new HtmlElementEventHandler(htmlDoc_Click);
webBrowser1.Document.Body.MouseDown += new HtmlElementEventHandler(Document_MouseDown);
webBrowser1.Document.Body.MouseUp += new HtmlElementEventHandler(Document_MouseMove);
webBrowser1.Document.Body.MouseUp += new HtmlElementEventHandler(Document_MouseUp);
HtmlElement head = doc.GetElementsByTagName("head")[0];
HtmlElement mscript = doc.CreateElement("script");
IHTMLScriptElement element = (IHTMLScriptElement)mscript.DomElement;
element.text = "function handleMouseEvent(e) { "
+ "var evt = (e==null ? event:e); "
+ "return true; } "
+ "document.onmousedown = handleMouseEvent; "
+ "document.onmouseup = handleMouseEvent; "
+ "document.onclick = handleMouseEvent; ";
head.AppendChild(mscript);
}
}
Also it would be fine to overlay a transparent Control "in front of" the WebBrowser control.

This is a custom control derived from a standard WinForms Panel, modified to be completely transparent but "solid" (receives the Mouse events).
The transparency is achieved using CreateParams adding an ExStyle = WS_EX_TRANSPARENT;
Also, Control.SetStyle() method is used to modify the control behaviour, adding these ControlStyles:
ControlStyles.Opaque prevents the painting of the control BackGround, so it's not managed by the System.
ControlStyles.SupportsTransparentBackColor allows the control to accept Alpha values for it's background color.
ControlStyles.ResizeRedraw causes the redrawing of the control when it's resized.
The Custom Control is initialized passing a reference of the control it has to overlay. It then resizes itself to the size of this referenced control, excluding the ScrollBars from this measure, so they can be used.
To set it to work, create a reference to the OverlayPanel class and call the helper method CreateOverlay(Control control):
private OverlayPanel overlayPanel;
private void CreateOverlay(Control control)
{
overlayPanel = new OverlayPanel(this.webBrowser1);
Controls.Add(overlayPanel);
overlayPanel.BringToFront();
}
The OverlayPanel class code can be inserted in a Form or in a class file of its own. It should be created when all controls in a Form already have their dimensions set: in the Form.Shown() event or any other time when the parent form is visible. The Form.Load() event might also well work most of the time, anyway.
As a note, this OverlayPanel doesn't have a Resize method at this moment, which is required if the overlayed control is resized at some point. But it's quite a simple implementation, should it be needed.
private class OverlayPanel : Panel
{
internal int WS_EX_TRANSPARENT = 0x00000020;
public OverlayPanel(Control RefControl)
{
InitializeComponent();
this.Size = new Size(RefControl.Size.Width - SystemInformation.VerticalScrollBarWidth,
RefControl.Size.Height - SystemInformation.HorizontalScrollBarHeight);
this.Location = RefControl.Location;
}
private void InitializeComponent()
{
this.SetStyle(ControlStyles.Opaque |
ControlStyles.ResizeRedraw |
ControlStyles.SupportsTransparentBackColor, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
this.BorderStyle = BorderStyle.None;
}
protected override CreateParams CreateParams
{
get
{
CreateParams parameters = base.CreateParams;
parameters.ExStyle |= WS_EX_TRANSPARENT;
return parameters;
}
}
}

Related

WinForm TabControl tab page contents does not update until the new tab is shown

I have a tabcontrol which allows the creation of new tabs. Each new tab has a web browser control CEFSharp on it. When the new tab is created it is not shown the previously opened tab is shown; which is what we want.
However, the browser on the newly created tab is only added to the tab page, and only partially runs... it does not go to the loading state until the tab page is shown.
Here is the Tabpage creation code:
private void AddNewBrowser()
{
Log("Adding New Tab and Browser");
UiBrowser browser = new UiBrowser();
TabPage tp = new TabPage();
tp.Controls.Add(browser);
customTabControl1.TabPages.Add(tp);
}
The UiBrowser is a UserControl which contains the CEFSharp Browser Control plus some extra UI.
And here is the Startup code for the Browser itself.
private void UiBrowser_Load(object sender, EventArgs e)
{
Execute();
}
private void Execute()
{
webBrowser = new ChromiumWebBrowser("http://google.co.uk")
{
Dock = DockStyle.Fill,
Text = "Loading...",
Tag = Tag
};
webBrowser.TitleChanged += Browser_TitleChanged;
webBrowser.AddressChanged += Browser_AddressChanged;
webBrowser.ConsoleMessage += Browser_ConsoleMessage;
webBrowser.LoadingStateChanged += Browser_LoadingStateChanged;
webBrowser.StatusMessage += Browser_StatusMessage;
browserPanel.Controls.Add(webBrowser);
Application.DoEvents();
}
The code has been simplified for clarity and I have not found a solution on SO or elsewhere for this problem.
Question:
How do I get the browser control to load the webpage whilst remaining in the background? That is while the TabPage that the control is on is NOT shown to the user.
The Load event will only happen when the control becomes visible the first time:
Occurs before the control becomes visible for the first time.
so try moving your Execute method into the UserControl's constructor code.
There is no "official" way of doing that.
But if you really need it and don't afraid using internals, you may take a look at my answer to WinForms: Respond to BindingSource being applied.
The solution (or hack) is encapsulated in this little helper
public static class ControlUtils
{
static readonly Action<Control, bool> CreateControlFunc = (Action<Control, bool>)Delegate.CreateDelegate(typeof(Action<Control, bool>),
typeof(Control).GetMethod("CreateControl", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(bool) }, null));
public static void CreateControls(this Control target)
{
if (!target.Created)
CreateControlFunc(target, true);
else
for (int i = 0; i < target.Controls.Count; i++)
target.Controls[i].CreateControls();
}
}
At the end of your form load event, add the following
this.CreateControls();
or
customTabControl1.CreateControls();
and also here
private void AddNewBrowser()
{
Log("Adding New Tab and Browser");
UiBrowser browser = new UiBrowser();
TabPage tp = new TabPage();
tp.Controls.Add(browser);
customTabControl1.TabPages.Add(tp);
if (customTabControl1.Created)
tp.CreateControls();
}

WinForm: How to implement button with correct background colors

I'm developing winform app with C#. And I created custom button inherent from UserControl as shown below:
public partial class UserButton : UserControl
{
public UserButton(string UserID)
{
this.Size = new Size(32, 50);
this.BackColor = Color.Transparent;
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
Img = WaseelaMonitoring.Properties.Resources.T;
g.DrawImage(Img, 0, 0, this.Size.Width, this.Size.Height);
}
}
Note: this is button png image (Click here)
Now, I want to show some buttons on picture box using this code:
UserButton TagButton1 = new UserButton("Button1");
TagButton1.Location = Points[0];
UserButton TagButton2 = new UserButton("Button2");
TagButton2.Location = Points[1];
UserButton TagButton3 = new UserButton("Button3");
TagButton1.Location = Points[2];
Picturebox1.Controls.Add(TagButton1);
Picturebox1.Controls.Add(TagButton2);
Picturebox1.Controls.Add(TagButton2);
Picturebox1.Invalidate();
Okay, when show only one button on the picture box, the background button is transparent(as I want) like this:
But if I want to show two or more buttons beside together the background button is white not transparent like this:
I'm using invalidate picture box and trying invalidate button also, but is not solve that problem.
WinForms does not support true Z-ordering of components; windowed controls (such as Button and UserControl) cannot have true alpha-channel support, and the this.Background - Color.Transparent trick is actually a special-case where the control will re-paint its parent's Background image or color to itself first.
If you are after a more flexible user-experience, I suggest switching to WPF, or doing all of your painting within a single WinForms Control.
I solved this problem by added this line to initializing constructor:
SetStyle(ControlStyles.Opaque, true);
And overridden this function:
protected override CreateParams CreateParams
{
get
{
const int WS_EX_TRANSPARENT = 0x00000020;
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_TRANSPARENT;
return cp;
}
}

Resizing custom user control according to data in the webBrowser control docked in it

I have a webBrowser control named webBrowser1 that is added and docked as DockStyle.Full on a custom user control. The web-browser accepts some HTML text dynamically and displays it. I disabled the scroll bars of the webBrowser control. My problem is that whenever the content is somewhat lengthy, the webBrowser hides it from below. But the requirement of my project objective is that the webBrowser must not show either scroll bars or it should not hide some of the content. The content must be completely shown as it is without scrolling. That means the user control on which the webBrowser is docked must resize itself according to webBrowser's content. So, can anyone please suggest me how to achieve this? I searched all over the internet and SO but found nothing.
You can get the current size of HTML window via WebBrowser.Document.Window.Size and resize the container control accordingly. Depending on how your WebBrowser control content receives dynamic updates, you'd probably need to do this after each update. You could also try WebBrowser.Document.Body.ScrollRectangle if Document.Window.Size doesn't grow in the expected way.
[EDITED] The following code works for me (IE10):
private void Form1_Load(object sender, EventArgs e)
{
this.BackColor = System.Drawing.Color.DarkGray;
this.webBrowser.ScrollBarsEnabled = false;
this.webBrowser.Dock = DockStyle.None;
this.webBrowser.Location = new System.Drawing.Point(0, 0);
this.webBrowser.Size = new System.Drawing.Size(320, 200);
DownloadAsync("http://www.example.com").ContinueWith((task) =>
{
var html = task.Result;
MessageBox.Show(String.Format(
"WebBrowser.Size: {0}, Document.Window.Size: {1}, Document.Body.ScrollRectangle: {2}\n\n{3}",
this.webBrowser.Size,
this.webBrowser.Document.Window.Size,
this.webBrowser.Document.Body.ScrollRectangle.Size,
html));
this.webBrowser.Size = this.webBrowser.Document.Body.ScrollRectangle.Size;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
async Task<string> DownloadAsync(string url)
{
TaskCompletionSource<bool> onloadTcs = new TaskCompletionSource<bool>();
WebBrowserDocumentCompletedEventHandler handler = null;
handler = delegate
{
this.webBrowser.DocumentCompleted -= handler;
// attach to subscribe to DOM onload event
this.webBrowser.Document.Window.AttachEventHandler("onload", delegate
{
// each navigation has its own TaskCompletionSource
if (onloadTcs.Task.IsCompleted)
return; // this should not be happening
// signal the completion of the page loading
onloadTcs.SetResult(true);
});
};
// register DocumentCompleted handler
this.webBrowser.DocumentCompleted += handler;
// Navigate to url
this.webBrowser.Navigate(url);
// continue upon onload
await onloadTcs.Task;
// the document has been fully loaded, can access DOM here
// return the current HTML snapshot
return ((dynamic)this.webBrowser.Document.DomDocument).documentElement.outerHTML.ToString();
}
To resize your usercontrol you first need to get the size needed by the content. This can be achived with TextRender.MeasureText, like so:
public static int GetContentHeight(string content, Control contentHolder, Font contentFont)
{
Font font = (contentFont != null) ? contentFont : contentHolder.Font;
Size sz = new Size(contentHolder.Width, int.MaxValue);
int padding = 3;
int borders = contentHolder.Height - contentHolder.ClientSize.Height;
TextFormatFlags flags = TextFormatFlags.WordBreak;
sz = TextRenderer.MeasureText(content, contentHolder.Font, sz, flags);
int cHeight = sz.Height + borders + padding;
return cHeight;
}
In your case it's a bit more tricky, as the text contains HTML-tags wich needs to be filtered away, to get the correct height.. I belive this can be achived with RegEx or a simple algo wich removes all content between < and > from a string.. You may also have to create special handlig for some HTML-tags (I.E Lists)

How do I add a button beside each node of a TreeView?

How do I add a button beside each node of a TreeView?
Adding a button beside each node of a treeview is difficult. You would have to handle drawing of the treeview yourself, and either draw the buttons yourself and emulate their functionality, or create child button controls and display them in the right places within the tree control and then handle repositioning them when the control scrolls, etc. Either way it is going to be a nightmare.
Luckily, there is an easy way out: you do not have to do any of that complicated stuff, BECAUSE YOU SHOULD NOT DO THEM!
Have you ever seen a tree control with buttons in it? No. Therefore, if your tree control has buttons in it, it will be seen by your end users as bizarre.
What you should do is consider how other applications have solved the problem that you are trying to solve without using tree controls with buttons in them, and do as they do.
The simplest way to do this is to draw tree yourself. Here is a small example (please note that PushButtonState is located inside System.Windows.Forms.VisualStyles namespace):
public class CustomTreeView : TreeView
{
private Rectangle buttonRect = new Rectangle(80, 2, 50, 26);
private StringFormat stringFormat;
public CustomTreeView()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
DrawMode = TreeViewDrawMode.OwnerDrawText;
ShowLines = false;
FullRowSelect = true;
ItemHeight = 30;
stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Near;
stringFormat.LineAlignment = StringAlignment.Center;
}
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
e.Graphics.DrawString(e.Node.Text, this.Font, new SolidBrush(this.ForeColor), e.Bounds, stringFormat);
ButtonRenderer.DrawButton(e.Graphics, new Rectangle(e.Node.Bounds.Location + new Size(buttonRect.Location), buttonRect.Size), "btn", this.Font, true, (e.Node.Tag != null) ? (PushButtonState)e.Node.Tag : PushButtonState.Normal);
}
protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e)
{
if (e.Node.Tag != null && (PushButtonState)e.Node.Tag == PushButtonState.Pressed)
{
e.Node.Tag = PushButtonState.Normal;
MessageBox.Show(e.Node.Text + " clicked");
// force redraw
e.Node.Text = e.Node.Text;
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
TreeNode tnode = GetNodeAt(e.Location);
if (tnode == null) return;
Rectangle btnRectAbsolute = new Rectangle(tnode.Bounds.Location + new Size(buttonRect.Location), buttonRect.Size);
if (btnRectAbsolute.Contains(e.Location))
{
tnode.Tag = PushButtonState.Pressed;
tnode.Text = tnode.Text;
}
}
}
Also, you can achieve this even without creating custom control - just add these event handlers to standard TreeView

Multi-tab application (C#)

I'm creating a multi-tabbed .NET application that allows the user to dynamically add and remove tabs at runtime. When a new tab is added, a control is added to it (as a child), in which the contents can be edited (eg. a text box). The user can perform tasks on the currently visible text box using a toolbar/menu bar.
To better explain this, look at the picture below to see an example of what I want to accomplish. It's just a mock-up, so it doesn't actually work that way, but it shows what I want to get done. Essentially, like a multi-tabbed Notepad.
View the image here: http://picasion.com/pic15/324b466729e42a74b9632c1473355d3b.gif
Is this possible in .NET? I'm pretty sure it is, I'm just looking for a way that it can be implemented.
You could use a simple extension method:
public static void PasteIntoCurrentTab(this TabControl tabControl)
{
if (tabControl.SelectedTab == null)
{
// Could throw here.
return;
}
if (tabControl.SelectedTab.Controls.Count == 0)
{
// Could throw here.
return;
}
RichTextBox textBox = tabControl.SelectedTab.Controls[0] as RichTextBox;
if (textBox == null)
{
// Could throw here.
return;
}
textBox.Paste();
}
Usage:
myTabControl.PasteIntoCurrentTab();
I suggest you keep some "current state" variables updated so you always have a pointer to the selected Tab Page, and its child control (in the case of a tabbed-notepad emulation discussed here : a TextBox). My preference would be to keep track of the TabPage<>TextBox connections using a Dictionary to avoid having to cast the TextBoxes if they are accessed using the TabPage.Controls route : the following code assumes you have a TabControl named 'tabControl1 on a Form :
Dictionary<TabPage, TextBox> dct_TabPageToTextBox;
int tabCnt = 1;
TabPage currentTabPage;
TextBox currentTextBox;
So, as you create each new TabPage at run-time you call something like this :
private void AddNewTabPage()
{
if (dct_TabPageToTextBox == null) dct_TabPageToTextBox = new Dictionary<TabPage, TextBox>();
currentTabPage = new TabPage("Page " + tabCnt.ToString());
tabControl1.TabPages.Add(currentTabPage);
currentTextBox = new TextBox();
dct_TabPageToTextBox.Add(currentTabPage, currentTextBox);
currentTabPage.Controls.Add(currentTextBox);
currentTextBox.Dock = DockStyle.Fill;
currentTextBox.Text = "sample text for page " + tabCnt.ToString();
tabControl1.SelectedTab = currentTabPage;
tabCnt++;
}
As the end-user changes the selected TabPage you can simply update your current state variables like this :
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
currentTabPage = tabControl1.SelectedTab;
currentTextBox = dct_TabPageToTextBox[currentTabPage];
MessageBox.Show("text in current Tab Page is : " + currentTextBox.Text);
}
So now have the code that is invoked by your menu choices applied only to the currentTextBox.
best, Bill
I tried this for fun ... I made a form with a ToolStripContainer, and a ToolStrip inside it, with the standard buttons (which includes the paste button). I renamed the paste button to pasteButton, and hooking everything up you get:
public Form2()
{
InitializeComponent();
TabControl tc = new TabControl();
toolStripContainer1.ContentPanel.Controls.Add(tc);
tc.Dock = DockStyle.Fill;
TextBox selectedTextBox = null;
pasteButton.Click += (_, __) => selectedTextBox.Paste(Clipboard.GetText(TextDataFormat.Text));
int pages = 0;
newTabButton.Click += (_,__) => {
TextBox tb = new TextBox { Multiline = true, Dock = DockStyle.Fill, ScrollBars = ScrollBars.Vertical };
TabPage tp = new TabPage("Page " + (++pages).ToString());
tc.Selected += (o, e) => selectedTextBox = e.TabPage == tp ? tb: selectedTextBox;
tp.Controls.Add(tb);
tc.TabPages.Add(tp);
tc.SelectedTab = tp;
selectedTextBox = tb;
};
}

Categories