1

I use MVVM for a desktop application. I have a model called Calculator, which does very expensive calculations in a own thread. The MainViewModel references the Calculator and uses the calculated results. The view takes the results and displays them.

The problem is, that the Calculator returns the calculation results by using an event. And I don't want to bind the view to the event from the Calculator because the used Calculator can change on runtime. As solution I have used the same event twice, which I don't like, has someone a better approach.

Model:

public class Calculator
{
    public event CalculatedHandler Calculated;
    public delegate void CalculatedHandler(object sender, IEnumerable<string> values);

    protected void OnCalculated(IEnumerable<string> values)
    {
        Calculated?.Invoke(this, values);
    }

    public void Run()
    {
        BackgroundWorker bw = new BackgroundWorker();
        bw.DoWork += new DoWorkEventHandler(Calculate);
    }

    private void Calculate(object sender, DoWorkEventArgs e)
    {
        List<string> values = new List<string>();
        for (int i = 0; i < 10000; i++)
        {
            // Do expensive stuff
            values.Add(i + "");
            OnCalculated(values);
        }
    }
}

ViewModel:

public class CalculatorViewModel
{
    private Calculator calculator;

    public event CalculatedHandler Calculated;
    public delegate void CalculatedHandler(object sender, IEnumerable<string> values);

    protected void OnCalculated(IEnumerable<string> values)
    {
        Calculated?.Invoke(this, values);
    }

    public CalculatorViewModel()
    {
        calculator = new Calculator();
        calculator.Calculated += Calculator_Calculated;
    }

    private void Calculator_Calculated(object sender, IEnumerable<string> values)
    {
        OnCalculated(values);
    }
}

View XAML:

<Window.DataContext>
    <viewmodels:CalculatorViewModel />
</Window.DataContext>

View code behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DataContextChanged += MainWindow_DataContextChanged;
    }

    private void MainWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        CalculatorViewModel viewModel = e.NewValue as CalculatorViewModel;

        viewModel.Calculated += ViewModel_Calculated;
    }

    private void ViewModel_Calculated(object sender, IEnumerable<string> values)
    {
        // Draw values on the canvas
    }
}
wake-0
  • 3,918
  • 5
  • 28
  • 45

1 Answers1

3

Implement INotifyPropertyChanged in the view model, which is a common way to update view when data changes. It will work fine with bindings (I think it is possible to use ItemsControl with Canvas as its panel to display calculation results without code-behind).

public class CalculatorViewModel: INotifyPropertyChanged
{
    private Calculator calculator;

    public event PropertyChangedEventHandler PropertyChanged;

    public IEnumerable<string> CalcValues { get; set; }

    public CalculatorViewModel()
    {
        calculator = new Calculator();
        calculator.Calculated += Calculator_Calculated;
    }

    private void Calculator_Calculated(object sender, IEnumerable<string> values)
    {
        CalcValues = values;
        PropertyChanged?.(this, new PropertyChangedEventArgs("CalcValues"));
    }
}
ASh
  • 34,632
  • 9
  • 60
  • 82
  • Okay, but than I have to bind to the `CalcValues` in the code behind from the view, so that it is posible to draw the values, etc. - is this what you meant? – wake-0 Feb 13 '17 at 09:11
  • 1
    @KevinWallis, I had in mind smth like `` in xaml, not in code-behind; and ItemsControl + Canvas combo is shown here: http://stackoverflow.com/questions/1265364/setting-canvas-properties-in-an-itemscontrol-datatemplate. You can also continue with code behind approach, but instead of `Calculated ` event subscribe to `PropertyChanged` – ASh Feb 13 '17 at 09:15
  • Thx for the hints! – wake-0 Feb 13 '17 at 09:17