I have a user control with a scrollbar (scrollbar appears as a contained user control, which inherits from Panel, is too large). When using the mouse to scroll all is well, but trying to scroll with the mousewheel dont work.
My solution here is to set focus to my child-control in an eventhandler for Scroll. This works. Now the question; Will this result in a lot of unecessary calls to childControl.Focus()? Is there a more neat way of doing this?
Edit: I think I was a bit unclear with my question so Rephrasing the question:
is
private void ChildControl_OnScroll(object sender, ScrollEventArgs scrollEventArgs)
{
this.childControl.Focus();
}
a bad way of setting the focus? I.e. will the focus be set mutliple times each time I scroll? or rather, will this cause (tiny) performance issues.
Here's another approach that gives focus when the scrollbar area of panel1 inside SomeUserControl is clicked. It uses NativeWindow so you don't have to change the panel in your UserControl. This way Focus() will only be called once, when the mouse goes down in the scrollbar area:
public partial class SomeUserControl : UserControl
{
private TrapMouseDownOnScrollArea trapScroll = null;
public SomeUserControl()
{
InitializeComponent();
this.VisibleChanged += new EventHandler(SomeUserControl_VisibleChanged);
}
void SomeUserControl_VisibleChanged(object sender, EventArgs e)
{
if (this.Visible && trapScroll == null)
{
trapScroll = new TrapMouseDownOnScrollArea(this.panel1);
}
}
private class TrapMouseDownOnScrollArea : NativeWindow
{
private Control control = null;
private const int WM_NCLBUTTONDOWN = 0xA1;
public TrapMouseDownOnScrollArea(Control ctl)
{
if (ctl != null && ctl.IsHandleCreated)
{
this.control = ctl;
this.AssignHandle(ctl.Handle);
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCLBUTTONDOWN:
if (this.control != null)
{
Rectangle screenBounds = control.RectangleToScreen(new Rectangle(0, 0, control.Width, control.Height));
if (screenBounds.Contains(Cursor.Position))
{
control.Focus();
}
}
break;
}
base.WndProc(ref m);
}
}
}
This might be overkill for your scenario, but it demonstrates one way to trap lower level messages. As said before, you could also derive from Panel to achieve the same affect. You could also trap messages at the application level with IMessageFilter.
The MouseWheel event is an event that "bubbles". Windows sends it to the control that has the focus, regardless of where the mouse cursor is located. The most typical problem is that you have a control that cannot receive the focus. A Panel for example.
This changes when you put a control on the panel. Now that control can get the focus and gets the MouseWheel message. It won't have any use for it so the message passes to its parent. Which does have a use for it, the panel scrolls as expected.
You can get a focusable panel control from this answer. A generic "make it work like a browser or Office program" solution from this question
If childControl has a MouseEnter() event then use that instead:
private void childControl_MouseEnter(object sender, EventArgs e)
{
childControl.Focus();
}
Then the mouse wheel events should be direct to childControl.
Related
I need to update the position of a child window inside my System.Windows.Forms.MDIClient container when the user scrolls it by dragging the MDIClient's scrollbar thumb.
However I can't find an event that triggers when this happens.
Am I simply missing it, or do I need a workaround, possibly by talking direct to the scrollbar?
I've already tried handling MDIClient.Layout events, but they aren't being triggered by scrolling.
EDIT: I actually only need to know when the scrolling has stopped, in order to change my child window's position.
EDIT2: As a temporary workaround, I'm resetting the child window position on a timer every second, obviously not ideal, but better than nothing. Looks terrible though!
That's possible although a bit awkward. Winforms doesn't make it very easy to find the MdiClient window back and the class itself doesn't expose the Scroll event. That can be worked around, as always in Winforms, you have to sub-class the native MDI client window of your parent window so you can capture the WM_VSCROLL message. This code worked well, paste it into your parent form class:
void MdiClient_Scroll(object sender, ScrollEventArgs e) {
if (e.Type == ScrollEventType.EndScroll) {
// Do your stuff
//...
}
}
private MdiClientWrapper wrapper;
protected override void OnHandleCreated(EventArgs e) {
// Find the MdiClient and sub-class it so we can get the Scroll event
base.OnHandleCreated(e);
if (wrapper != null) wrapper.Scroll -= MdiClient_Scroll;
var client = this.Controls.OfType<MdiClient>().First();
wrapper = new MdiClientWrapper();
wrapper.AssignHandle(client.Handle);
wrapper.Scroll += MdiClient_Scroll;
}
private class MdiClientWrapper : NativeWindow {
public event ScrollEventHandler Scroll;
private int oldPos;
protected override void WndProc(ref Message m) {
if (m.Msg == 0x115) { // Trap WM_VSCROLL
var type = (ScrollEventType)(m.WParam.ToInt32() & 0xffff);
var pos = m.WParam.ToInt32() >> 16;
Scroll(this, new ScrollEventArgs(type, oldPos, pos));
oldPos = pos;
}
base.WndProc(ref m);
}
}
I have a user control that's overlaid over other controls. A button brings it up and I want it to hide (Visible = false) when the mouse leaves it. What event should I use? I tried Leave, but that only fires after I manually hide it. I also thought about MouseLeave, but that's fired never.
EDIT: The control consists of a ListView and a Panel with a bunch of buttons in it. They are docked straight in the control, with no top-level container.
UserControl is in fact a panel holding some controls on it for convenience and easy re-use (it has advantage of design time support). In fact when you move your mouse out of the UserControl, one of its child control fires the MouseLeave, not the UserControl itself. I think you have to implement some Application-wide MouseLeave for your UserControl like this:
public partial class YourUserControl : UserControl, IMessageFilter {
public YourUserControl(){
InitializeComponent();
Application.AddMessageFilter(this);
}
bool entered;
public bool PreFilterMessage(ref Message m) {
if (m.Msg == 0x2a3 && entered) return true;//discard the default MouseLeave inside
if (m.Msg == 0x200) {
Control c = Control.FromHandle(m.HWnd);
if (Contains(c) || c == this) {
if (!entered) {
OnMouseEnter(EventArgs.Empty);
entered = true;
}
} else if (entered) {
OnMouseLeave(EventArgs.Empty);
entered = false;
}
}
return false;
}
}
I have made a custom Number Keypad control that I want to place in my winform application. All of the buttons have an OnClick event to send a value to the focused textbox in my form where I have placed my custom control. Like this:
private void btnNum1_Click(object sender, EventArgs e)
{
if (focusedCtrl != null && focusedCtrl is TextBox)
{
focusedCtrl.Focus();
SendKeys.Send("1");
}
}
focusedCtrl is supposed to be set on the MouseDown event of the button like this:
private void btnNum1_MouseDown(object sender, EventArgs e)
{
focusedCtrl = this.ActiveControl;
}
where this.ActiveControl represents the active control on the form.
My problem is that the button always receives the focus before the event detects what the focused control was previously. How can I detect which control had the focus before the button got the focus? Is there another event I should be using? Thanks in advance!
EDIT: Also, I would rather not use the GotFocus event on each textbox in the form to set focusedCtrl since that can be tedious and because I would like to have all the coding of my custom control be in the control itself and not on the form where it is placed. (I will do this, though, if there is no other practical way to do what I am asking)
Your requirement is fairly unwise, you'll want some kind of guarantee that your button isn't going to poke text into inappropriate places. You really do need to have the form co-operate, only it knows what places are appropriate.
But it is not impossible, you can sniff at input events before they are dispatched to the control with the focus. In other words, record which control has the focus before the focusing event is fired. That's possible in Winforms with the IMessageFilter interface.
Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form, replacing your existing buttons.
using System;
using System.Windows.Forms;
class CalculatorButton : Button, IMessageFilter {
public string Digit { get; set; }
protected override void OnClick(EventArgs e) {
var box = lastFocused as TextBoxBase;
if (box != null) {
box.AppendText(this.Digit);
box.SelectionStart = box.Text.Length;
box.Focus();
}
base.OnClick(e);
}
protected override void OnHandleCreated(EventArgs e) {
if (!this.DesignMode) Application.AddMessageFilter(this);
base.OnHandleCreated(e);
}
protected override void OnHandleDestroyed(EventArgs e) {
Application.RemoveMessageFilter(this);
base.OnHandleDestroyed(e);
}
bool IMessageFilter.PreFilterMessage(ref Message m) {
var focused = this.FindForm().ActiveControl;
if (focused != null && focused.GetType() != this.GetType()) lastFocused = focused;
return false;
}
private Control lastFocused;
}
Control focusedCtrl;
//Enter event handler for all your TextBoxes
private void TextBoxesEnter(object sender, EventArgs e){
focusedCtrl = sender as TextBox;
}
//Click event handler for your btnNum1
private void btnNum1_Click(object sender, EventArgs e)
{
if (focusedCtrl != null){
focusedCtrl.Focus();
SendKeys.Send("1");
}
}
you have an event called lostFocus you can use
button1.LostFocus +=new EventHandler(dataGridView1_LostFocus);
and in the event:
Control lastFocused;
void dataGridView1_LostFocus(object sender, EventArgs e)
{
lastFocused = sender as Control;
}
in that way you can always know what is the Control that was focused previously
now, correct me if i'm wrong, but you do it for the SendKeys.Send("1"); to know which textBox need to receive the number. for that you can use GotFocus event and register only the textBoxs to it.
you can also do what windows is doing and use just one textbox like here:
if it's fits your needs
What about using this with the parameter forward = false?
Control.SelectNextControl Method
You'd probably call it on your "custom Number Keypad control".
I'm working on a Windows Forms app and I'm wanting to remove the close button from the top. I'm aware of the ControlBox option, but I'm wanting to provide a help button. Is there a way to have the Close button not visible while maintaining the help button?
Your best bet may be to subcribe to the FormClosing event of the form like so and cancel the closing action:
// In your code somewhere subscribe to this event
Form1.FormClosing += Form1_FormClosing;
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
}
The benefit of doing this is that it prevents the user from closing the application from the close button and the taskbar.
Obviously you don't want to ALWAYS cancel the form from closing. So you will want to set some type of boolean flag that you will check in the event listener as to whether you want the form to be allowed to close or not. Example:
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (BlockClosing)
e.Cancel = true;
}
EDIT: If you don't want to approach the problem that way, and you really do intend to completely remove the close button, then your best bet is to create your own custom title bar. In that case, you set the form's FormBorderStyle property to None. And you then dock your custom title bar to the top of the form. Here is some sample code from one I made a while back:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace Spectrum.UI
{
public partial class TitleBar : UserControl
{
public delegate void EventHandler(object sender, EventArgs e);
public event EventHandler MinButtonClick;
public event EventHandler MaxButtonClick;
public event EventHandler CloseButtonClick;
#region Properties
[Category("Appearance")]
public string Title
{
get { return TitleLabel.Text; }
set { TitleLabel.Text = value; }
}
[Category("Appearance")]
public bool MinimizeEnabled
{
get
{
return minButton.Visible;
}
set
{
minButton.Visible = value;
}
}
[Category("Appearance")]
public bool MaximizeEnabled
{
get
{
return maxButton.Visible;
}
set
{
maxButton.Visible = value;
}
}
#endregion
public TitleBar()
{
InitializeComponent();
ShowTitleBarImage = false;
}
#region Mouse Events
private void TitleBar_MouseDown(object sender, MouseEventArgs e)
{
this.OnMouseDown(e);
}
private void TitleBar_MouseUp(object sender, MouseEventArgs e)
{
this.OnMouseUp(e);
}
private void TitleBar_MouseMove(object sender, MouseEventArgs e)
{
this.OnMouseMove(e);
}
#endregion
#region Button Click Events
private void minButton_Click(object sender, EventArgs e)
{
if (MinButtonClick != null)
this.MinButtonClick.Invoke(this, e);
}
private void maxButton_Click(object sender, EventArgs e)
{
if (MaxButtonClick != null)
this.MaxButtonClick.Invoke(this, e);
}
private void closeButton_Click(object sender, EventArgs e)
{
if (CloseButtonClick != null)
this.CloseButtonClick.Invoke(this, e);
}
#endregion
}
}
As you can see from the image, I also added a background image to the control. Depending on your patience and your requirements, you can use images and PictureBox controls to make this look as much like a standard title bar as you need.
In the above example I placed three buttons on the control with images I found online to represent minimize, maximize, and close. in your case you would simply exclude a close button. I also placed a string on the control with an appropriate font to serve as the title of the window.
Adding the custom title bar to your form is easy.
public TitleBar titleBar = new TitleBar();
titleBar.Dock = DockStyle.Top;
titleBar.MaximizeEnabled = true;
titleBar.MinimizeEnabled = true;
titleBar.Size = new System.Drawing.Size(10, 40); // Width doesn't matter - I wanted it 40 pixels tall
titleBar.Title = "Title Example";
titleBar.MinButtonClick += titleBar_MinButtonClick;
titleBar.Max ButtonClick += titleBar_MaxButtonClick;
this.Controls.Add(this.TitleBar);
And then last step is to set up your event listeners for the min and max button clicks:
private void titleBar_MinButtonClick(object sender, EventArgs e)
{
WindowState = FormWindowState.Minimized;
}
private void titleBar_MaxButtonClick(object sender, EventArgs e)
{
WindowState = FormWindowState.Maximized;
}
You may also note that I included events for mouse down, up and move in my title bar. This was so that I could create listeners in my form to move the form when the user clicked and dragged the title bar. This is optional and depends on if you need the user to be able to move your application window.
The added benefit of doing this is that can use the title bar for additional controls. For example, my application was custom written for use on a toughbook style tablet computer with a small touchscreen display. In my application, utilization of the limited space was extremely important. I was able to further modify what I've described here to also include menu bar style control directly on the title bar. In addition, I added more buttons to the left of the stand minimize, maximize, and close buttons. Really helped me utilize every square inch of the screen in my application. Couldn't have done it with the standard title bar.
Can you simply use Form.ControlBox = false (or via the designer as you point out rather negatively in your comment) and then add a custom help button on the form?
EDIT: A colleague of mine wrote an Excel add in and had a requirement to remove the X from certain forms (e.g. a Progress Bar that shouldn't be closed). He found a function written by Stephen Bullen that did just that. I've only seen this function used in VB, but perhaps you can get some ideas or direction out of his approach of using Windows API to solve your issue.
This code will disable the Close button. I am not sure if you can actually make it invisible. http://www.codeproject.com/Articles/20379/Disabling-Close-Button-on-Forms
//
// source code
// Code Snippet
private const int CP_NOCLOSE_BUTTON = 0x200;
protected override CreateParams CreateParams
{
get
{
CreateParams myCp = base.CreateParams;
myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON ;
return myCp;
}
}
Good luck!
Please try this.ControlBox = false.
I'd like a context menu on the caption bar right click
any tips/samples pref in c# ?
UPDATE - for various reasons, right click on the form won't work because the form is not empty and the form is composited dynamically so....
You can do this by trapping the WM_NCRBUTTONDOWN notification that Windows sends when the user right-clicks the title bar. The control class does not have an event for it, you'll need to override WndProc(). Here's an example form, you'll need to add a ContextMenuStrip:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
protected void OnTitlebarClick(Point pos) {
contextMenuStrip1.Show(pos);
}
protected override void WndProc(ref Message m) {
const int WM_NCRBUTTONDOWN = 0xa4;
if (m.Msg == WM_NCRBUTTONDOWN) {
var pos = new Point(m.LParam.ToInt32());
OnTitlebarClick(pos);
return;
}
base.WndProc(ref m);
}
}
MSDN explains how to handle right-clicks on Windows Forms controls. Controls, including Forms, inherit the MouseClick event.
MouseEventArgs will tell you what button was clicked through the Button property. Have a look at the MouseButtons Enumeration.
if you handle the form mouse-click, you can then use the following code:
private void Dialog_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
this.Text = "new caption text";
}
}
But you'll have to make sure that you generate this event for the top-level control on a form. For instance if you have a group box on the form, it will receive the mouse-click events rather than the form itself, for the areas of the form that are under the group box.
There is already a menu managed by Windows when you right-click the titlebar.
Do you want to replace it completely?
If you want to add to it you will have to use the Win32 API and interop and you will have to subclass the form.
See the AppendMenu() function.
Basically you need to use p-invoke to do this. There is a really great example at Here
You can see from the example you will need to manually mimic the event handlers, but this is pretty straight forward.
You can override WndProc of the form and capture the WM_NCRBUTTONDOWN message:
protected override void WndProc(ref Message m)
{
const int WM_NCRBUTTONDOWN = 0xA4;
if (m.Msg == WM_NCRBUTTONDOWN)
{
MessageBox.Show("Caption right clicked!");
}
else
{
base.WndProc(ref m);
}
}
This code will suppress the window's context menu, however. You may not wish this. The WM_NCRBUTTONDOWN message will also be sent if you right click the window borders as well. You may not desire this either.