WPF/MVVM: Binding DataGrid SelectedItem to TextBoxes in child-view

Ole Martin Gulbrandsen 26 Reputation points
2021-07-10T09:44:56.49+00:00

Hello!

This is a window I've called "LicenseHolderView", it contains a DataGrid, called "dgLicenseHolder".
It gets populated nicely using a BindableCollection (from the Stylet framework -- it's like an ObservableCollection).

113508-image.png

I am able to add rows (from a separate window), and delete rows (from the same window).
What I am not able to do is to open a selected row in a separate window, having the column data populate their respective TextBoxes.

I want to do this by clicking the "Se profil"-button, which opens the ProfileView-window.
It looks like this:

113553-image.png

I have a ViewModel for each view, and in my ProfileViewModel I have the following code, to try and hold the SelectedItem:

private object _selectedItem;  
public object SelectedItem {  
  
   get { return _selectedItem; }  
   set {  
  
          _selectedItem = value;  
          SetAndNotify(ref this._selectedItem, value);  
   }  
}  

SetAndNotify is a Stylet function, and it works like this:
113446-image.png

In LicenseHolderView, my DataGrid is set up like so:

 <DataGrid  
            x:Name="dgLicenseHolder"  
            Canvas.Left="31"  
            Canvas.Top="158"  
            Width="505"  
            Height="557"  
            AutoGenerateColumns="False"  
            BorderBrush="#48bb88"  
            CanUserAddRows="False"  
            CanUserDeleteRows="False"  
            FontSize="20"  
            IsReadOnly="True"  
            Loaded="{s:Action FillDataGridLicenseHolders}"  
            ItemsSource="{Binding Path=LicenseHolders}"   
            SelectedItem="{Binding SelectedItem}"  
            SelectionMode="Single" SelectionUnit="FullRow"   
            SelectionChanged="dgLicenseHolder_SelectionChanged" >  

And my columns like so:

<DataGridTextColumn  
      Width="310"  
      Header="Foretaksnavn"  
      HeaderStyle="{StaticResource CenterGridHeaderStyle}"  
      IsReadOnly="True"  
      Visibility="Visible"   
      Binding="{Binding Path=Foretaksnavn,   
      UpdateSourceTrigger=PropertyChanged}"/>  

In my ProfileView, my TextBoxes are bound like so:

<TextBox  
     Name="txtForetaksnavn"  
     Canvas.Left="150"  
     Canvas.Top="166"  
     Width="162"  
     Height="24"  
     VerticalContentAlignment="Center"  
     FontSize="12"  
     IsReadOnly="True"  
     Text="{Binding SelectedItem, Mode=TwoWay}" />  

But when I run my application, the TextBoxes are not populated.
Stylet is a ViewModel-first framework, and I am running with no code-behind.
(It is a lot like Caliburn.Micro, if people are more familiar with that).

Very appreciative for any help that will correct my thinking, because I am stuck! :-)

Developer technologies Windows Presentation Foundation
Developer technologies C#
0 comments No comments
{count} votes

Accepted answer
  1. Emon Haque 3,176 Reputation points
    2021-07-10T12:15:52.153+00:00

    Not familiar with Stylet or Caliburn, once wanted to look at Prism BUT some articles popped up, while searching about that, in my browser and read that I don't need anything else, for MVVM, other than INotifyPropertyChanged in WPF which actually is true. At some point, to make life easier, you've to get into code behind otherwise it gets too complicated to handle simple matters.

    If you want to populate a second window with various properties of the SelectedItem of your DataGrid of first window, you've to set the SelectedItem as the DataContext of your second window. Here's a small example, in the MainWindow.xaml I've these:

    <Grid Margin="20">  
        <Grid.ColumnDefinitions>  
            <ColumnDefinition/>  
            <ColumnDefinition/>  
        </Grid.ColumnDefinitions>  
        <DataGrid ItemsSource="{Binding Entries}"  
                  SelectedItem="{Binding Selected}"  
                  AutoGenerateColumns="False">  
            <DataGrid.Columns>  
                <DataGridTextColumn Header="Dr Head" Binding="{Binding DrHead}"/>  
                <DataGridTextColumn Header="Cr Head" Binding="{Binding CrHead}"/>  
                <DataGridTextColumn Header="Dr Amount" Binding="{Binding DrAmount}"/>  
                <DataGridTextColumn Header="Cr Amount" Binding="{Binding CrAmount}"/>  
            </DataGrid.Columns>  
        </DataGrid>  
        <Button Grid.Column="1" Content="Test" Click="Button_Click"/>  
    </Grid>  
    

    and in MainWindow.xaml.cs, which you could move to a separate viewmodel if you wish, these:

    public partial class MainWindow : Window  
    {  
        public Entry Selected { get; set; }  
        public ObservableCollection<Entry> Entries { get; set; }  
        public MainWindow() {  
            InitializeComponent();  
            Entries = new ObservableCollection<Entry>();  
            DataContext = this;  
        }  
    
        private void Button_Click(object sender, RoutedEventArgs e) {  
            var win = new SecondWindow() { DataContext = Selected };  
            win.Show();  
        }  
    }  
    public class Entry  
    {  
        public string DrHead { get; set; }  
        public string CrHead { get; set; }  
        public int? DrAmount { get; set; }  
        public int? CrAmount { get; set; }  
    }  
    

    you also could convert that click event into an ICommand/Action if you want. See in the event handler I've set the Selected property, which is bound to the SelectedItem of DataGrid, of the MainWindow/ViewModel as the DataContextofSecondWindow. In the SecondWindow.xaml` I've these:

    <StackPanel>  
        <TextBox Text="{Binding DrHead}"/>  
        <TextBox Text="{Binding CrHead}"/>  
        <TextBox Text="{Binding DrAmount}"/>  
        <TextBox Text="{Binding CrAmount}"/>  
    </StackPanel>   
    

    When I run the App, this is what happens on button click:

    113554-test.gif

    EDIT
    ----
    You can have a static Selected property in your viewModel. I've moved the code into a separate viewModel, MainVM, like this:

    class MainVM  
    {  
        public static Entry Selected { get; set; }  
        public ObservableCollection<Entry> Entries { get; set; }  
        public Command ACommand { get; set; }  
        public MainVM() {  
            Entries = new ObservableCollection<Entry>();  
            ACommand = new Command(show, (o) => true);  
        }  
        void show(object o) {  
            var window = new SecondWindow();  
            window.Show();  
        }  
    }  
    

    and also moved the Entry model in a separate Entry.cs file. You can bind the static Selected property in MainWindow like this:

    <Window x:Class="WPFTest.MainWindow"  
            ...  
            Title="MainWindow" Height="450" Width="800">  
        <Window.DataContext>  
            <local:MainVM />  
        </Window.DataContext>  
        <Grid Margin="20">  
            <Grid.ColumnDefinitions>  
                <ColumnDefinition/>  
                <ColumnDefinition/>  
            </Grid.ColumnDefinitions>  
            <DataGrid ItemsSource="{Binding Entries}"  
                      SelectedItem="{Binding Selected}"  
                      AutoGenerateColumns="False">  
                <DataGrid.Columns>  
                    <DataGridTextColumn Header="Dr Head" Binding="{Binding DrHead}"/>  
                    <DataGridTextColumn Header="Cr Head" Binding="{Binding CrHead}"/>  
                    <DataGridTextColumn Header="Dr Amount" Binding="{Binding DrAmount}"/>  
                    <DataGridTextColumn Header="Cr Amount" Binding="{Binding CrAmount}"/>  
                </DataGrid.Columns>  
            </DataGrid>  
            <Button Grid.Column="1" Content="Test" Command="{Binding ACommand}"/>  
        </Grid>  
    </Window>  
    

    and in SecondWindow like this:

    <Window x:Class="WPFTest.SecondWindow"  
            ...  
            Title="SecondWindow" Height="450" Width="800">  
        <StackPanel DataContext="{Binding Path=(local:MainVM.Selected)}">  
            <TextBox Text="{Binding DrHead}"/>  
            <TextBox Text="{Binding CrHead}"/>  
            <TextBox Text="{Binding DrAmount}"/>  
            <TextBox Text="{Binding CrAmount}"/>  
        </StackPanel>  
    </Window>  
    

    this also will work.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.