Just fiddling around with some WP8.1 development + MVVM-Light toolkit and am having trouble trying to figure out how to achieve something..
Basically:
I have a View (let's call it View1) which has a control (LongListSelector in this case) that is databound to a Collection of items (let's call them DataItem) (which is populated by a Service from a ViewModel)
And I want it so:
When the user taps on a particular item in this control, it passes the item tapped (or a property of this item) to a new View (called View2), which will either create a new ViewModel for View2 or re-use an existing one (depending on the Key of the instances in SimpleIoC, determined by some property in the DataItem tapped).
This new ViewModel then uses the passed property of the DataItem tapped in its' constructor to fetch data from a different Service
So how can I achieve this? I am thinking of Creating/Registering the new ViewModel on the SelectionChanged event of the control, passing it in the Service and Property like so:
private void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataItem item = e.AddedItems[0] as DataItem;
SimpleIoc.Default.Register(() => new ViewModel2(new Model2Service(), item.Name));
NavigationService.Navigate(new Uri("/View2.xaml", UriKind.Relative));
}
Which works fine for the first DataItem tapped, but not when a second is tapped.
Note: I couldn't register ViewModel2 in ViewModelLocator as I couldn't get DataItem properties passed to the constructor for ViewModel2, which is why I'm trying to Register it elsewhere.
Not sure if this is abiding by MVVM architecture, I suppose not as this answer states that I shouldn't be handling this in my View.
So to recap, I want a user to be able to tap on an item in a LongListSelector which will then navigate the user to a new View which is bound to a new (or existing) ViewModel according to a property of the selected item. How can I achieve this?
Any help would be greatly appreciated.
Have you tried using the Messenger in MVVMLight? Try creating the instance of the ViewModel2 in ViewModelLocator with the key being some default value. In the ViewModel's constructor, register to receive a string property (assuming item.Name is a string) like this:
MessengerInstance.Register<string>(this,name=>{NameProperty=name;});
Then in the LongListSelector_SelectionChanged, send the item.Name like this:
Messenger.Default.Send<string>(item.Name);
And then navigate to the page.
Marked as solution but whole comment thread below reveals more details
If you are using UWP with Template Studio 10 and MVVM Light and want to either:
Access the parameter that is passed in the NavigationServicesEx.Navigate method
Call a method in your ViewModel when a page is navigated to.
This blog by Marco Minerva is the only guidance I could find that is up to up to date with UWP, Templates Studio 10 and MVVM Light 5.0 (thanks again Marco!)
tl;dr It works by hooking in to the Frame_Navigating event that it missing from the vanilla NavigationServiceEx class.
Create the INavigable interface described in the blog:
public interface INavigable
{
Task OnNavigatedToAsync(object parameter, NavigationMode mode);
void OnNavigatingFrom(NavigatingCancelEventArgs e);
void OnNavigatedFrom();
}
Add a handler for the Frame.Navigating event in the NavigationServicesEx class (with some additional plumbing, see blog) then realise the INavigable interface in your ViewModels.
You will then be able to access the parameter that you passed in your Navigate call:
NavigationServiceEx.Navigate(typeof(DestinationPage).FullName, yourParameter);
In the OnNavigatedToAsync method that you implement in your ViewModel:
public Task OnNavigatedToAsync(object parameter, NavigationMode mode)
{
if (parameter != null)
{
YourThing thing = parameter as YourThing;
this.UseYourThing(thing);
}
return Task.CompletedTask;
}
Related
I'm new to WPF and MVVM and am going through an example on Microsoft's site, however, I don't see how the binding is done. In the example linked, there's this piece of code:
public partial class MainPage : UserControl
{
private PartInventoryViewModel viewModel;
public MainPage()
{
InitializeComponent();
viewModel = new PartInventoryViewModel();
this.DataContext = viewModel;
}
private void PartSearchButton_Click(object sender, RoutedEventArgs e)
{
viewModel.GetParts();
}
}
Apparently:
It notifies the ViewModel instance when the user clicks the PartSearchButton.
But how? There's no binding in the XAML above for the PartSearchButton in the example. Is it a normative that if you name your function YourButtonName_Clicked() it will trigger when the button is clicked? Or does it become a listener if you create the function with the (object sender, RoutedEventArgs e) arguments? Or is there some XAML which this tutorial is not showing, where the binding occurs?
Thank you for your answer, sorry if it's a newb one.
Microsoft is not showing all the code that is necessary here. Basically all that this code does is setting the DataContext to a newly instantiated ViewModel. The PartSearchButton_Click is a simple Click-Event-Handler that should look something like this in your XAML-file:
<Button Click="PartSearchButton_Click">Search</Button>
The whole binding thing is happening in these 2 lines of the datagrid in your xaml file:
ItemsSource="{Binding Parts}"
SelectedItem="{Binding CurrentPart, Mode=TwoWay}"
This is telling the DataGrid that it should look for a public property called Parts in the current DataContext. You set the DataContext to a new instance of PartInventoryViewModel, so there needs to be a public property Parts somewhere in there. I guess the PartInventoryViewModel class will be explained a bit further down on the Microsofts site.
The XAML snippets from your link are effectively missing that event handler.
The <source>_<event> guideline is the convention for naming event handlers, but by no means the function gets automatically bound to the corresponding event; you have to add the handler either programmatically or in XAML.
That said, associating application logic to buttons is usually done in WPF by means of commands instead of event handlers. The view model exposes a property of type ICommand, anf the view binds the Command dependency property of a Button (or other controls) to it. How that command is implemented under the hood is completely irrelevant to the view.
I've started a Windows Universal project following the MVVM pattern, and came to the situation where I need to navigate to another page with a button click.
Normally I would do this in code behind using the button's click event like the below:
private void AppBarButton_Click(object sender, RoutedEventArgs e)
{
// Navigation Without parameters
this.Frame.Navigate(typeof(SecondPage));
}
But since I need to follow the MVVM pattern with this app, I'm wondering how should I set up the navigation to a new View on button click?
I've come across ICommand for this task in WPF solution after a Google search, but not 100% on how it should be implemented for this Windows Universal Framework.
Basically you got two options
1. use a navigation service
You can define an INavigationService interface and pass it to all your ViewModels in your viewmodel assembly (assuming you are using different assemblies which is important to keep ensure you are not referencing to the view from your viewmodel and hence violate MVVM pattern).
public interface INavigationService
{
void Navigate(string page, object parameter);
}
In your viewmodels you can simply call it with navigationService.Navigate("UserEditPage", selectedUser.Id);.
Implementation could be as simple as
public class WinRtNavigationService : INavigationService
{
public void Navigate(string page, object parameter)
{
Type pageType = Type.GetType(string.Format("YourCompany.YourApp.ViewModels.{0}", page));
((Frame)Window.Current.Content).Navigate(pageType, parameter);
}
}
You use this, if you have the need to navigate from ViewModels.
2. use behaviors
You can use behaviours to add reusable navigation support to XAML directly, hence completely avoiding the code behind.
For this, Blend offers Interactivity Triggers and a NavigateToPageAction behavior.
<Page
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:c="using:Microsoft.Xaml.Interactions.Core" >
....
<Button Content="Edit">
<i:Interaction.Behaviors>
<i:BehaviorCollection>
<c:EventTriggerBehavior EventName="Tapped">
<c:NavigateToPageAction TargetPage="YourCompany.YourApp.ViewModel.UserEditPage" Parameter="{Binding Path=SelectedUser.Id}" />
</c:EventTriggerBehavior>
</i:BehaviorCollection>
</i:Interaction.Behaviors>
</Button>
...
</Page>
Blend Behaviors/Interaction Triggers are generally used to bind navigation functions to Buttons or other UI elements (i.e. click on a picture which doesn't have to be a button), as it doesn't require any code within the Code Behind or ViewModel.
If a navigation is to occur after some validation, i.e. you have a multi-page form for user registration and you have a "Send" Button binded to a RegisterCommand and the RegisterCommand does an online validation and you're required to go back to previous page, you'd want to use the INavigationService.
I am using the Telerik RadRibbonView in my WPF 4.5 project. The set up looks like this.
In my Shell I have a RibbonView and a TabControl defined as a regions called “RibbonRegion” and “TabRegion”. The RibbonRegion is basically the menu of the application and the TabRegion holds the main content.
I have also created a module with a View containing a RibbonTab and a RibbonButton. This button is hocked up to a command that sets the DataContext of a RibbonContextTabView and a TabItemView and registers them in their respective regions. The ContextTab and the TabItem is sharing the same ViewModel. This ViewModel has a propery “IsSelected” that the ContextTab and TabItem are bound to.
if (_regionManager.Regions["RibbonRegion"].Views.Any(v => v.GetType() == typeof(ContextTabView)) && _regionManager.Regions["TabRegion"].Views.Any(v => v.GetType == typeof(TabItemView)))
{
_regionManager.RequestNavigate("RibbonRegion", new Uri("ContextTabView", UriKind.Relative));
_regionManager.RequestNavigate("TabRegion", new Uri("TabItemView", UriKind.Relative));
}
else
{
ContextTabView contextTabView = _container.Resolve<ContextTabView>();
TabItemView tabItemView = _container.Resolve<TabItemView>();
contextTabView.DataContext = tabItemView.DataContext = new ContextTabTabItemViewModel();
_regionManager.RegisterViewWithRegion("RibbonRegion", () => contextTabView);
_regionManager.RegisterViewWithRegion("TabRegion", () => tabItemView);
}
The first time the Command above is executed the DataContext of the views is set and then they are registered in the regions. This also sets the “IsSelected” property to true. If I change focus to the RibbonTab my ContextTab and TabItem loses focus and the “IsSelected” propery is set to false. If I press the button again the RequestNavigate is executed and once again the property is set to true. Here is my problem. If I do this a third time nothing happens! The RequestNavigate is executed but the property is not set to true and the Views does not regain focus. I am fairly new to PRISM and I am afraid that I am way off here. Any help would be appreciated.
In order to keep communication between ViewModels in a loosely coupled manner, you could simply use the EventAggregator and raise an event from the Command Button implementation, which would be then handled by the TabItemViewModel.
The solution you mentioned by adding one ViewModel into another would not be ideal as these components would end up working with tight coupling and defining an incorrect situation as Views/ViewModels would not depend on another View.
Therefore, to accomplish the EventAgregation approach, you would need to receive the EventAggregator from the container throw constructor on the View/ViewModel where the button is clicked, and on each one of the ViewModels you would want to subscribe to that event setting the IsSelected property inside the EventHandler method.
You could subscribe to the "GiveFocusEvent" event and handle it on the ViewModels which would set their IsSelected property as shown below:
public TabItemViewModel(IEventAggregator eventAggregator, ..){
...
GiveFocusEvent setFocusEvent = eventAggregator.Get<GiveFocusEvent>();
setFocusEvent.Subscribe(SetFocusEventHandler, ThreadOption.UIThread);
}
public void SetFocusEventHandler(){
// change IsSelected property value..
}
The Event would be published from inside the Button's CommandHandler method as follows:
this.eventAggregator.GetEvent<GiveFocusEvent>().Publish();
Notice that you would need to create and make your "GiveFocusEvent" event class inherit from CompositePresentationEvent:
public class GiveFocusEvent : CompositePresentationEvent<string>{}
I hope this helped you,
Regards.
I am working with a WPF application and using my own architecture that strongly resembles a M-V-VM /MVC. I have controllers for each of the views, and I have ViewModels that are bound to the Views.
For Example, I have a ToolBarView that has a corresponding ToolBarViewModel, and ToolBar Controller.
I am using notifications to update all the views so that they do not need to reference each other, but that is not relevant for my question.
Each of the Views is listening for an event to trigger in their controller when the model has been updated. In the ToolBarView this looks like the following.
/*In Constructor in ToolbarView*/
Controller.Updated += UpdateView
/*Event Handler in ToolbarView*/
private void Updateview(object sender,EventArgs e)
{
DataContext = Controller.Model;
//Other Updating if needed
}
If not obvious, what the above code is doing is saying that when the Controller fires the Updated event, to invoke the UpdateView(object sender,EventArgs e).
The problem that I am experiencing is that the first time that the UpdateView() is invoked, everything is working fine, but when it is invoked the second time, the DataContext seems to still be bound to the original Controller.Model.
It seems almost as if I have to release the DataContext, or refresh it in order for it to be bound to the Model every time.
The Controller is performing operations on the Model, and therefor when the UpdateView() is invoked, it needs to display the newly assigned values on that model.
Is there some way I need to refresh the DataContext, or is there a different way I need to do this?
If you are assigning the DataContext to the same instance of your model, the in effect it won't "change". WPF expects objects to notify it when their state changes, either through DependencyProperty properties or by implementing INotifyPropertyChanged.
So if you do something like:
MyObject o = new MyObject();
o.MyString = "One";
this.DataContext = o;
// ... some time later ...
o.MyString = "Two";
this.DataContext = o;
Assuming MyObject doesn't implement INotifyPropertyChanged, then the second assignment to DataContext is effectively worthless. You would have to set DataContext to null, then assign your object again to have it "refresh".
But your best bet in general would be to implement INotifyPropertyChanged. This would end up being much more efficient, as only the property that actually change would need to be updated in the UI.
I've been doing the best I can to try to stay true to the separation recommended by the MVVM pattern. One thing I haven't figure out how to do correctly has to do with initializing my UserControls.
My most recent example of this has to do with a library that I wrote to talk to some low-level hardware. That assembly happens to have a UserControl that I can simply drop into any GUI that uses this hardware. All that is necessary for it to work is to set a reference to the object that has access to the low level methods.
However, that's where my problem lies -- currently, the UserControl is added to the GUI via XAML, where I define the namespace and then add the UserControl to my window. Of course, I have no control over its creation at this point, so the default constructor gets called. The only way to set the necessary reference for hardware control involves calling a method in the UC to do so. The ViewModel could feasibly call a method in the Model, e.g. GetController(), and then call the method in the UserControl to set the reference accordingly. The GUI can pass a reference to the UserControl to the ViewModel when said GUI creates the ViewModel, but this violates MVVM because the ViewModel shouldn't know anything about this control.
Another way I could deal with this is to not create the UserControl in XAML, but instead do it all from code-behind. After the ViewModel gets initialized and retrieves an initialized UserControl (i.e. one that has the low-level object reference set), it can set the Content of my Window to the UserControl. However, this also violates MVVM -- is there a way to databind the Content of a Window, TabControl, or any other element to a UserControl?
I'd like to hear if anyone has had to deal with this before, and if they approached it the first or second way I have outlined here, or if they took a completely different approach. If what I have asked here is unclear, please let me know and I'll do my best to update it with more information, diagrams, etc.
UPDATE
Thanks for the responses, guys, but I must not have explained the problem very well. I already use RelayCommands within the UserControl's ViewModel to handle all of the calls to the hardware layer (Model) when the user clicks in the control in the UserControl itself. My problem is related to initially passing a reference to the UserControl so it can talk to the hardware layer.
If I create the UserControl directly in XAML, then I can't pass it this reference via a constructor because I can only use the default constructor. The solution I have in place right now does not look MVVM-compliant -- I had to name the UserControl in XAML, and then in the code-behind (i.e. for the View), I have to call a method that I had added to be able to set this reference. For example, I have a GUI UserControl that contains the diagnostics UserControl for my hardware:
partial class GUI : UserControl
{
private MainViewModel ViewModel { get; set; }
public GUI( Model.MainModel model)
{
InitializeComponent();
ViewModel = new MainViewModel( model, this.Dispatcher);
ViewModel.Initialize();
this.DataContext = ViewModel;
diagnostics_toolbar.SetViewModel( ViewModel);
user_control_in_xaml.SetHardwareConnection( model.Connection);
}
}
where the outer class is the main GUI UserControl, and user_control_in_xaml is the UserControl I had to name in the GUI's XAML.
Looking at this again, I realize that it's probably okay to go with the naming approach because it's all used within the View itself. I'm not sure about passing the model information to user_control_in_xaml, because this means that a designer would have to know to call this method if he is to redo the GUI -- I thought the idea was to hide model details from the View layer, but I'm not sure how else to do this.
You will also notice that the main GUI is passed the Model in the constructor, which I assume is equally bad. Perhaps I need to revisit the design to see if it's possible to have the ViewModel create the Model, which is what I usually do, but in this case I can't remember why I had to create it outside of the GUI.
Am new to MVVM myself but here's a possible solution:
Create a property in your VM that is of the object type (that controls the hardware) and bind it to an attached property on your UserControl. Then you could set the property in your VM using dependency injection, so it would be set when the VM is created. The way I see it, the class that talks to the hardware (hardware controller) is a service. The service can be injected to your view model and bound to your UserControl. Am not sure if this is the best way to do it and if it is strict enough to all the MVVM principles but it seems like a possible solution.
if your question is: How do i show my viewmodel in the view? then my solution is always using viewmodelfirst approach and datatemplates.
so all you have to do is wire up your viewmodel via binding to a contentcontrol.content in xaml. wpf + datatemplates will do the work and instantiate your usercontrol for your viewmodel.
You are right, the ViewModel shouldn't know about anything in the View - or even that there is such a thing as a View, hence why MVVM rocks for unit testing too as the VM couldn't care less if it is exposing itself to a View or a test framework.
As far as I can see you might have to refactor things a little if you can. To stick to the MVVM pattern you could expose an ICommand, the ICommand calls an internal VM method that goes and gets the data (or whatever) from the Model, this method then updates an ObservableCollection property of the data objects for the View to bind to. So for example, in your VM you could have
private ICommand _getDataCommand;
public ICommand GetDataCommand
{
get
{
if (this._getDataCommand == null)
{
this._getDataCommand = new RelayCommand(param => this.GetMyData(), param => true);
}
return this._getDataCommand;
}
}
private void GetMyData{
//go and get data from Model and add to the MyControls collection
}
private ObservableCollection<MyUserControls> _uc;
public ObservableCollection<MyUserControls> MyControls
{
get
{
if (this._uc == null)
{
this._uc = new ObservableCollection<MyUserControls>();
}
return this._uc;
}
}
For the RelayCommand check out Josh Smiths MSDN article.
In the View you could either call the ICommand in the static constructor of your UC - I am guessing youwould need to add an event in your class for this - or call the ICommand from some sort of click event on your UC - maybe just have a 'load' button on the WPF window. And set the databinding of your UC to be the exposed observable collection of the VM.
If you can't change your UC at all then you could derive a new class from it and override certain behaviour.
Hope that helps a bit at least, like I say, have a look at Josh Smiths MVVM article as he covers the binding and ICommand stuff in there brilliantly.
If you set the DataContext of the Window or UserControl containing thisUserControl to the main view model, the user control can call SetHardwareConnection() on itself in its Loaded event (or DataContextChanged event handler).
If that's not possible because you're saying the UserControl is 'fixed', you should derive from it or wrap it up in another UserControl, which would serve as a MVVM 'adapter'.
(In order to bind the window: you could make the MainViewModel a singleton with a static Instance property and use DataContext="{x:Static MyClass.Instance}". A nice way to get things going quickly)
Note; this is based on my understanding that MVVM works because of Bindings.. I always bind the control to a ViewModel, not pass a ViewModel as a parameter.
Hope that helps!