"SelectedIndexChanged" not firing after "Items.Clear()" in ListBox - c#

For a ListBox (With Selection mode set to One), I wish to track whether there's a selected item or none selected. To do so, I subscribed a method to SelectedIndexChanged and checked if the SelectedIndex is -1 or not. However, I noticed that the event doesn't fire after calling Items.Clear(), even though SelectedIndex changes to -1 (if it wasn't already -1).
Why doesn't it fire?
I know I can work around this by assigning -1 to SelectedIndex before clearing the list. But is there a better way?
Here's a simple code to replicate this:
using System;
using System.Windows.Forms;
namespace ns
{
class Program
{
static ListBox lst = new ListBox();
public static void Main()
{
lst.SelectedIndexChanged += new EventHandler(lst_SelectedIndexChanged);
lst.Items.Add(1);
Console.WriteLine("Setting selected index to 0...");
lst.SelectedIndex = 0; //event fire here
Console.WriteLine("(Selected Index == {0})", lst.SelectedIndex);
Console.WriteLine("Clearing all items...");
lst.Items.Clear(); //event *should* fire here?!
//proof that the selected index has changed
Console.WriteLine("(Selected Index == {0})", lst.SelectedIndex);
}
static void lst_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine("[!] Selected Index Changed:{0}", lst.SelectedIndex);
}
}
}
Edit:
I am considering making a custom list by making a class that inherits from ListBox, or by making a user control. However I'm not sure how to approach this.
Any ideas on hiding/overriding the clear method using either inheritance/userControl?
Would it require hiding/overriding other methods as well or is there a way to avoid this?

Looking at the code in Reflector, the Clear() method on Items just resets the .Net object's internal object list (and does not, as you noticed, fire OnSelectedIndexChanged).
The SelectedIndex property returns -1 because the logic in the property's getter dictates that -1 should be returned if there are no items in the internal list.

Clear() only clears the internal collection of the control. Clear() won't fire the SelectedIndexChanged event because that event will only be raised by changing the CurrentlySelectedIndex. Try using lst.ClearSelected() instead. Calling this method is equivalent to setting the SelectedIndex property to negative one (-1). You can use this method to quickly unselect all items in the list. Alternatively you can try calling Items.Clear() and follow it with a call to ListBox.RefreshItems

probably a hackish solution but this is what i thought of:
class myListBox
{
public ListBox myList;
public myListBox()
{
myList = new ListBox();
}
public void listClear()
{
if (myList.Items.Count > 0)
{
myList.SelectedIndex = 0;
}
myList.Items.Clear();
}
}
than you can call this like this in your main form:
myListBox example = new myListBox();
example.myList.Items.Add("Example");
example.myList.SelectedIndexChanged += new EventHandler(lst_SelectedIndexChanged);
this.Controls.Add(example.myList);
example.listClear();
maybe that could solve your problem.

This is my way, it's compatible with existed code.
public class DetailsListView : ListView
{
public new class ListViewItemCollection : ListView.ListViewItemCollection
{
private DetailsListView m_owner;
public ListViewItemCollection(DetailsListView owner)
: base(owner)
{
m_owner = owner;
}
public override void Clear()
{
base.Clear();
m_owner.FireChanged();
}
}
private void FireChanged()
{
base.OnSelectedIndexChanged(EventArgs.Empty);
}
private ListViewItemCollection m_Items;
public DetailsListView()
{
m_Items = new ListViewItemCollection(this);
View = View.Details;
GridLines = true;
HideSelection = false;
FullRowSelect = true;
}
public new ListViewItemCollection Items
{
get
{
return m_Items;
}
}
}

Related

Update a ViewModel when an item in an observable collection is updated

Source code is here:
https://github.com/djangojazz/BubbleUpExample
The problem is I am wanting an ObservableCollection of a ViewModel to invoke an update when I update a property of an item in that collection. I can update the data that it is bound to just fine, but the ViewModel that holds the collection is not updating nor is the UI seeing it.
public int Amount
{
get { return _amount; }
set
{
_amount = value;
if (FakeRepo.Instance != null)
{
//The repo updates just fine, I need to somehow bubble this up to the
//collection's source that an item changed on it and do the updates there.
FakeRepo.Instance.UpdateTotals();
OnPropertyChanged("Trans");
}
OnPropertyChanged(nameof(Amount));
}
}
I basically need the member to tell the collection where ever it is called: "Hey I updated you, take notice and tell the parent you are a part of. I am just ignorant of bubble up routines or call backs to achieve this and the limited threads I found were slightly different than what I am doing. I know it could possible be done in many ways but I am having no luck.
In essence I just want to see step three in the picture below without having to click on the column first.
Provided that your underlying items adhere to INotifyPropertyChanged, you can use an observable collection that will bubble up the property changed notification such as the following.
public class ItemObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
if (args.NewItems != null)
foreach (INotifyPropertyChanged item in args.NewItems)
item.PropertyChanged += item_PropertyChanged;
if (args.OldItems != null)
foreach (INotifyPropertyChanged item in args.OldItems)
item.PropertyChanged -= item_PropertyChanged;
}
private void OnItemPropertyChanged(T sender, PropertyChangedEventArgs args)
{
if (ItemPropertyChanged != null)
ItemPropertyChanged(this, new ItemPropertyChangedEventArgs<T>(sender, args.PropertyName));
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemPropertyChanged((T)sender, e);
}
}
You should do two things to get it to work:
first: you should refactor the RunningTotal property so it can raise the property changed event. Like so:
private int _runningTotal;
public int RunningTotal
{
get => _runningTotal;
set
{
if (value == _runningTotal)
return;
_runningTotal = value;
OnPropertyChanged(nameof(RunningTotal));
}
}
Second thing you should do is calling the UpdateTotals after you add a DummyTransaction to the Trans. An option could be to refactor the AddToTrans method in the FakeRepo
public void AddToTrans(int id, string desc, int amount)
{
Trans.Add(new DummyTransaction(id, desc, amount));
UpdateTotals();
}

How do I get notified when an array property changes?

My class contain many properties and i need to handle each properties.
See this below:
public partial class my_form : Form
{
private Image[] _imagelist;
public Image[] imagelist
{
get
{
return _imagelist;
}
set
{
this._imagelist = value;
this.on_imagelist_changed();
}
}
private void on_imagelist_changed()
{
// do something.
}
private void button1_Click(object sender, EventArgs e)
{
/* set new imagelist */
this.imagelist = getimagelist();
}
}
Yes, It's work fine.
But when i call like this.
public partial class my_form
{
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
int selectedIndex = this.listView1.SelectedItems[0].ImageIndex;
this.imagelist[selectedIndex] = dosomething(this.imagelist[selectedIndex]);
}
}
It's don't call on_imagelist_changed(). Why ?
I can't add property by indexer like this. :(
public partial class my_form
{
public Image imagelist[int x]
{
get
{
return _imagelist[x];
}
set
{
this._imagelist[x] = value;
this.on_imagelist_changed();
}
}
}
Can anyone help me solve this problem ?
Can i avoid to make a control class like this C# Indexers ?
I founded some suggestion, they told me let try ObservableCollection. I don't understand about this. May be someone example for me ?
It's don't call on_imagelist_changed(). Why?
Because, quite bluntly, imageList has not changed. Some images inside imageList may have changed, but your code reacts only to the change of the entire imageList. The assignment calls get of your property twice; it never calls the setter.
I can't add property by indexer like this.
That's correct. However, you correctly noted that you could use an observable collection:
public ObservableCollection<Image> Images {get;}
private void OnImageListChanged(
object sender
, NotifyCollectionChangedEventArgs e) {
// do something.
}
public MyForm() {
Images = new ObservableCollection<Image>();
Images.CollectionChanged += OnImageListChanged;
}
Because your property gets and sets pointer to array, not array itself. So when you change array items pointer stays the same.
That's why properties should not return arrays.

Notify a ViewModel that an object in a collection has been selected

Consider the following object, part of a WPF MVVM application:
public class MyObject : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
private bool _isSelected;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
And its use in the following ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
private List<MyObject> _myObjects;
public List<MyObject> MyObjects
{
get
{
return _myObjects;
}
set
{
_myObjects = value;
OnPropertyChanged("MyObjects");
}
}
public bool CanDoSomething
{
get
{
return MyObjects.Where(d => d.IsSelected).Count() > 0;
}
}
}
In this situation, I can track which of my objects have been selected, and selecting them will fire OnPropertyChanged and so can notify the parent view.
However, CanDoSomething will always be false because there's nowhere I can fire an OnPropertyChanged to create a notification. If I put it in MyObject, it doesn't know anything about the property and so does nothing. There's nowhere to put it in the ViewModel because there's nothing that reacts when an object in the list is selected.
I've tried substituting the List for an ObservableCollection and a custom "TrulyObservableCollection" (see Notify ObservableCollection when Item changes ) but neither work.
How can I get round this, without resorting to click events?
I feel like if I had a better idea of what your end goal was I might be able to recommend a better approach. There is some stuff going on that just feels a little out place. Like maybe 'CanDoSomething' should be part of a command object. And I am wondering if more than one MyObject be selected at a time? If not then I would approach this in an entirely differnt way.
So anyway, you want to update CanDoSomething any time the IsSelected property of one of the items in MyObjects changes. It sounds like you were using an ObservableCollection at one point and then abandoned it. That was a mistake. You need to update CanDoSomething any time one of two events occur; the first is when items are added to or removed from MyObjects and the second is when the IsSelected property of any of the objects in MyObjects changes. For the first event you need something that implements INotifyCollectionChanged, i.e. an ObservableCollection. You already have the second event covered because the objects implement INotifyPropertyChanged. So you just have to combine those two things.
In the following example I have taken your code and made some changes. To start with I changed MyObjects back to an ObservableCollection<MyObject>. It does not have a setter because I have found that there usually is not good reason to change an observable collection; just add and remove objects as necessary. Then in the viewmodel's constructor I am register for the CollectionChanged event of MyObjects. In that handler I am grabbing items that are added to the collection and hooking up their PropertyChanged event to the OnIsSelectedChanged event handler and I am unhooking the PropertyChanged event from OnIsSelectedChanged for any objects that were removed from the collection. Because items have been added or removed we have no idea what the state of IsSelected may be of the objects in MyObjects so this is a good opportunity to update CanDoSomething, and I do at the bottom of the event handler. Finally, the OnIsSelectedChanged is where the other half of the magic happens. Every object in MyObjects will have their PropertyChanged event hooked up to this event handler. Whenever the IsSelected property on any of these objects changes the event handler will update CanDoSomething.
public class MyViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
public MyViewModel()
{
this._myObjects.CollectionChanged += (o, e) =>
{
if (e.NewItems != null)
{
foreach (var obj in e.NewItems.OfType<MyObject>())
{
obj.PropertyChanged += this.OnIsSelectedChanged;
}
}
if (e.OldItems != null)
{
foreach (var obj in e.OldItems.OfType<MyObject>())
{
obj.PropertyChanged -= this.OnIsSelectedChanged;
}
}
if (e.PropertyName == "IsSelected")
{
this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
}
};
}
private readonly ObservableCollection<MyObject> _myObjects =
new ObservableCollection<MyObject>();
public ObservableCollection<MyObject> MyObjects
{
get
{
return _myObjects;
}
}
private void OnIsSelectedChanged(object o, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
}
}
private bool _canDoSomething;
public bool CanDoSomething
{
get { return this._canDoSomething; }
private set
{
if (_canDoSomething != value)
{
_canDoSomething = value;
OnPropertyChanged("CanDoSomething");
}
}
}
}
First create a class that defines this attached property:
public static class ItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
control.ItemClick += OnItemClick;
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
}
Then just bind this attached property to a delegate command in your view model: helper:ItemClickCommand.Command="{Binding MyItemClickCommand}"
You can find more detail in this blog post: https://marcominerva.wordpress.com/2013/03/07/how-to-bind-the-itemclick-event-to-a-command-and-pass-the-clicked-item-to-it/
Let me know if it works

ObservableCollection<T> fires SelectionChanged event if populated in views constructor

I have data saved in the IsolatedStorage in my WP7 app, this data is a ObservableCollection
I then load the data into a observablecollection in the app that is databinded to a listview with a datatemplate
But when I do this (Or just add data to the databound list) in the constructor it fires a ListBox selectionchanged event, so before my app is fully loaded this happens.
I have an event for selectionchanged to show details about the clicked object and this crashes when this happens (Selectedindex is 0 for some reason so object 1 in the loaded list is selected automaticly when loaded)
public partial class MainPage : INotifyPropertyChanged
{
public ObservableCollection<Note> NotesCollection { get; set; }
public CollectionViewSource NotesViewSource;
private readonly IsolatedStorageSettings settings;
// Constructor
public MainPage()
{
InitializeComponent();
NotesCollection = new ObservableCollection<Note>();
settings = IsolatedStorageSettings.ApplicationSettings;
if (settings.Contains("Notes"))
{
NotesCollection = (ObservableCollection<Note>)settings["Notes"];
}
else
{
settings.Add("Notes", NotesCollection);
}
NotesViewSource.View.Refresh();
//var note = new Note("hej", "hej", DateTime.Now, DateTime.Now);
//NotesCollection.Add(note); this also fires the event
NotesViewSource = new CollectionViewSource { Source = NotesCollection };
DataContext = this;
ListBoxNotes.ItemsSource = NotesViewSource.View;
}
my Selectionchanged
private void ListBoxNotesSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListBoxNotes.SelectedIndex == -1)
return;
var note = ListBoxNotes.SelectedItem as Note;
if (!(note is Note)) return;
(Application.Current as App).Note = note;
ListBoxNotes.SelectedIndex = -1;
NavigationService.Navigate(new Uri("/Views/DetailsView.xaml", UriKind.Relative));
}
If you want to add items to the OC before any bindings may fire, then move the following line
InitializeComponent();
after the point where items are added. When this method is called, all the UI is created and bindings are set. You can right-click and go to definition to see it happening.
I would tie into the Loaded event.
Use a private and public. Notice the lowercase for the private.
private ObservableCollection<Note> notesCollection
Make SelectedIndex a public property and bind to it.
When you assign the private side set it to -1;
private int selectedIndex = -1;
By default the selected index is 0. And selected index changed is always going to fire when the app starts. You just need to set selectedIndex = -1 before the event is called.
With SelectedIndex as a public property I would do the logic in the set and not even have a changed event.

Handling double click events on ListBox items in C#

I am trying to do something when double clicking an item in a ListBox. I have found this code for doing that
void listBox1_MouseDoubleClick(object sender, MouseEventArgs e)
{
int index = this.listBox1.IndexFromPoint(e.Location);
if (index != System.Windows.Forms.ListBox.NoMatches)
{
MessageBox.Show(index.ToString());
//do your stuff here
}
}
However, when i click on an item, the event isn't fired. The event is fired if i click in the ListBox below all the items.
I set the DataSource property of the ListBox to IList<MyObject>.
Any ideas?
Tried creating a form with a ListBox with MouseDown and DoubleClick events. As far as I can see, the only situation, when DoubleClick won't fire, is if inside the MouseDown you call the MessageBox.Show(...). In other cases it works fine.
And one more thing, I don't know for sure, if it is important, but anyway. Of course, you can get the index of the item like this:
int index = this.listBox1.IndexFromPoint(e.Location);
But this way is fine as well:
if (listBox1.SelectedItem != null)
...
Works for me, so I assume there might be something about the items in the list (custom? intercepting the event?) or the event is not properly wired up.
For example try this (complete Form1.cs):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public class MyObject {
public MyObject(string someValue) {
SomeValue = someValue;
}
protected string SomeValue { get; set; }
public override string ToString() {
return SomeValue;
}
}
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
var list = new List<MyObject> {
new MyObject("Item one"), new MyObject("Item two")
};
listBox1.DataSource = list;
}
private void listBox1_DoubleClick(object sender, EventArgs e) {
Debug.WriteLine("DoubleClick event fired on ListBox");
}
}
}
With the designer source file (Form1.Designer.cs) containing this:
private void InitializeComponent() {
this.listBox1 = new System.Windows.Forms.ListBox();
... // left out for brevity
this.listBox1.DoubleClick += new System.EventHandler(this.listBox1_DoubleClick);
As a test, create a new Forms base application through the templates, then add just the ListBox and define a class MyObject. See whether you observe the same or a different behavior.
Thank you for all replies. It now works. I solved it, like 26071986 said, with handling double click in the MouseDown handler by checking if e.Clicks is 1. If so, I call DoDragDrop, if not, I call the method that handles double click.
private void MouseDown_handler(object sender, MouseEventArgs e)
{
var listBox = (ListBox) sender;
if (e.Clicks != 1)
{
DoubleClick_handler(listBox1.SelectedItem);
return;
}
var pt = new Point(e.X, e.Y);
int index = listBox.IndexFromPoint(pt);
// Starts a drag-and-drop operation with that item.
if (index >= 0)
{
var item = (listBox.Items[index] as MyObject).CommaDelimitedString();
listBox.DoDragDrop(item, DragDropEffects.Copy | DragDropEffects.Move);
}
}
Here's what I used in the MouseDoubleClick event.
private void YourMethodForDoubleClick(object sender, MouseButtonEventArgs e)
{
Type sourceType = e.OriginalSource.GetType();
if (sourceType != typeof(System.Windows.Controls.TextBlock)
&& sourceType != typeof(System.Windows.Controls.Border))
return;
//if you get here, it's one of the list items.
DoStuff();
...
}
John: then it works. But i figured out that the event isn't fired because I am also handling the MouseDown event. I tried to remove the MouseDown handling, and then it works. Is there a smooth way to handle both those events? If not, I just have to find some other way to catch a double click through the MouseDown event.

Categories