views:

407

answers:

5

OK, this must be a duplicate question, but I cannot find the answer: I have a listbox that is databound to a Collection. By default Items[0] is selected. How can I prevent this in order to ensure that the SelectionChanged event is raised any time I click a ListBoxItem?

EDIT: I had already dismissed the SelectedIndex=-1 route, but I tried again: Setting the SelectedIndex to -1 in the Listbox's constructor (or as an attribute in XAML) does not work. It seems that the listbox is populated after the initialization and the selectedindex will become 0 after all.Same story for setting the SelectedItem to null;

I tried this:

<ListBox ItemsSource="{Binding Value}" 
         SelectionChanged="ListBox_SelectionChanged"
         IsSynchronizedWithCurrentItem="True"
         Loaded="ListBox_Loaded">
</ListBox>

with:

 private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if(e.AddedItems.Count==0)return;//Prevents stackoverflow! ;-)
        ((ListBox)e.OriginalSource).SelectedItem=null;
    }

private void ListBox_Loaded(object sender, RoutedEventArgs e)
{
    ((ListBox) sender).SelectedItem = null;
}

This works, BUT it inserts a blank line on top of the items that the listbox displays, which is very ugly.... In my particular case I could solve the problem by removing the IsSynchronizedWithCurrentItem attribute.

But I can think of many scenarios in which this would not be acceptable.

The above statement is nonsense: either you want to make use of master-detail binding and set the IsSynchronizedWithCurrentItem to true, or you don't. It is rather unlikely that you want to make use of master-detail binding and then have no currently selected item in your listbox all the time

+1  A: 

You need to set the Index to -1, this tells the listbox not to select anything. In the SelectionChanged Event, set the listbox's Index property to -1.

Hope this helps, Best regards, Tom.

tommieb75
+1  A: 
listbox.selectedIndex = -1

That should de-select everything (since index -1 doesn't exist), but as far as I'm aware unless you explicitly set the default selectedItem the listview is generated with nothing selected, so it's possible that SelectedIndex has been inadvertently set at some point. Note that SelectedIndex can be set in XAML as well.

MoominTroll
+1  A: 

How about dumping the SelectionChanged event handler and data binding a dependency property or notifying property to the SelectedValue instead?

Danny Varod
Well that was an interesting excercise but it does not basically change the behavior. Moreover, setting the SelectedValue to null in the viewmodel's PropertyChangedCallBack does not work anymore, it seems that the clicked item is selected anyhow.
Dabblernl
+2  A: 

After running a few tests, ListBox's default behavior is to not pre select an item in the list. Removing IsSynchronizedWithCurrentItem="True" will ensure the listbox doesn't start pre selected and the SelectionChanged event will be raised when the user clicks on any item.

If you do set IsSynchronizedWithCurrentItem to true it means the selectedIndex will always equal the index of the ListBox.Items.CurrentItem of ListBox.Items. ListBox.Items always has a CurrentItem and cannot be set to -1 or null due to CurrentItem or CurrentIndex being readonly.

Hope that helps J

edit... have just read

"In my particular case I could solve the problem by removing the IsSynchronizedWithCurrentItem attribute. But I can think of many scenarios in which this would not be acceptable."

In which case i don't think it's possible for the reasons stated above.

James Hay
Thanks, at leat it explains the why of the problem!
Dabblernl
+1  A: 

Use CollectionView as an ItemsSource for your list box. A fresh WPF project with no item initially selected:

XAML:

<Window x:Class="ListBoxDataBinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
    <ListBox 
        ItemsSource="{Binding Path=Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" 
        SelectionChanged="ListBox_SelectionChanged"
        IsSynchronizedWithCurrentItem="True"
        >
    </ListBox>
</Grid>
</Window>

cs:

public partial class Window1: Window {
    public IEnumerable Value {
        get { return (IEnumerable)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(IEnumerable), typeof(Window1), new UIPropertyMetadata(null));


    public Window1() {
        InitializeComponent();
        var view = new TheCollectionView();
        view.MoveCurrentTo(null);
        this.Value = view;
    }

    private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
        MessageBox.Show("Selection changed");
    }
}

public class TheCollectionView: CollectionView {
    public TheCollectionView(): base(new TheCollection()) {
    }
}

public class TheCollection: List<string> {
    public TheCollection() {
        Add("string1");
        Add("string2");
        Add("string3");
    }
}
Stanislav Kniazev
Very nice, the code shows some approaches that I was unfamiliar with too. Thanks!
Dabblernl
Sorry about the code - it's just quick and dirty way to demonstrate the solution. The code itself is quite wrong in many aspects.
Stanislav Kniazev