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
Related
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?)
I have an Image element that's bound to an ImageSource element inside a class that I've created. The ImageSource gets updated every time a slider is changed. When I first instantiate my window, the ImageSource is blank until the user loads a file. Once the file is loaded, the image appears and the user can scroll the slider and see the image change. They can then select "OK" on the dialog to save this pattern. This all works fine.
However, if they double-click on the item in the ListView then it will re-open this dialog to make further edits. So, it creates a new dialog and then reloads the pertinent info about the image. However, for whatever reason... the image binding no longer works. I can put a breakpoint on the ImageSource getter and everytime I change the slider, the image does get updated... However, it just doesn't appear the be binding correctly. Why would it bind correctly on the first time the window is opened, but not on subsequent openings. I'll try to lay out my code.
In my .XAML code:
<UserControl x:Class="MyControls.CreatePattern"
x:Name="PatternCreation"
...
d:DesignHeight="160" d:DesignWidth="350">
<Slider Value="{Binding ElementName=PatternCreation, Path=Pattern.ZNorm, Mode=TwoWay}" Maximum="1" Name="Slider" VerticalAlignment="Stretch" />
<Image Name="PatternPreview" Source="{Binding ElementName=PatternCreation, Path=Pattern.WPFSlice}" Stretch="Uniform"></Image>
</UserControl
In my code behind I define the Pattern to be bound:
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; }
}
In my PatternVoxelBased class, I have a WPFSlice and ZNorm properties defined like this:
protected ImageSource mWPFSlice;
public ImageSource WPFSlice
{
get { return mWPFSlice; }
set
{
mWPFSlice = value;
NotifyPropertyChanged("WPFSlice");
}
}
protected double mZNorm = 0.5;
public double ZNorm
{
get { return mZNorm; }
set
{
if (mZNorm == value) return;
mZNorm = value;
NotifyPropertyChanged("ZNorm");
WPFSlice = BuildImageAtZ(mZNorm);
}
}
I have an event to load the dialog window the first time:
private void CreatePattern_Click(object sender, RoutedEventArgs e)
{
CCreateVoxelPattern dlg = new CCreateVoxelPattern();
dlg.DataContext = DataContext;
dlg.CShow(PatternLibraryMenu);
}
My ListView Double-Click function to reload the dialog window:
private void ListViewPatternLibrary_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
PatternVoxelBased item = ((ListView)sender).SelectedValue as PatternVoxelBased;
CCreateVoxelPattern dlg = new CCreateVoxelPattern();
dlg.DataContext = DataContext;
dlg.Main.Pattern = item;
dlg.Main.LoadPattern();
dlg.CShow(PatternLibraryMenu);
}
public void LoadPattern()
{
if (Pattern == null) return;
Pattern.WPFSlice = Pattern.BuildImageAtZ(Pattern.ZNorm);
}
In your class where this is
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; }
}
you have to implement INotifyPropertyChanged.
Example
public class YourClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; OnPropertyChanged(new PropertyChangedEventArgs("Pattern"));}
}
}
EDIT
In your Pattern-class, you have to implement that too on every Property.
ViewModel:
public FloatingToolbarWindowViewModel(GuiItems guiItems)
{
GuiItemsInstance = guiItems;
GuiItemsInstance.Host = Host;
GuiItemsInstance.RefreshVisibility = RefreshVisibility;
}
private Visibility _windowVisibility;
public Visibility WindowVisibility
{
get { return _windowVisibility; }
set
{
//raises PropertyChanged-event
SetValue(ref _windowVisibility, value);
}
}
// this check if any of the toolbars should be in a window and then sets visibility
public void RefreshVisibility(int RoleId)
{
if (GuiItemsInstance.ToolbarItems.Any(i => i.ToolbarLocation == ToolbarLocation.Float && i.RoleId == RoleId))
WindowVisibility = Visibility.Visible;
else
WindowVisibility = Visibility.Hidden;
}
XAML:
Visibility="{Binding WindowVisibility, Mode=TwoWay}"
This means it can never work because in the end the ShowWindow updates the property to Visible even though the initialization would have "decided" it should be Hidden.
So what I do is a hack in code behind file:
public partial class FloatingToolbarWindow : Window
{
public FloatingToolbarWindow()
{
InitializeComponent();
ContentRendered += FloatingToolbarWindow_ContentRendered;
}
private void FloatingToolbarWindow_ContentRendered(object sender, EventArgs e)
{
((FloatingToolbarWindowViewModel)DataContext).RefreshWindowVisibility();
}
ViewModel extra Hack-method:
public void RefreshVisibility()
{
RefreshVisibility(GuiItemsInstance.ActiveRoleId);
}
Is there a way to do this without this terrible hack. Moreover shouldn't this work with Mode=OneWay binding in the 1st place?
I'd make WindowVisibility a readonly property:
public Visibility WindowVisibility
{
get
{
if (GuiItemsInstance.ToolbarItems.Any(i => i.ToolbarLocation == ToolbarLocation.Float && i.RoleId == RoleId))
return Visibility.Visible;
else
return Visibility.Hidden;
}
}
bind the visibility OneWay:
Visibility="{Binding WindowVisibility, Mode=OneWay}"
and then whenever you have to "update" the visibility just raise the property changed... I don't know what you are using, could either
OnPropertyChanged("WindowVisibility");
or
OnPropertyChanged(() => WindowVisibility);
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}"/>
I have a panel with a button on it that is used to trigger an image capture from an external camera. The capture can take several seconds, so I want the button to disable when capture is in progress. I also want to be able to prevent the user capturing when my program is running a control script. Here is my ViewModel class:
public class CameraControlViewModel : ViewModelBase
{
public CameraControlViewModel()
{
}
public CameraControlViewModel( DataModel dataModel )
: base( dataModel )
{
dataModel.PropertyChanged += DataModelOnPropertyChanged;
_captureImageCommand = new RelayCommand( captureImage );
_capturedImage = new BitmapImage();
_capturedImage.BeginInit();
_capturedImage.UriSource = new Uri( "Images/fingerprint.jpg", UriKind.Relative );
_capturedImage.CacheOption = BitmapCacheOption.OnLoad;
_capturedImage.EndInit();
}
public ICommand CaptureImageCommand
{
get { return _captureImageCommand; }
}
public bool CanCaptureImage
{
get { return !dataModel.IsScriptRunning && !_captureInProgress; }
}
public bool IsCaptureInProgress
{
get { return _captureInProgress; }
set
{
if (_captureInProgress != value)
{
_captureInProgress = value;
OnPropertyChanged( "IsCaptureInProgress" );
OnPropertyChanged( "CanCaptureImage" );
}
}
}
public int PercentDone
{
get { return _percentDone; }
set
{
if (_percentDone != value)
{
_percentDone = value;
OnPropertyChanged( "PercentDone" );
}
}
}
public BitmapImage CapturedImage
{
get { return _capturedImage; }
}
private void DataModelOnPropertyChanged( object sender, PropertyChangedEventArgs propertyChangedEventArgs )
{
string property = propertyChangedEventArgs.PropertyName;
if (property == "IsScriptRunning")
{
OnPropertyChanged( "CanCaptureImage" );
}
OnPropertyChanged( property );
}
private void captureImage( object arg )
{
IsCaptureInProgress = true;
PercentDone = 0;
// TODO: remove this placeholder.
new FakeImageCapture( this );
// TODO (!)
}
internal void captureComplete()
{
IsCaptureInProgress = false;
}
// Remove this placeholder when we can take images.
private class FakeImageCapture
{
CameraControlViewModel _viewModel;
int _count;
Timer _timer = new Timer();
public FakeImageCapture( CameraControlViewModel viewModel )
{
this._viewModel = viewModel;
_timer.Interval = 50;
_timer.Elapsed += TimerOnTick;
_timer.Start();
}
private void TimerOnTick( object sender, EventArgs eventArgs )
{
++_count;
if (_count <= 100)
{
_viewModel.PercentDone = _count;
}
else
{
Application.Current.Dispatcher.Invoke( (Action)_viewModel.captureComplete );
_timer.Stop();
_timer = null;
_viewModel = null;
}
}
}
private readonly ICommand _captureImageCommand;
private volatile bool _captureInProgress;
private BitmapImage _capturedImage;
private int _percentDone;
}
Here is the XAML for the button:
<Button Command="{Binding CaptureImageCommand}"
Grid.Row="0" Grid.Column="0"
Margin="4"
IsEnabled="{Binding CanCaptureImage}"
ToolTip="Capture Image">
<Image Source="../Images/camera-icon.gif" Width="64" Height="64" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
Clicking the "capture" button goes fine. The button disables and elsewhere a progress bar appears showing the (currently faked) image capture progress. However, when the capture completes, even though I set the CanCaptureImage property in the captureComplete() method, the button does not change back to its "enabled" appearance. It will only do this when I click somewhere (anywhere) in the window. However, the button is actually enabled because I can click on it again to trigger a 2nd capture.
I have tried CommandManager.InvalidateRequerySuggested() inside captureComplete() but that doesn't help. Any ideas?
Rather than having a separate IsEnabled binding to enable/disable the button, you should really just use the CanExecute predicate of the RelayCommand: http://msdn.microsoft.com/en-us/library/hh727783.aspx
This would ensure that the button will get enabled/disabled properly when calling CommandManager.InvalidateRequerySuggested(). Get rid of the CanCaptureImage property and modify your code as follows:
public CameraControlViewModel( DataModel dataModel )
: base( dataModel )
{
dataModel.PropertyChanged += DataModelOnPropertyChanged;
_captureImageCommand = new RelayCommand( captureImage, captureImage_CanExecute );
_capturedImage = new BitmapImage();
_capturedImage.BeginInit();
_capturedImage.UriSource = new Uri( "Images/fingerprint.jpg", UriKind.Relative );
_capturedImage.CacheOption = BitmapCacheOption.OnLoad;
_capturedImage.EndInit();
}
private bool captureImage_CanExecute( object arg)
{
return !dataModel.IsScriptRunning && !_captureInProgress;
}