Changing SelectedItem property of a ListView programatically - c#

I have a 2 ListViews with same items in both of them. What I want to do is that when a selection is made in one ListView, the same selection should be reflected in the other ListView also. The two ListViews are bound to two different ViewModels but both the ViewModels implement the same interface.
I've overridden the Equals methods in both ViewModels.
The two ListViews are on different XAML pages. The first ListView say LV1 is in Page1.xaml and LV2 is in Page2.xaml. What I want is that when I am changing the selection in LV2 the selection in LV1 should also change( one way only ). I've set x:FieldModifier="public" on LV1 and exposing through a static property of Page1 like this:
public sealed partial class Page1 : Page
{
public static Page1 page1 { get; private set; }
}
And on Page2, I have this :
private async void LV2_ItemClick(object sender, ItemClickEventArgs e)
{
var selected = e.ClickedItem as ISomeCommonInterface;
//Comparision is successful --> Contains() always returns corect value;
if (Page1.page1.LV1.Items.ToList().Contains(selected))
{
Page1.page1.LV1.SelectedItem = null; // this works
Page1.page1.LV1.SelectedItem = selected; // this doesn't work
}
}
I've found that inside the if condition, assignment to null changes the SelectedItem of LV1 to null but the next line doesn't change it to selected ( it remains null ).

add after assignment:
Page1.page1.LV1.Select();

This works for me:
private void LV1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selected = (sender as ListView).SelectedItem as string;
int index = -1;
for (int i = 0; i < LV2.Items.Count(); i++)
{
if (LV2.Items[i] as string == selected){
index = i;
break;
}
}
// The if becomes obsolete here, it could be replaced by
// if(index >= 0)
if (LV2.Items.ToList().Contains(selected))
{
LV2.SelectedIndex = index;
}
}
There is probably an easier way of getting the index of LV1's SelectedItem in LV2, but it should be enough to get you on the right track.
You can check out the minimal testing app I created that shows that SelectedItem works too.

Method 1 - SelectionMode="Multiple" - both ListViews in sync
You should subscribe the SelectionChanged event on both ListViews - item may not get selected only by click - and there (when selection is changed) you should sync the selection.
private void SyncSelection(object sender, SelectionChangedEventArgs e)
{
ListView listViewToAdd = ReferenceEquals(sender, firstListView) ? secondListView : firstListView;
foreach (var item in e.AddedItems)
{
if (!listViewToAdd.SelectedItems.Contains(item))
{
listViewToAdd.SelectedItems.Add(item);
}
}
foreach (var item in e.RemovedItems)
{
listViewToAdd.SelectedItems.Remove(item);
}
}
Method 2 - SelectionMode="Multiple" - update one after selecting in the other
You should subscribe the SelectionChanged event only on the ListView where items could be selected.
private void SyncSelection(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems)
{
secondListView.SelectedItems.Add(item);
}
foreach (var item in e.RemovedItems)
{
secondListView.SelectedItems.Remove(item);
}
}
Method 3 - SelectionMode="Single"
Subscribe the SelectionChanged event on both if you want to make them be in sync or only on the selectable one if you only want to update the second based on the first.
private void SyncSelection(object sender, SelectionChangedEventArgs e)
{
ListView senderListView = (ListView)sender;
ListView listViewToAdd = ReferenceEquals(sender, firstListView) ? secondListView : firstListView;
listViewToAdd.SelectedItem = senderListView.SelectedItem;
}
You may need to replace var with your interface to make it work.

Related

How to change comboBox Item Source when this comboBoxSelectionChanged property is called WPF C#

So I am using external API which is providing class called CatInfoType which have for example int number catid and string catname.
I have a combobox with properties
< ComboBox x:Name="listOfCategories_comboBox" ... SelectionChanged="listOfCategories_comboBox_SelectionChanged" DisplayMemberPath="catname" />
Then in MainWindow cs file I have:
1) list of this class
List<CatInfoType> displayedCategories_List = new List<CatInfoType>();
2) in constructor
var comboBox = listOfCategories_comboBox as ComboBox;
comboBox.ItemsSource = displayedCategories_List;
3) after some button is clicked then I am filling values of combobox:
foreach (var item in allCategories_list)
{
if (item.catparent == 0)
{
displayedCategories_List.Add(item);
}
}
Until now everything is fine, but I would like to change combobox items after same comboBoxSelectionChanged is called:
private void listOfCategories_comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
CatInfoType selectedCategory = listOfCategories_comboBox.SelectedItem as CatInfoType;
int selectedCategoryId = selectedCategory.catid;
int selectedCategoryParentId = selectedCategory.catparent;
displayedCategories_List.Clear();
foreach (var item in allCategories_list)
{
if (item.catparent == selectedCategoryId)
displayedCategories_List.Add(item);
}
var comboBox = listOfCategories_comboBox as ComboBox; // I think those two lines are useless
comboBox.ItemsSource = displayedCategories_List;
}
However the combobox items are not getting changed. I was trying to do it in few ways. None of them get the result.
How I may do this ? Change comboBox Items "on live". After one of those item is pressed I want to clear the list and add new items to be displayed.
I hope code above and description is showing what I would like to do. In case you have questions feel free to ask.
try use ObservableCollection<CatInfoType> instead List<CatInfoType>

ListBox deletion

I have a listbox, with 2 buttons, new and delete. new adds an item into the list box, and the delete button should delete the item out of the list box. The list box items are tied to a class that stores user entered data from text boxes below.
private void AddListBox()
{
lstCondition.BeginUpdate();
Condition cond = new Condition("");
cond.Name = string.Format("Condition {0}", _selection.NetConditions.Count + 1);
_selection.NetConditions.Add(cond);
lstCondition.EndUpdate();
lstCondition.SelectedItem = cond;
cboNetCondition.Properties.Items.Clear();
cboNetCondition.Properties.Items.AddRange(NetCondition);
cboControlType.Properties.Items.Clear();
cboControlType.Properties.Items.AddRange(ControlType);
cboFlowRate.Properties.Items.Clear();
cboFlowRate.Properties.Items.AddRange(FlowRate);
}
private void btnNew_Click(object sender, EventArgs e)
{
AddListBox();
}
the cbo items are comboboxes, whose data gets tied in the condition class to each instance of the list box.
public frmNetConditions(Condition condo, Selection selection)
{
InitializeComponent();
_selection = selection;
lstCondition.DataSource = _selection.NetConditions;
condition = _selection.NetConditions.Count;
}
private void btnDelete_Click(object sender, EventArgs e)
{
selectedCondition = (Condition)lstCondition.SelectedItem;
cboControlType.SelectedIndex = -1;
cboNetCondition.SelectedIndex = -1;
cboFlowRate.SelectedIndex = -1;
txtFlowRate.Text = string.Empty;
txtStatPressure.Text = string.Empty;
txtDampOpening.Text = string.Empty;
txtDensity.Text = string.Empty;
cboDensity.SelectedIndex = -1;
lstCondition.Items.Remove(lstCondition.SelectedItem);
lstCondition.Refresh();
}
After pressing this delete button, the listbox, still contains the item i wish to delete, im unsure why thats the case?
Update with datasource
public List<Condition> NetConditions { get { return _netconditions; } }
As already suggested, you should bind to a BindingList<Condition> instead of a List<Condition>. This allows you to change the datasource and the control (ListBox) to get notified by your changes. The code should look like this:
lstCondition.ValueMember = "ConditionId";
lstCondition.DisplayMember = "Name";
lstCondition.DataSource = NetConditions;
After defining the binding, the correct way of operating on the ListBox items is to remove from the datasource, not the ListBox itself:
// SelectedItem should be checked for null (no selection is an option)
NetCondition.Remove((Condition)lstCondition.SelectedItem);
However, if you plan to change properties from an element (so, not the list itself), the control is notified only if your element (Condition) implements INotifyPropertyChanged interface.

Clearing All ComboBoxes method

I am trying to create a method that will clear all of the ComboBoxes on my window. This is what I have tried so far:
private void ClearAllComboboxes(ComboBox cmb)
{
cmb.SelectedIndex = -1;
}
And then I call the method like below, but I can only insert one ComboBox to clear at a time.
private void btnClearAll_Click(object sender, RoutedEventArgs e)
{
ClearAllComboboxes(cmbBarlocks);
}
So what I am trying to do is clear all of the comboboxes with as little coding as possible. Can someone please tell me how and what would be the best possible way to do this? Thank you :)
I assume that you are using MVVM and you have SelectedItem property for every combobox in your viewmodel.
In viewmodel, you can just set SelectedItem=null for each combobox.
It will clear your combobox selection.
If you are not using MVVM, then you can use following code in code behind:
private void ClearAllComboboxes()
{
List<ComboBox> comboBoxes = new List<ComboBox>();
GetLogicalChildCollection<ComboBox>(container, comboBoxes);
comboBoxes.ForEach(combobox => combobox.SelectedIndex = -1);
}
private static void GetLogicalChildCollection<T>(DependencyObject parent,List<T> logicalCollection) where T : DependencyObject
{
var children = LogicalTreeHelper.GetChildren(parent);
foreach (object child in children)
{
if (child is DependencyObject)
{
DependencyObject depChild = child as DependencyObject;
if (child is T)
{
logicalCollection.Add(child as T);
}
GetLogicalChildCollection(depChild, logicalCollection);
}
}
}
Let me assume All your comboboxes are inside a container(let it be stackPanel), the you can set selected index to -1 for all of them using the following snippet:
foreach (Control ctrl in stkContainer.Children)
{
if (ctrl.GetType() == typeof(ComboBox))
{
ComboBox cbo = ctrl as ComboBox;
ClearAllComboboxes(cbo);
}
}
If you want to clear the combobox means you have to re-define your method signature as:
private void ClearAllComboboxes(ComboBox cmb)
{
cmb.Items.Clear();
}
protected void btnAll_Click(object sender, EventArgs e)
{
ClearInputs(Page.Controls);
}
//For Clear All Control Values
void ClearInputs(ControlCollection ctrls)
{
foreach (Control ctrl in ctrls)
{
if (ctrl is ComboBox )
((ComboBox )ctrl).ClearSelection();
ClearInputs(ctrl.Controls);
}
}
In your handler try this, it worked when I had a similar issue with ListBox
void ClearCombos(params ComboxBox[] boxes)
{
foreach(var box in boxes)
box.ItemsSource = null;
}
and call it ClearCombos(x,y,z); where x,y,z are the boxes you want to clear

Select index from listview

I'm having some problem to get the index of the selected row in a listview. I wonder why this code isn't working? I get a red line below the SelectedIndex
private void lvRegAnimals_SelectedIndexChanged(object sender, EventArgs e)
{
int index = lvRegAnimals.SelectedIndex;
string specialData = motelManager.GetInfoFromList(index);
UppdateSpecialData(specialData);
}
Help is preciated. Thanks!
EDIT:
For some strange reason I get two messages when I click on one of the lines in the listView!? First I get the previous number and then the number for the last clicked line. What could be wrong?
private void lvRegAnimals_SelectedIndexChanged(object sender, EventArgs e)
{
int index = lvRegAnimals.FocusedItem.Index;
MessageBox.Show(Convert.ToString(index));
}
It's working now when I added a check like this:
if(lvRegAnimals.SelectedIndices.Count > 0)
Because ListView doesn't contain any SelectedIndex, instead there is a property of SelectedIndices.
var indices = lvRegAnimals.SelectedIndices;
//indices[0] you can use that to access the first selected index
ListView.SelectedIndices
When the MultiSelect property is set to true, this property returns a
collection containing the indexes of all items that are selected in
the ListView. For a single-selection ListView, this property returns a
collection containing a single element containing the index of the
only selected item in the ListView.
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
// Acquire SelectedItems reference.
var selectedItems = listView1.SelectedItems;
if (selectedItems.Count > 0)
{
// Display text of first item selected.
this.Text = selectedItems[0].Text;
}
else
{
// Display default string.
this.Text = "Empty";
}
}
Try :
listView1.FocusedItem.Index
This give you the index of the selected row.
There is another thread like this one, but here it goes again.
It can return NULL. Also the SelectedIndexChanged event can be FIRED TWICE. And the first time, there nothing selected yet.
So the only safe way to find it is like this:
private void lv1_SelectedIndexChanged(object sender, EventArgs e)
{
if (lv1.FocusedItem == null) return;
int p = lv1.FocusedItem.Index;
... now int p has the correct value...
The ListView is a darn hassle to work with sometimes.
A simple solution i've used is a for loop that checks for the
selected Item.
I've put my solution in the "When index change trigger" within the ListView.
Example:
int sel_item = 0; //an int to store the selected item index.
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
for (int i = 0; i < listView1.Items.Count; i++)
{
if (listView1.Items[i].Selected == true)
{
sel_item = i;
}
}
}
This would ofcourse only work correctly with the "Multiselection" option set as false.

Remove ListItem in silverlight

I have a ListBox in my Silverlight project.And,when to remove and add ListItem from a ListBox,I got the following error.
Operation not supported on read-only collection.
Code:
public void btnUp_Click(object sender, RoutedEventArgs e)
{
if (lbChoices.SelectedItem != null)
{
ListBoxItem selectedItem = new ListBoxItem();
selectedItem.Content = lbChoices.SelectedItem;
selectedItem.IsSelected = true;
int selectedIndex = lbChoices.SelectedIndex;
if (lbChoices.Items.Count > 1)
{
if (selectedIndex > 0)
{
lbChoices.Items.Remove(lbChoices.SelectedItem);
lbChoices.Items.Insert(selectedIndex - 1, selectedItem);
}
}
}
}
When you are using ItemsControl with an ItemsSource, you can not add/remove elements using the Items collection. You should modify your underlying collection instead.
"The problem stems from the fact that I’d bound my ListBox to an ObservableCollection, once bound the Items collection becomes read-only."
I guess you added items by binding the ItemsSource? If so, remove the item from the collection you are binding to.
You need to remove the item from the source that your ListBox is bound to not the ListBox itself. As soon as your remove it from the source, the ListBox will automatically refresh to not display the item.
Change your code like this:
private void button1_Click(object sender, RoutedEventArgs e)
{
if (lbChoices.SelectedItem != null)
{
ListBoxItem selectedItem = (ListBoxItem)lbChoices.SelectedItem;
int selectedIndex = lbChoices.SelectedIndex;
if (lbChoices.Items.Count > 1)
{
if (selectedIndex > 0)
{
lbChoices.Items.Remove(lbChoices.SelectedItem);
lbChoices.Items.Insert(selectedIndex - 1, selectedItem);
}
}
}
}
It seems that your are moving up the selected item in the list box.

Categories