So I have been working on my C# project which required to do some serial communication. Everything was working fine until I have one really... weird issue.
So right now I am recieving a code in serial which is stored in myString. Then below where I get the issue which is that if I am not debugging the code then a whole if statement just... get ignored. However when I use a breakpoint it does everything right. For referance everything here works except the part of (*l)
(the rest of the code was removed for needless parts. I can attach more if you guys think it can help.)
public delegate void AddDataDelegate(String myString);
public AddDataDelegate myDelegate;
//
//
this.myDelegate = new AddDataDelegate(AddDataMethod);
//
//
private void Receiver(object sender, SerialDataReceivedEventArgs e)
{
Byte[] str = System.Text.Encoding.ASCII.GetBytes(comPort.ReadExisting());
this.Invoke(this.myDelegate, new Object[]
{
System.Text.Encoding.Default.GetString(str)
});
}
//
private void button2_Click(object sender, EventArgs e)
{
if (connectionStatus.Text == "Connected")
{
comPort.Close();
}
else
{
try
{
comPort.PortName = port;
comPort.BaudRate = baudRate;
comPort.DataBits = dataBits;
comPort.StopBits = (StopBits)stopBits;
comPort.Parity = parity;
comPort.DataReceived += new SerialDataReceivedEventHandler(Receiver);
comPort.Open();
connectionButton.Text = "Disconnect";
connectionStatus.Text = "Connected";
}
catch
{
comPort.Close();
}
}
}
public void AddDataMethod(String myString)
{
try
{
string colorSensorValue;
string distanceSensorValue;
string proximitySwitch;
string limitSwitch;
if (myString.Contains("*c*"))
{
string[] colors;
int red;
int blue;
int green;
int max;
colorSensorValue = myString.Substring(myString.IndexOf("*c*") + 3);
if (colorSensorValue.Contains("*e")) colorSensorValue = colorSensorValue.Substring(0, colorSensorValue.IndexOf("*e*"));
colors = colorSensorValue.Split(',');
red = Convert.ToInt16(colors[0]);
green = Convert.ToInt16(colors[1]);
blue = Convert.ToInt16(colors[2]);
max = Math.Max(red, Math.Max(green, blue));
red = red * 255 / max;
blue = blue * 255 / max;
green = green * 255 / max;
color.BackColor = Color.FromArgb(red,blue,green);
colorSensorTextBox.Text = (colorSensorValue);
}
if (myString.Contains("*d"))
{
distanceSensorValue = myString.Substring(myString.IndexOf("*d*") + 3);
if (distanceSensorValue.Contains("*e")) distanceSensorValue = distanceSensorValue.Substring(0, distanceSensorValue.IndexOf("*e*"));
distanceSensorTextBox.Text = (distanceSensorValue);
}
if (myString.Contains("*l"))
{
limitSwitch = myString.Substring(myString.IndexOf("*l*") + 3);
if (limitSwitch.Contains("*e")) limitSwitch = limitSwitch.Substring(0, limitSwitch.IndexOf("*e*"));
limitRead.Text = limitSwitch;
}
if (myString.Contains("*p"))
{
proximitySwitch = myString.Substring(myString.IndexOf("*p*") + 3);
if (proximitySwitch.Contains("*e")) proximitySwitch = proximitySwitch.Substring(0, proximitySwitch.IndexOf("*e*"));
proximityRead.Text = proximitySwitch;
}
comPort.BaseStream.Flush();
}
catch
{
}
}
Example of myString:
*c*96,84,75*e**d*25.5*e**p*0*e**l*0*e*
so it will be read as:
colors: 96 , 84 , 75 (happens right!)
distance: 25.5 (happens right!)
prox : 0 (happens right!)
limit : 0 (doesnt happen..)
note that order of both sent and received data doesn't change which one (limit) that doesn't work unless I breakpoint
According the SerialPort: class information in MSDN:
The DataReceived event is raised on a secondary thread when data is
received from the SerialPort object. Because this event is raised on a
secondary thread, and not the main thread, attempting to modify some
elements in the main thread, such as UI elements, could raise a
threading exception. If it is necessary to modify elements in the main
Form or Control, post change requests back using Invoke, which will do
the work on the proper thread.
You should use use this form:
this.Invoke(this.AddDataMethod, new Object[] { The_Received_String });
}
Related
I'm a teacher with a very limited programming background struggling with C# to make some videos for my students who are pretty much anxious and depressed during the pandemic. I'm using GTA5 as my platform because it is easy to customize, has amazing locations, hundreds of potential characters, and supports voice as well (I'm using Amazon Polly). Needlessly to say I'm stripping out all the violence and swearing.
The roadblock I'm hitting, and there isn't any support to speak of on any GTA forums or Mods forums, is how to display lines of text on the screen on demand. I've managed to do this hardcoded, but I would prefer to read this from a text file and display line by line, not by a timer, but on demand with a single key binding, ideally with the ability to go back and forth (but line by line).
A friend of mine who works for a AAA gaming company, doesn't know the GTA5 environment but said the solution would be to read the lines into an array. Unfortunately I don't have that programming knowledge, so I'm stuck at this point. Here is my code. Right now it will only display the last line of a test text file. Code is put together from Microsoft documentation and random GTA forum posts. Again, I can do this manually through multiple hardcoded lines of text, each with a key bind, but that's totally impractical. Need text file, one keybinding and a way go line by line (and ideally backwards)
using System;
using System.Drawing;
using System.Windows.Forms;
using GTA;
using GTA.Math;
using GTA.Native;
namespace TextDrawing
{
public class TextDraw : Script
{
public static string TextString { get; set; }
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
}
private void onTick(object sender, EventArgs e)
{
if (Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// TextString = "Default Start Screen goes here if you want it on load";
var Text4Screen = new GTA.UI.TextElement(TextString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000); //last parameter is wrap width
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.NumPad7) //code replace code in Basics_Tick
{
string[] lines = System.IO.File.ReadAllLines
(#"C:\Users\UserName\test.txt");
foreach (string line in lines)
{
TextString = line; //"Lesson 1 Topics Today. \n\n Part 1. Drawing Text on Screen \n\n Part 2. Customizations" + TextString2;
DrawText();
}
}
}
}
}
There are many ways to do this. But a few key code changes should make it work.
Create two class-level variables, an array to keep all the read files and an int index to keep track of your current index.
Create a function to read and invoke that in the constructor (ideally it should not be read in the constructor but rather triggered by external user action. But let's keep it simple for now).
In the key press event, update the string and increase the index. Your code is currently not working because you are iterating through the entire array and assigning text to a single variable. So only the last value is remains on screen.
public class TextDraw : Script
{
public static string TextString { get; set; }
public string[] AllLines { get; set; }
public int currentIndex = 0;
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
ReadAllLines(); ///Ideally not here. But should work still.
}
private void ReadAllLines()
{
AllLines = System.IO.File.ReadAllLines (#"C:\Users\UserName\test.txt");
}
private void onTick(object sender, EventArgs e)
{
if (Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// TextString = "Default Start Screen goes here if you want it on load";
var Text4Screen = new GTA.UI.TextElement(TextString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000); //last parameter is wrap width
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.NumPad7) //code replace code in Basics_Tick
{
if (currentIndex >= 0 && currentIndex < AllLines.Length)
{
TextString = AllLines[currentIndex];
}
else
{
TextString = "The End";
}
currentIndex++;
DrawText();
}
}
}
I'm not familiar with GTA scripting, but maybe create a member variable: int index = 0; Then use that to figure out which line to print in DrawText():
namespace TextDrawing
{
public class TextDraw : Script
{
// The lines of the text file
string [] lines;
// The index of the next line to print
int index = 0;
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
// Read the file here
lines = System.IO.File.ReadAllLines(#"C:\Users\UserName\test.txt");
}
private void onTick(object sender, EventArgs e)
{
if (0 == lines.Length || Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// Get next line
string textString = lines[index];
var Text4Screen = new GTA.UI.TextElement(textString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000);
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
// When Keys.NumPad7 is pressed, show next line
if (e.KeyCode == Keys.NumPad7)
{
if (index < lines.Length - 1) index++;
}
// When Keys.NumPad8 is pressed, show previous line
else if (e.KeyCode == Keys.NumPad8)
{
if (index > 0) index--;
}
}
}
}
Updated to use onTick and go backwards. Apparently the onTick is needed to continually redraw the text. Being unfamiliar with the GTA API, I did not realize that.
I'm making a retro-style game with C# .NET-Framework, and for dialogue I'm using a for-statement, that prints my text letter by letter (like a typewriter-effect):
I'm working with different scenes, and I have a skip button (bottom right) that skips the current dialogue and passes to the next scene. My typewriter-effect automatically stops when all the text is displayed, but when I click on the skip button, it automatically skips to the next scene.
I would like it, when the typewriter is still active, and if I click on the skip button, that it first shows all the text, instead of skipping to the next scene.
So that it only skips to the next scene when all the text is displayed (automatically or manually).
This is the (working code) that I'm using for my typewriter method (+ variables):
public string FullTextBottom;
public string CurrentTextBottom = "";
public bool IsActive;
public async void TypeWriterEffectBottom()
{
if(this.BackgroundImage != null) // only runs on backgrounds that arent black
{
for(i=0; i < FullTextBottom.Length + 1; i++)
{
CurrentTextBottom = FullTextBottom.Substring(0, i); // updating current string with one extra letter
LblTextBottom.Text = CurrentTextBottom; // "temporarily place string in text box"
await Task.Delay(30); // wait for next update
#region checks for IsActive // for debugging only!
if(i < FullTextBottom.Length + 1)
{
IsActive = true;
Debug1.Text = "IsActive = " + IsActive.ToString();
}
if(CurrentTextBottom.Length == FullTextBottom.Length)
{
IsActive = false;
Debug1.Text = "IsActive = " + IsActive.ToString();
}
#endregion
}
}
}
And this is the code that I want to get for my skip button (named Pb_FastForward):
private void PbFastForward_Click(object sender, EventArgs e)
{
if( //typewriter is active)
{
//print all text into the textbox
}
else if( //all text is printed)
{
// skip to the next scene
}
}
But I don't know how to formulate the 2nd part of code. I've tried many different approaches, like using counters that increase on a buttonclick (and using that to check in an if-statement), and many different types of if-statements to see if the typewriter is still active or not, but I haven't got anything to work yet.
Edit
This is the sequence in which different components need to be loaded (on button click), which is related to the way different variables are updated:
Gamestate_Cycle() --> called for loading new scene.
FullTextBottom = LblTextBottom.Text --> called to refresh variables for typewriter.
TypeWriterEffectBottom() --> called to perform typewriter effect.
Avoid async void. Otherwise you can get an Exception that will break your game and you will not able to catch it.
Then use as less global variables in async methods as possible.
I suggest CancellationTokenSource as thread-safe way to stop the Type Writer.
public async Task TypeWriterEffectBottom(string text, CancellationToken token)
{
if (this.BackgroundImage != null)
{
Debug1.Text = "TypeWriter is active";
StringBuilder sb = new StringBuilder(text.Length);
try
{
foreach (char c in text)
{
LblTextBottom.Text = sb.Append(c).ToString();
await Task.Delay(30, token);
}
}
catch (OperationCanceledException)
{
LblTextBottom.Text = text;
}
Debug1.Text = "TypeWriter is finished";
}
}
Define CTS. It's thread-safe, so it's ok to have it in global scope.
private CancellationTokenSource cts = null;
Call TypeWriter from async method to be able to await it.
// set button layout as "Skip text" here
using (cts = new CancellationTokenSource())
{
await TypeWriterEffectBottom(yourString, cts.Token);
}
cts = null;
// set button layout as "Go to the next scene" here
And finally
private void PbFastForward_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts?.Cancel();
}
else
{
// go to the next scene
}
}
I pondered on your task a bit more and it occurred to me that it is a good job for the Rx.Net library.
An advantage of this approach is that you have less mutable state to care about and you almost don't need to think about threads, synchronization, etc.; you manipulate higher-level building blocks instead: observables, subscriptions.
I extended the task a bit to better illustrate Rx capabilities:
there are two pieces of animated text, each one can be fast-forwarded separately;
the user can fast-forward to the final state;
the user can reset the animation state.
Here is the form code (C# 8, System.Reactive.Linq v4.4.1):
private enum DialogState
{
NpcSpeaking,
PlayerSpeaking,
EverythingShown
}
private enum EventKind
{
AnimationFinished,
Skip,
SkipToEnd
}
DialogState _state;
private readonly Subject<DialogState> _stateChanges = new Subject<DialogState>();
Dictionary<DialogState, (string, Label)> _lines;
IDisposable _eventsSubscription;
IDisposable _animationSubscription;
public Form1()
{
InitializeComponent();
_lines = new Dictionary<DialogState, (string, Label)>
{
{ DialogState.NpcSpeaking, ("NPC speaking...", lblNpc) },
{ DialogState.PlayerSpeaking, ("Player speaking...", lblCharacter) },
};
// tick = 1,2...
IObservable<long> tick = Observable
.Interval(TimeSpan.FromSeconds(0.15))
.ObserveOn(this)
.StartWith(-1)
.Select(x => x + 2);
IObservable<EventPattern<object>> fastForwardClicks = Observable.FromEventPattern(
h => btnFastForward.Click += h,
h => btnFastForward.Click -= h);
IObservable<EventPattern<object>> skipToEndClicks = Observable.FromEventPattern(
h => btnSkipToEnd.Click += h,
h => btnSkipToEnd.Click -= h);
// On each state change animationFarames starts from scratch: 1,2...
IObservable<long> animationFarames = _stateChanges
.Select(
s => Observable.If(() => _lines.ContainsKey(s), tick.TakeUntil(_stateChanges)))
.Switch();
var animationFinished = new Subject<int>();
_animationSubscription = animationFarames.Subscribe(frame =>
{
(string line, Label lbl) = _lines[_state];
if (frame > line.Length)
{
animationFinished.OnNext(default);
return;
}
lbl.Text = line.Substring(0, (int)frame);
});
IObservable<EventKind> events = Observable.Merge(
skipToEndClicks.Select(_ => EventKind.SkipToEnd),
fastForwardClicks.Select(_ => EventKind.Skip),
animationFinished.Select(_ => EventKind.AnimationFinished));
_eventsSubscription = events.Subscribe(e =>
{
DialogState prev = _state;
_state = prev switch
{
DialogState.NpcSpeaking => WhenSpeaking(e, DialogState.PlayerSpeaking),
DialogState.PlayerSpeaking => WhenSpeaking(e, DialogState.EverythingShown),
DialogState.EverythingShown => WhenEverythingShown(e)
};
_stateChanges.OnNext(_state);
});
Reset();
}
private DialogState WhenEverythingShown(EventKind _)
{
Close();
return _state;
}
private DialogState WhenSpeaking(EventKind e, DialogState next)
{
switch (e)
{
case EventKind.AnimationFinished:
case EventKind.Skip:
{
(string l, Label lbl) = _lines[_state];
lbl.Text = l;
return next;
}
case EventKind.SkipToEnd:
{
ShowFinalState();
return DialogState.EverythingShown;
}
default:
throw new NotSupportedException($"Unknown event '{e}'.");
}
}
private void ShowFinalState()
{
foreach ((string l, Label lbl) in _lines.Values)
{
lbl.Text = l;
}
}
private void Reset()
{
foreach ((_, Label lbl) in _lines.Values)
{
lbl.Text = "";
}
_state = DialogState.NpcSpeaking;
_stateChanges.OnNext(_state);
}
protected override void OnClosed(EventArgs e)
{
_eventsSubscription?.Dispose();
_animationSubscription?.Dispose();
base.OnClosed(e);
}
private void btnReset_Click(object sender, EventArgs e)
{
Reset();
}
I adjusted your code a little bit to achieve your goal. I'm not sure it's the best way to do it, but it should work.
public async void TypeWriterEffectBottom()
{
if(this.BackgroundImage == null)
{
return;
}
IsActive = true;
for(i=0; i < FullTextBottom.Length && IsActive; i++)
{
CurrentTextBottom = FullTextBottom.Substring(0, i+1);
LblTextBottom.Text = CurrentTextBottom;
await Task.Delay(30);
Debug1.Text = "IsActive = " + IsActive.ToString();
}
IsActive = false;
}
private void PbFastForward_Click(object sender, EventArgs e)
{
if(IsActive)
{
LblTextBottom.Text = FullTextBottom;
IsActive = false;
return;
}
// IsActive == false means all text is printed
// skip to the next scene
}
UPD: Just noticed that Hans Kesting has suggested pretty much exactly this in his comment.
You write what skip / forward button does, so you control it. Just have a check if the length of written text is equal to text that supposed to be written and if yes move as usual if not just display the text in full have delay to be read and move on
I have a function called: DisplayAndSaveImageFromByteArray.
by its name you probably understand what i am trying to do. In the bytearray the values are pixeldata. so like this 255,220,130,0, etc..
The size of the byte is the Width and the Height of the image times 4.
because it works with strides.
public void DisplayAndSaveImageFromByteArray(byte[] byteArray)
{
try{
byte[] data = new byte[width * height * 4];
int o = 0;
for (int io = 0; io < width * height; io++){
byte value = byteArray[io];
data[o++] = value;
data[o++] = value;
data[o++] = value;
data[o++] = 0;
}
unsafe
{
fixed (byte* ptr = data)
{
using (image = new Bitmap((int)width, (int)height, (int)width * 4,System.Drawing.Imaging.PixelFormat.Format32bppRgb, new IntPtr(ptr)))
{
image.Save(#"c:\\testmap\" + nextpicture + ".jpg", ImageFormat.Jpeg);
if (nextpicture >= 10)
{
pbCameraPreview.BeginInvoke(new InvokeDelegate(InvokeMethod));
}
nextpicture++;
}
}
}
}
catch (Exception ex){
MessageBox.Show(ex.ToString());
}
}
When i run this code it will work, but only if all values are the same for example: White (255,255,255) or Black (0,0,0).
it is able to deviate about 5 up and down in the RGB(A) values until it will stop working.
But as soon as the color of the image changes it will stop working without giving me an Exception of anything.
The only error/Exception i will get it if i leave it on for a minute and the VS will recognize that the code being executed is not doing anything and will give me a warning. > ContextSwitchDeadlock
What did i do wrong for it to crash?
and what is the solution for it?
for some reason it wont let me put on the using and namespace name...
(updated)Complete code:
public Form1()
{
InitializeComponent();
pbCameraPreview.Image = defImg;
}
#region Global
int ii;
object __p1;
EventArgs __p2;
string path;
Image defImg = Image.FromFile(#"c:\\testimg\def.jpg");
UInt32 width;
UInt32 height;
int nextpicture = 0;
FGNodeInfoContainer InfoContainer = new FGNodeInfoContainer();
FGNodeInfo NodeInfo = new FGNodeInfo();
FireWrap_CtrlCenter CtrlCenter;
enFireWrapResult Result;
UInt32 XSize = new UInt32();
UInt32 YSize = new UInt32();
UInt32 NodeCnt;
public delegate void InvokeDelegate();
Bitmap image;
CameraCode Cam = new CameraCode();
FGFrame Frame = new FGFrame();
FGUIntHL Guid = new FGUIntHL();
#endregion
public void CheckDirectory()
{
path = #"c:\\testmap\" + ii + "\\";
if (Directory.Exists(#"c:\\testmap\") == false)
{
Directory.CreateDirectory(#"c:\\testmap\");
}
if (Directory.Exists(path))
{
if (File.Exists(path + "0.Jpeg"))
{
ii++;
CheckDirectory();
}
}
else
{
Directory.CreateDirectory(path);
}
}
//Haal de images op
/// <param name="__p1"></param>
/// <param name="__p2"></param>
public void btStart_Click(object sender, EventArgs e)
{
Debug.WriteLine("btStart_Click is clicked");
// Init module
CtrlCenter = FireWrap_CtrlCenter.GetInstance();
Result = CtrlCenter.FGInitModule();
// Register frame start event
CtrlCenter.OnFrameReady += new FireWrap_CtrlCenter.FireWrapEvent(OnFrameReady);
// Get list of connected nodes
if (Result == enFireWrapResult.E_NOERROR)
{
Result = InfoContainer.FGGetNodeList();
NodeCnt = InfoContainer.Size();
// Print Nodecnt
Console.WriteLine(NodeCnt.ToString() + " camera found");
// Connect with first node
InfoContainer.GetAt(NodeInfo, 0);
Result = Cam.Connect(NodeInfo.Guid);
if (Result == enFireWrapResult.E_NOERROR)
{
Cam.m_Guid = NodeInfo.Guid;
}
// Set Format7 Mode0 Y8
if (Result == enFireWrapResult.E_NOERROR)
{
Result = Cam.SetParameter(enFGParameter.E_IMAGEFORMAT,
(uint)(((uint)enFGResolution.E_RES_SCALABLE << 16) |
((uint)enColorMode.E_CCOLORMODE_Y8 << 8) |
0));
}
if (Result != enFireWrapResult.E_NOERROR)
{
Result = Cam.SetParameter(enFGParameter.E_IMAGEFORMAT,
(uint)(((uint)enFGResolution.E_RES_SCALABLE << 16) |
((uint)enColorMode.E_CCOLORMODE_Y8 << 8) |
1));
}
// Start DMA logic
if (Result == enFireWrapResult.E_NOERROR)
Result = Cam.OpenCapture();
// Print device settings
Result = Cam.GetParameter(enFGParameter.E_XSIZE, ref XSize);
Result = Cam.GetParameter(enFGParameter.E_YSIZE, ref YSize);
Debug.WriteLine(Cam.DeviceAll + " [" + Cam.m_Guid.Low.ToString() + "] " + XSize + "x" + YSize);
width = XSize;
height = YSize;
// Start camera
if (Result == enFireWrapResult.E_NOERROR)
{
Result = Cam.StartDevice();
}
}
}
public void btStop_Click(object sender, EventArgs e)
{
// Stop the device
Cam.StopDevice();
// Close capture
Cam.CloseCapture();
// Disconnect before ExitModule
Cam.Disconnect();
// Exit module
CtrlCenter.FGExitModule();
}
/// <param name="__p1"></param>
/// <param name="__p2"></param>
public void OnFrameReady(object __p1, EventArgs __p2)
{
Debug.WriteLine("OnFrameReady is called");
FGEventArgs args = (FGEventArgs)__p2;
Guid.High = args.High;
Guid.Low = args.Low;
if (Guid.Low == Cam.m_Guid.Low)
{
Result = Cam.GetFrame(Frame, 0);
// Process frame, skip FrameStart notification
if (Result == enFireWrapResult.E_NOERROR & Frame.Length > 0)
{
byte[] data = new byte[Frame.Length];
// Access to frame data
if (Frame.CloneData(data))
{
DisplayAndSaveImageFromByteArray(data);
// Here you can start your image processsing logic on data
string debug = String.Format("[{6}] Frame #{0} length:{1}byte [ {2} {3} {4} {5} ... ]",
Frame.Id, Frame.Length, data[0], data[1], data[2], data[3], Cam.m_Guid.Low);
Debug.WriteLine(debug);
}
// Return frame to module as fast as posible after this the Frame is not valid
Result = Cam.PutFrame(Frame);
}
}
}
public void DisplayAndSaveImageFromByteArray(byte[] byteArray)
{
try{
byte[] data = new byte[width * height * 4];
int o = 0;
for (int io = 0; io < width * height; io++){
byte value = byteArray[io];
data[o++] = value;
data[o++] = value;
data[o++] = value;
data[o++] = 0;
}
unsafe
{
fixed (byte* ptr = data)
{
using (image = new Bitmap((int)width, (int)height, (int)width * 4,System.Drawing.Imaging.PixelFormat.Format32bppRgb, new IntPtr(ptr)))
{
image.Save(#"c:\\testmap\" + nextpicture + ".jpg", ImageFormat.Jpeg);
if (nextpicture >= 10)
{
pbCameraPreview.BeginInvoke(new InvokeDelegate(InvokeMethod));
}
nextpicture++;
}
}
}
}
catch (Exception ex){
MessageBox.Show(ex.ToString());
}
}
public void InvokeMethod()
{
pbCameraPreview.Image = Image.FromFile(#"c:\\testmap\" + (nextpicture -10) + ".jpg");
}
}
public class CameraCode : FireWrap_Camera
{
public FGUIntHL m_Guid;
}}
Threads running:
I recorded it for extra information:
https://www.youtube.com/watch?v=i3TxWRyZaIU
I'm not 100% sure that I have understood your problem, since it is not very clear the format of the input array and how do you have to format it before parsing it into the Bitmap variable... But here we go, I hope these tips can help you. If they don't, please try to provide some extra details on what you are trying to do.
First of all, if I have understood well, you should increase "io" and update the variable "value" each time you assign it to data[o++] in the main loop, otherwise you are assigning the same value to R, G and B pixels, which will always result in a shade of gray.
Secondly, I see a couple things in your code that are not very .net-ly... .Net provides already ways to load an image from a byte array, using Memory Streams and stuff. Take a look at How to create bitmap from byte array?
And be sure to indicate the proper format of your byte array image when instantiating the Bitmap or Image --> https://msdn.microsoft.com/en-us/library/system.drawing.imaging.pixelformat(v=vs.110).aspx
Regards.
If you have trouble posting your code in StackOverflow, make sure each line is indented by at least 4 spaces (mark the code in Visual Studio, press Tab, then copy it) and separated by any other paragraphs by at least one empty line. Alternatively add a line of three backticks (`) at the beginning and and of the code.
When looking at your code, it seems to me that you are using a third-party control inside a Windows Form and try to bind the event of it to one of your Windows Forms event handlers. In that case you have to be aware that Windows Forms expect that all events are handled single-threaded (in the Event Dispatch Thread), so you should check (Debug.WriteLine) in your OnFrameReady method if InvokeRequired is true and if yes you have to take a few precautions:
Never access any of the Form's internal members (like pbCameraPreview) without wrapping the call into Invoke or BeginInvoke. Keep in mind that every Invoke call will effectively block until the single event-dispatch thread is available, so it will cost you a lot of performance to do invoke synchronously.
When accessing your own members (like width, height or nextpicture), make sure you use appropriate locking to avoid situations where one thread/callback changes the value in a situation where you don't expect it. In particular, since you have multiple cams but only a single width/height variable, if the resolutions differ, one camera callback could change the width while the other camera callback has just allocated the byte array but before passing the width to the Bitmap constructor, most likely resulting in a hang, crash, or memory access violation. Avoid this by passing width and heigth as variables into your method instead of using the global ones.
Your form's event handler (btStart_Click) contains a busy-wait loop. This has multiple problems:
As stated before, every Form event handler runs in the same thread (and every Invoke as well). So as long as your loop is busy-waiting, no other events can be handled and the UI will be completely frozen. If code uses Invoke, that code will also eventually have to wait and be blocked, too.
Busy wait loops without any sleep code in it will cause your processor to run at the full cpu speed and it will eat 100% of that core, causing battery drain on notebooks and high power consumption and probably loud fans on other machines. Just don't do that.
In your concrete example, just remove the loop and move everything after the loop into the stop button instead. To avoid the problems with Windows Forms and invoking, I'd suggest to put all the image handling logic into a separate class that does its own locking (each instance of the class handling one camera only), and just call into it to start the process. You might even prefer to do your app as a Console application first to avoid any event-dispatching issues and later convert it to a Windows Forms application once it is working as you desire.
in the following image generated by my code,
I want the tooltip to show value for each colour whilst my cursor is on it and when I click a particular place on the image i want a dash line to appear on the image.
this is my code:
RefBar.MouseMove += new MouseEventHandler(RefBar_MouseMove);
RefBar.MouseClick += new MouseEventHandler(RefBar_Click);
private void RefBar_MouseMove(object sender, MouseEventArgs e)
{
if (gotMapFirstTime == true)
{
Point LocalMousePosition = RefBar.PointToClient(System.Windows.Forms.Cursor.Position);
MousePointDisplay.SetToolTip(RefBar, WaferMap.getParamValueFromMousePointerXY(LocalMousePosition.X, LocalMousePosition.Y, 1, true).ToString());
}
}
private void RefBar_Click(object sender, EventArgs e)
{
byte[] bytes2;
Image image;
MouseEventArgs me = (MouseEventArgs)e;
Point coordinates = me.Location;
WaferMap.RefBarDashLines.Add(coordinates.Y);
int[] rfd = WaferMap.RefBarDashLines.ToArray();
if (rfd.Length > 2)
{
RefBar.Image.Dispose();
bytes2 = WaferMap.CreateMapReferenceBar(40, 580, 0, 0, 1);
WaferMap.RefBarDashLines = new List<int>();
WaferMap.UpperTrackBarLimit = 0.0;
WaferMap.LowerTrackBarLimit = 0.0;
pictureBox2.Image.Dispose();
bytes2 = WaferMap.CreateGridImage(120, 120, 9, 9, 5);
image = Image.FromFile(WaferMapImage);
pictureBox2.Image = image;
}
else if(rfd.Length == 2)
{
RefBar.Image.Dispose();
bytes2 = WaferMap.CreateMapReferenceBarByClick(40, 580, 0, 0, 1);
pictureBox2.Image.Dispose();
bytes2 = WaferMap.CreateGridImageFilteredByTrackBar(120, 120, 9, 9, 5);
image = Image.FromFile(WaferMapImage);
pictureBox2.Image = image;
}
else
{
RefBar.Image.Dispose();
bytes2 = WaferMap.CreateMapReferenceBarByClick(40, 580, 0, 0, 1);
}
image = Image.FromFile(ReferenceBarImage);
RefBar.Image = image;
MapLowerLimit.Text = coordinates.X.ToString() + " " + coordinates.Y.ToString();
}
in class wafermap we have this:
public static double getParamValueFromMousePointerXY(int x, int y, int boxSize, bool isRefBarOrHistogram)
{
double returnVal = 0.0;
Point UL;
Point BR;
int cellX;
int invertY;
int cellY;
if (isRefBarOrHistogram)
{
invertY = -1*(y - RefBarLength);
return get_YCell_to_ParamValue(invertY, RefBarLength);
}
else
{
foreach (die dd in dieList)
{
cellX = dd.col;
cellY = dd.row;
UL = new Point(boxSize * (cellX + 2), boxSize * (cellY + 4));
BR = new Point((boxSize * (cellX + 2)) + boxSize, (boxSize * (cellY + 4)) + boxSize);
if ((UL.X < x && x <= BR.X) && (UL.Y < y && y <= BR.Y))
{
return dd.ParamValue;
}
}
}
return returnVal;
}
public struct die
{
public int row;
public int col;
public int site;
public string param;
public double ParamValue;
}
the code for the Mouse click event works if the tool tip function is commented off, however when the tooltip function is is invoked for the mouse move function the code doesnt detect or detects after multiple clicks the mouse click event, how do i rectify this?
Your problem is probably that the getParamValueFromMousePointerXY takes so long to execute that your UI thread is blocked from execution any other task, such as handling your clicks.
You can offload the work to a background task and marshal setting the tooltip back to the UI thread:
Task.Run(() => {
string paramValue = WaferMap.getParamValueFromMousePointerXY(LocalMousePosition.X, LocalMousePosition.Y, 1, true).ToString();
MethodInvoker setTooltip = delegate() {
MousePointDisplay.SetToolTip(RefBar, paramValue);
};
RefBar.Invoke(setTooltip);
});
What you basically are doing here is to execute getParamValueFromMousePointerXY in a background task, while you continue to execute SetToolTip in the UI thread.
The only caveat here is that you possibly run a lot of background tasks here that will be in a race condition to set the tool tip. You can prevent that by using a cancellation token. You define a variable for a CancellationTokenSource:
CancellationTokenSource tooltipSource = null;
You can use this cancellation token source to prevent old updates to the tooltip:
tooltipSource?.Cancel();
tooltipSource = new CancellationTokenSource();
Task tooltipTask = new Task((tokenObj) => {
string paramValue = WaferMap.getParamValueFromMousePointerXY(LocalMousePosition.X, LocalMousePosition.Y, 1, true).ToString();
((CancellationToken)tokenObj).ThrowIfCancellationRequested();
MethodInvoker setTooltip = delegate() {
MousePointDisplay.SetToolTip(RefBar, paramValue);
};
RefBar.Invoke(setTooltip);
}, tooltipSource.Token);
tooltipTask.Start();
With this you should reduce the number of updates to your tooltip.
Of course you can pass the CancellationToken to getParamValueFromMousePointerXY and cancel the task even earlier.
I've been having issues for a few weeks now reading in data from a serial port and updating a canvas element based on the message being received. The canvas element to be updated is a small image, that is supposed to rotate to a certain angle based on the third section of the message being received. I'm not sure what's going wrong, it seems that a full message isn't always being received. I'm going to give as much detail about the port and data.
Structure - 8 data bits, 1 start bit, 1 stop bit, no parity. Message frequency is 15 Hz (the number of lines written every second). The default baud rate is 9,600.
There are five sections:
1. Section1 - two decimal places.
2. Section2 - two decimal places.
3. Angle - multiplied by 10 – i.e. 256.0 degrees is shown as 2560.
4. Section4 multiplied by -100 – i.e. 6.66 degrees is -666.
5. Section5 multiplied by 100 – i.e. 55.5 degrees is 555.
A message starts with a colon symbol (:) and ends with < CR>< LF> . A field that contains asterisks, '***', indicates that no value is defined for that field.
An example to illustrate:
Column
1 15 22 29 36 43
1.00 *** 0 0 0
: 1.00 20.20 2460 0 0
: 2.40 20.45 2460 10000 -10000
: 3.00 20.45 2355 1000 554
I have the latest message received being shown at the top of the window to ensure the user that data is being received, but I noticed the message is only show bits and pieces of what it should be, and thus messing up the rotation of the canvas element. So for instance, the message might be : 20 500 at first, and then get a complete message.
Here's a screenshot of the data being sent:
Here's relative code in my MainWindow.cs:
private Port port = new Port();
private double rovHeading;
Point rotate_Origin = new Point(0.5, 0.5);
public string LastCOMMessage
{
get { return (string)this.GetValue(LastCoMMessageProperty); }
set { this.SetValue(LastCoMMessageProperty, value); }
}
private void Connect_Port(object sender, RoutedEventArgs e)
{
if (port.Status == Port.PortStatus.Disconnected)
{
try
{
port.Connect("COM1", 9600, false, 250); //When set to false, test data is used on Connect. Last parameter is how often the UI is updated. Closer to 0, the faster it updates.
//Sets button State and Creates function call on data recieved
Connect_btn.Content = "Disconnect";
port.OnMessageReceived += new Port.MessageReceivedHandler(port_OnMessageReceived);
}
catch (Exception)
{
System.Windows.MessageBox.Show("Port is not available.");
}
}
else
{
try // just in case serial port is not open, could also be achieved using if(serial.IsOpen)
{
port.Disconnect();
Connect_btn.Content = "Connect";
}
catch
{
}
}
}
/// <summary>
/// Reads in the data streaming from the port, taking out the third value which is the ROV's heading and assigning it to rovHeading.
/// It then calls orientRovImage.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void port_OnMessageReceived(object sender, Port.MessageEventArgs e)
{
LastCOMMessage = e.Message;
if (rovImage != null && e.Message.EndsWith("\r\n")) //messages end in <CR><LF>. Was having issues where only half a message would be received and updated the heading to wrong number.
{
string[] messageComponents = LastCOMMessage.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (messageComponents.Length >= 3)
{
double.TryParse(messageComponents[3], out rovHeading);
if (rovHeading > 0)
{
rovHeading /= 10;
orientRovImage();
}
}
}
}
/// <summary>
/// Rotates the ROV icon based on the heading being streamed in.
/// </summary>
private void orientRovImage()
{
RotateTransform rotateRov = new RotateTransform(rovHeading + angle_Offset);
rovImage.RenderTransformOrigin = rotate_Origin;
rovImage.RenderTransform = rotateRov;
}
Here's my Port.cs:
class Port
{
private SerialPort serial = null;
private DispatcherTimer testTimer;
private string[] testData = new string[] { ": 3.00 20.45 2355 1000 554\r\n", ": 5.00 78.09 1725 3200 121\r\n", ": 9.20 10.12 1492 8820 197\r\n" }; //test data to be sent when liveData is set to false.
private int testDataIndex = -1;
private DateTime dateOflastMessageHandled;
public enum PortStatus
{
Connected,
Disconnected
}
public PortStatus Status { get; private set; }
public bool LiveData { get; private set; }
public int MillisecondsDelayBetweenMessages { get; private set; }
public class MessageEventArgs : EventArgs
{
public string Message { get; private set; }
public MessageEventArgs(string message)
{
Message = message;
}
}
public delegate void MessageReceivedHandler(object sender, MessageEventArgs e);
public event MessageReceivedHandler OnMessageReceived;
private void MessageReceived(string message)
{
if (OnMessageReceived == null)
{
return;
}
OnMessageReceived(this, new MessageEventArgs(message));
}
public Port()
{
Status = PortStatus.Disconnected;
}
public void Connect(string portName, int baudRate, bool liveData, int millisecondsDelayBetweenMessages)
{
LiveData = liveData;
MillisecondsDelayBetweenMessages = millisecondsDelayBetweenMessages;
Disconnect();
if (liveData)
{
serial = new SerialPort();
serial.PortName = portName;
serial.BaudRate = baudRate;
serial.Handshake = Handshake.None;
serial.Parity = Parity.None;
serial.DataBits = 8;
serial.StopBits = StopBits.One;
serial.ReadTimeout = 200;
serial.WriteTimeout = 50;
serial.Open();
serial.DataReceived += new SerialDataReceivedEventHandler(Receive);
}
else
{
testTimer = new DispatcherTimer();
testTimer.Interval = new TimeSpan(0, 0, 0, 0, 3);
testTimer.Tick += new EventHandler(testTimer_Tick);
testTimer.Start();
}
Status = PortStatus.Connected;
}
private void testTimer_Tick(object sender, EventArgs e)
{
if (dateOflastMessageHandled == null || DateTime.Now.Subtract(dateOflastMessageHandled).TotalMilliseconds >= MillisecondsDelayBetweenMessages)
{
dateOflastMessageHandled = DateTime.Now;
MessageReceived(testData[testDataIndex]);
testDataIndex++;
if (testDataIndex >= testData.Length)
{
testDataIndex = 0;
}
}
}
private void Receive(object sender, SerialDataReceivedEventArgs e)
{
if (dateOflastMessageHandled == null || DateTime.Now.Subtract(dateOflastMessageHandled).TotalMilliseconds >= MillisecondsDelayBetweenMessages)
{
dateOflastMessageHandled = DateTime.Now;
Application.Current.Dispatcher.BeginInvoke(new Action(
delegate()
{
//MessageReceived(serial.ReadLine());
MessageReceived(serial.ReadExisting());
}));
}
}
public void Disconnect()
{
if (testTimer != null)
{
testTimer.Stop();
}
testDataIndex = 0;
Status = PortStatus.Disconnected;
if (serial != null)
{
serial.Close();
}
}
}
And finally in my MainWindow.xaml, this just shows the last message received:
<Button Content="Connect" Click="Connect_Port" Name="Connect_btn" />
<TextBlock Text="{Binding LastCOMMessage, ElementName=this}" />
Any hints or help with this would be much appreciated, as this is the first time I've worked w/ ports. Thanks!