WPF databinding ProgressBar - c#

I'm making a WPF application where I use WebClient to download file. I want to show the ProgressPercentage within a ProgressBar controller. I have a method in a class where I use WebClient to download file. My question is how do I Databind the ProgressBar to the e.ProgressPercentage.
Method in a Class (DownloadFile):
public async Task DownloadProtocol(string address, string location)
{
Uri Uri = new Uri(address);
using (WebClient client = new WebClient())
{
//client.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
//client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
client.DownloadProgressChanged += (o, e) =>
{
Console.WriteLine(e.BytesReceived + " " + e.ProgressPercentage);
//ProgressBar = e.ProgressPercentage???
};
client.DownloadFileCompleted += (o, e) =>
{
if (e.Cancelled == true)
{
Console.WriteLine("Download has been canceled.");
}
else
{
Console.WriteLine("Download completed!");
}
};
await client.DownloadFileTaskAsync(Uri, location);
}
}
ProgressBar:
<ProgressBar HorizontalAlignment="Left" Height="24" Margin="130,127,0,0" VerticalAlignment="Top" Width="249"/>

For a clean solution you need a ViewModel class and in it we create a StartDownload method and you can call it by a Command or under a button click in your window.
On the other hand there is a good Type named IProgress<T>. It works as a informer for us and you can play with it like the following sample ;)
Inside DownloadViewModel.cs:
public sealed class DownloadViewModel : INotifyPropertyChanged
{
private readonly IProgress<double> _progress;
private double _progressValue;
public double ProgressValue
{
get
{
return _progressValue;
}
set
{
_progressValue = value;
OnPropertyChanged();
}
}
public DownloadViewModel()
{
_progress = new Progress<double>(ProgressValueChanged);
}
private void ProgressValueChanged(double d)
{
ProgressValue = d;
}
public async void StartDownload(string address, string location)
{
await new MyDlClass().DownloadProtocol(_progress, address, location);
}
//-----------------------------------------------------------------------
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Inside MyDlClass. cs
public class MyDlClass
{
public async Task DownloadProtocol(IProgress<double> progress, string address, string location)
{
Uri Uri = new Uri(address);
using (WebClient client = new WebClient())
{
//client.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
//client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
client.DownloadProgressChanged += (o, e) =>
{
Console.WriteLine(e.BytesReceived + " " + e.ProgressPercentage);
progress.Report(e.ProgressPercentage);
};
client.DownloadFileCompleted += (o, e) =>
{
if (e.Cancelled == true)
{
Console.WriteLine("Download has been canceled.");
}
else
{
Console.WriteLine("Download completed!");
}
};
await client.DownloadFileTaskAsync(Uri, location);
}
}
}
Inside MyWindow.Xaml.cs & MyWindow.Xaml:
Now you should fill your window DataContext with an instance of DownloadViewModel class (by Xaml or Code-Behind).
Binding to ProgressValue property of DownloadViewModel.cs class:
<ProgressBar HorizontalAlignment="Left" Height="24" Margin="130,127,0,0" VerticalAlignment="Top" Width="249"
Minimum="0"
Maximum="100"
Value="{Binding Path=ProgressValue}"/>
Finnaly, write in your button OnClick:
if(this.DataContext!=null)
((DownloadViewModel)this.DataContext).StartDownload("__#Address__","__#Location__");
else
MessageBox.Show("DataContext is Null!");
Result:

You should add an IProgress<double> progress parameter to your method, whenever your client wants to update progress then call progress.Report(). IProgress on your view/window side will point to a method where you'll implement the MyProgressBar.Value = value or whatever else.
Using IProgress effectively removes the interaction with the target control that will provide updates, in addition it also takes care of calling the dispatcher so you don't get usual invalid cross-thread call errors.
See this post for an example:
http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx

Related

How can I await for a button click in an async method?

I try to write a code to read a JSON File and allows user to input all the parametes for the objects in the JSON File one by one.
I try to write something like an "awaitable Button", but I failed to write a "GetAwaiter" extension for the button, although I found informations about how to do it.
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-inherit-from-existing-windows-forms-controls?view=netframeworkdesktop-4.8
how can I combine await.WhenAny() with GetAwaiter extension method
http://blog.roboblob.com/2014/10/23/awaiting-for-that-button-click/
So here is my code after clicking a button "loadJSON":
for (int i = 0; i<templist_net.Count; i++)
{
GeneratorFunctions.GetNetworkParameterList(templist_net[i].Type, templist_net[i], treeViewPath.SelectedPath, SolutionFolder);
cBoxPouItem.Text = templist_net[i].Type;
ListViewParam2.ItemsSource = GeneratorFunctions.TempList; // Parameter list binding
temp = GeneratorFunctions.TempList;
ListViewParam2.Visibility = Visibility.Visible; // Set list 2 visible
ListViewParam.Visibility = Visibility.Collapsed; // Set list 1 invisible
//something stop loop, and wait user to type parameters in Listview, and click Button, Then the loop move on.
}
And Here is code trying to write a Button with extension. I add a new class for custom control, and write the extension.
public partial class CustomControl2 : System.Windows.Forms.Button
{
static CustomControl2()
{
}
public static TaskAwaiter GetAwaiter(this Button self)
{
ArgumentNullException.ThrowIfNull(self);
TaskCompletionSource tcs = new();
self.Click += OnClick;
return tcs.Task.GetAwaiter();
void OnClick(object sender, EventArgs args)
{
self.Click -= OnClick;
tcs.SetResult();
}
}
}
But I can't write a extension, which inherit System.Windows.Forms.Button. What should I do?
UPDATE:
here is what i tried.
private async Task Btn_loadJsonAsync(object sender, RoutedEventArgs e) {
// Initialize an open file dialog, whose filter has a extend name ".json"
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "(*.json)|*.json";
TextBoxInformation.Text += "Opening project ...\n";
if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
networks = GeneratorFunctions.ReadjsonNetwork(openFileDialog.FileName);
for (int i = 0; i < networks.Count; i++)
{
if (temp != null)
{
if (networks[i].Type == "Network")
{
templist_net.Add(networks[i]);
i = 1;
}
if (networks[i].Type == "Subsystem")
{
templist_sub.Add(networks[i]);
i = 1;
}
if (networks[i].Type == "Component: Data Point Based Control")
{
templist_com.Add(networks[i]);
i = 1;
}
}
}
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
void OnClick(object sender, RoutedEventArgs e) => semaphore.Release();
btn.Click += OnClick;
for (int i = 0; i < templist_net.Count; i++)
{
//...
//wait here until [btn] is clicked...
await semaphore.WaitAsync();
}
btn.Click -= OnClick;
}}}
You can wait for a button click asynchronously using a SemaphoreSlim, e.g.:
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
void OnClick(object sender, RoutedEventArgs e) => semaphore.Release();
btn.Click += OnClick;
for (int i = 0; i < templist_net.Count; i++)
{
//...
//wait here until [btn] is clicked...
await semaphore.WaitAsync();
}
btn.Click -= OnClick;
}
Although you may want to redesign the way you are doing things, a quick an dirty solution would be to use a dialog box in modal mode and upon the dialog box closing, capture the data that got input and continue looping. The loop will block until the dialog box is closed.
First of all I must insist that your request goes against the principles of the MVVM pattern which is based on events.
Your logic should be in a separate class and expose an OnNext method which should be called from the model through an ActionCommand
Anyway, to conform (as much as possible) to the MVVM pattern, you don't want to await on the button but more on the command bound to the button.
So let's build an awaitable command :
public class AwaitableCommand : ICommand
{
private readonly object _lock = new();
private TaskCompletionSource? _taskCompletionSource;
/// <summary>
/// null-event since it's never raised
/// </summary>
public event EventHandler? CanExecuteChanged
{
add { }
remove { }
}
/// <summary>
/// Always executable
/// </summary>
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter)
{
lock (_lock)
{
if (_taskCompletionSource is null)
return;
_taskCompletionSource.SetResult();
// reset the cycle
_taskCompletionSource = null;
}
}
public Task WaitAsync()
{
lock (_lock)
{
// start a new cycle if needed
_taskCompletionSource ??= new TaskCompletionSource();
return _taskCompletionSource.Task;
}
}
}
Then you can create your logic with it (I put it in the model, wich is a bad practice):
public class Model : NotifyPropertyChangedBase
{
private int _count;
public Model()
{
RunLogicAsync();
}
public int Count
{
get => _count;
private set => Update(ref _count, value);
}
public AwaitableCommand OnNextCommand { get; } = new();
/// <summary>
/// I know, I know, we should avoid async void
/// </summary>
private async void RunLogicAsync()
{
try
{
for (;;)
{
await OnNextCommand.WaitAsync();
Count++;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
And your view:
<Window ...>
<Window.DataContext>
<viewModel:Model />
</Window.DataContext>
<Window.Resources>
<system:String x:Key="StringFormat">You clicked it {0} times</system:String>
</Window.Resources>
<Grid>
<Button Content="{Binding Count}"
ContentStringFormat="{StaticResource StringFormat}"
Command="{Binding OnNextCommand}"
Padding="10 5"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Window>
Working demo available here.

Using a Stopwatch and DataBinding on a WPF Window that is already being updated using IProgress

In my Main() WPF program I run a time consuming method asynchronously. When this method is running, I fire up a secondary window that contains a ProgressBar, which I update using IProgress.
Following is an example of my setup.
MAIN Program:
public partial class MainWindow : Window
{
private ProgressBarWindow pbwWindow = null;
public MainWindow()
{
InitializeComponent();
}
private void RunMethodAsync(IProgress<int> progress)
{
Dispatcher.Invoke(() =>
{
pbwWindow = new ProgressBarWindow("Processing...");
pbwWindow.Owner = this;
pbwWindow.Show();
});
TimeConsumingMethod(progress);
}
private void TimeConsumingMethod(IProgress<int> progress)
{
for (int i = 1; i <= 100; i++)
{
// Thread.Sleep() represents actual time consuming work being done.
Thread.Sleep(100);
progress.Report(i);
}
}
private async void btnRun_Click(object sender, RoutedEventArgs e)
{
IProgress<int> progress;
progress = new Progress<int>(i => pbwWindow.SetProgressUpdate(i));
await Task.Run(() => RunMethodAsync(progress));
}
}
My ProgressBarWindow which contains the progress bar looks like this:
public partial class ProgressBarWindow : Window
{
Stopwatch stopwatch = new Stopwatch();
BackgroundWorker worker = new BackgroundWorker();
public string ElapsedTimeString { get; set; }
public ProgressBarWindow(string infoText)
{
InitializeComponent();
SetTimer();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartTimer();
}
private void SetTimer()
{
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += (s, e) =>
{
while (!worker.CancellationPending)
{
worker.ReportProgress(0, stopwatch.Elapsed);
Thread.Sleep(1000);
}
};
worker.ProgressChanged += (s, e) =>
{
TimeSpan elapsedTime = (TimeSpan)e.UserState;
ElapsedTimeString = string.Format("{0}:{1}:{2}", elapsedTime.Minutes, elapsedTime.Seconds, elapsedTime.Milliseconds);
};
}
private void StartTimer()
{
stopwatch.Start();
worker.RunWorkerAsync();
}
private void StopTimer()
{
stopwatch.Stop();
worker.CancelAsync();
}
public void SetProgressUpdate(int progress)
{
pbLoad.Value = progress;
if (progress >= 100)
{
StopTimer();
Close();
}
}
}
I borrowed the StopWatch logic from this SO answer.
Then, on my ProgressBarWindow I have a TextBlock which I've used Binding as follows, just as the answer above says.
<TextBlock Name="tbElapsedTime" Text="{Binding ElapsedTimeString}"/>
Now when I run the program, the method executes, and the progress bar updates just fine. However, my TextBlock that's supposed to update with the elapsed time does not get updated.
To verify my timer's running fine, I updated TextBlock value directly as follows instead of Binding and it worked as expected and displayed Elapsed Time:
worker.ProgressChanged += (s, e) =>
{
TimeSpan elapsedTime = (TimeSpan)e.UserState;
ElapsedTimeString = string.Format("{0}:{1}:{2}", elapsedTime.Minutes, elapsedTime.Seconds, elapsedTime.Milliseconds);
tbElapsedTime.Text = ElapsedTimeString;
};
So I'm guessing my problem is with the Binding and possibly using BackgroundWorker on a windows that's already being run asynchronously? How could I fix this so I could use DataBinding?
As mentioned by Ginger Ninja, you have to implement INotifyPropertyChanged and use RelativeSource={RelativeSource Self} (as additional setting to the binding):
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _ElapsedTimeString;
public string ElapsedTimeString
{
get { return _ElapsedTimeString; }
set
{
if (_ElapsedTimeString != value)
{
_ElapsedTimeString = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ElapsedTimeString"));
}
}
}
// ....
}
and the XAML:
<TextBlock Name="tbElapsedTime" Text="{Binding ElapsedTimeString, RelativeSource={RelativeSource Self}}"/>
Data binding is often used in combination with MVVM. That is IMHO the prefered way to solve your problem... If you want to use MVVM, you have to implement a view model that contains all the logic and implements INotifyPropertyChanged. Than you can simply bind properties from the view model to the view. That ensures a nice separation between (GUI related) logic and view.

C# MVVM Calling ViewModel method from Code Behind using NotifyIcon

I can't seem to get a method in my ViewModel to run successfully from my XAML code behind when using NotifyIcon. The method executes correctly, as tested with debugging mode using breakpoints, but nothing happens in the View.
The method in question is RefreshData, and it can be called from either a button in the View (works as expected), or from right clicking the NotifyIcon and selecting Refresh Data (does nothing). I'll post relevant code below. Any help would be appreciated!
MainWindow constructor in CodeBehind
public MainWindow()
{
try
{
MM = new MMViewModel();
InitializeComponent();
DataContext = MM;
_notifyIcon = new NotifyIcon();
_notifyIcon.DoubleClick += (s, args) => ShowMainWindow(this);
_notifyIcon.Icon = Migration_Monitor_v2.Properties.Resources.mmc;
_notifyIcon.Visible = true;
Closing += MainWindow_Closing;
CreateContextMenu();
}
catch (Exception e)
{
logger.Error("App failed with exception: ", e);
}
}
private void CreateContextMenu()
{
_notifyIcon.ContextMenuStrip = new ContextMenuStrip();
_notifyIcon.ContextMenuStrip.Items.Add("Refresh Data").Click += (s,e) => MM.RefreshData();
_notifyIcon.ContextMenuStrip.Items.Add("Exit").Click += (s, e) => ExitApplication(this);
}
RefreshData method in ViewModel (works when executed from the Refresh button in the View)
public void RefreshData()
{
InfoPanelVisible = Visibility.Hidden;
InfoSummaryVisible = Visibility.Visible;
Task.Run(() => LoadData());
n = DateTime.Now;
ProgressBarText = "Click a project to show progress";
ProgressBarValue = 0;
lastRefresh.Reset();
lastRefresh.Start();
}
LoadData method (and associated methods) called from RefreshData
public async void LoadData()
{
IsLoading = Visibility.Visible;
await GetWebApiInfo();
MonitorData downloadInfo = main;
try { AssignDataToControls(downloadInfo); }
catch (Exception e) { Console.WriteLine("Error: " + e); }
finally { IsLoading = Visibility.Hidden; }
}
public void AssignDataToControls(MonitorData mon)
{
MainPanel.Clear();
MonitorText.Clear();
mon.MainPanel.ToList().ForEach(x => MainPanel.Add(x));
mon.MonitorText.ToList().ForEach(x => MonitorText.Add(x));
Information = mon.Information;
ProgressData = mon.progList;
}
public async Task GetWebApiInfo()
{
var url = "::::WEB API ADDRESS::::";
string responseFromServer;
using (HttpClient _client = new HttpClient())
using (var dataStream = await _client.GetStreamAsync(url))
using (var reader = new StreamReader(dataStream, Encoding.Unicode))
responseFromServer = await reader.ReadToEndAsync();
var deserializer = new JavaScriptSerializer();
main = deserializer.Deserialize<MonitorData>(responseFromServer);
}
RefreshCommand from ViewModel Commands.cs file
internal class RefreshCommand : ICommand
{
public RefreshCommand(MMViewModel viewModel)
{
_viewModel = viewModel;
}
private MMViewModel _viewModel;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _viewModel.CanRefresh;
}
public void Execute(object parameter)
{
_viewModel.RefreshData();
}
}
Solved this issue. The problem was that my ViewModel was being constructed three separate times, so it was unclear to the command which instance was being referenced.
To fix this, I removed references to the ViewModel in my XAML Window definition space. There were 2 references I thought I needed for the ViewModel as a local namespace (i.e. x:local.VM="MM.MyViewModel"). After removing those, the ViewModel is only being constructed once and all code is running as intended. Thanks to #Will for his help getting to the bottom of this!

Label content bound to a viewmodel property is not updated during RunWorkerCompleted event of the background worker

I looked at a couple of threads on the UI update using background workers in the view model, and made code changes as necessary, unfortunately I cant get it to work.
I am trying to update my label content which is bound to a property which implements INotifyPropertyChanged in the view model. The report is created, but The updated label content shows up only on reopening the MyWindow from the Main WindowViewModel.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this,
NotificationMessageReceived);
Closing += (s, e) => ViewModelLocator.Cleanup();
}
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "ShowMyWindow")
{
MyWindow ReportWind = new MyWindow();
ReportWind.DataContext = msg.Sender;
MyWindow.ShowDialog();
}
}
}
ViewModel.cs
public class MyViewModel
{
//Fields
public static string statusProp = string.Empty;
BackgroundWorker _BWorker = new BackgroundWorker();
//Properties
public string LblStatusContent
{
get
{
return statusProp;
}
set
{
statusProp = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("LblStatusContent");
}
}
public RelayCommand GoCmd { get; private set; }
public MyViewModel(IDialogService dService)
{
_dialogService = dService;
GoCmd = new RelayCommand(() => Go(_dialogService), () => true);
}
private void Go(IDialogService dService)
{
//dialogservice to show a savedialogbox
//do something.
//start my thread to save a pdf refort
_BWorker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_BWorker.DoWork += new DoWorkEventHandler(DoWork);
_BWorker.ProgressChanged += new
ProgressChangedEventHandler(WorkerProgress);
_BWorker.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(WorkerCompleted);
if (_BWorker.IsBusy !=true)
{
_BWorker.RunWorkerAsync();
}
}
private void WorkerProgress(object sender, ProgressChangedEventArgs e)
{
try
{
if (e.UserState != null && e.UserState.ToString() != "")
{
// LblStatusContent =
//((ReportWindow.ReportProgressArg)e.UserState).smsg;
//BatchCompareProgressBar.Value = e.ProgressPercentage;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
ErrorLogger.Log(LogLevel.Error, ex.ToString());
}
}
private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
if ((e.Cancelled == true))
{
this.LblStatusContent = "Cancelled";
}
else if (!(e.Error == null))
{
LblStatusContent = "Failed to create report";
OnPropertyChanged(LblStatusContent);
LblStatusVisibility = System.Windows.Visibility.Visible;
}
else
{
System.Windows.Application.Current.Dispatcher.Invoke(
DispatcherPriority.Normal,
(Action)delegate()
{
this.LblStatusContent = "Report created successfully";
OnPropertyChanged(LblStatusContent);
LblStatusVisibility = System.Windows.Visibility.Visible;
});
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
ErrorLogger.Log(LogLevel.Error, ex.ToString());
}
}
private void DoWork(object sender, DoWorkEventArgs e)
{
try
{
StartReport();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
ErrorLogger.Log(LogLevel.Error, ex.ToString());
}
finally { }
}
}
MyWindow.xaml
<Label Name="lblStatus" VerticalAlignment="Center"
Content="{Binding LblStatusContent}"
Visibility="{Binding LblStatusVisibility, Mode=TwoWay}"
Margin="0,80,12,203" Height="36"
HorizontalAlignment="Right" Width="450" />
Maybe this little example helps you figure it out?
<Window x:Class="WpfApplication1.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
Title="MainWindow"
Height="80"
Width="140">
<Window.DataContext>
<viewModel:MainViewModel />
</Window.DataContext>
<Label Content="{Binding Property}" />
</Window>
namespace WpfApplication1.ViewModel
{
public class MainViewModel : ViewModelBase
{
readonly BackgroundWorker _worker = new BackgroundWorker();
private string _property;
public MainViewModel()
{
_worker.WorkerReportsProgress = true;
_worker.DoWork += (sender, args) =>
{
int iterations = 0;
Property = "Starting...";
Thread.Sleep(1000);
while (iterations < 5)
{
_worker.ReportProgress(iterations * 25);
iterations++;
Thread.Sleep(350);
}
};
_worker.ProgressChanged += (sender, args) =>
Property = string.Format("Working...{0}%", args.ProgressPercentage);
_worker.RunWorkerCompleted += (sender, args) =>
{
Property = "Done!";
};
_worker.RunWorkerAsync();
}
public string Property
{
get { return _property; }
set { _property = value; RaisePropertyChanged(); }
}
}
}
I don't know how I missed subscribing to the INotifyPropertychanged at the class declaration,even though I had implemented the Onpropertychanged correctly.
To update the property while the window was still open after the background process was completed all I had to do was this :
public class MainViewModel : ViewModelBase, INotifyPropertyChanged.

looping through a folder of images in c# / WPF

So i'm trying to loop through a folder and change the image source each 2 seconds.
I think my code is right, but I seem to be missing something since my image won't update, but I don't get an error.
The code populates my array of files so it finds the pictures, I'm just doing something wrong to set the image source.
XAML code
<Grid>
<Image x:Name="Picture" Source="{Binding ImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
<Grid>
C# code
private string[] files;
private System.Timers.Timer timer;
private int counter;
private int Imagecounter;
Uri _MainImageSource = null;
public Uri MainImageSource {
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
}
}
public IntroScreen()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(this.MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(timer_Tick);
timer.Interval = (2000);
timer.Start();
files = Directory.GetFiles("../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
MessageBox.Show(Imagecounter.ToString());
counter = 0;
}
private void timer_Tick(object sender, EventArgs e)
{
counter++;
_MainImageSource = new Uri(files[counter - 1], UriKind.Relative);
if (counter == Imagecounter)
{
counter = 0;
}
}
Anyone know what I'm doing wrong ?
Updated code
XAML
<Image x:Name="Picture" Source="{Binding MainImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
C#
public partial class IntroScreen : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string[] files;
private System.Timers.Timer timer;
private int counter;
private int Imagecounter;
Uri _MainImageSource = null;
public Uri MainImageSource
{
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
OnPropertyChanged("MainImageSource");
}
}
public IntroScreen()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(this.MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
files = Directory.GetFiles("../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
counter = 0;
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(timer_Tick);
timer.Interval = (2000);
timer.Enabled = true;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
counter++;
MainImageSource = new Uri(files[counter - 1], UriKind.Relative);
if (counter == Imagecounter)
{
counter = 0;
}
}
I'm not getting any error's but the image still isen't switching. I'm wondering if my paths are even working. Is there any way to test this ?
You have forgot to do notify the update to MainImageSource to the binding.
To do so, you have to implement the interface : INotifyPropertyChanged and define DataContext.
And, as written in the MSDN documentation "Setting Enabled to true is the same as calling Start, while setting Enabled to false is the same as calling Stop.".
Like this:
public partial class IntroScreen : Window, INotifyPropertyChanged
{
private string[] files;
private Timer timer;
private int counter;
private int Imagecounter;
BitmapImage _MainImageSource = null;
public BitmapImage MainImageSource // Using Uri in the binding was no possible because the Source property of an Image is of type ImageSource. (Yes it is possible to write directly the path in the XAML to define the source, but it is a feature of XAML (called a TypeConverter), not WPF)
{
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
OnPropertyChanged("MainImageSource"); // Don't forget this line to notify WPF the value has changed.
}
}
public IntroScreen()
{
InitializeComponent();
DataContext = this; // The DataContext allow WPF to know the initial object the binding is applied on. Here, in the Binding, you have written "Path=MainImageSource", OK, the "MainImageSource" of which object? Of the object defined by the DataContext.
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
timer = new Timer();
timer.Elapsed += timer_Tick;
timer.Interval = 2000;
// Initialize "files", "Imagecounter", "counter" before starting the timer because the timer is not working in the same thread and it accesses these fields.
files = Directory.GetFiles(#"../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
MessageBox.Show(Imagecounter.ToString());
counter = 0;
timer.Start(); // timer.Start() and timer.Enabled are equivalent, only one is necessary
}
private void timer_Tick(object sender, EventArgs e)
{
// WPF requires all the function that modify (or even read sometimes) the visual interface to be called in a WPF dedicated thread.
// IntroScreen() and MainWindow_Loaded(...) are executed by this thread
// But, as I have said before, the Tick event of the Timer is called in another thread (a thread from the thread pool), then you can't directly modify the MainImageSource in this thread
// Why? Because a modification of its value calls OnPropertyChanged that raise the event PropertyChanged that will try to update the Binding (that is directly linked with WPF)
Dispatcher.Invoke(new Action(() => // Call a special portion of your code from the WPF thread (called dispatcher)
{
// Now that I have changed the type of MainImageSource, we have to load the bitmap ourselves.
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(files[counter], UriKind.Relative);
bitmapImage.CacheOption = BitmapCacheOption.OnLoad; // Don't know why. Found here (http://stackoverflow.com/questions/569561/dynamic-loading-of-images-in-wpf)
bitmapImage.EndInit();
MainImageSource = bitmapImage; // Set the property (because if you set the field "_MainImageSource", there will be no call to OnPropertyChanged("MainImageSource"), then, no update of the binding.
}));
if (++counter == Imagecounter)
counter = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And your XAML does not refer to the correct property:
<Grid>
<Image x:Name="Picture" Source="{Binding MainImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
<Grid>
Why do you need to implement INotifyPropertyChanged?
Basically, when you define a binding, WPF will check if the class that contains the corresponding property defines INotifyPropertyChanged. If so, it will subscribe to the event PropertyChanged of the class.
I'm not seeing any use of the INotifyPropertyChanged interface, which would be required to update a UI item the way you are using it. As it is now, the UI control has no way of knowing that the value was updated.

Categories