WPF ListBox in Popup setting null SelectedItem on PopupClose - c#

I have a WPF Popup, which is structured as below (with some senstitive stuff removed)...
<Popup>
<Border>
<StackPanel>
<ListBox
ItemSource="{Binding X}"
SelectedItem="{Binding Y}"
IsSynchronizedWithCurrentItem="True"/>
<Separator/>
<MenuItem Command="{Binding Path=EditModeCommand}"/>
</StackPanel>
</Border>
</Popup>
The ListBox works as expected, the list populates from the binding, and the selected item feeds back to the collection correctly.
However when the MenuItem fires its command, the SelectedItem binding fires as well, setting the SelectedItem to null. Is there a way to preserve the SelectedItem when the listbox is not the focus of the click?

Try setting the SelectedItem property before the ItemSource property in the declaration.

Related

Getting ListBox multiselect Items with Binding in WPF

Is there any way of getting the items of a multiselect in a listbox without using the code behind, but whith just the binding?
I know that I can do a foreach on the code behind etc.. But I'm guessing if there is a cleaner solution with just the binding between XAML and ViewModel.
On some of the listboxes the IsSelected property is used to set multiple items selected at the page loading.
Thanks for the help.
You can send the SelectedItems as a command parameter. For example, you can get the listbox's SelectedItems in a button's command like this.
<ListBox x:Name="listbox" ItemsSource="{Binding MyList}" SelectionMode="Multiple"/>
<Button x:Name="btn" Command="{Binding MyCommand}" CommandParameter="{Binding SelectedItems, ElementName=listbox}" Content="Get Selected Items"/>

Handle Multiselect in ListView (WPF + MVVM) [duplicate]

I have a ListBox that I populate dynamically via a binding (this is defined in a DataTemplate, which is why the binding is somewhat unusual):
<ListBox SelectionMode="Extended" ItemsSource="{Binding DataContext.ResultList, RelativeSource={RelativeSource AncestorType=Window}}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Object}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Each ListBoxItem's IsSelected property is bound to an IsSelected property on a custom object.
When I select individual ListBoxItems, the binding works properly - the custom object's IsSelected property is updated in my ViewModel. However, if I select all of the ListBoxItems with a Ctrl+A command, only the currently visible ListBoxItems (those that are currently in my scrolling viewport) update their ViewModel bindings. On the frontend, all the ListBoxItems appear to be selected, and the ListBox.SelectedItems.Count property on the container ListBox shows that all items are selected.
Furthermore, as I scroll through the ListBox after selecting all ListBoxItems with Ctrl+A, the bindings are successfully updated when each ListBoxItem is scrolled into view.
Why does this binding seem to be only partially working? Is there a better way to handle the binding of the IsSelected property when large numbers of ListBoxItems can be selected simultaneously?
Edit:
This behavior doesn't happen exclusively with the Ctrl+A command - I get the same results when selecting all the items using a shift+click.
I think the behavior you're seeing is to due to VirtualizingStackPanel.IsVirtualizing which is True by default when binding to ItemsSource of ListBox
if you for eg set your ListBox such as:
<ListBox VirtualizingStackPanel.IsVirtualizing="False" SelectionMode="Extended" ItemsSource="{Binding DataContext.ResultList, RelativeSource={RelativeSource AncestorType=Window}}">
or
<ListBox ...>
...
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
then you should see all your bound items have their IsSelected updated accordingly with Ctrl+A or Shift + ...
Properties such as Count of the collection even with virtualization would report the correct value to accommodate for things like computing the required ScrollBar.Height. Items which are outside the View-port do not get rendered hence no bindings are in effect on them until they actually get used.

How to bind to a source inside a ListBox different from the ItemsSource already specified

I have a ListBox inside a HubSection, whose Items are bound to a class "players" added to my DefaulViewModel via code behind.
First I simply put a TextBox bound to the property "PlayerName" of my class "players".
Now I would like to add a ComboBox with some items that are NOT part of the class players.
Is it possible ? I thought that definind an ItemsSource in the ComboBox would sort of override the ItemsSource of the ListBox, but nothing displays.
The DataContext of the whole page is defined like so:
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
Then the HubSection is like so:
<HubSection x:Name="HubSec1">
<DataTemplate>
<ListBox x:Name="ListBox1" ItemsSource="{Binding players}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=PlayerName, Mode=TwoWay}"/>
<ComboBox ItemsSource="{Binding Path=ListOfElements}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</HubSection>
If I define the ComboBox in the same way but outside the ListBox, it will display the string elements of "ListOfElements" properly.
But in this ListBox, the ComboBox is empty. So my guess is that having defined an ItemsSource for the ListBox, it is not possible to override it.
I have tried to define a DataTemplate but was not successful doing so, but it might be the good solution (and I did not proceed properly)
What am I missing ?
Edit :
The ComboBox items is an ObservableCollection. It is not part of the "players" class.
Here is how I added these elements to the DefaultViewModel
DefaultViewModel.Add("players", players);
DefaultViewModel.Add("MyItemsList", ListOfElements);
You can walk up the visual tree and bind to an ancestors datacontext:
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
EX:
{Binding Path=ListOfItems, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}
that should give you the datacontext that the listbox has, so assuming your ListOfItems exists in that data context.
Or you can name your control, and then bind to its datacontext by element name:
{Binding ElementName=mySourceElement,Path=ListOfItems}
It can be a little bit tricky to create a good working binding in Windows Apps. A widely used work around is to use the Tag property.
<ListBox x:Name="ListBox1" ItemsSource="{Binding players}" Margin="0,184,0,0" Tag="{Binding Path=ListOfElements}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=PlayerName, Mode=TwoWay}"/>
<TextBox Text="{Binding Path=Tag, ElementName=ListBox1}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A binding to an element wirh the specific name will work always. And the ListOfElements should be in the scope of the ListBox so you can use the Tag property as a proxy. If you need to bind more than one property, you can also use dummy XAML elements:
<Border Tag="{Binding ...}" Name="dummy1"/>

WPF ListBox selected item not changed while using DataTemplate

My listbox is binded to an items source and selecteditem property is also binded. Most of my work is being done in selecteditem property. Actually I have two listboxes, for each item in first list box there are some child items in the collection. Against all the items that are selected in first listbox, their child items are supposed to be added in the second list box.
Problem is Selecting the item(by checking the checkbox) does not raise the SelectedItem property changed
XAML for my listbox controls are
<ListBox SelectionMode="Multiple" ItemsSource="{Binding Charts,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedChart, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding ChartName}" VerticalAlignment="Center" IsChecked="{Binding IsChartSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox ItemsSource="{Binding Tracks, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsTrackSelected}"/>
<TextBlock Margin="5 0 0 0" VerticalAlignment="Center" Text="{Binding TrackName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
My Chart Selection Changed property in view model is
public ChartSourceForMultipleSelection SelectedChart
{
get { return _selectedChart; }
set
{
_selectedChart = value;
ChartSelectionChanged();
NotifyPropertyChanged("SelectedChart");
}
}
This binding doesn't make any sense:
<CheckBox IsChecked="{Binding IsTrackSelected,
RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" />
This attempts to bind to a property IsTrackSelected on the outer ListBoxItem, but no such property exists. It doesn't look like you need a RelativeSource here, provided IsTrackSelected is a property of the underlying data item.
Also, I'm not sure why you're a ListBox for the tracks collection; it looks like your concepts of track selection is separate from the ListBox concept of a selected item. Why not use a simple ItemsControl instead?
As to your primary problem, the CheckBox in the item template is eating the mouse/focus events that would normally tell the ListBox to select the parent ListBoxItem when clicked. You can manually update the selection state of a ListBoxItem whenever its inner CheckBox receives keyboard focus.
Add GotKeyboardFocus="OnChartCheckBoxGotKeyboardFocus" to the CheckBox your chart list item template, give the chart list box a name, e.g., x:Name="ChartsList", and write the handler in the code behind:
private void OnChartCheckBoxGotKeyboardFocus(
object sender,
KeyboardFocusChangedEventArgs e)
{
var checkBox = sender as CheckBox;
if (checkBox == null)
return;
var listBoxItem = ItemsControl.ContainerFromElement(ChartsList, checkBox)
as ListBoxItem;
if (listBoxItem != null)
listBoxItem.IsSelected = true;
}

WPF ComboBox doesn't initially select correct item

I have a WPF ComboBox that I bind to a list of custom objects, each of which contains an IsSelected property. This property is bound to its corresponding ComboBoxItem's IsSelected property. When initially creating the list, I set a particular object's IsSelected property to true, with the expectation that this item will be selected when the ComboBox is initially shown. However, the ComboBox always shows the first item in the list as selected, regardless of which object in its bound list has its IsSelected property set to true.
I know that the binding is working properly, because when I click the ComboBox and it expands to show all the available options, the ComboBox updates to show the correct selected item.
Here's the XAML the defines my ComboBox. It's in a DataTemplate, and is dynamically added/removed from the page, if that is at all relevant:
<ComboBox ItemsSource="{Binding DataContext.YearList, RelativeSource={RelativeSource AncestorType=Window}}" IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Object}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Try removing IsSynchronizedWithCurrentItem="True". That setting is telling the control to use the CurrentItem on the bound collection's generated ICollectionView, which defaults to the first item in the list. If you want to see what its doing you can play with this view in code by using CollectionViewSource.GetDefaultView(YearList) and then looking at or changing the current item.
Try binding to SelectedItem property, ComboBox will not select first item by default but your Source of data binding:
<ComboBox ItemsSource="{Binding YearList, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding <YourDefinedSelectedItemProperty>, RelativeSource={RelativeSource AncestorType=Window}}">
Hope this helps.

Categories