How to Debounce/Throttle Property changed using Rx - c#

I have been following a tutorial here and they are using IEnumerable<int> instead of using a collection how can I use a single values raised multiple times in milliseconds?
So when the user drags the slider the event gets raised many times a seconds how can I reduce the amount of command.
e.g The position value starts at 0.0 the user drags to 3000.56 in 100 milliseconds. How can I only print 0, then 3000.56, and ignore all in between. within the same motion if the user drag back to 2000.89 it should print 2000.89?
multiple controls raises this event so it should group by the Unique ID.
XMAL:
<Grid>
<TextBox x:Name="txtbilly" Text="{Binding Position, Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,382" ></TextBox>
<TextBox x:Name="txtbob" Text="{Binding Position, Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}" Margin="0,162,0,233" ></TextBox>
<Slider x:Name="slbilly" Value="{Binding Position, Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}" Margin="0,57,0,306" MouseDown="slbilly_MouseDown" MouseUp="slbilly_MouseUp"/>
<Slider x:Name="slbob" Value="{Binding Position, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,206,0,171" MouseDown="slbob_MouseDown" MouseUp="slbob_MouseUp"/>
</Grid>
Model:
public class Student : INotifyPropertyChanged
{
private string _ID;
public string ID
{
get
{
return _ID;
}
set
{
_ID = value;
OnPropertyChanged(new PropertyChangedEventArgs("ID"));
}
}
private float _Position;
public float Position
{
get
{
return _Position;
}
set
{
_Position = value;
OnPropertyChanged(new PropertyChangedEventArgs("Position"));
}
}
public Student(string id, float position)
{
this.Position = position;
this.ID = id;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Code Behind:
Student billy = new Student("Unique1", 0.0f);
Student Bob = new Student("Unique2", 0.0f);
public MainWindow()
{
InitializeComponent();
txtbilly.DataContext = billy;
slbilly.DataContext = billy;
billy.PropertyChanged += Students_PropertyChanged;
txtbob.DataContext = Bob;
slbob.DataContext = Bob;
Bob.PropertyChanged += Students_PropertyChanged;
}
private void Students_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// All values handles here
var anyStudent = (Student)sender;
Debug.Print(anyStudent.ID + " " + anyStudent.Position);
var observable = anyStudent.Position.ToObservable().Timestamp();
var throttled = observable.Throttle(TimeSpan.FromMilliseconds(100));
using (throttled.Subscribe(x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
}
private void slbob_MouseDown(object sender, MouseButtonEventArgs e)
{
//Start trottle
}
private void slbob_MouseUp(object sender, MouseButtonEventArgs e)
{
//stop trottle
}
private void slbilly_MouseDown(object sender, MouseButtonEventArgs e)
{
//Start trottle
}
private void slbilly_MouseUp(object sender, MouseButtonEventArgs e)
{
//stop trottle
}
Error:
Error CS1061 'float' does not contain a definition for 'ToObservable'
and no accessible extension method 'ToObservable' accepting a first
argument of type 'float' could be found (are you missing a using
directive or an assembly reference?)

Related

WPF volume dial code tutorial code has issues in my code but none in the tutorial code

Hey all I need some help with getting the code below working. I was following this tutorial and most things work but there are a few things in the code that seem not to work for some reason. All this code looks like the code in the tutorial so I'm unsure of what's going on?
User control xaml code:
<UserControl x:Class="carProg.dial"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:carProgForKids"
mc:Ignorable="d" Height="450" Width="450">
<Grid Manipulation.ManipulationMode="All" ManipulationDelta="Grid_ManipulationDelta">
<Ellipse Stroke="Black" Margin="30" Fill="#FFFFE15D"/>
<Grid>
<Grid.RenderTransform>
<RotateTransform CenterX="225" CenterY="225" Angle="{Binding Angle}" />
</Grid.RenderTransform>
<Ellipse Fill="#FFFFFDF3"
Height="100" Stroke="Black" Margin="0"
VerticalAlignment="Top" Width="100"/>
</Grid>
</Grid>
</UserControl>
Code behind:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace carProg
{
public partial class dial : UserControl, INotifyPropertyChanged
{
public dial()
{
InitializeComponent();
this.DataContext = this;
}
private void Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
this.Angle = GetAngle(e.Position, this.RenderSize);
this.Amount = (int)(this.Angle / 360 * 100);
}
int m_Amount = default(int);
public int Amount {
get { return m_Amount; }
set { SetProperty(ref m_Amount, value);
} }
double m_Angle = default(double);
public double Angle {
get { return m_Angle; }
set { SetProperty(ref m_Angle, value);
} }
public event PropertyChangedEventHandler PropertyChanged;
void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName]);
public enum Quadrants : int { nw = 2, ne = 1, sw = 4, se = 3 }
private double GetAngle(Point touchPoint, Size circleSize)
{
var _X = touchPoint.X - (circleSize.Width / 2d);
var _Y = circleSize.Height - touchPoint.Y - (circleSize.Height / 2d);
var _Hypot = Math.Sqrt(_X * _X + _Y * _Y);
var _Value = Math.Asin(_Y / _Hypot) * 180 / Math.PI;
var _Quadrant = (_X >= 0) ?
(_Y >= 0) ? Quadrants.ne : Quadrants.se :
(_Y >= 0) ? Quadrants.nw : Quadrants.sw;
switch (_Quadrant)
{
case Quadrants.ne: _Value = 090 - _Value; break;
case Quadrants.nw: _Value = 270 + _Value; break;
case Quadrants.se: _Value = 090 - _Value; break;
case Quadrants.sw: _Value = 270 + _Value; break;
}
return _Value;
}
}
}
Currently the issues are:
e.Position
Error:
Error CS1061 'ManipulationDeltaEventArgs' does not contain a
definition for 'Position' and no accessible extension method
'Position' accepting a first argument of type
'ManipulationDeltaEventArgs' could be found (are you missing a using
directive or an assembly reference?)
SetProperty
Error:
Error CS7036 There is no argument given that corresponds to the
required formal parameter '' of 'dial.SetProperty(ref T, T, ?)'
CallerMemberName
Error:
Error CS1061 'ManipulationDeltaEventArgs' does not contain a
definition for 'Position' and no accessible extension method
'Position' accepting a first argument of type
'ManipulationDeltaEventArgs' could be found (are you missing a using
directive or an assembly reference?)
And on the xaml user control:
<Grid Manipulation.ManipulationMode="All"
Error:
Error Manipulation is not active on the specified element.
Not sure why I am getting these errors since it's the same code the video tutorial is going off of and it has no errors showing like mine does..
UPDATE #1
UPDATE #2
The code should produce a dial:
UPDATE #3: Let mouse act as touch device
Adding the MouseTouchDevice.cs class in BlakeNUI:
public class MouseTouchDevice : TouchDevice, ITouchDevice
{
//Class Members
private static MouseTouchDevice device;
public Point Position { get; set; }
//Public Static Methods
public static void RegisterEvents(FrameworkElement root)
{
root.PreviewMouseDown += MouseDown;
root.PreviewMouseMove += MouseMove;
root.PreviewMouseUp += MouseUp;
root.LostMouseCapture += LostMouseCapture;
root.MouseLeave += MouseLeave;
}
//Private Static Methods
private static void MouseDown(object sender, MouseButtonEventArgs e)
{
if (device != null &&
device.IsActive)
{
device.ReportUp();
device.Deactivate();
device = null;
}
device = new MouseTouchDevice(e.MouseDevice.GetHashCode());
device.SetActiveSource(e.MouseDevice.ActiveSource);
device.Position = e.GetPosition(null);
device.Activate();
device.ReportDown();
}
private static void MouseMove(object sender, MouseEventArgs e)
{
if (device != null &&
device.IsActive)
{
device.Position = e.GetPosition(null);
device.ReportMove();
}
}
private static void MouseUp(object sender, MouseButtonEventArgs e)
{
LostMouseCapture(sender, e);
}
static void LostMouseCapture(object sender, MouseEventArgs e)
{
if (device != null &&
device.IsActive)
{
device.Position = e.GetPosition(null);
device.ReportUp();
device.Deactivate();
device = null;
}
}
static void MouseLeave(object sender, MouseEventArgs e)
{
LostMouseCapture(sender, e);
}
//Constructors
public MouseTouchDevice(int deviceId) :
base(deviceId)
{
Position = new Point();
}
//Overridden methods
public override TouchPointCollection GetIntermediateTouchPoints(IInputElement relativeTo)
{
return new TouchPointCollection();
}
public override TouchPoint GetTouchPoint(IInputElement relativeTo)
{
Point point = Position;
}
to my project and placing this:
public watchingMovie(String movie)
{
InitializeComponent();
MouseTouchDevice.RegisterEvents(this);
...
}
Allows it to function as if you were working with it on a touch device.
Currently the issues are:
Error CS1061 'ManipulationDeltaEventArgs' does not contain a definition for 'Position' and no accessible extension method 'Position' accepting a first argument of type 'ManipulationDeltaEventArgs' could be found (are you missing a using directive or an assembly reference?)
This error is because it doesn't exist, you can replace .Position with ManipulationOrigin which gets the point from which the manipulation originated and returns a System.Windows.Point:
this.Angle = GetAngle(e.ManipulationOrigin, this.RenderSize);
Error CS7036 There is no argument given that corresponds to the required formal parameter '' of 'dial.SetProperty(ref T, T, ?)'
Please see example implementation below:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
storage = value;
OnPropertyChanged(propName);
return true;
}
Error Manipulation is not active on the specified element.
To fix this issue set IsManipulationEnabled="True" on the grid iteself which means manipulation is enabled for the UIElement
All of this wrapped up:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace carProg
{
/// <summary>
/// Interaction logic for dial.xaml
/// </summary>
public partial class dial : UserControl, INotifyPropertyChanged
{
public dial()
{
InitializeComponent();
this.DataContext = this;
}
private void Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
this.Angle = GetAngle(e.ManipulationOrigin, this.RenderSize);
this.Amount = (int)(this.Angle / 360 * 100);
}
int m_Amount = default(int);
public int Amount
{
get { return m_Amount; }
set
{
SetProperty(ref m_Amount, value);
}
}
double m_Angle = default(double);
public double Angle
{
get { return m_Angle; }
set
{
SetProperty(ref m_Angle, value);
}
}
// Please note the changes with OnPropertyChanged and SetProperty
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
storage = value;
OnPropertyChanged(propName);
return true;
}
public enum Quadrants : int { nw = 2, ne = 1, sw = 4, se = 3 }
private double GetAngle(Point touchPoint, Size circleSize)
{
var _X = touchPoint.X - (circleSize.Width / 2d);
var _Y = circleSize.Height - touchPoint.Y - (circleSize.Height / 2d);
var _Hypot = Math.Sqrt(_X * _X + _Y * _Y);
var _Value = Math.Asin(_Y / _Hypot) * 180 / Math.PI;
var _Quadrant = (_X >= 0) ?
(_Y >= 0) ? Quadrants.ne : Quadrants.se :
(_Y >= 0) ? Quadrants.nw : Quadrants.sw;
switch (_Quadrant)
{
case Quadrants.ne: _Value = 090 - _Value; break;
case Quadrants.nw: _Value = 270 + _Value; break;
case Quadrants.se: _Value = 090 - _Value; break;
case Quadrants.sw: _Value = 270 + _Value; break;
}
return _Value;
}
}
}
// Please note: IsManipulationEnabled on the grid
<Grid IsManipulationEnabled="True" ManipulationDelta="Grid_ManipulationDelta">
<Ellipse Margin="30"
Fill="#FFFFE15D"
Stroke="Black"
/>
<Grid>
<Grid.RenderTransform>
<RotateTransform Angle="{Binding Angle}" CenterX="225" CenterY="225" />
</Grid.RenderTransform>
<Ellipse Width="100"
Height="100"
Margin="0"
VerticalAlignment="Top"
Fill="#FFFFFDF3"
Stroke="Black"
/>
</Grid>
</Grid>
Try to replace e.Position with e.ManipulationOrigin in the event handler and set the IsManipulationEnabled property of the Grid to true in the XAML markup:
<Grid IsManipulationEnabled="True" ...
You also need to actually implement the SetProperty<T> method.

Binding ObservableCollection<class> fields to ListBox DataTemplate

I am a student that just finished up a summer internship, and I brought home a project to work on briefly before school starts up. This project has a stopwatch in it, and I would rather use an ObservableCollection bound to my ListBox for my split times, rather that using the listbox.Items.Add(). When I add to the ObservableCollection, the ListBox UI does not update. Could anyone point me in the right direction on what I missed or what I did wrong?
I have my TimeSplits class:
public class TimeSplits : INotifyPropertyChanged
{
private int _hours;
private int _minutes;
private int _seconds;
public int hours
{
get
{
return _hours;
}
set
{
_hours = value;
NotifyPropertyChanged(hours);
}
}
public int minutes
{
get
{
return _minutes;
}
set
{
_minutes = value;
NotifyPropertyChanged(minutes);
}
}
public int seconds
{
get
{
return _seconds;
}
set
{
_seconds = value;
NotifyPropertyChanged(seconds);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(int propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameof(propertyName)));
}
}
public override string ToString()
{
return hours.ToString() + ":" + minutes.ToString() + ":" + seconds.ToString();
}
}
and my ObservableCollection in my Page:
public partial class StopwatchPage : Page , INotifyPropertyChanged
{
...
public ObservableCollection<TimeSplits> splits = new ObservableCollection<TimeSplits>();
...
public StopwatchPage()
{
DataContext = this;
InitializeComponent();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(stopwatchTimer);
}
...
private void splitButton_Click(object sender, RoutedEventArgs e)
{
TimeSplits split = new TimeSplits();
split.hours = Hours;
split.minutes = Minutes;
split.seconds = Seconds;
splits.Add(split);
}
...
}
and my xaml:
<ListBox x:Name="newSplitListBox" HorizontalAlignment="Left" Margin="139,0,0,47" Width="185" Height="268" VerticalAlignment="Bottom" ItemsSource="{Binding splits}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding hours}"/>
<TextBlock Text="{Binding minutes}"/>
<TextBlock Text="{Binding seconds}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am sure it is something small that I have no clue about, as I just started learning data binding this summer. Any help is greatly appreciated! Thanks in advance.
It looks like you have nameof() in the wrong place. The way your current code reads, it will always send the value of "propertyName" as the name of the property that changed, regardless of what property actually changed.
Try this:
public int hours
{
get
{
return _hours;
}
set
{
_hours = value;
NotifyPropertyChanged();
}
}
Then, in your NotifyPropertyChanged(), do this:
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
Edit: Added fix for the following:
Also, the ObservableCollection needs to be a property. Change this code:
public ObservableCollection<TimeSplits> splits = new ObservableCollection<TimeSplits>();
To this:
public ObservableCollection<TimeSplits> Splits { get; set; } = new ObservableCollection<TimeSplits>();
I learned a trick from Xamarin's ViewModel template that helped me immensely. Here is the code that it generates that handles an observable View Model (much like the ObservableCollection).
protected bool SetProperty<T>(ref T backingStore, T value,
Action onChanged = null,
[CallerMemberName]string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
Then, to use this, simply add this to your properties:
private string _title = string.Empty;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}

MediaElement.MediaEnded() not firing from ViewModel?

I have two MediaElements created in my ViewModel and bound in my View. On MediaOne.MediaEnded(), I'm trying to fire another method. BUT, MediaEnded() never fires!
Here is the relevant code:
ViewModel:
private MediaElement _mediaOne;
public MediaElement MediaOne
{
get { return _mediaOne; }
set { _mediaOne = value; NotifyPropertyChanged(); }
}
private MediaElement _mediaTwo;
public MediaElement MediaTwo
{
get { return _mediaTwo; }
set { _mediaTwo = value; NotifyPropertyChanged(); }
}
public SpeechViewModel()
{
_mediaOne = new MediaElement();
_mediaTwo = new MediaElement();
MediaOne.MediaEnded += MediaOne_MediaEnded;
MediaTwo.MediaEnded += MediaTwo_MediaEnded;
}
private async void MediaOne_MediaEnded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
// Do stuff
}
private async void MediaTwo_MediaEnded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
// Do other stuff
}
XAML
<ContentControl Content="{Binding mediaOne}"/>
<ContentControl Content="{Binding mediaTwo}"/>

Binding a textBlock text to a value in a custom class xaml c#

when i navigate to the new page where it should display the text, it appears empty
The Xaml code i have
xmlns:vm="using:Estimation"
<Page.DataContext>
<vm:PlayerClass/>
</Page.DataContext>
this is the textBlock im trying to bind the data too.
<TextBlock x:Name="PlayerOne"
Text="{Binding PlayerOneName}"
/>
The Class im binding is as follows
public class PlayerClass :INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
private string name;
public string PlayerOneName { get { return this.name; }
set { this.name = value ;
NotifyPropertChanged(PlayerOneName); } }
}}
and the class im changing the content in the text box is
private void StartButton_Click(object sender, RoutedEventArgs e)
{
if (PlayerOneTextBox.Text == EnterNameText ||
PlayerTwoTextBox.Text == EnterNameText ||
PlayerThreeTextBox.Text == EnterNameText ||
PlayerFourTextBox.Text == EnterNameText)
{
MessageDialog msgBox = new MessageDialog("Please Enter All Names Before Continuing");
msgBox.ShowAsync();
}
else
{
// playerNames.PropertyChanged += new DependencyPropertyChangedEventHandler(playerNames_PropertyChanged);
this.DataContex.PlayerOneName = PlayerOneTextBox.Text;
MessageDialog msgBox = new MessageDialog(playerNames.PlayerOneName);
msgBox.ShowAsync();
playerNames.PlayerTwoName = PlayerTwoTextBox.Text;
playerNames.PlayerThreeName = PlayerTwoTextBox.Text;
playerNames.PlayerFourName = PlayerFourTextBox.Text;
Frame.Navigate(typeof(NewRoundPage));
}
}
In the constructor set the name
public PlayerClass ()
{
PlayerOneName = "Jabba De Hutt";
}
Also set a fallback value to provide an indicator of a failed binding situation:
Text="{Binding PlayerOneName, FallBack=Unknown}"
The navigate should not change the datacontext of the textbox, change the viewmodel instead
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var PlayerNames = e.Parameter as PlayerClass;
this.DataContext.PlayerOneName = PlayerNames.PlayerOneName;
}

Slider - snap to point and dragging with events

I need to trigger an event when the thumb of a slider is either dragged to a new value, or clicked to a new value with snap to point. I only want this event to happen when the value is changed is these two manners, using the mouse, so a ValueChanged event won't work.
you can try this litte trick
first you need to set UpdateSourceTrigger to Explicit
<Slider Minimum="0"
Thumb.DragStarted="Slider_DragStarted"
Thumb.DragCompleted="Slider_DragCompleted"
Maximum="{Binding YourMaxBinding, Mode=OneWay}"
Value="{Binding CurrentPosition, Mode=TwoWay, UpdateSourceTrigger=Explicit}" />
code behind
private void Slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e) {
// try to prevent updating slider position from your view model
yourViewModel.DontUpdateSliderPosition = true;
}
private void Slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e) {
BindingExpression be = ((Slider)sender).GetBindingExpression(RangeBase.ValueProperty);
if (be != null) {
be.UpdateSource();
}
yourViewModel.DontUpdateSliderPosition = false;
}
code at your view model
private bool _dontUpdateSliderPosition;
public bool DontUpdateSliderPosition {
get {
return _dontUpdateSliderPosition;
}
set {
if (Equals(value, _dontUpdateSliderPosition)) {
return;
}
_dontUpdateSliderPosition = value;
yourPropertyChangedFunc("DontUpdateSliderPosition");
}
}
private int _currentPosition;
public int CurrentPosition {
get {
return _currentPosition;
}
set {
if (Equals(value, _currentPosition)) {
return;
}
_currentPosition = value;
yourPropertyChangedFunc("CurrentPosition");
}
}
private CodeBehindFuncToChangeTheSliderPosition(){
if (!DontUpdateSliderPosition) {
CurrentPosition = theNewPosition;
}
}
hope this helps

Categories

Resources