ListPicker SelectionChanged Event Called Multiple Times During Navigation - c#

I have a SelectionChanged event in a ListPicker within one of my application Pages that fires multiple times before the page is loaded. This is really inconvenient for me as when an item is selected, a MessageBox is displayed (and other actions will be performed). The MessageBox is displayed twice every time the page is NavigatedTo. How can I fix this?
XAML
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme"
ItemTemplate="{StaticResource PickerItemTemplate}"
SelectionChanged="ThemeListPicker_SelectionChanged"/>
XAML.CS
private void ThemeListPicker_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if(ThemeListPicker.SelectedIndex != -1)
{
var theme = (sender as ListPicker).SelectedItem;
if (index == 0)
{
Settings.LightTheme.Value = true;
MessageBox.Show("light");
}
else
{
Settings.LightTheme.Value = false;
MessageBox.Show("dark");
}
}
}

well, that's how a listpicker behaves, what best you can do is instead of making ThemeListPicker_SelectionChanged make a parent stackpanel inside the datatemplate somewhat like this
<Listpicker.ItemTemplate>
<DataTemplate x:Name="PickerItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock/>
</StackPanel>
</DataTemplate>
</Listpicker.ItemTemplate>
<Listpicker.FullModeItemTemplate>
<DataTemplate x:Name="PickerFullModeItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock/>
</StackPanel>
</DataTemplate>
<Listpicker.FullModeItemTemplate>
now use this tap stk_Tap to do your action as, this event would also get called every time the selection changed gets called but, it wont exhibit the buggy behavior like that of selection changed event.
hope this helps.

Attach the SelectionChanged event after the ListPicker is Loaded.
...
InitializeComponent();
YourListPicker.Loaded += YourListPicker_Loaded;
...
private void YourListPicker_Loaded(object sender, RoutedEventArgs e)
{
YourListPicker.SelectionChanged += YourListPicker_SelectionChanged;
}

Related

Items.Count = 0 in SelectionChanged when there are items

I want to display some custom content (using datatemplate) on button click:
<ContentControl x:Name="content" />
<Button Content="Test" Click="button_Click" />
Button shows/hides content like this
VM _vm = new VM();
void button_Click(object sender, RoutedEventArgs e) =>
content.Content = content.Content == null ? _vm : null;
Here is datatemplate:
<DataTemplate DataType="{x:Type local:VM}">
<ListBox ItemsSource="{Binding Items}" SelectionChanged="listBox_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
Event handler:
void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
Title = "Items: " + ((ListBox)sender).Items.Count;
Viewmodel:
public class VM
{
public List<Item> Items { get; } = new List<Item> { new Item(), new Item(), new Item() };
}
public class Item
{
public bool IsSelected { get; set; }
}
The problem: when datatemplate is unloaded then SelectionChanged for ListBox event is rised with no items.
I do not want this event. I don't want to see "Items: 0" after selecting something and unloading datatemplate.
Question: what is happening and how can I prevent this from happening?
Note: this is very short and simplified MCVE, i.e. not everything is pretty, though there are key points: datatemplate with ListBox inside, which uses IsSelected binding and I need to get rid from that SelectionChanged event at unloading.
Call stack:
This is working exactly as designed. You made a selection by clicking an item in the list box. When the template is unloaded, the ItemsSource binding is disconnected, and the items source becomes empty. At that point, the current selection is no longer valid (the item doesn't exist in the items source), so the selection is cleared. That's a selection change: the selection went from something to nothing. The event is expected to be raised under these circumstances.
It's rarely necessary to subscribe to SelectionChanged. It's usually better to bind the SelectedItem to a property on your view model. Whenever the selection changes, that property will be updated. Instead of responding to the SelectionChanged event, you can respond to that property changing.
This approach nicely avoids the issue you're seeing. Once the template is unloaded, the SelectedItem binding will be disconnected, so your view model won't be updated anymore. Consequently, you won't see that final change when the selection is cleared.
Alternate solution for multiple selections
If your ListBox supports multiple selections, you can continue subscribing to SelectionChanged. However, don't query listBoxItems; instead, scan through _vm.Items and see which items have IsSelected set to true. That should tell you the actual selection, and the results should not be affected by the template being unloaded.
You can also determine that the template was unloaded by checking whether (sender as ListBox)?.ItemsSource is null in your handler. However, this should not be necessary.
I think this is occurring because you are always overwriting the content:
VM _vm = new VM();
void button_Click(object sender, RoutedEventArgs e) =>
content.Content = content.Content == null ? _vm : null;
Change it to this so the list only gets assigned once, so it only gets assigned once.
void button_Click(object sender, RoutedEventArgs e)
{
if (content.Content == null )
{
content.Content = _vm;
// I also recommend you add the event handler for the ListBox here so it's not fired until you have content.
}
}
I think listBox_SelectionChanged when you unload the list this event is fired, because section actually changed, Check item count there, and if it is 0 set the title to default.
void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
Title = (((ListBox)sender).Items.Count > 0)? "Items: " + ((ListBox)sender).Items.Count: "Your title";

ScrollIntoView property not working for gridview in windows 10 universal app

I tried this below code:
XAML Code:
<GridView x:Name="listgrid">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="15,15,0,0">
<Image Height="170" Width="170" Source="{Binding}"></Image>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
Cs code:
for (int i = 1; i < 50; i++)
{
list.Add("ms-appx:///Images/A-aa.jpg");
}
listgrid.ItemsSource = list;
listgrid.ScrollIntoView(listgrid.Items[30]);
I above code to scroll view to my selected item, but it's not showing any changes, i think i used this property in a wrong way any one please help me to scroll to gridview position.
I have replied your same question in MSDN: https://social.msdn.microsoft.com/Forums/windowsapps/en-US/d0a772b3-80b9-4a11-92a9-89963c29a52f/scrollintoview-property-not-working-for-gridview-in-windows-10-universal-app?forum=wpdevelop
You need to have something more to distinguish items, for example, give every image a name since items you bind to GridView are same, ScrollIntoView default find the first one.
And commonly you need to set a height property for the GridView.
For more complex requirements, there is a good thread you can reference:
Windows 10 ScrollIntoView() is not scrolling to the items in the middle of a listview
Try to subscribe on Loaded event and call ScrollIntoView inside event handler:
listgrid.Loaded += Listgrid_Loaded;
....
private void Listgrid_Loaded(object sender, RoutedEventArgs e)
{
listgrid.ScrollIntoView(listgrid.Items[30]);
}
Try this
private void Gridview_Loaded(object sender, RoutedEventArgs e)
{
if (ShellPage.Current.SelectedRecItem != null)
{
this.gridview.SelectedItem = ShellPage.Current.SelectedRecItem;
this.gridview.UpdateLayout();
this.gridview.ScrollIntoView(ShellPage.Current.SelectedRecItem);
}
}

WPF TabControl no selected Item on start

I am using a WPF tabcontrol to display items which are bound from a viewmodel.
By default on start the first item of the list is selected but I want no item to be selected on start. I can set the SelectedItem in the OnSelectionChanged event to null then no item is selected on start but then it is no longer possible to manually select a item.
public partial class ProjectScopeMain : Window
{
private bool firstStart = true;
public ProjectScopeMain()
{
this.Initialized += this.ProjectScopeMain_Initialized;
this.InitializeComponent();
}
private void ProjectScopeMain_Initialized(object sender, System.EventArgs e)
{
this.TabControlSettings.SelectionChanged += TabControlSettingsOnSelectionChanged;
}
private void TabControlSettingsOnSelectionChanged(object sender, EventArgs e)
{
this.TabControlSettings.SelectedItem = null;
}
private void ButtonCreate_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.Close();
}
}
My XAML Code. SelectedIndex=-1 does not work
<customControls:TabControl x:Uid="tabControlSettings" x:Name="TabControlSettings"
prism:RegionManager.RegionName="{x:Static infrastructure:RegionNames.ProjectScopeTabsRegion}"
TabStripPlacement="Left" Style="{DynamicResource TabControlStyle}"
ItemContainerStyle="{DynamicResource TabItemVerticalProjectScopeStyle}" SelectedIndex="-1"/>
I don't believe the tab control lets you have nothing selected. An easy work around for this is to create an empty tab with a collapsed visibility, and navigate to it when you would otherwise wish to clear your tab control. This will result in a tab's content being shown (which in this case is empty) and no header being present.
<TabControl Name="MyTabControl" SelectedIndex="0">
<TabItem Header="" Visibility="Collapsed">
<!--There's nothing here-->
</TabItem>
<TabItem Header="Item 1">
<TextBlock Text="Some item 1" />
</TabItem>
<TabItem Header="Item 2">
<TextBlock Text="Some item 2" />
</TabItem>
</TabControl>
You could 'clear' it with:
MyTabControl.SelectedIndex = 0;
Since you wish to bind the child items, I would imagine you will need to combine the children in a resource first.
You can deselect any TabItem by setting its IsSelected property to false. The content of TabControl will be blank once none of its TabItems are selected.
Subscribe to the Loaded event of the TabControl then set SelectedItem to null:
<TabControl Loaded="TabControl_OnLoaded">
<TabItem Header="page 1" Content="page 1" />
<TabItem Header="page 2" Content="page 2" />
</TabControl>
private void TabControl_OnLoaded(object sender, RoutedEventArgs e)
{
((TabControl)sender).SelectedItem = null;
}
It will work even if you bind SelectedItem to a property in your ViewModel, but there will be a moment after loading the page that you'll get a non-null value there, and after that null, but from what I've seen it didn't cause any weird flickering or anything so it's probably fine.
Tested on .NET Framework 4.8
You can select nothing by setting SelectedTab property nullptr.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.tabcontrol.selectedtab?view=net-5.0
A TabPage that represents the selected tab page. If no tab page is selected, the value is null.

How do I handle a selected item of LongListMultiSelector?

I have list of items in LongListMultiSelector - how to handle a selected item?
My LongListMultiSelector xaml:
<tkit:LongListMultiSelector Name="longlist" SelectionChanged="longlist_SelectionChanged">
<tkit:LongListMultiSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" FontSize="32" Tap="TextBlock_Tap"/>
</DataTemplate>
</tkit:LongListMultiSelector.ItemTemplate>
</tkit:LongListMultiSelector>
TextBlock tap event handler code:
private void TextBlock_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
var itemTapped = (sender as FrameworkElement).DataContext as Book;
}
LongListMultiSelector SelectionChanged event handler code:
private void longlist_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
I found part of solution here, however, The problem if at least one item is selected, then textblockTap event doesn't handle - longlist_SelectionChanged event handles everything. How can i fix that?
Once you are using LongListMultiSelector, the SelectionChanged event is fired when item is added or removed. If you want to perform the action regardless item is added/removed, I've managed to do it like this (for a simle string):
private void longlist_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string selectedItem = String.Empty;
if (e.AddedItems.Count > 0) selectedItem = e.AddedItems[0] as string;
else selectedItem = e.RemovedItems[0] as string;
MessageBox.Show(selectedItem); // do your work
}
It should run while items are selected separately by tapping, but this method will have problems when more items are added/removed at the same time - if you need it, then you should handle this also.
Your XAML DataTemplate.
<DataTemplate x:Key="listItemTemplate">
<StackPanel Orientation="Horizontal" Margin="4,4">
<TextBlock Tap="textblockTap" Margin="0,-7,0,0" Text="{Binding Name}" Style="{StaticResource PhoneTextLargeStyle}"/>
</StackPanel>
</DataTemplate>
In your CS page;
private void textblockTap(object sender, EventArgs e)
{
var file = (TextBlock)sender;
var ContentFile = (string)file.Text;
MessageBox.Show(ContentFile);
}
This example will show you the text of the selected item in the MessageBox.

Inactive commands in wpf Toolbar

I have application with toolbar (Add and Delete commands) and TabControl. There is VariableGrid control in each tabItem of TabControl.
look image at: http://trueimages.ru/view/cNFyf
<DockPanel >
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Command="{x:Static VariableGrid:VariableGrid.AddRowCommand}"/>
<Button Content="Delete" Command="ApplicationCommands.Delete" />
</ToolBar>
</ToolBarTray>
<TabControl x:Name="tc">
<TabItem Header="Tab 1">
<vg:VariableGrid ItemsSource="{Binding Items1, Mode=TwoWay}"/> </TabItem>
<TabItem Header="Tab 2">
<vg:VariableGrid ItemsSource="{Binding Items2, Mode=TwoWay}"/>
</TabItem>
</TabControl>
<DockPanel >
Toolbar commands are implemented in my control:
public partial class VariableGrid : DataGrid, INotifyPropertyChanged
{
public static RoutedCommand AddRowCommand = new RoutedCommand();
public VariableGrid()
{
this.CommandBindings.Add(new CommandBinding(VariableGrid.AddRowCommand, AddRow));
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, R emoveRow, CanRemoveRow));
}
private void AddRow(object sender, ExecutedRoutedEventArgs e)
{
…
}
private void RemoveRow(object sender, ExecutedRoutedEventArgs e)
{
…
}
private void CanRemoveRow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (SelectedItems.Count > 0);
}
}
There are few cases when commands in toolbar are disabled:
when application is running
when I click on gray field of DataGrid
when DataGrid is empty
When any row of DataGrid is selected - commands of toolbar are becoming active.
Can you help me with my issue? What CommandTarget of toolbar buttons should I set?
PS: There are two VariableGrids in my application. Thats why I can't set CommandTarget as "{Binding ElementName=variableGrid}". I think it should be set to FocusedElement. But I don't know how to do this.
WPF should be calling your CanRemoveRow method every once in a while to check if it is ok to remove a row. You should put a Boolean condition in this method that will answer that question. If you want similar functionality for your AddRowCommand, add a CanAddRow method where you bind the AddRowCommand.
You may want to read the Commanding Overview at MSDN.
UPDATE >>>
Oh... do you want to know what code to use for these disabled conditions? I'll assume so:
when application is running
I'm guessing you mean 'when application is busy'... add a Boolean property named IsBusy, set it to true when the application performs any long running processes, then add !IsBusy into your method condition.
when I click on gray field of DataGrid
when DataGrid is empty
Both of these conditions can be judged using the SelectedItem property of the DataGrid by adding && dataGrid.SelectedItem != null into your method condition.
Therefore, you want something like the following:
private void CanRemoveRow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !IsBusy && SelectedItem != null);
}

Categories