I have a C# form application that communicate with PLC via Kepware OPC Server.
But this communication slowdown my GUI. I use thread for communication but form tabpages still very slow. I am sending a part of my code. Where am I wrong?
public Form1()
{
InitializeComponent();
Connect_Opc_Server("Kepware.KEPServerEX.V5");
ConnectToSqlToRead();
ShowPartType(1);
}
private void timer_Kepware_Tick(object sender, EventArgs e)
{
Thread KepwareThread = new Thread(new ThreadStart(Kepware_Read_Write));
if (KepwareThread.IsAlive)
{ }
else
{
KepwareThread.Start();
}
}
public void Kepware_Read_Write()
{
if (KepwarePLCReadError == false)
{
synch_read();
}
if (KepwarePLCReadOK == true)
{
synch_write();
}
}
You might want to create only 1 thread.
private void timer_Kepware_Tick(object sender, EventArgs e)
{
if (_KepwareThread == null)
{
_KepwareThread = new Thread(...);
}
if (!_KepwareThread.IsAlive)
{
_KewpareThread.Start();
}
}
Related
This is rather a difficult situation to explain. I have a Windows Forms application that uses a notification icon with a context menu. The app can be configured to start with no form shown or the form can be closed by the user leaving just the notify icon present. Selecting the "Show" option from the context menu strip of the notification icon will create (if closed)/restore(if minimized) the main form. Now this all works fine as the events generated from the menu are running on the STA thread. The problem is the single instance implementation. A notify icon type application really should only ever be running one instance, otherwise you'd have multiple notify icons in the tray and it'd be a huge mess. This is accomplished using a named EventWaitHandle and also includes detection of an attempt to run a second instance, when this happens it signals the main running instance which then restores/creates the main form thus:
public static bool InitSingleInstance(this Control control, string handleName, Action? NewInstanceAttempt = null)
{
EventWaitHandle ewh = new(false, EventResetMode.ManualReset, handleName, out bool isNew);
if (isNew)
{
Task.Run(() =>
{
while (!control.IsDisposed)
{
ewh.WaitOne();
ewh.Reset();
NewInstanceAttempt?.Invoke();
}
});
}
else
{
Task.Run(() =>
{
EventWaitHandle.SignalAndWait(ewh, ewh, 100, true);
}).Wait();
}
return isNew;
}
The issue I have is that the loop waiting for a signal from a second instance runs in another Thread and thus needs to be delegated to the STA thread to restore/create the form. I use a UserControl to contain the NotifyIcon and Menu (So I can edit them in the designer) and this is created in Program.cs in place of Application.Run(new Form()) but this control is never actually shown itself so it never gets a Handle assigned to it that I can Invoke on so I get an exception when the second instance is run.
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if(!this.InitSingleInstance("FOOBFOOB387846", NewInstanceAttempt))
return;
InitializeComponent();
Application.Run();
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Visible = true;
mainForm.Resize += MainForm_Resize;
}
mainForm.WindowState = mainFormState;
}
private void Quit()
{
Dispose();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Invoke(ShowMainForm); // << exception
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) ShowMainForm();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
Perhaps there is a better way to do this? I did think of running a loop in the constructor waiting on a locked object and pulsing it from the other thread. Like this
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("NotIconDemo387846", NewInstanceAttempt))
return;
InitializeComponent();
lock (this)
{
while (!IsDisposed)
{
Monitor.Wait(this);
ShowMainForm();
}
}
}
private void NewInstanceAttempt()
{
lock(this)
{
Monitor.Pulse(this);
}
}
But this seems messy.
EDIT: And indeed wouldn't work as it would lock up the STA thread.
I managed to solve it like this:
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("GROWPLENTYOFCHEESE4444", NewInstanceAttempt))
return;
InitializeComponent();
FormShowLoop();
Application.Run();
}
private async void FormShowLoop()
{
while (!IsDisposed)
{
await Task.Run(() =>
{
lock (this)
{
Monitor.Wait(this);
}
});
if(!IsDisposed)
ShowMainForm();
}
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Resize += MainForm_Resize;
mainForm.Visible = true;
}
mainForm.WindowState = mainFormState;
}
private void Pulse()
{
lock (this)
{
Monitor.Pulse(this);
}
}
private void Quit()
{
Dispose();
Pulse();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Pulse();
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) Pulse();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
So i have 2 forms, one is called "musica" and its playing a music (this form is invisible) and the other is called "Form1" and gives the option to stop and start the music with radio buttons.
The problem is that the radio buttons are working, but the video continues even if i click on "radionButton2" and if i use the "stop()" function on "musica_load" the music stops so i don't think the problem is from there either.
What is my mistake here?
Form1 code:
musica mus = new musica();
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
if (radioButton1.Checked == true)
{
mus.play();
}
}
private void radioButton2_CheckedChanged(object sender, EventArgs e)
{
if (radioButton2.Checked == true)
{
mus.stop();
}
}
musica code:
public void play()
{
axWindowsMediaPlayer1.Ctlcontrols.play();
}
public void stop()
{
axWindowsMediaPlayer1.Ctlcontrols.stop();
}
// Form1.cs
musica mus;
public Form1()
{
InitializeComponent();
mus = new musica();
}
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
if (radioButton1.Checked == true)
{
mus.Play(); ;
}
}
private void radioButton2_CheckedChanged(object sender, EventArgs e)
{
if (radioButton2.Checked == true)
{
mus.Stop();
}
}
//Musica.cs
public musica()
{
InitializeComponent();
}
public void Play()
{
MessageBox.Show("Play");
}
public void Stop()
{
MessageBox.Show("Stop");
}
This works fine there must be some problem with the Play and Stop methods.
Answer: The problem was that i was opennig both forms simultaneously at the start of the program and for some reason they can't work between each other, if i open the musica.cs in Form1_load everything works!
I'm writing a fairly basic application, where one of the tasks is communicating with an Arduino Uno card.
I would like to write the serial communication as a separate module, so the forms only calls for the input from Arduino, and the module will handle the creating and opening of the serialPort and reading data from it.
For testing purposes I wrote a program for the Arduino which prints the elapsed milliseconds every half second to the serialPort.
I would like to populate the textBox of my Form with the output from Arduino after I press a button.
I create the serialPort in the SerialComm class, and although I attach a DataReceived event handler to it, it never seems to fire.
Here is the code for the SerialComm class:
class SerialComm
{
private List<String> availablePorts;
private SerialPort arduino;
private string receivedText;
public String[] portList
{ get
{
EnumPorts();
return availablePorts.ToArray();
}
}
public string receivedData
{
get
{
return receivedText;
}
}
public void InitialiseSerial()
{
arduino = new SerialPort();
arduino.BaudRate = 9600;
arduino.DtrEnable = true;
// Add event handler
arduino.DataReceived += new SerialDataReceivedEventHandler(arduino_DataReceived);
}
public void EnumPorts()
{
availablePorts = new List<string>();
foreach (string s in SerialPort.GetPortNames())
{
availablePorts.Add(s);
}
}
public void StartMC(SerialPort serialPort, String portName)
{
arduino = serialPort;
if (arduino.IsOpen)
{
arduino.Close();
}
else
{
//Initialise Serial Port
arduino.PortName = portName;
arduino.Open();
}
}
//This never fires==================
private void arduino_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
receivedText = arduino.ReadExisting();
}
public void CloseMC()
{
if (arduino.IsOpen)
{
arduino.Close();
arduino.Dispose();
}
}
}
In the Form I call the text from Arduino like this: (I have a button (Button1), a combobox for selecting the COM port (comboBox1) and a textBox on the Form)
public partial class Form1 : Form
{
SerialComm arduino = new SerialComm();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//Arduino
arduino.InitialiseSerial();
String[] portList = arduino.portList;
foreach (String COM in portList)
{
comboBox1.Items.Add(COM);
}
if (portList.Length > 0)
{
comboBox1.SelectedItem = comboBox1.Items[0];
}
else
{
comboBox1.Text = "No microcontroller found!";
}
}
private void button1_Click(object sender, EventArgs e)
{
arduino.StartMC(serialPort1, comboBox1.SelectedItem.ToString());
//as the DataReceived never fires arduino.receivedData stays null
if (arduino.receivedData != null)
{
for (int i = 0; i < 101; i++)
{
textBox1.AppendText(arduino.receivedData);
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
arduino.CloseMC();
}
}
Do you see any reason why the event handler doesn't trigger?
Thank you very much for your help in advance.
Best Regards,
Peter
Okay so I have ran into a problem, I have created a server which handles a single client and now I want to kick it up a notch and have it handle multiple clients at one time.
I have looked and tried to do this using HashTables and also Async but I keep getting stuck, this is a grey area for me as I have only just recently started dealing with sockets etc...
I wondered if anyone had a way of doing it?
Any advise will be taken on board.
This is my server code(If it helps).
namespace ChatServer
{
delegate void UpdateTextBox(string msg);
public partial class Form1 : Form
{
private TcpListener ConnectionListener;
private BinaryReader MessageReader;
private BinaryWriter MessageWriter;
private Socket ClientConnection;
private NetworkStream DataStream;
private Thread ListeningThread;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
try
{
IPAddress.Parse(textBox3.Text);//
ListeningThread = new Thread(new ThreadStart(ListenForConnections));
ListeningThread.Start();
}
catch (Exception)
{
MessageBox.Show("Wrong Ip Address");
}
}
private void button2_Click(object sender, EventArgs e)
{
try
{
if (ClientConnection.Connected)
{
MessageWriter.Write(textBox2.Text);
textBox2.Clear();
}
}
catch (Exception)
{
MessageBox.Show("no client is connected");
}
}
private void textBox2_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
try
{
if (ClientConnection.Connected)
{
MessageWriter.Write(textBox2.Text);
textBox2.Clear();
}
}
catch (Exception)
{
MessageBox.Show("no client is connected");
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
System.Environment.Exit(System.Environment.ExitCode);
}
private void ListenForConnections()
{
try
{
ConnectionListener = new TcpListener(IPAddress.Parse(textBox3.Text), 80);
ConnectionListener.Start();
ChangeTextBoxContent("Listening For Connections");
ClientConnection = ConnectionListener.AcceptSocket();
DataStream = new NetworkStream(ClientConnection);
MessageReader = new BinaryReader(DataStream);
MessageWriter = new BinaryWriter(DataStream);
ChangeTextBoxContent("Connection Received");
HandleConnection();
MessageReader.Close();
MessageWriter.Close();
DataStream.Close();
ClientConnection.Close();
}
catch (Exception)
{
MessageBox.Show("Unable to connect, wrong ip address");
}
}
private void HandleConnection()
{
string message;
do
{
try
{
message = MessageReader.ReadString();
ChangeTextBoxContent(message);
}
catch (Exception)
{
ChangeTextBoxContent("connection Lost");
break;
}
} while (true);
}
private void ChangeTextBoxContent(string tx)
{
if (textBox1.InvokeRequired)
{
Invoke(new UpdateTextBox(ChangeTextBoxContent), new object[] { tx });
}
else
{
textBox1.Text += tx + "\r\n";
}
}
}
}
Thank you in advance.
You should handle each of your connections in a separate thread. Create a loop that constantly listens for remote connections, and when a remote connection is called, create a new thread with the connection as the object parameter.
I only want to have a single instance of my Window in WPF.
My code in the window:
public static bool IsOpen { get; private set; }
private void Window_Loaded(object sender, RoutedEventArgs e)
{
IsOpen = true;
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
{
IsOpen = false;
}
My code in the open function
if (MyWindow!= null)
{
if (MyWindowName.IsOpen)
{
MyWindow.Activate();
}
else
{
MyWindow.Close();
MyWindow= null;
}
}
if (MyWindow!= null) return;
MyWindow= new MyWindowName();
MyWindow.Show();
MyWindow.Activate();
But I am able to open many instances of the window if I click fast 3-5 times.
Thanks
There will be a delay between you opening with window with the show command (which will then process the message queue allowing the processing of the other clicks) and then the firing of the windows loaded event.
An extremely quick and dirty fix would be to move the IsOpen assignment to just before the show command, you could also use a return inside the if to remove the need for a second check.
if (MyWindow!= null)
{
if (MyWindowName.IsOpen)
{
MyWindow.Activate();
return;
}
else
{
MyWindow.Close();
MyWindow= null;
}
}
MyWindow= new MyWindowName();
MyWindow.IsOpen = true;
MyWindow.Show();
MyWindow.Activate();
Clearly you're hitting a race condition. It's not that surprising considering you're having to leverage the event model of the Window. Just declare an object in the same class MyWindow is declared in, like this:
private object _lockObj = new object();
and then when doing your work, do this:
lock (_lockObj)
{
if (MyWindow!= null)
{
if (MyWindowName.IsOpen)
{
MyWindow.Activate();
}
else
{
MyWindow.Close();
MyWindow= null;
}
}
if (MyWindow!= null) return;
MyWindow= new MyWindowName();
MyWindow.Show();
MyWindow.Activate();
}
How about this simple code.
Window2 win;
object locker = new Object();
private void OnShow(object sender, RoutedEventArgs e)
{
lock (locker)
{
if (win == null)
win = new Window2();
win.Show();
}
}
private void OnHide(object sender, RoutedEventArgs e)
{
if (win != null)
win.Hide();
}