I have a wpf application with a text box in the main window that is supposed to be used to display logging information while the user runs a long process.
<TextBox Grid.Row="1" Margin="10,10,10,10" AcceptsReturn="True" Name="txtLogging" TextWrapping="WrapWithOverflow"
Text="{Binding Path=LogText, Mode=TwoWay}" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" />
public string LogText
{
get { return _logText; }
set
{
_logText = value;
OnPropertyChanged();
}
}
One of the buttons on the ui kicks off a process that takes a minimum of 30 seconds, and sometimes up to a few hours. Needless to say, running this on a background worker is preferred. The issue is that the logging class in the program is being created on the UI thread and has to be accessed during the worker's execution to update the UI with the log of what is currently happening.
The logger looks like this;
using System;
using System.IO;
namespace BatchInvoice
{
public enum LoggingLevel
{
Verbose = 0,
Info = 1,
Warning = 2,
Error = 3
}
public sealed class Logger
{
string _logFile;
static Logger() { }
public bool LogToDataBase = false;
public bool LogToFile = true;
public bool LogToScreen = false;
private Logger()
{
//string filePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string filePath = Directory.GetCurrentDirectory();
filePath = filePath + #"\LogFiles";
string extension = ".log";
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
/*string currentDir = Environment.CurrentDirectory;
DirectoryInfo directory = new DirectoryInfo(currentDir);
string fullDirectory = directory.FullName;*/
string date = (DateTime.Now).ToString("yyyyMMddHHmmss");
_logFile = filePath + "\\" + date + extension;
minimumLoggingLevel = LoggingLevel.Info;
}
private LoggingLevel minimumLoggingLevel;
public static void SetMinimumLoggingLevel(LoggingLevel minimum)
{
Instance.minimumLoggingLevel = minimum;
}
public static LoggingLevel GetMinimumLoggingLevel()
{
return Instance.minimumLoggingLevel;
}
private static readonly Logger instance = new Logger();
public static Logger Instance
{
get
{
return instance;
}
}
public static void Write(string content)
{
using (StreamWriter fileWriter = File.AppendText(Instance._logFile))
{
fileWriter.WriteLine(content);
}
}
public static void Write(string content, LoggingLevel warningLevel)
{
if (Instance.minimumLoggingLevel <= warningLevel)
{
if (Instance.LogToFile)
{
using (StreamWriter fileWriter = File.AppendText(Instance._logFile))
{
fileWriter.WriteLine(warningLevel.ToString() + ": " + content);
}
}
if (Instance.LogToScreen)
ScreenLogging.Write(content, warningLevel);
if (Instance.LogToDataBase)
{
//enter database loggign code here.
}
}
}
}
}
using System.Windows;
using System.Windows.Controls;
namespace BatchInvoice
{
public class ScreenLogging
{
private static ScreenLogging _instance;
private ScreenLogging() { }
public static ScreenLogging Instance
{
get
{
if(_instance == null)
{
_instance = new ScreenLogging();
}
return _instance;
}
}
private TextBox _target;
public static void SetTarget(TextBox target)
{
Instance._target = target;
}
public static void Write(string content, LoggingLevel warningLevel)
{
//MessageBox.Show(content, warningLevel.ToString());
Instance._target.AppendText(warningLevel.ToString() + ": " + content + "\n");
}
}
}
(Yes there is a reason the screenlogging is separated into a different class, but I really hope I don't have to change that) What can I do to make the calls to this logging class reflect on the UI from within the background worker? Should I change the LogText property to read from an external file or something along those lines? Currently I don't have the background worker implemented, so the logging only shows after the task is completed, but I need to be able to monitor its progress while its running. When I tried putting it into a background worker it errored when it hit a line of code that tried to access the logger.
Since you are trying to update UI from another thread, you must do it in a special way, where threads must be synchronized to transfer data between the them. In other words, it's like the BackgroundWorker needs to do a pause to update the UI. It can be done using the ProgressChanged event of the BackgroundWorker, and the method ReportProgress. Here is a simple example:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// I guess this is how you are using your logger, right?
ScreenLogging.SetTarget(this.txtLogging);
BackgroundWorker worker = new BackgroundWorker();
// Your classic event to do the background work...
worker.DoWork += Worker_DoWork;
// Here you can sender messages to UI.
worker.ProgressChanged += Worker_ProgressChanged;
// Don't forget to turn this property to true.
worker.WorkerReportsProgress = true;
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
Thread.Sleep(3000);
// ReportProgress sends two values to the ProgressChanged method, for the
// ProgressChangedEventArgs object. The first one is the percentage of the
// work, and the second one can be any object that you need to pass to UI.
// In a simple example, I am passing my log message and just putting
// any random value at progress, since it does not matter here.
worker.ReportProgress(0, "Test!");
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Here you get your UserState object, wich is my string message passed on
// with the ReportProgress method above.
var message = e.UserState as string;
// Then you call your log as always. Simple, right?
ScreenLogging.Write(message, LoggingLevel.Info);
}
As your problem seems to not rewrite all your log calls, I will post another way of doing it, by just changing the ScreenLogging.Write method. I hope that works for you, since you will not need to change your calls to Logger.Write method.
public class ScreenLogging
{
private static ScreenLogging _instance;
private ScreenLogging() { }
public static ScreenLogging Instance
{
get
{
if (_instance == null)
{
_instance = new ScreenLogging();
}
return _instance;
}
}
private TextBox _target;
public static void SetTarget(TextBox target)
{
Instance._target = target;
}
public static void Write(string content, LoggingLevel warningLevel)
{
var appendTextAction = new Action(() =>
{
var text = warningLevel.ToString() + ": " + content + "\n";
Instance._target.AppendText(text);
});
// Only the thread that the Dispatcher was created on may access the
// DispatcherObject directly. To access a DispatcherObject from a
// thread other than the thread the DispatcherObject was created on,
// call Invoke and BeginInvoke on the Dispatcher the DispatcherObject
// is associated with.
// You can set the priority to Background, so you guarantee that your
// key operations will be processed first, and the screen updating
// operations will happen only after those operations are done.
Instance._target.Dispatcher.Invoke(appendTextAction,
DispatcherPriority.Background);
}
}
Related
I have a library that generates strings to a function called MessageOut. This function, i cannot change the structure of.
It looks like this:
public void MessageOut(string msg) //params or return-type cannot be changed
{
Console.WriteLine(msg);
}
I have a textbox in my form that i want to show this message in.
How would I go about appending msg to that textbox?
I've tried:
public void MessageOut(string msg) //params or return-type cannot be changed
{
Console.WriteLine(msg);
sendMessageTextBox(msg);
}
public string[] sendMessageTextBox(params string[] msg)
{
string send = "";
foreach(var i in msg){send = i;}
return send;
}
Form:
private void getWaveformBtn_Click(object sender, EventArgs e)
{
MyClass className = new MyClass();
foreach(var i in className.sendMessageTextBox())
{
errorTextBox.Text += i;
}
}
For obvious reasons, this doesn't work ,but i'm unsure how to go about doing this. (i've tried: how to send text to textbox through a different class?
Sending information to a textbox from a class to a form)
However, i cannot seem to get those solutions to work.
Any help is much appreciated.
TL;DR - i basically want to show the new strings that messageOut recieves in a textbox
This can be done in may different ways, one of them will be to use a queue, fill it from MessageOut and drain it on button press.
public class MessageHandler /* , MessageOutInterface */
{
private readonly Queue<string> messages;
public MessageHandler()
{
this.messages = new Queue<string>();
}
public void MessageOut(string message)
{
this.messages.Enqueue(message);
}
public IEnumerable<string> PendingMessages()
{
string message;
while (this.messages.Count > 0)
yield return this.messages.Dequeue();
}
}
Your UI code
private void getWaveformBtn_Click(object sender, EventArgs e)
{
foreach(var i in messageHandler.PendingMessages())
{
errorTextBox.Text += i;
}
}
If MessageOut is called on one thread (i.e. not the main thread) and the button press obviously happens on the main thread you'll need a thread safe approach:
public class MessageHandler /* , MessageOutInterface */
{
private readonly object syncRoot = new Object();
private readonly Queue<string> messages;
public MessageHandler()
{
this.messages = new Queue<string>();
}
public void MessageOut(string message)
{
lock (this.syncRoot)
{
this.messages.Enqueue(message);
}
}
public IEnumerable<string> PendingMessages()
{
lock (this.syncRoot)
{
var pending = this.messages.ToArray();
this.messages.Clear();
return pending;
}
}
}
This is not the best way to synchronize the threads but you can ask another question about synchronization.
this is basically a follow up to a previous question (Triggering an event in c# from c++ and declaring LPCWSTR). I've revised my code based on the answers and comments I have received and I solved the initial issue, which was passing the event to the GpioSetupInterruptPin from a gpio api. I don't have a lot of documentation on the api but what i'm trying to achieve is: have a form with a white label; after pressing a switch, the label turns yellow.
The problem i'm having now is the event seems to trigger as soon as it's created (the "execute" message is passed to the debug dialog and the label turns yellow) but it doesn't do anything when i toggle the switch. I was told in the last question to use WaitForSingleObject but i'm not really sure where to call it and this article only added to my confusion.
public partial class Form1 : Form
{
// P/Invoke CreateEvent and WaitForSingleObject
private void GPIO_Open() //get handle for gpio
private void GPIO_Output() //output pin declaration
private void button1_Click(object sender, EventArgs e)
{
Interrupt_Setup();
}
private void Interrupt_Setup()
{
hGPIO = GPIOapi.GpioOpenHandle(); //returns a handle to the gpio
GIPO_ON = true;
Debug.WriteLine("Driver open \n" + hGPIO);
GPIO_Output(); //set output pins
GPIO_Interrupt(Trigger); //configure interrupt
}
private void GPIO_Interrupt(string trigger)
{
bool ok;
_Main();
//INTERRUPT DECALRATION
ok = GPIOapi.GpioSetupInterruptPin(hGPIO, port6, 4, GPIOapi.INT_TRIGGER_MODE.TRIGGER_MODE_EDGE,
GPIOapi.INT_TRIGGER_POLARITY.TRIGGER_POL_HIGH_RISING, trigger, true);
Thread waitThread=new Thread(WaitForTrigger);
waitThread.Start();
if (!ok)
Debug.WriteLine("NO interrupt");
else
Debug.WriteLine("Interrupt set for:" + port6 + "04" + " at " + hGPIO);
}
public static string Trigger = "InputProcessUpdateHandler";
public static IntPtr handle = CreateEvent(IntPtr.Zero, false, false, Trigger); //used P/Invoke
private static InputProcessor inputProcessor = null;
public Color[] color =
{
Color.Orchid, Color.DarkOrchid, Color.GreenYellow, Color.CornflowerBlue, Color.SteelBlue,Color.Crimson
};
public int i = 0;
public void WaitForTrigger()
{
while(true)
{try
{
if (WaitForSingleObject(handle, 0xFFFFFFFF) == false)
{
BeginInvoke(((System.Action)(() =>label2.BackColor = color[i])));
i++;
if (i > 4)
i = 0;
}
Thread.Sleep(300);
}
catch (Exception e)
{ Debug.WriteLine("exception: " + e); }}
}
}
private void _Main()
{
inputProcessor = new InputProcessor();
ShowToggle showToggle = new ShowToggle(inputProcessor);
inputProcessor.Process(label1);
}
public class ShowToggle
{
private InputProcessor _inputProcessor = null;
public ShowToggle(InputProcessor inputProcessor)
{
_inputProcessor = inputProcessor;
_inputProcessor.updateHandledBy += InputProcessUpdateHandler;
}
private void InputProcessUpdateHandler(Label label)
{
label.BackColor = Color.Yellow;
Debug.Write("execute");
}
}
public class InputProcessor
{
public delegate void InputProcessUpdateHandler(Label label);
public event InputProcessUpdateHandler updateHandledBy = null;
public void Process(Label label)
{
if (updateHandledBy != null)
updateHandledBy(label);
}
}
If anyone could help me with this, I would be very grateful.
*** I got it working but it looks a right mess. Could anyone help me straighten it out?
You code is really confusing to me. I think what you want is something like this. Bear in mind I'm typing this into the SO text editor, so don't expect it to compile and just work - it's a guide. Consider it a step above pseudocode.
public class DeviceInterrupt
{
IntPtr m_gpio;
string m_eventName;
public event EventHandler OnInterrupt;
public DeviceInterrupt(int port)
{
// get a driver handle
m_gpio = GPIO_Open();
// generate some unique event name
m_eventName = "GPIO_evt_" + port;
// wire up the interrupt
GpioSetupInterruptPin(m_gpio, port, m_eventName, ...);
// start a listener
new Thread(EventListenerProc)
{
IsBackground = true,
Name = "gpio listener"
}
.Start();
}
public void Dispose()
{
// TODO: release the handle
}
private void EventListenerProc()
{
// create the event with the name we sent to the driver
var wh = new WaitHandle(false, m_eventName);
while (true)
{
// wait for it to get set by the driver
if (wh.WaitOne(1000))
{
// we have an interrupt
OnInterrupt.Fire(this, EventArgs.Empty);
}
}
}
}
Usage would then be something like this:
var intr = new DeviceInterrupt(4);
intr.OnInterrupt += MyHandler;
....
void MyHandler(object sender, EventArgs a)
{
Debug.WriteLine("Interrupt occurred!");
}
Note
The Compact Framework doesn't support actual named system events, so the named WaitHandle I use in my code above is not a CF-supplied WaitHandle. Instead I'm using the one from the Smart Device Framework. You could also P/Invoke to CreateEvent and WaitForSingleObject yourself.
in my WPF - C# application, I have a time consuming function, which I execute with a BackgroundWorker. The job of this function is to add given data from a file into a database. Now and then, I need some user feedback, for example the data is already in the store and I want to ask the user, whether he wants to merge the data or create a new object or skip the data completely. Much like the dialog windows shows, if I try to copy a file to a location, where a file with the same name already exists.
The problem is, that I cannot call a GUI-window from a non GUI-thread. How could I implement this behavior?
Thanks in advance,
Frank
You could work with EventWaitHandle ou AutoResetEvent, then whenever you want to prompt the user, you could the signal UI, and then wait for the responde. The information about the file could be stored on a variable.
If possible... my suggestion is to architect your long running task into atomic operations. Then you can create a queue of items accessible by both your background thread and UI thread.
public class WorkItem<T>
{
public T Data { get; set; }
public Func<bool> Validate { get; set; }
public Func<T, bool> Action { get; set; }
}
You can use something like this class. It uses a queue to manage the execution of your work items, and an observable collection to signal the UI:
public class TaskRunner<T>
{
private readonly Queue<WorkItem<T>> _queue;
public ObservableCollection<WorkItem<T>> NeedsAttention { get; private set; }
public bool WorkRemaining
{
get { return NeedsAttention.Count > 0 && _queue.Count > 0; }
}
public TaskRunner(IEnumerable<WorkItem<T>> items)
{
_queue = new Queue<WorkItem<T>>(items);
NeedsAttention = new ObservableCollection<WorkItem<T>>();
}
public event EventHandler WorkCompleted;
public void LongRunningTask()
{
while (WorkRemaining)
{
if (_queue.Any())
{
var workItem = _queue.Dequeue();
if (workItem.Validate())
{
workItem.Action(workItem.Data);
}
else
{
NeedsAttention.Add(workItem);
}
}
else
{
Thread.Sleep(500); // check if the queue has items every 500ms
}
}
var completedEvent = WorkCompleted;
if (completedEvent != null)
{
completedEvent(this, EventArgs.Empty);
}
}
public void Queue(WorkItem<T> item)
{
// TODO remove the item from the NeedsAttention collection
_queue.Enqueue(item);
}
}
Your UI codebehind could look something like
public class TaskRunnerPage : Page
{
private TaskRunner<XElement> _taskrunner;
public void DoWork()
{
var work = Enumerable.Empty<WorkItem<XElement>>(); // TODO create your workItems
_taskrunner = new TaskRunner<XElement>(work);
_taskrunner.NeedsAttention.CollectionChanged += OnItemNeedsAttention;
Task.Run(() => _taskrunner.LongRunningTask()); // run this on a non-UI thread
}
private void OnItemNeedsAttention(object sender, NotifyCollectionChangedEventArgs e)
{
// e.NewItems contains items that need attention.
foreach (var item in e.NewItems)
{
var workItem = (WorkItem<XElement>) item;
// do something with workItem
PromptUser();
}
}
/// <summary>
/// TODO Use this callback from your UI
/// </summary>
private void OnUserAction()
{
// TODO create a new workItem with your changed parameters
var workItem = new WorkItem<XElement>();
_taskrunner.Queue(workItem);
}
}
This code is untested! But the basic principle should work for you.
Specifically to your case
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(1000);
var a = Test1("a");
Thread.Sleep(1000);
var b = (string)Invoke(new Func<string>(() => Test2("b")));
MessageBox.Show(a + b);
}
private string Test1(string text)
{
if (this.InvokeRequired)
return (string)this.Invoke(new Func<string>(() => Test1(text)));
else
{
MessageBox.Show(text);
return "test1";
}
}
private string Test2(string text)
{
MessageBox.Show(text);
return "test2";
}
Test2 is a normal method which you have to invoke from background worker. Test1 can be called directly and uses safe pattern to invoke itself.
MessageBox.Show is similar to yourForm.ShowDialog (both are modal), you pass parameters to it (text) and you return value (can be a value of property of yourForm which is set when form is closed). I am using string, but it can be any data type obviously.
From the input of the answers here, I came to the following solution:
(Mis)Using the ReportProgress-method of the Backgroundworker in Combination with a EventWaitHandle. If I want to interact with the user, I call the ReportProgress-method and setting the background process on wait. In the Handler for the ReportProgress event I do the interaction and when finished, I release the EventWaitHandle.
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
}
// Starting the time consuming operation
private void Button_Click(object sender, RoutedEventArgs e)
{
bgw.RunWorkerAsync();
}
// using the ProgressChanged-Handler to execute the user interaction
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UserStateData usd = e.UserState as UserStateData;
// UserStateData.Message is used to see **who** called the method
if (usd.Message == "X")
{
// do the user interaction here
UserInteraction wnd = new UserInteraction();
wnd.ShowDialog();
// A global variable to carry the information and the EventWaitHandle
Controller.instance.TWS.Message = wnd.TextBox_Message.Text;
Controller.instance.TWS.Background.Set();
}
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(e.Result.ToString());
}
// our time consuming operation
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
// need 4 userinteraction: raise the ReportProgress event and Wait
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
// The WaitHandle was released, the needed information should be written to global variable
string first = Controller.instance.TWS.Message.ToString();
// ... and again
Thread.Sleep(2000);
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
e.Result = first + Controller.instance.TWS.Message;
}
I hope I did not overlooked some critical issues. I'm not so familar with multithreading - maybe there should be some lock(object) somewhere?
I've been trying to get this little IRC program working but for some reason I'm having issues with VS and cross threading. I'm not sure if I'm not doing it the proper way or what. Here are the parts causing the issue.
Main Thread:
public partial class MainUI : Form
{
private static IRC irc = new IRC();
public MainUI()
{
InitializeComponent();
}
public static void StartIRC()
{
irc.Start();
}
}
IRC Thread:
class IRC
{
private Thread ircThread;
private bool _running = true;
private NetworkStream stream;
private StreamWriter writer;
private StreamReader reader;
private TcpClient irc;
public IRC(){
ircThread = new Thread(new ThreadStart(Run));
ircThread.IsBackground = true;
}
public void Run(){
while (_running) {
parseInStream(reader.ReadLine());
}
}
public void Start()
{
ircThread.Start();
}
private void parseInStream(String inText)
{
String[] text = inText.Split(' ');
String name;
String message;
if (text[1].Equals("PRIVMSG")) {
name = capsFirstChar(getUser(inText));
message = inText.Substring(inText.IndexOf(":", 1) + 1);
sendToChatBox(capsFirstChar(name) + ": " + message, Color.Black);
}
else if (text[1].Equals("JOIN")) {
name = getUser(inText);
sendToChatBox(capsFirstChar(name) + " has joined the channel.", Color.LimeGreen);
}
else if (text[1].Equals("PART")) {
name = getUser(inText);
sendToChatBox(capsFirstChar(name) + " has left the channel.", Color.Red);
}
}
public void sendToChatBox(String text, Color color)
{
//Trying to send the text to the chatbox on the MainUI
//Works if the MainUI.Designer.cs file has it set to static
if (MainUI.txtMainChat.InvokeRequired) {
MainUI.txtMainChat.Invoke((MethodInvoker)delegate() {
sendToChatBox(text, color);
});
}
else {
MainUI.txtMainChat.SelectionColor = color;
MainUI.txtMainChat.AppendText(text);
}
}
private String getUser(String msg)
{
String[] split = msg.Split('!');
user = split[0].Substring(1);
return capsFirstChar(user);
}
private String capsFirstChar(String text)
{
return char.ToUpper(text[0]) + text.Substring(1).ToLower();
}
}
The only way I am able to get it to work is if I enter the MainUI.Designer.cs file and change the textbox to static and then change everything from this.txtMainChatto MainUI.txtMainChat.
My main problem is that when I make any changes on the visual side all the things labeled static or things named MainUI are deleted. I'm trying to figure out what I need to do to keep this from happening. Am I doing it the right way, or is there a better way? I tried using a background worker but it was using a lot of processing power to work that way for some reason.
I've looked around the web and can't seem to find out how one might relate to my setup. I see people calling a thread from the main thread and then sending things from the main thread to the thread it called but not the other way around. There is nothing else being written to the text box so there won't be an issue with it being used by two threads at the same time.
On my main UI thread I passed in "this" so I could reference the main window from my IRC Class. MainUI.txtMainChat
irc = new IRC(this);
Then in my IRC class
MainUI main;
public IRC(MainUI main){
this.main = main;
ircThread = new Thread(new ThreadStart(Run));
ircThread.IsBackground = true;
}
Then I was able to Change
//MainUI.txtMainChat to
main.txtMainChat
Like Cameron said, Though I know I was told it's not the best approach it gets me started.
Your designer file is rebuilt every time you change your UI in the designer.
You'll need to pass your MainUi to your IRC class, or give it an abstraction of it using an interface (best option).
public interface IMainUI
{
void AddText(string text, Color color);
void UiThread(Action code);
}
public class MainUI : IMainUI
{
// Whatever else
public void AddText(string text, Color color)
{
UiThread( () =>
{
// Same code that was in your Irc.SendToChatBox method.
});
}
public void UiThread(Action code)
{
if (InvokeRequired)
{
BeginInvoke(code);
return;
}
code.Invoke();
}
}
public class IRC
{
IMainUI _mainUi;
//Other properties, and fields
public IRC(IMainUI mainUi)
{
this._mainUi = mainUi;
// Other constructor stuff.
}
// Other logic and methods
}
I have the following code:
namespace SSS.RemoteTruckService
{
public partial class Startup : Form
{
private Timer _gpsTimer;
private Timer _ppsTimer;
private Timer _creditCardTimer;
private Timer _iniTimer;
public string Message
{
get { return richTextBox_Message.Text; }
set
{
richTextBox_Message.Invoke((MethodInvoker)(()
=> richTextBox_Message.Text = DateTime.Now + " " +
value + Environment.NewLine + richTextBox_Message.Text));
}
}
public Startup()
{
InitializeComponent();
}
private void ButtonStartClick(object sender, EventArgs e)
{
StartRemoteTruck();
}
private void ButtonPauseClick(object sender, EventArgs e)
{
if (_gpsTimer.Enabled) _gpsTimer.Enabled = false;
if (_ppsTimer.Enabled) _ppsTimer.Enabled = false;
if (_creditCardTimer.Enabled) _creditCardTimer.Enabled = false;
if (_iniTimer.Enabled) _iniTimer.Enabled = false;
ProcessIniFile.StopProcess();
}
public void StartRemoteTruck()
{
Message = "RemoteTruck started.";
if (Settings.GlobalSettings == null)
{
Message = "GlobalSettings was null or not loaded. Cannot continue.";
Logging.Log("GlobalSettings was null or not loaded. Cannot continue.", "RemoteTruck", Apps.RemoteTruckService);
Environment.Exit(0);
}
if (Settings.GlobalSettings.IniFileWatcherEnabled)
{
ProcessIniFile.StartProcess();
}
CreateTimers();
}
And in the ProcessIniFile.StartProcess() I have the code:
namespace SSS.RemoteTruckService.inifile
{
public static class ProcessIniFile
{
private static DateTime _iniLastWriteTime;
private static readonly string Inifile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "sss.ini");
private static FileSystemWatcher _watcher;
public static void StartProcess()
{
ReadIniFile();
SaveCurrentIniReadings();
CreateIniFileWatcher();
}
public static void StopProcess()
{
if (_watcher != null)
{
_watcher.EnableRaisingEvents = false;
_watcher = null;
}
}
private static void CreateIniFileWatcher()
{
_watcher = new FileSystemWatcher
{
Path = Environment.GetFolderPath(Environment.SpecialFolder.Windows),
NotifyFilter = NotifyFilters.LastWrite,
Filter = "sss.ini"
};
_watcher.Changed += SssIniWatcherChanged;
_watcher.EnableRaisingEvents = true;
}
I'd like to pass back the the calling form the status of the reads of the file watcher.
Maybe I'm overthinking this, but if I want to add to the Message on the main form, how do I get to it?
You can use Events for that. Your process can send events and your form can handle them.
More info: http://msdn.microsoft.com/en-us/library/awbftdfh.aspx
The simple but not pretty way I like to use is to make that part of the form static as well. For example, creating a static variable WriteMessage, and in your Form Load or Startup(), you can set it:
WriteMessage = (s) => Message = s;
Sure this has some issues, but it's a quick way to get it done. One of those issues is that, you may need to use Dispatcher.invoke if you're not on the UI thread.