Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Datagrid selectedItem and databinding

I have two datagrid displayed on my UI. When I select a particular row on datagrid 1, I would like to display the details of the datagrid 1 on datagrid 2. I am populating the datagrid data from a database. here is the two database table structure.

Note: both the table are mapped by the personid in the database

Person Table

PersonDetails Table

here is the code so far I have tried

Baseclass.cs

public class Baseclass
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void SetProperty<T>(ref T member, T value, [CallerMemberName] string propertyName = null)
    {
        member = value;
        this.RaiseNotification(propertyName);
    }

    protected void RaiseNotification(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

person.cs

public class person : Baseclass
{

    private int personID;
    public int PersonID
    {
        get { return personID; }
        set { this.SetProperty<int>(ref this.personID, value); }
    }

    private string firstName;
    public string FirstName
    {
        get { return firstName; }
        set { this.SetProperty<string>(ref this.firstName, value); }
    }

    private string lastName;
    public string LastName
    {
        get { return lastName; }
        set { this.SetProperty<string>(ref this.lastName, value); }
    }

    Model _personModel = new Model();
    private ObservableCollection<person> _person = new ObservableCollection<person>();
    public ObservableCollection<person> Getpersons
    {
        get { return _person; }
        set { _person = value; OnPropertyChanged("GetPersons"); }
    }

    public person()
    {
        initializeload();
    }

    private void initializeload()
    {
        try
        {
            DataTable table = _personModel.getData();

            for (int i = 0; i < table.Rows.Count; ++i)
                Getpersons.Add(new person
                {
                 PersonID = Convert.ToInt32(table.Rows[i][0]),
                 FirstName = table.Rows[i][1].ToString(),
                 LastName = table.Rows[i][2].ToString(),
                });
        }

        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyname)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyname));
    }

    public class Model
    {
        public DataTable getData()
        {
            DataTable ndt = new DataTable();
            SqlConnection sqlcon = new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
            sqlcon.Open();
            SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [Person].[dbo].[persons]", sqlcon);
            da.Fill(ndt);
            da.Dispose();
            sqlcon.Close();
            return ndt;
        }
    }

}

PersonDetail class

public  class PersonDetails : Baseclass
{
    private int personID;
    public int PersonID
    {
        get { return personID; }
        set { this.SetProperty<int>(ref this.personID, value); }
    }

    private string address;
    public string Address
    {
        get { return address; }
        set { this.SetProperty<string>(ref this.address, value); }
    }

    private string pos;
    public string Position
    {
        get { return pos; }
        set { this.SetProperty<string>(ref this.pos, value); }
    }

    DetailsModel _detailModel = new DetailsModel();
    private ObservableCollection<PersonDetails> _details = new ObservableCollection<PersonDetails>();
    public ObservableCollection<PersonDetails> GetDetails
    {
        get { return _details; }
        set { _details = value; OnPropertyChanged("GetDetails"); }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyname)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyname));
    }

    public PersonDetails()
    {
        initializeload();
    }

    private void initializeload()
    {
        try
        {
            DataTable table = _detailModel.getData();

            for (int i = 0; i < table.Rows.Count; ++i)
                GetDetails.Add(new PersonDetails
                {
                    PersonID = Convert.ToInt32(table.Rows[i][0]),
                    Address = table.Rows[i][1].ToString(),
                    Position = table.Rows[i][2].ToString(),                        
                });
        }

        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
    public class DetailsModel
    {
        public DataTable getData()
        {
            DataTable ndt = new DataTable();
            SqlConnection sqlcon = new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
            sqlcon.Open();
            SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [Person].[dbo].[personDetails]", sqlcon);
            da.Fill(ndt);
            da.Dispose();
            sqlcon.Close();
            return ndt;
        }
    }
}

MainViewModel.cs

    public class MainViewModel : Base, INotifyPropertyChanged
{
    public MainViewModel()
    {
    }

    private ObservableCollection<person> personValues;
    public ObservableCollection<person> Persons
    {
        get { return personValues; }
        set
        {
            this.SetProperty<ObservableCollection<person>>(ref this.personValues, value);
        }
    }

    private ObservableCollection<PersonDetails> detailsValues;
    public ObservableCollection<PersonDetails> Details
    {
        /* This is correct below ?? I have an error as 
        'PersonDemo.MainViewModel' does not contain a definition for 'GetDetails' and no extension method 'GetDetails' accepting a first argument of type 'PersonDemo.MainViewModel' could be found (are you missing a using directive or an assembly reference?)*/
        get { return this.GetDetails(this.Selectedperson.PersonID); }
    }

    private person selectedValue;
    public person Selectedperson
    {
        get { return selectedValue; }
        set
        {
            this.SetProperty<person>(ref this.selectedValue, value);
            this.RaiseNotification("Details");
        }
    }


}

XAML

  <Grid>
  <DataGrid Margin="100,20,116,211" ItemsSource="{Binding Persons}" SelectedItem="{Binding Selectedperson}"  />
  <DataGrid Margin="100,130,116,101" ItemsSource="{Binding Details}" />
  </Grid>

can anybody help to proceed in writing the MainViewModel? I am stuck here since weeks.

like image 615
Indhi Avatar asked Feb 11 '13 16:02

Indhi


2 Answers

First, I recomend to use the INotifyPropertyChanged interface in the base class. Maybe you use it and forget write it in the sample code. For make this with MVVM patter, you need to implement a ViewModel for both data grids. Let's call it in our example BothGridViewModel, you may call it as you want. Then in this view model you need the collection of all person, let's call it AllPerson, then you need to have a property of Person type, when you will have the selected person in the grid, let's call it SelectedPerson. It will be something like this:

public class BothGridViewModel:INotifyPropertyChanged
{
    ...
    public ObservableCollection<Person> AllPersons {...}
    public Person SelectedPerson {...}
    ...
}

All you need, is to set it in the DataContext of the View. And bindings:

<YourView DataContext={Binding SomeBothGridViewModelClass}>
    <Grid>
     <DataGrid Margin="100,20,116,211" ItemsSource="{Binding AllPersons}" SelectedItem="{Binding SelectedPerson}"  />
     <DataGrid Margin="100,130,116,101" ItemsSource="{Binding SelectedPerson.Getpersons}" /> <!--Or some SelectedPerson.PersonDetails.GetDetails-->
  </Grid>
</YourView DataContext={Binding SomeBothGridViewModelClass}>

I think this is the good view model structure for make what you want. Hope this helps you...


EDIT


I see your point now, you have two database tables, one for the main properties and other for the details. I see two good ways for doing this: 1) The first one, is that I don't belive that the second datagrid is necesary due for each person you don't have a collection of details. Instead you may use a grid, and other controls for showing the properties. Also I think you must to implement a view model for the person, for instance:

public class PersonViewModel:INotifyPropertyChanged
{
     public string FirstName {...}
     public string LastName {...}
     //Other properties

     public PersonDetails Details {...}
}

Then in the grid, you may bind the items source to a collection of PersonViewModel then you can make bindings to the selected item of the grid, for instance:

<Grid>
    <DataGrid x:Name="dataGrid" ItemsSource={Binding AllPersonViewModels}/>
    ...
    <!--Then some combo box, text block or text box binding to a property of the selected item's details-->
          ...
          <TextBox Text={Binding SelectedItem.Details.Address, ElementName=dataGrid}/>
          ...
<Grid>

2) The second way I think could be done, is showing all data in the same datagrid. For this you need to do the PersonViewModel class in this way:

public class PersonViewModel:INotifyPropertyChanged
{
     public string FirstName {...}
     public string LastName {...}
     //Other properties

     //Then the Details properties
     public string Address {...}
     //...
     //Note this class is like a wrapper for person, and person details
}

This way is a bit simpler, but maybe cause an unwanted database over access.


EDIT 2


After have a look of your code, I have to say a few things about it: In the PersonViewModel and PersonDetailViewModel you should use a DispatcherTimer instead a Timer, because we are in Wpf. Other thing, maybe you should use a Repository pattern, to create a PersonRepository and a PersonDetailRepository where to put all the DB comunication, in fact, PersonViewModel and PersonDetailViewModel are a in some way, repositories, but by now you don't need to change it, it should work. I'm going to show you here the code of the MainViewModel, I nodified it to have the SelectedPersonDetail property, in this way, all you need to do is make a binding in the View:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using tryout13022013.PersonViewModels;
using System.ComponentModel;
using tryout13022013.DetailsViewModel;
using tryout13022013.PersonModel;

namespace tryout13022013
{
    public class MainViewModel
    {
        private PersonViewModel _subPerson = new PersonViewModel();
        public PersonViewModel SubPerson
        {
            get 
            {
                return _subPerson;
            }
            set
            {
                if (_subPerson != value)
                {
                    _subPerson = value; OnPropertyChanged("SubPerson");

                }
            }
        }


        private PersonDetailsViewModel _subDetail = new PersonDetailsViewModel();
        public PersonDetailsViewModel SubDetail
        {
            get { return _subDetail; }
            set 
            {
                _subDetail = value; OnPropertyChanged("SubDetail");
            }

        }

        private Person _selectedPerson;
        public Person SelectedPerson
        {
            get { return _selectedPerson; }
            set {
                if (_selectedPerson != value)
                {
                    _selectedPerson = value;
                    OnPropertyChanged("SelectedPerson");
                    OnPropertyChanged("SelectedPersonDetail");  //In this way when Change the Selected Person, the Selected Detail will be changed again...
                    //if (this.SelectedPerson != null && this.SubDetail != null)
                    //{
                       // I dont know how to call the PersonDetailsViewModel class like a method here in order to load its data. kindly help
                       //this.SubDetail.MethodforLoadingPersonDetails(this.SelectedPerson);
                    //}
                }
            }

        }

        public PersonDetails SelectedPersonDetail
        {
            get
            {
                if (SubDetail == null || SelectedPerson ==null)
                    return  null;
                return SubDetails.DetailsData.FirstOrDefault(detail => detail.PersonID == SelectedPerson.PersonID);
            }           

        }


        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyname)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyname));
        }
    }
}

And this is an instace of a binding you can make in your View, in this case for selecting the item in the second grid:

<Window x:Class="tryout13022013.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:person="clr-namespace:tryout13022013.PersonViewModels"
    xmlns:details="clr-namespace:tryout13022013.DetailsViewModel"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <DataGrid ItemsSource="{Binding SubPerson.PersonData}"  SelectedItem="{Binding SelectedPerson, Mode=TwoWay}"
        AutoGenerateColumns="False" Height="77" HorizontalAlignment="Left"
        Margin="101,26,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="auto" >
        <DataGrid.DataContext>
            <person:PersonViewModel/>
        </DataGrid.DataContext>
        <DataGrid.Columns>
            <DataGridTextColumn Header="ID" Width="auto" Binding="{Binding PersonID}"/>
            <DataGridTextColumn Header="First Name" Width="auto" Binding="{Binding FirstName}"/>
            <DataGridTextColumn Header="Last Name" Width="auto" Binding="{Binding LastName}"/>     
        </DataGrid.Columns>
    </DataGrid>


    <DataGrid ItemsSource="{Binding SubDetail.DetailsData}" 
        AutoGenerateColumns="False" Height="77" HorizontalAlignment="Left" 
        Margin="101,135,0,0" Name="dataGrid2" VerticalAlignment="Top" Width="255" SelectedItem="{Binding SelectedPersonDetail, Mode=OneWayToSource}">
        <DataGrid.DataContext>
            <details:PersonDetailsViewModel/>
        </DataGrid.DataContext>
        <DataGrid.Columns>
            <DataGridTextColumn Header="ID" Width="auto" Binding="{Binding PersonID}"/>
            <DataGridTextColumn Header="Address" Width="auto" Binding="{Binding Address}"/>
            <DataGridTextColumn Header="Position" Width="auto" Binding="{Binding Description}"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

I forgot it, you need to make the binding to SubPerson.PersonData and SubDetail.DetailsData, these are the collection. Check it out...

like image 152
Raúl Otaño Avatar answered Sep 27 '22 22:09

Raúl Otaño


Having had a chat with Buba1947 in WPF room, I've concluded that she's actually got a 1-1 relation split table in database that she's actually trying to show as such in the split data grids.

I've proposed her to create a PersonViewModel on top of Person & PersonDetails tables so her MainViewModel contains only one ObservableCollection<PersonViewModel> under the Persons property to which we shall bind the two DataGrids.

so MainViewModel.cs (the DataContext) has:

private ObservableCollection<PersonViewModel> personValues; 
public ObservableCollection<PersonViewModel> Persons 
{ 
    get { return personValues; } 
    set { this.SetProperty<ObservableCollection<PersonViewModel>>(ref this.personValues, value); } 
} 

The data adaptor is changed to bring the data in using an inner join with relevant query being something along the lines of this (which fills the Persons property above):

"SELECT * FROM [Person].[dbo].[persons] INNER JOIN [Person].[dbo].[personDetails] ON [Person].[dbo].[persons].[Id] = [Person].[dbo].[personDetails].[Id]"

Next we shall bind a CollectionViewSource to Persons in Xaml:

<Window.Resources>
    <CollectionViewSource x:Key="PersonsData" Source={Binding Persons} />
</Window.Resources>

and bind DataGrids to this with individual columns:

<DataGrid ItemsSource={Binding Source={StaticResource PersonsData}} AutoGenerateColumns="false">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Person Id" Content={Binding PersonId} />
        <DataGridTextColumn Header="First Name" Content={Binding FirstName} />
        <DataGridTextColumn Header="Last Name" Content={Binding LastName} />
    </DataGrid.Columns>
</DataGrid>

<DataGrid ItemsSource={Binding Source={StaticResource PersonsData}} AutoGenerateColumns="false">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Person Id" Content={Binding PersonId} />
        <DataGridTextColumn Header="Address" Content={Binding Address} />
        <DataGridTextColumn Header="Position" Content={Binding Position} />
    </DataGrid.Columns>
</DataGrid>

Note: there may be typo in here as I haven't got VS to hand, please let me know if I need to fix something. I've written all of this from memory.

like image 34
Maverik Avatar answered Sep 27 '22 21:09

Maverik