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.
Related
I'm fairly new to C# and am trying to create a web browser for a specific function
I have Form1 (An invisible form) that calls Form2 (The browser) and monitor to make sure Form2 is always running and if it goes idle close and reopen Form2
I think I'm having an issue with threading, which I setup to run the timer (It's the only way I could work out)
I have determined that it only fails to launch Form2 when I try to call the function from inside the thread
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Browselite;
using System.Diagnostics;
using System.Threading;
namespace BrowseLite
{
public partial class Form1 : Form
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool SetProcessDPIAware();
public static Boolean IdleTimeoutEnabled { get; private set; }
public static int IdleTimeout { get; private set; }
public static Boolean ClearRunning { get; private set; }
public Form2 Browser { get; private set; }
public static Boolean programmaticClose { get; set; }
public static Boolean Form2Open { get; set; }
public Form1()
{
InitializeComponent();
}
[DllImport("user32.dll")]
public static extern Boolean GetLastInputInfo(ref tagLASTINPUTINFO plii);
public struct tagLASTINPUTINFO
{
public uint cbSize;
public Int32 dwTime;
}
private void Form1_Load(object sender, EventArgs e)
{
try
{
using (RegistryKey RootKey = Registry.CurrentUser.OpenSubKey("Software\\Policies\\BrowseLite"))
{
try
{
Form1.IdleTimeout = Int32.Parse(RootKey.GetValue("IdleTimeout", -1, RegistryValueOptions.None).ToString());
if (Form1.IdleTimeout <= 0)
{
Form1.IdleTimeoutEnabled = false;
}
else
{
Form1.IdleTimeoutEnabled = true;
}
}
catch
{
Form1.IdleTimeout = 0;
Form1.IdleTimeoutEnabled = false;
}
}
}
catch
{
Form1.IdleTimeout = 0;
Form1.IdleTimeoutEnabled = false;
}
Thread Timer = new Thread(new ThreadStart(MyTimer));
Browser = new Form2();
OpenBrowser();
Timer.Start();
}
private void MyTimer()
{
while (true)
{
FormCollection OpenForms = Application.OpenForms;
foreach (Form OpenForm in OpenForms)
{
if (OpenForm.Name.Contains("Form2"))
{
Form1.Form2Open = true;
}
}
if (!Form1.Form2Open)
{
Browser.ShowDialog();
Form1.Form2Open = true;
}
tagLASTINPUTINFO LastInput = new tagLASTINPUTINFO();
Int32 IdleTime;
LastInput.cbSize = (uint)Marshal.SizeOf(LastInput);
LastInput.dwTime = 0;
if (GetLastInputInfo(ref LastInput))
{
IdleTime = System.Environment.TickCount - LastInput.dwTime;
int IdleTimeSet = IdleTimeout * 60 * 1000;
if (Form1.IdleTimeoutEnabled)
{
if (IdleTime >= IdleTimeSet)
{
if (Form1.ClearRunning == false)
{
CloseBrowser();
OpenBrowser();
}
}
else
{
Form1.ClearRunning = false;
}
}
}
Thread.Sleep(1000 * 30); //Time in seconds (30)
}
}
private void CloseBrowser()
{
Form1.programmaticClose = true;
Browser.Close();
}
private void OpenBrowser()
{
Form1.programmaticClose = false;
Form1.Form2Open = true;
Browser.ShowDialog();
}
}
}
Any help would be appreciated, but as I said... I'm not good with this.
For anyone else that stumbles onto this, I found the answer myself
If setting a variable in the thread. Instead of using
Form1.Running = true;
instead use
BeginInvoke(new Action(() => Form1.Running = true), null);
And if calling a function from within the thread use
BeginInvoke(new InvokeDelegate(FUNCTION));
This seems to have completely fixed my issue
I have been recently learning C# and have a problem I just cant seem to wrap my head around. Please forgive me if this is noobish as I am very new to C# but my question is about delegates and invoke.
I have read many many tutorials online and watched many video tutorials about this as well but I am still getting the same error in my code and I just dont seem to grasp the subtleties. As I understand it a delegate is a pointer to a function and can be used to invoke that function from say another thread to update a textbox. I understand creating a delegate, that much I think I am doing right but when I invoke the delegate from a threat I always get the error Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.
The callback appears to be functional as it is calling the function that it is designed to however it does not seem to do it on the correct thread which I thought was the whole point of doing it this way. I know there is the option to set that warning break to false but I would rather learn what I am doing wrong and how to code this type of method properly. I appreciate any help, suggestions or answers you can provide as I am not sure any more of the tutorials are getting me any closer at this point to understanding where I have gone wrong.
My code is very basic and as I am just trying to understand the most basic concepts of properly coding for multithreading. Below is my code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Media;
using System.Threading;
using System.Reflection;
namespace WindowsFormsApplication3
{
//declair delegate name(vars) CORRECT
public delegate void Textboxdelegate(Int64 MyVar);
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void button1_Click(object sender, EventArgs e)
{
runme();
}
public void runme()
{
textBox1.Text = "87";
textupdate(44);
int target = 0;
Textboxdelegate TD = new Textboxdelegate(textupdate);
Number number = new Number(target, TD);
TD(11);
Thread thread1 = new Thread(new ThreadStart(number.worker));
thread1.Start();
}
public void textupdate(Int64 cntr)
{
textBox1.Text += cntr.ToString();
}
}
class Number
{
int _target;
Textboxdelegate _callbackMethod;
public Number(int target, Textboxdelegate TDD)
{
this._target = target;
this._callbackMethod = TDD;
}
public void worker()
{
Int64 counter = 0;
byte[] lifeforms = new byte[2146435071];
for (long y = 1; y <= 2146; y++)
{
for (long X = 1; X <= 1000000; X++)
{
lifeforms[X * y] = 20;
}
counter += 1;
if(_callbackMethod != null)
{
_callbackMethod(counter);
}
}
MessageBox.Show("Done!");
}
}
}
This crude example should do it for what you want to do:
//public delegate void Textboxdelegate(Int64 MyVar);
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public Action<Int64> Textboxdelegate;
public void runme()
{
textBox1.Text = "87";
textupdate(44);
int target = 0;
Textboxdelegate = textupdate;
Number number = new Number(target, Textboxdelegate,this);
Textboxdelegate(11);
Thread thread1 = new Thread(new ThreadStart(number.worker));
thread1.Start();
}
public void textupdate(Int64 cntr)
{
textBox1.Text += cntr.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
runme();
}
}
class Number
{
int _target;
Action<Int64> _callbackMethod;
Form1 frm;
public Number(int target, Action<Int64> act,Form1 frm)
{
this._target = target;
this._callbackMethod = act;
this.frm = frm;
}
public void worker()
{
Int64 counter = 0;
byte[] lifeforms = new byte[214643507];
for (long y = 1; y <= 2146; y++)
{
for (long X = 1; X <= 100000; X++)
{
lifeforms[X * y] = 20;
}
counter += 1;
if (_callbackMethod != null)
{
if (frm.InvokeRequired)
{
frm.Invoke(_callbackMethod,new object[]{counter});
}
else
{
_callbackMethod(counter);
}
}
}
MessageBox.Show("Done!");
}
}
Controls have thread-afinity,meaning they can only be accessed from the thread that created them,and you where accessing them by another thread.Now the Invoke method of the Control class(the base of form and all its controls)will allow you to safelly access them(very summarized explanation).
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.
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);
}
}
I'm working with a usb device. This Device receives messages and I don't know when or how often. The API that comes with the driver specifies a setreceiveCallBack function that gives a callback when the device receives a message.
But at random times or intervals I receive a callback on garbagecollected delegate exeption. I have searched for solutions to my problem but none of the solutions seem to work in my case.
The following is the biggest part of my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace CallBacktesting
{
public unsafe delegate void callBack(Form1.CANMsg *pmsg);
public partial class Form1 : Form
{
uint handle;
static WriteLog log = new WriteLog();
Boolean getCan = false;
static int frameCount = 0;
static CANMsg newmsg = new CANMsg();
callBack _setCallBack;
List<string> write = new List<string>();
public Form1()
{
InitializeComponent();
}
private void buttonOpen_Click(object sender, EventArgs e)
{
// Open connection
}
private void buttonClose_Click(object sender, EventArgs e)
{
// Close connection
}
private void buttonCallBack_Click(object sender, EventArgs e)
{
if (!getCan)
{
int rv;
unsafe
{
callBack _setCallBack = new callBack(call);
rv = canusb_setReceiveCallBack(handle, _setCallBack);
}
label1.Text = rv.ToString();
}
else
{
_setCallBack = null;
int rv = canusb_setReceiveCallBack(handle, _setCallBack);
GC.KeepAlive(_setCallBack);
label1.Text = rv.ToString();
}
}
public unsafe void call(CANMsg *pmsg)
{
newmsg = *pmsg;
update();
}
private void buttonExit_Click(object sender, EventArgs e)
{
GC.KeepAlive(_setCallBack);
Application.Exit();
}
[DllImport("canusbdrv.dll", EntryPoint = "canusb_setReceiveCallBack")]
public static extern int canusb_setReceiveCallBack(uint handle, callBack callBack);
unsafe private void timer_Tick(object sender, EventArgs e)
{
// update the form with received messages
}
public void update()
{
CANMsg msgrec = newmsg;
// Build str from messages with all data
write.Add(str);
log.logWrite(str);
frameCount++;
}
}
public class WriteLog
{
private void OpenFile()
{ }
public void logWrite(string log)
{ }
public void logAdd(string log)
{ }
private void logClose()
{ }
}
}
In your code, when you do
callBack setCallBack = new callBack(call);
rv = canusb_setReceiveCallBack(handle, call);
the delegate will be available for garbage collection after you invoke 'canusb_setReceiveCallBack' because no where in your code is the delegate referenced.
You could avoid this be storing it in a private field.
E.x.:
Class Form1
{
callBack _setCallBack;
private void buttonCallBack_Click(object sender, EventArgs e)
{
_setCallBack = new callBack(call);
rv = canusb_setReceiveCallBack(handle, _setCallBack);
}
}
But this may have some problems because each button click will create a new callback. This can be problematic if the previous callback needed to be referenced.
I think what you should do is refactor the code to use a SafeHandle to store the handle returned by canusb_Open.
I would design the class like this.
class CanUsbSafeHandle : SafeHandle
{
private EventHandler _receiveCallBack;
private readonly object _receiveCallBackLock = new object();
public event EventHandler ReceiveCallBack
{
add
{
lock (_receiveCallBackLock)
{
bool hasListeners = (_receiveCallBack != null);
_receiveCallBack += value;
//call canusb_setReceiveCallBack only when 1 or more listeners were added
//and there were previously no listeners
if (!hasListeners && (_receiveCallBack != null))
{
canusb_setReceiveCallBack(this, setCallBack);
}
}
}
remove
{
lock (_receiveCallBackLock)
{
bool hasListeners = (_receiveCallBack != null);
_receiveCallBack -= value;
//call canusb_setReceiveCallBack only when there are no more listeners.
if(hasListeners && (_receiveCallBack == null))
{
canusb_setReceiveCallBack(this, null);
}
}
}
}
public CanUsbSafeHandle()
: base(IntPtr.Zero, true)
{
}
public override bool IsInvalid
{
get { return handle == IntPtr.Zero; }
}
protected override bool ReleaseHandle()
{
return canusb_Close(handle);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
lock (_receiveCallBackLock)
{
_receiveCallBack = null;
}
}
base.Dispose(disposing);
}
}
That way, the SafeHandle will manage the 'receive callback' delegate's lifetime will be managed by the SafeHandle.
Is this correct / a typo?:
callBack setCallBack = new callBack(call);
rv = canusb_setReceiveCallBack(handle, call);
You appear to create an instance of callBack, but then pass something else to canusb_setReceiveCallBack - did you mean to pass setCallBack instead?
Also, on this line you are declaring setCallBack to be a local variable, and so even if you do pass setCallBack instead of call, you are still passing a locally scoped variable which will probably be garbage collected (I noticed that you do GC.KeepAlive(setCallBack);
to explicitly prevent this)