Resolved WPF DataGridRow values to TextBoxes-MVVM

Daz66

Active member
Joined
May 17, 2020
Messages
40
Programming Experience
Beginner
Hi,
I recently made the switch from WInforms to WPF.
I'm trying to learn the mvvm pattern and am having a few issues, I'm a novice, so if any of you guys would have the time to take a look and give me a nudge in the right direction I would be extremely grateful.
I have a DataGrid which populates fine from my ViewModel, I then have a child window with TextBoxes I use for update purpose. My research has led me to believe I need to bind the 'SelectedItem' of the DataGrid to the TextBoxes, I have tried this, but either get no results or just the top row of the Grid regardless of my row selection.

What I have tried.

XML:
<Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="159*" />
            </Grid.ColumnDefinitions>
            <DataGrid x:Name="dgCustomers"
                      Margin="5,60,10,10"
                      AutoGenerateColumns="False"
                      ItemsSource="{Binding Path=GetCustomers}"
                      SelectedItem="{Binding Path=SelectedItem}"
                      Grid.Column="1">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding custid}"
                                        Header="CustomerID" />
                    <DataGridTextColumn Binding="{Binding name}"
                                        Header="Customer" />
                    <DataGridTextColumn Binding="{Binding address}"
                                        Header="Address" />
                    <DataGridTextColumn Binding="{Binding city}"
                                        Header="City" />
                    <DataGridTextColumn Binding="{Binding postcode}"
                                        Header="Postcode" />
                    <DataGridTextColumn Binding="{Binding email}"
                                        Header="Email" />
                    <DataGridTextColumn Binding="{Binding phone}"
                                        Header="Phone" />
                </DataGrid.Columns>
            </DataGrid>
        </Grid>

This produces no results
XML:
<Label Name="lblCustomerName"
                       Grid.Column="0"
                       Grid.Row="2"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Left">Customer Name:</Label>
                <TextBox x:Name="txtCustomerName"
                         Text="{Binding Path=SelectedItem.name}"
                         Grid.Column="1"
                         Grid.Row="2"
                         VerticalContentAlignment="Center"
                         Height="22"
                         AcceptsReturn="True"
                         TextWrapping="Wrap"
                         SpellCheck.IsEnabled="True"
                         Language="en-U" />

My ViewModel

C#:
 public class CustomersViewModel
    {
        string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Business_MySQL"].ConnectionString;

        public CustomersViewModel()
        {
            LoadCustomers();
        }
        public ObservableCollection<Customer>? GetCustomers
        {
            get;
            set;
        }
        public Customer? Selected { get; set; }///Tried binding TextBox to this
        public void LoadCustomers()
        {
            ObservableCollection<Customer> customers = new();
            {
                try
                {
                    using (MySqlConnection? con = new(connectionString))
                    {
                        if (con.State == ConnectionState.Closed)
                        {
                            con.Open();
                        }

                        using (MySqlCommand cmd = new("AllCustomerRecords", con))
                        {
                            cmd.CommandType = CommandType.StoredProcedure;
                            using (MySqlDataReader reader = cmd.ExecuteReader())
                            {
                                while (reader.Read())
                                {
                                    customers.Add(new Customer()
                                    {
                                        id = (int)reader["ID"],
                                        custid = reader["CustomerID"].ToString(),
                                        name = reader["Customer_Name"].ToString(),
                                        address = reader["Customer_Address"].ToString(),
                                        city = reader["City"].ToString(),
                                        postcode = reader["Postcode"].ToString(),
                                        email = reader["Email"].ToString(),
                                        phone = reader["Phone"].ToString()
                                    });
                                    customers = new ObservableCollection<Customer>(customers.OrderBy(x => x.name));
                                }
                                GetCustomers = customers;
                            }
                        }
                    }
                }

                catch (MySqlException ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }
        public Customer SelectedItem { get; set; }///and this
    }

This works in non mvvm Enviroment but not here.

I am learning how to use ICommand to replace code behind :)

C#:
private void btnEditCustomer_Click(object sender, RoutedEventArgs e)
        {
            if (this.edit == null || this.edit.IsClosed)
            {
                this.edit = new CustomersAddEdit();
            }



            if (this.dgCustomers.SelectedItems.Count == 0)
            {
                MessageBox.Show("Please select a row!");
            }
            else if (this.dgCustomers.SelectedItems.Count == 1)
            {
                //var owner = this;
                //DataContext = dgCustomers.SelectedItem;
                var cus = (Customer)dgCustomers.SelectedItem;
                // cus = Selected;
            }
            this.edit.Show();
        }

Thank you in advance.
 
I haven't had a chance to try to set up a repro, but just asking through your code above, it looks like you are not notifying WPF when Selected changes as the selection in the grid changes, so it does not know to pick up new values. Since you started off with Selected being null, it is stuck with an empty value.
 
I haven't had a chance to try to set up a repro, but just asking through your code above, it looks like you are not notifying WPF when Selected changes as the selection in the grid changes, so it does not know to pick up new values. Since you started off with Selected being null, it is stuck with an empty value.

Thanks for the reply,

I've added the 'Selected' property to my Model, is that what you meant?


C#:
 private object Selected;

        public object selected
        {
            get
            {
                return this.Selected;

            }
            set
            {
                if (this.Selected != value)
                {
                    this.Selected = value;
                    RaisePropertyChanged("Selected");
                }
            }
        }
 
Yes.

Although by convention, the public members are in PascalCase, while private members are in camelCase.
 
If you don't mind it being several days late, here are some suggestions. First; in your xaml, you don't need to name anything unless you are going to refer to it in xaml somewhere down the file. Then you will be referring to ElementName. The bindings take care of the connection between the UI and the ViewModel. So your label can be changed to this
C#:
        <TextBlock>
            <Run Text="Customer Name:"/>
            <Run Text="{Binding CustomerName, FallbackValue='CustomerName'}"/>
        </TextBlock>
I like to use the FallbackValue while I design the UI so I can see things better. This is binding to a property in the main window view model.
C#:
        public string CustomerName
        {
            get => customerName;
            set
            {
                customerName = value;
                RaisePropertyChanged();
            }
        }

And your CustomersViewModel has to implement INotifyPropertyChanged. I usually use a ViewModelBaseClass that has the implementation, and then all my ViewModel classes derive from the base.
C#:
        /// <summary>
        /// Property Changes
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// checks to see if the property is changed
        /// </summary>
        /// <param name="propertyName"></param>
        public virtual void RaisePropertyChanged([CallerMemberName] string? propertyName = "")
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
If you are going to bind to an object that has multiple properties, then you will need a DataTemplate to tell which of the properties are going to be displayed. Or to display a complex item. You can start by putting this in the top of you xaml file between the <Window.Resources> tags. Or you can put many such items in a ResourceDictionary and refer to it in the <WindowsResources>.

C#:
<Window x:Class="MyProgram.View.MainWindowView"
        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:vm="clr-namespace:MyProgram.ViewModels"
        mc:Ignorable="d"
        Icon="../Images/MySpecialIcon.ico"
        Title="{Binding DisplayName}" Height="900" Width="1100"
        WindowStartupLocation="CenterScreen"
        WindowState="Maximized">

    <Window.Resources>
        <ResourceDictionary Source="./ViewResources.xaml" />
    </Window.Resources>

    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>

In my example the DataTemplate would be in the ViewResources
C#:
    <DataTemplate x:Key="CustomerTemplate">
        <Border BorderBrush="Transparent"
                    BorderThickness="1">
                <TextBlock>
                        <Run Text="{Binding Name}" />
                        <Run Text=" and " />
                        <Run Text={Binding LastName} />
                </TextBlock>
        </Border>
    </DataTemplate>

Binding to an ObservableCollection<Customer> is the same thing. Define the property and bind to it through a DataTemplate. The SelectedItem simply binds to a property in the ViewModel
C#:
            <ComboBox DockPanel.Dock="Right"
                          Background="Beige"
                          BorderBrush="Transparent"
                          Margin="2,2,3,2"
                          VerticalAlignment="Center"
                          HorizontalAlignment="Stretch"
                          HorizontalContentAlignment="Stretch"
                          SelectedIndex="0"
                          ToolTip="Select a customer."
                          IsTextSearchEnabled="True"
                          IsEditable="True"
                          IsSynchronizedWithCurrentItem="True"
                          SelectedItem="{Binding CustomerSelected}"
                          ItemTemplate="{StaticResource CustomerTemplate}"
                          ItemsSource="{Binding Customers}" />

and in the ViewModel

        private ObservableCollection<Customer> customers= new ObservableCollection<Customer>();
        public ObservableCollection<Customer> Customers
        {
            get => customers;
            set
            {
                customers= value;
                RaisePropertyChanged();
            }
        }

        public Customer CustomerSelected
        {
            get => _customer;
            set
            {
                if (_customer != value && value != null)
                {
                    // Do what you want here
                    RaisePropertyChanged();
                }
            }
        }

And finally, to handle commands you can use code found everywhere on the internet. RelayCommand is an implementation of the ICommand interface. This can also go into the ViewModel Base and then be available in every view model.
C#:
    public class RelayCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;

        public RelayCommand(Action<object> execute) : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            _execute = execute ?? throw new ArgumentNullException("execute");
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        public void Execute(object parameter)
        {
            _execute(parameter);
        }

    }

Use it like this

       private RelayCommand selectFilesCommand;
        public ICommand SelectFilesCommand
        {
            get
            {
                if (selectFilesCommand == null)
                {
                    selectFilesCommand = new RelayCommand(param => YourMethod());
                }
                return selectFilesCommand;
            }
        }

I hope this helps on your WPF journey. I went through this several years ago and I got a lot of help from everywhere. I still do. Skydiver has helped me more than anyone can image.

I forgot to mention. You bind to the command in your xaml with the "Command" property of a control.
C#:
            <Button Content="Start Test"
                        Margin="2"
                        Style="{StaticResource StartButtonStyle}"
                        Command="{Binding StartTestCommand}"
                        IsEnabled="{Binding CanStart}"/>
 
Last edited:
@Scottintexas, thank you so much for taking the time to reply, I've been quietly pulling my hair out in the background trying to figure this out :)

I figured I was missing wiring between my Model and view model as most of the resources I read shows the properties in the ViewModel which made me re-evaluate the whole MVVM concept and makes me wonder if it's not overkill for what i'm trying to achieve.

That said, I know it's the right path so I will work through your code and report back. And yes I concur, @Skydiver has helped me too in the past, I usually wait until I'm close to bouncing the Imac down the garden before I ask but I always get a nudge when I do.

Thanks again
 
Ok, so I have wound things back.
I've tidied up my CustomersModel using the correct naming conventions.
I have added x 2 helper classes to deal with InotifyPropertyChanged and ICommand and now understand a little better regarding inheritance.
What is foxing me now is DataTemplate. If I create DataTemplate in 'view' I lose the designer and my controls disappear. I'm having trouble understanding the relationship between DataTemplate and View.

What I have tried.

XML:
<UserControl.Resources>
        <DataTemplate DataType="{x:Type local:CustomersViewModel}">
            <DockPanel>
                <ToolBarTray DockPanel.Dock="Top"
                             Orientation="Horizontal">
                    <ToolBar Height="50"
                             HorizontalAlignment="Center"
                             Background="Aquamarine"
                             FontWeight="SemiBold">
                        <Button Content="Show Customers"
                                DockPanel.Dock="Right"
                                VerticalAlignment="Center"
                                Command="{Binding Path=GetCustomerCommand}">
                        </Button>
                    </ToolBar>
                </ToolBarTray>
            </DockPanel>
        </DataTemplate>
    </UserControl.Resources>

C#:
 public ICommand GetCustomerCommand
        {
            get
            {
                if (_getCustomerCommand == null)
                {
                    _getCustomerCommand = new RelayCommand(
                        param => LoadCustomers(),
                        param => ID > 0
                    );
                }
                return _getCustomerCommand;
            }
        }

I'm clearly doing something wrong so I will research further, I just wanted to give a progress update.
 
Ok, so I have wound things back.
I've tidied up my CustomersModel using the correct naming conventions.
I have added x 2 helper classes to deal with InotifyPropertyChanged and ICommand and now understand a little better regarding inheritance.
What is foxing me now is DataTemplate. If I create DataTemplate in 'view' I lose the designer and my controls disappear. I'm having trouble understanding the relationship between DataTemplate and View.

What I have tried.

XML:
<UserControl.Resources>
        <DataTemplate DataType="{x:Type local:CustomersViewModel}">
            <DockPanel>
                <ToolBarTray DockPanel.Dock="Top"
                             Orientation="Horizontal">
                    <ToolBar Height="50"
                             HorizontalAlignment="Center"
                             Background="Aquamarine"
                             FontWeight="SemiBold">
                        <Button Content="Show Customers"
                                DockPanel.Dock="Right"
                                VerticalAlignment="Center"
                                Command="{Binding Path=GetCustomerCommand}">
                        </Button>
                    </ToolBar>
                </ToolBarTray>
            </DockPanel>
        </DataTemplate>
    </UserControl.Resources>

C#:
 public ICommand GetCustomerCommand
        {
            get
            {
                if (_getCustomerCommand == null)
                {
                    _getCustomerCommand = new RelayCommand(
                        param => LoadCustomers(),
                        param => ID > 0
                    );
                }
                return _getCustomerCommand;
            }
        }

I'm clearly doing something wrong so I will research further, I just wanted to give a progress update.
I am sorry it took so long to reply.
When you define a data type for a DataTemplate, that tells the program to "show this" when ever you encounter a CustomerViewModel. When I do that, I have my DataTemplate defined like this.
C#:
    <DataTemplate DataType="{x:Type vm:InstrumentViewModel}">
        <vw:InstrumentView/>
    </DataTemplate>

In this case, vw is my defined "local." And the InstrumentView is a user control that has all of the fields and bindings defined. Usually, the ViewModels are going into an ObservableCollection which is the ItemsSource for a control. So as soon as the view model is placed in the collection and RaisePropertyChanged is fired, the view shows up with the view model being the data context.

The command GetCustomerCommand calls LoadCustomers(), is that ID parameter supposed to be passed to LoadCustomers? If so, maybe write it like this
C#:
new RelayCommmand(param => LoadCustomers(param);
The LoadCustomers parameter coming from the button as a CommandParameter.

I'll be more diligent in watching this.
 
I am sorry it took so long to reply.
When you define a data type for a DataTemplate, that tells the program to "show this" when ever you encounter a CustomerViewModel. When I do that, I have my DataTemplate defined like this.
C#:
    <DataTemplate DataType="{x:Type vm:InstrumentViewModel}">
        <vw:InstrumentView/>
    </DataTemplate>

In this case, vw is my defined "local." And the InstrumentView is a user control that has all of the fields and bindings defined. Usually, the ViewModels are going into an ObservableCollection which is the ItemsSource for a control. So as soon as the view model is placed in the collection and RaisePropertyChanged is fired, the view shows up with the view model being the data context.

The command GetCustomerCommand calls LoadCustomers(), is that ID parameter supposed to be passed to LoadCustomers? If so, maybe write it like this
C#:
new RelayCommmand(param => LoadCustomers(param);
The LoadCustomers parameter coming from the button as a CommandParameter.

I'll be more diligent in watching this.

No problem, any help is appreciated whenever it comes :)

I started afresh, taking on board your advice and also further research. I am at a similar point as per my original post; which is getting selected DataGrid row into TextBoxes in a child window. My approach is different now and is as follows:

Using data template in resource dictionary, I'm just using the Name TextBoxes here for testing purposes. I'm not sure if I should be creating x2 templates here targeting the model for TextBoxes binding and the view model for button commands.

C#:
<ResourceDictionary 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:Work_MVVM.ViewModels"
                    mc:Ignorable="d">

    <DataTemplate  DataType="{x:Type local:GetCustomersViewModel}">
        <Grid>
            <DockPanel VerticalAlignment="Top"
                       HorizontalAlignment="Left"
                       Margin="10"
                       LastChildFill="True">

                <Grid x:Name="GridMainLayout"
                      Margin="40,40,30,30"
                      DockPanel.Dock="Left"
                      HorizontalAlignment="Right">

                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="3*" />
                        <ColumnDefinition Width="7*" />
                    </Grid.ColumnDefinitions>

                    <Grid.RowDefinitions>
                        <RowDefinition Height="40" />
                        <!--Row 0-->
                        <RowDefinition Height="40" />
                        <!--Row 1-->
                        <RowDefinition Height="40" />
                        <!--Row 2-->
                        <RowDefinition Height="40" />
                        <RowDefinition Height="40" />
                        <RowDefinition Height="40" />
                        <RowDefinition Height="40" />
                        <RowDefinition Height="40" />
                        <RowDefinition Height="50" />

                    </Grid.RowDefinitions>


                    <TextBlock Grid.Column="0"
                               Grid.Row="2"
                               VerticalAlignment="Center"
                               HorizontalAlignment="Left">
                               <Run Text="Name" />
                    </TextBlock>
                    <TextBox x:Name="txtCustomerName"
                             Text="{Binding Path=Customer.Name}"//Is this ok to bind like this?
                             Grid.Column="1"
                             Grid.Row="2"
                             VerticalContentAlignment="Center"
                             Height="22"
                             AcceptsReturn="True"
                             TextWrapping="Wrap"
                             SpellCheck.IsEnabled="True"
                             Language="en-U" />
                    <Button Command="{Binding Path=SaveCommand}"//Example in view model
                            x:Name="btnSave"
                            Margin="0,10,140,10"
                            ToolTip="Save"
                            Grid.Column="1"
                            Grid.Row="8"
                            Width="60"
                            Height="30"
                            VerticalAlignment="Center"
                            VerticalContentAlignment="Center">
                        <StackPanel Orientation="Horizontal"
                                    VerticalAlignment="Center"
                                    HorizontalAlignment="Center">
                            <Image Source="\save.png"
                                   Height="20"
                                   Width="30" />
                            <TextBlock Margin="0,2,0,0"
                                       FontSize="12"
                                       HorizontalAlignment="Center">Save</TextBlock>
                        </StackPanel>
                    </Button>
                </Grid>
                <ContentControl Content="{Binding Path=Customer}" />
            </DockPanel>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

Then using it like this:
C#:
<Window x:Class="Work_MVVM.Views.AddEditCustomerView"
        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:Work_MVVM.ViewModels"
        d:DataContext="{d:DesignInstance Type=local:GetCustomersViewModel}"
        mc:Ignorable="d"
        Title="AddEditCustomerView"
        Height="600"
        Width="800"
        WindowStartupLocation="CenterScreen">
    <Window.DataContext>
        <local:GetCustomersViewModel />
    </Window.DataContext>
    <Window.Resources>
        <ResourceDictionary Source=" /Views/AddEditCustomerView_Dictionary.xaml" />
    </Window.Resources>
    <Grid>
        <ContentControl Content="{Binding}" />
    </Grid>
</Window>

This all works fine. I have added CustomerSelected property to my view model like this:
C#:
        #region Fields
        private CustomerModel? _customer;
        private ObservableCollection<CustomerModel>? getCustomers;
        private ICommand? _editCustomerCommand;
        private ICommand? _saveCommand;
        private object? customerSelected;
        #endregion

        #region Properties
        public GetCustomersViewModel()
        {
            LoadCustomers();
        }
        public CustomerModel? Customer
        {
            get
            {
                return _customer;
            }

            set
            {
                if (value != _customer)
                {
                    _customer = value;
                    OnPropertyChanged(nameof(Customer));
                }
            }

        }
        public object? CustomerSelected
        {
            get => customerSelected;
            set
            {
                customerSelected = value;
                OnPropertyChanged(nameof(CustomerSelected));
            }
        }
     
        public GetCustomersViewModel(string custSelect)
        {
            CustomerSelected = custSelect;
        }
     
        private ObservableCollection<CustomerModel> cust = new();
        public ObservableCollection<CustomerModel> GetCustomers
        {
            get => cust;
            set
            {
                cust = value;
                OnPropertyChanged(nameof(GetCustomers));
            }
        }

        public ICommand EditCustomerCommand
        {
            get
            {
                if (_editCustomerCommand == null)
                {
                    _editCustomerCommand = new RelayCommand(
                        param => EditCustomers()
                    );
                }
                return _editCustomerCommand;
            }
        }
        public ICommand SaveCommand
        {
            get
            {
                if (_saveCommand == null)
                {
                    _saveCommand = new RelayCommand(
                        param => Save()
                    );
                }
                return _saveCommand;
            }
        }

        #endregion

        #region Private helper
        private void LoadCustomers()
        {
            ObservableCollection<CustomerModel> customers = new();
            {
                try
                {
                    using (MySqlConnection? con = new(connectionString))
                    {
                        if (con.State == ConnectionState.Closed)
                        {
                            con.Open();
                        }

                        using (MySqlCommand cmd = new("AllCustomerRecords", con))
                        {
                            cmd.CommandType = CommandType.StoredProcedure;
                            using (MySqlDataReader reader = cmd.ExecuteReader())
                            {
                                while (reader.Read())
                                {
                                    customers.Add(new CustomerModel()
                                    {
                                        Id = (int)reader["PalmersID"],
                                        Custid = reader["CustomerID"].ToString(),
                                        Name = reader["Customer_Name"].ToString(),
                                        Address = reader["Customer_Address"].ToString(),
                                        City = reader["City"].ToString(),
                                        Postcode = reader["Postcode"].ToString(),
                                        Email = reader["Email"].ToString(),
                                        Phone = reader["Phone"].ToString()
                                    });
                                    customers = new ObservableCollection<CustomerModel>(customers.OrderBy(x => x.Name));
                                }
                                GetCustomers = customers;
                            }
                        }
                    }
                }

                catch (MySqlException ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }
        private void EditCustomers()
        {
            AddEditCustomerView cust = new();

            if (EditCustomerCommand != null)
            {
                if (cust == null || cust.IsClosed)
                {
                    cust = new AddEditCustomerView();
                }

                cust.Show();
                cust.Focus();
            }
        }

        private void Save()
        {

        }
        #endregion

And using it in my DataGrid like this:
C#:
 <DataGrid x:Name="dgCustomers"
                          Margin="5,60,10,10"
                          AutoGenerateColumns="False"
                          ItemsSource="{Binding Path=GetCustomers}"
                          SelectedItem="{Binding Path=CustomerSelected}"
                         
                          Grid.Column="1">
                    <DataGrid.Columns>
                        <DataGridTextColumn Binding="{Binding Custid}"
                                            Header="CustomerID" />
                        <DataGridTextColumn Binding="{Binding Name}"
                                            Header="Customer" />
                        <DataGridTextColumn Binding="{Binding Address}"
                                            Header="Address" />
                        <DataGridTextColumn Binding="{Binding City}"
                                            Header="City" />
                        <DataGridTextColumn Binding="{Binding Postcode}"
                                            Header="Postcode" />
                        <DataGridTextColumn Binding="{Binding Email}"
                                            Header="Email" />
                        <DataGridTextColumn Binding="{Binding Phone}"
                                            Header="Phone" />
                    </DataGrid.Columns>
                </DataGrid>

I figure now I need to add a command that references my DataGrid and CustomerSelected property; then opens the new Window and passes the selected row to it, but I don't know how to make my DataGrid visible in a method in my view model.

I am finding mvvm a steep learning curve but I don't think I'm far off, so I'll keep at it, any pointers greatly appreciated as always.
 
Last edited:
No problem, any help is appreciated whenever it comes :)

I started afresh, taking on board your advice and also further research. I am at a similar point as per my original post; which is getting selected DataGrid row into TextBoxes in a child window. My approach is different now and is as follows:

Using data template in resource dictionary, I'm just using the Name TextBoxes here for testing purposes. I'm not sure if I should be creating x2 templates here targeting the model for TextBoxes binding and the view model for button commands.

C#:
<ResourceDictionary 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:Work_MVVM.ViewModels"
                    mc:Ignorable="d">

    <DataTemplate  DataType="{x:Type local:GetCustomersViewModel}">
        <Grid>
            <DockPanel VerticalAlignment="Top"
                       HorizontalAlignment="Left"
                       Margin="10"
                       LastChildFill="True">

                <Grid x:Name="GridMainLayout"
                      Margin="40,40,30,30"
                      DockPanel.Dock="Left"
                      HorizontalAlignment="Right">

                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="3*" />
                        <ColumnDefinition Width="7*" />
                    </Grid.ColumnDefinitions>

                    <Grid.RowDefinitions>
                        <RowDefinition Height="40" />
                        <!--Row 0-->
                        <RowDefinition Height="40" />
                        <!--Row 1-->
                        <RowDefinition Height="40" />
                        <!--Row 2-->
                        <RowDefinition Height="40" />
                        <RowDefinition Height="40" />
                        <RowDefinition Height="40" />
                        <RowDefinition Height="40" />
                        <RowDefinition Height="40" />
                        <RowDefinition Height="50" />

                    </Grid.RowDefinitions>


                    <TextBlock Grid.Column="0"
                               Grid.Row="2"
                               VerticalAlignment="Center"
                               HorizontalAlignment="Left">
                               <Run Text="Name" />
                    </TextBlock>
                    <TextBox x:Name="txtCustomerName"
                             Text="{Binding Path=Customer.Name}"//Is this ok to bind like this?
                             Grid.Column="1"
                             Grid.Row="2"
                             VerticalContentAlignment="Center"
                             Height="22"
                             AcceptsReturn="True"
                             TextWrapping="Wrap"
                             SpellCheck.IsEnabled="True"
                             Language="en-U" />
                    <Button Command="{Binding Path=SaveCommand}"//Example in view model
                            x:Name="btnSave"
                            Margin="0,10,140,10"
                            ToolTip="Save"
                            Grid.Column="1"
                            Grid.Row="8"
                            Width="60"
                            Height="30"
                            VerticalAlignment="Center"
                            VerticalContentAlignment="Center">
                        <StackPanel Orientation="Horizontal"
                                    VerticalAlignment="Center"
                                    HorizontalAlignment="Center">
                            <Image Source="\save.png"
                                   Height="20"
                                   Width="30" />
                            <TextBlock Margin="0,2,0,0"
                                       FontSize="12"
                                       HorizontalAlignment="Center">Save</TextBlock>
                        </StackPanel>
                    </Button>
                </Grid>
                <ContentControl Content="{Binding Path=Customer}" />
            </DockPanel>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

Then using it like this:
C#:
<Window x:Class="Work_MVVM.Views.AddEditCustomerView"
        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:Work_MVVM.ViewModels"
        d:DataContext="{d:DesignInstance Type=local:GetCustomersViewModel}"
        mc:Ignorable="d"
        Title="AddEditCustomerView"
        Height="600"
        Width="800"
        WindowStartupLocation="CenterScreen">
    <Window.DataContext>
        <local:GetCustomersViewModel />
    </Window.DataContext>
    <Window.Resources>
        <ResourceDictionary Source=" /Views/AddEditCustomerView_Dictionary.xaml" />
    </Window.Resources>
    <Grid>
        <ContentControl Content="{Binding}" />
    </Grid>
</Window>

This all works fine. I have added CustomerSelected property to my view model like this:
C#:
        #region Fields
        private CustomerModel? _customer;
        private ObservableCollection<CustomerModel>? getCustomers;
        private ICommand? _editCustomerCommand;
        private ICommand? _saveCommand;
        private object? customerSelected;
        #endregion

        #region Properties
        public GetCustomersViewModel()
        {
            LoadCustomers();
        }
        public CustomerModel? Customer
        {
            get
            {
                return _customer;
            }

            set
            {
                if (value != _customer)
                {
                    _customer = value;
                    OnPropertyChanged(nameof(Customer));
                }
            }

        }
        public object? CustomerSelected
        {
            get => customerSelected;
            set
            {
                customerSelected = value;
                OnPropertyChanged(nameof(CustomerSelected));
            }
        }
    
        public GetCustomersViewModel(string custSelect)
        {
            CustomerSelected = custSelect;
        }
    
        private ObservableCollection<CustomerModel> cust = new();
        public ObservableCollection<CustomerModel> GetCustomers
        {
            get => cust;
            set
            {
                cust = value;
                OnPropertyChanged(nameof(GetCustomers));
            }
        }

        public ICommand EditCustomerCommand
        {
            get
            {
                if (_editCustomerCommand == null)
                {
                    _editCustomerCommand = new RelayCommand(
                        param => EditCustomers()
                    );
                }
                return _editCustomerCommand;
            }
        }
        public ICommand SaveCommand
        {
            get
            {
                if (_saveCommand == null)
                {
                    _saveCommand = new RelayCommand(
                        param => Save()
                    );
                }
                return _saveCommand;
            }
        }

        #endregion

        #region Private helper
        private void LoadCustomers()
        {
            ObservableCollection<CustomerModel> customers = new();
            {
                try
                {
                    using (MySqlConnection? con = new(connectionString))
                    {
                        if (con.State == ConnectionState.Closed)
                        {
                            con.Open();
                        }

                        using (MySqlCommand cmd = new("AllCustomerRecords", con))
                        {
                            cmd.CommandType = CommandType.StoredProcedure;
                            using (MySqlDataReader reader = cmd.ExecuteReader())
                            {
                                while (reader.Read())
                                {
                                    customers.Add(new CustomerModel()
                                    {
                                        Id = (int)reader["PalmersID"],
                                        Custid = reader["CustomerID"].ToString(),
                                        Name = reader["Customer_Name"].ToString(),
                                        Address = reader["Customer_Address"].ToString(),
                                        City = reader["City"].ToString(),
                                        Postcode = reader["Postcode"].ToString(),
                                        Email = reader["Email"].ToString(),
                                        Phone = reader["Phone"].ToString()
                                    });
                                    customers = new ObservableCollection<CustomerModel>(customers.OrderBy(x => x.Name));
                                }
                                GetCustomers = customers;
                            }
                        }
                    }
                }

                catch (MySqlException ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }
        private void EditCustomers()
        {
            AddEditCustomerView cust = new();

            if (EditCustomerCommand != null)
            {
                if (cust == null || cust.IsClosed)
                {
                    cust = new AddEditCustomerView();
                }

                cust.Show();
                cust.Focus();
            }
        }

        private void Save()
        {

        }
        #endregion

And using it in my DataGrid like this:
C#:
 <DataGrid x:Name="dgCustomers"
                          Margin="5,60,10,10"
                          AutoGenerateColumns="False"
                          ItemsSource="{Binding Path=GetCustomers}"
                          SelectedItem="{Binding Path=CustomerSelected}"
                        
                          Grid.Column="1">
                    <DataGrid.Columns>
                        <DataGridTextColumn Binding="{Binding Custid}"
                                            Header="CustomerID" />
                        <DataGridTextColumn Binding="{Binding Name}"
                                            Header="Customer" />
                        <DataGridTextColumn Binding="{Binding Address}"
                                            Header="Address" />
                        <DataGridTextColumn Binding="{Binding City}"
                                            Header="City" />
                        <DataGridTextColumn Binding="{Binding Postcode}"
                                            Header="Postcode" />
                        <DataGridTextColumn Binding="{Binding Email}"
                                            Header="Email" />
                        <DataGridTextColumn Binding="{Binding Phone}"
                                            Header="Phone" />
                    </DataGrid.Columns>
                </DataGrid>

I figure now I need to add a command that references my DataGrid and CustomerSelected property; then opens the new Window and passes the selected row to it, but I don't know how to make my DataGrid visible in a method in my view model.

I am finding mvvm a steep learning curve but I don't think I'm far off, so I'll keep at it, any pointers greatly appreciated as always.

You have the property CustomerSelected bound to the SelectedItem in the databgrid. So in the property just call a method and pass the "object" to it. The object in your case is a GetCustomer model. So everything is there. It is that object you want to edit. When you're done with it you can just put it back in the collection for the data grid. But you can also just edit it in the datagrid. Either way.
 
Out of respect for the quality of assistance I've received, a quick update.

I managed to achieve a resolution to my original question of sending DataGridRow data to TextBoxes in another window by accessing my grid in the viewModel and using a command to access my original method; it works perfect. I got hung up trying to bind the 'selectedItem' of the grid and have come to the conclusion, rightly or wrongly that this works better if editing the grid directly, which is not my case. Apart from a reference to the grid in my code behind constructor my xaml.cs is now code free.

I'm having fun with ComboBox controls at the mo and just worked out how to get distinct values from my collection:

C#:
public ObservableCollection<Customers> DistinctCities
        {
            get
            {
                return new((IEnumerable<Customers>)GetAllCustomers.DistinctBy(x => x.City).ToList());

                //DistinctBy(x=> new {x.CustomerID, x.City });
                //GroupBy(x => x.City, (Key, group) => group.OrderBy(x=> x.City));
            }
        }

Thanks again for helping me on my way.
 
Back
Top Bottom