Search code examples
c#xamllistboxwindows-store-apps

Listbox's SelectionChanged event getting fired multiple times


I am trying to make an auto-complete box in a Windows 8.1 app.

Xaml Code:

    <Grid Background="#CCFFFFFF" VerticalAlignment="Top"  >
        <TextBox x:Name="tb" IsTextPredictionEnabled="False" Margin="30" Height="50" PlaceholderText="Enter text" VerticalAlignment="Top"  Background="Transparent" BorderBrush="#333333" Foreground="#333333" FontWeight="SemiBold"  />
        <ListBox x:Name="lb" Background="Transparent" BorderBrush="#333333" MaxHeight="400" Visibility="Collapsed"   Margin=" 30 80 30 30" VerticalAlignment="Top" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="{Binding data}" Foreground="#333333"/>
                        <TextBlock Text="{Binding data1}" Grid.Column="1" Margin="10 0 10 0" Foreground="#333333"/>
                        <StackPanel Orientation="Horizontal" Grid.Column="2">
                            <TextBlock Text="{Binding data2}" Margin="10 0 10 0" Foreground="#333333" />
                            <TextBlock Text="{Binding data3}" Foreground="#333333" />
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>

EventHandler:

        private async void tb_TextChanged(object sender, TextChangedEventArgs e)
        {
            lb.SelectionChanged -= lb_SelectionChanged;
            if (tb.Text.Length < 1 || String.IsNullOrWhiteSpace(this.tb.Text))
            {
                return;
            }
            try
            {
                var list = await Data.getData();
                lb.ItemsSource = list;
                lb.Visibility = Windows.UI.Xaml.Visibility.Visible;
                lb.SelectionChanged += lb_SelectionChanged;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message.ToString());
            }
        }
        void lb_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            tb.TextChanged -= tb_TextChanged;
            if (lb.SelectedItem == null)
            {
                tb.TextChanged += tb_TextChanged;
                lb.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                return;
            }
            tb.Text = lb.SelectedItem.ToString();
            var item = (Data)lb.SelectedItem;
            lb.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
            tb.TextChanged += tb_TextChanged;
            System.Diagnostics.Debug.WriteLine("SelectionChanged Called\n");
        }

The problem is if i type one character in the textbox and select an item from the displayed listbox, the SelctionChanged event is raised once. If i type two characters and then again select an item from the displayed box, the SelectionChanged event is thrown twice and so on.


Solution

  • The problem is your removing/add code of the event handlers in combination with an async method.

    Imagine following situation:

    • User enters character in textbox: tb_TextChanged is called, removed event handler from lb.SelectionChanged. Then calls Data.GetData and returns immediately after scheduling a continuation.
    • User enters another character in textbox: tb_TextChanged is called, removed event handler from lb.SelectionChanged. Then calls Data.GetData and returns immediately after scheduling a continuation.
    • The result from the first call to Data.GetData is available, continuation is executed lb_SelectionChanged is added the the lb.SelectionChanged event.
    • The result from the second call to Data.GetData is available, continuation is executed lb_SelectionChanged is added the the lb.SelectionChanged event.

    Now SelectionChanged has two elements and your lb_SelectionChanged will be called twice.

    I would not use a scheme like that where you add/remove events on the fly all the time. A simple boolean variable should work much better.