Re: How to imitiate mouse move programmatically? [NSApp postEvent:atStart:] does not work...
Re: How to imitiate mouse move programmatically? [NSApp postEvent:atStart:] does not work...
- Subject: Re: How to imitiate mouse move programmatically? [NSApp postEvent:atStart:] does not work...
- From: Steve Christensen <email@hidden>
- Date: Tue, 03 Nov 2009 14:06:04 -0800
Oleg, had you thought of doing something like adding -isSelectionValid
and -setSelectionValid: methods to your controller class? The view
would always keep track of the mouse state, tell the controller what
the current selection is when the mouse moves, but won't update itself
or respond to mouse clicks while the lengthy operation is in progress.
No fake events since the view has been tracking the real mouse all
along. Just call -setNeedsDisplay: inside -setSelectionValid: to
automagically get the view to redraw itself to reflect the current
mouse state.
steve
On Nov 3, 2009, at 5:46 AM, Oleg Krupnov wrote:
Graham, I really appreciate you taking your time to write such
detailed posts for me, but what I am trying to say is that I am doing
everything exactly as you suggest, following the best design patterns
(I think). There is a controller and it maintains the selection. The
view only draws the selection in its drawRect, and during drawRect it
queries the selection from the controller. The view can also cause the
selection to change in response to mouse input. That's when the view
performs hit testing of its items, in its mouseMoved handler. Then the
view asks the controller to change the current selection. Then the
controller calls back the view telling that selection has changed, and
the view calls setNeedsDisplay on itself.
The way the items are displayed, and therefore also how they are
hit-tested, is encapsulated inside the view. In other words, the
controller and the model don't have to know how the items appear and
how to hit test them in each kind of view. The view is responsible to
process the mouse events.
This all is pretty standard MVC pattern, I believe. No problem with
that.
The need to refresh the view after animation is rather a tiny
exception from the otherwise solid implementation. And I am
considering the fake mouse event because it seems the most natural and
straightforward way of changing the state of the system in this case.
Think of it this way: while the animation is in progress, the user may
be moving the mouse around, but all those events were ignored, and
what matters is only the last position of the mouse at the moment when
the animation ends. So in a sense, the fake mouse move event is not so
much fake, but a single consolidated mouse event of all those events.
The way my system should respond to this event is exactly the same as
to an ordinary, non-fake mouse event.
If I don't generate that fake event and use some explicit methods, I
would have to do the following:
1. in controller, determine which view the mouse currently is over
(hit test the window)
2. localize the coordinate to that view and ask the view to hit test
its items for the coordinate
3. set the controller's selection to that item
This all, and exactly this, would happen all by itself, by the already
existing code, in case if I sent the fake mouse moved event.
On Tue, Nov 3, 2009 at 3:09 PM, Graham Cox <email@hidden>
wrote:
On 03/11/2009, at 11:15 PM, Oleg Krupnov wrote:
Maybe I'm not speaking clearly, but that's exactly what I'm trying
to
do -- use a mouse event to cause a state change, but in this case
the
mouse event would be fake. Mouse position is in no way part of the
view's state. I just want that at particular moment the view's state
becomes synchronized with the current mouse position, as if the
mouse
did move.
But why? State is an independent variable. It is coupled to the mouse
location in your case, but it's independent nevertheless. If you
like, state
is the part of the view's output (because it causes the visual
appearance to
change) whereas events are always input. Don't conflate them -
views are
designed to keep the input (events) and outputs (drawing) handled
in two
completely independent phases.
There are multiple items in the view, and the hovered one of the
items
should be highlighted. When the mouse event arrives, the view
performs
hit testing of its items and determines which of the items is
hovered.
If I make a setState method as you suggest, I would have to specify
which item to highlight. This would break the view's encapsulation,
because I would have to perform item hit testing externally.
I don't really follow this. At some point any hit testing does have
to be
done externally, if you have multiple objects, either by you or by
AppKit.
Doing it yourself is easy, and I strongly recommend that approach as
follows:
1. main view gets the localised mouse location.
2. it iterates over all of its sub-objects representing the items
and asks
each one to test itself against the mouse position which is passed
to it.
3. The first item that returns yes is sent the appropriate -
setState: to
highlight it (and the main view also keeps track of this one as "the
selected item").
4. the -setState: method invalidates the drawing area covered by
the object.
5. drawing deals with the appearance change.
This is the classic, no-nonsense, normal everyday approach.
Tracking to get
each individual object to handle its own mouse tracking
independently is not
only going to be hard to get right, it's likely to have poor
performance as
well. All you're really doing is shunting the mouse position
detection and
hit-testing off onto multiple NSTrackingAreas - it still has to be
done and
it's unlikely to be faster than a simple approach that can take
advantage of
knowledge of how your sub-objects work.
(Side discussion: In fact, an improved design than the above where
state is
linked to the concept of selection is to maintain a separate list
of the
selected items. When the items are drawn, pass them the selected
state on
the fly by testing if they belong to the selection set or not. This
avoids
the maintenance of two state variables that need to be synchronised
- one to
mean 'this object is part of my selection' and another one within
the object
that indicates 'highlighted'. If you don't have to keep those two
in synch
they can never get out of synch, and management of the selection
becomes
independent of the objects that are selected themselves, which
shouldn't
really care, other than to draw differently accordingly).
Again, I could implement a custom "refresh" method in each view,
which
would query the current mouse position and call the mouse moved
event
handler, but in addition to the extra code, before calling this
method
I would have to hit test the window to determine which view is
currently hovered, i.e. basically repeat what NSApp and NSWindow
normally do when dispatching mouse events. That's why I thought
simulating a mouse move event could be justified.
This sounds backwards. Instead, just pass the mouse position down
to the
sub-object as needed, usually to perform a hit-test. (It's good for
each
object to implement its own hit-test method because you can
customise it for
different classes if necessary). The main view that contains all
these other
objects should act as a controller, responding to mouse events and
asking
the relevant objects to change state.
If you find this repugnant for some reason (not sure why, it's what
nearly
all drawing apps actually do), another option is to make each sub-
object a
NSView subclass in its own right, so that the normal hit-testing
and mouse
dispatch is done for you. But this approach *is* discouraged by the
docs for
performance reasons - it's better to keep your sub-objects
lightweight and
let the main view do the work of grovelling over them. But even
NSView has a
-hitTest: method.
Also, beside this discussion of design, I'd appreciate to find out
why
postEvent actually did not work, just to improve my understanding of
Cocoa, even if I decide against using this approach.
Well, I have successfully posted mouse-up events in the (rare)
situation I
described, but I filled in all the parameters, so maybe Dave's
suggestion is
right - you have to make sure the event is well-formed. Many of its
parameters are esoteric, but can be obtained from the objects
around you -
the window, current graphics context, and so on.
If I haven't made my point yet however, don't pursue this - just do
it
correctlyâ„¢ instead. The above approach is what I use in DrawKit and
it has
never let me down ;-)
--Graham
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden