Threaded ticker control on top of Main thread control in wpf - c#

I have requirement in which i need to show smooth scrolling text with some GIF Images and jpeg or mediaelement on a ticker. However, since this involves lot of CPU cycles for the main UI thread, i planned to create the ticker control on another thread with a dispatcher and then host this ticker on the form. However, i am getting a cross-thread exception that thread cannot access the control as another thread owns it.
I have done similar thing in Delphi, wherein i have set the ticker parent with SetWindowParent();
my code is as below
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TickerControlContainer loclContainer = new TickerControlContainer(this);
}
}
public class TickerControlContainer
{
private MainWindow f_Window;
private void CreateControl()
{
TickerControl loclControl = new TickerControl();
loclControl.InitializeComponent();
f_Window.Dispatcher.Invoke((MethodInvoker)delegate { AddControl(loclControl); });
}
private void AddControl(TickerControl piclControl)
{
f_Window.Content = piclControl;
**// exception occurs**
}
public TickerControlContainer(MainWindow piclWindow)
{
f_Window = piclWindow;
ManualResetEvent loclResetEvent = new ManualResetEvent(false);
Dispatcher loclDispatcher = null;
Thread th1 = new Thread(new ThreadStart(() =>
{
loclDispatcher = Dispatcher.CurrentDispatcher;
loclResetEvent.Set();
Dispatcher.Run();
}));
th1.SetApartmentState(ApartmentState.STA);
th1.Start();
loclResetEvent.WaitOne();
loclDispatcher.BeginInvoke((MethodInvoker)delegate { CreateControl(); });
}
}
Do i need to put a contentcontrol or something on my form, instead of setting as the content of the form.
This is just a sample that i am trying to do. Please help.

There's only one UI thread in WPF/.NET (though I think different windows can run on separate threads), so I don't really think there's an easy way to do what you're trying to do here.
Is it the animation that's taking up a lot of CPU, or are you doing a lot of processing in addition to the animation? If so, I would offload the calculations to a background thread and then invoke it to the UI thread when complete.

I was able to host control created on another thread on my main window, but before creating the control, the Window has to be shown atleast once.
using...
namespace WpfMultiDispatcherUpdates
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnCreateControl_Click(object sender, RoutedEventArgs e)
{
TickerControlContainer loclContainer = new TickerControlContainer(this);
}
}
public class TickerControlContainer
{
private MainWindow f_Window;
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
private static extern IntPtr SetParent(IntPtr hwndChild, IntPtr hwndParent);
private void CreateControl(HwndSource piclSource)
{
TickerControl loclControl = new TickerControl();
loclControl.InitializeComponent();
Window loclHostWindow = new Window();
loclHostWindow.WindowStyle = WindowStyle.None;
loclHostWindow.WindowState = WindowState.Normal;
loclHostWindow.Left = 0;
loclHostWindow.Top = 0;
loclHostWindow.ShowInTaskbar = false;
loclHostWindow.Content = loclControl;
loclHostWindow.ShowActivated = true;
loclControl.Height = 200;
loclControl.Width = (double)f_Window.Dispatcher.Invoke(new Func<double>(() => { return f_Window.Width; }));
piclSource.SizeToContent = SizeToContent.WidthAndHeight;
loclHostWindow.SizeToContent = SizeToContent.WidthAndHeight;
loclHostWindow.Show();
SetParent(new WindowInteropHelper(loclHostWindow).Handle, piclSource.Handle);
}
private void AddControl(TickerControl piclControl)
{
f_Window.Content = new ContentControl() { Content = piclControl };
}
public TickerControlContainer(MainWindow piclWindow)
{
f_Window = piclWindow;
ManualResetEvent loclResetEvent = new ManualResetEvent(false);
Dispatcher loclDispatcher = null;
Thread th1 = new Thread(new ThreadStart(() =>
{
loclDispatcher = Dispatcher.CurrentDispatcher;
loclResetEvent.Set();
try
{
Dispatcher.Run();
}
catch (Exception E)
{
System.Windows.MessageBox.Show(E.Message);
}
}));
th1.SetApartmentState(ApartmentState.STA);
th1.Start();
loclResetEvent.WaitOne();
try
{
HwndSourceParameters loclSourceParams = new HwndSourceParameters();
loclSourceParams.WindowStyle = 0x10000000 | 0x40000000;
loclSourceParams.SetSize((int)piclWindow.Width, 200);
loclSourceParams.SetPosition(0, 20);
loclSourceParams.UsesPerPixelOpacity = true;
loclSourceParams.ParentWindow = new WindowInteropHelper(piclWindow).Handle;
HwndSource loclSource = new HwndSource(loclSourceParams);
loclSource.CompositionTarget.BackgroundColor = Colors.Transparent;
loclDispatcher.BeginInvoke((MethodInvoker)delegate { CreateControl(loclSource); });
}
catch (Exception E)
{
System.Windows.MessageBox.Show(E.Message);
}
}
}
}
However, i need to add the Resize events and resize my control when the MainWindow height and width changes. The values are hardcoded for testing purposes. Now, the drawing on the child control does not affect my Main Window, but the complexity of controlling my child control is more..
The host should not have any other child controls in the area where we host this threaded control.

Related

Create a child window from a FileSystemWatcher

I have a FileSystemWatcher watching for newly created files.
When it sees one, I would like it to open a child window.
Using this:
private void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
TableWindow win = new TableWindow();
win.Owner = this;
win.Text = "xxx";
win.ShowInTaskbar = false;
win.Show();
}
The error I'm getting is:
Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on
After some googling. I ended up with this
TableWindow win = new TableWindow();
win.Owner = this;
win.Text = "xxx";
win.ShowInTaskbar = false;
win.Invoke((MethodInvoker)delegate
{
win.Show();
});
which gives me a different error:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
Here's the scenario. on a game, each time a new table is opened, a new file is created. When that file is created, I want to open a child window to display statistics on that table.
Is this even possible?
What I've done in the past to work with InvokeRequired is to place it within an if statement that will call the method on the UI thread if it hasn't been called from the UI thread.
private void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
ShowWindow();
}
private void ShowWindow()
{
if (this.InvokeRequired)
{
var del = new MethodInvoker(ShowWindow);
this.BeginInvoke(del);
return;
}
TableWindow win = new TableWindow();
win.Owner = this;
win.Text = "xxx";
win.ShowInTaskbar = false;
win.Show();
}

Load another window in background; when rendered, close the parent window

I have been trying to load another window in background within a window; parent window acts as a splash screen in my case.
InitWindow I = null;
public InitWindow()
{
InitializeComponent();
I = this;
Thread T = new Thread(() =>
{
MainWindow M = new MainWindow();
M.Show();
M.ContentRendered += M_ContentRendered;
System.Windows.Threading.Dispatcher.Run();
M.Closed += (s, e) => M.Dispatcher.InvokeShutdown();
}) { IsBackground = true, Priority = ThreadPriority.Lowest };
T.SetApartmentState(ApartmentState.STA);
T.Start();
}
void M_ContentRendered(object sender, EventArgs e)
{
I.Close();
}
Everything else works fine but it throws an Invalid Operation Exception at:
I.Close();
the calling thread cannot access this object because a different thread owns it.
1) How do I switch/sync thread?
2) Is there a better workaround?
Changed code to:
InitWindow I = null;
Thread C = null;
public InitWindow()
{
InitializeComponent();
I = this;
C = Thread.CurrentThread;
Thread T = new Thread(() =>
{
MainWindow M = new MainWindow();
M.Show();
M.ContentRendered += M_ContentRendered;
System.Windows.Threading.Dispatcher.Run();
M.Closed += (s, e) => M.Dispatcher.InvokeShutdown();
}) { IsBackground = true, Priority = ThreadPriority.Lowest };
T.SetApartmentState(ApartmentState.STA);
T.Start();
}
void M_ContentRendered(object sender, EventArgs e)
{
// Making the parent thread background
C.IsBackground = true;
// foreground the current thread
Thread.CurrentThread.IsBackground = false;
// Abort the parent thread
C.Abort();
}
Works fine as of now, but I don't think it's a reliable solution.

STA Thread is not loading the Main Window in WPF

I am loading MainWindow in App_Startup (). I wanted to show the progress bar while loading the window. But it is not working :
void App_Startup(object sender, StartupEventArgs e)
{
Thread bootStrapThread = new Thread(new ThreadStart(runBootStrapProcess));
bootStrapThread.SetApartmentState(ApartmentState.STA);
bootStrapThread.IsBackground = true;
bootStrapThread.Start();
_loadingProgressBar = new loadingProgressBar();
_loadingProgressBar.ShowDialog();
}
I want to load the window from thread :
void runBootStrapProcess()
{
MetadataReader mr = new MetadataReader();
if (currentVersionNo.Equals(remoteVersionNo))
{
Application.Current.Shutdown();
}
else
{
MainWindow mw = new MainWindow();
mw.Show();
}
_loadingProgressBar.ShouldCloseNow = true;
}
You can try this:
void runBootStrapProcess() {
MetadataReader mr = new MetadataReader();
if (currentVersionNo.Equals(remoteVersionNo)) {
Application.Current.Shutdown();
} else {
System.Windows.Application.Current.Dispatcher.BeginInvoke(
new Action(
() => {
MainWindow mw = new MainWindow();
mw.Show();
}));
}
_loadingProgressBar.ShouldCloseNow = true;
}
You basically from the thread when you want to show the window send it to the main application thread. This thus stops the application from closing down when the thread exits since the MainWindow is Shown from the main thread.
I suspect the window is missing the message pump since the WPF Application class with its Dispatcher is running on a different STA Thread

Control.BeginInvoke does not call delegate when UI function is idle

I am modifying a windows desktop application that works with some external hardware. When the user activates the hardware from the application a progress (UI) form is started. This form creates a thread that performs all of the work with the hardware. The problem comes when I try to report progress back to the UI thread. It appears that the first of my Control.BeginInvoke ("Negotiating message") works fine. However, the second one (first adjustment to progressbar) never seems to call it's delegate and as a result the application locks up on the subsequent endinvoke. I believe the issue is that the GUI is now in an idle state, but I am not sure how to fix the situation. Any help would be appreciated. Code found below:
In the UI Load Method Thread:
private void frmTwainAquire_Load(object sender, EventArgs e)
{
try
{
//Show the GUI
this.Visible = showGUI;
pbScanningProgress.Value = 0;
btnCancel.Enabled = false;
btnCancel.Visible = false;
// Set the delegates.
SetScanMessageDelegate = new SetScanMessage(this.SetScanMessageMethod);
SetRegistrationMessageDelegate = new SetRegistrationMessage(this.SetRegistrationMessageMethod);
AddScanProgressDelegate = new AddScanProgress(this.AddScanProgressMethod);
AddRecogProgressDelegate = new AddRecogProgress(this.AddRecogProgressMethod);
// Set progress bars.
pbScanningProgress.Value = 0;
pbRecognition.Value = 0;
abortScan = false;
// Create thread here!
twainInstance = new rScan.Twain();
rScanning = new rScanThread(this, twainInstance);
// Start the thread.
rScanning.tScan = new Thread(rScanning.Scan);
rScanning.tScan.Start();
}
catch (Exception ex)
{
// Error checking here.
}
}
Delegate Methods:
public void SetScanMessageMethod(string scanMessage)
{
this.lblScanMessage.Text = scanMessage;
}
public void SetRegistrationMessageMethod(string recogMessage)
{
this.lblRecognition.Text = recogMessage;
}
public void AddScanProgressMethod(int progress)
{
this.pbScanningProgress.Value += progress;
}
public void AddRecogProgressMethod(int progress)
{
this.pbRecognition.Value += progress;
}
Thread method that is giving the problem. Please note that the thread is in a different class then the previous two code blocks (both are in the UI class):
public class rScanThread : IMessageFilter
public void Scan()
{
// Set progress bar message.
IAsyncResult result;
if (frmTwainAquireInstance.lblScanMessage.IsHandleCreated && frmTwainAquireInstance.lblScanMessage.InvokeRequired)
{
result = frmTwainAquireInstance.lblScanMessage.BeginInvoke(frmTwainAquireInstance.SetScanMessageDelegate, "Negotiating Capabilities with Scanner.");
frmTwainAquireInstance.lblScanMessage.EndInvoke(result);
}
else
{
frmTwainAquireInstance.lblScanMessage.Text = "Negotiating Capabilities with Scanner.";
}
// Start the intialization of the rScan process.
bool intializeSuccess = twainInstance.Initialize(frmTwainAquireInstance.Handle);
// If the process could not be started then quit.
if (!intializeSuccess)
{
frmTwainAquireInstance.Close();
return;
}
if (frmTwainAquireInstance.pbScanningProgress.IsHandleCreated && frmTwainAquireInstance.pbScanningProgress.InvokeRequired)
{
result = frmTwainAquireInstance.pbScanningProgress.BeginInvoke(frmTwainAquireInstance.AddScanProgressDelegate, 33);
frmTwainAquireInstance.pbScanningProgress.EndInvoke(result); // Lock up here.
}
else
{
frmTwainAquireInstance.pbScanningProgress.Value += 33;
}
// Do more work after. The code never makes it this far.
} // End of rScanThread.Scan()

timer in thread problem

I have my application in which in three datagridview independently in three thread load data from wcf service. I execute in each thread timer which every second load this data.
My problem is that every time my thread go threw each thread but only like I show in method timerNowyYork_Elapsed
Any idea why this happens ? I bad lock thread?
this code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;
namespace Sprawdzanie_warunków_pogodowych
{
public partial class Form1 : Form
{
PogodaEntities entity = new PogodaEntities();
System.Timers.Timer timerKrakow = new System.Timers.Timer();
System.Timers.Timer timerSzczecin = new System.Timers.Timer();
System.Timers.Timer timerNowyYork = new System.Timers.Timer();
KeyValuePair<string, string> krakowInfo;
KeyValuePair<string, string> szczecinInfo;
KeyValuePair<string, string> nowyYorkInfo;
public Form1()
{
System.Net.ServicePointManager.Expect100Continue = false;
InitializeComponent();
List<MiastoContainer> miasta = (from miasto in entity.Miasta
select new MiastoContainer()
{
MiastoName = miasto.Nazwa,
Panstwo = miasto.Państwo
}).ToList();
krakowInfo = new KeyValuePair<string, string>(miasta[0].MiastoName, miasta[0].Panstwo);
szczecinInfo = new KeyValuePair<string, string>(miasta[1].MiastoName, miasta[1].Panstwo);
nowyYorkInfo = new KeyValuePair<string, string>(miasta[2].MiastoName, miasta[2].Panstwo);
ParameterizedThreadStart ptsKrakow = new ParameterizedThreadStart(PobierzKrakow);
Thread tKrakow = new Thread(ptsKrakow);
tKrakow.Start(this.dataGridViewKrakow);
ParameterizedThreadStart ptsSzczecin = new ParameterizedThreadStart(PobierzSzczecin);
Thread tSzczecin = new Thread(ptsSzczecin);
tSzczecin.Start(this.dataGridViewSzczecin);
}
private void oAutorzeToolStripMenuItem_Click(object sender, EventArgs e)
{
new AboutBox1().Show();
}
private void zapiszRaportToolStripMenuItem_Click(object sender, EventArgs e)
{
}
public void PobierzKrakow(object parameters)
{
this.timerKrakow.Elapsed += new System.Timers.ElapsedEventHandler(timerKrakow_Elapsed);
this.timerKrakow.Enabled = true;
this.timerKrakow.Interval = 1000;
this.timerKrakow.Start();
}
public void PobierzSzczecin(object parameters)
{
this.timerSzczecin.Elapsed += new System.Timers.ElapsedEventHandler(timerSzczecin_Elapsed);
this.timerSzczecin.Enabled = true;
this.timerSzczecin.Interval = 1000;
this.timerSzczecin.Start();
}
public void PobierzNowyYork(object parameters)
{
this.timerNowyYork.Elapsed += new System.Timers.ElapsedEventHandler(timerNowyYork_Elapsed);
this.timerNowyYork.Enabled = true;
this.timerNowyYork.Interval = 1000;
this.timerNowyYork.Start();
}
void timerNowyYork_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{ GlobalWeather.Weather weather = new GlobalWeather.Weather();
lock (weather)
{
//thread always start from here
List<object> weatherList = new List<object>();
weatherList.Add(weather.GetTempreature(nowyYorkInfo.Key, nowyYorkInfo.Value));
//and end here , never come any line further
weatherList.Add(weather.GetPressure(nowyYorkInfo.Key, nowyYorkInfo.Value));
weatherList.Add(weather.GetHumidity(nowyYorkInfo.Key, nowyYorkInfo.Value));
weatherList.Add(weather.GetVisibility(nowyYorkInfo.Key, nowyYorkInfo.Value));
entity.SaveChanges();
WarunkiPogodowe warunki = new WarunkiPogodowe()
{
Temperatura = weatherList[0].ToString(),
Ciśnienie = weatherList[1].ToString(),
Wilgotność = weatherList[2].ToString(),
Widoczność = weatherList[3].ToString(),
DataSprawdzenia = DateTime.Now
};
entity.AddToWarunkiPogodowe(warunki);
entity.SaveChanges();
int miastoId = entity.Miasta.First(m => m.Nazwa == nowyYorkInfo.Key).id;
Miasto_has_WarunkiPogodowe m_has_wp = new Miasto_has_WarunkiPogodowe()
{
idMiasto_FK = miastoId,
idWarunkiPogodowe_FK = warunki.id
};
entity.AddToMiasto_has_WarunkiPogodowe(m_has_wp);
entity.SaveChanges();
this.dataGridViewNowyYork.Rows.Add(warunki);
}
}
void timerSzczecin_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
GlobalWeather.Weather weather = new GlobalWeather.Weather();
lock (weather)
{
List<object> weatherList = new List<object>();
weatherList.Add(weather.GetTempreature(szczecinInfo.Key, szczecinInfo.Value));
weatherList.Add(weather.GetPressure(szczecinInfo.Key, szczecinInfo.Value));
weatherList.Add(weather.GetHumidity(szczecinInfo.Key, szczecinInfo.Value));
weatherList.Add(weather.GetVisibility(szczecinInfo.Key, szczecinInfo.Value));
entity.SaveChanges();
WarunkiPogodowe warunki = new WarunkiPogodowe()
{
Temperatura = weatherList[0].ToString(),
Ciśnienie = weatherList[1].ToString(),
Wilgotność = weatherList[2].ToString(),
Widoczność = weatherList[3].ToString(),
DataSprawdzenia = DateTime.Now
};
entity.AddToWarunkiPogodowe(warunki);
entity.SaveChanges();
int miastoId = entity.Miasta.First(m => m.Nazwa == szczecinInfo.Key).id;
Miasto_has_WarunkiPogodowe m_has_wp = new Miasto_has_WarunkiPogodowe()
{
idMiasto_FK = miastoId,
idWarunkiPogodowe_FK = warunki.id
};
entity.AddToMiasto_has_WarunkiPogodowe(m_has_wp);
entity.SaveChanges();
this.dataGridViewSzczecin.Rows.Add(warunki);
}
}
void timerKrakow_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
GlobalWeather.Weather weather = new GlobalWeather.Weather();
lock (weather)
{
List<object> weatherList = new List<object>();
weatherList.Add(weather.GetTempreature(krakowInfo.Key, krakowInfo.Value));
weatherList.Add(weather.GetPressure(krakowInfo.Key, krakowInfo.Value));
weatherList.Add(weather.GetHumidity(krakowInfo.Key, krakowInfo.Value));
weatherList.Add(weather.GetVisibility(krakowInfo.Key, krakowInfo.Value));
entity.SaveChanges();
WarunkiPogodowe warunki = new WarunkiPogodowe()
{
Temperatura = weatherList[0].ToString(),
Ciśnienie = weatherList[1].ToString(),
Wilgotność = weatherList[2].ToString(),
Widoczność = weatherList[3].ToString(),
DataSprawdzenia = DateTime.Now
};
entity.AddToWarunkiPogodowe(warunki);
entity.SaveChanges();
int miastoId = entity.Miasta.First(m => m.Nazwa == krakowInfo.Key).id;
Miasto_has_WarunkiPogodowe m_has_wp = new Miasto_has_WarunkiPogodowe()
{
idMiasto_FK = miastoId,
idWarunkiPogodowe_FK = warunki.id
};
entity.AddToMiasto_has_WarunkiPogodowe(m_has_wp);
entity.SaveChanges();
this.dataGridViewKrakow.Rows.Add(warunki);
}
}
}
class MiastoContainer
{
string miastoName;
public string MiastoName
{
get { return miastoName; }
set { miastoName = value; }
}
string panstwo;
public string Panstwo
{
get { return panstwo; }
set { panstwo = value; }
}
public MiastoContainer()
{ }
public MiastoContainer(string miasto, string panstwo)
{
this.MiastoName = miasto;
this.Panstwo = panstwo;
}
public void Add(MiastoContainer item)
{
((ICollection<MiastoContainer>)this).Add(item);
}
}
}
Your locks are completely useless. As you are locking on an object that you just created, each lock will have it's own identifier and does not affect each other at all.
You need all locks that should exclude each other to use the same object as identifier.
System.Timers.Timer lets you set the SynchronizingObject so that it will invoke the callback on the UI thread. When you create your timers, write:
this.timerKrakow.SynchronizingObject = this;
The timer's elapsed event will then be invoked on the UI thread. That eliminates the need for locks in your event handlers.
You could do the same thing, by the way, with a System.Windows.Forms.Timer, which always invokes the event handler on the UI thread.
The drawback to raising the event on the UI thread is that it might block the user interface. It depends on how much time is spent in the event handler. If your event handler is very quick, then this isn't a problem. If it will take 100 milliseconds to process the event handler, though, you probably don't want to do it on the UI thread.
If you elect not to do it on the UI thread, you need to synchronize access to the UI. The timer event handler can't just modify user interface elements. Instead, you need to call this.Invoke so that any UI modification is done on the UI thread.
I strongly suggest that you NOT use System.Timers.Timer. As the documentation states:
The Timer component catches and
suppresses all exceptions thrown by
event handlers for the Elapsed event.
This behavior is subject to change in
future releases of the .NET Framework.
In other words, if there is a bug in your event handler that throws an exception, you will never know it. I suggest using System.Windows.Forms.Timer or System.Threading.Timer instead.
I don't fully understand your question, but (unless I'm mistaken) the timer callbacks occur in the ThreadPool (or the GUI thread, dependent on usage), so starting them in different threads is pointless.
It seems to me that you are accessing DataGridView directly from another thread. You should not do that. UI controls must always be called from the UI thread. You can use the ISynchronizeInvoke interface to pass the data into correct thread.
this.dataGridViewNowyYork.Invoke(new Action(() => {
this.dataGridViewNowyYork.Rows.Add(warunki);
}), null);

Categories

Resources