c# WPF Listbox databinding and getting the row's data item from a click event.

moondaddy 911 Reputation points
2021-01-28T05:48:18.41+00:00

I have a WPF Listbox where the ItemTemplate is made up of a TextBlock and 2 custom hyperlink controls. When a user clicks on one of the hyperlink controls they fire a click event. In this even I need to get data item bound to that listbox row, but I dont know how to reference it. Below is the ListBox xaml and the c# used to set the ItemsSource property.

<ListBox x:Name="LbxRecentViews" HorizontalAlignment="Stretch" Height="200"  VerticalAlignment="Top">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="22"  />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="60"/>
                    <ColumnDefinition Width="60"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Row="0" Grid.Column="0" x:Name="TbkAppVersion" Text="{Binding AppName, Mode=OneWay}"/>
                <vHyprLnk:vHyperlinkButton  Grid.Row="0" Grid.Column="1"  x:Name="BtnTable" Content="Tbl" Click="BtnTable_Click" />
                <vHyprLnk:vHyperlinkButton  Grid.Row="0" Grid.Column="2"  x:Name="BtnStoredProcedure" Content="SP" Click="BtnStoredProcedure_Click" />
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

and the ItemsSource:

List<WcRecentView_lst_Binding> list = new List<WcRecentView_lst_Binding>();
for (int i = 0; i < array.GetUpperBound(0) + 1; i++)
{
    list.Add(new WcRecentView_lst_Binding((object[])array[i]));
}
LbxRecentViews.ItemsSource = list;

and the click event.

        private void BtnTable_Click(object sender, RoutedEventArgs e)
        {

        }

Thank you.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,679 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,299 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
767 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2021-01-29T04:13:13.14+00:00

    HI,
    with MVVM pattern you are very flexible. In small applications you can use one ViewModel for MainWindow with UserControls like in following demo. In large applications you can use main ViewModel and ViewModels for UserControls or second Windows.

    XAML MainWindow:

    <Window x:Class="WpfApp1.Window029"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp029"  
            xmlns:vHyprLnk="clr-namespace:WpfApp029"  
            xmlns:uc1="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"  
            mc:Ignorable="d"  
            Title="MainWindow" Height="450" Width="800">  
      <Window.Resources>  
        <local:ViewModel x:Key="vm"/>  
      </Window.Resources>  
      <StackPanel DataContext="{StaticResource vm}">  
        <Button Content="Button in MainWindow" Command="{Binding Cmd}" CommandParameter="From MainWindow"/>  
        <uc1:Window029UC1 Margin="5"/>  
        <Label Content="{Binding Status}"/>  
        <ListBox HorizontalAlignment="Stretch" Height="200"  VerticalAlignment="Top"  
                 ItemsSource="{Binding View}">  
          <ListBox.ItemTemplate>  
            <DataTemplate>  
              <Grid>  
                <Grid.RowDefinitions>  
                  <RowDefinition Height="22"  />  
                </Grid.RowDefinitions>  
                <Grid.ColumnDefinitions>  
                  <ColumnDefinition Width="150"/>  
                  <ColumnDefinition Width="60"/>  
                  <ColumnDefinition Width="60"/>  
                </Grid.ColumnDefinitions>  
                <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding AppName, Mode=OneWay}"/>  
                <vHyprLnk:vHyperlinkButton  Grid.Row="0" Grid.Column="1"  Content="Tbl"   
                                            Command="{Binding BtnTable_Click, Source={StaticResource vm}}" CommandParameter="{Binding}" />  
                <vHyprLnk:vHyperlinkButton  Grid.Row="0" Grid.Column="2"  Content="SP"   
                                            Command="{Binding BtnStoredProcedure_Click, Source={StaticResource vm}}" CommandParameter="{Binding}" />  
              </Grid>  
            </DataTemplate>  
          </ListBox.ItemTemplate>  
        </ListBox>  
      </StackPanel>  
    </Window>  
    

    Classes:

    using System;  
    using System.Collections.Generic;  
    using System.ComponentModel;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Input;  
      
    namespace WpfApp029  
    {  
      public class ViewModel : INotifyPropertyChanged  
      {  
        private string _status;  
        public string Status { get => this._status; set { this._status = value; OnPropertyChanged(); } }  
      
        private CollectionViewSource cvs = new CollectionViewSource();  
      
        public ICollectionView View { get => cvs.View; }  
      
        public ICommand BtnTable_Click  
        {  
          get => new RelayCommand((state) => Status = $"BtnTable_Click: {((WcRecentView_lst_Binding)state).AppName}");  
        }  
      
        public ICommand BtnStoredProcedure_Click  
        {  
          get => new RelayCommand((state) => Status = $"BtnStoredProcedure_Click: {((WcRecentView_lst_Binding)state).AppName}");  
        }  
      
        public ICommand Cmd { get => new RelayCommand(CmdExec); }  
      
        private void CmdExec(object state)  
        {  
          switch (state.ToString())  
          {  
            case "From MainWindow":  
              Status = "Click from MainWindow";  
              break;  
            case "From UserControl":  
              Status = "Click from UserControl";  
              break;  
            case "Load From UserControl":  
              LoadData();  
              break;  
            default:  
              break;  
          }  
        }  
      
        private void LoadData()  
        {  
          List<WcRecentView_lst_Binding> list = new List<WcRecentView_lst_Binding>();  
          for (int i = 0; i < 100; i++) list.Add(new WcRecentView_lst_Binding() { AppName = $"App {i}" });  
          cvs.Source = list;  
          OnPropertyChanged(nameof(View));  
        }  
      
        public event PropertyChangedEventHandler PropertyChanged;  
        private void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
      
      public class WcRecentView_lst_Binding  
      {  
        public string AppName { get; set; }  
      }  
      
      public class vHyperlinkButton : Button { }  
      public class RelayCommand : ICommand  
      {  
        private readonly Predicate<object> _canExecute;  
        private readonly Action<object> _action;  
        public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }  
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }  
        public void Execute(object o) => _action(o);  
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);  
        public event EventHandler CanExecuteChanged  
        {  
          add { CommandManager.RequerySuggested += value; }  
          remove { CommandManager.RequerySuggested -= value; }  
        }  
      }  
    }  
    

    And XAML UserControl:

    <UserControl x:Class="WpfControlLibrary1.Window029UC1"  
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"   
                 xmlns:local="clr-namespace:WpfControlLibrary1"  
                 mc:Ignorable="d"   
                 d:DesignHeight="80" d:DesignWidth="200">  
      <UserControl.Resources>  
        <Style TargetType="Button">  
          <Setter Property="Margin" Value="5"/>  
        </Style>  
      </UserControl.Resources>  
      <Border BorderBrush="Red" BorderThickness="3">  
        <StackPanel>  
          <Button Content="Button in UserControl" Command="{Binding Cmd}" CommandParameter="From UserControl"/>  
          <Button Content="Start Load from UserControl" Command="{Binding Cmd}" CommandParameter="Load From UserControl"/>  
        </StackPanel>  
      </Border>  
    </UserControl>  
    

    Result:

    61665-x.gif


4 additional answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2021-01-28T07:30:22.013+00:00

    Hi,
    you can use MVVM pattern, ICommand-Relay class and bind CommandParamter to data object. Try following demo:

    XAML:

    <Window x:Class="WpfApp1.Window029"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp029"  
            xmlns:vHyprLnk="clr-namespace:WpfApp029"  
            mc:Ignorable="d"  
            Title="Window029" Height="450" Width="800">  
      <Window.Resources>  
        <local:ViewModel x:Key="vm"/>  
      </Window.Resources>  
      <Grid DataContext="{StaticResource vm}">  
        <ListBox HorizontalAlignment="Stretch" Height="200"  VerticalAlignment="Top"  
                 ItemsSource="{Binding View}">  
          <ListBox.ItemTemplate>  
            <DataTemplate>  
              <Grid>  
                <Grid.RowDefinitions>  
                  <RowDefinition Height="22"  />  
                </Grid.RowDefinitions>  
                <Grid.ColumnDefinitions>  
                  <ColumnDefinition Width="150"/>  
                  <ColumnDefinition Width="60"/>  
                  <ColumnDefinition Width="60"/>  
                </Grid.ColumnDefinitions>  
                <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding AppName, Mode=OneWay}"/>  
                <vHyprLnk:vHyperlinkButton  Grid.Row="0" Grid.Column="1"  Content="Tbl"   
                                            Command="{Binding BtnTable_Click, Source={StaticResource vm}}" CommandParameter="{Binding}" />  
                <vHyprLnk:vHyperlinkButton  Grid.Row="0" Grid.Column="2"  Content="SP"   
                                            Command="{Binding BtnStoredProcedure_Click, Source={StaticResource vm}}" CommandParameter="{Binding}" />  
              </Grid>  
            </DataTemplate>  
          </ListBox.ItemTemplate>  
        </ListBox>  
      </Grid>  
    </Window>  
    

    And classes:

    using System;  
    using System.Collections.Generic;  
    using System.ComponentModel;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Input;  
      
    namespace WpfApp029  
    {  
      public class ViewModel   
      {  
        public ViewModel() => LoadData();  
      
        private CollectionViewSource cvs = new CollectionViewSource();  
      
        public ICollectionView View { get => cvs.View; }  
      
        public ICommand BtnTable_Click { get => new RelayCommand((state) =>   
          MessageBox.Show($"BtnTable_Click: {((WcRecentView_lst_Binding)state).AppName}")); }  
      
        public ICommand BtnStoredProcedure_Click { get => new RelayCommand((state) =>   
          MessageBox.Show($"BtnStoredProcedure_Click: {((WcRecentView_lst_Binding)state).AppName}")); }  
      
        private void LoadData()  
        {  
          List<WcRecentView_lst_Binding> list = new List<WcRecentView_lst_Binding>();  
          for (int i = 0; i < 100; i++) list.Add(new WcRecentView_lst_Binding() { AppName = $"App {i}" });  
          cvs.Source = list;  
        }  
      }  
      
      public class WcRecentView_lst_Binding  
      {  
        public string AppName { get; set; }  
      }  
      
      public class vHyperlinkButton : Button { }  
      public class RelayCommand : ICommand  
      {  
        private readonly Predicate<object> _canExecute;  
        private readonly Action<object> _action;  
        public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }  
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }  
        public void Execute(object o) => _action(o);  
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);  
        public event EventHandler CanExecuteChanged  
        {  
          add { CommandManager.RequerySuggested += value; }  
          remove { CommandManager.RequerySuggested -= value; }  
        }  
      }  
    }  
    

    Result:

    61249-x.gif

    0 comments No comments

  2. moondaddy 911 Reputation points
    2021-01-28T17:52:10.94+00:00

    Thank you Peter. I need to pass the data in from an outside source based on other events and context. So for example, I would need to make LoadData() public like this:

    public void LoadData(List<WcRecentView_lst_Binding> list)
    

    And call this method from outside the ViewModel class - such as from various button click events in Window029 for example, but I don't know how to get access to ViewModel from c# in Window029.

    Thank again.

    0 comments No comments

  3. Peter Fleischer (former MVP) 19,231 Reputation points
    2021-01-28T21:12:01.847+00:00

    Hi,
    you can include in XAML Button binded to additional ICommand property in ViewModel:

    XAML:

    <Window x:Class="WpfApp1.Window029"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp029"  
            xmlns:vHyprLnk="clr-namespace:WpfApp029"  
            mc:Ignorable="d"  
            Title="Window029" Height="450" Width="800">  
      <Window.Resources>  
        <local:ViewModel x:Key="vm"/>  
      </Window.Resources>  
      <StackPanel DataContext="{StaticResource vm}">  
        <Button Content="Load Data" Command="{Binding Cmd}"/>  
        <ListBox HorizontalAlignment="Stretch" Height="200"  VerticalAlignment="Top"  
                 ItemsSource="{Binding View}">  
          <ListBox.ItemTemplate>  
            <DataTemplate>  
              <Grid>  
                <Grid.RowDefinitions>  
                  <RowDefinition Height="22"  />  
                </Grid.RowDefinitions>  
                <Grid.ColumnDefinitions>  
                  <ColumnDefinition Width="150"/>  
                  <ColumnDefinition Width="60"/>  
                  <ColumnDefinition Width="60"/>  
                </Grid.ColumnDefinitions>  
                <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding AppName, Mode=OneWay}"/>  
                <vHyprLnk:vHyperlinkButton  Grid.Row="0" Grid.Column="1"  Content="Tbl"   
                                            Command="{Binding BtnTable_Click, Source={StaticResource vm}}" CommandParameter="{Binding}" />  
                <vHyprLnk:vHyperlinkButton  Grid.Row="0" Grid.Column="2"  Content="SP"   
                                            Command="{Binding BtnStoredProcedure_Click, Source={StaticResource vm}}" CommandParameter="{Binding}" />  
              </Grid>  
            </DataTemplate>  
          </ListBox.ItemTemplate>  
        </ListBox>  
      </StackPanel>  
    </Window>  
    

    Classes:

    using System;  
    using System.Collections.Generic;  
    using System.ComponentModel;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
      
    namespace WpfApp029  
    {  
      public class ViewModel : INotifyPropertyChanged  
      {  
        private CollectionViewSource cvs = new CollectionViewSource();  
      
        public ICollectionView View { get => cvs.View; }  
      
        public ICommand BtnTable_Click { get => new RelayCommand((state) =>   
          MessageBox.Show($"BtnTable_Click: {((WcRecentView_lst_Binding)state).AppName}")); }  
      
        public ICommand BtnStoredProcedure_Click { get => new RelayCommand((state) =>   
          MessageBox.Show($"BtnStoredProcedure_Click: {((WcRecentView_lst_Binding)state).AppName}")); }  
      
        public ICommand Cmd { get => new RelayCommand(LoadData); }  
      
        private void LoadData(object state)  
        {  
          List<WcRecentView_lst_Binding> list = new List<WcRecentView_lst_Binding>();  
          for (int i = 0; i < 100; i++) list.Add(new WcRecentView_lst_Binding() { AppName = $"App {i}" });  
          cvs.Source = list;  
          OnPropertyChanged(nameof(View));  
        }  
      
        public event PropertyChangedEventHandler PropertyChanged;  
        private void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
      
      public class WcRecentView_lst_Binding  
      {  
        public string AppName { get; set; }  
      }  
      
      public class vHyperlinkButton : Button { }  
      public class RelayCommand : ICommand  
      {  
        private readonly Predicate<object> _canExecute;  
        private readonly Action<object> _action;  
        public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }  
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }  
        public void Execute(object o) => _action(o);  
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);  
        public event EventHandler CanExecuteChanged  
        {  
          add { CommandManager.RequerySuggested += value; }  
          remove { CommandManager.RequerySuggested -= value; }  
        }  
      }  
    }  
    

    Result:

    61527-x.gif

    0 comments No comments

  4. moondaddy 911 Reputation points
    2021-01-28T21:38:56.437+00:00

    Thanks for all the samples Peter. The button is calling the command "Command="{Binding Cmd}"", but I need to pass in actual data from outside sources. it could be from a button click event, but could also be from events bubbling up from nested user controls below, or from web service callbacks, etc. I need the flexibility to just pass in a chunk of data and populate the Listbox.

    0 comments No comments