MVVM: Bring control into view - c#

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!

Related

Specify custom guard property for Caliburn.Micro Action Message

Using Caliburn.Micro, I can have a guard property that defines the IsEnabled state of e.g. a button:
<Button cal:Message.Attach="DoSomething" Content="Do it"/>
And in the ViewModel
public bool CanDoSomething { get {...} }
public void DoSomething() {... }
My problem is that I have a lot of buttons and corresponding actions in a ribbon menu. Almost all buttons have the same guard logic, something like "enabled if the application is not busy with some long running operation". My VM tends to get cluttered with many "CanXYZ" properties that all have the same content. Of course all guard properties can delegate to one single property containing the actual logic, but is there a way to avoid all those single guard properties? Something like
<Button cal:Message.Attach="DoSomething" cal:Message.Attach.Guard="IsAppIdle" Content="Do it"/>
Thanks in advance...
Not impossible to do it but then DoSomething is hooked up in the ViewModel and the way Caliburn Micro works is that it looks for the properties in the datacontext. I can see this ending up with a static class that it can invoke when RaiseCanExecute is called.
Attach is also an attached property. You'll have to change the property to a class that contains Guard so you'll have to end up writing what I suggested or extending caliburn micro's Message class.

How to show a View from a ViewModel and also set the data context of that view in WPF with MVVM

How to open a new View(Dialog) from a view model command and also set the new view's data context with its view model. this question is bothering me a lot, although there has been so many question on this but I could not get satisfied with any of the answer so far.
So, suppose:
I have a start up dialog called MainView and I show this dialog and set its data context in App.xaml.cs (OnStartUp) method. In MainView, there is a button called "open a new dialog" and this button's command is bind with a delegate command in MainViewModel. So, when user hits this button, then command calls the execute method.
Let's say command in MainViewModel which is bind with button in view is as following
public ICommand ShowNewDialogCommand
{
if(this._showNewDialogCommand == null)
{
this._showNewDialgoCommand = new DelegateCommand(ShowDialogFromVM);
}
}
private void ShowDialogFromVM()
{
}
And let's say the new dialog that I want to show is ListAllStudentsView and its ViewModel is StudentsViewModel. So, what are the various approaches of showing this dialog without breaching the MVVM pattern? And what are the merits and demerits of each approach?
First, we need to create a view (somewhere) with its datacontext set. Easy enough, we instantiate the view and either pass it the view model (assuming the view sets its data context in its constructor) or set it manually. The view could also have declared the view model in XAML if we so desired.
Method 1:
Window dialog = new ListAllStudentsView(new StudentsViewModel());
Method 2:
Window dialog = new ListAllStudentsView();
dialog.DataContext = new StudentsViewModel();
Method 3:
<Window.DataContext>
<local:StudentsViewModel/>
</Window.DataContext>
Now we need to put this code (and the associated dialog.ShowDialog() somewhere). I see two options, right in the command's execute function, or in the view's code-behind (triggered by an event raised by the command's execute function like "RequestDialog").
I prefer the first, even though it doesn't adhere as rigidly to MVVM because it is a lot simpler, less code, and easier to manage. If you want to be very strict about adhering to MVVM however, I would have the ViewModel raise an event like "RequestDialog" in the command function that the view listens to and runs the constructor and ShowDialog() function.

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.

WPF event handling between objects

I'm pretty new to C#/WPF, coming from Obj-c background. I'm not really sure how this works in terms of object oriented design, and how different classes see each other.
So I have a large view (MainView) that has some custom plots and a datagrid on the bottom (Datagrid is in its separate xaml and has .cs file behind it). There is a custom object that gets added to the plots that when you drag it, the datagrid gets updated (by using dataGrid.ScrollIntoView). The code for the ScrollIntoView is in the xaml.cs file of the Datagrid.
To me this makes sense since the MainView has all the components and "sees" all the objects, so when the event handler of the dragWindow gets called, then the MainView asks for the DataGrid, and calls its method to update its column position. (This is the way I understand it, please feel free to correct me if I'm wrong).
Now I want to go the other way as well. So that if I update the scrollbar horizontally, then the dragwindow in the MainView will get updated. This does not make as much sense to me. I can create an event handler in the xaml.cs of the datagrid. But it does not see the dragWindow in the MainView right? So I'm not quite sure how to start implementing this functionality. Any help is always appreciated. Thanks!
Your grid control should expose an event to notify any consumers (MainView in this case) that scrolling has taken place.
public class YourGridControl
{
public event EventHandler GridScrolled;
}
MainView can then attach a handler to this event in the designer or in the code:
gridCtrl.GridScrolled += OnGridScrolled;
private void OnGridScrolled(object sender, EventArgs e)
{
// Logic here
}

User Interface Interaction with the MVVM

I did some googling and didn't find an answer to this puzzle.
Provided you have the following:
MySuperView
MySuperViewModel
MySuperView has two textboxes both bound to string properties on the ViewModel
and your using a DelegateCommand to bind your 'Save' button to the ViewModel using syntax such as:
ViewModel:
this.SaveOrderCommand = new DelegateCommand<object>(this.Save, this.CanSave);
View:
Command="{Binding SaveOrderCommand}"
How do you deal with UI Elements to make the User Interaction more pleasing. For example, lets say some lower level failure occurring during the save action of the DelegateCommand and you would like to trigger the tooltip of one of the TextBoxs. How would this typically occur?
I'd like to stick with as clean code-behind as possible but I am not adverse to putting UI specific code in there.
I would recommend that your ViewModel implement IDataErrorInfo so you can take advantage of validation stuff in WPF. You don't need to wait until someone clicks the save button, once the textbox gets updated it will be validated.
public string this[ColumnName]
{
if (Column == "TextProperty")
{
if(!ValidateTextProperty())
return "TextProperty is invalid";
}
}
void Save(object param)
{
if (CanSave)
{
if (string.IsNullOrEmpty(this["TextProperty"])
{
//Add Save code here
}
}
}
In your View:
<TextBox Text={Binding TextProperty, ValidateOnDataErrors="true",
UpdateSourceTrigger=PropertyChanged}/>
This will put a red box around the textbox and you can add a validation error template to the textbox style to add a tooltip see here
To show exceptions in a tooltip, I would add a property to the ViewModel that exposes the error message as a string, and bind that to your TextBox's ToolTip. Then in your Save method, you would start by setting that property to the empty string, and then doing all the real work inside a try..catch that, if an exception occurs, pushes the exception message into that property, so it automatically shows up in the ToolTip.
You would need to provide change notification for your property, either by making it a DependencyProperty or by using INotifyPropertyChanged.
Basically, you would want a create properties for your view to observe (usually through triggers) that would update your UI depending on what is happening in your code execution.

Categories