WPF threads and tasks on textchanged event - c#

Using Visual Studio 2012 ulti, C# .NET WPF.
Using Tasks in my code on winforms used to be simple.
All I would do is create a delegate, create a function for my code, create a task and the event would be a simple button. Easy stuff. Problem I have is the following...
Create a thread as per-usual But the event will be on text changed.
The problem im having is thinking about the logic, if I simply change the event I cant see this working as the user could type faster than the code could run ( in this case an sql query select statement). There for it would try to run many tasks which I don't even think would work.
Basically User enters text box that used for searching an account by name or number.
In this textbox I would like to thread the entire process.
The only solution I can think of is as the text changes if there is a thread still running stop that thread and create the new one, but not sure if thats a clean way of doing it as its a sql stored procedure ill be calling.
So any body got a solution to this?
If you need any more info just ask. Ill also provide some code that currently works to give you an understanding if needed...
Set Invoke method up:
private void SetDataGrid(bool AutoGenerateColumns, Object DataSource, String DataMember, DataGridViewAutoSizeColumnsMode Mode)
{
if (this.ParetoGrid.InvokeRequired)
{
this.ParetoGrid.Invoke(new Action<bool, Object, String, DataGridViewAutoSizeColumnsMode>(SetDataGrid),
AutoGenerateColumns, DataSource, DataMember, Mode);
}
else
{
this.ParetoGrid.AutoGenerateColumns = AutoGenerateColumns;
this.ParetoGrid.DataSource = DataSource;
this.ParetoGrid.DataMember = DataMember;
ParetoGrid.AutoResizeColumns(Mode);
}
}
Call invoke method in another method:
Private void GetSomething()
{
//sql code get data
SetDataGrid(true, dataSet1, "Pareto", DataGridViewAutoSizeColumnsMode.AllCells);
}
Then simply start task on event:
private void myButton_Click(Object sender, EventArgs e)
{
Task t = new Task(() => getSomething());
t.Start();
}
As you can see simple stuff, but simple changing event seems to mess the whole logic up.

I'd recomend moving this logic from the task into a Timer callback, then have your OnTextChanged handler actually reset the timer each time it's fired (only have the timer fire once of course). By making the timer elapse after .5-1 sec, or something like that, you'll wait until all of their text has been entered before actually calling your logic. But the user's experience will still be quite responsive.
Example:
private System.Threading.Timer keyEntryTimer = new Timer(Logic,null,-1,-1);
public void HandleEvent(objet sender, EventArgs args)
{
keyEntryTimer.Change(500,-1);
}
public void Logic(objet state)
{
//Your task logic would go here to read from the text etc...
//You'll have to handle any UI updates either by firing off a task once the DB results return or using a dispatcher
}

You can simply cache all accounts when they enter this search mode. Then you can search through cached accounts when they enter text into the textbox. Doing a query in a different thread for every keypress is very heavy on the app.

I would probably do a combination of the other suggested answers and try to delay the firing of the SQL statement using a timer or some similar method, but if the user delayed long enough for the SQL to fire then try to just filter the returned results from that point forward (only if the search string is becoming more restrictive obviously). This could save you expensive SQL round trips and give you a working cache after the first hit (which should be smaller than if you tried to cache everything).
Hope this helps.

Related

Is this a correct C# implementation of VB6's threading model?

I've read about VB6's threading model, and found this link very helpful.
With the following points in mind...
Do VB6 event handlers run in separate threads?
Not really, because there aren't separate threads. Your code runs on a single thread, wrapped in the service-like architecture I described above. Most of what you talk to that is threaded is other COM objects which have their own apartments. So to communicate back and forth, you are basically doing RPC calls when the threads talk to each other: you aren't directly manipulating them.
Among other things, the VB6 program had a timer that woke up every 4 seconds, manipulated some global variables and went back to sleep, while the main program was doing its thing. I can't understand why this didn't result in collisions.
The "timer" is on a separate thread created for the timer, but when it calls into your code, you are guaranteed not to interrupt any other functions, because the function calls are basically queued one at a time in the thread.
... I've attempted to implement VB6's event handling behavior in the code below.
ActionManager.cs
public class ActionManager : IDisposable
{
private readonly BlockingCollection<Action> ActionQueue = new BlockingCollection<Action>(new ConcurrentQueue<Action>());
public ActionManager()
{
}
public void Kickoff()
{
// Start consumer thread
new Thread(ExecuteLoop)
{
IsBackground = true
}.Start();
}
public void AddAction(Action action)
{
ActionQueue.Add(action);
}
private void ExecuteLoop()
{
// Blocks until new actions are available
foreach (var action in ActionQueue.GetConsumingEnumerable())
{
action.Invoke();
}
}
public void Dispose()
{
ActionQueue.CompleteAdding();
ActionQueue.Dispose();
}
}
MainForm.cs
public partial class MainForm : Form
{
public ActionManager actionManager = new ActionManager();
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load()
{
// Perform preparatory steps, such as initializing resources,
// configuring settings, etc.
// (Insert preparatory steps here)
// Once preparatory steps are complete, start the ActionManager
actionManager.Kickoff();
}
// Event handler for when the Timer's specified interval has elapsed
private void Timer_Tick(object sender, EventArgs e)
{
actionManager.AddAction(() => {
// (Insert timer event steps here)
});
}
// Event handler for when SomeButton is clicked
private void SomeButton_Click(object sender, EventArgs e)
{
actionManager.AddAction(() => {
// (Insert button click event steps here)
});
}
}
An ActionManager manages an event queue by executing each event one after the other. Any type of event, such as mouse clicks, timer ticks, network packet arrivals, and the like, will enqueue their respective event handling code to the event queue. This way, the code will run "on a single thread," which will also handle the problem of unsynchronized global variables.
Is this a correct implementation? Please share your thoughts!
What you have is a somewhat decent starting place for a custom message loop, if you were to begin writing your own UI framework from scratch. But you're using winforms, you're not writing your own UI framework from scratch. Winforms already has its own message loop that processes messages, and a mechanism for scheduling work to run in that loop. You don't need to create any of that from scratch. All of the events fired from the winforms controls will already be firing in the UI thread, so you don't need to create your own special UI thread and manage scheduling actions into it.
In fact doing so would cause problems, as you would end up having the UI thread that winforms is using to manage its UI objects, and you would have your second thread that you're creating. If you ever used any UI controls in that thread things would break as they are designed to only be used from the winforms UI thread.
(I figured I should ask in the comments first if my suspicion about a legacy app was right.)
Okay, time for the bad news: you should NOT do this. Please, please, please, do NOT do this. I'm telling you as a developer that has been in your shoes that this will NOT end well if you try to go down this road.
Here's what's going on. You've got a legacy app - and it probably does a lot of things that are very important for the company.
But the problem is, it's likely not written very well, it's cranky, and it did not port very well into the modern .NET world.
Now, you can try to go down the road of shoehorning .NET into the VB6 model of the world... but all you've done is kick the can down the road. You've still got a badly-written, cranky legacy app that you're still having to maintain - and worse, you're having to maintain the .NET-to-VB6-threading-approach as well.
I can guarantee you that the correct approach is to Redesign/Rearchitect it. Write out what it does, ask yourself if there's anything you can do to improve the process, and write it from scratch in .NET. Several reasons:
You're going to have a more stable end product
You're going to spend FAR less time maintaining the new product
You'd have to rearchitect the program eventually anyways.
If it helps, let me tell you a story of an old job I had. A coworker and I were both responsible for porting VB6 apps into .NET. He had a tire inspection app, and I had a rubber mixing app.
He tried porting his existing VB6 app into .NET, getting all the language
differences worked out, GUI/Thread issues altered, etc
I sat down with a rep from the user area, and went ahead just
rewriting the rubber mixing app.
... I was done much sooner than the coworker, my app was far more user-friendly, and it was a heck of a lot less of a maintenance issue.
Management likely will not like hearing advice that you should rewrite the whole thing. But you need to push and fight for this. If it helps, point out that most software dev time isn't on new coding, it's on maintaining existing software. It might take more time up front to get it rewritten (even that's not a given) but it'll pay for itself very quickly in the long run.

Make main thread execute code on button press after form.show

I have a piece of code that does some calculations and then calls the form.show command. Now I have a library (the revit api) that does not allow me to store variables in a project without being in the main thread.
The logical solution for this is to get the spawned thread to call the main thread using say a producer/consumer pattern with code looking a bit like this:
form.Show(owner);
while(AppIsRunning){
if(clicked)
commit();
else
Thread.sleep(100);
}
However when I do this the gui does not load fully (black background, no text in buttons ext.).
I have also tried doing this using the evoke method
private void BtnOK_Click(object sender, System.EventArgs e)
{
Commit();
Invoke(Commit);
}
private void Invoke(Action commit)
{
commit.Invoke();
}
However this just tells me that it's not the main thread that's executing the commit function.
Is there another way to do this or am I just making an error.
Just to be clear I have a form.show(owner) command that throws an error if it's not executed by the main thread. I also have a commit() function that must be excused by the main thread or it throws an error. The execution must wait until a button press. But the main thread polling the gui thread for changing causes the program to hang. According to my google search it' s also possible to do something involving an external event to get back into the right context but the example given was using python to invoke c# code, is there a good way to raise an external event to get back into a given thread in c#?
Edit: based on some suggestions I have created the following code:
public class ThreadManager
{
static List<ThreadAble> orders = new List<ThreadAble>();
public static bool running = false;
public static void execute(ThreadAble action)
{
orders.Add(action);
}
static System.Timers.Timer timer;
public static void RegisterAPIThreadAndHold(ExternalCommandData commandData)
{
UIApplication uiapp = commandData.Application;
uiapp.Idling += Application_Idle;
}
private static void Application_Idle(Object o,IdlingEventArgs e)
{
if (orders.Count != 0)
{
ThreadAble f = orders.First();
orders.Remove(f);
f.execute();
}
}
}
public interface ThreadAble {
void execute();
}
However this does not appear to actually run when I use it as
public override Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
Form frm = new OverviewForm(ExternalCommandData commandData);
frm.show()
ThreadManager.RegisterAPIThreadAndHold(commandData);
ThreadManager.Execute(new run_ThrowError())
where ThrowError.execute() is
Throw new Exception(" this is actually being executed" );
Your first example could work if you will replace Thread.Sleep by the System.Windows.Forms.Application.DoEvents(). It should give time to paint GUI and do not froze application completly.
form.Show(owner);
while(AppIsRunning){
if(clicked)
commit();
else
{
System.Windows.Forms.Application.DoEvents();
// Thread.sleep(100);
}
}
But this is not perfect solution to achieve this.
Better would be calling Dispatcher.Invoke command inside your dialog to perform MainThread operations.
You can use i.e. GalaSoft library - please refer to DispatcherHelper object documentation and samples.
The two ways to do this I'm aware of are with the External Event or the Idling event.
With the idling event, you'll register it, and while it is registered, your code (in the main thread) will get a callback from Revit every time that it's not busy with something else. Often a few times per second.
Once you are in the Idling callback, then you're able to create transactions and interact with the model. So your callback checks the state of the form and decides whether there is something to do.
The External Event works similarly in terms of registration, but you're able to request a trigger of the callback.
Jeremy Tammik must have 20 posts on thebuildingcoder.typepad.com on Modeless dialog / Revit stuff.
For a simple solution to this, please refer to the Revit SDK ModelessDialog ModelessForm_ExternalEvent sample application. It demonstrates exactly what you are asking for.

Is this a good practice to handle the displaying of the progress of data processing in a Class library?

I developed a C# class library, some of their methods shows information of its processing progress because they read and write millions of records, and the user asked for knowing how the process is going and the time they should wait.
Using dependency injection to avoid the "if console app write progress on console else if WPF app display progress bar", (1) I have got the displaying on the console the time for every one million records processed if the method is invoked from a console application and (2) I have got the displaying a progress bar on a GUI if the method is invoked from a WPF application.
The question here is, is it a good practice what I am doing or, is there better/correct alternative to this matter?
My best regards.
Please don't do this. If you are building a class library, you should make zero assumptions about the UI is interacting with the user.
Your solution sounds like it might work if you have a console window or a WPF application, but what if it's being called from a website or inside a service? I've seen many a service get brought down beause some rogue class library was trying to display a dialog but there was nobody around to click OK.
The better solution is to simply raise an event whenever you want to report some progress, and let the consuming UI application worry how it wants to display that progress to the user.
See how the BackgroundWorker class works for a good model of this: http://msdn.microsoft.com/en-us/library/8xs8549b.aspx
I wouldn't expect a class library to display the progress itself. I'd expect it provide hooks - probably in the form of events - so that whatever using the class library can display that information in the most appropriate form.
Quite how much control you want to give over that (e.g. report to me on every item or every N items) is a matter you'll have to work out for yourself - but it should be fairly easy for a handler to work that sort of thing out for itself.
Here's an example of raising events, this code will go in your class that is doing the work on the background thread. The MessageEventsArgs derives from EventArgs (MessageEventArgs : EventArgs) so custom information can be passed to the caller. This isn't required, one could use EventArgs e as well.
public delegate void SchemaProcessorMessageEventHandler(object sender, MessageEventArgs e);
public event SchemaProcessorMessageEventHandler SchemaProcessorMessage;
protected virtual void OnSchemaProcessorMessage(MessageEventArgs e)
{
if (SchemaProcessorMessage != null)
{
SchemaProcessorMessage(this, e);
}
}
Now in your caller (UI) set up the event listener. Remove the event listener -= when finished.
_SchemaProcessor = new ServerSchemaUtilityFramework.SchemaProcessor();
_SchemaProcessor.SchemaProcessorMessage += new ServerSchemaUtilityFramework.SchemaProcessor.SchemaProcessorMessageEventHandler(sp_SchemaProcessorMessage);
void sp_SchemaProcessorMessage(object sender, ServerSchemaUtilityFramework.MessageEventArgs e)
{
//Update the UI, if on background will need to (!this.Dispatcher.CheckAccess())
}

Label text not updated

I have a Windows Form with a status bar which shows the current state of application.
I have a class named AppState with update the Label in the status bar and in dispose it changes the state back to "Ready".
In code when I do an operation like:
using (AppState state = new AppState("Processing..."))
{
//Do some work that take some seconds
}
But the label remains the same. I am not getting any exceptions. The label text is updated but on UI it keeps on showing previous value. Am I missing anything here?
santosc you are right, thats the only thing I am doing. Here is the AppState code
public class AppState : IDisposable
{
static string Default = "Ready";
public AppState(string status)
{
Form.StatusLabel.Text = status;
}
public void Dispose()
{
Form.StatusLabel.Text = Default;
}
}
It's always the same thing...
If you want to start something that takes a while, don't do it within your GUI thread or your GUI will freeze (no updates of label, no resizing, no moving, no whatever).
Filling your code on thousand places with Application.DoEvents() is also a bad practice.
If you have some long running task (long means > 1 sec) you should probably use a BackgroundWorker. Maybe it's a little bit harder at the beginning, but you will love it if your program gets more complex. Due to the fact, that this has already being discussed several time, here is a link with some sample code.
Now that you know the right tool (BackgroundWorker) to solve your problem, you should get it to work (or ask another question about your new specific problem).
Looks like you want to put Application.DoEvents() after setting the StatusLabel text field value. This tells Windows Forms to process the Windows event queue for your form, causing changes to be repainted.
in order to be "thread safe" use Invoke, and test with the InvokeRequired in the form like:
// code outside the myForm:-----------------------
if (myForm.InvokeRequired)
myForm.Invoke(new ChangeLabelEventHandler(ChangeLabel), "teeeest");
else
myForm.ChangeLabel("teeeest");
// code in the myForm:-----------------------------
public delegate void ChangeLabelEventHandler(string newText);
private void ChangeLabel(string newLabelText)
{
this.label1.Text = newLabelText;
}
I'm new to C# stuff, but why can't you just do something like:
private void updateStatusBar(string status)
{
if (StatusLabel.InvokeRequired)
{
StatusLabel.Invoke((MethodInvoker)(() =>
{
StatusLabel.Text = status;
}));
}
else
{
StatusLabel.Text = status;
}
}
When you want to update the status?
Maybe multiple threads could solve your problem.
The easiest way is using a BackgroundWorker.
The reason is that the UI is only able to redraw when the UI thread has nothing else to do. And you are blocking it with your calculation.
use Label.Refresh(); it saves a lot of time.This should work for u

Receiving CrossThreadMessagingException while Debugging WinForms Application

I am using Wndows XP SP3 x86 + VSTS 2008 to write a simple Windows Forms application using C#. There is a button called button1 and here is the event handler for its click event, when executing the if statement, there is Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException. Does anyone have any good ideas what is wrong?
private void button1_Click(object sender, EventArgs e)
{
string recording = ConfigurationSettings.AppSettings["recording"];
// exception thrown when executing the following if statement
if (recording.Equals("enable", StringComparison.InvariantCultureIgnoreCase))
{
CameraEncoder.Stop();
}
}
Some more code:
static WMEncoder CameraEncoder = new WMEncoder();
EDIT1:
I am confused how to apply Marc's idea of using Invoke in my code. Should I use the following code segment?
CameraEncoder.Invoke((MethodInvoker) delegate
{
CameraEncoder.Stop();
});
Normally, the problem when we see this (regularly) is something like a worker thread or a timer updating the UI - but a button click should be raised through the UI thread, so I don't think it is the "usual problem".
So: what is camera? And what is Recording? Neither is explained, and we can't guess without introducing extra variables...
Depending on what they are, maybe this'll work...
camera.Invoke((MethodInvoker) delegate
{
if (camera.Equals("enable", StringComparison.InvariantCultureIgnoreCase))
{
Recording.Stop();
}
});
But without knowing what canera is, I'm clutching at straws...
Maybe the camera object is created and managed by another thread.. Could you expose more code regarding the camera object?
I know WMEncoder is a COM object. You might try creating CameraEncoder in the GUI thread instead of a different thread.

Categories

Resources