locked
The selected item in my listbox is always the previous item RRS feed

  • Question

  • It's easier if you see the code but I'll give you the specific problem. My listbox is set to contain a grid format and each listboxitem has a ListBoxItem.Selected event. The problem is, the selected item is always set to the previous item.

    Here is what happens in steps:

     

    1. My image loads up

    2. I click a button to generate a grid overlay of 32x32 cells over the image, each a listboxitem in a cell of that overlay

    3. I click on a cell 

    4. OnSelected method fires (each listboxitem is tied to this handler through the Selected event)

    5. The SelectedItem is null when I click on cell (0,0)

    6. If I click on cell (0,1), the Selected event fires but now the SelectedItem is set to the object in the previous cell (0,0) instead of (0,1). Then, clicking on (0,0) will show the SelectedItem as being (0,1) and so on.

    7. Repeat for any other cell.

    XAML

     

     
    <DockPanel Name="dockTest">
     <Menu DockPanel.Dock="Top" Width="Auto" Height="Auto">
     <MenuItem Header="File">
     <MenuItem Header="Load Image" Command="Open"></MenuItem>
     <Separator />
     <MenuItem Header="Exit" Command="Close"></MenuItem>
     </MenuItem>
     </Menu>
     <ListBox Name="lstTiles" DockPanel.Dock="Right" PreviewMouseRightButtonDown="grdMain_MouseRightButtonDown" 
     PreviewMouseRightButtonUp="grdMain_MouseRightButtonUp" SelectionMode="Extended">
     <ListBox.ItemContainerStyle>
     <Style>
     <EventSetter Event="ListBoxItem.Selected" Handler="OnSelected" />
     <Setter Property="Grid.Row" Value="{Binding RelativeSource={x:Static RelativeSource.Self},
     Path=Content.Row}"/>
     <Setter Property="Grid.Column" Value="{Binding RelativeSource={x:Static RelativeSource.Self},
     Path=Content.Column}"/>
     <Setter Property="ListBoxItem.Height" Value="{Binding RelativeSource={x:Static RelativeSource.Self},
     Path=Content.Height}" />
     <Setter Property="ListBoxItem.Width" Value="{Binding RelativeSource={x:Static RelativeSource.Self},
     Path=Content.Width}" />
     <Setter Property="ListBoxItem.IsHitTestVisible" Value="True" />
     <Style.Resources>
     <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Blue" Opacity=".3" />
     <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
     </Style.Resources>
     </Style>
     </ListBox.ItemContainerStyle>
     <ListBox.ItemsPanel>
     <ItemsPanelTemplate>
     <Grid ShowGridLines="True" IsItemsHost="True" Background="{DynamicResource LoadedImage}" 
     Name="grdMain" MaxHeight="600" MaxWidth="800" MinHeight="600" MinWidth="800" >
     </Grid>
     </ItemsPanelTemplate>
     </ListBox.ItemsPanel>
     </ListBox>
     <StackPanel DockPanel.Dock="Left">
     <Label Margin="5">Tile Size</Label>
     <ComboBox Name="cmbGridSize">
     <TextBlock Name="txt3232">32x32</TextBlock>
     <TextBlock Name="txt1616">16x16</TextBlock>
     </ComboBox>
     <Button Name="btnChangeGrid" Click="GridBtn_Click" Margin="4">Make Grid</Button>
     <Label>Tile Type</Label>
     <ComboBox Name="cmbTileType" SelectionChanged="cmbTileType_SelectionChanged">
     
     </ComboBox>
     <TextBlock Name="txtTest"></TextBlock>
     </StackPanel>
    
     </DockPanel>
    
    

     

    Code Behind

     

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
     {
     //Stores enum array of object types
     tileTypes = Enum.GetNames(typeof(Tile.TileType));
    
     //Gets main grid in template of main page
     mainGrid = Helpers.FindItemsPanel(lstTiles) as Grid;
    
     //Sets combobox to array of enums
     cmbTileType.ItemsSource = tileTypes;
     
     //cmbTileType.SelectedItem = "None";
     }
    
     //Generates the grid overlay
     private void Grid_Build()
     {
     //Sets grid overlay to user preferences of tile size
     if (tileList.Count > 0)
     {
     tileList.Clear();
     }
    
     if (mainGrid.RowDefinitions.Count > 0)
     {
     mainGrid.RowDefinitions.Clear();
     }
    
     if (mainGrid.ColumnDefinitions.Count > 0)
     {
     mainGrid.ColumnDefinitions.Clear();
     }
    
     int numberOfColumns = Convert.ToInt32(Math.Ceiling((float)imageWidth / (float)tileHeight));
     int numberOfRows = Convert.ToInt32(Math.Ceiling((float)imageHeight / (float)tileHeight));
     
     for (int i = 0; i < numberOfRows; i++)
     {
     mainGrid.RowDefinitions.Add(new RowDefinition());
     }
    
     for (int i = 0; i < numberOfColumns; i++)
     {
     mainGrid.ColumnDefinitions.Add(new ColumnDefinition());
     }
    
     int addCounter = 0;
    
     //Stores and generates tile objects in generated rows and columns
     for (int row = 0; row < numberOfRows; row++)
     {
     for (int col = 0; col < numberOfColumns; col++)
     {
     Tile tempTile = (new Tile(row, col, tileHeight, tileWidth, addCounter, Tile.TileType.None));
     tempTile.IsHitTestVisible = true;
     tileList.Add(tempTile);
     addCounter++;
     }
     }
    
     //Sets listbox of tiles in xaml to the list of tile objects
     lstTiles.ItemsSource = tileList;
     }
    
     //Occurs when a listbox item is selected
     private void OnSelected(object sender, RoutedEventArgs e)
     {
     //If a single tile object is selected
     if (lstTiles.SelectedItems.Count == 1)
     {
     //Stores array of selected tile listboxitems in listbox
     var items = lstTiles.SelectedItems;
     
     foreach (Tile it in items)
     {
     txtTest.Text = it.Row.ToString() + " " + it.Column.ToString();
     
     //Sets tile type combobox to the property set in each tile object
     cmbTileType.SelectedItem = (string)Enum.GetName(typeof(Tile.TileType), it.TileTyp);
     }
     }
     }
     //Occurs when the when the tile type combobox is changed to a different type
     private void cmbTileType_SelectionChanged(object sender, SelectionChangedEventArgs e)
     {
     //Stores the tile type property of the selected tile object
     string newType = cmbTileType.SelectedItem.ToString();
     
     
     var items = lstTiles.SelectedItems;
    
     foreach (Tile it in items)
     {
     cmbTileType.SelectedItem = newType;
     it.TileTyp = (Tile.TileType)Enum.Parse(typeof(Tile.TileType), newType, true);
     
     }
     }
    
    

     

    • Edited by Ilyaka Wednesday, October 6, 2010 6:40 PM
    Sunday, October 3, 2010 1:51 AM

Answers

  • <EventSetter Event="ListBoxItem.PreviewMouseLeftButtonDown" Handler="OnSelected" />
    

    The problem is you hooked your "OnSelected" callback to the PreviewMouseLeftButtonDown event, so when it gets called, the selection  changed hasn't actually happened yet. The way you're using it, you should call it "OnAboutToBeSelected" :-)

    Why not use the ListBox.SelectionChanged event instead?

    this.lstTiles.SelectionChanged += new SelectionChangedEventHandler( lstTiles_SelectionChanged );
    
    
    
    void lstTiles_SelectionChanged( object sender, SelectionChangedEventArgs e )
    
    {
    
     // process selection change
    
    }
    
    
    I made a mistake with my description. I meant to say every listboxitem is hooked to the selected event which matches up with the code I posted.


    no, in the code you posted you are hooking it to the PreviewMouseLeftButtonDown event. This is copied directly from your code:

    <EventSetter Event="ListBoxItem.PreviewMouseLeftButtonDown" Handler="OnSelected" />
    

    ...and you are getting exactly the behavior that would be expected, as I already explained. Why don't you try my suggestion and instead handle the SelectionChanged event from the ListBox itself.

    • Edited by matte303 Wednesday, October 6, 2010 4:33 PM edit
    • Marked as answer by Ilyaka Wednesday, October 6, 2010 6:47 PM
    Wednesday, October 6, 2010 4:32 PM

All replies

  • Hi,

     

    I had the same problem with a listbox and a treeview. The array is indexed and starts with 0. Which means that upon loading the index of the array is set to its startpositoion which is 0. To get over this problem I used the listbox.selectionchanged event to tell my code via a boolean (bLoading = true) that the tablerow index should start at 0. And while it was not loading (when the user browses items in the listbox) I used the same code but set its index to selectedindex. See my code - maybe this helps. Sorry it is in VB.

     

      Private Sub lbPolicyDetails_SelectionChanged(ByVal sender As Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) Handles lbPolicyDetails.SelectionChanged
        If bLoading Then
          Dim Tablerow = lbPolicyDetails.Items(0)
        ElseIf Not bLoading Then
          Dim sitem As Integer = lbPolicyDetails.SelectedIndex
          Dim Tablerow = lbPolicyDetails.Items(sitem)
        End If
      End Sub

    New to VB
    Sunday, October 3, 2010 1:16 PM
  • Hi,

     

    I had the same problem with a listbox and a treeview. The array is indexed and starts with 0. Which means that upon loading the index of the array is set to its startpositoion which is 0. To get over this problem I used the listbox.selectionchanged event to tell my code via a boolean (bLoading = true) that the tablerow index should start at 0. And while it was not loading (when the user browses items in the listbox) I used the same code but set its index to selectedindex. See my code - maybe this helps. Sorry it is in VB.

     

     Private Sub lbPolicyDetails_SelectionChanged(ByVal sender As Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) Handles lbPolicyDetails.SelectionChanged
     If bLoading Then
      Dim Tablerow = lbPolicyDetails.Items(0)
     ElseIf Not bLoading Then
      Dim sitem As Integer = lbPolicyDetails.SelectedIndex
      Dim Tablerow = lbPolicyDetails.Items(sitem)
     End If
     End Sub

    New to VB
    Honestly do appreciate you having taken the time to take a look but am not following
    Monday, October 4, 2010 9:34 AM
  • Is there anywhere I can go with this question next? Maybe somewhere where I can pay a small fee for someone to take a look?
    • Proposed as answer by matte303 Tuesday, October 5, 2010 11:03 PM
    Tuesday, October 5, 2010 8:52 PM
  • The problem is you hooked your "OnSelected" callback to the PreviewMouseLeftButtonDown event, so when it gets called, the selection  changed hasn't actually happened yet. The way you're using it, you should call it "OnAboutToBeSelected" :-)

    Why not use the ListBox.SelectionChanged event instead?

    this.lstTiles.SelectionChanged += new SelectionChangedEventHandler( lstTiles_SelectionChanged );
    
    void lstTiles_SelectionChanged( object sender, SelectionChangedEventArgs e )
    {
      // process selection change
    }
    
    • Proposed as answer by matte303 Tuesday, October 5, 2010 11:09 PM
    Tuesday, October 5, 2010 11:09 PM
  • The problem is you hooked your "OnSelected" callback to the PreviewMouseLeftButtonDown event, so when it gets called, the selection  changed hasn't actually happened yet. The way you're using it, you should call it "OnAboutToBeSelected" :-)

    Why not use the ListBox.SelectionChanged event instead?

    this.lstTiles.SelectionChanged += new SelectionChangedEventHandler( lstTiles_SelectionChanged );
    
    void lstTiles_SelectionChanged( object sender, SelectionChangedEventArgs e )
    {
     // process selection change
    }
    
    I made a mistake with my description. I meant to say every listboxitem is hooked to the selected event which matches up with the code I posted.
    Wednesday, October 6, 2010 1:54 AM
  • <EventSetter Event="ListBoxItem.PreviewMouseLeftButtonDown" Handler="OnSelected" />
    

    The problem is you hooked your "OnSelected" callback to the PreviewMouseLeftButtonDown event, so when it gets called, the selection  changed hasn't actually happened yet. The way you're using it, you should call it "OnAboutToBeSelected" :-)

    Why not use the ListBox.SelectionChanged event instead?

    this.lstTiles.SelectionChanged += new SelectionChangedEventHandler( lstTiles_SelectionChanged );
    
    
    
    void lstTiles_SelectionChanged( object sender, SelectionChangedEventArgs e )
    
    {
    
     // process selection change
    
    }
    
    
    I made a mistake with my description. I meant to say every listboxitem is hooked to the selected event which matches up with the code I posted.


    no, in the code you posted you are hooking it to the PreviewMouseLeftButtonDown event. This is copied directly from your code:

    <EventSetter Event="ListBoxItem.PreviewMouseLeftButtonDown" Handler="OnSelected" />
    

    ...and you are getting exactly the behavior that would be expected, as I already explained. Why don't you try my suggestion and instead handle the SelectionChanged event from the ListBox itself.

    • Edited by matte303 Wednesday, October 6, 2010 4:33 PM edit
    • Marked as answer by Ilyaka Wednesday, October 6, 2010 6:47 PM
    Wednesday, October 6, 2010 4:32 PM
  • <EventSetter Event="ListBoxItem.PreviewMouseLeftButtonDown" Handler="OnSelected" />
    

    The problem is you hooked your "OnSelected" callback to the PreviewMouseLeftButtonDown event, so when it gets called, the selection  changed hasn't actually happened yet. The way you're using it, you should call it "OnAboutToBeSelected" :-)

    Why not use the ListBox.SelectionChanged event instead?

    this.lstTiles.SelectionChanged += new SelectionChangedEventHandler( lstTiles_SelectionChanged );
    
    
    
    void lstTiles_SelectionChanged( object sender, SelectionChangedEventArgs e )
    
    {
    
     // process selection change
    
    }
    
    
    I made a mistake with my description. I meant to say every listboxitem is hooked to the selected event which matches up with the code I posted.


    no, in the code you posted you are hooking it to the PreviewMouseLeftButtonDown event. This is copied directly from your code:

    <EventSetter Event="ListBoxItem.PreviewMouseLeftButtonDown" Handler="OnSelected" />
    

    ...and you are getting exactly the behavior that would be expected, as I already explained. Why don't you try my suggestion and instead handle the SelectionChanged event from the ListBox itself.

    Actually, the code I copy had another mistake in it in that PreviewLeft was from an older build (at the time of posting this that eventsetter was indeed set to the Selected event, my mistake sorry). Here's the thing, SelectionChanged works but Selected (what I originally had) does not. Why...I don't know. Thanks for the answer, honestly do appreciate it but if you can take a guess at why SelectionChanged would work here versus Selected that'd be appreciated. 
    Wednesday, October 6, 2010 6:46 PM
  • Actually, the code I copy had another mistake in it in that PreviewLeft was from an older build,. Here's the thing, SelectionChanged works but Selected (what I originally had) does not. Why...I don't know. Thanks for the answer, honestly do appreciate it but if you can take a guess at why SelectionChanged would work here versus Selected that'd be appreciated. 

    I imagine it's simply that the "Selected" event for the individual item in the ListBox is fired before the ListBox's SelectedItems collection is actually updated, whereas the SelectedItems collection is guaranteed to be correct by the time the ListBox.SelectionChanged event is fired.
    Wednesday, October 6, 2010 7:01 PM
  • Actually, the code I copy had another mistake in it in that PreviewLeft was from an older build,. Here's the thing, SelectionChanged works but Selected (what I originally had) does not. Why...I don't know. Thanks for the answer, honestly do appreciate it but if you can take a guess at why SelectionChanged would work here versus Selected that'd be appreciated. 

    I imagine it's simply that the "Selected" event for the individual item in the ListBox is fired before the ListBox's SelectedItems collection is actually updated, whereas the SelectedItems collection is guaranteed to be correct by the time the ListBox.SelectionChanged event is fired.
    Thank you x 1000. I've run into a couple things in WPF which mess me up because there is some implicit rules under the hood which aren't marked out in any of the books I have read. It'd be nice if there were a popup box in any of the WPF books I had which said "Note: SelectedItems collection may not be initialized on the Selected event! To guarantee it's initialized on time, use SelectionChanged!" 
    Wednesday, October 6, 2010 9:11 PM
  • You're welcome, happy to help!  Keep in mind, the "Selected" event comes from a ListBoxItem object (an item in the list, not the list itself), it means that that item was just selected, you can't assume that anyone else (for example the ListBox that contains that item) knows about the change yet. The 'SelectionChanged' event, on the other hand, is coming from the ListBox itself, so when you get that event, you can be sure the ListBox itself is fully aware of the change. That kind of distinction is important to understand when dealing with WPF/.NET events.

    Wednesday, October 6, 2010 9:35 PM