WPF Listview SelectionChanged event - wpf

I have a ListView binding to an ItemsSource and the SelectionChanged event is firing on the load/databound events? I assume that it is because a 'default' items ie index 0 is selected.
How can I disable this?

The listView should not fire the SelectionChange if you only set the ItemsSource property. However if you bind the SelectedIndex property to a property of your dataContext object the selection will move to the index that is specified by the binded property.
this doesn't fires the Selector_OnSelectionChanged event when the page loads:
<ListView SelectionChanged="Selector_OnSelectionChanged"
ItemsSource="{Binding Path=Items}"
></ListView>
but this does:
<ListView SelectionChanged="Selector_OnSelectionChanged"
SelectedIndex="{Binding Path=SelectedIndexValue}"
ItemsSource="{Binding Path=Items}"
></ListView>
because the SelectedIndex is set to the SelecteIndexValue through binding.
To avoid this and still keep the bindings in your markup set the SelectedIndexValue of your dataContext object to -1 before binding (Before InitializeComponent() is called in your form constructor).
Hope this helps.

thanks for the responses.
When I put a breakpoint on the SelectionChanged event, it breaks proceedings there before the screen is fully loaded. You will also see that the first row is 'selected' afterwards on the list. I am not binding to a SelectedIndexValue as you can see in the code. The DataContext for the list is a ReadonlyCollection
In my SelectionChanged event as you can see I notify other objects to be loaded with data relating to the selected item. I only want this to happen when one is selected but not a default one to be set. I have to of these ListViews representing similar data but on loaded none must have an item selected.
I have noticed that the default Selected index is set to -1 on the properties window for the Listview. I can even set this is code on the List_Loaded event, but by then the first SelectionChanged has happened already.
<ListView PreviewMouseDown="ActiveCasesView_MouseDown" x:Name="ActiveCasesView"
DataContext="{StaticResource ActiveCasesViewSource}"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource CasesItemTemplate}"
SelectionMode="Single"
SelectionChanged="ActiveCasesView_SelectionChanged"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto" >
</ListView>
private void ActiveCasesView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (mouseClicked)
if (e.AddedItems.Count > 0)
App.Messenger.NotifyColleagues(App.MSG_SELECT_ACTIVE_CASE, ((CaseViewModel)ActiveCasesView.SelectedItem).CaseNumber);
}
I added the PreviewMouseDown to set an indicator that I have clicked on the listview in the SelectionChanged event. This does help but I'm not convinced that its the best solution.
Thanks
Petrus

I don't know if you still need help with this, but I solved this problem by making a variable that tracks the selectedindex, in my case -- when initially bound it's always 0 so it's a little easier for me to do, however if you inform the viewmodel of the appropriate index, I simply added a
ComboBox box = e.OriginalSource as ComboBox;
if (_previousIndex == cb.SelectedIndex) return;
//do stuff you need to do with a new SelectedIndex

You can try to set the SelectedIndex property to -1 in your binding but this also is not an elegant solution.
<ListView PreviewMouseDown="ActiveCasesView_MouseDown" x:Name="ActiveCasesView"
DataContext="{StaticResource ActiveCasesViewSource}"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource CasesItemTemplate}"
SelectionMode="Single"
SelectionChanged="ActiveCasesView_SelectionChanged"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
**SelectedIndex="-1"**>
</ListView>
I tried to reproduce your behavior but without success.
What is the Type of the ItemsSource collection that you are binding to?

you can use window loaded event to block the action
bool loaded = false;
window.Loaded += new RoutedEventHandler(MainWindow_Loaded);
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
windowLoaded = true;
}
private void ActiveCasesView_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if(!loaded)
return ;
//do actions here ....
}

Related

Binding Visibility to Yes/No ComboBox with Converters

I've got a form that gets given a datarow from a dataset to bind all its elements. One of them is a bool, but I want that bool to be represented by by a Yes/No combo box. So I did this and it works nicely.
I also want to bind the visibility of a couple elements to this bool field. When the form loads, the initial setting of the visibility works. When I change the combobox selection, the ConvertBack() method of the ComboBox gets called (i.e. it's setting the bound value). But the other elements that have their visibility bound to that same field don't get updated. I set breakpoints in the Conversion methods and they never get called like they do when the form loads.
Here's the relevant XAML:
<ComboBox SelectedIndex="{Binding Path=[Adequate], Converter={StaticResource b2iConverter}}" Name="cb_Adequate" >
<ComboBoxItem>Yes</ComboBoxItem>
<ComboBoxItem>No</ComboBoxItem>
</ComboBox>
<Label Content="Reason:"
VerticalAlignment="Center"
Visibility="{Binding Path=[Adequate],
Converter={StaticResource b2vConverterInverse}}"/>
<TextBox Text="{Binding Path=[NotAdequateReason]}"
Visibility="{Binding Path=[Adequate],
Converter={StaticResource b2vConverterInverse}}"/>
"Adequate" is the bool field
b2iConverter is just booleanToIndexConverter (from the above link)
b2vConverterInverse is just an inverted boolean to visibility converter (I want the label and textbox shown when Adequate is FALSE or 0).
Thanks for any help. I can post more code if needed, I figure the problem is in the XAML...
EDIT: Apparently it's not possible with XAML (see Greg's post below), so I just do it in code:
private void cb_Adequate_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Visibility vis = (cb_Adequate.SelectedItem as ComboBoxItem).Content.ToString() == "Yes" ? Visibility.Collapsed : Visibility.Visible;
label_Reason.Visibility = tb_AdequateDesc.Visibility = vis;
}
If you want your UI elements to change state when a data property changes, you need to implement INotifyPropertyChanged on your data class.
This means that you can't use the DataRow for your purposes. You'll have to create a new class, then at run time populate it with values from the DataRow and then bind that object to your view.

How to bind to ItemsSource in DataTemplate?

in my Silverlight 4 application, I want to use an AutoCompleteBox from the Silverlight Toolkit. I use this AutoCompleteBox in a listbox, which items are defined in a DataTemplate
<ListBox x:Name="ListBoxCharacteristics">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="{StaticResource SolidBrushVeryLightGrey}">
<sdk:AutoCompleteBox Text="{Binding Name, FallbackValue=[None], Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" IsTextCompletionEnabled="True"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
To provide the suggested items to the AutoCompleteBox, I need to bind it on the ItemsSource property. The idea was to create the list in the constructor and then bind it to the AutoCompleteBox. But the AutoCompleteBox is just in the DataTemplate, so I cannot reference it directly.
Any idea, how to achieve that? I thought about something like "ItemsSource="{Binding SuggestionList"} but that would mean I'd need to create this list as a Property for the class of the objects that I bind to the list, which would be a big overhead.
Thanks in advance,
Frank
I subscribed to the GotFocus-Event of the AutoCompleteBox and bind the list there. Thanks to Nathan and Shelby for putting my head towards the right direction!
You should be able to traverse the tree by referencing the listbox in code something like:
(ListBoxCharacteristics.ItemTemplate.VisualTree as AutoCompleteBox).ItemSource = your_new_list;
but you might be better off creating the Binding in that constructor:
Binding B = new Binding();
B.Mode = BindingMode.TwoWay;
B.NotifyOnValidationErrors = true;
B.FallbackValue = "[None]"; // not sure about this one
B.ValidatesOnExceptions = true;
B.Source = your_new_list;
(ListBoxCharacteristics.ItemTemplate.VisualTree as AutoCompleteBox).SetBinding(AutoCompleteBox.TextProperty, B);
ListBoxCharacteristics.ItemTemplate.VisualTree should give you that the root node of your ItemsTemplate and you should be able to cast that object to your AutoCompleteBox. If you had further embedded elements you would want to cast and attempt to get a container property for that element to continue further down into the template.
try this. This has worked for me a dozen times.
AutoCompleteBox autoComplete = Listbox.ItemTemplate.GetVisualDescendants().OfType<AutoCompleteBox>().SingleOrDefault();
autoComplete.ItemsSource = theListYouHavePopulated;
that is, of course, if there is only one AutoCompleteBox in the listbox template, if it comes first, then try,
FirstOrDefault();
at the end of your query.
Let me know if you need anything else.
You can set the ItemsSource property of the AutoCompleteBox in a handler of its Loaded event (you'll get the AutoCompleteBox itself as the sender of the event).
xaml:
<sdk:AutoCompleteBox ...
Loaded="autoCompleteBox_Loaded"/>
code behind:
private void autoCompleteBox_Loaded(object sender, RoutedEventArgs e)
{
var autoCompleteBox = sender as AutoCompleteBox;
autoCompleteBox.ItemsSource = SuggestionList; //the list you want to bind to
}
Hope this helps

How to access ItemsControl's ItemsSource element from the ItemsControl's Item's code behind?

In my main view I have an ItemsControl which is bound to a collection of objects:
<ItemsControl ItemsSource="{Binding Path=Concepts}"
ItemTemplate="{StaticResource ActivationLevelTemplate}"
/>
Where the ActivationLevelTemplate is just another view:
<DataTemplate x:Key="ActivationLevelTemplate">
<view:ConceptActivationView Height="50"/>
</DataTemplate>
In this view there is a text block, bound to a property of an object from the collection mentioned above. The property is displayed correctly, and now I need to access other properties of the same object from the view's code behind. It seems trivial but I could not get it working.
<TextBlock Text="{Binding Path=Name}"
HorizontalAlignment="Center"
/>
<d3:Plotter2D Name="Plotter"/>
The best thing I came across was ItemContainerGenerator but it does not seem to be what is needed.
What is important is the context in which you try to access that object. If you for example deal with an event inside the DataTemplate you can easily get the object from the DataContext of the sender (has to be a FrameworkElement), e.g. if i were to handle a button click:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = (FrameworkElement)sender;
var employee = (Employee)button.DataContext;
//...
}
In fact if your whole view is inside the DataTemplate you can get the object directly from the View's DataContext as well.
You should be able to iterate through the items in the ItemsControl and get all the properties you need. Give the ItemsControl a name so you can address it in the code behind:
<ItemsControl Name="itemsControl" ... />
Then in code behind
foreach (YourItem item in itemsControl.Items)
{
// your logic...
}
If you need a specific item you could try CurrentItem or GetItemAt() instead
itemsControl.Items.CurrentItem
// or
itemsControl.Items.GetItemAt()

How do I set focus to a control within the ItemTemplate when the ListBoxItem is selected?

I have a ListBox which presents objects using a DataTemplate. The DataTemplate contains a TextBox. When the user selects an item in the ListBox, I would like to set focus to the TextBox for the selected item.
I have been able to partially achieve this by handling ListBox.SelectionChanged, but it only works when the user clicks on the ListBox to select the item - it does not work if the user tabs into the ListBox and uses the arrow keys to select the item even though TextBox.Focus() is invoked.
How can I set focus to the TextBox when the user uses the keyboard to select an item?
Here is the markup for the ListBox:
<ListBox Name="lb1" SelectionChanged="ListBox_SelectionChanged" ItemsSource="{Binding Items}" >
<ListBox.ItemTemplate>
<DataTemplate >
<TextBox></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is the handling code:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBoxItem lbi = (ListBoxItem)this.lb1.ItemContainerGenerator.ContainerFromItem(this.lb1.SelectedItem);
Visual v = GetDescendantByType<TextBox>(lbi);
TextBox tb = (TextBox)v;
tb.Focus();
}
One way to do this is to replace the tb.Focus() from your SelectionChanged event handler with:
tb.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(delegate()
{
tb.Focus();
}));
This works because calling BeginInvoke on the dispatcher causes the specified code to run when the dispatcher is available - i.e. after WPF has finished handling the events internally.
The catch is that, after you first press down arrow when a list item has focus, the next list item will became selected, its textbox will become focused and you will not be able to move selection again with the down arrow. You'll probably want to write some code to handle this case too.

WPF Listbox - change itemsource of control in datatemplate

I have a listbox with a datatemplate that holds a number of controls bound to my collection.
What i want is to change the itemsource for one of these comboboxes dependant upon the value selected in one of the other comboboxes in the same row. I don't want to change the itemsource for all corresponding comboboxes within the rest of the rows in the listbox.
How do I get a handle on the control in the selected row only.
Is this something that is easier to try doing witht the WPF datagrid?
Thanks.
This is actually easier with the ListBox, as the DataTemplate defines all the controls for a row.
I think the easiest way is to use a converter on a binding. You will bind your second ComboBox's ItemsSource to the SelectedItem of the first ComboBox:
<myNamespace:MyConverter x:Key="sourceConverter" />
<StackPanel Orientation="Horizontal>
<ComboBox x:Name="cbo1" ... />
...
<ComboBox ItemsSource="{Binding SelectedItem, ElementName=cbo1, Converter={StaticResource sourceConverter}}" ... />
...
</StackPanel>
Note that if you need additional information from the DataContext of the Row, you can make it a MultiBinding and an IMultiValueConverter, and pass in the DataContext easily by doing:
<MultiBinding Converter="{StaticResource sourceConverter}">
<Binding />
<Binding Path="SelectedItem", ElementName="cbo1" />
</MultiBinding>
Then, in your converter class, do whatever it is you have to do in order to get the correct items source.
Get the SelectionChanged event of that paticular combobox and set the Itemsource of the other combobox inside the event.
private void cmb1SelectionChanged(object sender, SelectionChangedEventArgs e)
{
cmboBox2.ItemSource = yourItemSource;
}
Also it is better to get the SelectionChaged event of listview and handle it.
private void OnlistviewSelectionChanged( object sender, SelectionChangedEventArgs e )
{
// Handles the selection changed event so that it will not reflect to other user controls.
e.Handled = true;
}

Resources