ViewModel calling a method of a user control (web browser) - c#

I am trying to program in MVVM and I have the following use case:
A TextBox's text is bound to a property in the VM
A Button is command bound to a relay command
When the user presses the Button, the web browser's Navigate(url) method is called with the URL being the text in the TextBox
Above is the use case I want to create, but 1 and 2 is possible using the MVVM design pattern, but I could not find an adequate way to invoke the browser's Navigate() method. First of all, is it possible to call a method of a control from VM (please let me know if there is a way)? And in the above use case, what would be the appropriate way to structure the program if it is not possible?
Thanks

You could do the following:
Add a property MyUrl to your ViewModel
Bind MyUrl to your WebBrower's Source property
Make sure the property implements INotifyPropertyChanged. Then your Xaml:
<WebBrowser Source="{Binding MyUrl}" />
What if you REALLY wanted to call a UI method from the ViewModel?
If you ever do run into a situation where you absolutely need to call a method on a UI control for instance, you can hook up events on the ViewModel and then your UI registers to this event and does something UI specific...
VM code...
//... some VM logic
EpicNavigateEvent(url) // raise event, allowing UI to handle how
In your code-behind on your view (this is the part where some MVVM purests freak), you could register the event:
myVm.Navigate += doSomeNavigation;
...
public void doSomeNavigation(string url)
{
// call Navigate
}
I've successfully used this approach for applications where we have a single ViewModel layer and multiple technologies hooked up the views (WinForms, WPF and Asp.Net).
If you're looking for something more elegant, have a look at the User Interaction Patterns on MSDN.
The concept is the same though: Call something on the VM and the View is handles it appropriately.
Common scenarios for this type of approach is want to show a message to the user from the VM. Your VM should raise an event saying: ShowMyMessage("You're awesome"), then your UI is notified and handles it: MessageBox.Show(msg) or whatever.
As long as you stick to there rules you should be golden:
ViewModels should NOT be concerned about UI code
Views must ONLY handle the presentation of the data provided by your ViewModels.
Don't overcomplicate it. KISS...

Related

Passing Parameters to Shell Viewmodel Using Unity BootStrapper With Prsim

I've created a dialog service using interfaces to show custom dialog/confirmation boxes (I know that Prism comes with this ability but the dialog boxes don't appear to be customizable and don't match my layout/color scheme). The dialog service has a register function that registers the dialog view type with dialog viewmodel type in a dictionary. This is done so that a viewmodel can be passed in and in a loosely coupled fashion, an instance of the needed view can be created. The code looks like this:
private readonly IDialogService dialogService = new DialogService(Application.Current.MainWindow);
// Registers the confirmation window viewmodel with the confirmation window view
dialogService.Register<ConfirmationWindowViewModel, ConfirmationWindow>();
so my initial thought was to try to create this in the unity bootstrapper (because of the registration passing in views and viewmodels) but I can't seem to find a way to do that and pass in the dialog service.
I must note that the constructor for the main window viewmodel also injects the region manager and the event aggregator for Prism; I had tried creating an instance in the bootstrapper and registering the instance but the creation of the region manager vs the injection causes errors. If I declare and initialize the dialog service in the main window viewmodel it of course works but from my understanding of MVVM we don't want the viewmodels to have any knowledge of the views so I'm trying to find another way to do it, without breaking IoC for region manager and event aggregator.
I am new to MVVM and Prism/Unity so my grasp of these concepts isn't fully solidified yet.
I know that Prism comes with this ability but the dialog boxes don't appear to be customizable and don't match my layout/color scheme
You can create whatever you like as dialog, just derive from PopupWindowAction and override CreateWindow (and other methods as needed) to create the dialog you always wanted.
In case anyone sees this later and is curious, my end decision was to get rid of the 'Register' function altogether in favor of a solid convention instead.
Previously, I would use this function and kept a dictionary of all the registered views/viewmodels:
dialogService.Register<ConfirmationWindowViewModel, ConfirmationWindow>();
this would register take the and store them in the dictionary so I could later pass in a viewmodel and create an instance of the appropriate confirmation message view. Instead I removed all code regarding this part of the solution and replaced it with some reflection mixed in with naming conventions.
Step 1: Ensure all views are named with the suffix View at the end.
Step 2: Ensure all viewmodels are named with the suffix ViewModel at the end.
Step 3: Ensure these are all in appropriately named namespaces (views in views namespace and viewmodels in viewmodels namespace).
(most of this ^^ is done anyway)
Final Step: Replaced dictionary with this code:
var viewTypeName = viewModel.GetType().AssemblyQualifiedName.Replace("Model", "");
var viewType = Type.GetType(viewTypeName);
in the dialog interface. Now, no matter what viewmodel is passed in, it will pull the appropriate view with less code and no necessary linking as was done before.

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.

Caliburn.Micro screen transition via conductor

I have a Caliburn.Micro shell (i.e., an empty XAML view to contain other views) rendered by a Conductor ViewModel. From there I open a Screen via:
ActivateItem(...)
Usually from the newly displayed dialog the user can perform some operations and click buttons (OK, Cancel, Build....) which should each transition to another screen (in the shell).
public MyDialog : Screen
{
public void Ok()
{
// TODO: Somehow tell the conductor or called of this class about this action.
}
}
What are good ways to achieve these kind of dialog action/message screen transitions?
Simple .NET events are possible -- Wouldn't that be a bad idea?
CM IEventAggregator should also work by changing the view
Checking from the shell Conductor the ViewModel result once it has been closed via TryClose() -- Should be possible, just don't know how to achieve this in CM.
Reference the shell Conductor instance from that screen (via IoC or directly) -- That seems strong coupling.
Could you please advise.
My preferred approach is to use the EventAggregator to facilitate messaging between VMs.
This works especially well when you have multiple windows which are listening for a certain type of event (e.g. a Visual Studio style interface with multiple tool windows which may show context sensitive properties), however it sounds a little overkill for this implementation. Of course the advantages are still a good loose coupling between VMs and a lack of events (which is a good thing!)
It sounds like you want a modal dialog to popup and present an option, and then activate another screen once the first one has returned.
You can attach an event handler to the Deactivated event in the child VM which will fire when an item deactivates. It also passes a boolean in the arguments to notify if the item which deactivated was closed - you can check for this and activate the corresponding screen in your conductor.
e.g.
this.Deactivated += new EventHandler<DeactivationEventArgs>(WorkspaceViewModel_Deactivated);
void WorkspaceViewModel_Deactivated(object sender, DeactivationEventArgs e)
{
if(e.WasClosed) // raise some event
}
Then pass an event up to the conductor, I wouldn't really go the event route for this. This couples the VMs one-way so it may not be the most flexible solution
The alternative is to fire a message via the event aggregator to tell the conductor it needs to open a different window when the child VM closes. The same method can be used but it's decoupled
this.Deactivated += new EventHandler<DeactivationEventArgs>(WorkspaceViewModel_Deactivated);
void WorkspaceViewModel_Deactivated(object sender, DeactivationEventArgs e)
{
if(e.WasClosed) MainConductor.EventAggregator.Publish(new ActivateWindowMessage(typeof(SomeVM));
}

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