Reputation: 324
I have a PlayerV.xaml View with a Slider inside:
<Slider Value="{Binding CurrentProgress}"/>
and have a button:
<Button Content="next song" Command="{Binding playNext}"/>
Button works correct. Button's playNext command is contained in PlayerVM.cs
I want slider's ValueChanged to call a function which is stored in PlayerVM.cs:
[1]:<Slider Value="{Binding CurrentProgress}" ValueChanged="{Binding playNext}"/>
I know [1] has an incorrect syntax, I used it for sake of clarity of explanation.
====Additional Explanation====
I know I can write:
<Slider ValueChanged="Slider_ValueChanged" Value="{Binding CurrentProgress}" />
And in PlayerV.xaml.cs there will be
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
//some logic (actions)
}
}
But I don't want any logic in there. I want it to be in PlayerVM.cs (like button's command handler functions). How to do that? Also in my App.xaml.cs startup function is:
private void OnStartup(object sender, StartupEventArgs e)
{
MainWindow _mainWindow = new MainWindow();
PlayerVM playerVM = new PlayerVM();
_mainWindow.DataContext = playerVM;
_mainWindow.Show();
}
Upvotes: 5
Views: 23287
Reputation: 801
There is one more option. You can make your own version of Slider control, that can execute command when user interacts with it.
An imperfect but working code might look like this:
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
public class CommandSlider : Slider, ICommandSource
{
public static readonly DependencyProperty CommandParameterProperty = ButtonBase.CommandParameterProperty.AddOwner(typeof(CommandSlider));
public static readonly DependencyProperty CommandProperty = ButtonBase.CommandProperty.AddOwner(typeof(CommandSlider));
public static readonly DependencyProperty CommandTargetProperty = ButtonBase.CommandTargetProperty.AddOwner(typeof(CommandSlider));
private volatile int _commandArmed = 0;
[Bindable(true), Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
[Bindable(true), Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
[Bindable(true), Category("Action")]
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
protected override void OnDecreaseLarge()
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnDecreaseLarge();
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnDecreaseSmall()
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnDecreaseSmall();
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnIncreaseLarge()
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnIncreaseLarge();
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnIncreaseSmall()
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnIncreaseSmall();
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnMaximizeValue()
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnMaximizeValue();
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnMinimizeValue()
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnMinimizeValue();
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnThumbDragCompleted(DragCompletedEventArgs e)
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnThumbDragCompleted(e);
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnThumbDragDelta(DragDeltaEventArgs e)
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnThumbDragDelta(e);
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnThumbDragStarted(DragStartedEventArgs e)
{
try
{
Interlocked.Increment(ref _commandArmed);
base.OnThumbDragStarted(e);
}
finally
{
Interlocked.Decrement(ref _commandArmed);
}
}
protected override void OnValueChanged(double oldValue, double newValue)
{
base.OnValueChanged(oldValue, newValue);
if (_commandArmed > 0 && IsEnabled && oldValue != newValue)
{
ExecuteCommand();
}
}
private void ExecuteCommand()
{
var command = Command;
if (command == null)
return;
var parameter = CommandParameter;
if (command is RoutedCommand routedCommand)
{
var target = CommandTarget ?? this;
if (routedCommand.CanExecute(parameter, target))
{
routedCommand.Execute(parameter, target);
}
}
else if (command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
Then you can use this like that:
<local:CommandSlider Value="{Binding CurrentProgress, Mode=OneWay}"
Command="{Binding ChangeCurrentProgress, Mode=OneWay}"
CommandParameter="{Binding Value, RelativeSource={RelativeSource Self}}" />
I would also suggest to add some debounce, because during drag of slider's handle, the command will be executed dozens of times.
Upvotes: 0
Reputation: 69959
You have two options. First, despite what you said about not wanting to use code behind, one solution is for you to do just that. In the ValueChanged
event handler, you can simply call your view model method when ever the value is changed:
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Slider slider = sender as Slider;
PlayerVM viewModel = (PlayerVM)DataContext;
viewModel.YourMethod(slider.Value);
}
I've offered this solution because I suspect that you're new to MVVM and still think that you're not allowed to use the code behind. In fact, that is not the case at all and for purely UI matters such as this, it's a good place for it.
Another option is just to data bind a property directly to the Slider.Value
. As the Slider
value is changed, so will the property be. Therefore, you can simply call your method from the data bound property setter:
public double CurrentProgress
{
get { return currentProgress; }
set
{
currentProgress = value;
NotifyPropertyChanged("CurrentProgress");
YourMethod(value);
}
}
One further option involves handling the ValueChanged
event in a custom Attached Property. There is a bit more to this solution than the others, so I'd prefer to direct you to some answers that I have already written for other questions, rather than re-writing it all again. Please see my answers to the How to set Focus to a WPF Control using MVVM? and WPF C# - navigate WebBrowser on mouseclick through Binding questions for explanations and code examples of this method.
Upvotes: 17
Reputation: 570
By using the event to commend logic you can bind events to your view model, but you need help. You need to use functions from the System.Windows.Interactivity Namespace and include a MVVM Light (there might be other MVVM libraries that have that feature but i use MVVM Light).
refer this: Is it possible to bind a WPF Event to MVVM ViewModel command?
Upvotes: 3