I am trying to get my head around MVVM, I created little project to test some things that I will need in the real one.
I have MainView, MainViewWindow, ProgressView which contains ProgressBar and ProgressViewModel that uses BackgroundWorker to update ProgressBar.
In my MainView I have UserControl that has it's content bound to ProgressViewModel.
Whatever starting value for Progress I set in ProgressViewModel() is reflected on ProgressBar when I start my App. It does not update however. From what I understand BackgroundWorker reports progress back to UI thread, but I tried Invoking anyway with to luck... I guess the problem is in MainViewModel or MainView but I can't get to it. Does MainViewModel need to implement INotifyPropertyChanged in case of UserControl? What am I missing here?
Here's code:
MainView.xaml
<Window x:Class="Testing.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dsvm="clr-namespace:Testing.ViewModels"
xmlns:dsv="clr-namespace:Testing.Views"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<dsvm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type dsvm:ProgressViewModel}">
<dsv:ProgressView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Click to Test Binding" Command="{Binding ButtonCommand}"/>
<UserControl Grid.Row="1" Content="{Binding viewModel}"/>
</Grid>
</Window>
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Testing.Models;
namespace Testing.ViewModels
{
public class MainViewModel
{
private ICommand _buttonCommand;
public ICommand ButtonCommand
{
get
{
return _buttonCommand;
}
set
{
_buttonCommand = value;
}
}
public ProgressViewModel viewModel {get;set;}
public MainViewModel()
{
ButtonCommand = new RelayCommand(RunCommand);
viewModel = new ProgressViewModel();
}
public void RunCommand(object obj)
{
viewModel.Run();
}
}
}
ProgressView.xaml
<UserControl x:Class="Testing.Views.ProgressView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dsvm="clr-namespace:Testing.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<dsvm:ProgressViewModel/>
</UserControl.DataContext>
<DockPanel>
<ProgressBar IsIndeterminate="False" Minimum="0" Maximum="100" Value="{Binding Progress, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
</DockPanel>
</UserControl>
ProgressViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Testing.ViewModels
{
public class ProgressViewModel : INotifyPropertyChanged
{
public BackgroundWorker bgWorker { get; set; }
private double _progress;
public double Progress
{
get { return _progress; }
set
{
if (_progress != value)
{
_progress = value;
RaisePropertyChanged("Progress");
}
}
}
public ProgressViewModel()
{
_progress = 0.0;
bgWorker = new BackgroundWorker();
bgWorker.DoWork += bgWorker_DoWork;
bgWorker.ProgressChanged += bgWorker_ProgressChanged;
bgWorker.WorkerReportsProgress = true;
}
public void Run()
{
bgWorker.RunWorkerAsync();
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; ++i)
bgWorker.ReportProgress(i);
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Progress = e.ProgressPercentage;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and RelayCommand.cs
from http://www.codeproject.com/Tips/813345/Basic-MVVM-and-ICommand-Usage-Example
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Testing.Models
{
public class RelayCommand : ICommand
{
private Action<object> execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
public RelayCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
if (canExecute == null)
throw new ArgumentNullException("canExecute");
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
this.CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
this.CanExecuteChangedInternal -= value;
}
}
public bool CanExecute(object parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChangedInternal;
if (handler != null)
handler.Invoke(this, EventArgs.Empty);
}
public void Destroy()
{
this.canExecute = _ => false;
this.execute = _ => { return; };
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
}
}
Thank you in advance, I am gratefull for all tips I can get :)