True MVVM and third party controls

2019-04-09 07:51发布

问题:

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.

回答1:

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



回答2:

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.



回答3:

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.



回答4:

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)
    {}


回答5:

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.



回答6:

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).