Calling method on control in MVVM - c#

I'm currently working with the ArcGIS Runtime SDK .NET where I'd like to have the current viewport of the map bound to a property on my view model. However, the view port (expressed as Extent) does not have a setter, despite being a dependency property.
In order to set the viewport, I need to call a method on the control. But how do I do that from the view model? I already found similiar questions here on SO, but most of them were answered with The view model shouldn't be aware of the view. I agree with that, but unfortunately I can't change the fact that the setter of a property on a proprietary control is a different method than the actual property I can bind to and read values from.

There are many ways to do the job that might be considered MVVM friendly.
The one that I suggest is that you wrap your ArcGIS view in a custom control that you have full control over. This way you can expose your required dependency properties and handle their setters in your custom control.
I use this method almost every time I'm using a third-party component this way I make the component loosely coupled with my other code and I can replace them easily.

You can use an attached property to push make a VM request any listening view to go to a certain viewpoint. I'm using that in my sample here:
https://github.com/Esri/arcgis-runtime-demos-dotnet/blob/master/src/TurnByTurn/RoutingSample.Shared/CommandBinder.cs
In your VM you simply raise INPC for a viewpoint:
public Viewpoint ViewpointRequested
{
get { return m_ViewpointRequested; }
private set
{
m_ViewpointRequested = value;
RaisePropertyChanged("ViewpointRequested");
}
}
And then lastly just bind this to the attached property on the MapView:
<esri:MapView Map="{Binding Map}"
local:CommandBinder.RequestView="{Binding ViewpointRequested}" />

Related

WPF IValueConverter changes property: how to force update?

In a MVVM model, my view uses a value converter. This value converter has a property X that influences the converted value. This property X might change
Whenever X changes, all values that were converted using this value converter need to be updated. Of course My ViewModel does not know that my Views use this converter, so It can't notify PropertyChanged. Besides I think it is not neat to let the ViewModel know in what format values are converted.
My value converter does not know for which values it is used. Luckily my XAML and its code behind class do.
So, my view has two converters as resources, and two text blocks that use these resources:
MyView.XAML:
<UserControl.Resources>
<convert:FormattedStringConverter x:Key="SelectedHistoryConverter" />
<vm:TimeFrameConverter x:Key="TimeFrameConverter"/>
</UserControl.Resources>
...
<TextBlock Height="20" Name="HistoryTime"
Text="{vm:CultureInfoBinding Path=SelectedHistoryTime,
Converter= {StaticResource SelectedHistoryConverter},
ConverterParameter='History Time: {0:G}'}"/>
<TextBlock Height="20" Name="Timeframe"
Text="{vm:CultureInfoBinding Path=Timeframe,
Converter= {StaticResource TimeFrameConverter},
ConverterParameter='Time Frame: [{0:G}, {1:G}]'}"/>
Event handler in MyView.XAML.cs
private void OnMyParameterChanged(object sender, EventArgs e)
{
UpdateConverterParameters(); // updates the changed property in the converters
UpdateTargets(); // forces an Update of all Targets that use these converters
}
In UpdateTargets I need to tell the two TextBlocks to update their values, which will use the changed converters.
For this I used the accepted answer in StackOverflow: How to force a WPF binding to refresh?
public void UpdateTargets()
{
BindingExpression historyTimeExpression = HistoryTime.GetBindingExpression(TextBlock.TextProperty);
historyTimeExpression.UpdateTarget();
BindingExpression timeframeExpression = Timeframe.GetBindingExpression(TextBlock.TextProperty);
timeframeExpression .UpdateTarget();
}
This works fine. However this means that whenever I add an element in XAML that uses this binding I'll have to add this element to UpdateTargets.
Is there a way for a class Derived from Binding to know which Targets are bound to it?
Whenever X changes, all values that were converted using this value converter need to be updated.
In this situation, instead of a converter, perhaps have the VM provide the computed values via on demand properties which the controls will subsequently bind to.
So to somewhat borrow from your example I have three properties, two are computed (which do the job of the converter(s)) and one is the existing one which the computed properties utilize.
Computed
public string HistoryTime { get { return SelectedHistoryTime.AddDays(-2).ToShortDate(); } }
public string Timeframe {
get
{
return $"Time Frame: [{SelectedHistoryTime.AddDays(-14)}, {SelectedHistoryTime.AddDays(+14).ToShortDate()}]";
}
}
Existing Notifies All
Then whenever all corresponding properties which HistoryTime and TimeFrame use, in their setters notify a change such as
public DateTime SelectedHistoryTime
{
get { return _SelectedHistoryTime; }
set
{
_SelectedHistoryTime = value;
NotifyChange("SelectedHistoryTime");
// These rely on this property, so they change too
NotifyChange("HistoryTime");
NotifyChange("Timeframe");
}
}
Within the WPF binding system, it is best to allow the system to respond to notifications directly associated with bound values (properties). It is possible to explicitly refresh bindings, as you've found. But that's an error-prone, inefficient way to approach the problem.
As explained here, the best way to do this is to write an IMultiValueConverter instead. This allows for the source properties to be each be bound via INotifyPropertyChanged events, working smoothly with the binding system just like any other properties you might bind. This way, both the main source property and the "property X" will automatically cause the converter to be invoked, updating the target property as needed if either changes.
It is also possible to write a composite property in your view model, as suggested by the other answer. But it's my preference to declare as much of the view-oriented code as possible, rather than putting imperative implementation in the code-behind. This provides the best flexibility, as well as follows the "separation of concerns" philosophy better (i.e. by isolating the "data conversion" aspect in the converter itself, rather than overloading the view model with that logic).
There are numerous examples of how to implement IMultiValueConverter, here on Stack Overflow, on Microsoft's documentation web site, and of course various tutorials on the web. I trust you'll be able to easily apply what you already know about XAML binding syntax, value converters, etc. to implement your current scenario using IMultiValueConverter.

Using ViewModels in ObservableCollections in Prism

As far as I know, the default way to use a ObservableCollection that is bound to a listview is with model classes as elements (ObservableCollection<MyModel>). So when a listview element is selected, we use NavigateAsync and pass the model, which then can be used by the ViewModel to "fill itself".
The problem with this approach is, that it's not possible to use ViewModel properties for binding in the listview.
For example:
I have a View, ViewModel and Model "PickList", which contains a collection of "PickLine" objects - each having a View, ViewModel and Model themselves. The PickLine object contains a property "PickedQuantity" and a property "OpenQuantity". Now in my PickList view, I don't want to bind these two to separate items (e.g. two labels), but I want to have one label to display both I a format like for example "PickedQuantity / OpenQuantity". I know this example can be solved by using multi binding or something like this. But that's not the meaning of it all.
My PickLine ViewModel already has a property "QuantityString", that I want to bind to the label of a listview element via DataTemplate. But how can I do this. Is it even possible?
Make a property that combines the two other properties and bind to that. E.g.:
public string FullQuantity {get {return $"{PickedQuantity} / {OpenQuantity}";}}
Then in the setter for PickedQuantity and OpenQuantity, you will want to call whatever PropertyChanged method you have set up to notify the bindings of a property change and pass in the FullQuantity property name so elements that are bound to FullQuantity get updated when either PickedQuantity or OpenQuantity are changed.
This way, you are only binding one label's text to one property and that label would get updated when either of the two quantity properties are changed.
Note: I am unfamiliar with Prism, but this approach should work regardless of the Mvvm framework in use.
Your PickListViewModel should expose a collection property whose items are of type PickLineViewModel (not PickLine).
Whether you need an ObservableCollection<PickLineViewModel> depends on where changes can happen - in service / model that initially created the PickLines or in the GUI or both. In any way, you have to make sure the changes are propagated from one side (the collection of view models) to the other (the collection of models). Google wrapping observable collection as a starter (hint: avoid two-way sync if possible). These blog posts are old but still relevant and make a good reading. A trivial wrapping is described in this answer.

Property change notification when property members change

Say I have a property named Location of type Point in my ViewModel, to which my View binds. I'm using MVVM Light's ObservableObject and RaisePropertyChanged setup to raise property change notifications. The problem is that when I execute the following in the ViewModel:
this.Location = new Point(100,100);
my View updates correctly. But if I do:
this.Location.Offset(10,10);
my View doesn't update, means the Location property does not notify the View about the change. Why is this so? How do sub-properties of built-in types such as Point or Size deal with change notification?
The fundamental problem is that Point is mutable.
You could write your own ImmutablePoint that allows conversion to and from the existing Point class for API compatibility, but doesn't allow methods like Offset.
It's not an ideal solution, but it gets around the problem of changing the fields on a property rather than the property itself.
In MVVM land, your ViewModel is responsible for notifying the View when properties change. The majority of the .NET classes do not implement INotifyPropertyChanged themselves, so you won't get automatic wiring when using them. You'll have to raise PropertyChanged yourself on the Location property each time you modify any properties of Location
Alternatively, you could implement your own Point object that implements INotifyPropertyChanged.
You can add INotifyPropertyChanged to your class members automatically using either Castle Dynamic Proxy (which wraps your models in a proxy at runtime) or PropertyChanged.Fody (which modifies the model's IL at compile time). Google is your friend.

How can control in the view get specific data from view model?

I have multiple of views (user controls), each with its own ViewModel. To navigate between them I am using buttons. Buttons display image and text from corresponding view model and also need column and row (because there are like 10 views: 10 columns with different number of rows each).
Right now buttons are created dynamically (I made a Navigator control for this) and for view models I have base class to hold text, image, column and row. Number of views available will be different (depends on user level and certain settings), that's why it's I need control here.
Question: how shall my control get data from view models?
Right now I have interface INavigator, defined in (lol) control itself. And view models implement it. I could go opposite, let my control to know about view models. Both looks wrong.
There is a single Navigator control what has, lets say, Items bound to a list of view models. It can cast each view model to INavigator or ViewModelBase (common for all pages) to obtain specific view model image, text, column and row. So either view model knows about control (to implement INavigator) or control knows about ViewModelBase.. And this is a problem, both solution bind tight control and view models, which is bad in mvvm.
Schematically
The way you've drawn your diagram answers your own question as to how you should structure the code for this.
What you need is one VM (let's call it MainVM) which contains an ObservableCollection<VMBase> of the other VMs (using your base type so that they can all happily live in the same collection).
Your View needs an ItemsControl (bound to your ObservableCollection<VMBase>) where you specify a DataTemplate for the Button using the properties exposed by the VMBase type only. Set the Command property in the Button to call SwitchCommand, CommandParameter is set to the item itself (i.e. {Binding .}).
Your View also needs a ContentControl bound to a SelectedVM property on MainVM which you can populate.
Implement SwitchCommand to set the SelectedVM property based on the value from the CommandParameter.
public void ExecuteSwitchCommand(object parameter)
{
var vmBase = parameter as VMBase;
if (vmBase != null)
SelectedVM = vmBase;
}
All properties mentioned here should be INotifyPropertyChanged enabled so that the View registers when they change and updates the UI.
To get the different UIs for the ContentControl, add type-specific DataTemplates for each of your specific VM types to the Resources file of your View (or if you're smart and are building a custom plug-in framework, merge the Resource Dictionaries).
A lot of people forget with MVVM that the whole point is that there is a purposeful separation of View from ViewModel, thus meaning you can potentially have many Views for a single ViewModel, which is what this demonstrates.
I find it's easiest to think of MVVM as a top-down approach... View knows about it's ViewModel, ViewModel knows about its Model, but Model does not know about its ViewModel and ViewModel does not know about its View.
I also find a View-first approach to development the easiest to work with, as UI development in XAML is static (has to be).
I think a lot of people get to wrapped up in 'making every component (M, V, VM) standalone and replaceable', myself included, but I've slowly come to the conclusion that is just counter-productive.
Technically, sure you could get very complicated and using IoC containers, create some ViewLocator object which binds a View-type to a ViewModel-type, but... what exactly does that gain you besides more confusion? It makes it honestly harder (because I've done this at one point) to develop because now you've lost design-time support first and foremost, among other things; and you're still either binding to a specific view model interface in your view or creating the binding at run-time. Why complicate it?
This article is a good read, and the first Note: explicitly talks about View vs. ViewModel. Hopefully, it will help you draw your own conclusions.
To directly answer your question, I think having your ViewModels implement an INavigator interface of some sort is probably ideal. Remember your VM is 'glue' between your view and model/business logic, its job is to transform business data into data that is consumable by your views, so it exists somewhere between both your UI and business layers.
This is why there are things like Messengers and View Services, which is where your navigator service on the ViewModels can fit in nicely.
I think the design has led to a no way out situation.
I believe that creating a custom button control where the dependency properties tie the image, the row and column actually provide a way for the page, which it resides on ,to get that information to them; whether they are dynamically created or not.
Continuing on with that thought. There is no MVVM logic applied to a custom control, the control contains what it needs to do its job and that is through the dependency properties as mentioned. Any functionality of the button should be done by commanding; all this makes the button data driven and robust enough to use in a MVVM methodology or not.
Question: how shall my control get data from view models?
There should only one viewmodel which is the page the control resides on. The control is simply bound to information which ultimately resides on that VM. How it gets there, that is up to the programmer. If the button is going to contain state data, that is bound from its dependency property in a two way fashion back to the item it is bound to.
By keeping VMs out of the buttons and only having one VM that is the best way to segregate and maintain the data. Unless I am really missing something here....
Same as others here I find it a bit hard to actually understand what you are asking, so this is quite general. The answer to the question header is simply: the Control gets the data from the ViewModel through bindings, always. You set the DataContext of your Control to the corresponding ViewModel, and from there you keep the ViewModel and the Control synchronized:
If you add an ItemsControl containing buttons to the View, you add an ObservableCollection<ButtonViewModel> to the ViewModel and bind the ItemsSource of the ItemsControl to this.
If you allow the user to dynamically add content to the View, the actual code that does it resides in the ViewModel, e.g. when the user clicks on a button "Add Button", you use the Command property to call a ViewModel method that adds a ButtonViewModel to the collection and the View will automatically reflect your changes.
There do exist complicated cases that are impossible to code exclusively in the ViewModel, I have found Behaviors to be the missing link there, but I'll get into that when you show me the specific case.
If you'd like to get a working example, please provide as much code as you can, with your exact expectations of what it should do.

True MVVM and third party controls

In a True MVVM model we do not expect any code behind in xaml.cs also we do not expect viewModel to have refernece of view.
However all third party controls do not provide good support for True MVVM.
In my case I am using Infragistics xamDatagrid control and I want to export its data to excel. The only way I can export data to excel of data grid is by using following code:
xamDataGridExcelExporter.xamDataGridExcelExporter xamDataGridExcelExporter1 =
new xamDataGridExcelExporter.xamDataGridExcelExporter();
xamDataGridExcelExporter1.Export(**this.xamDataGrid1**,
#"C:\Excel\ExportFile.xls");
However, XamDataGridExcelExporter takes input as this.xamDataGrid. xamDataGrid is part of View not viewModel.
So how can we handle such kind of cases where we need instance of view in viewModel.
It is a common misconception that MVVM forbids code-behind. The truth is that code-behind is not reusable and it is inseparable from the view, so it cannot be unit tested without automation. But it does have its uses.
There is nothing inherently bad about code-behind. In fact, it's not much different than all the other code your write in support of your view like converters, custom controls, etc. None of this code can be tested by your view-model unit tests. The only difference with code-behind is that it is less reusable. But it's still part of your view and views are not bad.
In general, the absence of code-behind is a good indicator of a clean separation between the view and the view-model. However the presence of some code-behind in an otherwise clean design usually merely indicates something that is hard to do with the standard controls and data binding and commands.
In your case, exporting the XamDataGrid is definitely view-specific. It has to do precisely with the third-party library you have chosen for the view. So it makes perfect sense that it should not be part of the view-model.
If you are are still dead set against any code-behind, you can use behaviors, such as ACB or Blend Behaviors to write functionality that you would otherwise put into the code-behind. Just realize that even behaviors are still part of the view, only more reusable that code-behind.
You can write a wrapper around xamDataGrid that has a dependencyproperty called filename. The viewmodel can then bind to this property. When the xamDataGrid detects a change on the filename property it can then execute the code you suggested. Afterwards reset the filename property for further notification.
This solution keeps out the code from you code behind and makes the xamDataGrid responsible for exporting its data.
-------edit---------
A second solution can make use of the MVVM light messenger class. In stead of declaring a dependency property, make your wrapper listen to a message. When the viewmodel sends the message (which could for example have the filename as parameter) the wrapper can then execute the code.
eg
public class ExportableXamDataGrid: XamDataGrid
{
public ExportableXamDataGrid():base()
{
Messenger.Default.Register<string>(this,"ExportExcel",ExportFile);
}
private void ExportFile(string file)
{
xamDataGridExcelExporter.xamDataGridExcelExporter xamDataGridExcelExporter1 =
new xamDataGridExcelExporter.xamDataGridExcelExporter();
xamDataGridExcelExporter1.Export(**this.xamDataGrid1**,
#"C:\Excel\ExportFile.xls");
}
}
Then in your viewmodel you can do:
Messenger.Default.Send(#"C:\Excel\ExportFile.xls","ExportExcel");
There are many solutions to your problem, all of which you do not have to start writing logic in your view.
http://www.lucbos.net/2011/06/using-codebehind-in-mvvm.html
I'd use code behind because the 'problem' is cause by the view so I would keep it there.
Yes, that will break MVVM but using these controls it is already broken. By keeping the solution in the code behind you willl keep the ViewModel as clean as possible so when the controls do support MVVM it is easier to clean up.
I'd strongly recommend to use System.Windows.Interactivity.Interaction.Triggers in XAML and use the Event trigger to call a event of XamDataGrid and use 'CallDataMethod' which will call a custom method that you will create on the ViewModel. The best thing in this is that you will get the object(XamDataGrid) reference as sender.
This will be purely MVVM and you will be able to acheive your goal. Also, i would recommend to use WPF DataGrid which is very light weight as compared to XamDataGrid. Only use XamDataGrid if you are using some major functionalities provided by this control, cuz just to initialize this UI Element the processor takes 200 milliseconds or may be more.
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedCellsChanged">
<is:CallDataMethod Method="YourMethodNameInViewModel" />
</i:EventTrigger>
</i:Interaction.Triggers>
And in the View Model your Method i.e.
public void YourMethodNameInViewModel(Object sender, EventArgs e)
{}
Don't worry too much about it. Yes, having "heavy" views is opposed to the ideas of MVVM (thin views, testability). But there are always exceptions to the rule.
The decision here is using the "free/existing" XAMDataGrid export functionality or write your own MVVM version of it (which resides in the ViewModel).
If you choose Option1, you'd need to cache the View object within the ViewModel (use ctor injection) in addition the usual approach of setting View.DataContext = ViewModel and relying on data-binding to handle the rest.
Instead of keeping the Excel Exporter in ViewModel you can place it in a Behavior around the event you are triggering the export.
create a DataPresenter(xamdatagrid) type dependency property in your behavior and bind that to your existing xamdatagrid in XAMLcode to get access of your xamdatagrid. This way you'll functionally achieve and ViewModel will be free of UI objects.
<i:Interaction.Behaviors>
<behav:ExcelExporterBehavior MyDataPresenter="{Binding ElementName=myxamdatagrid,Mode=OneTime}"></behav:ExcelExporterBehavior>
</i:Interaction.Behaviors>
if MyDataPresenter is the property in ExcelExporterBehavior behavior which is set to any other UI control (say any button to export).

Categories