I have a WinForms Dialog with 2 ListBox controls. In the application under test, doubleClicking any items in one of the listbox controls (I'll call this the CONTROL LISTBOX) results in selecting the matching item in the other listbox (SLAVE LISTBOX).
My test causes multiple entries to be made in the CONTROL LISTBOX. The test then performs a ListBox.SelectedItem.DoubleClick() on each of the CONTROL lISTBOX items, comparing the ListBox.SelectedItemText from both listbox controls.
In the application UI, this ALWAYS works, but the test of the call to ListBox.SelectedItemText for SLAVE LISTBOX returns the text matching what is slected in the UI correctly ONLY on the initial iteration of the doubleclick\compare.
Can anybody help me figure out what I'm doing wrong? Thanks!
Here is my code:
public bool SelectMainEventViaErrorEvent(int eventIdx)
{
bool bSuccess = false;
errorEvents.Items.Select(eventIdx);
System.Threading.Thread.Sleep(1000);
errorEvents.Items.SelectedItem.DoubleClick();
System.Threading.Thread.Sleep(1000);
if (eventIdx > 0)
{
IVScrollBar vertScroll = mainEvents.ScrollBars.Vertical;
vertScroll.ScrollDownLarge();
}
if (errorEvents.SelectedItemText == mainEvents.SelectedItemText)
{
bSuccess = true;
}
log.Info($"SelectMainEventViaErrorEvent({eventIdx}) selected error event = {errorEvents.SelectedItemText}");
log.Info($"SelectMainEventViaErrorEvent({eventIdx}) selected main event = {mainEvents.SelectedItemText}");
return bSuccess;
}
As you can see, by the following image, the text in both list boxes are identical. However, the call to ListBox.SelectedItemText for the top listbox (SLAVE LISTBOX) returns the value from the first iteration, which matched the first item in the bottom listbox (CONTROL LISTBOX) during the first iteration of the doubleclick/compare.
Proof that the text of the selected listbox items match
Comparing with plain text is bad idea since "Text" != "Text ". What you could use in yor case is DisplayMember and ValueMember properties.
I will demonstrate it for you with manually populating listboxes but you do it from database or however you do it.
First of all create class that will store your values and it's ID's. I usually create it like this (so i am able to use that class later for something else)
public class Int_String
{
public int _int { get; set; } // Important to be declared like properties and not like variables
public string _string { get; set; }
}
Now let's populate our listbox like this:
public YourForm()
{
List<Int_String> list = new List<Int_String>();
list.Add(new Int_String { _int = 1, _string = "Some text" }); // I am populating it manually but you will populate it from DB or somewhere else
list.Add(new Int_String { _int = 2, _string = "Some other text" });
list.Add(new Int_String { _int = 3, _string = "One more text" });
// Now when we have list we need to bind it to our listbox.
// IMPORTANT!!!!!
// Display member and Value member properties SHOULD be able to be added before and after assigning datasource to control (like combobox) BUT for some reason on listbox it only works when you assign it AFTER you bind your datasource to listbox.
// If you ever work with other controls and use these values, ALWAYS declare display member and value member BEFORE you bind datasource. Why? For now let's just say it is much faster but explanation is for other question
myListBox1.DataSource = list;
myListBox1.DisplayMember = "_string"; // When you start typing .DisplayMember, you will not get that property in recommendation since it is hidden so do not think there is not that property there.
myListBox1.ValueMember = "_int"; // Same as above
}
Now when you populate listbox like this and second one on same way with same id's you could simply do if(listbox1.SelectedValue == listbox2.SelectedValue) and compare them even if their text is not equal but id is.
BONUS:
Also what you can do is expand class like this:
public class Int_String
{
public int _int { get; set; }
public string _string { get; set; }
public string SomethingOther = "AsD";
public bool IsTrue()
{
return true;
}
}
then bind it on same way and do this:
Int_String item = listbox1.SelectedItem as Int_String;
bool check = item.IsTrue();
MessageBox.Show(item.SomethingOther);
So basically you bind whole class for each item in listbox, display to user one of the variables (in our case _string), set ValueMember to other unique variable so it is easy to search whole listbox and when needed get whole class from that item.
I wasn't ever able to get this working by iterating forward through the errorEvent listbox in my automated test code, but it DOES work when iterating backwards through the errorEvent listbox.
Calling code:
for (int i = eventViewer.GetErrorEventsCount() - 1; i >= 0; i--)
{
bResult = eventViewer.SelectMainEventViaErrorEvent(i);
if (!bResult)
{
break;
}
System.Threading.Thread.Sleep(2000);
}
Verification Code:
public bool SelectMainEventViaErrorEvent(int eventIdx)
{
bool bSuccess = false;
DisableToolTips(true);
errorEvents.Select(eventIdx);
errorEvents.SelectedItem.DoubleClick();
System.Threading.Thread.Sleep(1000);
log.Info($"SelectMainEventViaErrorEvent({eventIdx}) selected error event = {errorEvents.SelectedItemText}");
log.Info($"SelectMainEventViaErrorEvent({eventIdx}) selected main event = {mainEvents.SelectedItemText}");
if (errorEvents.SelectedItemText == mainEvents.SelectedItemText)
{
bSuccess = true;
}
return bSuccess;
}
Related
I'm using a WPF ListView to show and select/deselect some items but there are some conditions that user cannot select/deselect an item and I should handle it in the code behind.
I tried to use ListView.SelectionChanged event to handle it but the problem is that when I change the selected item in the code behind (select it again if it was deselected or the other way), the event triggers again and I don't want that.
What is the best way to set conditions on select/deselect ListView item?
I tried to solve it using ListView.PreviewMouseLeftButtonDown event instead of ListView.SelectionChanged. But I wanted to know if there is a better way?
There might be other WPF controls (especially third party ones) that can handle this a little more gracefully. Additionally you can hook into the style of the ListView to alter the mouse-over behavior.
As a crude approach; however, I was able to accomplish what you're looking for by using the SelectionChanged event to effectively "undo" any selections we deem invalid.
First a simple ListView in xaml:
<ListView
x:Name="ItemsLv"
SelectionChanged="ItemsLv_SelectionChanged"
SelectionMode="Single" />
The SelectionMode being Single is important here as the approach for undoing a selection when multiple items are selected is more complicated.
Then, an object to represent our list items with the ToString() overload implemented so we don't have to fiddle with data templates and binding.
public class MyListViewItem
{
public string Text { get; set; }
public bool IsSelectable { get; set; }
public override string ToString()
{
return Text;
}
}
This object just represents a string (our data) paired with a boolean of whether or not we want this item to be selectable.
Then, a quick little setup to populate the list with a few item for testing:
public MainWindow()
{
InitializeComponent();
var items = new List<MyListViewItem>
{
new() { Text = "Item One", IsSelectable = true },
new() { Text = "Item Two", IsSelectable = true },
new() { Text = "Item Three", IsSelectable = false },
new() { Text = "Item Four", IsSelectable = true }
};
ItemsLv.ItemsSource = items;
}
And finally, the "magic". The SelectionChanged event handler:
private void ItemsLv_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// if this event wasn't caused by a new item that wasn't already
// selected being selected, don't do anything extra
if (e.AddedItems.Count <= 0)
{
return;
}
// get the newly selected item and cast it to a MyListViewItem so we can inspect the IsSelectable property
var selectedItem = (MyListViewItem)ItemsLv.SelectedItem;
// if it's a selectable item, we don't need to intervene
if (selectedItem.IsSelectable)
{
return;
}
// we made it this far that means we tried to select an item that should NOT be selectable
// if the new selected item caused us to UNselect an old item, put the selection back
if (e.RemovedItems.Count > 0)
{
ItemsLv.SelectedItem = e.RemovedItems[0];
}
// otherwise (the first selection ever?) just set selection back to null
else
{
ItemsLv.SelectedItem = null;
}
}
Hopefully the code comments in there make it clear what's going on.
I have 2 forms: 1 form contains a listView and another form contains a comboBox.
I would like the first column of the listView to be loaded into the comboBox on the second form.
This is my attempt:
comboBox1.Items.Add(Form2.listView2.columnHeader1);
However, this does not work. (Form2.ListView is inaccessible due to its protection level). Suggestions would be appreciated.
Quick and dirty solution:
Go to the Properties of listView2 in the winforms designer and look for Modifiers. Then select Public like you see in the picture below:
And now it will be accessible
The more elegant solution:
Create a property in your first Form which has only a getter. In this getter you can safely return the columnHeader1:
public ColumnHeader ColumnHeader { get { return this.listView1.Columns["columnHeader1"]; }}
or:
public ColumnHeader ColumnHeader { get { return this.columnHeader1; }}
EDIT:
It seems that you rather would like to have all values from that column. So in this case you would have to return all the values, which can be done like this:
public List<string> AlllValuesFromColumn
{
get
{
int indexOfColumn = listView2.Columns.IndexOf(this.columnHeader1);
return listView2.Items.OfType<ListViewItem>().Select(x => x.SubItems[indexOfColumn].Text).ToList();
}
}
To Add all the values in one blow to the ComboBox you can use AddRange:
comboBox1.Items.AddRange(Form2.AlllValuesFromColumn.ToArray());
EDIT 2:
But the solution I personally would prefer is to hold the data source in an extra variable. This can be passed around. No magic there.
Make a public method in your second form and let the method set the comboboxitems.
Form1:
bool Do = true;
int i = 0;
Form2 F = new Form2();
while (Do)
{
try
{
F.AddItem(listView1.Columns[i].Name);
i++;
}
catch
{
Do = false;
}
}
Form2:
public void AddItem(string ToAdd)
{
comboBox1.Items.Add(ToAdd);
}
I have used a combobox with items from a tabel from MySql which works fine. I can pick an item and save an object and the chosen object is shown. But if the user wants to edit the content I can't get the combobox to show the chosen item in the edit-window. The textboxes work fine but the combobox shows the first one in the list and not the selected item.
My constructor:
public CreateForm(Letter brev)
{
InitializeComponent();
this.brev = brev;
GetDropDownBoxReady();
saveButton.Visible = false;
deleteButton.Visible = false;
insertText(brev);
}
private void GetDropDownBoxReady()
{
fraByCB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown;
fraByCB.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
fraByCB.AutoCompleteSource = AutoCompleteSource.ListItems;
fraOmraadeCB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown;
fraOmraadeCB.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
fraOmraadeCB.AutoCompleteSource = AutoCompleteSource.ListItems;
}
public void insertText(Letter brev)
{
// these work fine
objekt1textBox.Text = brev.Object1;
objekt2TextBox.Text = brev.Object2;
diverse2TextBox.Text = brev.Diverse2;
portoTakstTextBox.Text = brev.PortoTakst;
portoTillægTextBox.Text = brev.PortoTillaeg;
portoFraTextBox.Text = brev.PortoFra.ToString("dd-MM-yyyy");
portoTilTextBox.Text = brev.PortoTil.ToString("dd-MM-yyyy");
// these don't
fraByCB.SelectedText = brev.FraBy;
fraOmraadeCB.SelectedItem = fraOmraadeCB.FindStringExact(brev.FraOmraade);
}
Let me know if you need more of the code too find the problem.
You says that you could see the Items in the combobox dropdown but there is no code shown above that fills the dropdown. Then, probably, you fill the Items property of the combobox with strings in the InitializeComponent call with predefined strings (Using the WinForms designer).
At this point the error is in the insertText method:
fraByCB.SelectedIndex = fraByCB.FindStringExact(brev.FraBy);
fraOmraadeCB.SelectedIndex = fraOmraadeCB.FindStringExact(brev.FraOmraade);
FindStringExact returns the position of the item searched if it is found, you could use this value to set the SelectedIndex property of the combos
So your actual code contains two erros. The first is about the fraByCB.SelectedText. This method is used to get/set the highlight on some or all of the text portion of the combo, not to select an intem. The second error is the assignement of the return value of FindStringExact (an integer) to the SelectedItem property that wants the string instead
I have a method that adds items to my listbox called refreshInterface which is called as soon as the programe starts, adding names of homeforms in the listbox using the FormItems class, here is the rereshInterface method below
public void refreshInterface()
{
//int number = 0;
foreach (DataSet1.xspGetAnalysisUsageTypesRow homeForms in myDataSet.xspGetAnalysisUsageTypes)
{
var forms = new FormItems(homeForms);
listBox1.Items.Add(forms);
}
}
The FormItems class is this below
public class FormItems
{
public DataSet1.xspGetAnalysisUsageTypesRow types { get; set; }
public FormItems(DataSet1.xspGetAnalysisUsageTypesRow usageTypes)
{
types = usageTypes;
}
public override string ToString()
{
// returns the rows that are relating to types.xlib_ID
var libtyps = types.GetxAnalysisUsageRows();
var cnt = 0;
foreach (DataSet1.xAnalysisUsageRow ty in libtyps)
{
//returns true if ty is null
bool typeNull = ty.Isxanu_DefaultNull();
// if its false, if xanu_Default is set
if (!typeNull)
{
cnt += 1;
}
}
var ret = String.Format("set {0} [Set: {1}]", types.xlib_Desc, cnt);
//return this.types.xlib_Desc;
return ret;
}
}
Each listbox (the listbox is on the left of the homeform) item has a number of reports that can be added to it, so for instance, i select an homeform from my listbox, there are 12 textboxes on the right hand side and each textbox has a pair of buttons which are Browse and Clear. If I click on the browse button a new form appears, and i select a report from that form and add it to a particular textbox, the count for that homeform should update, and i clear a textbox for a particular homeform, the count should also update.
At the moment when i debug the application, it shows me the count of each Homeform depending on the amount of reports added to the homeform, but while the programe is running, if i add a new report to a homeform, the count does not update until i restart the debug session. I was told about using a Databinding method but not sure of how i could use it here
How do i ge my listbox item to update ?
You should probably look into binding. Here is a good place to start:
http://www.codeproject.com/Articles/140621/WPF-Tutorial-Concept-Binding
If you want a GUI to respond to data changes then binding is your best friend.
You should bind List Box component source to Observable Collection, every update you do to Observable Collection will update List Box data.
Might not be exact but should give you an idea.
public void refreshInterface()
{
Dictionary<int,string> items = new Dictionary<int,string>();
//int number = 0;
foreach (DataSet1.xspGetAnalysisUsageTypesRow homeForms in myDataSet.xspGetAnalysisUsageTypes)
{
var formitem = new FormItems(homeForms);
items.Add(formitem.someprop, formitem.toString());
}
listbox.DataSource = items;
listbox.DisplayMember = "Value";
listbox.ValueMember = "Key";
}
I have a datagridview which we will call dataGridViewExample.
My object (the uncommon datatypes is because my database is SQLite):
class MyObject
{
public Int64 Vnr { get; set; }
public string Name { get; set; }
public Single Price { get; set; }
public int Amount { get; set; }
}
Here is the relevant code:
//This form gets called with a .ShowDialog(); in my form1.
private List<MyObjecte> ExampleList = new List<MyObject>();
public MyForm()
{
dataGridViewExample.DataSource = OrdreInkøbsListe;
}
private void AddtoDataGridViewExample()
{
//Add a new MyObject to the list
ExampleList.Add(new myObject()
{
Vnr = newVnr,
Amount = newAmount,
Price = newPrice,
Name = newName
});
//refresh datasource
dataGridViewExample.DataSource = null;
dataGridViewExample.Refresh();
dataGridViewExample.DataSource = OrdreInkøbsListe;
ddataGridViewExample.Refresh();
}
When MyForm gets called with a .ShowDialog, it shows up fine and displays my DataGridView example just fine. As you can read from the code, the ExampleListis initially empty, so it just shows an empty datagridview with 4 columns: Vnr, Name, Price & Amount. If I click inside it etc. nothing happens - so everything is working as planned, so far.
Everytime I call AddtoDataGridViewExample() it adds the new object to the Datagridview, and the datagridview does update, listing all the objects added so far (they show themself as rows, again according to plan).
Now, remember that I just said that nothing happened if you clicked inside DataGridViewExample before I have called AddtoDataGridViewExample()?
Well, after having called AddtoDataGridViewExample() once or more, the program will crash if I click inside DataGridViewExample (for example: the users wants to select a row). It throws an IndexOutOfRangeException and talks about an -1 index.
It also throws the exception in the other form, on the line where I call MyForm with .ShowDialog();
I really am stuck on this, do you guys have any idea what is wrong??
My only clue is that I do believe the refresh of DataGridViewExample's datasource might be the cause of the problem.
Another important note: I have yet bound any events to my DataGridViewExample. So you can rule that idea out.
Here is all DataGridViewExample's properties:
this.dataGridViewExample.AllowUserToAddRows = false;
this.dataGridViewExample.AllowUserToDeleteRows = false;
this.dataGridViewExample.AllowUserToResizeColumns = false;
this.dataGridViewExample.AllowUserToResizeRows = false;
this.dataGridViewExample.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dataGridViewExample.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridViewExample.Location = new System.Drawing.Point(591, 53);
this.dataGridViewExample.MultiSelect = false;
this.dataGridViewExample.Name = "dataGridViewExample";
this.dataGridViewExample.ReadOnly = true;
this.dataGridViewExample.RowHeadersVisible = false;
this.dataGridViewExample.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dataGridViewExample.ShowEditingIcon = false;
this.dataGridViewExample.Size = new System.Drawing.Size(240, 150);
this.dataGridViewExample.TabIndex = 31;
I guess the click event tries to get the currently selected row and do something with it, while dataGridViewExample.DataSource = null; clears the datasource, and the currently selected row becomes null.
If you set the DataGridView.DataSource to the list, you don't need to reset it to null, refresh, and reset it to the list again (and refresh again) to see the changes. It will be enough to just refresh the DataGridView.
You can also just try using an BindingList<T> object instead of a List<T>, which will automatically notify your grid of its internal changes (Adding and removing elements), and there's also an INotifyPropertyChanged interface you can implement on your MyObject class, that will make every property change in an object show on the grid (For any changes made to the object in the code, and not through the grid itself).
Have you tried running the debugger and break when InedxOutOfRangeException is thrown to see where the exception is thrown?
Select Debug > Exceptions then there's a Find button on the dialog so you don't have to browse through all of the possibilities.
I had similar situation. I assigned generic list of certain object to DataGridView. Then I was setting null to DataSource and after that refresh. After that I assign list of objects to DataSource. While clicked on grid while runtime error occured IndexOutOfRange. My solution was to assign new empty list of my object to that grid and refresh and after changes on my working list I do assign to DataSource and call Refresh. Now, it is working without any crashes. Please look on my code before:
grid.DataSource = null;
grid.Refresh();
if(cases.Count() > 0)
{
grid.DataSource = cases;
grid.Refresh();
}
And now on my code after:
grid.DataSource = new List<MyCase>();
grid.Refresh();
//do something with cases
if(cases.Count() > 0)
{
grid.DataSource = cases;
grid.Refresh();
}