how to bind the click event of a button and the selecteditemchanged of a listbox to a viewmodel in mvvm in Silverlight - silverlight

i'm just starting with the mvvm model in Silverlight.
In step 1 i got a listbox bound to my viewmodel, but now i want to propagate a click in a button and a selecteditemchanged of the listbox back to the viewmodel.
I guess i have to bind the click event of the button and the selecteditemchanged of the listbox to 2 methods in my viewmodel somehow?
For the selecteditemchanged of the listbox i think there must also be a 'return call' possible when the viewmodel tries to set the selecteditem to another value?
i come from a asp.net (mvc) background, but can't figure out how to do it in silverlight.

Roboblob provides excellent step-by-step solution for Silverlight 4. It strictly follows MVVM paradigm.

I would not bind or tie the VM in any way directly to the events of controls within the View. Instead, have a separate event that is raised by the View in response to the button click.
[disclaimer: this code is all done straight from my head, not copy & pasted from VS - treat it as an example!!]
So in pseudo code, the View will look like this:
private void MyView_Loaded(...)
{
MyButton.Click += new EventHandler(MyButton_Click);
}
private void MyButton_Click(...)
{
//Raise my event:
OnUserPressedGo();
}
private void OnUserPressedGo()
{
if (UserPressedTheGoButton != null)
this.UserPressedTheGoButton(this, EventArgs.Empty);
}
public EventHandler UserPressedTheGoButton;
and the VM would have a line like this:
MyView.UserPressedTheGoButton += new EventHandler(myHandler);
this may seem a little long-winded, why not do it a bit more directly? The main reason for this is you do not want to tie your VM too tightly (if at all) to the contents of the View, otherwise it becomes difficult to change the View. Having one UI agnostic event like this means the button can change at any time without affecting the VM - you could change it from a button to a hyperlink or that kool kat designer you hire may change it to something totally weird and funky, it doesn't matter.
Now, let's talk about the SelectedItemChanged event of the listbox. Chances are you want to intercept an event for this so that you can modify the data bound to another control in the View. If this is a correct assumption, then read on - if i'm wrong then stop reading and reuse the example from above :)
The odds are that you may be able to get away with not needing a handler for that event. If you bind the SelectedItem of the listbox to a property in the VM:
<ListBox ItemSource={Binding SomeList} SelectedItem={Binding MyListSelectedItem} />
and then in the MyListSelectedItem property of the VM:
public object MyListSelectedItem
{
get { return _myListSelectedItem; }
set
{
bool changed = _myListSelectedItem != value;
if (changed)
{
_myListSelectedItem = value;
OnPropertyChanged("MyListSelectedItem");
}
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.NotifyPropertyChanged != null)
this.NotifyPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
To get that NotifyPropertyChanged event, just implement the INotifyPropertyChanged interface on your VM (which you should have done already). That is the basic stuff out of the way... what you then follow this up with is a NotifyPropertyChanged event handler on the VM itself:
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MyListSelectedItem":
//at this point i know the value of MyListSelectedItem has changed, so
//i can now retrieve its value and use it to modify a secondary
//piece of data:
MySecondaryList = AllAvailableItemsForSecondaryList.Select(p => p.Id == MyListSelectedItem.Id);
break;
}
}
All you need now is for MySecondaryList to also notify that its value has changed:
public List<someObject> MySecondaryList
{
get { return _mySecondaryList; }
set
{
bool changed = .......;
if (changed)
{
... etc ...
OnNotifyPropertyChanged("MySecondaryList");
}
}
}
and anything bound to it will automatically be updated. Once again, it may seem that this is the long way to do things, but it means you have avoided having any handlers for UI events from the View, you have kept the abstraction between the View and the ViewModel.
I hope this has made some sense to you. With my code, i try to have the ViewModel knowing absolutely zero about the View, and the View only knowing the bare minimum about the ViewModel (the View recieves the ViewModel as an interface, so it can only know what the interface has specified).

Regarding binding the button click event I can recommend Laurent Bugnion's MVVM Light Toolkit (http://www.galasoft.ch/mvvm/getstarted/) as a way of dealing with this, I'll provide a little example, but Laurent's documentation is most likely a better way of understanding his framework.
Reference a couple of assemblies in your xaml page
xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
add a blend behaviour to the button
<Button Content="Press Me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<command:EventToCommand Command="{Binding ViewModelEventName}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
and create the event within your viewmodel which will be called when the button is clicked
public RelayCommand ViewModelEventName { get; protected set; }
...
public PageViewModel()
{
ViewModelEventName = new RelayCommand(
() => DoWork()
);
}
This supports passing parameters, checking whether execution is allowed etc also.
Although I haven't used it myself, I think the Prism framework also allows you to do something similar.

Related

Click on CheckBox Binding

I have Car.xaml:
<CheckBox Name="carA" IsChecked="{Binding CarACheck, Mode=TwoWay, FallbackValue=true}" />
Then in Car.xaml.cs:
public Car()
{
//...
DataContext = this;
}
public bool CarACheck
{
get => carA.IsChecked;
set => carA.IsChecked = value;
}
When I run it and click on CheckBox, the app crashes with the error below:
set => carA.IsChecked = value;
System.StackOverflowException
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
Cause of Error
The reason is that your setter is called over and over.
The CheckBox is clicked.
The IsChecked property of the CheckBox is set.
The binding sets value on the bound property CarACheck.
The setter of CarACheck sets the IsChecked property of the CheckBox.
Go to 3.
In the long run this causes the stack to overflow and the application crashes.
An MVVM Solution
It seems that you are trying to build a view that displays data about cars. What I see from your sample is that you mix your data and most likely business logic with the user interface code. You should not do that, because it harms code quality and maintainability in the long run. A better and sustainable approach is to separate your view from data and business logic. This is what the MVVM pattern is about.
In your sample code, we have a view, which we would call CarView. This is where the CheckBox is defined along with the rest of the user interface to represent a car. The data is exposed to the view through a separate view model type called CarViewModel. This view model contains properties that are bound from the view.
When using simple properties, regardless of having a backing field or not, the bindings are not able to determine changes to properties from the view model side. This is why you have to implement the INotifyPropertyChanged interface, which provides an event for this purpose. The event is raised, whenever a property is changed. Usually this is in done in the setter.
public class CarViewModel : INotifyPropertyChanged
{
private bool _carACheck;
public bool CarACheck
{
get => _carACheck;
set
{
if (_carACheck == value)
return;
_carACheck = value;
OnPropertyChanged(nameof(_carACheck));
}
}
// ...other code, maybe even setting CarACheck.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the view, simply assign an instance of this view model to the DataContext in XAML or code-behind, e.g.:
public Car()
{
// ...other code.
DataContext = new CarViewModel();
}
In your view, you do not need a name for the CheckBox, as there is no explicit reference to it. The Mode=TwoWay declaration can be removed, too, because the IsChecked property binds two-way by default.
<CheckBox IsChecked="{Binding CarACheck, FallbackValue=true}"/>
Remarks about your original code: I hope that you can see the benefits of the MVVM pattern. Of course, after reviewing this solution, you could simply add a backing field and INotifyPropertyChanged to your current code to make it work, too, but the lesson to learn here is that this separation is valuable and worth investing although it might seem more verbose. See also:
Data binding overview (WPF .NET)
How to: Implement Property Change Notification

WPF Is there a simple way to update GUI from main thread [duplicate]

I'm a web and backend programmer by nature. Normally I try to avaoid making windows programs. Now I have to make a WPF client.
I have a background task that raises an event every often time. (It is working like a poller and when the criteria are met an event is raised). Noob as I am I wrote this code that was attached to the event to update the UI.
private void IsDisconnectedEvent()
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
}
This gives an exception because I am not on the same thread. After some googling I found that I should change the code with:
private void IsDisconnectedEvent()
{
Dispatcher.Invoke(() =>
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
});
}
This works, but this is not the only event and thus makes my code horrible ugly. Are there better ways to do this?
Regarding this:
This works, but this is not the only event and thus makes my code
horrible ugly
Yes, your WPF-based code will definitely be extremely horrible unless you understand and embrace The WPF Mentality.
Basically, all interactions between your custom logic (AKA Business logic or Application Logic) and the WPF UI should manifest in the form of Declarative DataBinding as opposed to the traditional imperative approach.
This means that there should be nothing like this:
UserWindow.Visibility = Visibility.Hidden;
anywhere in your code, simply because introducing things like that makes your code dependent on the UI and thus only executable on the UI thread.
Instead, the WPF approach to that would be to declaratively DataBind the Visibility propety of the UI element (IN XAML) to a relevant bool property that you can operate from the outside, like this:
<UserWindow Visibility="{Binding ShowUserWindow, Converter={my:BoolToVisibilityConverter}}">
<!-- ... -->
</UserWindow>
Then, you would need to create a relevant class that contains the properties the UI is expecting to bind to. This is called a ViewModel.
Notice that in order to properly support Two-Way WPF DataBinding, your ViewModels must Implement the INotifyPropertyChanged interface.
When doing so, it is also convenient to have the PropertyChanged event from that interface marshalled to the UI thread, so that you no longer have to worry about setting the ViewModel's properties by using the Dispatcher.
Therefore our first step is to have all our ViewModels inherit from a class like this:
(taken from this answer):
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
Once we have our Property Change Notification Dispatch to the UI Thread in place, we can proceed to create a relevant ViewModel that suits, in this case, the UserWindow and it's DataBinding expectations:
public class UserViewModel: PropertyChangedBase
{
private bool _showUserWindow;
public bool ShowUserWindow
{
get {return _showUserWindow; }
set
{
_showUserWindow = value;
OnPropertyChanged("ShowUserWindow"); //This is important!!!
}
}
}
Finally, you would need to set the Window's DataContext to an instance of it's corresponding ViewModel. One simple way to do that is in the Window's constructor:
public UserWindow() //Window's Constructor
{
InitializeComponent(); //this is required.
DataContext = new UserViewModel(); //here we set the DataContext
}
As you can see in this example, there is literally no need to manipulate the UI element's properties in procedural code. This is good not only because it resolves the Thread Affinity issues (because now you can set the ShowUserWindow property from any thread), but also because it makes your ViewModels and logic completely decoupled from the UI and thus testable and more scalable.
This same concept applies to EVERYTHING in WPF.
One detail that I need to mention is that I'm making use of a technique of Combining MarkupExtension and IValueConverter in order to reduce the the XAML boilerplate involved in using Converters.
You can read more about that in the link and also the MSDN DataBinding page linked above.
Let me know if you need further details.

How else can I trigger animation in the view from the viewmodel?

So right now I'm using a property on my control that binds to a bool property on my viewmodel called searcheditemfound (with its pair, searcheditemnotfound). I can't have just one property because even if I raise OnPropertyChanged("variableName), it will not activate the trigger unless there was an actual value change. This means that in the code for the view model, I have to do this really ugly looking:
SearchedItemNotFound = false;
SearchedItemNotFound = true;
private bool _SearchedItemNotFound;
public bool SearchedItemNotFound
{
get
{
return _SearchedItemNotFound;
}
set
{
if (value != _SearchedItemNotFound)
{
_SearchedItemNotFound = value;
OnPropertyChanged("SearchedItemNotFound");
}
}
}
when what I would really like is to just tie to an event in the view model. However, eventtriggers only trigger off routed events. Can I place routed events in the viewmodel? I think I have to inherit from control to do that.
One post here: How can I Have a WPF EventTrigger on a View trigger when the underlying Viewmodel dictates it should?
mentioned using
<i:Interaction.Triggers>
<samples:DataEventTrigger EventName="YourEvent">
<im:ControlStoryboardAction Storyboard="{StaticResource Storyboard1}"
ControlStoryboardOption="Play"/>
</samples:DataEventTrigger>
</i:Interaction.Triggers>
which looks perfect, except that it requires expression blend and I'm in visual studio 2008 where it isn't available. I'm wondering what other options I have. I don't even mind if it's not MVVM. I'm not a purist, I'm just trying to understand what my options are.
A RoutedEvent is a UI element and has no place in a view model. Personally, I can't really see the problem with changing your bool property from true to false and then back to true again... there is a valid reason for doing that. If it really bothers you, then just hide it away in a method and call that instead:
private StartSearchedItemNotFoundAnimation()
{
SearchedItemNotFound = false;
SearchedItemNotFound = true;
}
There's hardly any point in using any more complex and/or expensive ways to replicate the functionality that you already have.

MVVM pattern violation: MediaElement.Play()

I understand that ViewModel shouldn't have any knowledge of View, but how can I call MediaElement.Play() method from ViewModel, other than having a reference to View (or directly to MediaElement) in ViewModel?
Other (linked) question: how can I manage View's controls visibility from ViewModel without violating MVVM pattern?
1) Do not call Play() from the view model. Raise an event in the view model instead (for instance PlayRequested) and listen to this event in the view:
view model:
public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
this.PlayRequested(this, EventArgs.Empty);
}
view:
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.PlayRequested += (sender, e) =>
{
this.myMediaElement.Play();
};
2) You can expose in the view model a public boolean property, and bind the Visibility property of your controls to this property. As Visibility is of type Visibility and not bool, you'll have to use a converter.
You can find a basic implementation of such a converter here.
This related question might help you too.
For all the late-comers,
There are many ways to achieve the same result and it really depends on how you would like to implement yours, as long as your code is not difficult to maintain, I do believe it's ok to break the MVVM pattern under certain cases.
But having said that, I also believe there is always way to do this within the pattern, and the following is one of them just in case if anyone would like to know what other alternatives are available.
The Tasks:
we don't want to have direct reference from the ViewModel to any UI elements, i.e. the the MediaElement and the View itself.
we want to use Command to do the magic here
The Solution:
In short, we are going to introduce an interface between the View and the ViewModel to break the dependecy, and the View will be implementing the interface and be responsible for the direct controlling of the MediaElement while leaving the ViewModel talking only to the interface, which can be swapped with other implementation for testing purposes if needed, and here comes the long version:
Introduce an interface called IMediaService as below:
public interface IMediaService
{
void Play();
void Pause();
void Stop();
void Rewind();
void FastForward();
}
Implement the IMediaService in the View:
public partial class DemoView : UserControl, IMediaService
{
public DemoView()
{
InitializeComponent();
}
void IMediaService.FastForward()
{
this.MediaPlayer.Position += TimeSpan.FromSeconds(10);
}
void IMediaService.Pause()
{
this.MediaPlayer.Pause();
}
void IMediaService.Play()
{
this.MediaPlayer.Play();
}
void IMediaService.Rewind()
{
this.MediaPlayer.Position -= TimeSpan.FromSeconds(10);
}
void IMediaService.Stop()
{
this.MediaPlayer.Stop();
}
}
we then do few things in the DemoView.XAML:
Give the MediaElement a name so the code behind can access it like above:
<MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
Give the view a name so we can pass it as a parameter, and
import the interactivity namespace for later use (some default namespaces are omitted for simplicity reason):
<UserControl x:Class="Test.DemoView"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="MediaService">
Hookup the Loaded event through Trigger to pass the view itself to the view model through a Command
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction>
</ia:EventTrigger>
</ia:Interaction.Triggers>
last but not least, we need to hookup the media controls through Commands:
<Button Command="{Binding PlayCommand}" Content="Play"></Button>
<Button Command="{Binding PauseCommand}" Content="Pause"></Button>
<Button Command="{Binding StopCommand}" Content="Stop"></Button>
<Button Command="{Binding RewindCommand}" Content="Rewind"></Button>
<Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button>
We now can catch everything in the ViewModel (I'm using prism's DelegateCommand here):
public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest
{
public IMediaService {get; private set;}
private DelegateCommand<IMediaService> loadedCommand;
public DelegateCommand<IMediaService> LoadedCommand
{
get
{
if (this.loadedCommand == null)
{
this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) =>
{
this.MediaService = mediaService;
});
}
return loadedCommand;
}
}
private DelegateCommand playCommand;
public DelegateCommand PlayCommand
{
get
{
if (this.playCommand == null)
{
this.playCommand = new DelegateCommand(() =>
{
this.MediaService.Play();
});
}
return playCommand;
}
}
.
. // other commands are not listed, but you get the idea
.
}
Side note: I use Prism's Auto Wiring feature to link up the View and ViewModel. So at the View's code behind file there is no DataContext assignment code, and I prefer to keep it that way, and hence I chose to use purely Commands to achieve this result.
I use media element to play sounds in UI whenever an event occurs in the application. The view model handling this, was created with a Source property of type Uri (with notify property changed, but you already know you need that to notify UI).
All you have to do whenever source changes (and this is up to you), is to set the source property to null (this is why Source property should be Uri and not string, MediaElement will naturally throw exception, NotSupportedException I think), then set it to whatever URI you want.
Probably, the most important aspect of this tip is that you have to set MediaElement's property LoadedBehaviour to Play in XAML of your view. Hopefully no code behind is needed for what you want to achieve.
The trick is extremely simple so I won't post a complete example. The view model's play function should look like this:
private void PlaySomething(string fileUri)
{
if (string.IsNullOrWhiteSpace(fileUri))
return;
// HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI.
this.Source = null;
this.Source = new Uri(fileUri);
}
Here is the Source property, nothing special about it:
#region Source property
/// <summary>
/// Stores Source value.
/// </summary>
private Uri _Source = null;
/// <summary>
/// Gets or sets file URI to play.
/// </summary>
public Uri Source
{
get { return this._Source; }
private set
{
if (this._Source != value)
{
this._Source = value;
this.RaisePropertyChanged("Source");
}
}
}
#endregion Source property
As for Visibility, and stuff like this, you can use converters (e.g. from bool to visibility, which you can find on CodePlex for WPF, SL, WP7,8) and bind your control's property to that of the view model's (e.g. IsVisible). This way, you control parts of you view's aspect. Or you can just have Visibility property typed System.Windows.Visibility on your view model (I don't see any pattern breach here). Really, it's not that uncommon.
Good luck,
Andrei
P.S. I have to mention that .NET 4.5 is the version where I tested this, but I think it should work on other versions as well.

Binding SelectionChanged to ViewModel using Caliburn.Micro

We've using Caliburn.Micro on a new Silverlight project and everythings working great. The inbuilt conventions bind buttons click events to the viewModel, but I'm not sure what the best way to handle the selectionChanged event on datagrids and comboboxes is.
At the moment, I'm binding to the selected item and calling custom logic, but I feel like this is a bit of a code smell and that I should seperate the setting of the property and the selectedChange event. But if I seperate these, how do I bind the selection changed event to my viewModel, by commands? or an EventTrigger? Or is the code below acceptable? Its a small change but I do this logic everywhere.
private Foo _selectedFoo;
public Foo SelectedFoo
{
get
{
return _Foo;
}
set
{
if (_Foo != null && _Foo.Equals(value)) return;
_Foo = value;
NotifyOfPropertyChange("SelectedFoo");
NotifyOfPropertyChange("CanRemove");
LoadRelatedBars();
}
}
I use this technique regularly and I feel very comfortable with it.
I find perfectly fine that the VM reacts to its own state change, without the need for the external actor (which incidentally is the View, but could be another component, too) to set the new state, THEN signal the VM that the state is changed.
If you really want to, however, you can use the Message.Attach attached property to hook an event in the View to an action in the VM:
cal:Message.Attach="[Event SelectionChanged] = [OnSelectionChangedAction]"
(see also https://caliburnmicro.com/documentation/actions)
Here is a sample for MVVM and Caliburn.Micro using. Some actions like SelectionChanged should get an explicit a event arguments, so you should set it in caliburn event action part. Freqently first argument is passing $this (The actual ui element to which the action is attached.) and you gets in handler a datacontext for the row but to get to the Grid you should pass $source, as the first argument ($source - is the actual FrameworkElement that triggered the ActionMessage to be sent). According to the manual Caliburn manual.
XAML
cal:Message.Attach="[Event SelectionChanged]=[Action DataGrid_JobTypesSelectionChanged($source,$eventArgs)];"
Code:
public void DataGrid_JobTypesSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var grid = sender as DataGrid;
JobTypesSelectedCollection = grid.SelectedItems.Cast<JobComplexModel>().ToList();
}

Resources