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)
Related
I have a WPF (.NET Framework 4.6) application that uses websocket-sharp (version 3.0.0) to create a websocket server.
I have a WebsocketServer and using EventHandler to tranfer event to MainWindow.xaml.cs but it not working. The MainWindow.xaml.cs listened to a RaiseOnScanDevice event but not any event invoked here.
I think this issue is relative to different thread. I try using Dispatcher.Invoke but it still not working.
System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() =>
{
RaiseOnScanDevice(this, new EventArgs());
}));
I found an issue (https://github.com/sta/websocket-sharp/issues/350) but the answers do not resolve my issue.
Please help me a solution for this issue.
WebsocketServer.cs file
public class WebsocketServer : WebSocketBehavior
{
private static readonly Lazy<WebsocketServer> lazyInstance = new Lazy<WebsocketServer>(() => new WebsocketServer());
public static WebsocketServer Instance
{
get
{
return lazyInstance.Value;
}
}
private const string TAG = "WebsocketServer";
private const string HOST_IP_ADDRESS = "127.0.0.2"; // localhost
private const int PORT = 38001;
public WebSocketServer socket;
private PacketHandler packetHandler = new PacketHandler();
public event EventHandler<EventArgs> RaiseOnScanDevice = new EventHandler<EventArgs>((a, e) => { });
public WebsocketServer()
{
Initialize();
}
public void Initialize()
{
socket = new WebSocketServer(IPAddress.Parse(HOST_IP_ADDRESS), PORT);
socket.AddWebSocketService<WebsocketServer>("/");
StartServer();
}
public void StartServer()
{
socket.Start();
}
public void StopServer()
{
socket.Stop();
}
protected override Task OnOpen()
{
return base.OnOpen();
}
protected override Task OnClose(CloseEventArgs e)
{
return base.OnClose(e);
}
protected override Task OnError(ErrorEventArgs e)
{
return base.OnError(e);
}
protected override Task OnMessage(MessageEventArgs e)
{
System.IO.StreamReader reader = new System.IO.StreamReader(e.Data);
string message = reader.ReadToEnd();
//Converting the event back to 'eventName' and 'JsonPayload'
PacketModel packet = packetHandler.OpenPacket(message);
HandleMessageFromClient(packet);
return base.OnMessage(e);
}
private void HandleMessageFromClient(PacketModel packet) {
var eventName = packet.EventName;
var data = packet.Data;
if (eventName == null || eventName.Equals(""))
{
return;
}
switch (eventName)
{
case SocketEvent.Hello:
Send("OK");
break;
case SocketEvent.ScanDevice:
ScanDevice();
break;
default:
break;
}
}
private void ScanDevice()
{
try
{
RaiseOnScanDevice(this, new EventArgs());
// or dispatch to Main Thread
System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() =>
{
RaiseOnScanDevice(this, new EventArgs());
}));
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
}
MainWindow.xaml.cs file
public partial class MainWindow : Window
{
public WebsocketServer WebsocketConnection
{
get { return WebsocketServer.Instance; }
}
public MainWindow()
{
InitializeComponent();
WebsocketConnection.RaiseOnScanDevice += SocketConnection_RaiseOnScanDevice;
}
private void SocketConnection_RaiseOnScanDevice(object sender, EventArgs e)
{
Console.WriteLine("SocketConnection_RaiseOnScanDevice");
}
The queue of messages is a good idea but you may want to use a lock to guard access to it. Most likely it won't be an issue but if you don't, you leave yourself open to the possibility of an error if the coroutine is reading from the queue as the websocket is writing to it. For example you could do something like this:
var queueLock = new object();
var queue = new Queue<MyMessageType>();
// use this to read from the queue
MyMessageType GetNextMessage()
{
lock (queueLock) {
if (queue.Count > 0) return queue.Dequeue();
else return null;
}
}
// use this to write to the queue
void QueueMessage(MyMessageType msg)
{
lock(queueLock) {
queue.Enqueue(msg);
}
}
I have a v4.0.0.1 implementation of Somdron's "Reliable Pub-Sub" pattern for communication between two parts of a new application. This application will have a "Server" (the engine that does all the heavy calculations) and "Clients" that will send requests and get information on progress back from the server.
The problem I have with my current version of "Reliable Pub-Sub" is that I don't seem to have a proper way for sending requests to the sever from the client. Let me start by showing you the code:
SERVER:
using NetMQ;
using NetMQ.Sockets;
using System;
using System.Linq;
namespace Demo.Messaging.Server
{
public class ZeroMqMessageServer : IDisposable
{
private const string WELCOME_MESSAGE = "WM";
private const string HEARTBEAT_MESSAGE = "HB";
private const string PUBLISH_MESSAGE_TOPIC = "PUB";
private readonly TimeSpan HEARTBEAT_INTERVAL = TimeSpan.FromSeconds(2);
private NetMQActor actor;
private NetMQTimer heartbeatTimer;
private XPublisherSocket publisher;
private NetMQPoller poller;
public ZeroMqMessageServer(string address)
{
Address = address;
actor = NetMQActor.Create(Start);
}
private void Start(PairSocket shim)
{
using (publisher = new XPublisherSocket())
{
publisher.SetWelcomeMessage(WELCOME_MESSAGE);
publisher.Bind(Address);
//publisher.ReceiveReady -= DropPublisherSubscriptions;
publisher.ReceiveReady += DropPublisherSubscriptions;
heartbeatTimer = new NetMQTimer(HEARTBEAT_INTERVAL);
heartbeatTimer.Elapsed += OnHeartbeatTimeElapsed;
shim.ReceiveReady += OnShimReceiveReady;
shim.SignalOK(); // Let the actor know we are ready to work.
poller = new NetMQPoller() { publisher, shim, heartbeatTimer };
poller.Run();
}
}
private void DropPublisherSubscriptions(object sender, NetMQSocketEventArgs e)
{
publisher.SkipMultipartMessage();
}
private void OnHeartbeatTimeElapsed(object sender, NetMQTimerEventArgs e)
{
publisher.SendFrame(HEARTBEAT_MESSAGE);
}
private void OnShimReceiveReady(object sender, NetMQSocketEventArgs e)
{
var socket = e.Socket;
string command = socket.ReceiveFrameString();
if (command == PUBLISH_MESSAGE_TOPIC)
{
// Forward the message to the publisher.
NetMQMessage message = socket.ReceiveMultipartMessage();
publisher.SendMultipartMessage(message);
}
else if (command == NetMQActor.EndShimMessage)
{
// Dispose command received, stop the poller.
poller.Stop();
}
}
public void PublishMessage(NetMQMessage message)
{
// We can use actor like NetMQSocket and publish messages.
actor.SendMoreFrame(PUBLISH_MESSAGE_TOPIC)
.SendMultipartMessage(message);
}
public string Address { get; private set; }
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
actor?.Dispose();
publisher?.Dispose();
poller?.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
}
CLIENT:
using NetMQ;
using NetMQ.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using Messaging.Helpers;
namespace Demo.Messaging.Client
{
public class ZeroMqMessageClient : IDisposable
{
private string SUBSCRIBE_COMMAND = "S";
private const string WELCOME_MESSAGE = "WM";
private const string HEARTBEAT_MESSAGE = "HB";
private const string PUBLISH_MESSAGE_TOPIC = "PUB";
private readonly TimeSpan TIMEOUT = TimeSpan.FromSeconds(5);
private readonly TimeSpan RECONNECTION_PERIOD = TimeSpan.FromSeconds(5);
private readonly string[] addressCollection;
private List<string> subscriptions = new List<string>();
private NetMQTimer timeoutTimer;
private NetMQTimer reconnectionTimer;
private NetMQActor actor;
private SubscriberSocket subscriber;
private PairSocket shim;
private NetMQPoller poller;
public ZeroMqMessageClient(params string[] addresses)
{
addressCollection = addresses;
actor = NetMQActor.Create(Start);
}
private void Start(PairSocket shim)
{
this.shim = shim;
shim.ReceiveReady += OnShimReceiveReady;
timeoutTimer = new NetMQTimer(TIMEOUT);
timeoutTimer.Elapsed += OnTimeoutTimerElapsed;
reconnectionTimer = new NetMQTimer(RECONNECTION_PERIOD);
reconnectionTimer.Elapsed += OnReconnectionTimerElapsed;
poller = new NetMQPoller() { shim, timeoutTimer, reconnectionTimer };
shim.SignalOK();
Connect();
poller.Run();
if (subscriber != null)
subscriber.Dispose();
}
private void Connect()
{
using (NetMQPoller tmpPoller = new NetMQPoller())
{
List<SubscriberSocket> socketCollection = new List<SubscriberSocket>();
SubscriberSocket connectedSocket = null;
EventHandler<NetMQSocketEventArgs> messageHandler = (s, e) =>
{
connectedSocket = (SubscriberSocket)e.Socket;
tmpPoller.Stop();
};
// We cancel the poller without setting the connected socket.
NetMQTimer tmpTimeoutTimer = new NetMQTimer(TIMEOUT);
tmpTimeoutTimer.Elapsed += (s, e) => tmpPoller.Stop();
tmpPoller.Add(tmpTimeoutTimer);
// Attempt to subscribe to the supplied list of addresses.
foreach (var address in addressCollection)
{
SubscriberSocket socket = new SubscriberSocket();
socketCollection.Add(socket);
//socket.ReceiveReady -= messageHandler;
socket.ReceiveReady += messageHandler;
tmpPoller.Add(socket);
// Subscribe to welcome messages.
socket.Subscribe(WELCOME_MESSAGE);
socket.Connect(address);
}
tmpPoller.Run(); // Block and wait for connection.
// We should have an active socket/conection.
if (connectedSocket != null)
{
// Remove the connected socket from the collection.
socketCollection.Remove(connectedSocket);
ZeroMqHelpers.CloseConnectionsImmediately(socketCollection);
// Set the active socket.
subscriber = connectedSocket;
//subscriber.SkipMultipartMessage(); // This skips the welcome message.
// Subscribe to subscriptions.
subscriber.Subscribe(HEARTBEAT_MESSAGE);
foreach (var subscription in subscriptions)
subscriber.Subscribe(subscription);
// Remove start-up handler, now handle messages properly.
subscriber.ReceiveReady -= messageHandler;
subscriber.ReceiveReady += OnSubscriberReceiveReady;
poller.Add(subscriber);
// Reset timers.
timeoutTimer.Enable = true;
reconnectionTimer.Enable = false;
}
else // We need to attempt re-connection.
{
// Close all existing connections.
ZeroMqHelpers.CloseConnectionsImmediately(socketCollection);
timeoutTimer.Enable = false;
reconnectionTimer.Enable = true;
}
}
}
private void OnShimReceiveReady(object sender, NetMQSocketEventArgs e)
{
string command = e.Socket.ReceiveFrameString();
if (command == NetMQActor.EndShimMessage)
{
poller.Stop();
}
else if (command == SUBSCRIBE_COMMAND)
{
string topic = e.Socket.ReceiveFrameString();
subscriptions.Add(topic);
if (subscriber != null)
subscriber.Subscribe(topic);
}
}
private void OnTimeoutTimerElapsed(object sender, NetMQTimerEventArgs e)
{
if (subscriber != null)
{
poller.Remove(subscriber);
subscriber.Dispose();
subscriber = null;
Connect();
}
}
private void OnReconnectionTimerElapsed(object sender, NetMQTimerEventArgs e)
{
// We re-attempt connection.
Connect();
}
private void OnSubscriberReceiveReady(object sender, NetMQSocketEventArgs e)
{
// Here we just forwward the message on to the actor.
var message = subscriber.ReceiveMultipartMessage();
string topic = message[0].ConvertToString();
// Let us see what is in the message.
if (message.Count() > 1)
{
string content = message[1].ConvertToString();
Console.WriteLine($"ZMQ_ALT - {topic}:: {content}");
}
if (topic == WELCOME_MESSAGE)
{
// Disconnection has occurred we might want to restore state from a snapshot.
}
else if (topic == HEARTBEAT_MESSAGE)
{
// We got a heartbeat, lets postponed the timer.
timeoutTimer.Enable = false;
timeoutTimer.Enable = true;
}
else
{
shim.SendMultipartMessage(message);
}
}
public void Subscribe(string topic)
{
actor.SendMoreFrame(SUBSCRIBE_COMMAND).SendFrame(topic);
}
public NetMQMessage ReceiveMessage()
{
return actor.ReceiveMultipartMessage();
}
public void PublishMessage(NetMQMessage message)
{
actor.SendMoreFrame(PUBLISH_MESSAGE_TOPIC)
.SendMultipartMessage(message);
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
actor?.Dispose();
subscriber?.Dispose();
shim?.Dispose();
poller?.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
}
Now, I can send messages from the server to the client which is awesome and the client using the following code from the main method in two separate console applications
Program.cs for SERVER:
class Program
{
static void Main(string[] args)
{
using (ZeroMqMessageServer server = new ZeroMqMessageServer("tcp://127.0.0.1:6669"))
{
while (true)
{
NetMQMessage message = new NetMQMessage();
message.Append("A");
message.Append(new Random().Next().ToString());
server.PublishMessage(message);
Thread.Sleep(200);
}
}
}
}
Program.cs for CLIENT:
class Program
{
static void Main(string[] args)
{
Task.Run(() =>
{
using (ZeroMqMessageClient client = new ZeroMqMessageClient("tcp://127.0.0.1:6669"))
{
client.Subscribe(String.Empty);
while (true) { }
}
});
Console.ReadLine();
}
}
The client correctly auto-detects dropped connections and reconnects, fantastic little pattern.
However, this pattern out-of-the-box does not allow the client to send messages to the server. So in the client I have added the following code
public void PublishMessage(NetMQMessage message)
{
actor.SendMoreFrame(PUBLISH_MESSAGE_TOPIC)
.SendMultipartMessage(message);
}
and in the client I have changed the publisher.ReceiveReady += DropPublisherSubscriptions; event handler to
private void DropPublisherSubscriptions(object sender, NetMQSocketEventArgs e)
{
var message = e.Socket.ReceiveMultipartMessage();
string topic = message[0].ConvertToString();
Console.WriteLine($"TOPIC = {topic}");
// Let us see what is in the message.
if (message.Count() > 1)
{
string content = message[1].ConvertToString();
Console.WriteLine($"TEST RECIEVE FROM CLIENT - {topic}:: {content}");
}
publisher.SkipMultipartMessage();
}
but this does not seem to handle my messages. It receives the heartbeats and welcome messages, but I am not doing this right.
How can I enable/facilitate the client to talk to the server without breaking what I have already?
Thanks for your time.
I currently have an installation "framework" that does specific things. What I need now to do is be able to call my form in parallel with my script. Something like this:
InstallationForm f = new InstallationForm();
Application.Run(f);
InstallSoftware(f);
private static void InstallSoftware(InstallationForm f) {
f.WriteToTextbox("Starting installation...");
Utils.Execute(#"C:\temp\setup.msi", #"-s C:\temp\instructions.xml");
...
f.WriteToTextbox("Installation finished");
The current way I can do this is by adding the Form.Shown handler in InstallSoftware, but that seems really messy. Is there anyway I can do this better?
Your code will not work, because Application.Run(f) returns not until the form was closed.
You may use a simplified Model/View/Controller pattern. Create an InstallationFormController class that has several events, e.g. for textual notifications to be written to your textbox. The InstallationForm registers on these events in it's OnLoad() method and then calls InstallationFormController.Initialize(). That method starts your installation (on a worker thread/task). That installation callback method fires several text events.
InstallationForm f = new InstallationForm(new InstallationFormController());
Application.Run(f);
internal class InstallationFormController
{
public event EventHandler<DataEventArgsT<string>> NotificationTextChanged;
public InstallationFormController()
{
}
public void Initialize()
{
Task.Factory.StartNew(DoInstallation);
}
private void DoInstallation()
{
...
OnNotificationTextChanged(new DataEventArgsT<string>("Installation finished"));
}
private void OnNotificationTextChanged(DataEventArgsT<string> e)
{
if(NotificationTextChanged != null)
NotificationTextChanged(this, e);
}
}
public class DataEventArgsT<T> : EventArgs
{
...
public T Data { get; set; }
}
internal class InstallationForm : Form
{
private readonly InstallationFormController _controller;
public InstallationForm()
{
InitializeComponent();
}
public InstallationForm(InstallationFormController controller) : this()
{
if(controller == null)
throw new ArgumentNullException("controller")
_controller = controller;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_controller.NotificationTextChanged += Controller_NotificationTextChanged;
_controller.Initialize();
}
protected virtual void Controller_NotificationTextChanged(object sender, DataEventArgsT<string> e)
{
if(this.InvokeRequired)
{ // call this method on UI thread!!!
var callback = new EventHandler<DataEventArgsT<string>>(Controller_NotificationTextChanged);
this.Invoke(callback, new object[] {sender, e});
}
else
{
_myTextBox.Text = e.Data;
}
}
...
}
I am trying to call a class called ThreadCreator() and then one of its methods called CreateWindow() and have it return a new window it created so that I can use that object somewhere else to invoke a method on it.
Here's the breakdown. My thread creator class that pops a new wpf window on a new thread:
using System;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.ComponentModel;
using System.Windows;
namespace Windamow
{
public static class ThreadCreator
{
private static NewWindow W;
public static NewWindow CreateWindow()
{
string appName = "";
try
{
appName = Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().Location);
const string IE_EMULATION = #"Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION";
using (var fbeKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(IE_EMULATION, true))
{
fbeKey.SetValue(appName, 9000, Microsoft.Win32.RegistryValueKind.DWord);
}
}
catch (Exception ex)
{
MessageBox.Show(appName + "\n" + ex.ToString(), "Unexpected error setting browser mode!");
}
Thread t = new Thread(ThreadProc);
t.SetApartmentState(ApartmentState.STA);
t.Start();
return W;
}
private static void ThreadProc(object obj)
{
W = new NewWindow();
W.ShowDialog();
}
}
}
New Window.xaml.cs looks like this:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Windamow
{
/// <summary>
/// Interaction logic for NewWindow.xaml
/// </summary>
public partial class NewWindow : Window
{
string _message;
public string Message
{
get { return _message; }
set
{
_message = value;
this.Dispatcher.Invoke(() => { this.webBrowser.NavigateToString(_message); });
}
}
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
IntPtr _handle;
public IntPtr Handle { get { return _handle; } }
int _pid;
public int PID { get { return _pid; } }
public NewWindow()
{
InitializeComponent();
}
private void Window_Closed(object sender, EventArgs e)
{
WindowNotifier.OnIamClosed(Handle);
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
WindowNotifier.OnIamClosing(Handle);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_pid = Process.GetCurrentProcess().Id;
_handle = GetForegroundWindow();
WindowNotifier.OnIamCreated(Handle);
}
}
}
What I am trying to do with the window is invoke a NavigateToString() method every time a method called CheckIfWindowOpen() is run. It will basically check if window is open and either open it and execute NavigateToString() or it will just execute NavigateToString() with possibly new inputs (refresh).
Here's how I am calling it:
namespace Windamow
{
public class Win32F
{
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
public class DynamoDataVizNodes
{
[DllImport("user32.dll", SetLastError = true)]
public static extern int GetLastError();
// test html string
public static string HTMLString = "<html></html>";
IntPtr HandleOfWindowOnNewThread;
void WindowNotifier_IamCreatedEvent(IntPtr handle)
{
HandleOfWindowOnNewThread = handle;
Debug.WriteLine(string.Format("I am created : {0}", handle));
}
void WindowNotifier_IamClosedEvent(IntPtr handle)
{
if (HandleOfWindowOnNewThread == handle)
HandleOfWindowOnNewThread = IntPtr.Zero;
Debug.WriteLine(string.Format("I am closed : {0}", handle));
}
void WindowNotifier_IamClosingEvent(IntPtr handle)
{
Debug.WriteLine(string.Format("I am closing : {0}", handle));
}
public static NewWindow window;
public void CheckIfWindowOpen(string SourceString)
{
WindowNotifier.IamClosingEvent += WindowNotifier_IamClosingEvent;
WindowNotifier.IamClosedEvent += WindowNotifier_IamClosedEvent;
WindowNotifier.IamCreatedEvent += WindowNotifier_IamCreatedEvent;
if (HandleOfWindowOnNewThread == IntPtr.Zero)
{
// create new window and set browser
window = ThreadCreator.CreateWindow();
window.Dispatcher.Invoke(() => { window.webBrowser.NavigateToString(SourceString); });
}
else
{
// only set browser
window.Dispatcher.Invoke(() => { window.webBrowser.NavigateToString(SourceString); });
}
}
}
}
The error happens in the CheckIfWindowOpen() method when I am trying to return a NewWindow object to then invoke a NavigateToString() method on it. It basically returns null. Is there a way to make ThreadCreator.CreateWindow() return the created window?
Thanks!
Solution1 : Without storing reference to NewWindow in MainWindow. Instead raising RefreshBrowser event from MainWindow.
Dropbox link
Solution2 : Storing reference to NewWindow in MainWindow :
New solution changed to your requirements :
Dropbox link
Thread creation routine doesn't wait for ThreadProcedure to run, so return value will be null always. But you can use this value in say some other event handler. Because once this routine finishes, variable window will contain valid value.
However this is not proper way to do what you are trying to do.
There is a cleaner way in today's event driven programming world. Remember, controls/windows fire events and we access the sender and get additional info using corresponding eventargs. Eg;
private void Window_Loaded(object sender, RoutedEventArgs e)
Following same pattern as above, we modify our delegates like below :
public static class WindowNotifier2
{
public static event CreatedDelegateCallback IamCreatedEvent;
public delegate void CreatedDelegateCallback(object sender, WindowNotifierEventArgs args);
public static event ClosingDelegateCallback IamClosingEvent;
public delegate void ClosingDelegateCallback(object sender, WindowNotifierEventArgs args);
public static event ClosedDelegateCallback IamClosedEvent;
public delegate void ClosedDelegateCallback(object sender, WindowNotifierEventArgs args);
public static void OnIamCreated(object sender, WindowNotifierEventArgs args)
{
if (IamCreatedEvent != null)
IamCreatedEvent(sender, args);
}
public static void OnIamClosing(object sender, WindowNotifierEventArgs args)
{
if (IamClosingEvent != null)
IamClosingEvent(sender, args);
}
public static void OnIamClosed(object sender, WindowNotifierEventArgs args)
{
if (IamClosedEvent != null)
IamClosedEvent(sender, args);
}
}
public class WindowNotifierEventArgs : EventArgs
{
public IntPtr WindowHandle { get; set; }
}
...
NewWindow.xaml.cs
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_pid = Process.GetCurrentProcess().Id;
_handle = GetForegroundWindow();
WindowNotifier2.OnIamCreated(this, new WindowNotifierEventArgs() { WindowHandle = _handle });
}
MainWindow.xaml.cs
private void btnCheckNewWindow_Click(object sender, RoutedEventArgs e)
{
if (HandleOfWindowOnNewThread == IntPtr.Zero)
{
WindowNotifier2.IamCreatedEvent += (object source, WindowNotifierEventArgs args) =>
{
_newWindow = (NewWindow)source;
HandleOfWindowOnNewThread = args.WindowHandle;
Debug.WriteLine(string.Format("I am created : {0}", args.WindowHandle));
_newWindow.tbMsgFromMainWindow.Dispatcher.InvokeAsync(() => {
_newWindow.tbMsgFromMainWindow.Text = "I created you ! I am your creator.";
});
};
ThreadCreator.CreateWindow();
}
else
{
_newWindow.tbMsgFromMainWindow.Dispatcher.InvokeAsync(() => {
_newWindow.tbMsgFromMainWindow.Text = "I can see you are already running !";
});
}
}
W is only set on the another thread and when you return W it most like is still null. Besides that why do you try to run a new thread as dispatcher; have a look at the dispatcher run method.
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.