ListView item selection and exception handling - c#

I have a listView of items, in which the user can only select one at a time. I have the following code, that only works properly when I put it in a try/catch block. The problem is, that some of the item names are too long, and they cause a lot of white space for the other items. Clicking on the white space will cause an ArgumentOutOfRangeException, even though it is in the same row as an item. I solved this by shoving it in a try/catch block, but I feel this is a dirty way of doing it, even if it works. Below is the code.
private void listView1_DoubleClick(object sender, EventArgs e)
{
try
{
string[] arr1 = File.ReadAllLines(listView1.SelectedItems[0].Tag.ToString());
string[] arr2 = arr1[0].Split(';');
}
catch
{
//no catch
}
}
I would like to avoid this altogether, but I do not know how to change the code to make it work without the try/catch. I tried if(!String.IsNullOrEmpty), but it still doesn't work. What is the solution here?

Since your ListView is in View=List selecting makes it necessary to hit the item text.
This is inconvenient I have to admit and turnnig on FullRowSelect doesn't help, as it is only for View=Details.
Here is a quick fix:
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
var hit = listView1.HitTest(e.Location);
if (hit.Item != null)
{
string file = hit.Item.Text;
string[] arr1 = null;
if (File.Exists(file)) arr1 = File.ReadLines(file).ToArray();
...
}
}
You may instead want to go to the bottom of the issue and add code to the MouseUp event to select the row..:
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
var hit = listView1.HitTest(e.Location);
if (hit.Item != null) hit.Item.Selected = true;
}
Note that the HitTest will only catch Items, empty or not, not really emtpy background space to the right or bottom..!

Related

C# communication between forms bug

I'm working on a GUI for an admin interface for management of a student complex. Currently the GUI has a listbox with predefined 6 rules for the students. In the beginning of the code, I add them to a list
private void Form1_Load(object sender, EventArgs e)
{
foreach (string rule in lbRules.Items)
ruleList.Add(rule);
}
Then, the GUI provides the admin with an option to modify the rules. To do so he selects a rule from the listbox and clicks a "Modify" button, which opens another form:
private void BtnModify_Click(object sender, EventArgs e)
{
if (lbRules.SelectedItems.Count > 0)
{
selectedRule = lbRules.SelectedItem.ToString();
selectedIndex = lbRules.SelectedIndex;
selectedRuleNumber = selectedRule.Substring(0, 3);
selectedRule = selectedRule.Substring(6);
var rulesForm = new Rules();
rulesForm.Show();
}
}
On the second form load event I get the rule's text and number:
private void Rules_Load(object sender, EventArgs e)
{
tbRule.Text = Form1.selectedRuleNumber;
tbModifyRule.Text = Form1.selectedRule;
}
The text gets added to a RichTextBox, from where the rule can be edited.
Then the admin clicks a "Save" button, which gets the edited text from the RichTextBox(tbModifyRule) and adds it to a static ruleList in form1, sets a static boolean from form1 to true. Afterwards the second form gets closed:
private void BtnSave_Click(object sender, EventArgs e)
{
saveRule = Form1.selectedRuleNumber + " - " + tbModifyRule.Text;
Form1.ruleList.Insert(Form1.selectedIndex, saveRule);
Form1.ruleList.RemoveAt(Form1.selectedIndex+1);
Form1.formOpen = true;
this.Dispose();
}
At this point we are back to form1, in which we have a timer with timer_tick event. In there we check whether the boolean formOpen is true (which it is set before closing form2). Inside the if statement we clear the listbox and add each rule from the ruleList (previously edited in form2) to the listbox, then sets the formOpen back to false so it doesn't get executed all the time:
if (formOpen)
{
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
formOpen = false;
Now this is really weird, and at this point makes absolutely no sense to me, since I tried debugging it for over an hour, trying different ways, which also led me to mysterious wonders of WHY TF IT WORKS WHENEVER IT WANTS...
So this works randomly, like it would work the first time, the second and third times it won't. Or vice versa. It's all random.
Strangely, I tried adding a breakpoint on the
lbRules.Items.Add(item);
in the foreach loop, so it stops on each item. And I actually saw the changed rule getting added from the ruleList into the listBox, however in the end it was not there.
And weirdly enough, I also tried adding the text from form2 in the listBox in form1, without using a list, but for whatever odd reason, I use the int selectedIndex, which gets the index of the selected item from the BtnModify_Click event to insert the text in that particular index, but this very index gets RANDOMLY set to bloody 0 after form2 closes.
hence, it again works from time to time, because at some tries it doesn't get set to 0 and it works.
if (formOpen)
{
selectedRule = Rules.saveRule;
lbRules.Items.Insert(selectedIndex, selectedRule);
lbRules.Items.RemoveAt(selectedIndex+1);
}
formOpen = false;
I don't assign value to this integer ANYWHERE else in the code.
I really tried digging some sense, but I hit a solid hard rock.
Any help appreciated!
And thanks for the time!
edit1:
as requested - rest of the timer method
private void Timer1_Tick(object sender, EventArgs e)
{
foreach (string text in ws.messages)
message = text;
if (ws.messages.Count > 0)
{
if (message.Contains("comp"))
{
Complaints();
message = String.Empty;
ws.messages.Clear();
}
}
if (formOpen)
{
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
formOpen = false;
}
I would change your code to the following:
if (formOpen)
{
formOpen = false;
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
The issue with having the formOpen = false; outside the if statement is that there is a chance that once the user clicks the Save button the timer could be about to execute the formOpen = false instruction setting it to false making the code inside the If statement to never be executed.
I truly believe this is not random but just a timing issue due to complicated logic.
If I were you, I'd do a couple things:
Use a separate class for data exchange between forms, avoid using public static (I assume) form members for this.
Instead of a timer, subscribe to the Form.Closed event of RulesForm
This might make code flow a bit more predictable and allow you to find errors more easily.
Better yet, use the following pattern:
class Form1
{
private void BtnModify_Click(object sender, EventArgs e)
{
var ruleData = ..... //get current rule data
var rulesForm = new Rules();
rulesForm.SetData(ruleData); //pass initial state to the form
rulesForm.SaveChanges = this.ApplyRules; //pass a method which will be called on save
rulesForm.Show();
}
private bool ApplyRules(RuleData ruleData)
{
//do whatever you like with the rules here
return true;
}
}
class RuleForm
{
public void SetData(RuleData ruleData)
{
//initialize fields, etc
}
public Func<RuleData, bool> SaveChanges { get; set; }
private void BtnSave_Click(object sender, EventArgs e)
{
var ruleData = .... //get data from form fields
if(this.SaveChanges(ruleData))
this.Close();
}
}
class RuleData
{
//whatever data you need
}

Delete items from a llist box in c# through if and else

I'm doing an exercise where I need to delete items from a listbox...
I already know how to do it one by one and all at the same time.
I was wondering how to do it like: if an item were selected, delete only that item, else delete all.
I'm trying for a little while and I can't figure it out.
SOLVED
private void btnDelete_Click(object sender, EventArgs e)
{
listBox.Items.RemoveAt(listBox.SelectedIndex);
listBox.Items.Clear();
}
I was thinking on use these two lines in an "If and Else" but I realize that works with "Try and Catch", like:
try
{
listBox.Items.RemoveAt(listBox.SelectedIndex);
}
catch (Exception)
{
listBox.Items.Clear();
}

check dropdown menu if matches

Long story short: There are specific tags given (like Pop, Rock, Metal) and the User should write into a textbox and every time he adds a char the given tags are checked if one (or more) matches. At the moment I'm using a combobox with the following code:
private void EnterComboBox_TextChanged(object sender, EventArgs e)
{
List<string> AllTags = new List<string>();
AllTags.Add("Pop");
if (AlleTags[0].ToLower().StartsWith(EnterComboBox.Text.ToLower()))
{
EnterComboBox.Items.Clear();
EnterComboBox.Items.Add("Pop");
EnterComboBox.DroppedDown = true;
}
}
this is working fine but the problem is, that after the first char entered the dropbox drops down and the entered text is marked and will be overwritten when a new char is entered. Any ideas how I could fix this? Every idea is welcome it doesn't have to be a combo box :)!
Edit:
After some more (detailed) research I realized I could explain it like this: Basically I want the combobox the behave like the search-bar from google. The users enters letters and in the dropdown menu are autocomplete suggestions
At the moment I solved it like this:
I placed a textbox in front of a combobox so that only the arrow of the combobx is visible and if you click on it you automatically write in the textbox.
public Form1()
{
InitializeComponent();
EingabeTextBox.AutoSize = false;
EingabeTextBox.Size = new Size(243, 21); //the size of the combobox is 260;21
}
private void EingabeTextBox_TextChanged(object sender, EventArgs e)
{
EingabeComboBox.Items.Clear();
List<string> AlleTags = new List<string>();
AlleTags.Add("Example");
if (AlleTags[0].ToLower().StartsWith(EingabeTextBox.Text.ToLower()))
{
EingabeComboBox.Items.Add(AlleTags[0]);
EingabeComboBox.DroppedDown = true;
}
}
For me it would work like this. I hope I can help someone else with this too, but I am still open for any better ideas :)!
Changing the ComboBox entries while typing into it obviously creates undesired interferences. Instead combine a TextBox and a ListBox.
private bool changing;
private void TextBox_TextChanged(object sender, EventArgs e)
{
if (!changing) {
changing = true;
try {
// manipulate entries in the ListBox
} finally {
changing = false;
}
}
}
private void ListBox_IndexChanged(object sender, EventArgs e)
{
if (!changing) {
changing = true;
try {
// Put selected entry into TextBox
} finally {
changing = false;
}
}
}
The changing guard makes sure that the ListBox does not influence the TextBox while you are entering text into the TextBox and vice versa.
The try-finally ensures that the guard will be reset in any circumstances, even if an exception should occur.

Drag multiple files

I have a ListBox with a list of filepaths, which has the property SelectionMode set to MultiExtended. Thus, I can select many items from this list.
Now, I want to Drag and Drop files starting from those paths in the destination folder where I drop them.
My code:
private void Form1_Load(object sender, EventArgs e)
{
// populate the FileList
// i.e. FileList.Items.Add("d:/Samples/file-00" + i + ".wav");
this.FileList.MouseDown += new MouseEventHandler(FileList_MouseDown);
this.FileList.DragOver += new DragEventHandler(FileList_DragOver);
}
void FileList_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
void FileList_MouseDown(object sender, MouseEventArgs e)
{
List<string> filesToDrag = new List<string>();
foreach (var item in FileList.SelectedItems)
{
filesToDrag.Add(item.ToString().Trim());
}
this.FileList.DoDragDrop(new DataObject(DataFormats.FileDrop,
filesToDrag.ToArray()), DragDropEffects.Copy);
}
it works perfect if I select and drop 1 single line/file from the ListBox to the destination folder.
Instead, if I do a multiple selection and I try to drag and drop, it can just select that one line where I start to drag. Seems that MouseDown prevent this?
How would you fix the problem?
Doing this with a ListBox seems to be ridiculously hard. Actually I haven't found a solution at all..
Use a ListView instead! It is easy as pie, using the ItemDrag event and is a much better control anyway.. I can't count how often I had to change from a 'cheap' ListBox to ListView because I needed this or that 'little' extra..
Here is your code moved to a ItemDrag:
private void listView1_ItemDrag(object sender, ItemDragEventArgs e)
{
List<string> filesToDrag = new List<string>();
foreach (var item in listView1.SelectedItems)
{
filesToDrag.Add(item.ToString().Trim());
}
this.listView1.DoDragDrop(new DataObject(DataFormats.FileDrop,
filesToDrag.ToArray()), DragDropEffects.Copy);
}
Note that this only solves the problem of the MouseDown changing the selection. It is in itself not a guarantuee that the actual copying will work.
I found this interesting article that proposes a solution. Maybe you don't need it, as you have said that you got copying one file already working..
Yeah...I don't think there's a good way around that problem. When you click again to initiate the drag it will toggle that item. We don't know that the user actually wants to do a drag until the mouse is held down and moved.
One potential solution is to initiate the drag/drop from something else, just somehow make it really clear that is what the user should drag. Here I'm using a Label instead of the ListBox:
void label1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
List<string> filesToDrag = new List<string>();
foreach (var item in FileList.SelectedItems)
{
filesToDrag.Add(item.ToString().Trim());
}
if (filesToDrag.Count > 0)
{
this.FileList.DoDragDrop(new DataObject(DataFormats.FileDrop,
filesToDrag.ToArray()), DragDropEffects.Copy);
}
else
{
MessageBox.Show("Select Files First!");
}
}
}
You have to be picky about the mouse down and mouse move activities. When it is within the graphics rectangle of your listbox, you'll want normal behavior. When it is outside the bounds of this rectangle, you'll want drag/drop functionality. You can try the pseudo code below:
MouseDown(sender, e)
{
var x = <your sender as control>.ItemFromPoint(.....)
this.mouseLocation = x == null ? x : e.Location;
}
MouseMove(sender, e)
{
if control rectangle doesn't contain the current location then
<your sender as control>.Capture = false
DoDragDrop
}

How to get ListBox to load after selecting ComboBox value?

Working in VS 2012, WinForms, C#...
I have a ListBox I would like to populate depending upon the value selected in a ComboBox. I've tested my SQL Query and it works, but I'm getting a weird problem where, when I run my routines, my ComboBox comes up empty, as well as my ListBox. When I comment out the code in my cb_Session_SelectedValueChanged routine, my CB and LB load just fine, but when it's not commented out is when my LB and CB end up blank.
This is what I have:
private void cb_Session_SelectedValueChanged(object sender, EventArgs e)
{
listbox_Sessions.Visible = true;
LoadSessionListbox();
}
private void LoadSessionListbox()
{
int tempID = Convert.ToInt32(cb_Session.SelectedValue);
// Code here to load listbox, which works without above routine.
}
Am I missing something? Why are my CB and LB blank with that first routine added?
[EDIT]:
I put the routines which were in SelectedValueChanged in a MouseClick event and it works, but not when I want it to... You have to click a couple times to get it to re-load with the correct ID. I feel like I'm getting closer, but still not the right event.
Try this:
private void cb_Session_SelectedValueChanged(object sender, EventArgs e)
{
if(cb_Session.SelectedValue>-1)
{
listbox_Sessions.Visible = true;
LoadSessionListbox();
}
}
Figured it out!!
I ended up adding a simple if statement to my SelectedValueChanged routine, and it fixed everything!
private void cb_Sessions_SelectedValueChanged(object sender, EventArgs e)
{
listBox_Sessions.Visible = true;
if (cb_Sessions.SelectedValue != null)
LoadSessionListbox();
}
Works perfectly now.
Try in SelectedIndexChanged Event and follow
private void cb_Session_SelectedIndexChanged(object sender, EventArgs e)
{
if (cb_Session.SelectedValue == null) return;
if (cb_Session.SelectedIndex == -1) return;
listbox_Sessions.Visible = true;
LoadSessionListbox((int)cb_Session.SelectedValue);
}
private void LoadSessionListbox(int selectedValue)
{
//TODO: Do stuff
}

Categories