wpf combobox function operating on a delay - c#

private void messageTypeMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string option = messageTypeMenu.Text;
if (option.Equals("Errors"))
{
LogParser.information = false;
LogParser.messages = false;
LogParser.errors = true;
}
if (option.Equals("Information"))
{
LogParser.information = true;
LogParser.messages = false;
LogParser.errors = false;
}
if (option.Equals("Messages"))
{
LogParser.information = false;
LogParser.messages = true;
LogParser.errors = false;
}
}
is a function that is supposed to update a static class depending on the selected value. however, its operating on a delay and im not sure why.
for some context, information, messages, and errors are 3 different message type for logs stored in a file that i am parsing. when someone selected information and hits view or save, they should only get the logs of that type. however, it appears to be operating on a delay.
for example, if i load in a file of logs and select information, it will show me the whoel file. then if i switch to messages, i will see only the information. then if i switch back to information, it will only show messages and so on. it seems like the combobox is looking at the value that was already present when they go to change this, but im not sure why.

In order to stay synchronized with the window, you should consider using the observable pattern and implement INotifyPropertyChanged combined with bindings. You will need to redesign your application since you can't implement interfaces on static classes but that may be worth it.
Another way would be using Dependency Properties.

Related

NAudio hanging and crashing when default device is changed

I've been stuck on this all day, so I'm going to post everything I've been able to find today that might be useful to helping me, it will be a long post. I'm having 2 issues that I believe are related to the same problem. First, let me explain what I am doing. I have 3 Winforms combo boxes that are bound to lists of all of the devices found by MMDeviceEnumerator. Two output device boxes, one input device. I am using the MMDeviceEnumerator to register a callback for whenever the devices are changed, removed, or a default device is set. The callback fires an event that then invokes a delegate to the form thread to re-enumerate the devices into combo boxes. It looks like this:
public void OnDefaultDeviceChanged(DataFlow dataFlow, Role deviceRole, string defaultDeviceId)
{
Devices.OnDevicesUpdated();
}
//The handler called by this event:
private void UpdateDeviceSelectors(object? sender = null, EventArgs? e = null)
{
Invoke(delegate ()
{
int primaryIndex = Devices.PrimaryOutput + 1, secondaryIndex = Devices.SecondaryOutput + 2, microphoneIndex = Devices.Microphone + 1;
Devices.Refresh();
try
{
SecondaryOutputComboBox.SelectedIndex = secondaryIndex;
PrimaryOutputComboBox.SelectedIndex = primaryIndex;
MicrophoneSelectComboBox.SelectedIndex = microphoneIndex;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
});
}
Now for the two issues I have. The first one involves a semi-random crash that leads back to NAudio.Wasapi.dll, where a System.ExecutionEngineException is thrown. It is kind of easy to reproduce. All I do is change the values of the combo boxes, switch the default devices around, and it will randomly crash.
The second issue occurs when another part of my code is involved. I have a microphone injector, which redirects a WaveInEvent that records a selected input device to a WaveOutEvent, like a loopback. Here is the relevant code for this:
public void Start()
{
if (Soundboard.Devices.SecondaryOutput == -2) return;
micStream = new WaveInEvent();
micStream.BufferMilliseconds = 50;
micStream.WaveFormat = new WaveFormat(44100, WaveIn.GetCapabilities(Soundboard.Devices.Microphone).Channels);
micStream.DeviceNumber = Soundboard.Devices.Microphone;
WaveInProvider waveIn = new(micStream);
var volumeSampleProvider = new VolumeSampleProvider(waveIn.ToSampleProvider());
volumeSampleProvider.Volume = 1 + Settings.Default.MicrophoneGain;
virtualCable = new WaveOutEvent();
virtualCable.DesiredLatency = 150;
virtualCable.DeviceNumber = Soundboard.Devices.SecondaryOutput;
virtualCable.Init(volumeSampleProvider);
micStream.StartRecording();
virtualCable.Play();
}
public void Stop()
{
try
{
if (micStream != null && virtualCable != null)
{
micStream.Dispose();
micStream = null;
virtualCable.Dispose();
virtualCable = null;
}
}
catch
{
micStream = null;
virtualCable = null;
}
}
In the delegate mentioned earlier, I am calling the Stop method of the Mic Injector and then the Start method to refresh the WaveIn and WaveOut devices to use the current device numbers so users do not see a device selected when a different device is being used. When this happens, the program, rather than crashing instantly and inconsistently, always hangs and has to be killed from the task manager. I am certain these 2 problems are related to the same root cause, but I have no idea what that root cause may be. Switching to Wasapi, DirectSound, or ASIO won't work because they lack certain functionalities I need, so I would really like to get this working still using Wave streams.
I've tried to find different ways to detect the device changes, assuming it is an issue deep inside NAudio, but I just can't find anything else. For the second problem specifically, I have moved the calls to the Mic Injector around thinking it may be a threading issue or something, but it didn't work or change the behavior.

Updating object at program level within class

I have a program that runs in the system tray that communicates with our server and "syncs" data based on a users preferenced jobs. The idea is similar to Dropbox, but for our surveying software called 12d Synergy. The idea is that users can sync data without needing to navigate through the softwares Client.
I want to add the functionality so that when the program is syncing, the icon in the system tray changes to indicate that its still syncing, but i can't figure out how to get access to the original object within the portion of the program where the event is located.
My program stucture is as follows (in c#):
Program.cs
using (ProcessingIcon pi = new ProcessingIcon())
{
pi.SetIcon(Resources._12d);
pi.Display();
Application.Run();
}
ProcessingIcon.cs
NotifyIcon ni;
public void SetIcon(Icon path)
{
ni.Icon = path;
}
public void Display()
{
ni.Text = "Sunrise Surveying 12d Synergy Sync Tool";
ni.Visible = true;
ni.ContextMenuStrip = new ContextMenus().Create();
}
ContextMenus.cs
public ContextMenuStrip Create()
{
// Sync Now
item = new ToolStripMenuItem();
item.Text = "Sync Now";
item.Click += new EventHandler(syncNow_Click);
item.Image = Resources.Sync.ToBitmap();
cms.Items.Add(item);
}
void syncNow_Click(object sender, EventArgs e)
{
string[] jobs = Sync.GetSharedFiles();
string[] files = Sync.GetDataToSync(jobs);
Sync.SyncData(files);
}
What i want to happen, is in the syncNow_click, call the ProcessingIcon.SetIcon() to change the icon, but i can't figure out how to get access to an object that exists 3 layers up in the program.
I should note that i am not a programmer, i'm a surveyor with an interest in programming. I am completely self taught, so i know there is probably something relatively simple i'm missing. This is also my first post in StackOverflow, so i'm not 100% how to use this site to the full capability, so if this has been answered somewhere i apologise.
Any help or advice would be greatly appreciated.
So i worked out a way to answer my own question. Just putting it here in case anyone has the same issue. It turned out to be incredibly simple, and purely just by me not fully understanding the classes/objects structure.
I added a constructor for my ContextMenus object which passed in the the NotifyIcon that was calling it. This was passed to a NotifyIcon variable in that class which i could then access.
class ContextMenus
{
public NotifyIcon ni;
public ContextMenus(NotifyIcon ni)
{
this.ni = ni;
}
}

How to update controls on UI every time I change it's property

I have to work on a C# GUI application that displays information regarding an alternator that sends back messages every 20ms. So whenever I receive a certain message for ex regarding the temperature, I have to update a Textbox's text property on the UI with the correct information. The problem is I have tried using multithreading with delegates, did not work, and using invoke's did not work. Using a thread pool didn't work. I just want a chunk of code that forces the UI to update a textbox's text property and never thought this could be that hard.
At the moment I have this:
delegate void UpdateTextBoxTextDelegate(double newValue);
private void UpdateTextBoxText(double newValue)
{
if(tbxTemp.InvokeRequired)
{
UpdateTextBoxTextDelegate del = new UpdateTextBoxTextDelegate(UpdateTextBoxText);
tbxTemp.Invoke(del, new object[] {newValue});
}
else
{
tbxTemp.Text = Convert.ToString(newValue + "°C");
}
}
private double alternatorTemperature;
public double _alternatorTemperature
{
get { return alternatorTemperature; }
set
{
alternatorTemperature = value;
UpdateTextBoxText(alternatorTemperature);
}
}
And then I have this function which is basically executing every time a CAN message is received with a certain PGN. This function call hierarchy basically leads back up to the main thread of the application, not the UI thread if that helps.
//Alternator Temperature
case 0xFEA7:
temp = (UInt16)(canMessage.Data4);
alternatorTemperature = Convert.ToDouble(temp);
_alternatorTemperature = alternatorTemperature;
break;
The following code changes the textbox text almost 20 seconds later after there have been thousands of messages sent which is not acceptable. I have also tried the invoke way but only has the same delayed result
tbxRPM.Invoke(new Action(() => tbxRPM.Text = alternatorRPM.ToString() + "/rpm"));
If anyone has an easy way of changing a textbox.text property and then have it immediately show up on the UI, it would be great.
I would recomend you to create a class representing your "Alternator" with a property for "Temperature" and one method for "GetTemperature" that would be the same has your "function which is basically executing every time a CAN message is received with a certain PGN". Once it is done, bind your textbox to the instatiated class and property using:
tbxTemp.DataBindings.Add("Text",
objAlternator,
"Temperature",
false,
DataSourceUpdateMode.OnPropertyChanged);
In the end, the method I used to force UI controls to change even though the main thread was busy all the time was the following. I used this method on text boxes and it turned out to be very responsive
private string _someVariable = "Initial text";
public string SomeVariable
{
get { return _someVariable; }
set { _someVariable= value;
if(PropertyChanged != null)
{
PropertyChanged(this,new System.ComponentModel.PropertyChangedEventArgs(SomeVariable));
}
}
}
Within the main form's constructor I added the following:
lblTextBox.DataBindings.Add("Text", this, "SomeVariable");
And then to trigger changes on the UI controls when I needed I just changed the text property of the textbox you bound the data to and it just worked without the need for multi-threading, delegates, or any complex stuff.
eg.
Where you need a control to change:
lblTextBox.Text = "Some text";

Enable tooltips ActiveGantt 3.0.9.0

I'm putting together a form using the ActiveGantt 3.0.9.0 CSN control from http://www.bootes.co/EN/Default.aspx.
private void activeGanttCSNCtl1_OnMouseHoverToolTipDraw(object sender, ToolTipEventArgs e)
{
switch (e.EventTarget)
{
case E_EVENTTARGET.EVT_TASK:
TaskToolTipDraw(e);
e.CustomDraw = true;
return;
case E_EVENTTARGET.EVT_SELECTEDTASK:
TaskToolTipDraw(e);
e.CustomDraw = true;
return;
case E_EVENTTARGET.EVT_PERCENTAGE:
TaskToolTipDraw(e);
e.CustomDraw = true;
return;
case E_EVENTTARGET.EVT_SELECTEDPERCENTAGE:
TaskToolTipDraw(e);
e.CustomDraw = true;
return;
}
However e.EventTarget refuses to trigger on tasks. I can check the value and move around the control, and I can get EVT_Clientarea, EVT_Row, EVT_Column, EVT_None, EVT_Splitter, EVT_Timeline, EVT_TimelineScrollBar to trigger. However on tasks that I have added via this function it simply states that I am in EVT_Clientarea:
activeGanttCSNCtl1.Tasks.Add(AddingTask.Description, AddingTask.RowKey, AddingTask.StartTime, AddingTask.EndTime, AddingTask.ID, AddingTask.Style, AddingTask.Layer);
The tasks all show up fine, but simply do not trigger the Task event when I mouse over. If anyone could give me any guidance as to what I'm missing I would really appreciate it.
Thanks,
Mike
Thank you for asking this question. Try Overriding ToolTipOnMouseHover:
private void ActiveGanttCSNCtl1_ToolTipOnMouseHover(object sender, AGCSN.ToolTipEventArgs e)
{
switch (e.EventTarget)
{
case E_EVENTTARGET.EVT_TASK:
case E_EVENTTARGET.EVT_SELECTEDTASK:
ActiveGanttCSNCtl1.ToolTip.Visible = true;
return;
}
ActiveGanttCSNCtl1.ToolTip.Visible = false;
}
OnMouseHoverToolTipDraw fires when the Visible property of the ToolTip object is set to true, ToolTipOnMouseHover and OnMouseHoverToolTipDraw work in conjunction. In ToolTipOnMouseHover you generally perform calculations and do the actual drawing in OnMouseHoverToolTipDraw.
In the above example OnMouseHoverToolTipDraw would only be raised for tasks and ignored for all other objects. ActiveGantt handles Tasks and Tasks that are selected as two different objects.
Best Regards,
Julio Luzardo
Boötes Systems SAS

Have MessageBox appear once (code inside timer)

UPDATE: I've managed to fix my problem. Using the code below, I moved my MessageBox AFTER my XML saving and changed the Timer from 100ms to 400ms. I now have 1 box appear, thank god. Although If anyone has a short cut to updating a single value (ActReminded) in the List array(ActListTask), that'd be great to know.
I'm having a little issue with displaying the MessageBox. Show inside a timer without it spamming me. Here's the part of the code I've been working with:
public class ActiveTasks
{
//Properties here
}
public List<ActiveTasks> ActTaskList = new List<ActiveTasks>();
for (int i = 0; i < ListActive.Items.Count; i++)
{
if (DTime.Date == newDateTime.Date)
{
if (newDateTimeLeft.CompareTo(TimeSpan.Zero) <= 0 && ActTaskList[i].ActReminded != "true")
{
MessageBox.Show("!!!!");
ActTaskList.Add(new ActiveTasks()
{
ActTitle = ActTaskList[i].ActTitle,
ActDesc = ActTaskList[i].ActDesc,
ActDate = ActTaskList[i].ActDate,
ActTime = ActTaskList[i].ActTime,
ActStatus = ActTaskList[i].ActStatus,
ActReminded = "true",
ActRepeat = ActTaskList[i].ActRepeat
});
ListActive.Items.RemoveAt(i);
ActTaskList.RemoveAt(i);
XDocument XmlActTasks = GenerateActiveListToXML(ActTaskList);
}
}
}
I actually decided I may want to hold onto the reminder status, whether it has been shown or not as I wouldn't want a repeated reminder every time the program is opened. Since I don't know of a way to update an individual part of ActTaskList I just re-added it, and then deleted the original. This code manages to recognise that if it happens, it will change the reminder status from false, to true; after I've Ok'ed all the spam. So it will stop the MessageBox once I've managed to closed all the Messageboxes. However, it doesn't stop the spam. Would it be anything to do with the fact I've set the timer to 100ms? Or could their be an alternative way to make the messagebox appear without it being inside the timer?
The odds of the current time lining up exactly to the second what is happening in your loop is small. Why not treat newDateTime as a cut off point and just set a flag?
//Declare this outside of the loop
bool hasDisplayed = false;
//Inside the timer event handler
if (!hasDisplayed && DateTime.Now >= newDateTime)
{
hasDisplayed = true;
MessageBox.Show("!!!!!!!!!!!!!");
}
Can you do something like this?
Action message = () => MessageBox.Show("!!!!!!!!!!!!!"));
object lockOb = new object();
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
lock(lockOb)
if(null != message)
{
message();
message = null;
}
}
You say you've already tried a boolean indicating the message has already been shown, I'm assuming because the code probably looked like it did below.
void TimerLoop()
{
bool msgAlreadyShown;
if(!msgAlreadyShown)
{
MessageBox.Show("!!!!!!!");
}
// Other work in your timer function
}
The problem with that code is that the bool will be set to false each time the function is called by the timer. You haven't posted much code, but you've at least stated what you're trying to accomplish, a timer that checks if a reminder should be presented to the user.
I'm about to make some wild guesses about how you've put together your software, there's a good chance it's way off, but I hope it might point you in the right direction. You could have some sort of reminder class like this:
public class Reminder
{
string Message { get; set;}
DateTime Alarm { get; set; }
bool IsDismissed { get; set; }
}
I'm assuming you might want to have multiple reminders that can be checked for in the timer loop, so your timer loop could look something like:
private List<Reminder> _activeReminders; // A list of reminders
void TimerLoop(object s, ElapsedEventArgs e)
{
lock(_activeReminders)
{
var now = DateTime.Now;
foreach(var reminder in _activeReminders)
{
// only run this code if the time has passed and it hasn't already
// been shown
if(now.CompareTo(reminder.Alarm) >= 0 && !reminder.IsDismissed)
{
MessageBox.Show(reminder.Message);
reminder.IsDismissed = true;
}
}
}
}
This is a pretty naive implementation, since you probably don't want to hold onto the reminders for forever and the reminders are never removed from the _activeReminders list, but you essentially just need to add some sort of state to determine if the reminder has already been shown.
Of course, this isn't a complete example either, since I never new up the _activeReminders field or add anything to it, but I think this might help get the idea of what you need to do across. Also, you might not care about multiple reminders, and your timer code could look nothing like this. The main idea was to show you how you can keep track of the state of a reminder, and tailor it to your own code. The above was just an example.
Also, I haven't actually tested it, so treat it more like pseudocode than anything else. However, the logic is sound, and should it should only cause the message box to appear once.

Categories