Refresh C# listbox when change in Loaded Text file - c#

Ok I know similar questions have been asked, But I am trying to have a Listbox monitor a text file that is changed by another program. I have it setup to select the top line in the file, and execute a command based off that line, then it deletes top line. I just need the listbox to refresh after deletion and read new top line and continue to read until null. I am also having it monitor if the serialPort is OPEN or not. ( I want it to only loop if the serial Port is closed). I am fairly new to C# so any help is appreciated. My current code:
public partial class Form1 : Form
{
List<string> myList = new List<string>();
public Form1()
{
InitializeComponent();
myList = System.IO.File.ReadLines("textfile").ToList();
this.GPSCOM.DataSource = myList;
GPSCOM.SelectionMode = SelectionMode.One;
myList.FirstOrDefault();
GPSCOM.SetSelected(0, true);
if (serialPort.IsOpen)
{
return;
}
else
{
for(;;)
{
switch ((string)GPSCOM.SelectedItem)
{
case "set1":
var lines = System.IO.File.ReadAllLines("textfile");
System.IO.File.WriteAllLines("textfile", lines.Skip(1).ToArray());
return;
case "set2":
var lines1 = System.IO.File.ReadAllLines("textfile");
System.IO.File.WriteAllLines("textfile", lines1.Skip(1).ToArray());
return;
case "set3":
var lines2 = System.IO.File.ReadAllLines("textfile");
System.IO.File.WriteAllLines("textfile", lines2.Skip(1).ToArray());
return;
}
}
}
}

A FileSystemWatcher would be your best bet.
I would like to ask however if it would be inconvenient to make the other program output to its standard output stream and use ProcessStartInfo in System.Diagnosics to redirect the output. If would be a faster and more direct solution as the data could be moved directly between the two programs instead of using the less indirect file storage method. Cut out the middle man as it were.

You would be better off using a FileSystemWatcher and catching the change notification than continuously polling the contents of the file.

Related

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;
}
}

An ItemsControl is inconsistent with its items source - Problem when using Dispatcher.Invoke()

I'm writing a WPF application (MVVM pattern using MVVM Light Toolkit) to read and display a bunch of internal log files my company uses. The goal is to read from multiple files, extract content from each line, put them in a class object and add the said object to an ObservableCollection. I've set the ItemsSource of a DataGrid on my GUI to this list so it displays the data in neat rows and columns. I have a ProgressBar control in a second window, which during the file read and display process will update the progress.
Setup
Note that all these methods are stripped down to the essentials removing all the irrelevant code bits.
Load Button
When the user selects the directory which contains log files and clicks this button, the process begins. I open up the window that contains the ProgressBar at this point. I use a BackgroundWorker for this process.
public void LoadButtonClicked()
{
_dialogService = new DialogService();
BackgroundWorker worker = new BackgroundWorker
{
WorkerReportsProgress = true
};
worker.DoWork += ProcessFiles;
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerAsync();
}
ProcessFiles() Method
This reads all files in the directory selected, and processes them one by one. Here, when launching the progress bar window, I'm using Dispatcher.Invoke().
private void ProcessFiles(object sender, DoWorkEventArgs e)
{
LogLineList = new ObservableCollection<LogLine>();
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
_dialogService.ShowProgressBarDialog();
});
var fileCount = 0;
foreach (string file in FileList)
{
fileCount++;
int currProgress = Convert.ToInt32(fileCount / (double)FileList.Length * 100);
ProcessOneFile(file);
(sender as BackgroundWorker).ReportProgress(currProgress);
}
}
ProcessOneFile() Method
This, as the name suggests, reads one file, go through line-by-line, converts the content to my class objects and adds them to the list.
public void ProcessOneFile(string fileName)
{
if (FileIO.OpenAndReadAllLinesInFile(fileName, out List<string> strLineList))
{
foreach (string line in strLineList)
{
if (CreateLogLine(line, out LogLine logLine))
{
if (logLine.IsRobotLog)
{
LogLineList.Add(logLine);
}
}
}
}
}
So this works just fine, and displays my logs as I want them.
Problem
However, after displaying them, if I scroll my DataGrid, the GUI hangs and gives me the following exception.
System.InvalidOperationException: 'An ItemsControl is inconsistent
with its items source. See the inner exception for more
information.'
After reading about this on SO and with the help of Google I have figured out that this is because my LogLineList being inconsistent with the ItemsSource which results in a conflict.
Current Solution
I found out that if I put the line of code in ProcessOneFile where I add a class object to my list inside a second Dispatcher.Invoke() it solves my problem. Like so:
if (logLine.IsRobotLog)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
LogLineList.Add(logLine);
});
}
Now this again works fine, but the problem is this terribly slows down the processing time. Whereas previously a log file with 10,000 lines took about 1s, now it's taking maybe 5-10 times as longer.
Am I doing something wrong, or is this to be expected? Is there a better way to handle this?
Well observable collection is not thread safe. So it works the second way because all work is being done on the UI thread via dispatcher.
You can use asynchronous operations to make this type of flow easier. By awaiting for the results and updating the collection\progress on the result, you will keep your UI responsive and code clean.
If you cant or don't want to use asynchronous operations, batch the updates to the collection and do the update on the UI thread.
Edit
Something like this as an example
private async void Button_Click(object sender, RoutedEventArgs e)
{
//dir contents
var files = new string[4] { "file1", "file2", "file3", "file4" };
//progress bar for each file
Pg.Value = 0;
Pg.Maximum = files.Length;
foreach(var file in files)
{
await ProcessOneFile(file, entries =>
{
foreach(var entry in entries)
{
LogEntries.Add(entry);
}
});
Pg.Value++;
}
}
public async Task ProcessOneFile(string fileName, Action<List<string>> onEntryBatch)
{
//Get the lines
var lines = await Task.Run(() => GetRandom());
//the max amount of lines you want to update at once
var batchBuffer = new List<string>(100);
//Process lines
foreach (string line in lines)
{
//Create the line
if (CreateLogLine(line, out object logLine))
{
//do your check
if (logLine != null)
{
//add
batchBuffer.Add($"{fileName} -{logLine.ToString()}");
//check if we need to flush
if (batchBuffer.Count != batchBuffer.Capacity)
continue;
//update\flush
onEntryBatch(batchBuffer);
//clear
batchBuffer.Clear();
}
}
}
//One last flush
if(batchBuffer.Count > 0)
onEntryBatch(batchBuffer);
}
public object SyncLock = new object();
In your constructor:
BindingOperations.EnableCollectionSynchronization(LogLineList, SyncLock);
Then in your function:
if (logLine.IsRobotLog)
{
lock(SyncLock)
{
LogLineList.Add(logLine);
}
}
This will keep the collection synchronized in which ever thread you update it from.

c# add clickable images to gui using code and not the designer

i have a folder full of images etc and i want to add them to my gui inside a tab once the program is ran.
steps:
read contents inside folder
add images to tabcontrol and assign a number to them according to in what order they were added (1) for the first picture added (2) for second etc
the images need to be added in rows of 3 with differend x values for every 3 pictures each and 1 y value change for each row of 3
[] [] []
[] [] []
[] []
doesnt need to be divideable in 3 ^ this is fine
my issue is that i didnt even find a way to add images to a gui without using the designer and im fairly new to c# so this is a huge challange for me :(
in AHK i would just gui,add,picture but it doesnt work that way in c#
help or some form of tips/guidance is very appreciated
the reason the pictures need to "have a value" on them is that once they are clicked i know which image was clicked because they should all have their click event be sent to the same function that checks what number that was and changes the value of variables accordingly
thanks for the help SO C# is overwhelming
public static void ProcessDirectory(string targetDirectory)
{
Console.WriteLine("Processed folder '{0}'.", targetDirectory);
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(targetDirectory);
foreach (string fileName in fileEntries)
ProcessFile(fileName);
// Recurse into subdirectories of this directory.
string[] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
foreach (string subdirectory in subdirectoryEntries)
ProcessDirectory(subdirectory);
}
// Insert logic for processing found files here.
public static void ProcessFile(string path)
{
Console.WriteLine("Processed file '{0}'.", path);
if (!ImageExtensions.Contains(Path.GetExtension(path).ToUpperInvariant()))
{
Console.WriteLine("not image");
return;
}
FlowLayoutPanel flowLayoutPanel1 = new FlowLayoutPanel();
Image i = new Bitmap(path);
PictureBox p = new PictureBox();
p.Image = i;
p.Tag = "im1";
p.Click += OnImageClick; // this will let you have the same event for all of the pictures
flowLayoutPanel1.Controls.Add(p);
}
// this is what handles the clicks
private static void OnImageClick(object sender, EventArgs e)
{
MessageBox.Show("hey");
// I will leave this for you to implement... the 'sender' is the picturebox that was clicked.
// you can get it back to a PictureBox by casting, like (PictureBox)sender
// throw new NotImplementedException();
}
To add images, text, or anything else to a GUI programmatically in Winforms (which is sounds like you're using) you'll create an instance of something derived from Control and add it to the form's Controls ControlCollection. You can use a FLowLayoutPanel to give it a grid like view.
// get the list of paths to the files from your directory into an array
var fileList = Directory.GetFiles(#"C:\Images","*.jpg");
// create a flowlayout panel
FlowLayoutPanel f = new FlowLayoutPanel();
foreach (string path in fileList)
{
Image i = new Bitmap(path);
PictureBox p = new PictureBox();
p.Image = i;
p.Click += OnImageClick; // this will let you have the same event for all of the pictures
f.Controls.Add(p);
}
// add the panel to the form
this.Controls.Add(p);
// this is what handles the clicks
private void OnImageClick(object sender, EventArgs e)
{
// I will leave this for you to implement... the 'sender' is the picturebox that was clicked.
// you can get it back to a PictureBox by casting, like (PictureBox)sender
throw new NotImplementedException();
}
This implementation is quick and dirty: that is, it doesn't handle if the directory isn't found, if there aren't any files in there, etc). It should give you a good idea of where to go. I would recommend reading a lot more, getting a book about C# and walking through some examples/tutorials can be immensely helpful. You're right, C# can be overwhelming... that's why Google is your best friend! You don't need to immediately learn how to do everything; I find I learn well by choosing a small-in-scope project that I want to do and learning as I find things that I need.
This MSDN article talks about how to get all the filepaths of files in specific directory ("folder")
Their example:
// For Directory.GetFiles and Directory.GetDirectories
// For File.Exists, Directory.Exists
using System;
using System.IO;
using System.Collections;
public class RecursiveFileProcessor
{
public static void Main(string[] args)
{
foreach(string path in args)
{
if(File.Exists(path))
{
// This path is a file
ProcessFile(path);
}
else if(Directory.Exists(path))
{
// This path is a directory
ProcessDirectory(path);
}
else
{
Console.WriteLine("{0} is not a valid file or directory.", path);
}
}
}
// Process all files in the directory passed in, recurse on any directories
// that are found, and process the files they contain.
public static void ProcessDirectory(string targetDirectory)
{
// Process the list of files found in the directory.
string [] fileEntries = Directory.GetFiles(targetDirectory);
foreach(string fileName in fileEntries)
ProcessFile(fileName);
// Recurse into subdirectories of this directory.
string [] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
foreach(string subdirectory in subdirectoryEntries)
ProcessDirectory(subdirectory);
}
// Insert logic for processing found files here.
public static void ProcessFile(string path)
{
Console.WriteLine("Processed file '{0}'.", path);
}
}
This article (also MSDN) talks about adding control dynamically at run time, here is their example code:
// C#
private void button1_Click(object sender, System.EventArgs e)
{
TextBox myText = new TextBox();
myText.Location = new Point(25,25);
this.Controls.Add (myText);
}
Finally this article (again MSDN) article talks about how to set an image from a file, here is their example code:
// C#
// You should replace the bolded image
// in the sample below with an icon of your own choosing.
// Note the escape character used (#) when specifying the path.
pictureBox1.Image = Image.FromFile
(System.Environment.GetFolderPath
(System.Environment.SpecialFolder.MyPictures)
+ #"\Image.gif");
EDIT: In the comments it was asked how to wire up a control click event, since we are adding these controls programmatically we need to add the click event programmatically. For demo purposes assume there is a control with id Button1, to fire a function when it is clicked you would use code that goes something like this:
Button1.Click += new System.EventHandler(this.myEventHandler);
This will fire the function myEventHandler when Button1 is clicked
So thats a lot of code!
While it may not seem like it, if you look at each piece and consider how they all fit together, this should help you on the right path to (big hint), dynamically get a list of images, add an image control to your page, then set that image control to display a specific image, and finally wire up a click event on an image control
You can use Image.Tag property to add extra information to the image.

Problems when starting application with files from Explorer

I just added the feature where I can make my program the default-program for multiple music filetypes by following this question on Stackoverflow
It seemed to work fine, since all of the mp3 files etc has my icon on them in Explorer.
But when I tried starting one (I have handled the arguments just fine) the process itself starts (it shows up in Task Manager) but nothing happens. I even tried adding a Messagebox in the beginning of the "Window.Initialized"-event, but no messagebox came up.
What might be the cause of this problem? I have literally no idea what's wrong.
If you need code or anything, just ask for it since I don't know what to include in this question.
Thank you.
Here's the code for Window_Initialied
private void Window_Initialized(object sender, EventArgs e)
{
MessageBox.Show("asd");
HandleInstances(); // Checks if multiple instances of the program is running. Exits it if there's more than one instance (tried commenting this out, didn't work)
if (Properties.Settings.Default.HasRegisteredFiletypes == false) // Checks if theres a need to add the filetypes
AddExec();
StartWithMusic(Environment.GetCommandLineArgs().ToList()); // Here I call for the arguments. Checks if there are valid files in the arguments (.mp3, .flac etc)
SettingsLoadBGs.IsChecked = Properties.Settings.Default.LoadBGs;
// set accentcolor box
List<AccentColor> ac = new List<AccentColor>();
string userAccent = Mplayer.Properties.Settings.Default.Accent.ToLower();
foreach (Accent c in ThemeManager.DefaultAccents)
{
AccentColor acEnt = new AccentColor();
acEnt.Name = c.Name;
ac.Add(acEnt);
if (c.Name.ToLower() == userAccent)
ThemeManager.ChangeTheme(this, c, Theme.Dark);
}
ThemeManager.IsThemeChanged += new EventHandler<OnThemeChangedEventArgs>(ThemeChanged);
accentChooserBox.ItemsSource = ac;
}

Reload UI language strings after changing settings

I have a problem with changing my UI strings after the user has changed the language in the option window. To change the UI strings of the main form, I have to restart the program every time, so that changes take effect, but that's annoying. So I tried it with a delegate to call the function, which loads the strings for the main window in the option window after saving the new settings. The function is called in the option window, but it doesn't change the strings of the main window.
Code in the main window
public delegate void CallLoadUIStrings(SupportedLanguages lang);
public CallLoadUIStrings callLoadUIStrings;
public Renamer()
{
callLoadUIStrings = new CallLoadUIStrings(LoadUIStrings);
}
public void LoadUIStrings(SupportedLanguages lang)
{
try
{
switch (lang)
{
#region "DE/JA/FR/ES/NL"
case SupportedLanguages.De:
case SupportedLanguages.Ja:
case SupportedLanguages.Fr:
case SupportedLanguages.Es:
case SupportedLanguages.Nl:
// reads the language file where the ui strings are stored
langHelper.Read(RenamerLangOpener.RenamerMainWindow);
this.mnuFile.Text = langHelper.Files;
this.mnuClose.Text = langHelper.Close;
this.mnuEdit.Text = langHelper.Edit;
this.mnuUndo.Text = langHelper.Undo;
this.mnuCut.Text = langHelper.Cut;
this.mnuCopy.Text = langHelper.Copy;
this.mnuPaste.Text = langHelper.Paste;
this.mnuDelete.Text = langHelper.Delete;
this.mnuSelectAll.Text = langHelper.SelectAll;
#endregion
}
}
catch (Exception ex) { //exception handling }
}
private void mnuOpt_Click(object sender, EventArgs e)
{
Preferences opt = new Preferences(this);
opt.ShowDialog();
}
Code in the option window
internal Renamer instance = null;
public Preferences(Renamer form)
{
instance = form;
}
public void UpdateUI()
{
langHelper.ReadSettingsValues();
instance.BeginInvoke(instance.callLoadUIStrings,new object[] { langHelper.GetLang});
}
Since I've never worked with delegates I don't have a clue where the mistake is.
I've googled so much to find a solution for a similar problem, but I haven't found something that matched my problem.
I assume this is winforms and not WPF question, and that you have one main form that is open from app's Main function. My solution to changing a language is to open this one form in a loop, and continue the loop as long as the form has a property set to some language identifier. If this property is set then I change the language to that value, and go for another loop iteration. I copy all other properties that need be copied form one form to another, with main being the form's position.
If the form is closed without the language ID being set then we break the loop and exit application as usual.

Categories