Hey, i'm in a bit of a deadline here, so i need your help. I'm trying to develop an application that, upon selection of an event and its respective "listener" (a button), fires the event name and the current time. Thing is, so i can print the event name i must be able to pass the event name to the handler function. I've done this:

Dictionary<Generico,Control> lst = new Dictionary<Generico,Control>();

where Generico is an inner class:

public class Generico : FormRecorder//Inner class
        {
            private EventInfo nome;
            private bool rec;
      
            public Generico(EventInfo e,bool isrec)
            {       
                    nome = e;
                    rec = isrec;
            }

            public String getName()
            {
                return nome.Name;
            }

            public EventInfo getEvtInfo()
            {
                return nome;
            }
            public bool getRec(){
                return rec;
            }

            public override string ToString()
            {
                return (nome.Name + ":");
            }
        }

And here's the code thats giving me problems...if i select just one event and one button, it all works fine. but if i select more that one event/button, it just works for the first event assigned.

private void chkLstBx2_ItemCheck(object sender, ItemCheckEventArgs ice)
        {

            for(int i = 0; i<chkLstBx1.Items.Count; i++){

            if(chkLstBx1.GetItemCheckState(i) == CheckState.Checked){
    
              EventInfo[] eventList = f.Controls[i].GetType().GetEvents(); //eventos associados ao botao seleccionado

                   foreach(EventInfo e in eventList){

                        if (chkLstBx2.Items[ice.Index].Equals((e.Name)))
                        {
                            Type eventHandlerType = e.EventHandlerType;
                            Generico g = new Generico(e, isRec);
                            
                            MethodInfo mi = this.GetType().GetMethod("GenericMethod", BindingFlags.Instance | BindingFlags.Public |
                                                                BindingFlags.NonPublic);
                            Delegate d = Delegate.CreateDelegate(eventHandlerType,this,mi,true);

                            e.AddEventHandler(f.Controls[i], d);
                                lst.Add(g,f.Controls[i]); //Nao posso adicionar duas keys iguais
                               break;


                        }
                    }
                }

            } 
        }

        public void GenericMethod(Object s, EventArgs evtargs) //o sender é o botao que dispara o evento
        {
            //Ler as assinaturas dos handlers dos eventos de outros tipos
            //Construir chamada ao método onEvent utilizando os casts necessários

            Control ctrl = s as Control;

            if (ctrl != null)
            {
          
                listBox1.Items.Add(lst[ctrl].Name.ToString() + DateTime.Now.TimeOfDay);
            }
        }

I dont know how i can get info from the fired event in the handler method. Please help!! thanks ;)

Recommended Answers

All 30 Replies

Welcome Nogat21!

First : post your code in code tags, info is available on this site.
Second : I think you are having problems with the checed listbox class. It does not always behave as you think. I don't use it any more.

Thanks...sorry for the code thing, im new at this :P

Well i'm getting problems for two reasons: first, not all events that can occur to a button instance are of the EventHandler type, so the handler methods to associate with the events can have different signatures. My answer to this is to associate a generic method (signature: void (object, object)) to the event. However, emerges problem number two...how can i print on a message box the name of the raised event when it occurs?

private void chkLstBx2_ItemCheck(object sender, ItemCheckEventArgs ice)
        {
        
            for (int i = 0; i < chkLstBx1.Items.Count; i++)
            {

if (chkLstBx1.GetItemCheckState(i) == CheckState.Checked)
                {

                    EventInfo[] eventList = f.Controls[i].GetType().GetEvents(); //eventos associados ao botao seleccionado

                    foreach (EventInfo e in eventList)
                    {

                        if (chkLstBx2.Items[ice.Index].Equals((e.Name)))
                        {
                            Type eventHandlerType = e.EventHandlerType;
                            Generico g = new Generico(e, isRec);

                            MethodInfo mi = this.GetType().GetMethod("GenericMethod", BindingFlags.Instance | BindingFlags.Public |
                                                                BindingFlags.NonPublic);
                            Delegate d = Delegate.CreateDelegate(eventHandlerType, this, mi, true);

                            e.AddEventHandler(f.Controls[i], d);

the GenericMethod is supposed to print on the textbox the name of the event and the time of the day. But how can i keep track of the name of the events generated when there are more than 1 event selected for "listening"?

public void GenericMethod(Object s, Object evtargs) 
        {
  //argument s is the control that generated the event

Control c = s as Control;

if(c!=null)
                    listBox1.Items.Add(//Event name goes here + DateTime.Now.TimeOfDay);
            }

        }

I hope i was clear in describing my problem...i'm getting nuts with this :P Thanks a lot ;)

Did you take this from the Trab project out of curiosity? And use the signature (object sender, EventArgs e). This first param for 99% of events is sender, and the second inherits from EventArgs.

For the next question the only way I know you can tell which event fired is to take a look at the Environment.Callstack when the generic event is fired.

I've solved this...i did this:

public class Generico : FormRecorder//Inner class
        {
            private EventInfo nome;
            public event GenericEvent FiredEvent;
            public TimeSpan time;

            public Generico(EventInfo e, bool isrec)
            {
                nome = e;
                
            }

public void GenericHandleEvent(object o, object i)
            {
                FiredEvent.Invoke(this,i);
            }
private void chkLstBx2_ItemCheck(object sender, ItemCheckEventArgs ice)
        {
           
            for (int i = 0; i < chkLstBx1.Items.Count; i++)
            {

                if (chkLstBx1.GetItemCheckState(i) == CheckState.Checked)
                {

                    EventInfo[] eventList = f.Controls[i].GetType().GetEvents(); //eventos associados ao botao seleccionado

                    foreach (EventInfo e in eventList)
                    {

                        if (chkLstBx2.Items[ice.Index].Equals((e.Name)))
                        {
                            Type eventHandlerType = e.EventHandlerType;
                            Generico g = new Generico(e, isRec);

                            g.FiredEvent += EventList;

                            MethodInfo mi = g.GetType().GetMethod("GenericHandleEvent", BindingFlags.Instance | BindingFlags.Public |
                                                                BindingFlags.NonPublic);
                            Delegate d = Delegate.CreateDelegate(eventHandlerType, g, mi, true);

                            e.AddEventHandler(f.Controls[i], d);
                            if (isRec)
                            {
                                lst.Add(g, f.Controls[i]);
                            }
                            break;

                        }
                    }
                }
            }
        }
private void EventList(object o, object e)
        {
            Generico c = o as Generico;
            TimeSpan tempo = DateTime.Now.TimeOfDay;
            if (c != null)
            {
                c.time = tempo;
                listBox1.Items.Add(c.getName() + ": " + tempo);
            }
        }

Everything inside the form class....
Now my task is to record all events selected upon the selected buttons and, when the replay button is pressed, the events are fired again, maintaining the time difference on which they ocurred originally. How can i do this? thanks ;)

Post what you have so far and I will take a look at it. Another guy was doing this exact same project 2 days ago... are you and someone else on here working on this project?

Probably we're class colleagues cos this is a this is a college assingment.
Thanks for your attention...i'm sick and tired of this for today. Here's what i've got:

Good job on encapsulating the event handler like that. I didn't think of doing that :)

I have taken a look at it and it seems to me the only real way to "Record" and "Playback" is to capture the mouse and keyboard input and make the application replay that behavior. You can't really fire off the mousehover event from code without actually hovering ... or you could but it wouldn't make sense. You need to replay the source action which is the human input devices (mouse, keydb, etc). Is this an acceptable solution?

Well we're not supposed to record the input behaviours...we're supposed to call (maintaining the original time differences in which the events originally ocurred) the methods called "on + event name" (for example, for Click events we should call the OnClick method)...thats why i do the following piece of code:

public void Replay()
            {
                
                MethodInfo minf = origin.GetType().GetMethod("On" + nome.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
              
                minf.Invoke(origin, new object[1]);
            }

Those methods are within the framework, so i just have to invoke them right? by the way, when i use the invoke method upon the methodinfo instance, what is the purpose of the second parameter that i pass (an object[])? i tried to pass it a parameterinfo[] but i get a execution time exception saying that it isnt possible to pass a parameterinfo[] when the method expects an eventargs (wtf??).

Thanks for your help ;)

You are right .. so it is possible. Let me give it a go.

The reason you are calling new object[1] is because when you invoke a method from reflection you have to have the same number of arguments as the method, and of the same type. In the .NET CLR every managed type can be boxed to an object so it is always safe to use object. The problem is if anything tries to access the parameters in the method they will get a null reference exception but the .CLR is smart so we don't have to worry about that here (i think). However to be safe lets do this. Take a look at all of the On* methods the button has:

protected override void OnClick(EventArgs e);
    protected override void OnFontChanged(EventArgs e);
    protected override void OnMouseEnter(EventArgs e);
    protected override void OnMouseLeave(EventArgs e);
    protected override void OnMouseUp(MouseEventArgs mevent);
    protected override void OnTextChanged(EventArgs e);
    protected override void OnEnabledChanged(EventArgs e);
    protected override void OnGotFocus(EventArgs e);
    protected override void OnKeyDown(KeyEventArgs kevent);
    protected override void OnKeyUp(KeyEventArgs kevent);
    protected override void OnLostFocus(EventArgs e);
    protected override void OnMouseDown(MouseEventArgs mevent);
    protected override void OnMouseEnter(EventArgs eventargs);
    protected override void OnMouseLeave(EventArgs eventargs);
    protected override void OnMouseMove(MouseEventArgs mevent);
    protected override void OnMouseUp(MouseEventArgs mevent);
    protected override void OnPaint(PaintEventArgs pevent);
    protected override void OnParentChanged(EventArgs e);
    protected override void OnTextChanged(EventArgs e);
    protected override void OnVisibleChanged(EventArgs e);
    protected virtual void OnAutoSizeChanged(EventArgs e);
    protected virtual void OnBackColorChanged(EventArgs e);
    protected virtual void OnBackgroundImageChanged(EventArgs e);
    protected virtual void OnBackgroundImageLayoutChanged(EventArgs e);
    protected virtual void OnBindingContextChanged(EventArgs e);
    protected virtual void OnCausesValidationChanged(EventArgs e);
    protected virtual void OnChangeUICues(UICuesEventArgs e);
    protected virtual void OnClick(EventArgs e);
    protected virtual void OnClientSizeChanged(EventArgs e);
    protected virtual void OnContextMenuChanged(EventArgs e);
    protected virtual void OnContextMenuStripChanged(EventArgs e);
    protected virtual void OnControlAdded(ControlEventArgs e);
    protected virtual void OnControlRemoved(ControlEventArgs e);
    protected virtual void OnCreateControl();
    protected virtual void OnCursorChanged(EventArgs e);
    protected virtual void OnDockChanged(EventArgs e);
    protected virtual void OnDoubleClick(EventArgs e);
    protected virtual void OnDragDrop(DragEventArgs drgevent);
    protected virtual void OnDragEnter(DragEventArgs drgevent);
    protected virtual void OnDragLeave(EventArgs e);
    protected virtual void OnDragOver(DragEventArgs drgevent);
    protected virtual void OnEnabledChanged(EventArgs e);
    protected virtual void OnEnter(EventArgs e);
    protected virtual void OnFontChanged(EventArgs e);
    protected virtual void OnForeColorChanged(EventArgs e);
    protected virtual void OnGiveFeedback(GiveFeedbackEventArgs gfbevent);
    protected virtual void OnGotFocus(EventArgs e);
    protected virtual void OnHandleCreated(EventArgs e);
    protected virtual void OnHandleDestroyed(EventArgs e);
    protected virtual void OnHelpRequested(HelpEventArgs hevent);
    protected virtual void OnImeModeChanged(EventArgs e);
    protected virtual void OnInvalidated(InvalidateEventArgs e);
    protected virtual void OnKeyDown(KeyEventArgs e);
    protected virtual void OnKeyPress(KeyPressEventArgs e);
    protected virtual void OnKeyUp(KeyEventArgs e);
    protected virtual void OnLayout(LayoutEventArgs levent);
    protected virtual void OnLeave(EventArgs e);
    protected virtual void OnLocationChanged(EventArgs e);
    protected virtual void OnLostFocus(EventArgs e);
    protected virtual void OnMarginChanged(EventArgs e);
    protected virtual void OnMouseCaptureChanged(EventArgs e);
    protected virtual void OnMouseClick(MouseEventArgs e);
    protected virtual void OnMouseDoubleClick(MouseEventArgs e);
    protected virtual void OnMouseDown(MouseEventArgs e);
    protected virtual void OnMouseEnter(EventArgs e);
    protected virtual void OnMouseHover(EventArgs e);
    protected virtual void OnMouseLeave(EventArgs e);
    protected virtual void OnMouseMove(MouseEventArgs e);
    protected virtual void OnMouseUp(MouseEventArgs e);
    protected virtual void OnMouseWheel(MouseEventArgs e);
    protected virtual void OnMove(EventArgs e);
    protected virtual void OnNotifyMessage(Message m);
    protected virtual void OnPaddingChanged(EventArgs e);
    protected virtual void OnPaint(PaintEventArgs e);
    protected virtual void OnPaintBackground(PaintEventArgs pevent);
    protected virtual void OnParentBackColorChanged(EventArgs e);
    protected virtual void OnParentBackgroundImageChanged(EventArgs e);
    protected virtual void OnParentBindingContextChanged(EventArgs e);
    protected virtual void OnParentChanged(EventArgs e);
    protected virtual void OnParentCursorChanged(EventArgs e);
    protected virtual void OnParentEnabledChanged(EventArgs e);
    protected virtual void OnParentFontChanged(EventArgs e);
    protected virtual void OnParentForeColorChanged(EventArgs e);
    protected virtual void OnParentRightToLeftChanged(EventArgs e);
    protected virtual void OnParentVisibleChanged(EventArgs e);
    protected virtual void OnPreviewKeyDown(PreviewKeyDownEventArgs e);
    protected virtual void OnPrint(PaintEventArgs e);
    protected virtual void OnQueryContinueDrag(QueryContinueDragEventArgs qcdevent);
    protected virtual void OnRegionChanged(EventArgs e);
    protected virtual void OnResize(EventArgs e);
    protected virtual void OnRightToLeftChanged(EventArgs e);
    protected virtual void OnSizeChanged(EventArgs e);
    protected virtual void OnStyleChanged(EventArgs e);
    protected virtual void OnSystemColorsChanged(EventArgs e);
    protected virtual void OnTabIndexChanged(EventArgs e);
    protected virtual void OnTabStopChanged(EventArgs e);
    protected virtual void OnTextChanged(EventArgs e);
    protected virtual void OnValidated(EventArgs e);
    protected virtual void OnValidating(CancelEventArgs e);
    protected virtual void OnVisibleChanged(EventArgs e);

So instead of new object[1] lets invoke it this way:

minf.Invoke(origin, new object[] { new EventArgs() });

I have a better understanding of your project now so let me take a look at it again for firing the events in time.

Ah...now i understand it...i was trying to pass the parameterinfo array (and this might sound stupid) cos i thought thi second parameter was the set of parameters for the corresponding "on" method.

Thanks

OK I made a number of changes in your source code. I commented the code with "//sk" above the areas I made changes. The biggest changes were making your replay on another thread so you didn't lock the main thread up while replaying events .. which causes the UI never to update and it doesn't look like your program works.

Here is how I stuck it in another thread:

private void RepBt_Click(object sender, EventArgs e)
    {
      f.ResetForm();
      //foreach is somewhat slower than this because it calls IEnumerable.GetEnumerator()
      //so lets use a for() statement since time is important

      Thread t = new Thread(FormRecorder.DoWork);
      t.Start(lst);
      Thread.Sleep(1000); //make sure it has enough time to start
    }

    public static void DoWork(object o)
    {
      
      List<Generico> lst = (o as List<Generico>);
      for (int i1 = 0; i1 < lst.Count; i1++)
      {
        Generico g = lst[i1];
        DateTime prevRecTime = (i1 == 0 ? DateTime.MaxValue : lst[i1 - 1].time);
        int wait = (int)Math.Max(lst[i1].time.Subtract(prevRecTime).TotalMilliseconds, 0);
        Thread.Sleep(wait);
        g.Replay();
      }
    }

By doing this you were calling .Replay() on another thread which will cause an exception to be thrown, so we had to invoke the replay on the control's thread:

public void Replay()
      {
        //Lets make this guy thread safe
        MethodInfo minf = origin.GetType().GetMethod("On" + nome.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        ParameterInfo[] pi = minf.GetParameters();
        origin.Invoke((MethodInvoker)delegate
        {
          minf.Invoke(origin, new object[] { new EventArgs() });
        });
      }

I use VS2008 so you will have to merge the code by hand i'm afraid.

Well it's cool, i like the changes you've made...but probably i wasnt able to explain myself properly... With your changes, the event repeats itself for an infinite amount of times, and i want it to replicate the number of times and the temporal space between the firing of the events.

To test the application we should do the following:
1.click the record button, so the record process begins
2.select the buttons to "listen"
3.Select the events to fire on the buttons selected previously
4.Mess around with the loggedform, generating the events selected
5. Back on the formrecorder, click replay...and then sit back and watch the events happening at the same pace they did when we generated them.

i'm sorry to disturb you like this...i bet you have better things to do. But if you could give me some tips on how to deal with this, it would be great.

Btw, in my college course (CLR via C#) we dont approach threading. So when i present my work to the professor we will have a long discussion about this :P but i dont care, its almost the same as the use of threads in java :)

In my original code i was saving the time in which the events were fired and then, when the replay button was clicked, i was "foreaching" each event in the list, getting the time difference between the current and the next event in the list, and making a thread.sleep with that difference before the calling of the replay method.

I thought that MO would be right for serving the 2 purposes...calling the events the same amount of times as they happened and with the same time differences. What do u think?

The threads repeat an infinite amount of times if you forget to click on the "Record" button after you're done recording because then it adds the events you are invoking from the replay in the list ... then it keeps growing. That happened to me as well. You should disable allowing playback while the application is recording.

Upload your new application so I can see where it got buggered up in translation.

Here it is:

Whats wrong with it right now? Currently the time is off a little bit (<1s) but that is all that I see.

Wait -- I use the app differently than you. I:
1. Click "All" for the button chooser
2. Select the "Click" event in the event chooser
3. Select record
4. Go over and press buttons on the other screen
5. Go back and hit the "Record" button to stop it again.
6. Hit replay

The problem is when you replay the events, you replay them an infinite amount of times.

Imagine you generate for 2 times the event click. And it's printed in the text box: click:15:35:10 and click:15:35:20.

When you replay them you should read: click:17:30:00 and click:17:30:10 (assuming that you click replay at 17:30:00).

It doesn't repeat forever if you use the app the way I explained above...
Change your click:

private void RepBt_Click(object sender, EventArgs e)
        {
          if (isRec)
            throw new Exception("You must stop recording first");
          f.ResetForm();
          //foreach is somewhat slower than this because it calls IEnumerable.GetEnumerator()
          //so lets use a for() statement since time is important

          Thread t = new Thread(FormRecorder.DoWork);
          t.Start(lst);
          Thread.Sleep(1000); //make sure it has enough time to start
        }

See if it blows up

No it doesnt blow up...thanks!

But the time difference problem remains. If i generate the event 2 times in 5 seconds, when i do the replay the amount of times on which the events ocurr is fine(2), but they come with the same time, and not with the 5 second difference.

That doesn't happen on my end. I don't know what to say :(

You mean when you hit replay the events appear with the same difference as of when you originally generated them?

It is less than <1s off but yes. The reason for the delay is because if the original delay was 2 seconds then when the events are replayed the thread sleeps for 2 seconds, then fires off the event, which is ~2.1 seconds total. The thread sleep should be decreased slightly to allow for the execution time.

i've tested it with the click event and the mouse leave event...well when i hit replay, it doesnt keep the original time difference, but its only for a bit, so its not important. thing is, when i select only one event to be replicated, the times are always the same. Heres what im saying:

I don't know man.. there are a lot of bugs in the UI of that application so its hard to say since we're running on different versions of visual studio. Like one is if you select the event "click", unselect it, then reselect it, it registers 2 delegates for it so it double clicks the button basically. There could be a lot of things that are going wrong

[edit] wait .. i think I was running the wrong version of your proj[/edit]

I was running the wrong version but it still works on my end...

Wait a minute .. give me the exact click sequence you are doing with your user interface from the moment you start it until that screenshot was taken. I think you might be tripping one of the UI bugs...

1.Click record
2.Select the button
3.Select the "click" event
4. on logged form click on the button...wait around 5 secs and click again
5.Back on formrecorder, click record to stop recording, and then click replay

nevermind this post. it made no difference

AHHHH...holy shit, it works! Jesus how dumb am i? i was running it in debug mode, and i was doing it wrong. now in release mode it all works fine.

I cant thank you more for your help...thanks a lot man, you'r a life saver...cheers;)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.