I have a listbox in a wpf window thats bound to a list in a viewmodel object. When I run a method in the viewmodel object it processes members of the list and each member has a progress. I would like to have the gui update continuously during execution. As it is now, it only updates gui when the processing is finished.
Here I have tried to create a small example of what I have right now:
MainWindow.xaml:
<Window x:Class="WPF_MVVM_Thread_Progressbar.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF_MVVM_Thread_Progressbar"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:TestViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8*"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" Grid.Row="0" Margin="5" ItemsSource="{Binding TestWorker.TestList}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress, Mode=OneWay}" Background="Bisque">
<ProgressBar.Style>
<Style TargetType="{x:Type ProgressBar}">
<Style.Triggers>
<DataTrigger Binding="{Binding Progress}" Value="0">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ProgressBar.Style>
</ProgressBar>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Background="Transparent"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Column="0" Grid.Row="1" Content="TestRun" Command="{Binding TestRunCommand}"></Button>
<TextBlock Text="{Binding SelectedIdx}" Grid.Column="1" Grid.Row="1"/>
</Grid>
</Window>
MainWindowl.xaml.cs:
using Prism.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace WPF_MVVM_Thread_Progressbar
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class TestViewModel : INotifyPropertyChanged
{
private WorkingClass _testWorker;
private DelegateCommand _testRunCommand;
public DelegateCommand TestRunCommand
{
get { return _testRunCommand; }
set { _testRunCommand = value; }
}
public WorkingClass TestWorker
{
get { return _testWorker; }
set { _testWorker = value; RaisePropertyChanged("TestWork"); }
}
private int _selectedIdx;
public int SelectedIdx
{
get { return _selectedIdx; }
set { _selectedIdx = value; RaisePropertyChanged("SelectedIdx"); }
}
public TestViewModel()
{
_testWorker = new WorkingClass();
_testRunCommand = new DelegateCommand(TestRun, canRun);
}
public async void TestRun()
{
//await Task.Run(() => _testWorker.Work());
_testWorker.Work();
}
private bool canRun()
{
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class WorkingClass : INotifyPropertyChanged
{
private ObservableCollection<TestObject> _testList;
public ObservableCollection<TestObject> TestList
{
get { return _testList; }
set { _testList = value; RaisePropertyChanged("TestList"); }
}
public WorkingClass()
{
_testList = new ObservableCollection<TestObject>();
_testList.Add(new TestObject("Object A"));
_testList.Add(new TestObject("Object B"));
_testList.Add(new TestObject("Object C"));
RaisePropertyChanged("TestList");
}
public void Work()
{
foreach (var obj in TestList)
{
obj.TestWork();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class TestObject : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private int _progress;
public int Progress
{
get { return _progress; }
set { _progress = value; RaisePropertyChanged("Progress"); }
}
public TestObject(string name)
{
this._name = name;
_progress = 0;
}
public void TestWork()
{
for (int i = 0; i < 100; i++)
{
System.Threading.Thread.Sleep(10);
Progress++;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I have tried to use ObservableCollection and INotifyPropertyChanged but this it seems not to be enough.
Eventually I would like to be able to have the same effect using async/await call from the TestViewModel.TestRun().
Could someone perhaps offer some insights on this? It would be much appreciated.
I think the current reason that you have the UI only updating once completed, is that you are running all of this on the UI thread. I would instead try this:
Task.Run(async delegate
{
await _testWorker.Work();
});
Or
Task.Run(() =>
{
_testWorker.Work();
});
Or
Task.Factory.StartNew(() =>
{
_testWorker.Work();
});
Or
var newThread = new Thread(new ThreadStart(_testWorker.Work());
newThread.Start();
This will return back to the UI instantly but allow your code to continue.
Note: You will have to be careful about the use of objects off the UI thread. ObservableCollections can only be created on the same thread as the dispatcher that handles the UI work. If you are using two-way binding, again you have to be careful about thread safety.
I've successfully done this in the past using a BackgroundWorker.
public class TestObject : INotifyPropertyChanged {
private BackgroundWorker worker;
public TestObject() {
worker = new BackgroundWorker() {
WorkerReportsProgress = true
};
worker.DoWork += DoWork;
worker.ProgressChanged += WorkProgress;
worker.RunWorkerCompleted += WorkFinished;
}
public int Progress
{
get { return _progress; }
set { _progress = value; RaisePropertyChanged("Progress"); }
}
// Begin doing work
public void TestWork() {
worker.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs eventArgs) {
worker.ReportProgress(0, "Work started");
for (int i = 0; i < 100; i++) {
System.Threading.Thread.Sleep(10);
worker.ReportProgress(i, "Message");
}
}
// Fires when the progress of a job changes.
private void WorkProgress(object sender, ProgressChangedEventArgs e) {
// Do something with the progress here
Progress = e.ProgressPercentage;
}
// Fires when a job finishes.
private void WorkFinished(object sender, RunWorkerCompletedEventArgs e) {
// The work finished. Do something?
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
// NOTE: If you're running C#6 use the null conditional operator for this check.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(e));
}
}
A BackgroundWorker basically runs everything on a separate thread and reports back when its progress changes or it finishes working. You can pull out the ProgressPercentage from its progress report and use that in the UI. Hope that helps. To keep the example simple I didn't include some of your code but that should given you an idea of how it can be done.
Related
I have a WPF application which has a DataGrid that is bound to to a ObservableCollection<string> named "Customers" and I also have a button which is bound to a command which starts a some what heavy task. it simulates adding a bunch of entries to the DataGridas fast as possible.
The issue I am facing is that while it's adding entries to the DataGrid there appears to be stutters and some times deadlocks when trying to move the UI as it's adding entries to the DataGrid.
From my understanding it's because I am updating the DataGrid on the UI thread using Application.Current.Dispatcher.Invoke(() => { /*Update OC*/ });
and even tho they might be small updates to the UI, a lot of them might cause stuttering, now that's my understanding and I might be completely wrong.
My question is.. Is there a way to make this async or reduce the stuttering / deadlocks some other way?
XAML UI
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<DataGrid ItemsSource="{Binding Customers}" AutoGenerateColumns="False"
Width="300" Height="200">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Image" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding }" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Width="100"
Height="25"
Content="Start"
Command="{Binding StartAddingCommand}"/>
</StackPanel>
<Border VerticalAlignment="Bottom"
Height="25" Background="Orange">
<TextBlock Text="{Binding Customers.Count}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>
MainViewModel
class MainViewModel : ObservableObject
{
public ObservableCollection<string> Customers { get; set; }
public RelayCommand StartAddingCommand { get; set; }
public MainViewModel()
{
Customers = new ObservableCollection<string>();
StartAddingCommand = new RelayCommand(o => AddCustomers(), o => true);
}
private void AddCustomers()
{
Task.Run(() =>
{
try
{
foreach (var VARIABLE in GetHTML("https://pastebin.com/raw/gG540TEj"))
{
Application.Current.Dispatcher.Invoke(() =>
{
Customers.Add(VARIABLE.ToString());
});
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
});
}
public string GetHTML(string page)
{
WebClient client = new WebClient();
return client.DownloadString(page);
}
}
And the RelayCommand & ObservableObject are just generic ones.
RelayCommand
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
ObservableObject
class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Doing a foreach with Dispatcher.Invoke without any delay will of course block the UI.
You should definitely have an async version of your GetHTML method and write the AddCustomers like shown below. Use for example one of the Get...Async methods of the HttpClient class.
private async Task AddCustomers()
{
try
{
foreach (var result in await GetHTMLAsync("https://pastebin.com/raw/gG540TEj"))
{
Customers.Add(result.ToString());
}
}
catch (Exception e)
{
Debug.WriteLine(e);
throw;
}
}
Then await the method in the command action:
StartAddingCommand = new RelayCommand(async o => await AddCustomers(), o => true);
I've got a class with manage data, and this class has a ObservableCollection which is bind in UI menu. The problem is that the observable collection loads data, but my UI does not show it.
My class is like this
public class DAL : INotifyPropertyChanged
{
public DAL()
{
this.unity = new UnitOfWork(#"http://192.168.0.173/vocalcontactapi");
}
private UnitOfWork unity;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<EstadosAgente> estadosPausa = new ObservableCollection<EstadosAgente>();
public ObservableCollection<EstadosAgente> EstadosPausa
{
get { return this.estadosPausa; }
}
public async Task<bool> GetAgentStatesAsync()
{
await awaitGetAgentStatesTask();
OnPropertyChanged("EstadosPausa");
return true;
}
private Task awaitGetAgentStatesTask()
{
//UnitOfWork unity = new UnitOfWork(Properties.Settings.Default.restServer);
NameValueCollection parms = new NameValueCollection();
parms.Add("servicioId", "1");
return Task.Run(() =>
{
try
{
var estados = unity.EstadosAgente.GetAll(parms).Where(q => q.habilitado == true).Select(p => p).ToList();
if (estados == null)
return;
estados.ForEach(x =>
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
this.EstadosPausa.Add(x); //*** I think here is the problem***
}));
});
}
catch (Exception ex)
{
string err = ex.Message;
}
});
}
}
And mainWindow I have a Property of class DAL:
private DAL data = new DAL();
public DAL Data { get{ return this.data}}
In my menu I've got next:
<Menu Grid.Row="1">
<MenuItem Header="uno" ItemsSource="{Binding DAL.EstadosPausa}" Click="DataBoundMenuItem_Click">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding estado}"/>
<Setter Property="Tag" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
Obviously, all properties of the loaded data are correct. Any help please?
There's few things in this code which makes me "itchy" but anyway...
You have public DAL Data property but you are binding to DAL.EstadosPausa. You need to bind to Data.EstadosPausa and that'll solve it. You'll see all binding errors in your debuggers Output window.
Here, consider this simplified yet 100% working version of your code base. Now you are fooling around with async/awaits, returning Task<bool> and Dispatching work to UI thread for no good reason (that is obvious from your example).
XAML
<Menu Grid.Row="1">
<MenuItem Header="Uno" ItemsSource="{Binding Data.EstadosPausa}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Estado}"/>
<Setter Property="Tag" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
CodeBehind
public partial class MainWindow : Window
{
private readonly Dal _data = new Dal();
public MainWindow()
{
InitializeComponent();
DataContext = this;
_data.GetAgentStatesAsync(); // Fire Task away, no time to wait!!1
}
public Dal Data { get { return _data; } }
}
public class EstadosAgente
{
public string Estado { get; set; }
}
public class Dal : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Task GetAgentStatesAsync()
{
return Task.Run(() =>
{
Thread.Sleep(1000); // I'm a long running operation...
var estados = new List<EstadosAgente>
{
new EstadosAgente { Estado = "Estado 1" },
new EstadosAgente { Estado = "Estado 2" }
};
EstadosPausa = new ObservableCollection<EstadosAgente>(estados);
OnPropertyChanged("EstadosPausa");
});
}
public ObservableCollection<EstadosAgente> EstadosPausa { get; private set; }
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged; // <- always assign to local variable!
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
During the OnPropertyChanged(), the PropertyChanged event is always NULL so the event never gets called. What am I missing?
Here is the XML Code:
<ProgressBar x:Name="ProgressBar1" Value="{Binding PBarValue, UpdateSourceTrigger=PropertyChanged}" Width="200" Height="20" Minimum="0" Maximum="100"></ProgressBar>
C# Code:
public PBar progress_bar = new PBar();
public class PBar : INotifyPropertyChanged
{
private int _progress;
public int progress
{
get { return _progress; }
set
{
if (value != _progress)
{
_progress = value;
OnPropertyChanged("PBarValue");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
private void Main_Initialized(object sender, EventArgs e)
{
ProgressBar1.DataContext = progress_bar;
progress_bar.progress=75; // This does call the set and
// OnPropertyChanged but
// PropertyChanged is always null
// so it never updates UI
}
I am setting the "value" of the Progress Bar from another thread so this is why I need the binding.
I have tried this so many different ways at this point my head is spinning!
Change your xaml to bind to progress:
<ProgressBar x:Name="ProgressBar1" Value="{Binding progress, UpdateSourceTrigger=PropertyChanged}" Width="200" Height="20" Minimum="0" Maximum="100"></ProgressBar>
And then change your property to raise notification on "progress" and not "PBarValue":
private int _progress;
public int progress
{
get { return _progress; }
set
{
if (value != _progress)
{
_progress = value;
OnPropertyChanged("progress");
}
}
}
Please change your binding in the XAML from PBarValue to progress. Refer below code.
<ProgressBar x:Name="ProgressBar1" Value="{Binding progress, UpdateSourceTrigger=PropertyChanged}" Width="200" Height="20" Minimum="0" Maximum="100"></ProgressBar>
I am brand new to WPF and am trying to implement some sort of table (or I don't know how to implement it if not a table), that has its fields selectable (like in the this example - click the Open App icon and then File->New Project and if you click a table field you will see it coloring blue or going back white) and after they have been selected, to send some signals through the USB port with each columns fields.
Now my question is: How could I implement this table (or whatever)?
And another question would be: How could I browse the array (or whatever I would use to memorize states of the fields)?
Thank you in advance for everybody.
Simple example :D
Set a breakpoint for the Check status button clicked event so you can check which buttons are ticked or not.
Code:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var clickableGrid = new ClickableGrid(3, 3);
DataContext = clickableGrid;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var clickableGrid = (ClickableGrid) DataContext;
ObservableCollection<MyItem> observableCollection = clickableGrid.MyItems;
}
}
public class ClickableGrid : INotifyPropertyChanged
{
private int _columns;
private ObservableCollection<MyItem> _myItems;
private int _rows;
public ClickableGrid(int columns, int rows)
{
Columns = columns;
Rows = rows;
UpdateArray();
}
public ObservableCollection<MyItem> MyItems
{
get { return _myItems; }
set
{
_myItems = value;
OnPropertyChanged();
}
}
public int Columns
{
get { return _columns; }
set
{
_columns = value;
OnPropertyChanged();
}
}
public int Rows
{
get { return _rows; }
set
{
_rows = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void UpdateArray()
{
int columns = Columns;
int rows = Rows;
if (columns <= 0) columns = 1;
if (rows <= 0) rows = 1;
var observableCollection = new ObservableCollection<MyItem>();
for (int i = 0; i < columns*rows; i++)
{
observableCollection.Add(new MyItem());
}
MyItems = observableCollection;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MyItem : INotifyPropertyChanged
{
private bool _isTicked;
public bool IsTicked
{
get { return _isTicked; }
set
{
_isTicked = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public override string ToString()
{
return string.Format("IsTicked: {0}", _isTicked);
}
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding MyItems}" d:DataContext="{d:DesignInstance wpfApplication1:ClickableGrid}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding Columns}"
IsItemsHost="True"
Rows="{Binding Rows}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="wpfApplication1:MyItem">
<ToggleButton x:Name="toggleButton" IsChecked="{Binding Path=IsTicked}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="1"
Click="ButtonBase_OnClick"
Content="Check status" />
</Grid>
</Window>
How do I get the text bound to txtMessage from the second view model? When I had only one view model, the text was working fine. It does not work anymore when I moved the actual download code to second view model. Am I missing something? Any help appreciated.
Xaml:
<DockPanel DockPanel.Dock="Top">
<TextBlock x:Name="txtMessage" DockPanel.Dock="Top" Margin="5" Text="{Binding viewModel1.Message}" />
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5,5">
<ProgressBar Width="300" Visibility="{Binding IsDownloading, Converter={converter:VisibilityConverter}}" IsIndeterminate="True" />
<Button Content="Cancel" />
</StackPanel>
</DockPanel>
<Button Content="Download" Width="120" Margin="0,0,5,0" Name="btnSubmit" Click="btnSubmit_Click" />
CodeBehind:
public partial class DownloadWindow: Window
{
DownloadWindowViewModel viewModel = new DownloadWindowViewModel();
public DownloadWindow()
{
InitializeComponent();
this.DataContext = viewModel;
}
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
viewModel.IsDownloading = true;
viewModel.Download();
}
}
viewModel:
public class DownloadWindowViewModel: INotifyPropertyChanged
{
Thread downloadThread;
public DownloadViewModel viewModel1;
public DownloadWindowViewModel()
{
viewModel1 = new DownloadViewModel();
}
private bool _isDownloading; = false;
public bool IsDownloading
{
get
{
return _isDownloading;
}
set
{
_isDownloading; = value;
OnPropertyChanged("IsDownloading");
}
}
public void Download()
{
viewModel1.Download();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
viewModel1:
public class DownloadViewModel: INotifyPropertyChanged
{
Thread _thread;
public void Download()
{
ThreadStart threadStart = delegate()
{
StartDownload();
};
_thread = new Thread(threadStart);
_thread.IsBackground = true;
_thread.Start();
}
private void StartDownload()
{
for (int i = 10; i < 1500; i++)
{
Thread.Sleep(5000);
Message = "Downloading " + i.ToString();
}
}
private string _message = "";
public string Message
{
get
{
return _message;
}
set
{
if (_message != value)
{
_message = value;
OnPropertyChanged("Message");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Your viewModel1 has to be a property, and it's a field at the moment. Change it to:
public DownloadViewModel viewModel1 { get; set; }
Explanation why such restriction exists, can be found here (primarily due to notification/verifications mechanisms simply not working for fields):
Why does WPF support binding to properties of an object, but not fields?