question

OleMartinGulbrandsen-4027 avatar image
0 Votes"
OleMartinGulbrandsen-4027 asked OleMartinGulbrandsen-4027 commented

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

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! :-)

dotnet-csharpwindows-wpf
image.png (19.9 KiB)
image.png (30.9 KiB)
image.png (13.2 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

EmonHaque-1485 avatar image
1 Vote"
EmonHaque-1485 answered OleMartinGulbrandsen-4027 commented

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 DataContext` of SecondWindow. 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.


test.gif (522.0 KiB)
· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@EmonHaque-1485, thank you very much for your reply.

I have tried to set things up as you described. Your solution looks simple and logical to me.

But I am having some trouble implementing it, things get complicated because of my need to stay off of code-behind, and when I try to adapt it to a ViewModel first approach, I run into problems.
I get your point, strictly following MVVM design principles is starting to feel like more of a hassle each day.
But for now I have to stick to it.

Therefore, my questions are as follows:

I am not sure how to set "Selected" to be the DataContext for the ProfileView (child window).
IHow can I tell my ProfileView.xaml that the DataContext is to be "Selected"? I can not reference DataContext directly in the ViewModel.
How can I then reference it in the XAML for the ProfileView (child window)?

And, as you suggest, I have a Action bound to a button in the parent window, it calls a method that looks like this:

 public void ShowProfileViewDialog() {
    
      var viewModel = new ProfileViewModel(windowManager);
      bool? result = this.windowManager.ShowDialog(viewModel);
                
       if (result.GetValueOrDefault(true)) {
    
       }
 }

How can I adapt that code to be able to set the DataContext like you did?

Many thanks in advance for your continued assistance!










0 Votes 0 ·
EmonHaque-1485 avatar image EmonHaque-1485 OleMartinGulbrandsen-4027 ·

does your viewModel, var viewModel = new ProfileViewModel(windowManager);, have a Selected property? If it doesn't, have one and set that property in between these lines:

   var viewModel = new ProfileViewModel(windowManager);
   //viewModel.Selected = thisViewModel.SelectedItem;
   bool? result = this.windowManager.ShowDialog(viewModel);

and see whether it works. See the EDIT part for more info ...

1 Vote 1 ·

Thank you very much for your help! It's working now.
Stylet couples the View and the ViewModel together automatically, so it got me confused a bit.
I did what you exemplified; set the DataContext of the Canvas containing the TextBoxes to: DataContext="{Binding Path=(viewmodel:LicenseHolderViewModel.Selected)}, and then it worked like a charm. I would prefer to have the Selected method in the child ViewModel (`ProfileViewModel`) instead, but I couldn't get that working. That's a problem for another day.

Thank you!! :-)

1 Vote 1 ·