I have a problem concerning following piece of code (C#):
stockTickerHub.On("notify", () =>
Context.Post(delegate
{
mainForm.textBox1.Text += "Notified!\n";
}, null)
);
Above code runs in an async Sub in a VB.NET Module. I am not allowed to access the Text-Property of textBox1. How does the above code look like in VB.NET?
I tried:
myHub.On(Of String, String)("addMessagea", _
Sub(nam, param)
mainForm.textBox1.Text = param.ToString()
Console.WriteLine("Should have append '" + nam.ToString() + param.ToString() + "'")
End Sub)
SignalR is a framework released by Microsoft.
Thanks in advance!
You need to use the Invoke method on the control. It will make sure that the delegate you supply is invoked on the main thread.
This is one of the reasons why WPF is nicer to work with, when you data-bind and NotifyPropertyChanged triggers the framework will fix that for you
Related
How to update data in GUI with messages that are being received by a thread of another class ?
I have a class with a thread that receives data from a server. This thread raises an event whenever it gets a message from the server. This event is handled inside the Starter Class (main class which contains the GUI).
The event handler (say DisplayData() has to display the message received by the other class.
My code is like this
Class GUI
receiverObj = New Receiver()
Addhandler receiverObj.MessageAlert, Addressof DisplayData
...
...
Sub DisplayData()
Dim str As receiverObj.ReceiveData
lbEvents.Add.Items(str) ' lbEvents is a ListBox inside the GUI that displays messages from Receiver
End Sub
End Class
Class Receiver
Public Event MessageAlert()
Sub New ()
MyTcpClient = New TcpClient(hostIP, port)
MyTcpClient.GetStream.BeginRead(ReceiveData, 0, PacketSize, AddressOf ReceiveStream, Nothing)
End Sub
Public Sub ReceiveStream(ByVal ar As IAsyncResult)
Dim ByteCount As Integer
Try
ByteCount = MyTcpClient.GetStream.EndRead(ar)
Dim t As New Threading.Thread(Sub() RaiseEvent MessageAlert())
MyTcpClient.GetStream.BeginRead(ReceiveData, 0, PacketSize, AddressOf ReceiveStream, Nothing)
End Sub
End Class
The Window crashes or hangs and the listbox does not display data. Throws exception saying
Cross-thread operation not valid: Control xxx accessed from a thread other than the thread it was created on.
Can anybody suggest a way to fix this error ?
How to update data in GUI with messages that are being received by a thread of another class ?
Updates to GUI elements of a Windows application must take place on the thread that created the GUI.
To fix this, there's a method called Invoke that allows you to fire a delegate that can ensure control is passed to the GUI thread and perform the update you are attempting.
You need a few things to make this work:
A Delegate type , such as
Delegate Sub MyGUIUpdateDelegate()
A variable of the type of your delegate
Public myGUIUpdateDelegate as MyGUIUpdateDelegate
A method having a signature that matches the delegate and does the work:
Public Sub MyGuiEventHandler()
' Do work on proper GUI thread, via Control.Invoke,
' such as listbox population
If (Me.InvokeRequired) Then
Me.Invoke( myGUIUpdateDelegate)
Else
// do control specific work, we're on the GUI thread here
End If
End Sub
An assignment of the event handler to your delegate:
myGUIUpdateDelegate = New MyGuiUpdateDelegate(AddressOf myForm.MyGuiEventHandler)
A call to your updater method via Control.Invoke to the proper thread from the event thread (assuming
your form instance variable is named myForm):
myForm.Invoke(myForm.myGUIUpdateDelegate);
That's at least a framework that should help you get started. The idea is that the background thread that wants to induce the update should not (and in reality, cannot) make direct GUI updates. The proper way to initiate a context switch to the GUI thread is by calling the Invoke method to call the GUI updater on the proper GUI thread.
Additionally, if you need to pass parameters to your delegate, simply alter the signature of the Delegate you define to include the parameter(s), and modify the Invoke method to provide the arguments in the handler, and the 2nd argument to Invoke.
Hope this helps.
I am new to creating WCF projects as well as windows phone 7.
I created a simple method in WCF which just returns a list of an object.
public List<Sticky> GetSticky()
{
return stickys;
}
I then used it very simply
Sticky[] test = client.GetSticky();
When I import the WCF dll via a service reference into a console app the method acts how it should. When I import the method into a Windows Phone 7 application it become an async method (not sure what this means)and doesnt return a list, it comes up void.
client.GetStickyAsync();
If anyone can help explain what is going on and help me to be a little less confused.
Silverlight wants you to avoid making blocking service calls on the UI thread, so it forces you to use the non-blocking, async version of WCF method calls. This means that the call returns immediately and you must get the result of the call with the related event. What you need to do is register an event handler before you make the call.
client.GetStickyCompleted
+= new EventHandler<ServiceClient.GetStickyCompletedEventArgs>(client_GetStickyCompleted);
client.GetStickyAsync();
The result of your method call is one of the parameters passed into the event handler, like such
void client_GetStickyCompleted(object sender, ServiceClient.GetStickyCompletedEventArgs e)
{
List<Sticky> retList = e.Result;
}
I have a class written in C#. In it I want to run a certain function in parallel on a list. After it completes on each item I would like to update a progress bar. However, I get very odd behavior from my program. It executes the event and reaches my sub but never proceeds to actually execute any code. Instead it just freezes. (I've mixed vb.net and c#. It will be rewritten at some point)
so in my windows form I call
progressBar.Visible = True
progressBar.Value = 0
progressBar.Maximum = dataGrid.SelectedRows.Count
AddHandler quoteManager.refreshStarted, AddressOf progressBarCounter
quoteManager.refreshAllAsync(list)
and the event is simply
Private Sub progressBarCounter()
Me.Invoke(Sub()
If progressBar.Value = progressBar.Maximum Then
progressBar.Visible = False
Else
progressBar.Value += 1
End If
End Sub)
End Sub
and in the quote manager class I have this defined.
public event Action refreshStarted;
public void refreshAllAsync(List<BindableQuote> bindableQuotes)
{
bindableQuotes.AsParallel()
.WithDegreeOfParallelism(10)
.ForAll((quote) => {
quote.refreshAll();
if (refreshStarted != null) { refreshStarted(); }
});
}
So for some reason I get it to enter progressBarCounter on each item in the list but it never exists. Instead it just keeps the form frozen.
I am not exactly sure this is what is happening, but it looks like progressBarCounter is blocking because you are calling Invoke. Should you be using BeginInvoke instead? Using BeginInvoke might solve the deadlock issue. See this post: What's the difference between Invoke() and BeginInvoke()
What appears to be happening here is that you access UI objects from multiple threads.
That's not supported. You'll have to run this code on a worker thread, and let it somehow accumulate progress, and send messages back to the UI thread. The BackgroundWorker class can help you implement the marshalling back to the UI thread.
Ok it looks likes like I have stumbled upon a strange timing issue... I made a quick SQL wrapper class for executing sql statements. However after .execute() is called, the SQLEvent.RESULT event is never fired, but the new entry in the DB is created as it should be. The really really odd part is if I put a setTimeout() just after calling execute() the event fires as expected.. I hope I'm missing something really obvious here... Here is a link to an example air app:
http://www.massivepoint.com/airsqltest/AIRSQL.zip
And here is the code to the wrapper class:
if you look down at line 51 in the SQLRequest class, you will see the commented out setTimeout() method. To make everything work, just uncomment that line.. but to me this doesn't make any sense...
anyone have any thoughts? I'm totally stumped here...
package com.jac.sqlite
{//Package
import flash.data.SQLConnection;
import flash.data.SQLStatement;
import flash.events.EventDispatcher;
import flash.events.SQLErrorEvent;
import flash.events.SQLEvent;
import flash.utils.setTimeout;
public class SQLRequest extends EventDispatcher
{//SQLRequest Class
private var _callback:Function;
private var _dbConn:SQLConnection;
private var _query:String;
private var _params:Object;
private var _statement:SQLStatement;
public function SQLRequest(callback:Function, connection:SQLConnection, query:String, parameters:Object=null):void
{//SQLRequest
trace("Creating new SQL Request");
_callback = callback;
_dbConn = connection;
_query = query;
_params = parameters;
_statement = new SQLStatement();
_statement.sqlConnection = _dbConn;
_statement.text = _query;
if (_params != null)
{//assign
for (var param:String in _params)
{//params
trace("Setting Param: " + param + " to: " + _params[param]);
_statement.parameters[param] = _params[param];
}//params
}//assign
//setup events
_statement.addEventListener(SQLEvent.RESULT, handleResult, false, 0, true);
_statement.addEventListener(SQLErrorEvent.ERROR, handleError, false, 0, true);
}//SQLRequest
public function startLoad():void
{//execute
_statement.execute();
//setTimeout(handleTimeOut, 10000);
}//execute
//TEMP
private function handleTimeOut():void
{//handleTimeOut
trace("Executing: " + _statement.executing + " / " + executing);
}//handleTimeOut
private function handleResult(e:SQLEvent):void
{//handleResult
trace("Good SQL Request");
_callback(e);
dispatchEvent(e);
}//handleResult
private function handleError(e:SQLErrorEvent):void
{//handleError
trace("SQL Error: " + e.errorID + ": " + e.error);
//dispatchEvent(e);
}//handleError
public function get executing():Boolean
{//get executing
return _statement.executing;
}//get executing
public function get query():String { return _query; }
public function get statement():SQLStatement { return _statement; }
}//SQLRequest Class
}//Package
I think what you're missing here is garbage collection.
Haven't tested your code, but this could certainly be the source of the problem.
var sqlReq:SQLRequest = new SQLRequest(handleResult, _dbConn, sql);
sqlReq.startLoad();
The reference sqlReq is local to the function and becomes unreacheable when the function returns. That makes it collectable. I guess there must be some code in the AIR runtime that collects garbage more agressively when there are sql connections involved. Because generally, you'll get away with not storing a ref to your object (at least in a web based environment, in my experience; this is a bug in such code, nevertheless; you just have to be in a bad day to experience it).
The setTimeout masks this problem (or almost solves it, although in an unintended way), because the setTimeout function uses a Timer internally. Running timers are not collected. So, the timer is alive and kicking and has a reference to your SQLRequest instance, which makes it reacheable, and so, not elligible for collection. If your DB call takes longer than the timeout though, you're back in the same situation.
To solve this, store a ref to the object and dispose it properly when you're done.
Edit
Another option, if you don't want to change the way you calling code works, is storing a ref to the instance in a class-scoped (i.e. static) dictionary for the duration of the call (this dictionary shoul not use weak referenced keys for obvious reasons).
You are adding a hidden side effect to your method, which is not a sign of good design in general, but as long as you remove it when the call to the DB is finished (whether it succeded or not), you're safe, so I think the problem is more of style than anything else.
What I mean is something like this:
private static var _dict:Dictionary = new Dictionary();
public function startLoad():void
{//execute
_statement.execute();
// add a self reference to dict so the instance won't be collected
// do this in the last line, so if we have an exception in execute, this
// code will not run (or add a try/catch if you want, but this is simpler
// and cleaner, IMO
addToDict();
}//execute
private function handleResult(e:SQLEvent):void
{//handleResult
// remove the self reference before running any other code
// (again, this is in case the code that follows throws)
removeFromDict();
trace("Good SQL Request");
_callback(e);
dispatchEvent(e);
}//handleResult
private function handleError(e:SQLErrorEvent):void
{//handleError
// same comment as handleResult
removeFromDict();
trace("SQL Error: " + e.errorID + ": " + e.error);
//dispatchEvent(e);
}//handleError
private function addToDict():void {
_dict[this] = true;
}
private function removeFromDict():void {
if(_dict[this]) {
delete _dict[this];
}
}
I have a .net class library with a com class that calls a form.
I want to to SetCompatibleTextRenderingDefault(false) to ensure the form fonts look nice.
If I run the command in the class constructor I get the following error:
SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application.
Where can/should I run this? Surely there is no earlier place than sub New!
Thank in advance
Jon
Edit1: To clarify, I get this error when initiating the class from a .net test harness, if I call it from a VB6 app then I simply get "Automation Error"
Edit2: Is the answer that I cannot use SetCompatibleTextRenderingDefault in a com class when calling from a vb6 app?? Maybe it's the "parent" app that needs to call this method and as such a vb6 app cannot?
Edit3: Maybe I am asking this question in the wrong way! - Maybe the question is: how can I make the fonts look nice in a .net class library form called from a vb6 app?
A possible workaround would be to set the property manually on all buttons and labels in the form constructor:
public Form1()
{
InitializeComponent();
DisableCompatibleTextRendering(this);
}
private static void DisableCompatibleTextRendering(Control c)
{
var button = (c as ButtonBase);
var label = (c as Label);
if (button != null)
{
button.UseCompatibleTextRendering = false;
}
if (label != null)
{
label.UseCompatibleTextRendering = false;
}
foreach (var child in c.Controls.Cast<Control>())
{
DisableCompatibleTextRendering(child);
}
}
Place this inside the application startup code before the first window is created. Under C# this would be the main routine that then creates the initial window.