How to make an ownerdraw Trackbar in WinForms - winforms

I'm trying to make a trackbar with a custom graphic for the slider thumb. I have started out with the following code:
namespace testapp
{
partial class MyTrackBar : System.Windows.Forms.TrackBar
{
public MyTrackBar()
{
InitializeComponent();
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
// base.OnPaint(e);
e.Graphics.FillRectangle(System.Drawing.Brushes.DarkSalmon, ClientRectangle);
}
}
}
But it never calls OnPaint. Anyone else come across this? I have used this technique before to create an ownerdraw button but for some reason it doesn't work with TrackBar.
PS. Yes, I have seen question #625728 but the solution there was to completely re-implement the control from scratch. I just want to modify the existing control a little.

If you want to paint over the top of the trackbar you can capture the WM_PAINT message manually, this means you dont have to re-write all the painting code yourself and can simply paint it, like this:
using System.Drawing;
using System.Windows.Forms;
namespace TrackBarTest
{
public class CustomPaintTrackBar : TrackBar
{
public event PaintEventHandler PaintOver;
public CustomPaintTrackBar()
: base()
{
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
// WM_PAINT
if (m.Msg == 0x0F)
{
using(Graphics lgGraphics = Graphics.FromHwndInternal(m.HWnd))
OnPaintOver(new PaintEventArgs(lgGraphics, this.ClientRectangle));
}
}
protected virtual void OnPaintOver(PaintEventArgs e)
{
if (PaintOver != null)
PaintOver(this, e);
// Paint over code here
}
}
}

I've solved it by setting the UserPaint style in the constructor like so:
public MyTrackBar()
{
InitializeComponent();
SetStyle(ControlStyles.UserPaint, true);
}
OnPaint now gets called.

In this answer PaintOver is never called, because it is never assigned, its value is null.

Related

Why would MessageBox.Show show a blank message?

I have the following code for a gallery control inside a user control that is used in an XAF Property editor.
I am using a MessageBox to troubleshoot why the OnPaint method sometimes fails.
However the MessageBox itself displays a blank message when it is located near the bottom of the screen.
using DevExpress.XtraBars.Ribbon;
using DevExpress.XtraBars.Ribbon.Gallery;
using System;
using System.Windows.Forms;
namespace MyApp.Module.Win.Features.Jama.Editors.ThinWorkflow
{
public class MyGalleryControl : GalleryControl
{
protected override GalleryControlGallery CreateGallery() { return new myGallery(this); }
protected override void OnPaint(PaintEventArgs args)
{
try
{
base.OnPaint(args);
} catch(Exception ex)
{
MessageBox.Show($"In OnPaint:inner {ex.InnerException} :full:{ex.Message}", "MyGalleryControl:OnPaint");
// throw new Exception("In OnPaint");
}
}
}
}
I think it happens when the text is long enough to cause a line wrap off the screen.

Winforms WebBrowser control without IE popups not appearing [duplicate]

I am trying to implement a simple web browser control in one of my apps. This is to help integrate a web app into a toolset i am creating.
The problem is, this web app absolutly loves popup windows....
When a popup is opened, it opens in an IE window which is not a child of the MDI Container form that my main window is part of.
How can i get any and all popups created by clicking links in my WebBrowser to be a child of my MDI container (similar to setting the MDIParent property of a form)?
Thanks in advance.
The web browser control supports the NewWindow event to get notified about a popup window. The Winforms wrapper however does not let you do much with it, you can only cancel the popup. The native COM wrapper permits passing back a new instance of the web browser, that instance will then be used to display the popup.
Taking advantage of this requires some work. For starters, use Project + Add Reference, Browse tab and select c:\windows\system32\shdocvw.dll. That adds a reference to the native COM interface.
Create a form that acts as the popup form. Drop a WebBrowser on it and make its code look similar to this:
public partial class Form2 : Form {
public Form2() {
InitializeComponent();
}
public WebBrowser Browser {
get { return webBrowser1; }
}
}
The Browser property gives access to the browser that will be used to display the web page in the popup window.
Now back to the main form. Drop a WebBrowser on it and make its code look like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
webBrowser1.Url = new Uri("http://google.com");
}
SHDocVw.WebBrowser nativeBrowser;
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
nativeBrowser = (SHDocVw.WebBrowser)webBrowser1.ActiveXInstance;
nativeBrowser.NewWindow2 += nativeBrowser_NewWindow2;
}
protected override void OnFormClosing(FormClosingEventArgs e) {
nativeBrowser.NewWindow2 -= nativeBrowser_NewWindow2;
base.OnFormClosing(e);
}
void nativeBrowser_NewWindow2(ref object ppDisp, ref bool Cancel) {
var popup = new Form2();
popup.Show(this);
ppDisp = popup.Browser.ActiveXInstance;
}
}
The OnLoad method obtains a reference to the native COM interface, then subscribes an event handler to the NewWindow2 event. I made sure to unsubscribe that event in the FormClosing event handler, not 100% sure if that's necessary. Better safe then sorry.
The NewWindow2 event handler is the crux, note that the first argument allows passing back an untyped reference. That should be the native browser in the popup window. So I create an instance of Form2 and Show() it. Note the argument to Show(), that ensures that the popup is an owned window. Substitute this as necessary for your app, I assume you'd want to create an MDI child window in your case.
Do beware that this event doesn't fire for the window displayed when Javascript uses alert(). The browser doesn't treat that window as an HTML popup and doesn't use a browser window to display it so you cannot intercept or replace it.
I found that the best way to do this was to implement/sink the NewWindow3 event
Add the reference to c:\windows\system32\shdocvw.dll as mentioned in the other answers here.
Add event handler
SHDocVw.WebBrowser wbCOMmain = (SHDocVw.WebBrowser)webbrowser.ActiveXInstance;
wbCOMmain.NewWindow3 += wbCOMmain_NewWindow3;
Event method
void wbCOMmain_NewWindow3(ref object ppDisp,
ref bool Cancel,
uint dwFlags,
string bstrUrlContext,
string bstrUrl)
{
// bstrUrl is the url being navigated to
Cancel = true; // stop the navigation
// Do whatever else you want to do with that URL
// open in the same browser or new browser, etc.
}
Set "Embed Interop Types" for the "Interop.SHDocVw" assembly to false
Set the "local copy" to true.
Source for that help MSDN Post
Refining Hans answer, you can derive the WebBrowser for accessing the COM without adding the reference. It is by using the unpublished Winforms WebBrowser.AttachInterface and DetachInterface methods.
More elaborated here.
Here is the code:
Usage (change your WebBrowser instance to WebBrowserNewWindow2)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.webBrowser1.NewWindow2 += webBrowser_NewWindow2;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
webBrowser1.NewWindow2 -= webBrowser_NewWindow2;
base.OnFormClosing(e);
}
void webBrowser_NewWindow2(object sender, WebBrowserNewWindow2EventArgs e)
{
var popup = new Form1();
popup.Show(this);
e.PpDisp = popup.Browser.ActiveXInstance;
}
public WebBrowserNewWindow2 Browser
{
get { return webBrowser1; }
}
}
Code:
using System;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace SHDocVw
{
public delegate void WebBrowserNewWindow2EventHandler(object sender, WebBrowserNewWindow2EventArgs e);
public class WebBrowserNewWindow2EventArgs : EventArgs
{
public WebBrowserNewWindow2EventArgs(object ppDisp, bool cancel)
{
PpDisp = ppDisp;
Cancel = cancel;
}
public object PpDisp { get; set; }
public bool Cancel { get; set; }
}
public class WebBrowserNewWindow2 : WebBrowser
{
private AxHost.ConnectionPointCookie _cookie;
private WebBrowser2EventHelper _helper;
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void CreateSink()
{
base.CreateSink();
_helper = new WebBrowser2EventHelper(this);
_cookie = new AxHost.ConnectionPointCookie(
this.ActiveXInstance, _helper, typeof(DWebBrowserEvents2));
}
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachSink()
{
if (_cookie != null)
{
_cookie.Disconnect();
_cookie = null;
}
base.DetachSink();
}
public event WebBrowserNewWindow2EventHandler NewWindow2;
private class WebBrowser2EventHelper : StandardOleMarshalObject, DWebBrowserEvents2
{
private readonly WebBrowserNewWindow2 _parent;
public WebBrowser2EventHelper(WebBrowserNewWindow2 parent)
{
_parent = parent;
}
public void NewWindow2(ref object pDisp, ref bool cancel)
{
WebBrowserNewWindow2EventArgs arg = new WebBrowserNewWindow2EventArgs(pDisp, cancel);
_parent.NewWindow2(this, arg);
if (pDisp != arg.PpDisp)
pDisp = arg.PpDisp;
if (cancel != arg.Cancel)
cancel = arg.Cancel;
}
}
[ComImport, Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
TypeLibType(TypeLibTypeFlags.FHidden)]
public interface DWebBrowserEvents2
{
[DispId(0xfb)]
void NewWindow2(
[In, Out, MarshalAs(UnmanagedType.IDispatch)] ref object ppDisp,
[In, Out] ref bool cancel);
}
}
}
I know the question is very old but I solved it this way: add new reference, in COM choose Microsoft Internet Controls and in the code, before the click that opens a new window add the following:
SHDocVw.WebBrowser_V1 axBrowser = (SHDocVw.WebBrowser_V1)webBrowser1.ActiveXInstance;
axBrowser.NewWindow += axBrowser_NewWindow;
and then add the following method:
void axBrowser_NewWindow(string URL, int Flags, string TargetFrameName, ref object PostData, string Headers, ref bool Processed)
{
Processed = true;
webBrowser1.Navigate(URL);
}

Capturing OnEnter() in Winforms TextBox specifically via TAB only

I want to capture when a control is TABed to.
I thought it might be easier to handle when a control is NOT entered via mouse click. I was hoping that OnMouseDown() happened before OnEnter() so I could set a flag but it doesn't. I guess I could check mouse button state in OnEnter() but that seems like a kludge.
I don't think I care about what happens when focus is set programmatically. The default, I guess.
Any ideas?
Well, it is awkward to do but you can technically spy on the input events generate by Windows before they are dispatched. Have you main form implement the IMessageFilter interface and look at the message number. For example:
public partial class Form1 : Form, IMessageFilter {
public Form1() {
InitializeComponent();
Application.AddMessageFilter(this);
this.FormClosed += delegate { Application.RemoveMessageFilter(this); };
}
public enum InputEvent { Unknown, Keyboard, Mouse };
public static InputEvent LastInputEvent { get; private set; }
bool IMessageFilter.PreFilterMessage(ref Message m) {
if (m.Msg >= 0x100 && m.Msg <= 0x109) LastInputEvent = InputEvent.Keyboard;
if (m.Msg >= 0x200 && m.Msg <= 0x20A) LastInputEvent = InputEvent.Mouse;
return false;
}
}
Sample usage:
private void textBox2_Enter(object sender, EventArgs e) {
textBox2.BackColor = LastInputEvent == InputEvent.Mouse ? Color.AliceBlue : Color.Yellow;
}
I can't think of an obvious failure mode, there might be one. Strange request btw.

Closing child window minimizes parent

The following code demonstrates an issue I'm having where closing a child window minimizes the parent window, which I dont want to happen.
class SomeDialog : Window
{
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new CustomMessageBox().ShowDialog();
}
}
class CustomMessageBox : Window
{
public CustomMessageBox()
{
Owner = Application.Current.MainWindow;
}
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new SomeDialog() { Owner = this }.Show();
}
}
Window1 is the main application window.
SomeDialog is a window that pops up on some event within Window1(double clicking window1 in the example) that needs to be modeless.
CustomMessageBox is a window that pops up on some event within "SomeDialog" (double clicking SomeDialog in the example) that needs to be modal.
If you run the application, and then double click Window1's content to bring up SomeDialog, and then you then double click SomeDialog's content to bring up the CustomMessagebox.
Now you close CustomMessagebox. Fine.
Now if you close SomeDialog, Window1 minimizes? Why is it minimizing and how can I stop it?
Edit : It appears the workaround is rather simple, using the technique suggesrted by Viv.
class SomeDialog : Window
{
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new CustomMessageBox().ShowDialog();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
Owner = null;
}
}
Why is it minimizing and how can I stop it?
Not sure about the "Why" maybe you can report it as a bug and see what they reply with as with a non-modal dialog you do not expect this to happen.
As for a workaround, Try something like this:
public partial class MainWindow : Window {
...
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
base.OnMouseDoubleClick(e);
var x = new SomeDialog { Owner = this };
x.Closing += (sender, args) => {
var window = sender as Window;
if (window != null)
window.Owner = null;
};
x.Show();
}
}
^^ This should prevent the MainWindow(parent) from minimizing when SomeDialog is closed.
My workaround for this interesting problem is to activate the MainWindow once and after that activate the SomeDialog window again.
class SomeDialog : Window
{
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
new CustomMessageBox().ShowDialog();
Owner.Activate();
Activate();
}
}
A very late answer... I just sat having this same issue and Viv's workaround solved it for me aswell. As for the "Why" part of the answer, i believe it happens when your child window spawns a child window of its own in its lifetime.
For my experience it occured whenever pressing my Save button, which is in a flow which requires a child window to be opened. But pressing the Cancel (or escape) or the windows default quit button did not invoke the issue.
First, as your code stands, I can confirm this strange behaviour. There are two things that I noticed here. The first is that the SomeDialog.Owner is not set, or that it ends up with a null value with this code:
new SomeDialog() { Owner = this }.Show();
Adding this code fixes that problem:
public SomeDialog()
{
Owner = Application.Current.MainWindow;
}
Unfortunately, this doesn't stop the MainWindow from being minimised when the child Window is closed. Then I found that I could stop it from being minimised, but only when calling new SomeDialog().ShowDialog(); instead of new SomeDialog().Show(); However, that makes this Window into a dialog, which is not what you're after I believe.
We had similar problem, but cause was quite simple. Method Close() of Window was called twice. After we had removed second call, all got back to normal.

override the super class methods in wpf Window

I wanna make a customized window base. So I made a custom window which inherits from Window.
For example:
public class MyWindowBase : Window
{
...
...
}
I wanna override the different Brushes of the super class Window for my own purpose.
In my previous experience, to override methods/properties with no abstract or virtual in the super class need the key word "new".
For example:
public new void DoSomething() { ........ base.DoSomething() ....... }
public new string SomeText { get { ... } set {......} }
It works in my previous work.
However, in this time of dealing with WPF Window, it doesn't work.
I tried to override the different Brushes of the super class Window as follows:
public new Brush BorderBrush
{
get { ... }
set { myBorderBrush = value; base.BorderBrush = null }
}
public new Brush Background
{
get { ... }
set { myBackground = value; base.Backgound = null; }
}
.....
.....
.....
I tried the change the value of the above Brushes in MyWindowBase, it just change the value of the super class Window, it doesn't change the value of myBorderBrush and myBackground.
So, how could I override the methods and properties of the super class Window?
Actually, I want to override the base background so that it will be null or transparent forever, but the changed value will be applied on my own custom Background.
Thank you very much!
If you only want to set the value, then you can set it using
this.BorderBrush = Brushes.Blue;
this.Background = Brushes.Red;
If you wish to overwrite the Property Metadata (for things like default value, property changed logic, or validation), you can use OverrideMetadata
Window.BackgroundProperty.OverrideMetadata(
typeof(MyWindowBase), myPropertyMetadata);
If you simply want to add some logic on Changed, you can use a DependencyPropertyDescriptor
var dpd = DependencyPropertyDescriptor.FromProperty(
Window.BackgroundProperty, typeof(Window));
dpd.AddValueChanged(this.Value, new EventHandler(BackgroundChanged));
private void BackgroundChanged(object sender, EventArgs e)
{
//do the code here
}
And if you're looking to override a Method, and not a Property, then you can use the override keyword
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
}

Resources