I have a function that gets the running apps every 10 seconds, place them on a listbox and sends them to the other window if you click the send button. Now the problem is whenever I try to open then immediately close an app, it would send an error pointing to my list.
I'm not sure what to do here.
Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index.
Here's my code if in case it brings any help:
private List<int> listedProcesses = new List<int>();
private void SendData()
{
String processID = "";
String processName = "";
String processFileName = "";
String processPath = "";
string hostName = System.Net.Dns.GetHostName();
listBox1.BeginUpdate();
try
{
for (int i = 0; i < listBox1.Items.Count; i++)
{
piis = GetAllProcessInfos();
try
{
if (!listedProcesses.Contains(piis[i].Id)) //place this on a list to avoid redundancy
{
listedProcesses.Add(piis[i].Id);
processID = piis[i].Id.ToString();
processName = piis[i].Name.ToString();
processFileName = piis[i].FileName.ToString();
processPath = piis[i].Path.ToString();
output.Text += "\n\nSENT DATA : \n\t" + processID + "\n\t" + processName + "\n\t" + processFileName + "\n\t" + processPath + "\n";
}
}
catch (Exception ex)
{
wait.Abort();
output.Text += "Error..... " + ex.StackTrace;
}
NetworkStream ns = tcpclnt.GetStream();
String data = "";
data = "--++" + " " + processID + " " + processPath + " " + processFileName + " " + hostName;
if (ns.CanWrite)
{
byte[] bf = new ASCIIEncoding().GetBytes(data);
ns.Write(bf, 0, bf.Length);
ns.Flush();
}
}
}
finally
{
listBox1.EndUpdate();
}
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
ProcessInfoItem pii = piis.FirstOrDefault(x => x.Id == (int)(sender as ListBox).SelectedValue); //setting value for list box
if (pii != null)
{
string hostName = System.Net.Dns.GetHostName();
textBox4.Text = listBox1.SelectedValue.ToString();
textBox5.Text = (pii.FileName);
textBox6.Text = (pii.Path);
textBox7.Text = hostName;
}
}
private List<ProcessInfoItem> piis = new List<ProcessInfoItem>();
private void Form1_Load(object sender, EventArgs e)
{
piis = GetAllProcessInfos();
listBox1.DisplayMember = "Name";
listBox1.ValueMember = "Id";
listBox1.DataSource = piis;
textBox1.Text = GetIpAdd().ToString();
}
private List<ProcessInfoItem> GetAllProcessInfos()
{
List<ProcessInfoItem> result = new List<ProcessInfoItem>();
Process currentProcess = Process.GetCurrentProcess();
Process[] processes = Process.GetProcesses();
foreach (Process p in processes)
{
if (!String.IsNullOrEmpty(p.MainWindowTitle))
{
ProcessInfoItem pii = new ProcessInfoItem(p.Id,p.MainModule.ModuleName, p.MainWindowTitle, p.MainModule.FileName);
result.Add(pii);
}
}
return result;
}
public class ProcessInfoItem
{
public int Id { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
public string Path { get; set; }
public ProcessInfoItem(int id, string name, string filename, string path)
{
this.Id = id;
this.Name = name;
this.FileName = filename;
this.Path = path;
}
}
You are indexing over a different collection that your for loop is referencing. It sounds like you may want:
piis = GetAllProcessInfos();
for (int i = 0; i < piis.Count; i++)
{
instead. However you are calling that function form within the for loop so it's not clear what you should be iterating over.
try to change,
for (int i = 0; i < listBox1.Items.Count; i++)
{
piis = GetAllProcessInfos();
to
piis = GetAllProcessInfos();
for (int i = 0; i < piis.Count; i++)
{
Related
This is an app that uses WMI to fetch Local Administrator group members on a remote computer. In an attempt to make threadsafe calls to update my main UI from within Background Worker I am getting a StackOverflowException. I have copied the example from another thread on Stack. Could someone help me to identify the cause?
private void getLocalAdministrators(string serverName)
{
try
{
System.Management.ManagementScope scope = new System.Management.ManagementScope("\\\\" + serverName + "\\root\\cimv2");
scope.Connect();
StringBuilder qs = new StringBuilder();
qs.Append("SELECT PartComponent FROM Win32_GroupUser WHERE GroupComponent = \"Win32_Group.Domain='" + serverName + "',Name='" + "Administrators'\"");
System.Management.ObjectQuery query = new System.Management.ObjectQuery(qs.ToString());
System.Management.ManagementObjectSearcher searcher = new System.Management.ManagementObjectSearcher(scope, query);
foreach (System.Management.ManagementObject group in searcher.Get())
{
string groupDetails = serverName + tab + group["PartComponent"].ToString() + tab;
string domainPart = groupDetails.Split('=')[1];
domainPart = domainPart.Replace("\"","").Replace(",Name","");
string accountPart = groupDetails.Split('=')[2];
accountPart = accountPart.Replace("\"", "");
if (query != null)
{
updateUISafely(serverName + tab + domainPart + tab + accountPart);
}
else
{
updateUISafely("Error with: " + serverName);
}
}
}
catch (Exception ex)
{
updateUISafely("Error with: " + serverName + ". " + ex.Message);
}
}
public delegate void ProcessResultDelegate(string result);
void updateUISafely(string result)
{
if (textBox2.InvokeRequired)
{
var d = new ProcessResultDelegate(updateUISafely);
d.Invoke(result);
}
else
{
textBox2.AppendText(result + nl);
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
string[] strArray;
strArray = textBox1.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
int objectCount = strArray.Count();
int count = 0;
foreach (string str in strArray)
{
getLocalAdministrators(str);
count++;
backgroundWorker1.ReportProgress((100 * count) / objectCount);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
foreach (string item in resultsList)
{
textBox2.AppendText(item + Environment.NewLine);
}
btnGet.Enabled = true;
}
You are invoking (aka calling) the delegate, causing it to repeatedly call updateUISafely without ever invoking on the UI thread.
You should call Invoke on the form/control instead providing the delegate as parameter.
Use:
this.Invoke(d);
In my program, when I click a particular row in DataGridView, if that row contains "\" it should pop up an error message that "\ is not allowed in name or in path". I don't know how to do that.
Here is the code:
namespace OVF_ImportExport
{
public partial class Form1 : Form
{
string sName = "";
string sPath = "";
public Form1()
{
InitializeComponent();
}
private void dataGridView1_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
foreach (DataGridViewRow row in dataGridView1.SelectedRows)
{
sName = row.Cells[0].Value.ToString();
sPath = row.Cells[1].Value.ToString();
}
}
private void BtnCreate_Click(object sender, EventArgs e)
{
richTextBox1.Text = "";
StreamWriter file = new StreamWriter("Export.bat");
file.WriteLine("c: ");
file.WriteLine("cd \\");
file.WriteLine("cd Program Files ");
file.WriteLine("cd VMware");
file.WriteLine("cd VMware OVF Tool");
foreach (DataGridViewRow row in dataGridView1.SelectedRows)
{
sName = row.Cells[0].Value.ToString();
sName = sName.Trim();
sPath = row.Cells[1].Value.ToString();
file.WriteLine("start ovftool.exe --powerOffSource vi://" + TxtUsername.Text + ":" + TxtPassword.Text + "#"
+ TxtIP.Text + sPath + " " + "\"" + TxtBrowsepath.Text + "\\" + sName + "\\" + sName + ".ovf" + "\"" + Environment.NewLine);
}
file.WriteLine("pause");
MessageBox.Show("Batch File Created","Batch File");
file.Close();
}
try using this:
// Attach DataGridView events to the corresponding event handlers.
this.dataGridView1.CellValidating += new DataGridViewCellValidatingEventHandler(dataGridView1_CellValidating);
method for above event handler:
private void dataGridView1_CellValidating(object sender,
DataGridViewCellValidatingEventArgs e)
{
// Validate the CompanyName entry by disallowing empty strings.
if (dataGridView1.Columns[e.ColumnIndex].Name == "CompanyName")
{
if (String.IsNullOrEmpty(e.FormattedValue.ToString()))
{
dataGridView1.Rows[e.RowIndex].ErrorText =
"Company Name must not be empty";
e.Cancel = true;
}
}
}
I have a client-server applications I wrote in c#.
My server monitors the commands sent to it by the client.
I wish to send a "finish" message back to the client when the process finishes, I'm just not sure how to do it...
Here is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Management;
using Microsoft.Win32;
namespace RM
{
public partial class RMFORM : Form
{
private Socket clientSock;
private string userName = "";
private string testName = "";
private string testNameString = "";
private string targetPath = "";
private bool isConnected = false;
private TestForm testForm;
private List<string> listOfFilesToCopy = new List<string>();
private List<string> listOfPathToCopy = new List<string>();
private RegistryKey registryKey;
public RMFORM ()
{
InitializeComponent();
userName = RemoteUtils.getConnectedUser();
targetPath = RemoteUtils.targetPath + userName;
testForm = new TestForm(RemoteUtils.remoteIpAddress, userName);
}
private void createNewCommand(string executable, string selectedPath, bool isDirectory)
{
string selectedOutDir = "";
testForm.enableFinishedButton();
testForm.setNumOfProcessesTest();
testForm.ShowDialog();
while (!testForm.getIsFinished())
{
if (testForm.getIsFormClosed())
{
testForm.Hide();
return;
}
}
testName = testForm.getTestName();
testNameString = testForm.getSelectedTestType();
selectedOutDir = targetPath + "\\" + testName;
try
{
if (Directory.Exists(selectedOutDir))
{
DialogResult dialogResult = MessageBox.Show("Test named " + testName + " already exists.\nDo You Wish To Continue?", "Warning!", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.No)
{
return;
}
}
else
Directory.CreateDirectory(selectedOutDir);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error!");
this.Close();
}
if (testNameString.CompareTo("Mek") == 0)
{
addSingleTest(executable, selectedPath, selectedOutDir, "mek", isDirectory);
}
if (testNameString.CompareTo("Mel") == 0)
{
addSingleTest(executable, selectedPath, selectedOutDir, "mel", isDirectory);
}
if (testNameString.CompareTo("Mem") == 0)
{
addSingleTest(executable, selectedPath, selectedOutDir, "mem", isDirectory);
}
}
private void addSingleTest(string executable, string selectedPath, string outputDir, string testType, bool isDirectory)
{
string commandToRun = "";
commandToRun = targetPath + RemoteUtils.batchRunPath + executable + testForm.getTestPerformance() + " " + selectedPath + " " + outputDir;
if (!isDirectory)
{
commandToRun += "\\" + Path.GetFileNameWithoutExtension(selectedPath) + "." + testType + ".pos " + testType;
}
else
{
commandToRun += " " + testType + " " + testForm.getNumOfProcess();
}
removeOne.Enabled = true;
removeAll.Enabled = true;
sendBtn.Enabled = true;
listORequestedCommands.Items.Add(commandToRun);
listORequestedCommands.Refresh();
}
private bool searchForFiles(string parentDirectory)
{
bool found = false;
if (Directory.GetFiles(parentDirectory, "*" + RemoteUtils.sequenceExtenssion).Length > 0)
return true;
try
{
foreach (string subDir in Directory.GetDirectories(parentDirectory))
{
if (Directory.GetFiles(subDir, "*" + RemoteUtils.sequenceExtenssion).Length > 0)
return true;
found = searchForFiles(subDir);
}
}
catch (System.Exception excpt)
{
Console.WriteLine(excpt.Message);
this.Close();
}
return found;
}
private void connectBtn_Click(object sender, EventArgs e)
{
try
{
if (!isConnected)
{
registryKey = Registry.CurrentUser.CreateSubKey(RemoteUtils.registrySubKey);
clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
connectBtn.Text = "Disconnect From Server";
connectBtn.Refresh();
clientSock.Connect(RemoteUtils.remoteIpAddress, RemoteUtils.remotePort);
statusColor.BackColor = Color.Green;
statusColor.Refresh();
isConnected = true;
buttonAddDirectory.Enabled = true;
buttonAddFile.Enabled = true;
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (sender1, e1) => RemoteUtils.copyDllsToServer(targetPath);
backgroundWorker.RunWorkerAsync();
}
else
{
registryKey.Close();
connectBtn.Text = "Connect To Server";
isConnected = false;
statusColor.BackColor = Color.Red;
buttonAddDirectory.Enabled = false;
buttonAddFile.Enabled = false;
clientSock.Close();
clientSock.Dispose();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
this.Close();
}
}
private void sendBtn_Click(object sender, EventArgs e)
{
try
{
string [] commandsInRegistry = registryKey.GetValueNames();
for (int i = 0; i < commandsInRegistry.Length; i++)
{
registryKey.DeleteValue(commandsInRegistry[i]);
}
for (int i = 0; i < listORequestedCommands.Items.Count; i++)
{
clientSock.Send(Encoding.Default.GetBytes(listORequestedCommands.Items[i].ToString() + " <eom> "));
registryKey.SetValue("Command " + (i + 1).ToString(), listORequestedCommands.Items[i].ToString());
}
removeAll_Click(sender, e);
sendBtn.Enabled = false;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error!");
this.Close();
}
}
private void buttonAddDirectory_Click(object sender, EventArgs e)
{
folderBrowserDialog = new FolderBrowserDialog();
folderBrowserDialog.SelectedPath = "C:\\";
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
string selectedPath = folderBrowserDialog.SelectedPath;
if (!searchForFiles(selectedPath))
{
MessageBox.Show("The directory: " + selectedPath + " doesn't contain sequences.", "Error!");
return;
}
testForm.enableNumOfProcesses();
createNewCommand(RemoteUtils.runBatchScript, selectedPath, true);
}
}
private void buttonAddFile_Click(object sender, EventArgs e)
{
openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = "C:\\";
openFileDialog.Filter = "PMD files (*" + RemoteUtils.sequenceExtenssion + ")|*" + RemoteUtils.sequenceExtenssion + "|All files (*.*)|*.*";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
string selectedFile = openFileDialog.FileName;
if (Path.GetExtension(selectedFile).CompareTo(RemoteUtils.sequenceExtenssion) != 0)
{
MessageBox.Show("The file: " + selectedFile + " is not a sequence file.", "Error!");
return;
}
createNewCommand(RemoteUtils.batchRunExe, selectedFile, false);
}
}
private void removeOne_Click(object sender, EventArgs e)
{
int iRemoveIndex = listORequestedCommands.SelectedIndex;
if (iRemoveIndex > -1)
{
listORequestedCommands.Items.RemoveAt(iRemoveIndex);
listORequestedCommands.Refresh();
if (listORequestedCommands.Items.Count == 0)
{
removeOne.Enabled = false;
removeAll.Enabled = false;
sendBtn.Enabled = false;
}
}
}
private void removeAll_Click(object sender, EventArgs e)
{
listORequestedCommands.Items.Clear();
listORequestedCommands.Refresh();
buttonAddDirectory.Enabled = true;
buttonAddFile.Enabled = true;
removeOne.Enabled = false;
removeAll.Enabled = false;
sendBtn.Enabled = false;
}
}
}
Any ideas?
Have you looked at
http://www.thecodeproject.com/csharp/ZaSocks5Proxy.asp
We use code based on this in our s/w so that our "server" monitors input to broadcasts the message to all other users hooked into the "server"
In essence each workstation monitors in incoming string. The first word of the string we use as a "command" and process accordingly. "finish" could be such a command.
I have a Form with two ListBoxes on them. I have removeFromBoxWaiting that watches for creating of new files in a directory.
Once a file is created it prints it out and then it should add the name of the file into the list box.
My problem is that the list box does not get updated. Items actually have been added but they do not show and i have tried update() as well but it didn't work. So any tips will be appreciated. Thanks in advance.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using DevExpress.XtraBars;
using System.Threading;
using System.Configuration;
namespace PrntToKitchen
{
public partial class PrintFiles : DevExpress.XtraBars.Ribbon.RibbonForm
{
FileSystemWatcher filesWatcher = new FileSystemWatcher();
public void barButtonItem1_ItemClick(object sender, ItemClickEventArgs e)
{
MessageBox.Show(e.Item.Name);
if (Properties.Settings.Default[e.Item.Name + "Read"].ToString().Length > 1 && Properties.Settings.Default[e.Item.Name + "ExtIn"].ToString().Length > 1)
{
filesWatcher.Path = Properties.Settings.Default[e.Item.Name + "Read"].ToString();
filesWatcher.Created += new System.IO.FileSystemEventHandler(eventG);
//filesWatcher.Deleted += new System.IO.FileSystemEventHandler(addToBoxFinished);
filesWatcher.EnableRaisingEvents = true;
}
}
public void eventG(object sender, FileSystemEventArgs e)
{
string CurrentPrinter = "";
string findPrt = e.FullPath.Substring(0, e.FullPath.Length-(e.Name.Length + 1));
string findPrtExt = e.Name.Substring(e.Name.LastIndexOf("."));
for (int i = 1; i <= Properties.Settings.Default.NumberOfPrinters; i++)
{
string testPrt = Properties.Settings.Default["Printer" + i + "Read"].ToString();
string testExt = Properties.Settings.Default["Printer" + i + "ExtIn"].ToString();
if (testPrt == findPrt && testExt == findPrtExt)
{
CurrentPrinter = "Printer" + i.ToString();
POSPrinter printer = new POSPrinter(Properties.Settings.Default["Printer" + i + "Port"].ToString(), (int)Properties.Settings.Default["Printer" + i + "Speed"]);
addToBoxWaiting(e.FullPath.ToString());
string file = e.FullPath;
printer.BeginPrint();
printer.PrintFile(file);
printer.EndPrint();
printer.Dispose();
if ((bool)Properties.Settings.Default[CurrentPrinter + "Delete"])
{
IsFileLocked(file);
System.IO.File.Delete(file);
}
else
{
System.IO.File.Move(file, Properties.Settings.Default[CurrentPrinter + "Store"] + "\\" + e.Name + Properties.Settings.Default[CurrentPrinter + "ExtOut"]);
}
removeFromBoxWaiting(e.FullPath.ToString());
addToBoxFinished(e.FullPath.ToString());
busy = false;
break;
}
}
}
void addToBoxWaiting(string text)
{
listBox1.Items.Add(text);
}
void removeFromBoxWaiting(string text)
{
listBox1.Items.Remove(text);
}
public void addToBoxFinished(string destination)
{
listBox2.Items.Add(destination);
}
}
}
I believe your problem is that you are trying to add/remove listbox items while iterating through a loop (and both this actions are happening on UI thread). You should move your for loop into a separate thread, and your addToBoxWaiting method will then look like this:
private void AddToListBox(string item)
{
MethodInvoker del = delegate
{
listBox1.Items.Add(item);
};
BeginInvoke(del);
}
Edit. Added thread code.
public void eventG(object sender, FileSystemEventArgs e)
{
Thread eventThread = new Thread(ThreadProcEventG);
eventThread.Start(e);
}
private void ThreadProcEventG(object eventArgs)
{
var e = (FileSystemEventArgs)eventArgs;
string CurrentPrinter = "";
string findPrt = e.FullPath.Substring(0, e.FullPath.Length-(e.Name.Length + 1));
string findPrtExt = e.Name.Substring(e.Name.LastIndexOf("."));
for (int i = 1; i <= Properties.Settings.Default.NumberOfPrinters; i++)
{
string testPrt = Properties.Settings.Default["Printer" + i + "Read"].ToString();
string testExt = Properties.Settings.Default["Printer" + i + "ExtIn"].ToString();
if (testPrt == findPrt && testExt == findPrtExt)
{
CurrentPrinter = "Printer" + i.ToString();
POSPrinter printer = new POSPrinter(Properties.Settings.Default["Printer" + i + "Port"].ToString(), (int)Properties.Settings.Default["Printer" + i + "Speed"]);
addToBoxWaiting(e.FullPath.ToString());
string file = e.FullPath;
printer.BeginPrint();
printer.PrintFile(file);
printer.EndPrint();
printer.Dispose();
if ((bool)Properties.Settings.Default[CurrentPrinter + "Delete"])
{
IsFileLocked(file);
System.IO.File.Delete(file);
}
else
{
System.IO.File.Move(file, Properties.Settings.Default[CurrentPrinter + "Store"] + "\\" + e.Name + Properties.Settings.Default[CurrentPrinter + "ExtOut"]);
}
removeFromBoxWaiting(e.FullPath.ToString());
addToBoxFinished(e.FullPath.ToString());
busy = false;
break;
}
}
}
You may try the following code:
listBox.Dispatcher.Invoke(ew Action(()=>listBox.Item.Add(item))
How do I print specified rows out to a file from a DataGridView?
Also how can I print out certain columns?
This is what I have been trying to work with.. but it is not working..:
private void saveButton_Click(object sender, EventArgs e)
{
saveFile1.DefaultExt = "*.txt";
saveFile1.Filter = ".txt Files|*.txt|All Files (*.*)|*.*";
saveFile1.RestoreDirectory = true;
if (saveFile1.ShowDialog() == DialogResult.OK)
{
StreamWriter sw = new StreamWriter(saveFile1.FileName);
List<string> theList = new List<string>();
foreach (var line in theFinalDGV.Rows)
{
if (line.ToString().Contains("FUJI"))
richTextBox1.AppendText(line + "\n");
}
}
}
Can anyone help me in the right direction?
DataGridViewRow.ToString() only gives you the typename back, not the row content. You can use this extender to get the rowcontent ('ColumnName'):
public static class Extender {
public static string RowToString(this DataGridViewRow dgvr) {
string output = "";
DataGridView dgv = dgvr.DataGridView;
foreach (DataGridViewCell cell in dgvr.Cells) {
DataGridViewColumn col = cell.OwningColumn;
output += col.HeaderText + ":" + cell.Value.ToString() + ((dgv.Columns.IndexOf(col) < dgv.Columns.Count - 1) ? ", " : "");
}
return output;
}
}
if you only want the content of the row without the coulmn-headername use this (space separated):
public static class Extender {
public static string RowToString(this DataGridViewRow dgvr) {
string output = "";
foreach (DataGridViewCell cell in dgvr.Cells) {
output += cell.Value.ToString() + " ";
}
return output.TrimEnd();
}
}
your code will look like this:
class YourClass
{
private void saveButton_Click(object sender, EventArgs e)
{
saveFile1.DefaultExt = "*.txt";
saveFile1.Filter = ".txt Files|*.txt|All Files (*.*)|*.*";
saveFile1.RestoreDirectory = true;
if (saveFile1.ShowDialog() == DialogResult.OK)
{
StreamWriter sw = new StreamWriter(saveFile1.FileName);
List<string> theList = new List<string>();
foreach (var line in theFinalDGV.Rows)
{
string linecontent = line.RowToString();
if (linecontent.Contains("FUJI"))
richTextBox1.AppendText(linecontent + "\n");
}
}
}
}
public static class Extender {
public static string RowToString(this DataGridViewRow dgvr) {
string output = "";
DataGridView dgv = dgvr.DataGridView;
foreach (DataGridViewCell cell in dgvr.Cells) {
DataGridViewColumn col = cell.OwningColumn;
output += col.HeaderText + ":" + cell.Value.ToString() + ((dgv.Columns.IndexOf(col) < dgv.Columns.Count - 1) ? ", " : "");
}
return output;
}
}