I'd like to create a custom context menu. The idea is to create a panel with a textBox a button and a list of labels and be able to show it on right click and make it behave exactly like a contextMenu. I can probably use a form without borders but I was thinking there might be a class I can derive from that would help me handle the positionnig of the context menu and the shading. Any ideas?
Thank you
Edit: An example to clear a few ideas: Say you have a label on your form, when you right click on it (or even left click) a menu appears. This menu is NOT the classic context menu but rather a custom panel with controls that I created personnaly. An example is search box ont top with a list of items. As you enter letters the list is trimmed to the matching items and when an item is clicked the context menu disappears and the value selected is wrtitten in the label we first clicked on.
You can use the method described here:
http://www.codeproject.com/Articles/22780/Super-Context-Menu-Strip
Since it uses ContextMenuStrip you can set its position:
contextMenuStrip1.Show(Cursor.Position);
and shadow effect:
http://msdn.microsoft.com/en-us/library/system.windows.controls.contextmenu.hasdropshadow.aspx
The simplest way (since this doesn't appear to be an actual menu) would be to create a borderless form and add shadow to it:
public class ShadowForm : Form
{
// Define the CS_DROPSHADOW constant
private const int CS_DROPSHADOW = 0x00020000;
// Override the CreateParams property
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ClassStyle |= CS_DROPSHADOW;
return cp;
}
}
}
Regarding position, there is not much to it. Just check Cursor.Position or set coordinates using the arguments in your MouseUp event handler.
Complete code would look something like:
public partial class ParentForm : Form
{
public ParentForm()
{
InitializeComponent();
}
protected override OnMouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
var menu = new CustomMenu();
menu.Location = PointToScreen(e.Location);
menu.Show(this);
}
}
}
and for the "menu" form:
public partial class CustomMenu : Form
{
public CustomMenu()
{
InitializeComponent();
this.StartPosition = FormStartPosition.Manual;
}
private const int CS_DROPSHADOW = 0x00020000;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ClassStyle |= CS_DROPSHADOW;
return cp;
}
}
protected override void OnLostFocus(EventArgs e)
{
this.Close();
base.OnLostFocus(e);
}
}
When you click outside the ContextMenu it disappears. Yes. With this CustomMenu also you can do that.
protected override void OnDeactivate(EventArgs e)
{
this.Close();
base.OnDeactivate(e);
}
I tested this and it worked fine for me. OnLeave and OnLostFocus did not fire when you click outside the form.
Related
I want my UserControl (at the middle-top in the image) to lose focus when I click outside of it, anywhere.
I have tried this:
private void Form1_Click(object sender, EventArgs e)
{
ActiveControl = null;
}
It only works when I click the Form itself, the TrackBar and the MediaPlayer. It doesn't work when I click other controls, even the FlowLayoutPanel.
What can I do about it?
Eric,
To remove the focus from an item in your custom control, you will need some other focusable control to pass the focus to. I would do something like this:
private void StealFocus() => lbl_SomeLabel.Focus();
And then drop StealFocus() in your form's click event handler:
private void Form1_Click(object sender, EventArgs e) => StealFocus();
and any other control the user might click that doesn't execute a command.
Note that you cannot set focus to the Form. Container controls like Form and Panel will pass the Focus on to their first child control. Which could be the custom control you wanted to remove focus from.
You can use a general purpose Message handler/dispatcher:
Make your Forms that need to provide control over Mouse events implement IMessageFilter, so these Forms will know beforehand what messages are sent to which child Control, what kind of event is generated and any other details that may be useful, such as the position of the Mouse pointer when the event is generated. Mouse related messages are generated in any case, no matter whether you click on a Control that usually cannot get Focus, as the Form itself, or a Panel etc.
Your Forms also implement another Interface that just defines a public event that interested parties can subscribe to in order to receive fresh notifications about these events.
This also allows Control to detect whether the Parent Form is actually a Mouse Event notifier guy.
When a Mouse event is notified (here, just the event generated when a WM_LBUTTONDOWN message is received), subscribers of the event(s) can decide to act upon it. In your case, you can call the SelectNextControl() method of the Parent Form to set a different ActiveControl, only when your UserControl has the Focus and a MouseDown event is generate outside its bounds.
Call Application.AddMessageFilter() before the Form is constructed, passing the Form instance itself, since this Form implements the IMessageFilter interface.
Remove the message filter when the Form closes.
Form side:
add a message filter to capture a mouse down event (WM_LBUTTONDOWN) and rise an event to notify the subscribers of the location where the event is generated and which is the Control that will be affected (passing its Handle).
public partial class SomeForm : Form, IMessageFilter, IMouseHandler {
private const int WM_LBUTTONDOWN = 0x0201;
public event EventHandler<MouseDownEventArgs> MouseEvent;
public SomeForm() => InitializeComponent();
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN) {
var pos = MousePosition;
MouseEvent?.Invoke(this, new MouseDownEventArgs(m.HWnd, pos));
}
return false;
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (!DesignMode) Application.AddMessageFilter(this);
}
protected override void OnHandleDestroyed(EventArgs e)
{
base.OnHandleDestroyed(e);
if (!DesignMode) Application.RemoveMessageFilter(this);
}
}
UserControl part:
if the Parent Form implements IMouseHandler, subscribe to its MouseEvent. When the event is raised, verify that the UserControl is the current ActiveControl (it contains the Focus) and that the Mouse event is generated outside its bounds. If these conditions are met, call the Parent Form's SelectNextControl() method to move the Focus elsewhere.
The bool m_MouseEventSubscribed is there because a UserControl may regenerate its Handle more than once in its life-time.
public partial class MyUserControl : UserControl
{
private bool m_MouseEventSubscribed = false;
public MyUserControl() => InitializeComponent();
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
var form = this.ParentForm;
if (form != null && form is IMouseHandler && !m_MouseEventSubscribed) {
m_MouseEventSubscribed = true;
((IMouseHandler)form).MouseEvent += (s, a) => {
if (this.ContainsFocus && !this.ClientRectangle.Contains(PointToClient(a.Position))) {
form.SelectNextControl(this, true, true, false, true);
}
};
}
}
}
IMouseHandler Interface:
public interface IMouseHandler
{
event EventHandler<MouseDownEventArgs> MouseEvent;
}
Custom EventArgs class:
using System;
using System.Drawing;
public class MouseDownEventArgs : EventArgs
{
public MouseDownEventArgs() { }
public MouseDownEventArgs(IntPtr hWnd, Point point)
{
this.ControlHandle = hWnd;
this.Position = point;
}
public IntPtr ControlHandle { get; }
public Point Position { get; }
}
When you click on controls, the click event is eaten by them and doesn't go to the form itself.
To notify the form that one of its controls was clicked, use something like this:
private void control_Click(object sender, EventArgs e)
{
// you control click code here
this.OnClick(new EventArgs());
}
The project I am working on needs a couple buttons on multiple forms, instead of doing the code shown below I was hoping it could be made global.
This is only one part of the project, all the code does is enlarge the button picture when the user hovers over it.
I've tried looking at classes, tags and attributes. I know classes can be made to use across multiple forms but I cant find out if they work with events.
private void btnEnter_MouseEnter(object sender, EventArgs e)
{
Button btn = (Button)sender;
btn.Size = new Size(299, 102);
}
private void btnLeave_MouseLeave(object sender, EventArgs e)
{
Button btn = (Button)sender;
btn.Size = new Size(289, 92);
}
You can create an inherited button. Add a new class then make sure you put : Button after the class name.
using System.Drawing;
using System.Windows.Forms;
namespace InheritedButton
{
public class ExpandButton : Button
{
public Size EnterSize { get; set; }
private Size _LeaveSize;
public Size LeaveSize
{
get
{
return (_LeaveSize);
}
set
{
_LeaveSize = value;
this.Size = LeaveSize;
}
}
public ExpandButton() : base()
{
}
protected override void OnMouseEnter(EventArgs e)
{
this.Size = EnterSize;
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
this.Size = LeaveSize;
base.OnMouseLeave(e);
}
}
}
Build your project and the new button will appear in the toolbox. Drop it onto a form/control and make sure you set the EnterSize and LeaveSize. EnterSize determines the size of the button when you mouse over and LeaveSize sets the initial size and sets the size of the button when you mouse out. You don't need to set the Size property, just set LeaveSize.
Any time you want to use the expanding/contracting button just use the inherited one instead.
Is it possible to have some common event or setting for all forms that create automatically when adding a Form to project?
For example I want to have some override event that is common for all of The Project Forms, so I want to write it once and accept for all forms and speed up developing and decrease mistakes.
Update:
in BaseForm:
public BaseForm()
{
InitializeComponent();
}
int _originalExStyle = -1;
bool _enableFormLevelDoubleBuffering = true;
protected override CreateParams CreateParams
{
get
{
if (_originalExStyle == -1)
_originalExStyle = base.CreateParams.ExStyle;
CreateParams cp = base.CreateParams;
if (_enableFormLevelDoubleBuffering)
cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
else
cp.ExStyle = _originalExStyle;
return cp;
}
}
public void TurnOnOffFormLevelDoubleBuffering(bool command)
{
_enableFormLevelDoubleBuffering = command;
}
private void BaseForm_ResizeBegin(object sender, EventArgs e)
{
TurnOnOffFormLevelDoubleBuffering(true);
}
private void BaseForm_ResizeEnd(object sender, EventArgs e)
{
TurnOnOffFormLevelDoubleBuffering(false);
}
in other forms in inherit BaseForm like this:
public partial class Form1 : BaseForm
{
public Form1()
{
InitializeComponent();
}
}
the problem is in form1 the events not fire.
what's wrong or is there any other way?
Thanks in advance.
I extended the button control to have also LabelName. When I press the button I need to write the name of the button in the label.
My first idea was using events - easy and simple.
The question is: Is there more elegant way to do it? (I've been asked to bind the button and the label)...
I think that the best way to do it would be to use an action listener and the best way to use the action listener would be to build it into your class that extends the button control so that the user doesn't have to do this on their own. It would look like this.
class Button2 : Button
{
public string LabelName = "";
public Button2()
{
this.Click += this.SetLabelName;
}
private void SetLabelName(object sender, EventArgs e)
{
this.LabelName = "Something?";
}
//You could also do this instead.
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
}
}
If you're talking about changing the Text property of an external Label control, then simply create a property in your Button to hold a reference to a Label. You can set this via the IDE like any other property:
Here's the Button class:
public class MyButton : Button
{
private Label _Label = null;
public Label Label
{
get { return _Label; }
set { _Label = value; }
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
if (this.Label != null)
{
this.Label.Text = this.Name;
}
}
}
Here's the Label after I clicked the Button:
So having trying for a while with no luck, i eventually decided to ask about a way to make a transparent controls display each others.
As you see in the picture i have 2 transparent picture boxes, they show the background very well but when it comes to the selected picturebox as you can see in the picture it only renders the background image of the form but not the other picture box below it. I know that there are a common circumstances in winforms due to the lack of proper rendering but the question is :
Is there a way to get around this rendering glitch, is there a way to make the transparent controls render each others ?
Well this is the Answer : Transparent images with C# WinForms
The transparency of a control depends on its parent control .You can however, use a custom container control instead of a picture box for the parent image.and maybe this code is usfull
using System;
using System.Windows.Forms;
using System.Drawing;
public class TransparentControl : Control
{
private readonly Timer refresher;
private Image _image;
public TransparentControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.Transparent;
refresher = new Timer();
refresher.Tick += TimerOnTick;
refresher.Interval = 50;
refresher.Enabled = true;
refresher.Start();
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
protected override void OnPaint(PaintEventArgs e)
{
if (_image != null)
{
e.Graphics.DrawImage(_image, (Width / 2) - (_image.Width / 2), (Height / 2) - (_image.Height / 2));
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//Do not paint background
}
//Hack
public void Redraw()
{
RecreateHandle();
}
private void TimerOnTick(object source, EventArgs e)
{
RecreateHandle();
refresher.Stop();
}
public Image Image
{
get
{
return _image;
}
set
{
_image = value;
RecreateHandle();
}
}
}