locked
Finding SelectedIndex for a Listbox in a DataTemplate? RRS feed

  • Question

  • In this post -- http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/bf7968d1-352d-439c-8bfe-f19aefd24452 -- I asked about getting to controls that reside in a data template, and was told (quite rightly) that rather than trying to do that, I should be binding control properties to properties in my data.  I have done that, and things are working great.  However, I have one more problem that I haven't been able to find a solution for in various posts.  Among other things in this DataTemplate, I have a textbox where you enter text, a plus button to put data into the listbox, a minus button to take data out of the listbox, and the listbox itself.  This listbox is bound to an observable collection (UsersList), and the textbox is bound to a UsernameEntered property, so when that prop changes, the listbox is populated.  Relevant xaml is:

    <DataTemplate DataType="{x:Type local:Users}">
       ...
       <TextBox Grid.Row="2" Grid.Column="0" etc. etc Name="tbUsername" Text="{Binding Path=UsernameEntered}"/>
       <Button Grid.Row="1" Grid.Column="1" etc. etc. Name="plusButton" MinWidth="20" MaxHeight="20" Content="+" Click="plusButton_Click"/> 
       <Button Grid.Row="1" Grid.Column="1" etc. etc.  Name="minusButton" MinWidth="20" MaxHeight="20" Content="-" Click="minusButton_Click"/>
       <ListBox Grid.Row="1" Grid.Column="2" Margin="40,40,0,0" Name="lbUsers" ItemsSource="{Binding UsersList}" MinHeight="200" MinWidth="150" />
    </DataTemplate>

    When you hit the plus button, m_users.UsersList.Add(m_users.UsernameEntered); happens, and the listbox gets updated.

    The problem is, what about the minus button?  I select something in the listbox, but since I can't get to the actual listbox control by name (lbUsers), how do I determine the selected index so I know which item to remove from the UserList collection?

    Thanks

     

    Thursday, April 7, 2011 3:07 PM

Answers

  • Hi fbs419,

    I think you need to traverse through the VisualTree to find the parent TabControl. In order to do that you could write code like below:

    public DependencyObject FindParent(DependencyObject o, Type parentType)
    
    {
    
     DependencyObject parent = o;
    
     while (parent != null)
    
     {
    
      if (parent.GetType() == parentType)
    
       break;
    
      else
    
       parent = VisualTreeHelper.GetParent(parent);
    
     }
    
    
    
     return parent;
    
    }
    
    

    Then you could use it like,

    TabControl tabCtrl = FindParent(listviewrow, typeof(TabControl)) as TabControl;
    
    
    
    

    Hope this information is helpful for you! If you still have any questions please feel free to let me know.

    Best regards


    Yves Zhang [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    • Marked as answer by fbs419 Monday, April 11, 2011 3:03 PM
    • Edited by Yves.Z Monday, April 11, 2011 3:13 PM typeOf ----> typeof
    Monday, April 11, 2011 2:26 PM

All replies

  •  

    Hi Fbs419,

     

    You can write below code in your list Box selection changed event or any event of your listbox, thorugh this i think you will be able to get your listview item selection for that particular...row...in which you have selected the listbox........... If I am understood your problem correctly then this is the solution i think so...

          UIElement element;
          if (e.OriginalSource is Button)
          {
           element = ((ListBox )e.OriginalSource).Parent as UIElement;
          }
          else
          {
           element = e.OriginalSource as UIElement;
           }
          ListViewItem lvi = GetDependencyObjectFromVisualTree(element as DependencyObject, typeof(ListViewItem)) as ListViewItem;
          Your_ListVIew_NAme.SelectedItem = lvi.Content;
    


    Vipul Mistry
    Thursday, April 7, 2011 3:22 PM
  •  

    Either you may traverse the VisualTree and find the ListBox or Bind Button ("minusButton") as bellow
     <Button Grid.Row="1" Grid.Column="1" etc. etc. Name="minusButton" MinWidth="20" MaxHeight="20" Content="-" Click="minusButton_Click" Tag={Binding SelectedIndex, ElementName=listBox}/>
      <ListBox x:Name="listBox" Grid.Row="1" Grid.Column="2" Margin="40,40,0,0" Name="lbUsers" ItemsSource="{Binding UsersList}" MinHeight="200" MinWidth="150" />
    
    Now your button tag will have the listbox selected index.
    

     


    Ajosh Jose
    Thursday, April 7, 2011 3:28 PM
  • To get the selection, how about just:

    private void lbUsers_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
       ListBox lb = e.OriginalSource as ListBox;
       int iIndex = lb.SelectedIndex;
    }

    Then I can use that index as a class member, and I'll know which item to delete.

    Should have thought of writing a Listbox SelectionChanged event. 

    • Proposed as answer by Yves.Z Monday, April 11, 2011 1:52 PM
    Thursday, April 7, 2011 5:49 PM
  • fbs419,

    Have you considered changing your approach?  Using event handlers that tie directly to unrelated items makes your code harder to maintain and reduces the amount of reuse you can get out of it.  In this case you have a window, a code behind file, a DataTemplate and what looks like a model class tied together, you can’t change one without potentially affecting the others. 

    What I would suggest would be to create a ViewModel for the user list.  A data template for the ViewModel .  Now you can simply host the ViewModel where you need it and WPF will take care of locating and instating the datatemplate.

    With this viewmodel:

     class UserListViewModel : INotifyPropertyChanged
      {
        private string _enteredname;
        private ObservableCollection<string> _users;
        private readonly ICommand _addtolist;
        private readonly ICommand _removefromlist;
    
        public string EnteredName { get { return _enteredname; }set{if(_enteredname==value) return;_enteredname = value;OnPropertyChanged("EnteredName");} }
        public ObservableCollection<string> Users{get { return _users ?? (_users = new ObservableCollection<string>()); }}
        public ICommand Add { get { return _addtolist; } }
        public ICommand Remove { get { return _removefromlist; } }
        public UserListViewModel()
        {
          _addtolist = new SimpleCommand {Canexecute = val => val==null ? false : !NameInList(val.ToString()), Commandaction = AddToList};
          _removefromlist = new SimpleCommand { Canexecute = val => val == null ? false : NameInList(val.ToString()), Commandaction = RemoveFromList };
    
        }
        private bool NameInList(string value)
        {
          return _users.Contains(value);
        }
        private void RemoveFromList(object value)
        {
          var name = value as string;
          if (NameInList(name))
            _users.Remove(name);
        }
        private void AddToList(object value)
        {
          var name = value as string;
          if (!NameInList(name))
            _users.Add(name);
        }
        #region Implementation of INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
          PropertyChangedEventHandler handler = PropertyChanged;
          if (handler != null)
          {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
          }
        }
    
        #endregion
      }
    

     Your DataTemplate could look like this:

     <DataTemplate DataType="{x:Type ViewModel:UserList}">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>        
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>        
          </Grid.ColumnDefinitions>
          <TextBox Grid.Row="0" Grid.Column="0" Text="{Binding EnteredName}" Name="Name"/>
          <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="1">
            <Button Command="{Binding Add}" CommandParameter="{Binding ElementName=Name,Path=Text}">Add</Button>
            <Button Command="{Binding Remove}" CommandParameter="{Binding ElementName=Name,Path=Text}">Remove</Button>
          </StackPanel>
          <ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Users}"/>
        </Grid>
      </DataTemplate>
    

     Now where you need a userlist you can simply bind an instance of UserListViewModel and WPF will create the template for you, no code behind, no dependency on a specific window.

     

     

    Thursday, April 7, 2011 7:07 PM
  • Yes, changing my approach is a good idea, and I am actually in the process of making this an MVVM app.  Related to this, I do have one more thing I need to do, and I'm wondering if MVVM is relevant in this case, since what I want to do is create a UI element.  Just for my own knowledge, I would like to learn how to do the following:

    One of my views in this app is a ResultsView.  It's defined by a DataTemplate tied to a Results class.  It has a TabControl, with a TabItem containing a ListView and GridView (with columns bound to database columns).  

    When I doubleclick on one of the rows in the ListView, I would like another tab (with another GridView) to come up (tied to other data) that has more specific info about the row.   I have this working when the new TabItem (not shown below) is created in .xaml code, and the new tab gets filled in correctly with the new data, but I don't want the new tab to show unless the user doubleclicks.  That's why I want to create it in codebehind.  I know I can create the TabItem via C# code, and I have the doubleclick handler already (ItemContainerStyle="{StaticResource itemstyle}"). 

    BUT IT'S THE SAME PROBLEM -- BECAUSE IT'S IN A DataTemplate, HOW DO I GET TO THE PARENT TabControl SO I KNOW WHERE TO ADD THE TabItem VIA C#?

    <DataTemplate DataType="{x:Type local:Results}">
       <StackPanel Name="ResultsSP">
          <TabControl Name="ResultsTC" MinHeight="300"  Margin="5">
             <TabItem Name="AccountsTabItem" Header="Accounts">
                <Grid Name="AccountResultsGrid">
                   <ListView Margin="5" Name="lstAccountResults" ItemsSource="{Binding AccountResultsList}" ItemContainerStyle="{StaticResource itemstyle}">
                      <ListView.View>
                          <GridView>
                              <GridView.Columns>
                                  <GridViewColumn  DisplayMemberBinding="{Binding Path=AccountName}">
                                     <GridViewColumnHeader Width="140" Content=" Account" HorizontalContentAlignment="Left"/>
                                  </GridViewColumn>
                                  <GridViewColumn  DisplayMemberBinding="{Binding Path=AccountName}">
                                     <GridViewColumnHeader Width="140" Content=" Account" HorizontalContentAlignment="Left"/>
                                  </GridViewColumn>
                                  etc. etc.
                              </GridView.Columns>
                          </GridView> 
                       </ListView.View>
                    </ListView>
                </Grid> 
             </TabItem>
          </TabControl>
       </StackPanel>
    </DataTemplate>

    SO AGAIN, I WOULD LIKE TO ADD A NEW TABITEM WHEN THE USER DOUBLECLICKS A ROW IN THE LISTVIEW.  HOW CAN I DO THAT WHEN IT'S IN A DataTemplate?

    Thanks

     

    Sunday, April 10, 2011 2:44 PM
  • You might want to look at this.

    It allows you to send events from a control to a command handler, if you need any help with the code let me know :D

    Sunday, April 10, 2011 8:41 PM
  • Wow, that looks kind of nasty.  I was hoping for a much simpler solution.  So there's no way to be able to find that TabControl (ResultsTC) so I can just create a TabItem and do an Items.Add?

    Or what about having another tab item in the .xaml (for Users instead of Accounts), under the ResultsTC tab control, bound to another collection (UserResultsList), and set to Hidden.  For example:

    <TabItem Name="UserTabItem" Header="User" Visibility="Hidden">
       <Grid Name="UserResultsGrid">
          <ListView Margin="5" Name="lstUserResults" ItemsSource="{Binding UserResultsList}">
             <ListView.View>
                <GridView>
                   different columns, different DisplayMemberBinding (for columns in UserResultsList), etc. 

    Then when you DoubleClick, it sets the prop on UserTabItem to Visible, maybe changes the header, etc.

    I was hoping to bind the new tab control to the new collection, UserResultsList.  Maybe that only works if the DataTemplate is inside the tab control, not the other way around, as I have it.  I'm doing it this way because the DataTemplates represent views, depending on which listbox item is clicked in the main window.

    Seems like there must be an easier way.

    Thanks

    Monday, April 11, 2011 1:51 PM
  • Hi fbs419,

    I think you need to traverse through the VisualTree to find the parent TabControl. In order to do that you could write code like below:

    public DependencyObject FindParent(DependencyObject o, Type parentType)
    
    {
    
     DependencyObject parent = o;
    
     while (parent != null)
    
     {
    
      if (parent.GetType() == parentType)
    
       break;
    
      else
    
       parent = VisualTreeHelper.GetParent(parent);
    
     }
    
    
    
     return parent;
    
    }
    
    

    Then you could use it like,

    TabControl tabCtrl = FindParent(listviewrow, typeof(TabControl)) as TabControl;
    
    
    
    

    Hope this information is helpful for you! If you still have any questions please feel free to let me know.

    Best regards


    Yves Zhang [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    • Marked as answer by fbs419 Monday, April 11, 2011 3:03 PM
    • Edited by Yves.Z Monday, April 11, 2011 3:13 PM typeOf ----> typeof
    Monday, April 11, 2011 2:26 PM
  • Thank you Yves.Z.  That works perfectly!  (typeOf didn't compile -- typeof does).  Thanks also to everyone else on this thread for their help, especially Vipul for the SelectionChanged event suggestion, and Frank Holden for his help with MVVM.

    Monday, April 11, 2011 3:07 PM
  • Cheers! I am glad to see that helps. :)

    And sorry for the typeOf mistake! I shall correct it at once.


    Yves Zhang [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, April 11, 2011 3:12 PM