How to handle the Slider.ValueChanged event in a view model? - c#

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();
}

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.

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?

Related

Can't deselect ListView Item in MVVM UWP

I want to be able to click ListView item, which then takes me to appropriate page. But since there doesn't exists anything like ClickedItem to go along with the ItemClick, I have to use the SelectedItem (to get the object of what the user clicked) and SelectionChanged to capture when it happens (because this is setup in a way that when user clicks, he makes a selection, which triggers this).
Since in MVVM I can't use events, I'm binding what would be events to methods in my ViewModel.
<GridView x:Name="MyGrid"
ItemsSource="{x:Bind ViewModel.myList, Mode=OneWay}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
IsSwipeEnabled="false"
SelectedItem="{Binding mySelectedItem, Mode=TwoWay}" // Binding makes it easier to bind the whole object
SelectionChanged="{x:Bind ViewModel.SelectioMade}"
>
I fill up my list in the ViewModel. I'm using Template10 implementation of INotifyPropertyChanged.
private MyListItemClass _mySelectedItem;
public MyListItemClass mySelectedItem{
get { return _mySelectedItem; }
set { Set(ref _mySelectedItem, value); }
}
And this simple method pushes me to the next page when user clickes on an item.
public void SelectioMade() {
if (_mySelectedItem != null) {
NavigationService.Navigate(typeof(Views.DetailPage), _mySelectedItem.id);
}
}
This works.
Problem is that a selection is made and it persists. When I hit the back button on the DetailPage, I go back to this list as I left it and the clicked item is still selected. And hence, clicking it again doesn't actually make a selection and trigger the SelectionChanged.
Obvious choice seemed to be to just set mySelectedItem to null when I no longer need the value, but it doesn't work.
public void SelectioMade() {
if (_mySelectedItem != null) {
NavigationService.Navigate(typeof(Views.DetailPage), _mySelectedItem.id);
mySelectedItem = null;
}
}
I can't seem to be able to set it back to null. If I place a break point on the mySelectedItem = null; it just doesn't do anything. It does trigger the set { Set(ref _mySelectedItem, value); }, but the View doesn't update. Neither the clicked item becomes deselected, nor a TextBlock I bound to one of the mySelectedItem.id properties gets changed (or rather emptied).
I would like to know why doesn't this work and possibly how to fix it. My MVVM may not be perfect, I'm still learning. And while it may not be perfect, I'm not really looking for advice how to properly write MVVM. I want to know why this doesn't work, because in my opinion, it should work just fine.
It seems that GridView doesn't like the SelectedItem property being changed within the SelectionChanged handler (it could result in an infinite loop if guards are not used). You could instead set SelectedItem to null in the OnNavigatedTo handler for that page (or whatever the Template 10 equivalent of that is).
Also you don't really need to subscribe to the SelectionChanged event since you can detect this in the setter of your mySelectedItem property.
However, I think it is wrong to handle item clicks by listening for selection changed events because the selection can be changed by other means (up/down arrow key, or tab key, for example). All you want to do is to respond to an item click and obtain the clicked item, right? For this, you can x:Bind the ItemClick event to a method in your view model:
<GridView ItemClick="{x:Bind ViewModel.ItemClick}" SelectionMode="None" IsItemClickEnabled="True">
public void ItemClick(object sender, ItemClickEventArgs e)
{
var item = e.ClickedItem;
}
If you're uneasy about the ItemClick method signature in your view model, then you can make your own ItemClick behavior to execute a Command exposed in your view model with the command's parameter bound to the clicked item.
If you're not using behaviors for some reason, then you can make your own attached property instead, something like this:
public class ViewHelpers
{
#region ItemClickCommand
public static readonly DependencyProperty ItemClickCommandProperty =
DependencyProperty.RegisterAttached("ItemClickCommand", typeof(ICommand), typeof(ViewHelpers), new PropertyMetadata(null, onItemClickCommandPropertyChanged));
public static void SetItemClickCommand(DependencyObject d, ICommand value)
{
d.SetValue(ItemClickCommandProperty, value);
}
public static ICommand GetItemClickCommand(DependencyObject d)
{
return (ICommand)d.GetValue(ItemClickCommandProperty);
}
static void onItemClickCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listView = d as ListViewBase;
if (listView == null)
throw new Exception("Dependency object must be a ListViewBase");
listView.ItemClick -= onItemClick;
listView.ItemClick += onItemClick;
}
static void onItemClick(object sender, ItemClickEventArgs e)
{
var listView = sender as ListViewBase;
var command = GetItemClickCommand(listView);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
#endregion
}
XAML doesn't require MVVM patterns to be used, which means there is lots of "missing" functionality that you need to write yourself to make MVVM easier for you (like the above ItemClick attached property). Maybe Template 10 provides some behaviors for you already? I'm not familiar with it.
My first instinct would be to check your Set method, to ensure that it is really sending the proper notification to the view. I am not familiar with the Template10 implementation, so it seems strange to me that you are not required to provide a property name with Set().
Beyond that, I would suggest that you go back to using Click rather than SelectionChanged, since that is the behavior you are actually interested in. You should read a bit about attached properties, which are a great way to accomplish tasks that would normally require code-behind without actually using code-behind. They make MVVM a lot more practical and a lot less hackish.
https://msdn.microsoft.com/en-us/library/ms749011(v=vs.110).aspx
An attached property, more or less, allows you to define a DependencyProperty that you can attach to any element in XAML, like your GridView. Because you get access to the Element in the setter, you are free to attach to its events. So, you can create an attached property with a delegate type, which will forward an event like click to the delegate. Back in the view, you bind it to your handler in the ViewModel like this:
<GridView something:MyAttachedProperties.ClickHandler="{Binding MyClickHandler}" />
Hope this helps!
SelectedIndex = -1, following your null set of the SelectedItem property? So yes another property would be required or make sure that caching is disabled for that page as well.

MVVMCross: How to bind Xamarin.Android events to ViewModel commands

I am trying to go from an activity to another. I am still learning about MVVMCross so this whole pattern is still very new to me. I am applying it with Xamarin.Android only at the moment.
The setup:
MainDashboardActivity has an Android Design Support library's NavigationView.
The ViewModel MainDashboardViewModel has an IMvxCommand GoToSecondDashboard which is just a simple ShowViewModel to another activity.
The NavigationView has a NavigationItemSelected event. Normally, I would just do this:
navigationView.NavigationItemSelected += (o, e) =>
{
if(e.MenuItem.ItemId == Resource.Id.SecondDashboardMenu)
{
// make new intent to target activity
}
};
Now I have tucked the navigation logic into the ViewModel's IMvxCommand, and I want to bind it to the NavigationView's event, no longer creating intents and whatnot. How would I achieve this?
I want to use the fluent binding logic in the code file and not in the layout, like how this answer does:
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.View_Tip);
var edit = this.FindViewById<EditText>(Resource.Id.FluentEdit);
var set = this.CreateBindingSet<TipView, TipViewModel>();
set.Bind(edit).To(vm => vm.SubTotal);
set.Apply();
// for non-default properties use 'For':
// set.Bind(edit).For(ed => ed.Text).To(vm => vm.SubTotal);
// you can also use:
// .WithConversion("converter", "optional parameter")
// .OneTime(), .OneWay() or .TwoWay()
}
But NavigationItemSelected is an event. I have not been able to find a way to bind events to commands. There is also the logic of filtering ItemId before that can happen, so it's not going to even be a straightforward event-to-command binding.
I am not sure if this is the correct approach to this. All I want is to bind menu taps to commands in the code file instead of the layout file.
Since there are no Binding Targets defined for NavigationView, you won't be able to bind as Cyriac describes in his post.
What a target binding does internally is simply subscribe to an event and react to it and exposing that data as a property.
So since there is no way to take an ItemsSource and bind to a NavigationView currently, you have to do something like you are doing already, hooking an EventHandler up to the event, and call directly into your ViewModel, i.e. invoking a Command. This looks something like this:
navigationView.NavigationItemSelected += ItemSelected;
private void ItemSelected(object sender, NavigationItemSelectedEventArgs args)
{
ViewModel.NavigateCommand.Execute(args.MenuItem.TitleFormatted.ToString());
}
Then in your ViewModel in your Command:
private void DoNavigateCommand(string title)
{
if (title == "Derp")
ShowViewModel<DerpViewModel>();
}
Alternatively you could wrap this code in a Target Binding. You can see how these are implemented in the official MvvmCross github repository.
I found an answer by someone else on http://crosscuttingconcerns.com/MvvmCross-Fluent-Databinding , which you should try out. I think you just cant reference directly the Event, rather have to use the string.
protected override void OnViewModelSet ()
{
SetContentView (Resource.Layout.TermsPage);
var set = this.CreateBindingSet<TermsView, TermsViewModel>();
set.Bind(FindViewById<Button>(Resource.Id.acceptTermsButton))
.For("Click")
.To(vm => vm.AcceptTermsCommand);
set.Apply();
}
Well of course you have do adjust it depending on your event.

Hair loss and MVVM user controls

I have a user control written in C# & WPF using the MVVM pattern.
All I want to do is have a property in the bound ViewModel exposed to outside of the control. I want to be able to bind to it and I want any changes to the property to be picked up by anything outside the control that is bound to the exposed value.
This sounds simple, but its making me pull out my hair (and there is not much of that left).
I have a dependency property in the user control. The ViewModel has the property implementing the INotifyPropertyChanged interface and is calling the PropertyChanged event correctly.
Some questions:
1) How do I pick up the changes to the ViewModel Property and tie it to the Dependency Property without breaking the MVVM separation? So far the only way I've managed to do this is to assign the ViewModels PropertyChanged Event in the Controls code behind, which is definitely not MVVM.
2) Using the above fudge, I can get the Dependency property to kick off its PropertyChangedCallback, but anything bound to it outside the control does not pick up the change.
There has to be a simple way to do all of this. Note that I've not posted any code here - I'm hoping not to influence the answers with my existing code. Also, you'd probably all laugh at it anyway...
Rob
OK, to clarify - code examples:
usercontrol code behind:
public static DependencyProperty NewRepositoryRunProperty = DependencyProperty.Register("NewRepositoryRun", typeof(int?), typeof(GroupTree),
new FrameworkPropertyMetadata( null, new PropertyChangedCallback(OnNewRepositoryRunChanged)));
public int? NewRepositoryRun
{
get { return (int?)GetValue(NewRepositoryRunProperty); }
set
{
SetValue(NewRepositoryRunProperty, value);
}
}
private static void OnNewRepositoryRunChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != e.NewValue)
{
}
}
public GroupTree()
{
InitializeComponent();
GroupTreeVM vm = new GroupTreeVM();
this.DataContext = vm;
}
Viewmodel (GroupTreeVM.cs)
private int? _NewRepositoryRun;
public int? NewRepositoryRun
{
get
{
return _NewRepositoryRun;
}
set
{
_NewRepositoryRun = value;
NotifyPropertyChanged();
}
}
And now for my weekly "don't do that" answer...
Creating a ViewModel for your UserControl is a code smell.
You're experiencing this issue because of that smell, and it should be an indication that you're doing something wrong.
The solution is to ditch the VM built for the UserControl. If it contains business logic, it should be moved to an appropriate location in another ViewModel.
You should think of a UserControl as nothing more than a more complex control. Does the TextBox have its own ViewModel? No. You bind your VM's property to the Text property of the control, and the control shows your text in its UI.
Think of UserControls in MVVM like this--For each model, you have a UserControl, and it is designed to present the data in that model to the user. You can use it anywhere you want to show the user that model. Does it need a button? Expose an ICommand property on your UserControl and let your business logic bind to it. Does your business logic need to know something going on inside? Add a routed event.
Normally, in WPF, if you find yourself asking why it hurts to do something, it's because you shouldn't do it.
Perhaps I've misunderstood, but it seems like you're trying to use binding in the code behind?
public MyUserControl()
{
InitializeComponent();
// Set your datacontext.
var binding = new Binding("SomeVMProperty");
binding.Source = this.DataContext;
SetBinding(MyDependencyProperty, binding);
}

MVVM: Bring control into view

I have a Grid with a ScrollViewer around it. At the top of my ScrollViewer is a Button. On a Click on the Button, I want the ScrollViewer to scroll to a Control at the bottom of the ScrollViewer.
With the following XAML I can bring the Control into view:
<Button Grid.Row="2" Content="Some Button" Command="{Binding DoJumpCommand}" CommandParameter="{Binding ElementName=window}"/>
The Command in the ViewModel is:
if (parameter is MainWindowView)
{
var mainWindowView = parameter as MainWindowView;
mainWindowView.myJumpTarget.BringIntoView();
}
This works fine. But I'm not sure if this is clean MVVM because I pass the complete View into the ViewModel.
Is there a better way to do this?
When I first saw your question, I thought that the general solution to handling events with MVVM is to handle them in an Attached Property. However, looking again, it occurred to me that you're not actually handling any events... you just want to call a method from a UI control. So really, all you need is a way to pass a message from the view model to the view. There are many ways to do this, but my favourite way is to define a custom delegate.
First, let's create the actual delegate in the view model:
public delegate void TypeOfDelegate();
It doesn't need any input parameters, because you don't need to pass anything from the view model to the view, except a signal... your intention to scroll the ScrollViewer.
Now let's add a getter and setter:
public TypeOfDelegate DelegateProperty { get; set; }
Now let's create a method in the code behind that matches the in and out parameters of the delegate (none in your case):
public void CanBeCalledAnythingButMustMatchTheDelegateSignature()
{
if (window is MainWindowView) // Set whatever conditions you want here
{
window.myJumpTarget.BringIntoView();
}
}
Now we can set this method as one (of many) handlers for this delegate in a Loaded event handler in the view code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Assumes your DataContext is correctly set to an instance of YourViewModel
YourViewModel viewModel = (YourViewModel)DataContext;
viewModel.DelegateProperty += CanBeCalledAnythingButMustMatchTheDelegateSignature;
}
Finally, let's call our delegate from the view model... this is equivalent to raising the event:
if (DelegateProperty != null) DelegateProperty(dataInstanceOfTypeYourDataType);
Note the important check for null. If the DelegateProperty is not null, then all of the attached handler methods will be called one by one. So that's it! If you want more or less parameters, just add or remove them from the delegate declaration and the handling method... simple.
So this is an MVVM way to call methods on a UI control from a view model. However, in your case it could well be argued that implementing this method would be overkill, because you could just put the BringIntoView code into a basic Click handler attached to your Button. I have supplied this answer more as a resource for future users searching for a way to actually call a UI method from a view model, but if you also chose to use it, then great!

What is the "proper" way in WPF MVVM to bind a frameworkElement event to a viewmodel handler?

So here is my dilemma, I want to handle view events on my view model, trouble is, in order to add an event handler, my view has to have a code behind file and therefore a class attribute has to be set. I'm 99% sure this is a bad idea, and to be perfectly honest, i'm not even sure how to do it (other than the obvious x:Class="" part) What is the proper way to do this in an MVVM application?
<ResourceDictionary>
<DataTemplate DataType="{x:Type vm:OutletViewModel}">
<Button Click="IHaveNoBinding">
</DataTemplate>
</ResourceDictionary>
Use commands:
<Button Command="{Binding ACommandOnYourViewModel}"/>
See this post of mine for a useful command implementation you can use in your view models.
Assuming you can't use commands, use attached command behaviors.
I use Attached Behaviors. Attached behaviors basically translate events into commands. Check out this link for an example:
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
Here is the code for a TextChangedBehavior.
public static class TextChangedBehavior
{
public static readonly DependencyProperty TextChangedCommandProperty =
DependencyProperty.RegisterAttached("TextChangedCommand",
typeof(ICommand),
typeof(TextChangedBehavior),
new PropertyMetadata(null, TextChangedCommandChanged));
public static ICommand GetTextChangedCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(TextChangedCommandProperty);
}
public static void SetTextChangedCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(TextChangedCommandProperty, value);
}
private static void TextChangedCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBoxBase textBox = obj as TextBoxBase;
if (textBox != null)
{
textBox.TextChanged += new TextChangedEventHandler(HandleTextChanged);
}
}
private static void HandleTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox != null)
{
ICommand command = GetTextChangedCommand(textBox);
command.Execute(textBox.Text);
}
}
}
XAML:
<TextBox behavior:TextChangedBehavior.TextChangedCommand="{Binding TextChangedCommand}" />
Generally I will not use the attached behavior pattern for simple things like this. As a consultant I find It complicates things for newer developers.
So how then do you handle control interaction when no commands are available. Get ready to pick your self up off of the floor :-) I will often times use the code behind for this. The event handler in the code behind handles the event, it gathers any data it needs from the event args and then forwards the request to the View Model. You do not lose much by doing this as most things that do not support ICommand cannot leverage the hide/show/enable/disable anyway.
There are some rules however. The code behind can only be used for control forwarding to the View Model. As long as you don't pass event arguments directly to the View Model I think it is fine to use events in this way. The fact of that matter is in large scale applications you cannot always get away from having code behinds. If you use them as they were intended i.e. page controls I see no harm in doing so.
Code behind isn't a bad thing at all. There are enough scenarios where you can't use WPF data binding (e.g. PasswordBox) and then you have to create a code behind file.
How you can use a PasswordBox without binding is shown in the ViewModel example of this project:
WPF Application Framework (WAF)
http://waf.codeplex.com

Categories