Direct calls from View to ViewModel. Can DataContext be changed on itself? - c#

Sometimes, i need to do some calls from View to VM. I know, that it is not MVVM style, but still. Should i always re-check DataContext to be my VM (in case it can be changed by re-activation from tombstoning or something like that), or it is enough to store in once?
var vm = DataContext as MyViewModel;
if (vm == null) return;
vm.DoSomething();
DataContext is set via Mvvm Light Locator
DataContext="{Binding MyViewModel, Mode=OneWay, Source={StaticResource ViewModelLocator}}"

It always is safe, when it's limited to a page. Whether in constructor, Loaded event handler or OnNavigatedTo, you can create objects and never check them again - either the page was kept in memory with all the objects, or the constructor, Loaded and OnNavigatedTo were called again upon re-activating from tombstoning. Problems with tombstoning occur mostly in cases of one page relying on the fact, that other page created something.

I use that sometimes, like that:
private MyViewModel viewModel;
When is the page loaded, then add value to that field
viewModel = (MyViewModel)DataContext;
Than I can use it anytime in my view after it.
I know it is not MVVM, but still ;)

Related

XAML x:bind oneTime on initially null property still works. Why?

x:Bind defaults to OneTime, which updates the target UI with the data when the Page's Loading event triggers the generated code's Initialize function.
I have a Page with a ViewModel property. This ViewModel class implements INPC for its properties. The data for the viewModel is loaded asynchronously, only after the page is loaded. So on Page initialization, and subsequently the generated code initialization, the UI target using x:Bind will have null data.
Since it is OneTime, it shouldn't change unless I manually call Update(which I don't).
So why does my x:Bind UI work?
The following is some simplified code snippets.
<Page x:Name="MyPage" x:Class="MyProject.Pages.MyPage">
<Button Command="{x:Bind ViewModel.GoToAnotherPageCommand}">
public sealed partial class MyPage : Page
{
public MyPageViewModel ViewModel { get; set; }
public MyPage()
{
this.InitializeComponent();
}
// called by an event bound to a Frame's Navigated, which all pages use
public void OnNavigatedTo()
{
this.ViewModel = new MyPageViewModel();
}
}
public class MyPageViewModel : INotifyPropertyChanged, INotifyPropertyChanging
{
// GoToAnotherPageCommand is an INPC property and its set in the constructor
The reason that your command works fine is because OnNavigatedTo will be called before the command instantiation. This means by the time the code tries to set the command, the ViewModel has already been instantiated and is no longer null.
To prove my point, first go open the file under the following path(could be ARM or *x64 depending on which platform you are running on) -
obj/x86/Debug/MyPage.g.cs
This is basically the code-generated file that hooks up all the x:Bind stuff for your page.
Now put a breakpoint at where the command is set. In my case, it's a method called Set_Windows_UI_Xaml_Controls_Primitives_ButtonBase_Command. Then put another breakpoint at OnNavigatedTo.
Now run the app, you will see that the OnNavigatedTo method gets called first.
If your page's NavigationCacheMode is set to Disabled, this behavior makes OnNavigatedTo the ideal place to instantiate x:Bind bindings so the page only uses memory to create these new objects when the user actually navigates to it, instead of doing everything inside the page constructor.
Don't do this inside the Loaded event of the Page though. Because it will get called after the command instantiation. You can try the following code to instantiate the ViewModel, and the result is very different(your command will not work).
public MyPage()
{
InitializeComponent();
Loaded += (s, e) => ViewModel = new MyPageViewModel();
}
The compiled binding system (x:Bind) is smart enough to check for initial null values and not consider them the actual value you wish to bind. It will wait for the first non-null value and bind that value.
This is by design, as binding to an initial null value is (almost) never the intention of the binding.
I didn't find the source of this information, but I believe it was in the Build talk detailing the x:Bind system in 2015.
Updated:
As Justin mentions in the comments below and in his own answer, the binding will not work if the view model is set after the binding operation happens.
I believe this is because the binding terminates when it encounter a null reference in the property chain, but I haven't tested this, so I might be incorrect.

WPF & MVVM Light- Pass object into new window

I would like to learn the most proper way to go about this: I have a Listview in my GameView that is bound to an ObservableCollection<Adventurer>. Upon double-clicking on a cell, I need a new window (or something else if anything is more appropriate) to open and display data about the correct Adventurer according to the cell. So far I haven't been able to. This is what I have so far (it's not much, but nothing I've tried has worked).
The trigger/command in my ListView in GameView.xaml
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=ShowAdvCommand}"
CommandParameter="{Binding ElementName=AdvListView,
Path=SelectedItem}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
And the command in GameViewModel.cs
ShowAdvCommand = new RelayCommand<Adventurer>(p =>
{
System.Windows.MessageBox.Show(p.Name);
});
The MessageBox is just there to confirm that Eventtocommand was working.
I essentially need a container that will take in the correct Adventurer as a parameter after double-clicking a Listview cell and allow me to display data specific to that instance. I would also prefer to stick to something MVVM-friendly.
Any advice would be greatly appreciated.
Update: I may have made a little progress:
GameViewModel:
ShowAdvCommand = new RelayCommand<Adventurer>(p =>
{
AdventurerView adv = new AdventurerView(p);
adv.Show();
});
AdventurerView:
public partial class AdventurerView : Window
{
Adventurer adv;
public AdventurerView(Adventurer adv)
{
this.adv = adv;
InitializeComponent();
}
}
Now I need to figure out how to make this work in XAML, databinding and such.
Update: ...and then I realized that this completely goes against MVVM. Does anybody have any advice?
Update: Would MVVM Light's messenger help me here? I've been tinkering with it but haven't gotten it to work.
Update: This question is still up in the air. I tried the Prism approach but there was some conflict between Prism and MVVM Light that caused more trouble than it was worth. I'm open to any ideas that are compatible with MVVM Light and the MVVM pattern in general.
Update: Also, I would like to do this in a way where multiple popups can exist concurrently, if possible.
In a similar situation, I've used MvvmLight's Messenger, and it worked really well. On double click, send a message from your viewmodel containing the entity you want to pass. Somewhere you will need to register to receive the message, depending on how you have set up your views and viewmodels to be activated.
You could register to receive the message in your MainPage.xaml, and either pass the entity straight to the view's constructor, or access the view's DataContext via an interface to pass the entity, depending on whether you're using a viewmodel in you childwindow. E.g.
AdventurerView adv = new AdventurerView();
IEntityViewModel vm = adv.DataContext as IEntityViewModel;
vm.SetCurrentEntity(entity);
adv.Show();
The IEntityViewModel might look like the following:
public interface IEntityViewModel<T> where T : class
{
void SetCurrentEntity(T entity);
}
The viewmodel would implement this interface:
public class AdventurerViewModel : IEntityViewModel<Adventurer>
{
public void SetCurrentEntity(Adventurer entity)
{
// Do what you need to with the entity - depending on your needs,
// you might keep it intact in case editing is cancelled, and just
// work on a copy.
}
}
As you've pointed out, proper MVVM wouldn't instantiate the view and pass the view model in through the constructor. You'd be better off binding the ViewModel to the View and there are many different ways of doing it.
One pattern that has emerged is a concept known as a "screen conductor". This is a top level ViewModel or controller that handles which ViewModel represents the main window. Again, many different ways to do this. For example, the ViewModel could raise a standard .net event that the Screen Conductor handles. You could use an message passing system like Caliburn.Micro's EventAggregator or MVVM Light's Messenger. I think MEFedMVVM also has an event aggregator to accomplish this as well.
Caliburn.Micro also has a WindowManager that you can pass in your ViewModel and have it automatically find the corresponding View and manage the window lifetime.
Lots of options. Find the one that works the best for you.
This is a nice case for Prism's InteractionRequest. Essentially, you have an InteractionRequest object on your ViewModel that you raise when you double click (inside your double click command). Your view has an Action on it that handles the Raised event and shows the new view. You pass a new ViewModel to that interaction and that's the DataContext for the window that'll display. Here's some good information to get you started. This is how I display all child windows in my application.

Should Controls Be in the ViewModel?

I am trying to learn MVVM and using MVVM light with my phone application but I am kinda confused on how to access some information.
I am trying to not to use code behind events as much as possible as that does not seem to be the true MVVM way but I ran into a problem I don't know how to do.
I am using Google authentication and I am checking the Naviagted Event after each browser load.
public ICommand BrowserNavigated
{
get
{
return new RelayCommand<NavigationEventArgs>(e =>
{
var d = e;
var a = d;
});
}
}
However I also need the actual object control(I want to access the html that page is spitting back out) but I don't know how to get it.
private void wbGoogle_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
var d = e;
var d2 = d;
}
in the above code I could just cast "sender" to a web browser object but with me doing it the MVVM way I don't know how to access it.
Should I have another property or something for WebBrowser in my ViewModel?
In MVVM, code behind is allowed, but perhaps bindings are preferred. However, having GUI controls / events (hard coupling) is not allowed.
There may be ways to avoid code behind, but if you have to handle an event, get the data out of the event and set the property on your ViewModel in your code behind, then that is a better way to do it than adding UI code to your ViewModel which is clearly not keeping with MVVM.
Perhaps you can create some sort of EventTrigger which sets a property for a webbrowser that you can databind to create a re-usable Trigger that you can set in your XAML? (There's probably lots of ways to be clever on how to avoid code behind and create reusable code)
Your ViewModel should be totally unaware of the View or particular controls. Whether or not to keep the codebehind of your view clear or not, is a matter of religion.
If you want to keep it clean, which I recommend whenever possible, there are a number of concepts, which allow you to do so.
First, you need to design your View/ViewModel relationship in a way, that all data relevant for the ViewModel is present 'at all times' in the ViewModel or can be passed to the ViewModel via CommandParameter of a ICommand. In your case, if the page of the Webbrowser is controlled by (i.e. might be set from) the ViewModel, the ViewModel should hold a property, which is bound to the Source property of the browser. If the ViewModel just needs to 'know' the Uri when the BrowserNavigated is executed, just pass it as a CommandParameter.
Secondly, for your particular case, you want to execute a command on the ViewModel, when the Navigated event of the WebBrowser is raised. As always, there are several options. I prefer the option which comes with the framework: The EventTrigger in System.Windows.Interactivity allows you to relay any event of any control to commands via bindings.
This way, the Uri can be set from the ViewModel:
<WebBrowser Source="{Binding BrowserPageUri}" Name="wbGoogle">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Navigated" >
<i:InvokeCommandAction Command="{Binding BrowserNavigated}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</WebBrowser>
This way, you can handle the Uri as parameter of the command:
<WebBrowser Name="wbGoogle">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Navigated" >
<i:InvokeCommandAction Command="{Binding BrowserNavigated}"
CommandParameter="{Binding Source, ElementName=wbGoogle}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</WebBrowser>
Of course, this only lets you access the Uri of the page in the WebBrowser, not the page itself. If you need to bind to the page object itself, you need to extend the WebBrowser, with an attached property, that makes the Document property bindable. This is quite straight-forward:
Attached Document property for WebBrowser
After attaching this property to your WebBrowser, you can define the bindings of the WebBrowser just as in the above code, just with the attached property, instead of the Source property.
Note, that the syntax for binding to an attached property would be:
{Binding Path=(WebBrowserExtentions.Document)}
MVVM can be great for data binding and by using toolkits like MVVMLight, events that deal with user interactions can also be neatly dealt with.
However sometimes, controls like WebBrowserControl or ApplicationBar present a challenge to this. They can be difficult or impossible to bind with event triggers, or have complex behaviours. In these cases it is simpler if you handle the process of getting information from the control in your View code behind and send a simple message down to the VM.
Sure you could create an event that updates a property, write an Attached Property, or maybe use a 3rd party library; and there are cases that warrant that approach.
In your example I personally would use code-behind to handle the Navigated event and send down a message (or a method call on your VM) containing everything the VM wants in one go.
For instance:
private void wbGoogle_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
var vm = (TypeOfMyViewModel) this.DataContext;
//... read your HTML, get URL etc ...
vm.WebBrowserNavigatedTo(url, html, loadTime);
}
Similarly if an event raised from your VM would cause many things to happen in your View there comes a point where it is simpler to send a message or event to your View and let the View update the controls in code.
The key thing is keep the roles of MVVM distinct, e.g. to avoid a direct dependency of the ViewModel on the View. Interfaces can help here well as Messaging that comes with MVVMLight and its alternatives.

MVVM MEF WindowFormHost

I am currently trying to design an application that loads viewmodels through MEF imports.
So far so good, I navigate from viewmodel to viewmodel, having loaded each vm datatemplate through dictionaries.
Each time I navigate, I modify the content of the main contentPresenter in my Shell (MainWindow).
One of the viewmodel allows me to display a WindowFormHost for an activeX control (such as acrobat reader for example). Since WindowFormHost does not allow binding, I created the windowFormHost in the viewmodel and binded it to a ContentPresenter in the view.
And here is where it fails : when coming back to the same viewmodel, the view is created again... throwing a “Element is already the child of another element.” error.
How can I prevent that ? Should I unload WindowFormHost when view is reloaded ? Or Can I keep view instances so that I keep only one instance for each view and let data binding update controls ? (It looks better for memory consumption).
Thanks for your help !
[EDIT]
Loaded dictionary :
<DataTemplate x:Shared="False" DataType="{x:Type vm:DAVPDC3DVIAControlViewModel}">
<vw:MyUserControl />
</DataTemplate>
View :
<DockPanel>
<ContentControl Name="WFH3DVia" Content="{Binding Path=Control3DVIA, Mode=OneWay} </ContentControl>"
<!--<WindowsFormsHost Name="WFH3DVia"></WindowsFormsHost>-->
</DockPanel>
VM (singleton, mef module) :
[Export(typeof(IDAVPDC3DVIAControl))]
public partial class DAVPDC3DVIAControlViewModel : ViewModelBase, IViewModel, IPartImportsSatisfiedNotification
VM (main window)
[Export]
public class MainWindowViewModel : ViewModelBase, IPartImportsSatisfiedNotification
// CurrentUC binds main widow view to controller active viewmodel
public IViewModel CurrentUC
{
get
{
return myAddinManager.CurrentVM;
}
}
Main view :
Controler (displays module on event) :
private void ModuleReadyEventAction(string iModuleName)
{
if (null != this.Modules && this.Modules.Count() > 0)
{
foreach (var item in Modules)
{
IBaseModule ibasemodule = item as IBaseModule;
if (null != ibasemodule)
{
Type tp = ibasemodule.GetType();
if (0 == tp.Name.CompareTo(iModuleName))
{
CurrentVM = ibasemodule.GetViewModel();
break;
}
}
}
}
}
I'm also working on a project in WPF using Prism v4 and MVVM (except I'm using Unity). I also have at least two controls that I need to use which are Windows Forms controls that must be hosted in a WindowsFormsHost. Let me explain my thoughts on the process..
It seems to me, that you are trying to avoid any code in your View's code behind. That's the only reason I can think of that you are moving your WindowsFormsHost into your ViewModel. I think that this is fundamentally the wrong approach. The WindowsFormsHost exists for the reason of displaying a graphical Windows Forms control. Therefore, it belongs in the view!
Now, I understand the appeal of DataBindings. Trust me, I've wanted to able to DataBind many parts of my WindowForms control. Of course, to accept a WPF data binding the property must be a dependency property on a dependency object. The easiest solution, which is not unreasonable, is to simply add the code to configure your windows forms control in the code behind for your view. Adding your UI logic into your ViewModel is an actual violation of the MVVM design pattern, while adding code behind is not. (And in some cases is the best approach)
I've seen possible hacks to try to get around this limitation. Including using "proxies" which inject a databinding, or perhaps extending WindowsFormsHost and adding DependencyProperties which wrap a specific hosted control's properties, or writing classes using reflection and trying to throw in windows forms bindings. However, nothing I've seen can solve the problem completely. For example, my windows forms control can contain other graphical components, and those components would need to support binding as well.
The simplest approach is to simply synchronize your view with your viewmodel in your view's code behind. Your view model can keep the file or document that is open, filename, title, etc., but leave the display and display related controls up to the View.
Last, let me comment more directly on your question. I would need to see how you are registering your View and ViewModel with the MEF Container and how you are navigating to understand why you are receiving that error. It would seem to me that either your view or view model is getting created more than once, while the other is not. Are these registered as singleton types? Regardless, I stand by what I said about not including the WindowsFormsHost in your ViewModel.

Prism ConfirmNavigationRequest() called twice when DataContext = this

I'm using Prism and my views implement IConfirmNavigationRequest in order to enable them to perform validations and cancel the navigation if required.
My problem is that I have several views which don't use MVVM, and define DataContext = this. Doing so causes Prism to call my view's ConfirmNavigationRequest() twice, which means I ask for the user's response twice.
Basically what's going on is this:
Prism checks if the view implements IConfirmNavigationRequest and calls ConfirmNavigationRequest() on it if it does.
The user is asked whether he'd like to continue.
The user clicks OK and ConfirmNavigationRequest() returns true.
Prism checks if the viewmodel (in my case, it's the view again) implements IConfirmNavigationRequest and calls ConfirmNavigationRequest() on it if it does.
The user is asked again whether he'd like to continue.
As you can see, Prism asks my view for confirmation twice because it queries both the view and the viewmodel.
So my question is, how can I prevent this from happening or how can I detect which call is which so I can ignore one of them? I thought about investigating the continuationCallback parameter, but I don't like this solution so much since it's not unlikely it'll break in the next versions of Prism.
The best solution I got so far is the one I got from DCherubini at Prism's forum, which suggests that I won't set the view's DataContext on my UserControl, but use an inner element that will hold the view, and set the DataContext for it instead:
<UserControl>
<Grid x:Name="grid">
...
</Grid>
</UserControl>
grid.DataContext = this;
instead of
<UserControl x:Name="uc">
</UserControl>
uc.DataContext = this;
This should work, but it means I need to change each view individually. A solution that doesn't require making changes to the views would be nicer.

Categories