I'm coding a class to move and copy files. I'm raising events when the current file progress and the total progress changes. When I test the code on my XP machine, it works fine, but when I run it on my Windows 7 64-Bit machine, the current progress doesn't update the UI correctly. The current progress ProgressBar only gets half way then starts on the next file which does the same. The total progress ProgressBar updates fine. Any ideas why this is happening?
EDIT: The Windows 7 machine is running a quad-core and the XP is running a dual-core. Not sure if that might be what's making a difference. I'm only a hobbyist so excuse my ignorance :)
EDIT: Code added (Background)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Timers;
using Timer = System.Timers.Timer;
namespace nGenSolutions.IO
{
public class FileTransporter
{
#region Delegates
public delegate void CurrentFileChangedEventHandler(string fileName);
public delegate void CurrentProgressChangedEventHandler(int percentComplete);
public delegate void CurrentWriteSpeedUpdatedEventHandler(long bytesPerSecond);
public delegate void TotalProgressChangedEventHandler(int percentComplete);
public delegate void TransportCompleteEventHandler(FileTransportResult result);
#endregion
private readonly List<string> _destinationFiles = new List<string>();
private readonly List<string> _sourceFiles = new List<string>();
private long _bytesCopiedSinceInterval;
private FileTransportResult _result;
private Timer _speedTimer;
private long _totalDataLength;
private BackgroundWorker _worker;
public bool TransportInProgress { get; private set; }
public event CurrentFileChangedEventHandler CurrentFileChanged;
public event CurrentProgressChangedEventHandler CurrentProgressChanged;
public event CurrentWriteSpeedUpdatedEventHandler CurrentWriteSpeedUpdated;
public event TotalProgressChangedEventHandler TotalProgressChanged;
public event TransportCompleteEventHandler TransportComplete;
public void AddFile(string sourceFile, string destinationFile)
{
if (!File.Exists(sourceFile))
throw new FileNotFoundException("The specified file does not exist!", sourceFile);
var fileInfo = new FileInfo(sourceFile);
_totalDataLength += fileInfo.Length;
_sourceFiles.Add(sourceFile);
_destinationFiles.Add(destinationFile);
}
public void BeginTransport()
{
// update the write speed every 3 seconds
_speedTimer = new Timer {Interval = 3000};
_speedTimer.Elapsed += SpeedTimerElapsed;
_worker = new BackgroundWorker();
_worker.DoWork += DoTransport;
_worker.RunWorkerCompleted += WorkerCompleted;
_worker.RunWorkerAsync();
_speedTimer.Start();
TransportInProgress = true;
}
private void SpeedTimerElapsed(object sender, ElapsedEventArgs e)
{
InvokeCurrentSpeedUpdated(_bytesCopiedSinceInterval);
_bytesCopiedSinceInterval = 0;
}
private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
TransportInProgress = false;
InvokeTransportComplete(_result);
}
public void CancelTransport(bool rollbackChanges)
{
if (TransportInProgress == false)
throw new InvalidOperationException("You tried to stop the transport before you started it!");
_result = FileTransportResult.Cancelled;
_worker.CancelAsync();
while (_worker.IsBusy)
{
// wait for worker to die an 'orrible death
}
// TODO: rollback changes if requested
}
private void DoTransport(object sender, DoWorkEventArgs e)
{
long totalBytesCopied = 0;
int totalPercentComplete = 0;
for (int i = 0; i < _sourceFiles.Count; i++)
{
string sourceFile = _sourceFiles[i];
string destinationFile = _destinationFiles[i];
long currentFileLength = new FileInfo(sourceFile).Length;
InvokeCurrentFileChanged(sourceFile);
using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
{
using (var destinationStream = new FileStream(destinationFile, FileMode.Create, FileAccess.Write))
{
using (var reader = new BinaryReader(sourceStream))
{
using (var writer = new BinaryWriter(destinationStream))
{
int lastPercentComplete = 0;
for (int j = 0; j < currentFileLength; j++)
{
writer.Write(reader.ReadByte());
totalBytesCopied += 1;
_bytesCopiedSinceInterval += 1;
int current = Convert.ToInt32((j/(double) currentFileLength)*100);
int total = Convert.ToInt32((totalBytesCopied/(double) _totalDataLength)*100);
// raise progress events every 3%
if (current%3 == 0)
{
// only raise the event if the progress has increased
if (current > lastPercentComplete)
{
lastPercentComplete = current;
InvokeCurrentProgressChanged(lastPercentComplete);
}
}
if (total%3 == 0)
{
// only raise the event if the progress has increased
if (total > totalPercentComplete)
{
totalPercentComplete = total;
InvokeTotalProgressChanged(totalPercentComplete);
}
}
}
}
InvokeCurrentProgressChanged(100);
}
}
}
}
InvokeTotalProgressChanged(100);
}
private void InvokeCurrentFileChanged(string fileName)
{
CurrentFileChangedEventHandler handler = CurrentFileChanged;
if (handler == null) return;
handler(fileName);
}
private void InvokeCurrentProgressChanged(int percentComplete)
{
CurrentProgressChangedEventHandler handler = CurrentProgressChanged;
if (handler == null) return;
handler(percentComplete);
}
private void InvokeCurrentSpeedUpdated(long bytesPerSecond)
{
CurrentWriteSpeedUpdatedEventHandler handler = CurrentWriteSpeedUpdated;
if (handler == null) return;
handler(bytesPerSecond);
}
private void InvokeTotalProgressChanged(int percentComplete)
{
TotalProgressChangedEventHandler handler = TotalProgressChanged;
if (handler == null) return;
handler(percentComplete);
}
private void InvokeTransportComplete(FileTransportResult result)
{
TransportCompleteEventHandler handler = TransportComplete;
if (handler == null) return;
handler(result);
}
}
}
EDIT: Code added (GUI)
using System;
using System.IO;
using System.Windows.Forms;
using ExtensionMethods;
using nGenSolutions.IO;
namespace TestApplication
{
public partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
}
private void ProgressForm_Load(object sender, EventArgs e)
{
var transporter = new FileTransporter();
foreach (string fileName in Directory.GetFiles("C:\\Temp\\"))
{
transporter.AddFile(fileName, "C:\\" + Path.GetFileName(fileName));
}
transporter.CurrentFileChanged += transporter_CurrentFileChanged;
transporter.CurrentProgressChanged += transporter_CurrentProgressChanged;
transporter.TotalProgressChanged += transporter_TotalProgressChanged;
transporter.CurrentWriteSpeedUpdated += transporter_CurrentWriteSpeedUpdated;
transporter.TransportComplete += transporter_TransportComplete;
transporter.BeginTransport();
}
void transporter_TransportComplete(FileTransportResult result)
{
Close();
}
void transporter_CurrentWriteSpeedUpdated(long bytesPerSecond)
{
double megaBytesPerSecond = (double)bytesPerSecond/1024000;
currentSpeedLabel.SafeInvoke(x=> x.Text = string.Format("Transfer speed: {0:0.0} MB/s", megaBytesPerSecond));
}
private void transporter_TotalProgressChanged(int percentComplete)
{
totalProgressBar.SafeInvoke(x => x.Value = percentComplete);
}
private void transporter_CurrentProgressChanged(int percentComplete)
{
currentProgressBar.SafeInvoke(x => x.Value = percentComplete);
}
private void transporter_CurrentFileChanged(string fileName)
{
this.SafeInvoke(x => x.Text = string.Format("Current file: {0}", fileName));
}
}
}
EDIT: SafeInvoke code added
public static void SafeInvoke<T>(this T #this, Action<T> action) where T : Control
{
if (#this.InvokeRequired)
{
#this.Invoke(action, new object[] {#this});
}
else
{
if (!#this.IsHandleCreated) return;
if (#this.IsDisposed)
throw new ObjectDisposedException("#this is disposed.");
action(#this);
}
}
Well, if transporter_CurrentProgressChanged gets correct values, the program works properly. You can try to add some minimal Thread.Sleep call to InvokeCurrentProgressChanged (maybe with 0 parameter) when progress value is 100%, to get UI chance to update itself, but in this case you reduce the program performance. It is possibly better to leave the program unchanged, since it works as expected, and main progress bar is updated.
Related
I have a class that generates images in a thread and invokes an event on every frame. A Form subscribes to this event and displays the image in a PictureBox using Invoke.
If the image generation process is allowed to finish, all is good. However, if the Form is closed while the thread is running, the Form tries to stop the thread but ends up in some sort of a deadlock.
When I try to use the threaded class in without a Form or in a Console app, start the process, wait for a second, then Cancel it, everything works fine.
The question is, something must be wrong with either the Form_Closing method of the Form or the Stop method of the threaded class.
I have kept the code to a minimum and it can be pasted into LinqPad, etc.
An auxiliary question: Should the Process method invoke the Stop method to clean up?
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
namespace VideoMask.WinFormsAppApp
{
internal static class Program
{
[STAThread]
private static void Main ()
{
Program.TestWithoutForm(); // Runs fine.
Program.TestWithForm(); // Deadlocks.
}
private static void TestWithForm ()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FormMain());
}
private static void TestWithoutForm ()
{
new TestWithoutForm().Run();
}
}
public class TestWithoutForm
{
private VideoProcessor VideoProcessor = new VideoProcessor();
public TestWithoutForm ()
{
this.VideoProcessor.SourceFrameRead += this.VideoProcessor_SourceFrameRead;
}
public void Run ()
{
this.VideoProcessor.Start();
Thread.Sleep(1000);
this.VideoProcessor.Stop();
MessageBox.Show("Done");
}
private void VideoProcessor_SourceFrameRead (object sender, VideoProcessorEventArgs e)
{
var filename = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Sample.png");
e.Bitmap.Save(filename, ImageFormat.Png);
}
}
public class FormMain: Form
{
private PictureBox PictureBox = new PictureBox();
private VideoProcessor VideoProcessor = new VideoProcessor();
public FormMain ()
{
this.Controls.Add(this.PictureBox);
this.PictureBox.Dock = DockStyle.Fill;
this.Shown += this.FormMain_Shown;
this.FormClosing += this.FormMain_FormClosing;
this.WindowState = FormWindowState.Maximized;
}
private void FormMain_Shown (object sender, EventArgs e)
{
this.VideoProcessor.SourceFrameRead += this.VideoProcessor_SourceFrameRead;
this.VideoProcessor.Start();
}
private void FormMain_FormClosing (object sender, FormClosingEventArgs e)
{
this.VideoProcessor.Stop();
this.VideoProcessor.Dispose();
this.VideoProcessor = null;
}
private void VideoProcessor_SourceFrameRead (object sender, VideoProcessorEventArgs e)
{
this.Invoke
(
new Action
(
() =>
{
using (var bitmap = this.PictureBox.Image)
{
this.PictureBox.Image = new Bitmap(e.Bitmap);
}
}
)
);
}
}
public sealed class VideoProcessor: IDisposable
{
public event EventHandler<ErrorEventArgs> ConversionError = null;
public event EventHandler<VideoProcessorEventArgs> SourceFrameRead = null;
private Font Font = null;
private Bitmap Bitmap = null;
private Thread Thread = null;
private Graphics Graphics = null;
private readonly object SyncRoot = new object();
private CancellationTokenSource CancellationTokenSource = null;
public VideoProcessor ()
{
this.Bitmap = new Bitmap(800, 600, PixelFormat.Format32bppArgb);
this.Graphics = Graphics.FromImage(this.Bitmap);
this.Font = new Font(FontFamily.GenericSansSerif, 48, GraphicsUnit.Point);
}
public bool IsRunning { get; private set; }
public void Start ()
{
lock (this.SyncRoot)
{
if (this.IsRunning) { throw (new Exception("A video conversion process is already running.")); }
this.Stop();
this.CancellationTokenSource = new CancellationTokenSource();
this.Thread = new Thread(new ParameterizedThreadStart(this.Process));
this.Thread.Start(this.CancellationTokenSource);
}
}
public void Stop ()
{
lock (this.SyncRoot)
{
if (!this.IsRunning) { return; }
this.CancellationTokenSource?.Cancel();
this.Thread.Join();
this.Thread = null;
this.CancellationTokenSource?.Dispose();
this.CancellationTokenSource = null;
}
}
private void Process (object cancellationTokenSource)
{
var source = (CancellationTokenSource) cancellationTokenSource ?? throw (new ArgumentNullException(nameof(cancellationTokenSource)));
lock (this.SyncRoot) { if (this.IsRunning) { throw (new Exception("A conversion process is already running.")); } }
this.IsRunning = true;
for (var i = 1; i <= int.MaxValue; i++)
{
if (source.IsCancellationRequested) { break; }
this.Graphics.Clear(Color.White);
this.Graphics.DrawString(i.ToString(), this.Font, Brushes.Black, 10, 10);
this.SourceFrameRead?.Invoke(this, new VideoProcessorEventArgs(this.Bitmap));
Thread.Sleep(33);
}
this.IsRunning = false;
}
public void Dispose () => this.Stop();
}
public class VideoProcessorEventArgs: EventArgs
{
public Bitmap Bitmap { get; private set; }
public VideoProcessorEventArgs (Bitmap bitmap) { this.Bitmap = bitmap; }
}
}
I have WinForms App where I am using the code in this following Post to check the InActivity Status of my app (Please see the accepted answer in the post). InActivity In WinForms. Once the app reaches inactivity its stopping the inactivity monitor. But then I want to restart the time once the user logs in.
So I have a notification mechanism when the user logs in and I am calling the start timer method again. I get the Started Monitor Message but the app never tracks inactivity and I don't get Timer reporting app is InACTIVE message at all. Please help.
public static System.Windows.Forms.Timer IdleTimer =null;
static int MilliSeconds = 60000;
static void Main(string[] args)
{
f = new GeneStudyForm(true, arguments.SystemTimeOutFolder, arguments.SystemTimeOutFile, StartInActivityMonitor);
int x = StartInActivityMonitor();
}
public static void StartInActivityMonitor()
{
IdleTimer = new Timer();
LeaveIdleMessageFilter limf = new LeaveIdleMessageFilter();
Application.AddMessageFilter(limf);
IdleTimer.Interval = MilliSeconds; //One minute; change as needed
Application.Idle += new EventHandler(Application_Idle);
if (IdleTimer != null)
{
MessageBox.Show(IdleTimer.Interval.ToString());
}
IdleTimer.Tick += TimeDone;
IdleTimer.Tag = InActivityTimer.Started;
MessageBox.Show("starting");
IdleTimer.Start();
}
static private void Application_Idle(object sender, EventArgs e)
{
if (!IdleTimer.Enabled) // not yet idling?
IdleTimer.Start();
}
static private void TimeDone(object sender, EventArgs e)
{
try
{
MessageBox.Show("Stopped");
IdleTimer.Stop(); // not really necessary
f.MonitorDirectory();
f.UpdateInActivityStatus();
IdleTimer.Tick -= TimeDone;
Application.Idle -= new EventHandler(Application_Idle);
}
catch(Exception ex)
{
MessageBox.Show(ex.InnerException + ex.Data.ToString());
}
}
Here is my GeneStudyForm
public partial class GeneStudyForm
{
GeneStudySystemTimeOutIO GeneStudyIO;
Func<int> StartTimer;
//Passing the StartInActivityMonitor Method as Func Delegate
public GeneStudyForm(bool isStandalone, string TimeOutFolder, string TimeOutFile, System.Func<int> MyMethod)
{
GeneStudyIO = GeneStudySystemTimeOutIO.GetInstance(TimeOutFolder, TimeOutFile);
UpdateActivityStatus(AppName.GeneStudyStatus, ActivityStatus.Active);
this.StartTimer = MyMethod;
}
public void UpdateActivityStatus(AppName name, ActivityStatus status)
{
if (GeneStudyIO != null)
{
GeneStudyIO.WriteToFile(name, status);
}
}
public void MonitorDirectory()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(GeneStudyIO.GetDriectory());
fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
fileSystemWatcher.Filter = "*.json";
fileSystemWatcher.Changed += FileSystemWatcher_Changed;
fileSystemWatcher.EnableRaisingEvents = true;
}
public void UnRegister(FileSystemWatcher fileSystemWatcher)
{
fileSystemWatcher.Changed -= FileSystemWatcher_Changed;
}
// I am writing the inactive status to a file. So this event will fill
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
try
{
var root = GeneStudyIO.GetDesrializedJson();
if (root != null && root.AllApplications != null)
{
var item = root.AllApplications.Any(x => x.Status == ActivityStatus.Active.ToString());
if (!item)
{
if (InActivecount == 0)
{
GeneStudyAndApplicationCommon.TimeStatus = InActivityTimer.Ended;
MessageBox.Show("I am hiding");
this.Hide();
InActivecount++;
}
}
else
{
if (GeneStudyAndApplicationCommon.TimeStatus == InActivityTimer.Ended)
{
MessageBox.Show("I am showing");
this.Show();
UnRegister(sender as FileSystemWatcher);
UpdateActivityStatus(AppName.GeneStudyStatus, ActivityStatus.Active);
MessageBox.Show("Updated Status");
if (StartTimer != null)
{
MessageBox.Show("Starting Timer again");
if (StartTimer() == -1)
{
MessageBox.Show("Couldn't start timer");
}
}
}
}
}
}
catch (Exception ex)
{
SystemDebugLogLogger.LogException(ex);
}
}
}
This soulution is quite different from what I have posted. But I could solve my problem with this. But I want to post it if it helps someone. Here is the post I am following Last User Input
I created a class called IdleCheck where I am getting LastUserInput as follows
public static class IdleCheck
{
[StructLayout(LayoutKind.Sequential)]
private struct LASTINPUTINFO
{
[MarshalAs(UnmanagedType.U4)]
public int cbSize;
[MarshalAs(UnmanagedType.U4)]
public int dwTime;
}
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO x);
public static int GetLastInputTime()
{
var inf = new LASTINPUTINFO();
inf.cbSize = Marshal.SizeOf(inf);
inf.dwTime = 0;
return (GetLastInputInfo(ref inf)) ? Environment.TickCount - inf.dwTime : 0;
}
}
Next in the actual Form this is my code. I am using a simple yes no message box to see if the timer can be stopped and recalled again when needed. You can apply your own locking mechanism.
I want the app to time out if it is InActive for 20 seconds. Change it as needed.
public partial class Form1 : Form
{
Timer timer;
const int TIMEOUT_DONE = 20000;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Reset();
}
void timer_Tick(object sender, EventArgs e)
{
//var ms = TIMEOUT_DONE - IdleCheck.GetLastInputTime();
if (IdleCheck.GetLastInputTime() > TIMEOUT_DONE)
{
DialogResult dialogResult = MessageBox.Show("Sure", "Some Title", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
Stop();
Reset();
}
}
}
public void Reset()
{
timer = new Timer();
timer.Interval = 10000;
timer.Tick += timer_Tick;
timer.Start();
}
public void Stop()
{
timer.Tick -= timer_Tick;
timer.Stop();
}
}
I don't know if my title is correct. But here's what I want to know.
I have a Download class that returns certain events and has a couple of methods. Each instance of Download class can download a single file. And all those events and methods are related to the file being downloaded.
As it's a multi file downloader, multiple instantiations are required when more than a single file needs to be downloaded.
Each download has a download id, but that is not supplied to the Download class, to keep it independent from the other classes.
Now getting all the info from each instance of the file download and being able to control a single download, is the problem. How do I know which download is which?
Any solutions? Or design patterns you could recommend? I've hit a roadblock.
Download class:
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
namespace Mackerel_Download_Manager
{
public class Download
{
public event EventHandler<DownloadStatusChangedEventArgs> ResumablityChanged;
public event EventHandler<DownloadProgressChangedEventArgs> ProgressChanged;
public event EventHandler Completed;
public bool stop = true; // by default stop is true
public bool paused = false;
SemaphoreSlim pauseLock = new SemaphoreSlim(1);
string filename;
public void DownloadFile(Uri DownloadLink, string Path)
{
filename = System.IO.Path.GetFileName(Path);
stop = false; // always set this bool to false, everytime this method is called
var fileInfo = new FileInfo(Path);
long existingLength = 0;
if (fileInfo.Exists)
existingLength = fileInfo.Length;
var request = (HttpWebRequest)HttpWebRequest.Create(DownloadLink);
request.Proxy = null;
request.AddRange(existingLength);
try
{
using (var response = (HttpWebResponse)request.GetResponse())
{
long fileSize = existingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
bool downloadResumable; // need it for not sending any progress
if ((int)response.StatusCode == 206) //same as: response.StatusCode == HttpStatusCode.PartialContent
{
//Console.WriteLine("Resumable");
downloadResumable = true;
}
else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
{
if (existingLength > 0)
{
if (ResumeUnsupportedWarning() == false) // warn and ask for confirmation to continue if the half downloaded file is unresumable
{
return;
}
}
existingLength = 0;
downloadResumable = false;
}
OnResumabilityChanged(new DownloadStatusChangedEventArgs(downloadResumable));
using (var saveFileStream = fileInfo.Open(downloadResumable ? FileMode.Append : FileMode.Create, FileAccess.Write))
using (var stream = response.GetResponseStream())
{
byte[] downBuffer = new byte[4096];
int byteSize = 0;
long totalReceived = byteSize + existingLength;
var sw = Stopwatch.StartNew();
while (!stop && (byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
saveFileStream.Write(downBuffer, 0, byteSize);
totalReceived += byteSize;
float currentSpeed = totalReceived / (float)sw.Elapsed.TotalSeconds;
OnProgressChanged(new DownloadProgressChangedEventArgs(totalReceived, fileSize, (long)currentSpeed));
pauseLock.Wait();
pauseLock.Release();
}
sw.Stop();
}
}
if (!stop) OnCompleted(EventArgs.Empty);
}
catch (WebException e)
{
System.Windows.MessageBox.Show(e.Message, filename);
}
}
public void pause()
{
if (!paused)
{
paused = true;
// Note this cannot block for more than a moment
// since the download thread doesn't keep the lock held
pauseLock.Wait();
}
}
public void unpause()
{
if (paused)
{
paused = false;
pauseLock.Release();
}
}
public void StopDownload()
{
stop = true;
this.unpause(); // stop waiting on lock if needed
}
public bool ResumeUnsupportedWarning()
{
var messageBoxResult = System.Windows.MessageBox.Show("When trying to resume the download , Mackerel got a response from the server that it doesn't support resuming the download. It's possible that it's a temporary error of the server, and you will be able to resume the file at a later time, but at this time Mackerel can download this file from the beginning.\n\nDo you want to download this file from the beginning?", filename, System.Windows.MessageBoxButton.YesNo);
if (messageBoxResult == System.Windows.MessageBoxResult.Yes)
{
return true;
}
else
{
return false;
}
}
protected virtual void OnResumabilityChanged(DownloadStatusChangedEventArgs e)
{
var handler = ResumablityChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnProgressChanged(DownloadProgressChangedEventArgs e)
{
var handler = ProgressChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnCompleted(EventArgs e)
{
var handler = Completed;
if (handler != null)
{
handler(this, e);
}
}
}
public class DownloadStatusChangedEventArgs : EventArgs
{
public DownloadStatusChangedEventArgs(bool canResume)
{
ResumeSupported = canResume;
}
public bool ResumeSupported { get; private set; }
}
public class DownloadProgressChangedEventArgs : EventArgs
{
public DownloadProgressChangedEventArgs(long totalReceived, long fileSize, long currentSpeed)
{
BytesReceived = totalReceived;
TotalBytesToReceive = fileSize;
CurrentSpeed = currentSpeed;
}
public long BytesReceived { get; private set; }
public long TotalBytesToReceive { get; private set; }
public float ProgressPercentage
{
get
{
return ((float)BytesReceived / (float)TotalBytesToReceive) * 100;
}
}
public float CurrentSpeed { get; private set; } // in bytes
public TimeSpan TimeLeft
{
get
{
var bytesRemainingtoBeReceived = TotalBytesToReceive - BytesReceived;
return TimeSpan.FromSeconds(bytesRemainingtoBeReceived / CurrentSpeed);
}
}
}
}
Download class is instantiated inside a Mackerel class, that starts the download for the given downloads.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
namespace Mackerel_Download_Manager
{
public static class Mackerel
{
//Main Menu functions
public static void ResumeDownload(string[] DownloadIDs)
{
foreach (var DownloadID in DownloadIDs)
{
var itemToResume = Downloads.DownloadEntries.Where(download => download.DownloadID == DownloadID).FirstOrDefault();
if (itemToResume.Running == false)
{
itemToResume.Running = true;
var download = new Download();
download.DownloadFile(itemToResume.DownloadLink, itemToResume.SaveTo);
var window = new Dialogs.DownloadProgress(itemToResume);
window.Show();
double progress = 0;
itemToResume.Status = string.Format("{0:0.00}%", progress);
Downloads.DownloadEntries.CollectionChanged += delegate
{
if (!itemToResume.Running) window.Close();
};
}
}
}
public static void StopDownload(string[] DownloadIDs)
{
foreach (var DownloadID in DownloadIDs)
{
var itemToStop = Downloads.DownloadEntries.Where(download => download.DownloadID == DownloadID).FirstOrDefault();
if (itemToStop.Running == true)
itemToStop.Running = false;
}
}
public static void StopAllDownloads()
{
foreach (var itemToStop in Downloads.DownloadEntries.Where(download => download.Running == true))
itemToStop.Running = false;
}
public static void RemoveDownload(string[] DownloadIDs) // this method is able to delete multiple downloads
{
foreach (var DownloadID in DownloadIDs)
{
// delete from the download list
var selectedDownload = Downloads.DownloadEntries.Where(download => download.DownloadID == DownloadID).FirstOrDefault();
var selectedDownloadIndex = Downloads.DownloadEntries.IndexOf(selectedDownload);
Downloads.DownloadEntries.RemoveAt(selectedDownloadIndex);
//delete from the harddrive
if (File.Exists(selectedDownload.SaveTo))
File.Delete(selectedDownload.SaveTo);
}
Downloads.Serialize(); // save current state of object
}
public static void RemoveCompletedDownloads() // this method just removes all completed downloads from Mackerel's download list (it doesn't delete them from the hard drive)
{
foreach (var itemToRemove in Downloads.DownloadEntries.Where(download => download.Status == "Complete").ToList())
{
Downloads.DownloadEntries.Remove(itemToRemove);
}
}
// Context Menu
public static void OpenDownloadProperties(string DownloadID) // Open "Download Properties" for the given download ID
{
var DownloadProperties = new Dialogs.Context_Menu.DownloadProperties(DownloadID);
DownloadProperties.Owner = Application.Current.MainWindow; // so that this dialog centers to its parent window, as its window is set to WindowStartupLocation="CenterOwner"
DownloadProperties.ShowDialog();
}
}
}
Full source code is here: https://github.com/Expenzor/mackerel-download-manager
Sounds like your making the download itself an object but are using "downloader" as the name instead. I would maybe suggest an array of downloaded objects or similar. The downloader class can have a method which creates a new object - perhaps a download object. Using an array or linked list etc will give you the opportunity to address the object and call them independently - know which is which.
Posting your code would help as well.
I'm in the process of diving into Android development. Most of the projects I was involved with, were in C#. Delegates were elements of C# I did use very often, I've used also stuff like passing data using class that extends EventArgs or properties with set and get. With my programming knowledge I think I will be able to get started with Android development pretty smoothly. The thing is I have completly no idea how to approach an implementing mechanism similar to C# delagte in Java.
Below I present some exemplary class that works in C# just fine and contains some elements of C# language that I would like to use in my future Android projects. Can someone provide me with a translation of this code? I would prefer that 'cos working with my own example and its conversion would allow me to catch it all faster. Also, any valuable resources on the topic (not only delegates but genereal topic of converting C# into Java) would be apreciated.
CountdownTimer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace SampleDelegateApp
{
public class CountdownTimer
{
Timer tmrTicks = new Timer();
int secondsLeft = 0;
int numberOfSecondsToCountdown = 0;
public bool IsWorking
{
get { return tmrTicks.Enabled; }
}
public CountdownTimer(int seconds)
{
if (secondsLeft < 0) secondsLeft = 0;
numberOfSecondsToCountdown = seconds;
secondsLeft = seconds;
tmrTicks.Interval = 1000;
tmrTicks.Tick += new EventHandler(tmrTicks_Tick);
tmrTicks.Enabled = false;
}
void tmrTicks_Tick(object sender, EventArgs e)
{
secondsLeft--;
if (secondsLeft >= 1)
WhenCountdownTimerTick(new CountdownTimerEventArgs(secondsLeft, numberOfSecondsToCountdown, false));
else
{
Stop();
WhenCountdownTimerTick(new CountdownTimerEventArgs(secondsLeft, numberOfSecondsToCountdown, true));
}
}
public void Reset()
{
Stop();
secondsLeft = numberOfSecondsToCountdown;
if (secondsLeft < 0) secondsLeft = 0;
WhenCountdownTimerTick(new CountdownTimerEventArgs(secondsLeft, numberOfSecondsToCountdown, false));
}
public void Stop()
{
tmrTicks.Enabled = false;
}
public void Start()
{
if (secondsLeft <= 0)
{
secondsLeft = 0;
WhenCountdownTimerTick(new CountdownTimerEventArgs(secondsLeft, numberOfSecondsToCountdown, true));
}
else
{
tmrTicks.Enabled = true;
}
}
public delegate void CountdownTimerTickEventHandler(object sender, CountdownTimerEventArgs ea);
public event CountdownTimerTickEventHandler CountdownTimerTick;
protected virtual void WhenCountdownTimerTick(CountdownTimerEventArgs ea)
{
if (CountdownTimerTick != null)
{
CountdownTimerTick(this, ea);
}
}
}
public class CountdownTimerEventArgs : EventArgs
{
public string timeString = "";
public float procentOfTimeLeft = 0.0f;
public bool countdownFinished = false;
public CountdownTimerEventArgs(int secondsLeft, int SecondsToCountdown, bool isfinished)
{
countdownFinished = isfinished;
timeString = string.Format("{0:00}:{1:00}", secondsLeft / 60, secondsLeft % 60);
}
}
}
frmTest.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace SampleDelegateApp
{
public partial class frmTest : Form
{
CountdownTimer ctmrTest;
public frmTest()
{
InitializeComponent();
ctmrTest = new CountdownTimer(-44);
ctmrTest.CountdownTimerTick += new CountdownTimer.CountdownTimerTickEventHandler(ctmrTest_CountdownTimerTick);
}
void ctmrTest_CountdownTimerTick(object sender, CountdownTimerEventArgs ea)
{
lblTimer.Text = ea.timeString;
if (ea.countdownFinished) countdownEnd();
}
private void btnStart_Click(object sender, EventArgs e)
{
ctmrTest.Reset();
ctmrTest.Start();
}
void countdownEnd()
{
MessageBox.Show("Finish");
}
}
}
This is all about the observer pattern really. In .NET this has been made easy with delegates and stuff, in Java you must implement it by hand, using interfaces for the handlers and plain objects for the args. Look it up, you will find plenty of info.
I apparently used some words in my question that made it all unclear and/or persons who tried to help me completly missunderstood my needs as for this question. Putting word "delegate" here and there made it even more complexed. After one more question asked in other way i got my answer that made me able to answer my question:
How to achieve c# event behavior in java (Android)
I'm not saying that code below is up to the best coding standards, and example shown here got any use in real life project, but this is exactly what i was looking for. Below working example:
tester.java
public class tester{
public static void main(String[] args) {
test test1 = new test();
test1.start();
}
}
test.java
public class test implements CountdownTextTickListener {
public test() { }
public void start() {
CountdownText ctx = new CountdownText(100);
ctx.setListener(this);
ctx.Start();
}
#Override
public void CountdownTextTickEventFired(Object sender,
CountdownTextTickEventArgs eventArgs) {
System.out.println(eventArgs.TimeString);
if(eventArgs.isStopped) System.out.println("- END -");
}
}
CountdownTextTickListener.java
public interface CountdownTextTickListener {
void CountdownTextTickEventFired(Object sender, CountdownTextTickEventArgs eventArgs);
}
CountdownTextTickEventArgs.java
public class CountdownTextTickEventArgs {
public String TimeString = "";
public boolean isStopped = false;
public CountdownTextTickEventArgs(int seconds, boolean isStoppedState) {
TimeString = String.format("%02d:%02d",seconds/60, seconds % 60);
isStopped = isStoppedState;
}
}
CountdownText.java
import java.util.Timer;
import java.util.TimerTask;
public class CountdownText {
Timer tmrTicks = new Timer();
int secondsLeft = 0;
int numberOfSecondsToCountdown = 0;
boolean isWorking = false;
private CountdownTextTickListener listener = null;
public boolean getIsWorking(){
return isWorking;
}
public CountdownText(int seconds) {
if (secondsLeft < 0) secondsLeft = 0;
numberOfSecondsToCountdown = seconds;
secondsLeft = seconds;
}
void startTimer() {
isWorking = true;
fireEvent(secondsLeft, false);
tmrTicks = new Timer();
tmrTicks.scheduleAtFixedRate( new TimerTask(){
#Override
public void run(){
tickTimer();
}
}, 1000, 1000);
}
private void stopTimer() {
isWorking = false;
tmrTicks.cancel();
}
private void tickTimer() {
secondsLeft--;
if (secondsLeft >= 1)
{
fireEvent(secondsLeft, false);
}
else
{
Stop();
fireEvent(secondsLeft, true);
}
}
public void Reset() {
Stop();
secondsLeft = numberOfSecondsToCountdown;
fireEvent(secondsLeft, false);
}
public void Stop() {
stopTimer();
}
public void Start() {
if (secondsLeft <= 0)
{
secondsLeft = 0;
fireEvent(secondsLeft, true);
}
else
{
startTimer();
}
}
protected void fireEvent(int seconds, boolean isStoppedState) {
if (listener != null) {
Object sender = this;
CountdownTextTickEventArgs eventArgs = new CountdownTextTickEventArgs(seconds, isStoppedState);
listener.CountdownTextTickEventFired(sender, eventArgs);
}
}
public void setListener(CountdownTextTickListener listener) {
this.listener = listener;
}
}
From what I understand, there are actually ways to get your C# code to work in Android. I suggest looking at Mono for Android if your situation allows you to do your work in C#. Not only do you get to leave your code in C#, but you can also port it over to MonoTouch for iOS and .NET 4.5 for Windows Phone more easily. If not, I can't help you, but I'm sure someone more knowledgeable will.
I have a ListBox that is bound to a BindingList. The BindingList is built up when a third party application raises an event. I can see the BindingList being bound correctly... but nothing enters the ListBox. I have used the exact same logic with some of my own custom types and it usually works very well.
Form class
private Facade.ControlFacade _controlFacade;
public UavControlForm()
{
InitializeComponent();
_controlFacade = new UavController.Facade.ControlFacade();
UpdateEntityListBox();
}
private void UpdateEntityListBox()
{
lsbEntities.DataSource = _controlFacade.GetEntityTally();
lsbEntities.DisplayMember = "InstanceName";
}
Facade class
private Scenario _scenario;
public ControlFacade()
{
_scenario = new Scenario();
}
public BindingList<AgStkObject> GetEntityTally()
{
BindingList<AgStkObject> entityTally = _scenario.EntityTally;
return entityTally;
}
Scenario class
private static BindingList<IAgStkObject> _entityTally = new BindingList<AgStkObject>();
public Scenario()
{
if (UtilStk.CheckThatStkIsAvailable())
{
UtilStk.StkRoot.OnStkObjectAdded += new IAgStkObjectRootEvents_OnStkObjectAddedEventHandler(TallyScenarioObjects);
UtilStk.StkRoot.OnStkObjectDeleted += new IAgStkObjectRootEvents_OnStkObjectDeletedEventHandler(TallyScenarioObjects);
}
}
private void TallyScenarioObjects(object sender)
{
List<AgStkObject> tallyOfStkObjects = UtilStk.GetRunningTallyOfAllStkObjects();
List<string> stkObjectNames = UtilStk.GetInstanceNamesOfStkObjects(tallyOfStkObjects);
foreach (string stkObjectName in stkObjectNames)
{
if (!SearchFlightUavTallyByName(stkObjectName))
{
if (!SearchLoiterUavTallyByName(stkObjectName))
{
if (!SearchEntityTallyByName(stkObjectName))
{
int i = stkObjectNames.IndexOf(stkObjectName);
_entityTally.Add(tallyOfStkObjects[i]);
}
}
}
}
}
I can see the event fire from the third-party application - this adds an entity to _entityList as desired, but noothing is added to lsbEntities - why?
(jump right to the last example if you want to see it fixed etc)
Threads and "observer" patterns (such as the data-binding on winforms) are rarely good friends. You could try replacing your BindingList<T> usage with the ThreadedBindingList<T> code I used on a previous answer - but this combination of threads and UI is not an intentional use-case of winforms data-binding.
The listbox itself should support binding via list notification events (IBindingList / IBindingListView), as long as they arrive form the right thread. ThreadedBindingList<T> attempts to fix this by thread-switching on your behalf. Note that for this to work you must create the ThreadedBindingList<T> from the UI thread, after it has a sync-context, i.e. after it has started displaying forms.
To illustrate the point that listbox does respect list-change notifications (when dealing with a single thread):
using System;
using System.ComponentModel;
using System.Windows.Forms;
class Foo
{
public int Value { get; set; }
public Foo(int value) { Value = value; }
public override string ToString() { return Value.ToString(); }
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
using(var form = new Form())
using (var lst = new ListBox())
using (var timer = new Timer())
{
var data = new BindingList<Foo>();
form.Controls.Add(lst);
lst.DataSource = data;
timer.Interval = 1000;
int i = 0;
timer.Tick += delegate
{
data.Add(new Foo(i++));
};
lst.Dock = DockStyle.Fill;
form.Shown += delegate
{
timer.Start();
};
Application.Run(form);
}
}
}
and now with added threading / ThreadedBindingList<T> (it doesn't work with the regular BindingList<T>):
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
class Foo
{
public int Value { get; set; }
public Foo(int value) { Value = value; }
public override string ToString() { return Value.ToString(); }
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
using(var form = new Form())
using (var lst = new ListBox())
{
form.Controls.Add(lst);
lst.Dock = DockStyle.Fill;
form.Shown += delegate
{
BindingList<Foo> data = new ThreadedBindingList<Foo>();
lst.DataSource = data;
ThreadPool.QueueUserWorkItem(delegate
{
int i = 0;
while (true)
{
data.Add(new Foo(i++));
Thread.Sleep(1000);
}
});
};
Application.Run(form);
}
}
}
public class ThreadedBindingList<T> : BindingList<T>
{
private readonly SynchronizationContext ctx;
public ThreadedBindingList()
{
ctx = SynchronizationContext.Current;
}
protected override void OnAddingNew(AddingNewEventArgs e)
{
SynchronizationContext ctx = SynchronizationContext.Current;
if (ctx == null)
{
BaseAddingNew(e);
}
else
{
ctx.Send(delegate
{
BaseAddingNew(e);
}, null);
}
}
void BaseAddingNew(AddingNewEventArgs e)
{
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (ctx == null)
{
BaseListChanged(e);
}
else
{
ctx.Send(delegate
{
BaseListChanged(e);
}, null);
}
}
void BaseListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
}
}