I've been trying to get my head around creating a custom event to handle mouse controls and such, but i keep running into certain errors that none of the tutorials seem to address. I'm pretty sure my logic of creation / assigning is correct, but maybe theres something fundamentally wrong that I'm missing here.
First off i create my delegate with the same signature as the method;
public delegate void MouseDown(object sender, MouseEventArgs e, Control control);
And then i assign it my event;
public event MouseDown OnMouseDown;
and then finally i try and subscribe to the event;
public static void Init(Control control, Control container, Direction direction)
{
control.MouseDown += OnMouseDown;
}
//ignore the parameters I'm passing, these are out of the scope of my problem.
However on this section I'm getting the error "an ohject reference is required for the non-static field, method, or propery "blah blah.OnMouseDown""
Finally heres my method I'm trying to subscribe to on mouseDown;
public void MouseDownEvent(object sender, MouseEventArgs e, Control control)
{
Dragging = true;
DragStart = new Point(e.X, e.Y);
control.Capture = true;
}
It probably doesn't help that I'm trying to modify a helper class i found somewhere. If any further information is needed feel free to ask.
Note: The prime objective of this class is to allow controls to be moved at runtime.
Edit:
I believe the first two have worked, but to move i need to use the following method;
public void MouseMoveEvent(object sender, MouseEventArgs e, Control control, Control container, Direction direction)
{
if (Dragging)
{
if (direction != Direction.Vertical)
container.Left = Math.Max(0, e.X + container.Left - DragStart.X);
if (direction != Direction.Horizontal)
container.Top = Math.Max(0, e.Y + container.Top - DragStart.Y);
}
}
so i need to send Direction direction, which i can't send as sender. The reason I'm making these changes to the whole system is, i had it working before using anonymous delegates, but these proved tough to unsubscribe from when i wanted to re-lock a control in place.
Edit 2:
scratch that, the mouseDown and mouseUp won't work if i don't pass the correct control, at least subscribing the way i was doing it before. i could try your method but, the way i was doing it i was just calling one method which subscribed to all 3 MouseControls. it looks like either i can subscribe in sepearate methods as suggest, or i need to pass the right control correctly, i.e. not sender as control. any ideas?
Currently I'm subscribing by running this method from anywhere;
helper.Init(this.Controls["btn" + i]);
and then it runs through these methods before subscribing the button to my mouseup, down and move.
public static void Init(Control control)
{
Init(control, Direction.Any);
}
public static void Init(Control control, Direction direction)
{
Init(control, control, direction);
}
public static void Init(Control control, Control container, Direction direction)
{
control.MouseDown += new MouseEventHandler(FireOnMouseDown);
control.MouseUp += new MouseEventHandler(FireOnMouseUp);
control.MouseMove += delegate(object sender, MouseEventArgs e)
{
if (Dragging)
{
if (direction != Direction.Vertical)
container.Left = Math.Max(0, e.X + container.Left - DragStart.X);
if (direction != Direction.Horizontal)
container.Top = Math.Max(0, e.Y + container.Top - DragStart.Y);
}
};
}
Note: the third subscription is how they were before (anon delegates). I believe i need to pass the correct control in the events though. Does this give more clarity to my problem?
Answer to this question:
However on this section i'm getting the error "an ohject reference is
required for the non-static field, method, or propery "blah
blah.OnMouseDown""
Init method is static which means that any non local variable used inside it must be static. In your code, the public event MouseDown OnMouseDown; must be static.
Just do this and it will work fine (without the need for a delegate):
EDIT
Please see code below to see how to get the control that has been clicked.
public static void Init(Control control, Control container, Direction direction)
{
control.MouseDown += new System.Windows.Forms.MouseEventHandler(On_MouseDown);;
}
private static void On_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
Control control = sender as Control;
if(control != null){
// Here we go, use the control to do whatever you want with it
}
}
You should write:
control.MouseDown += new MouseEventHandler(MouseDownEvent);
Be careful here that you must change the signature of MouseDownEvent, here, since the MouseDown event of Control needs only the sender and the MouseEventArgs as parameters.
But I'm not sure if this is what you really want. The effect of this instruction is that when the mouse goes down on the control MouseDownEvent is executed. Is this what you want?
The instruction public event MouseDown OnMouseDown; is unesuful unless somewhere in the same class where this appears you do not write something that fires this event in this way:
if (OnMouseDown!= null)
OnMouseDown(aSenderObject, aMouseEventArgs, aControl);
And to subscribe to thgis event you should write this (here hc anObj is an instance of the class where the event is defined):
anObj.OnMouseDown+= new MouseDown(MouseDownEvent);
Hope it helps. Maybe a little more context could be useful.
EDIT:
If you are trying to implement a class that manages the movement of controls you could have the Helper class that contains:
public delegate void MouseDown(object sender, MouseEventArgs e, Control control);
public static event MouseDown OnMouseDown;
If you want to be able to "register" a control to your class this is the way to do it (note that the event is static). Here you say that when the mouse goes down on control, FireMouseDown is executed.
public static void Init(Control control, Control container, Direction direction)
{
control.MouseDown += new MouseEventHandler(FireOnMouseDown);
}
In FireMouseDown you have to fire the OnMouseEvent with the right arguments:
public static void FireOnMouseDown(object sender, MouseEventArgs e)
{
if (OnMouseDown != null)
OnMouseDown(this, e, sender as Control);
}
Now you can subscribe to OnMouseDown your original method from outside Helper:
Helper.OnMouseDown += new MouseDown(MouseDownEvent);
EDIT2:
I didn't specify it, but what's happening here is that the OnMouseDown event is fired everytime there is a MouseDown event (the windows event, I mean) on one of the controls that were passed to Init. This means that the registration to OnMouseDown has to be done only once. After that, every time you have a MouseDown on one of these controls MouseDownEvent is executed passing these parameters:
sender -> the Helper class
e -> the original MouseEventArgs generated by Windows
control -> the control that generated the original event
With regard to the MouseMove event, you should do something like for MouseDown, just adding the extra parameters you need (even if it's not clear to me what is the meaning of container and direction, since it seems that container is always equal to control and direction is always Direction.Any). I mean something like:
public delegate void MouseMove(object sender, MouseEventArgs e, Control control, Control container, Direction direction);
public static event MouseMove OnMouseMove;
public static void FireOnMouseMove(object sender, MouseEventArgs e)
{
if (OnMouseMove != null)
OnMouseMove(this, e, sender as Control, aContainer, aDirection);
}
What I don't understand here is from where you are going to find out aContainer and aDirection. If I should replicate your Init I would write OnMouseMove(this, e, sender as Control, sender as Control, Direction.Any);, but I don't think that it would work.
Related
I have an application where I need to take a certain action when the user gets to a certain place in a ScrollViewer. This action sometimes includes scrolling the ScrollViewer to a different location programmatically.
In order to moniter the user's scrolling action, I am listening for the ViewChanged event of the ScrollViewer. The issue is that when I scroll progrmatically from within the ViewChanged event handler, that same event handler ends up getting called again, causing undesired additional scrolling to happen.
I have tried creating a custom method to remove the event handler before calling ScrollViewer.ChangeView(), but this seems to have no effect.
Can anyone come up with a way around this issue, or a way to differentiate the user's scrolling action from my programmatic one?
private void MyScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (conditionals)
{
ScrollTo(location);
}
}
private void ScrollTo(double offset)
{
MyScrollViewer.ViewChanged -= MyScrollViewer_ViewChanged;
MyScrollViewer.ChangeView(offset, null, null);
MyScrollViewer.ViewChanged += MyScrollViewer_ViewChanged;
}
It is, unfortunately, not possible to determine what triggered a ViewChanged event. It is however possible to solve this problem.
The issue is that ChangeView() is asynchronous, so re-adding the event handler immediately after calling ChangeView is too soon. ChangeView will raise a bunch of ViewChanged events with a final one where e.IsIntermediate == false; only once that happens should you re-hook the event handler. The best way to handle this might be to use a temporary event handler that waits for that e.IsIntermediate == false and then re-hooks the original handler.
To prevent the user from interacting with the ScrollViewer during the execution of ChangeView, the scroll and zoom modes can be temporarily disabled.
Finally, if the user is manipulating the ScrollViewer when the conditionals are met, that manipulation needs to be canceled before calling ScrollTo().
EDIT: In my implementation, an issue arose where because of the number of times these handlers were called, event handlers were added more than once. To combat this, I've taken the simple strategy from here.
private void MyScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (!conditionals) return;
if (e.IsIntermediate)
{
var uiElement = MyScrollViewer.Content as UIElement;
uiElement?.CancelDirectManipulations();
}
ScrollTo(location);
}
private void Temporary_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (e.IsIntermediate) return;
MyScrollViewer.ViewChanged -= Temporary_ViewChanged;
MyScrollViewer.ViewChanged -= MyScrollViewer_ViewChanged;
MyScrollViewer.ViewChanged += MyScrollViewer_ViewChanged;
MyScrollViewer.HorizontalScrollMode = ScrollMode.Enabled;
MyScrollViewer.VerticalScrollMode = ScrollMode.Enabled;
MyScrollViewer.ZoomMode = ZoomMode.Enabled;
}
private void ScrollTo(double offset)
{
MyScrollViewer.ViewChanged -= MyScrollViewer_ViewChanged;
MyScrollViewer.ViewChanged -= Temporary_ViewChanged;
MyScrollViewer.ViewChanged += Temporary_ViewChanged;
MyScrollViewer.HorizontalScrollMode = ScrollMode.Disabled;
MyScrollViewer.VerticalScrollMode = ScrollMode.Disabled;
MyScrollViewer.ZoomMode = ZoomMode.Disabled;
MyScrollViewer.ChangeView(offset, null, null);
}
If I have a button which does something and also a double-click event on a data grid which I want to do the same thing, what is the best way to ensure that only one function has to be maintained?
Apart from doing the following, is there any fancy C# way to indicate that two events are to do the same thing?
void button1_Click(...) { MyFunction(); }
void dataGrid1_DoubleClick(...) { MyFunction(); }
void MyFunction() { // do stuff }
I suppose that you are talking about a DataGridView (WinForms) so the signature of the event DoubleClick in the DataGridView and the signature of Click event on a button control is the same.
(An EventHadler). In this case you can simply set the same method using the form designer or manually bind the event
dataGridView1.DoubleClick += new EventHandler(MyFunction);
button1.Click += new EventHandler(MyFunction);
Of course the MyFunction method should match the expected signature of an EventHandler
private void MyFunction(object sender, EventArgs e)
{
// do your work
}
Reviewing my answer after a few minutes I wish to add:
If you find yourself in a situation in which you need to differentiate between the controls using the sender object (like Control c = sender as Control; if (c.Name == "someName") ) I really suggest you to return to the first idea. Call a common method but keep the EventHandler separated for each control involved.
Using VS, in the form's designer view You can set the procedure You want to call to each control's each event in the control's properties window.
image
Just to add to what Steve said, you will want to bind these events to your function manually in the Load event of your form, instead of using the events under the lightning bolt in the properties window in the designer, like so:
private void Form1_Load(object sender, EventArgs e)
{
button1.Click += MyMethod;
dataGridView1.DoubleClick += MyMethod;
}
void MyMethod(object sender, EventArgs e)
{
//Do Stuff
}
Also, declaring a new instance of the EventHandler class has been redundant since Anonymous methods were introduced to C#, you can just point the event directly at the method as shown above.
I'm creating listviews in a flowpanel at run time which later will accept drag and dropped files. the reason being is i want these to act as folders so a user double clicks and gets a window displaying the contents.
i'm having difficulty setting up the events for my listviews as they are added.
how do i create some events (like MouseDoubleClick and DragDrop) dynamically for each added listview? can i create a single function for both of these events and have listview1, listview2, listviewX use it?
i have a button that is adding the listviews, which works fine. please advise, i apologize if this is too conceptual and not exact enough.
private void addNewWOButton_Click(object sender, EventArgs e)
{
ListView newListView = new ListView();
newListView.AllowDrop = true;
flowPanel.Controls.Add(newListView);
}
You would have to have the routine already created in your code:
private void listView_DragDrop(object sender, DragEventArgs e) {
// do stuff
}
private void listView_DragEnter(object sender, DragEventArgs e) {
// do stuff
}
and then in your routine, your wire it up:
private void addNewWOButton_Click(object sender, EventArgs e)
{
ListView newListView = new ListView();
newListView.AllowDrop = true;
newListView.DragDrop += listView_DragDrop;
newListView.DragEnter += listView_DragEnter;
flowPanel.Controls.Add(newListView);
}
You would have to check who the "sender" is if you need to know which ListView control is firing the event.
You can also just use a lambda function for simple things:
newListView.DragEnter += (s, de) => de.Effect = DragDropEffects.Copy;
Just make sure to unwire the events with -= if you also remove the ListViews dynamically.
To answer the other half of your question, you can use a single handler for any event, from any source, that has the handler's signature. In the body of the handler, you just have to check the sender argument to determine which control raised the event.
You need a way to tell one control from a different one of the same class, however. One way to do this is to make sure to set the Name property on each control when you create it; e.g., newListView.Name = "FilesListView".
Then, before you do anything else in your event handler, check the sender.
private void listView_DragDrop(object sender, DragEventArgs e) {
ListView sendingListView = sender as ListView;
if(sendingListView == null) {
// Sender wasn't a ListView. (But bear in mind it could be any class of
// control that you've wired to this handler, so check those classes if
// need be.)
return;
}
switch(sendingListView.Name) {
case "FilesListView":
// do stuff for a dropped file
break;
case "TextListView":
// do stuff for dropped text
break;
.....
}
}
I have a WinForms app that contains many NumericUpDown controls. In a nutshell, if my users enter a value into the control and then delete the text, I want to restore it (the text) when the control loses focus. So I decided that I'd check .Text when the control loses focus and if it's empty, I set .Text = .Value.ToString().
I'm doing this in the Leave event handler and it works just fine. But as I said, I have many of these controls (18, to be exact). I don't like creating 18 Leave event handlers that all do the same thing so I created a generic one like this:
private void numericUpDown_GenericLeave(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(((NumericUpDown)sender).Text))
((NumericUpDown)sender).Text = ((NumericUpDown)sender).Value.ToString();
}
I started to hook up all of the controls to this generic event handler but I quickly got tired of doing this:
numericUpDown1.Leave += numericUpDown_GenericLeave;
numericUpDown2.Leave += numericUpDown_GenericLeave;
numericUpDown3.Leave += numericUpDown_GenericLeave;
...
numericUpDown18.Leave += numericUpDown_GenericLeave;
So I thought I'd create a function that would return a list of all the controls of a specified type and then loop through that list and hookup the event handlers. That function looks like this:
public static List<Control> GetControlsOfSpecificType(Control container, Type type)
{
var controls = new List<Control>();
foreach (Control ctrl in container.Controls)
{
if (ctrl.GetType() == type)
controls.Add(ctrl);
controls.AddRange(GetControlsOfSpecificType(ctrl, type));
}
return controls;
}
I call the function like this:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
foreach (var numericUpDownControl in listOfControls)
{
numericUpDownControl.Leave += numericUpDown_GenericLeave;
}
When I run my app, however, I don't see the expected behavior that occurs when I manually hookup each control to the generic event handler. This code is currently in the constructor of my form and I've tried calling it before as well as after the call to InitializeComponent() but neither one seems to be working. I get no error of any kind, I just don't see the behavior that I was expecting. I have a breakpoint set inside the generic event handler but the debugger never breaks so it seems like the event handler isn't being hooked up correctly. Does anyone know why this might be or how I can troubleshoot it further? Thanks!
EDIT
I just realized that the call to:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
was happening before the call to InitializeComponent() so of course the list of controls being returned was empty. DOH! Thanks for all the replys. I apologize for wasting everyones time. :-(
You're passing this to your method, which is presumably a reference to your form. Your method will only catch the controls that are placed directly on your form. Any NumericUpDown controls that are not directly on the form (i.e. they're sitting on a panel or something) will be missed.
Why not create a user control that has a NumericUpDown control in it.
Then handle this is in the user control events.
This worked for me:
private decimal _previous = 0;
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
if (((NumericUpDown)sender).Text.Length > 0)
{
_previous = this.numericUpDown1.Value;
}
}
private void UserControl1_Leave(object sender, EventArgs e)
{
if (this.numericUpDown1.Text == "")
{
this.numericUpDown1.Value = _previous;
this.numericUpDown1.Text = System.Convert.ToString(_previous);
}
}
Just note that the Leave event is on the user control not on the updown control itself.
Question answered. See Edit above. Thanks to bsegraves for pointing me in the right direction.
I have a c# form (let's call it MainForm) with a number of custom controls on it. I'd like to have the MainForm.OnClick() method fire anytime someone clicks on the form regardless of whether the click happened on the form or if the click was on one of the custom controls. I'm looking for behavior similar to the KeyPreview feature of forms except for mouse clicks rather than key presses.
I recommend creating a base form for the other forms in your application to inherit. Add this code to your base form to create a new event called GlobalMouseClickEventHandler:
namespace Temp
{
public delegate void GlobalMouseClickEventHander(object sender, MouseEventArgs e);
public partial class TestForm : Form
{
[Category("Action")]
[Description("Fires when any control on the form is clicked.")]
public event GlobalMouseClickEventHander GlobalMouseClick;
public TestForm()
{
InitializeComponent();
BindControlMouseClicks(this);
}
private void BindControlMouseClicks(Control con)
{
con.MouseClick += delegate(object sender, MouseEventArgs e)
{
TriggerMouseClicked(sender, e);
};
// bind to controls already added
foreach (Control i in con.Controls)
{
BindControlMouseClicks(i);
}
// bind to controls added in the future
con.ControlAdded += delegate(object sender, ControlEventArgs e)
{
BindControlMouseClicks(e.Control);
};
}
private void TriggerMouseClicked(object sender, MouseEventArgs e)
{
if (GlobalMouseClick != null)
{
GlobalMouseClick(sender, e);
}
}
}
}
This solution will work not only for top-level controls, but also nested controls such as controls placed inside of panels.
In the form's ControlAdded event, add a MouseClick handler to the control, with the Address of the form's click event. I haven't tested this, but it might work.
Private Sub Example_ControlAdded(ByVal sender As Object, ByVal e As System.Windows.Forms.ControlEventArgs) Handles Me.ControlAdded
AddHandler e.Control.MouseClick, AddressOf Example_MouseClick
End Sub
Private Sub Example_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick
MessageBox.Show("Click")
End Sub
The only way I've ever managed to do this is to handle the [c]Click[/c] event of every control. I don't believe the event is raised before the control processes it.
In WPF, there are "tunneling" preview events that provide this functionality, but that doesn't really help you in WinForms.
You can hook all the control's events, if you like, and then monitor that way. I assume there is some uber fancy Win32 api way to trap them all, but that is beyond me at the moment.
public Form1()
{
InitializeComponent();
HookEvents();
}
private void HookEvents() {
foreach (Control ctl in this.Controls) {
ctl.MouseClick += new MouseEventHandler(Form1_MouseClick);
}
}
void Form1_MouseClick(object sender, MouseEventArgs e)
{
LogEvent(sender, "MouseClick");
}
// and then this just logs to a multiline textbox you have somwhere on the form
private void LogEvent(object sender, string msg) {
this.textBox1.Text = string.Format("{0} {1} ({2}) \n {3}",
DateTime.Now.TimeOfDay.ToString(),
msg,
sender.GetType().Name,
textBox1.Text
);
}
The output is something like this, showing all the events and who "sent" them up:
14:51:42.3381985 MouseClick (Form1)
14:51:40.6194485 MouseClick (RichTextBox)
14:51:40.0100735 MouseClick (TextBox)
14:51:39.6194485 MouseClick (Form1)
14:51:39.2131985 MouseClick (RichTextBox)
14:51:38.8694485 MouseClick (Button)
HTH.
Catching a click on an open space on the form is easy, but to get a click that's actually on a control, you'll need the cooperation of that control to send it to the form.
One possibility is to place a transparent control over the entire form, and accept clicks onto that, deal with them, and then pass them onto the proper control underneath.
This is a common pattern in development, its called the Observer pattern. There are lots of examples of Observer patterns and c# here is 1 example http://msdn.microsoft.com/en-us/library/ms954621.aspx but have a good google around.