none
Call method only when two properties changed

    Question

  • I want to call a method only when two properties changed. How can I accomplish that?

    For example

    private int x;
            public int X
            {
                get { return x; }
                set
                {
                    x = value;
                    RaisePropertyChanged(() => this.X);
    
                    currentVirtualCamera.X = X;
    //CALL THIS ONLY IF Y CHANGED AS WELL
                    orchestrator.SelectLayout(CurrentLayout);
                }
            }
    
            private int y;
            public int Y
            {
                get { return y; }
                set
                {
                    y = value;
                    RaisePropertyChanged(() => this.Y);
    
                    currentVirtualCamera.Y = Y;
    //CALL THIS ONLY IF X CHANGED AS WELL
                    orchestrator.SelectLayout(CurrentLayout);
                }
            }


    Take a look at WPF FlashMessage
    About.me

    Wednesday, July 17, 2013 2:45 PM

All replies

  • Hi,

    Change relative to what?

    Regardless, the first thing you'll need to do is create observables that represent each property.  For example:

    private int x;
    public int X
    {
    	get { return x; }
    	set
    	{
    		x = value;
    		...
    		xChanges.OnNext(x);
    	}
    }
    
    private int y;
    public int Y
    {
    	get { return y; }
    	set
    	{
    		y = value;
    		...
    		yChanges.OnNext(y);
    	}
    }
    
    private readonly Subject<int> xChanges = new Subject<int>();
    private readonly Subject<int> yChanges = new Subject<int>();

    Now you can define your query in terms of xChanges and yChanges.

    Since you haven't provided a complete specification, here are a few examples that check for changes in different ways:

    var xAndY = xChanges.CombineLatest(yChanges, (x, y) => Unit.Default);
    	-OR-
    var xAndY = xChanges.Zip(yChanges, (x, y) => Unit.Default);
    	-OR-
    var xAndY = xChanges.DistinctUntilChanged().CombineLatest(yChanges.DistinctUntilChanged(), (x, y) => Unit.Default);
    
    ...
    
    xAndY.Subscribe(_ => orchestrator.SelectLayout(CurrentLayout));

    CombineLatest generates a notification whenever either property changes, though not until both properties are assigned at least once.

    Zip generates a notification whenever both properties are assigned, in a pairwise fashion; i.e., each assignment to X is paired with an assignment to Y.  The assumption here is that callers will typically assign both X and together, though since they're separate properties they must be assigned one at a time, and you only want to generate a notification for the pair rather than for each individual assignment.

    DistinctUntilChanged drops consecutive notifications with the same value.  Applying this operator with CombineLatest defines a query that generates a notification whenever either property truly changes, though not until both properties are assigned at least once.

    You could also apply DistinctUntilChanged with Zip.

    There are many other possibilities as well.  I've just included a few common examples.

    - Dave


    http://davesexton.com/blog

    • Edited by Dave Sexton Wednesday, July 17, 2013 3:45 PM Formatting
    • Marked as answer by Joba Diniz Wednesday, July 17, 2013 4:03 PM
    • Unmarked as answer by Joba Diniz Wednesday, July 17, 2013 6:29 PM
    Wednesday, July 17, 2013 3:36 PM
  • Awesome!

    The Zip is what I was looking for.

    RxExtensions is so awesome, but I'm a newbie on it. I guess it's a different way of thinking.

    Well, thanks!


    Take a look at WPF FlashMessage
    About.me

    Wednesday, July 17, 2013 3:55 PM
  • Hi,

    I'd be remiss if I didn't also mention that you should consider defining a single property of type Point instead of having two individual X and Y properties.

    That way you'll force callers to specify both values simultaneously, and then you don't have to use a reactive query at all.  This also makes the contract part of your type rather than assuming that callers are always going to assign X and Y in a pairwise fashion.

    Keep in mind that using Zip means that if a caller assigns X1, X2, X3, then Y1, only a single notification will be generated for (X1, Y1).  Then, a subsequent assignment to Y2 will immediately generate a notification for (X2, Y2) without waiting for another X.  In other words, if callers don't adhere perfectly to your assumption, then your query may still result in a consecutive sequence of layout updates, even though it seems that was what you were trying to avoid.

    - Dave


    http://davesexton.com/blog

    • Edited by Dave Sexton Wednesday, July 17, 2013 4:39 PM Clarification
    Wednesday, July 17, 2013 4:39 PM
  • Ok, it seems that Zip did not resolve my problem. My problem is that I have a Joystick class that has X and Y properties, which are updated by timers, each one have its own:

    public class Joystick : Control
        {
     private Timer timerx;
            private Timer timery;
    
    private void TimerxElapsed(object sender, ElapsedEventArgs e)
            {
                if (Math.Abs(distance.X) < 15)
                    timerx.Interval = 80;
                else if (Math.Abs(distance.X) < 30)
                    timerx.Interval = 30;
                else if (Math.Abs(distance.X) < 40)
                    timerx.Interval = 15;
                else
                    timerx.Interval = 2;
                Dispatcher.BeginInvoke((Action)(() =>
                {
                    X += (distance.X > 0 ? 1 : (distance.X < 0 ? -1 : 0));
                }));
            }
    
            private void TimeryElapsed(object sender, ElapsedEventArgs e)
            {
                if (Math.Abs(distance.Y) < 15)
                    timery.Interval = 80;
                else if (Math.Abs(distance.Y) < 30)
                    timery.Interval = 30;
                else if (Math.Abs(distance.Y) < 40)
                    timery.Interval = 15;
                else
                    timery.Interval = 2;
                Dispatcher.BeginInvoke((Action)(() =>
                {
                    Y += (distance.Y > 0 ? 1 : (distance.Y < 0 ? -1 : 0));
                 }));
            }
    
    //More stuff..

    The Joystick X and Y properties are bound to the ViewModel I just showed in the first post. Before your answer using Zip, whenever the X changed, the layout was updated and whenever the Y changed, the layout was also updated. What I'm changing is in fact sort of a virtual camera, so by not changing the X and Y together, when you move the Joystick in diagonal, the virtual camera does not move in diagonal, it moves in X axis and then Y axis. Everything happen so fast, but it is noticeable that it is not moving in diagonal.

    So, that's what I'm trying to accomplish: move the virtual camera in diagonal as you move Joystick in diagonal.

    Now, as I see, even if I change the Joystick properties to Point, I don't see any improvements, because I would have to instanciate a new Point, and as I do so, the property in ViewModel would change (two way binding).


    Take a look at WPF FlashMessage
    About.me

    Wednesday, July 17, 2013 6:39 PM
  • Hi,

    Why are you using two separate timers?  I see that the distance of X affects the rate of its timer, and likewise for Y, but I don't understand why.  In other words, the resolution of the calculated joystick movement depends on where the cursor is located on the screen, right?  How is that useful to you?  Why not use a static resolution instead?

    Another way of looking at it:  X is useless without Y, and vice versa, so shouldn't they be updated together as Point?  That would not only give you a perfect diagonal, but also perfect movement in any direction.

    Sorry if I'm missing something here.  Maybe it's related to some kind of gameplay dynamic?

    To answer your original question based on your new example with 2 timers, I think it's important to first notice that you may get a sequence of mutations that looks like this:

    X  X  X  X  Y  X  X  X  X  Y

    or like this:

    Y  Y  Y  Y  X  Y  Y  Y  Y  X

    depending upon which axis currently has a faster rate.

    For the former you want to wait for each Y to perform updates.
    For the latter you want to wait for each X to perform updates.

    In other words, you want a query that generates notifications based on the least frequently updated value, which changes dynamically.

    I'm concerned that a query such as this isn't really the correct solution to whatever problem you're trying to solve by using 2 timers.  Would you care to elaborate?

    - Dave


    http://davesexton.com/blog

    Wednesday, July 17, 2013 7:35 PM
  • Hi,

    Why are you using two separate timers?  I see that the distance of X affects the rate of its timer, and likewise for Y, but I don't understand why.

    - Dave


    http://davesexton.com/blog

    The reason is the velocity. The Joystick has a Radius of 50.

    So, as the user drags the Josytick's thumb farther away, the velocity must increase, that is, the X or Y should be updated more frequently. Also, note that the X and Y must be update only by 1 each time.

    To picture the scenario, think if you want to move a rectangle on the screen. But to move it, you will not use the mouse, instead, you will use a Joystick.


    Take a look at WPF FlashMessage
    About.me

    Wednesday, July 17, 2013 8:11 PM
  • Hi,

    Thanks for clarifying.  I don't own a joystick anymore, but I understand what you mean now.  :)

    How about instead of changing the frequency of the timer and incrementing by 1, define an asynchronous UI loop (e.g., via async and await) that with each iteration increments by the current magnitude of the joystick (times some averaging factor perhaps, for more consistency across heterogeneous systems), synchronously updates the layout and then asynchronously awaits the dispatcher for the next iteration?

    That seems like it would be the most efficient implementation.  It avoids the extra costs of introducing concurrency of the timers and the complications of having to define a reactive query in order to avoid over-updating the UI.  In other words, by running the (async) loop on the UI thread, it will update as fast as possible (with respect to any limiting factors that you include) to give the smoothest possible visual experience without wasting system resources.

    Does that make sense?

    Regardless, here's the problem with the query concept:

    Perhaps it's not only about defining a query that generates notifications based on the least frequently updated value.  What if the joystick is perfectly horizontal?

    X X X ... X X Y

    Consider if X is at its fastest possible rate and Y is at its slowest possible rate.  Y's rate would dictate the layout rate of both X and Y, so then what's the point of having the timer on X?

    Or am I missing something else?

    - Dave


    http://davesexton.com/blog

    Wednesday, July 17, 2013 9:01 PM
  • Hi,

    Alternatively, does the joystick raise a "Changed" event?  If so, then you could use that instead of my async loop recommendation.

    Alternatively, does the joystick raise a "Changing" event?  If so, then you could use that instead of my async loop recommendation.

    Though I doubt it exists since the joystick would have to raise the event at an interval even while it's stationary.  I think the async loop is probably better anyway because it's tied to the capabilities of the UI, not some arbitrary device interval.

    - Dave


    http://davesexton.com/blog

    • Edited by Dave Sexton Wednesday, July 17, 2013 9:16 PM Clarification
    Wednesday, July 17, 2013 9:11 PM
  • We tried using one timer and change X and Y by factor greater than 1, however, the result was not satisfactory. The "rectangle" jumped to one location to another, it was not moving smoothly. That's why you need to move 1 by 1.

    My colleague suggested creating another timer that will fire an event with the 2 new values of X and Y. Maybe this works, but it's ugly enough.


    Take a look at WPF FlashMessage
    About.me


    • Edited by Joba Diniz Wednesday, July 17, 2013 9:29 PM fdsf
    Wednesday, July 17, 2013 9:26 PM
  • Hi,

    Well, my suggestion was to use 0 timers.  :)

    In other words, do everything on the UI thread, asynchronously.  I can provide a code example if you're not familiar with the technique.

    - Dave


    http://davesexton.com/blog

    Wednesday, July 17, 2013 11:13 PM
  • Hi,

    Here's an example of what I meant by running everything on the UI thread, asynchronously.  Note that async/await wasn't even necessary for this simple example.  The animation seems quite smooth to me, even though I'm only using the UI thread without any timers.  Furthermore, the example also uses the mouse to simulate a joystick on the UI thread (because I don't have an actual device), yet it doesn't seem to have any effect on the quality of the sprite animation.  Please let me know if your experience is different.

    Important:  Make sure to run this program without attaching the debugger; otherwise, the animation will be choppy.

    1. Create a new WPF Application project in Visual Studio 2012.  (I think it'll work in VS 2010 as well, but I haven't tested it.)
    2. Add a NuGet Package reference for Rx-WPF.
    3. Open MainWindow.xaml and paste the code below.
    4. Open MainWindow.xaml.cs and paste the code below.
    5. Run the application without attaching the debugger.
    6. Drag the white circle with your mouse to simulate joystick movement.  Watch the red sprite accelerate based on the angles of the joystick.
    7. Note: The mouse is captured while using the joystick and the cursor disappears; however, it's still bounded by the extent in which it can move on your screen.  If you move the application window near the bottom of the screen, then you may not be able to move the joystick down as far as it can go.  Therefore, you might not want to maximize the window.

    MainWindow.xaml

    <Window x:Class="WpfApplication1.MainWindow"
    				xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    				xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    				Title="MainWindow" Height="500" Width="700">
    
    	<Grid>
    		<Grid.RowDefinitions>
    			<RowDefinition />
    			<RowDefinition Height="Auto" />
    			<RowDefinition Height="Auto" />
    		</Grid.RowDefinitions>
    
    		<Border Grid.Row="0" BorderThickness="1" BorderBrush="Black" Margin="5">
    			<Canvas Name="spriteBox">
    				<Ellipse Name="sprite" Fill="Red" Width="60" Height="60" Canvas.Left="1" Canvas.Top="1" />
    			</Canvas>
    		</Border>
    
    		<Border Grid.Row="1" BorderBrush="DarkBlue" BorderThickness="5" Background="Gray"
    						HorizontalAlignment="Center" VerticalAlignment="Center">
    			<Canvas Name="joystickBox" Height="100" Width="100">
    				<Ellipse Fill="Black" Stroke="Blue" Width="50" Height="50" Canvas.Left="25" Canvas.Top="25" />
    				<Line Name="joystickShaft" X1="50" Y1="50" X2="50" Y2="50" Stroke="DarkGray" StrokeThickness="26" StrokeStartLineCap="Round" />
    				<Ellipse Name="joystick" Fill="White" Stroke="Blue" Width="26" Height="26" Canvas.Left="37" Canvas.Top="37" />
    			</Canvas>
    		</Border>
    
    		<TextBlock Grid.Row="2" TextAlignment="Center" Margin="5">
    			Drag the white dot to simulate joystick movement, which will accelerate the red sprite.
    		</TextBlock>
    
    	</Grid>
    
    </Window>

     

    MainWindow.xaml.cs

    using System;
    using System.Diagnostics;
    using System.Reactive.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Threading;
    
    namespace WpfApplication1
    {
    	public partial class MainWindow : Window
    	{
    		private Point currentJoystickOffset = default(Point);
    
    		public MainWindow()
    		{
    			InitializeComponent();
    
    			// Simulate a joystick that requires polling
    			GetJoystickOffsets().Subscribe(offset => currentJoystickOffset = offset);
    
    			// Poll the joystick and animate the sprite on the UI thread
    			AnimateSprite();
    		}
    
    		private void AnimateSprite()
    		{
    			// Poll the joystick (on the UI thread).
    			var offset = currentJoystickOffset;
    
    			// Synchronously move the sprite (on the UI thread).
    			UpdateLayout(offset);
    
    			// Temporarily yield to allow the UI thread to process other messages.
    			Dispatcher.InvokeAsync(AnimateSprite, DispatcherPriority.Background);
    		}
    
    		private void UpdateLayout(Point offset)
    		{
    			var x = Canvas.GetLeft(sprite);
    			var y = Canvas.GetTop(sprite);
    
    			const double slowFactor = .001d;
    
    			x += offset.X * slowFactor;
    			y += offset.Y * slowFactor;
    
    			Canvas.SetLeft(sprite, Math.Max(Math.Min(x, spriteBox.ActualWidth - sprite.Width), 0));
    			Canvas.SetTop(sprite, Math.Max(Math.Min(y, spriteBox.ActualHeight - sprite.Height), 0));
    		}
    
    		private IObservable<Point> GetJoystickOffsets()
    		{
    			var joystickTopLeft = new Point(Canvas.GetLeft(joystick), Canvas.GetTop(joystick));
    			var bounds = new Size(joystickBox.Width, joystickBox.Height);
    			var origin = new Point(bounds.Width / 2, bounds.Height / 2);
    			var extent = new Point(bounds.Width * 2.2, bounds.Height * 2.2);
    
    			var joystickGrabs = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
    				 eh => joystick.MouseDown += eh,
    				 eh => joystick.MouseDown -= eh);
    
    			var joystickReleases = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
    				eh => joystick.MouseUp += eh,
    				eh => joystick.MouseUp -= eh);
    
    			var joystickMouseMovements = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
    				eh => joystick.MouseMove += eh,
    				eh => joystick.MouseMove -= eh);
    
    			var joystickChanges =
    				(from _ in joystickGrabs.Do(_ =>
    					{
    						joystick.Cursor = Cursors.None;
    						joystick.CaptureMouse();
    					})
    				 from e in joystickMouseMovements
    				 let mouse = e.EventArgs.GetPosition(joystickBox)
    				 let offset = new Point(
    					 Math.Max(Math.Min(mouse.X, extent.X), -extent.X + joystick.Width) + -origin.X,
    					 Math.Max(Math.Min(mouse.Y, extent.Y), -extent.Y + joystick.Height) + -origin.Y)
    				 let x = joystickTopLeft.X + (offset.X / bounds.Width) * 20
    				 let y = joystickTopLeft.Y + (offset.Y / bounds.Height) * 20
    				 let location = new Point(x, y)
    				 select new
    				 {
    					 Offset = offset,
    					 Location = location
    				 })
    				.TakeUntil(joystickReleases)
    				.Concat(Observable.Return(new { Offset = default(Point), Location = default(Point) }))
    				.Finally(() =>
    					{
    						joystick.ReleaseMouseCapture();
    						joystick.Cursor = Cursors.Arrow;
    
    						Canvas.SetLeft(joystick, joystickTopLeft.X);
    						Canvas.SetTop(joystick, joystickTopLeft.Y);
    
    						joystickShaft.X2 = origin.X;
    						joystickShaft.Y2 = origin.Y;
    					})
    				.Repeat();
    
    			return joystickChanges
    				.Do(data =>
    					{
    						Debug.WriteLine(data);
    
    						Canvas.SetLeft(joystick, data.Location.X);
    						Canvas.SetTop(joystick, data.Location.Y);
    
    						joystickShaft.X2 = data.Location.X + joystick.Width / 2;
    						joystickShaft.Y2 = data.Location.Y + joystick.Height / 2;
    					})
    				.Select(data => data.Offset);
    		}
    	}
    }

    - Dave


    http://davesexton.com/blog

    • Marked as answer by Joba Diniz Thursday, July 18, 2013 4:11 PM
    • Unmarked as answer by Joba Diniz Friday, August 23, 2013 2:45 PM
    Thursday, July 18, 2013 3:59 AM
  • Hi,

    You'll probably notice that the CPU usage of my example program is relatively high, even when it's idle.  That's because it's continuously running the update loop as fast as possible.  This gives the smoothest animation possible because it uses the highest possible frame rate and adjusts it dynamically based on real-time UI latency.  Theoretically, you could reduce CPU usage to 0 while the sprite is idle by stopping the loop when the joystick is not in use; however, CPU usage will still remain high while using the joystick.

    Alternatively, the frame rate can be controlled to greatly reduce CPU usage and still produce a smooth-enough animation.  Furthermore, it will help to ensure consistency across heterogeneous systems.  It can be done simply by using a single DispatcherTimer instead of recursion, and instead of other timers that introduce unnecessary concurrency.  The interval of course depends upon how much work the application does during each update.  The longer the updates, the longer the interval must be to accommodate them, thus you'll have a slower frame rate.  My example program is very simple, so I can use a very small timer interval to get a very high frame rate.

    The following example replaces MainWindow.xaml.cs in my previous post, though I've omitted GetJoystickOffsets for brevity since it hasn't changed.

    I've set the frame rate to 10ms, which greatly reduces CPU usage, yet the animation still appears to be smooth.

    Important:  Run the program without attaching the debugger; otherwise, the animation will be choppy.

    using System;
    using System.Diagnostics;
    using System.Reactive.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Threading;
    
    namespace WpfApplication1
    {
    	public partial class MainWindow : Window
    	{
    		private Point currentJoystickOffset = default(Point);
    
    		public MainWindow()
    		{
    			InitializeComponent();
    
    			// Simulate a joystick that requires polling
    			GetJoystickOffsets().Subscribe(offset => currentJoystickOffset = offset);
    
    			// Poll the joystick and animate the sprite on the UI thread
    			var timer = new DispatcherTimer(DispatcherPriority.Background, Dispatcher);
    			timer.Interval = TimeSpan.FromMilliseconds(10);
    			timer.Tick += (_, __) => AnimateSprite();
    			timer.Start();
    		}
    
    		private void AnimateSprite()
    		{
    			// Poll the joystick (on the UI thread).
    			var offset = currentJoystickOffset;
    
    			// Synchronously move the sprite (on the UI thread).
    			UpdateLayout(offset);
    		}
    
    		private void UpdateLayout(Point offset)
    		{
    			var x = Canvas.GetLeft(sprite);
    			var y = Canvas.GetTop(sprite);
    
    			const double slowFactor = .05d;
    
    			x += offset.X * slowFactor;
    			y += offset.Y * slowFactor;
    
    			Canvas.SetLeft(sprite, Math.Max(Math.Min(x, spriteBox.ActualWidth - sprite.Width), 0));
    			Canvas.SetTop(sprite, Math.Max(Math.Min(y, spriteBox.ActualHeight - sprite.Height), 0));
    		}
    	}
    }

    - Dave


    http://davesexton.com/blog

    Thursday, July 18, 2013 4:33 AM
  • Thanks man.

    Awesome examples!


    Take a look at WPF FlashMessage
    About.me

    Thursday, July 18, 2013 4:11 PM
  • Hi, I still need some more help. I'm back on this issue. Unfortunately, I can't share the solution/project, it would be much easier to explain as you could see it in action. So I'll share some code and some prints.
    The problem is that I can't use the DispatherTime on the UI thread. I need to use the Timer class, because I'm not actually moving another Control in the UI with the Joystick as your example does. What my application does with the value of the X and Y is up to the developer. In this particular case, we are using the X and Y in the ViewModel, which pass them to Orchestrator class, which pass them to MixerAdapter class. So, ultimately, X and Y will be used in the following code:

    public void ConfigureScenes(IEnumerable<MixerScene> scenes)
            {
               //ommited...
                foreach (var scene in scenes.OrderByDescending(s => s.Target))
                {
                    //ommited...                
                    var sx = scene.Source.PercentageLeft + scene.VirtualCamera.X / 100;
                    var sy = scene.Source.PercentageTop + scene.VirtualCamera.Y / 100;
                    //ommited...
                }
            }

    You see the scene.VirtualCamera.X and scene.VirtualCamera.Y? That's the values of the Joystick.
    Here is Joystick:

    public class Joystick : Control
        {
            private Thumb thumb;
            private const double Radius = 50;
            private Point center;
            private Timer timer;
            private Vector currentDistance;
            private double slowFactor = 0.01d;
    
            public Joystick()
            {
                currentDistance = new Vector();
                timer = new Timer(10);
                //timer.Interval = TimeSpan.FromMilliseconds(10);
                //timer.Tick += Tick;
                timer.Elapsed += Elapsed;
            }
        
            static Joystick()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(Joystick), new FrameworkPropertyMetadata(typeof(Joystick)));
            }
    
            public static readonly DependencyProperty PositionProperty =
               DependencyProperty.Register("Position", typeof(Point), typeof(Joystick), new PropertyMetadata());
    
            public Point Position
            {
                get { return (Point)GetValue(PositionProperty); }
                set { SetValue(PositionProperty, value); }
            }
    
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    
                thumb = GetTemplateChild("PART_thumb") as Thumb;
                if (thumb != null)
                    Initialize();
            }
    
            private void Initialize()
            {
                var initialLeft = Canvas.GetLeft(thumb);
                var initialTop = Canvas.GetTop(thumb);
                center = new Point(initialLeft, initialTop);
    
                GetJoystickOffsets().Subscribe(distance => currentDistance = distance);
    
                //var timer = new DispatcherTimer(DispatcherPriority.Background, Dispatcher);
                //timer.Interval = TimeSpan.FromMilliseconds(10);
                //timer.Tick += Tick;
                //timer.Start();
            }
    
            private void Elapsed(object sender, ElapsedEventArgs e)
            {
                Debug.WriteLine("X = " + currentDistance.X * slowFactor + " Y = " + currentDistance.Y * slowFactor);
                //Position = new Point(currentDistance.X * slowFactor, currentDistance.Y * slowFactor);
                Dispatcher.BeginInvoke((Action)(() =>
                {
                    Position = new Point(currentDistance.X * slowFactor, currentDistance.Y * slowFactor);
                }));
            }
    
            private IObservable<Vector> GetJoystickOffsets()
            {
                var joystickGrabs = Observable.FromEventPattern<DragStartedEventHandler, DragStartedEventArgs>(
                     eh => thumb.DragStarted += eh,
                     eh => thumb.DragStarted -= eh);
    
                var joystickReleases = Observable.FromEventPattern<DragCompletedEventHandler, DragCompletedEventArgs>(
                    eh => thumb.DragCompleted += eh,
                    eh => thumb.DragCompleted -= eh);
    
                var joystickMouseMovements = Observable.FromEventPattern<DragDeltaEventHandler, DragDeltaEventArgs>(
                    eh => thumb.DragDelta += eh,
                    eh => thumb.DragDelta -= eh);
    
                var joystickChanges =
                    (
                     from _ in joystickGrabs.Do(_ =>
                        {
                            timer.Start();
                        })
                     from e in joystickMouseMovements
                     let newLeft = Canvas.GetLeft(thumb) + e.EventArgs.HorizontalChange
                     let newTop = Canvas.GetTop(thumb) + e.EventArgs.VerticalChange
                     let newPoint = new Point(newLeft, newTop)
                     let location = IsInsideBoundaries(newPoint) ? newPoint : GetNearestPointOnCircle(newPoint)
                     let distance = newPoint - center
                     select new
                     {
                         Distance = distance,
                         Location = location
                     })
                    .TakeUntil(joystickReleases)
                    .Concat(Observable.Return(new { Distance = default(Vector), Location = default(Point) }))
                    .Finally(() =>
                    {
                        MoveTo(center);
                        timer.Stop();
                    })
                    .Repeat();
    
                return joystickChanges
                    .Do(data =>
                    {
                        MoveTo(data.Location);
                    })
                    .Select(data => data.Distance);
            }
    
            private void MoveTo(Point point)
            {
                Canvas.SetLeft(thumb, point.X);
                Canvas.SetTop(thumb, point.Y);
            }
    
            private bool IsInsideBoundaries(Point point)
            {
                var powX = Math.Pow(point.X - center.X, 2);
                var powY = Math.Pow(point.Y - center.Y, 2);
                var powRadius = Math.Pow(Radius, 2);
    
                return powX + powY < powRadius;
            }
    
            private Point GetNearestPointOnCircle(Point point)
            {
                //V = (P - C)
                //PontoMaisPerto = C + V / |V| * R;
                //onde P é o ponto, C é o centro, R é o raio, e |V| é o tamanho de V.
    
                var vPoint = point - center;
                var distance = Math.Sqrt(Math.Pow(vPoint.X, 2) + Math.Pow(vPoint.Y, 2));
                var nearestX = center.X + vPoint.X / distance * Radius;
                var nearestY = center.Y + vPoint.Y / distance * Radius;
    
                return new Point(nearestX, nearestY);
            }
        }

    You probably  can't notice that my Joystick does not have a rectangular box (which would be the bounds). What I did was to set a Radius of 50, and on top of that calculate the bounds of the Thumb. So, I'm giving you a picture of it:

    With the current code I'm showing here, the UI freezes forever when I try to move the Thumb.

    Please, if you could help.


    Take a look at WPF FlashMessage
    About.me


    • Edited by Joba Diniz Tuesday, August 20, 2013 9:01 PM
    Tuesday, August 20, 2013 9:00 PM