Click here to Skip to main content
12,251,352 members (42,100 online)
Rate this:
 
Please Sign up or sign in to vote.
See more: C# XAML WPF
I am developing a User Interface for a host monitoring application, which is already being monitored on database level. I have displayed 2 datagrids on my UI which will populate on run time.These two datagrids are connected by the HostID ( HostID is the Foreign Key in the LogDatagrid).

The First datagrid displays the list of Host with their Status(either Running or Stopped). I would like to display the Log Status of the respective HostID when a user wants to know the status in detail. How to achieve this when a user selects the Host ID in the HostDatagrid ? I have added my XAML and screenshot of my UI. ( the datagrid data is populated from a Database-kindly have a look in the ViewModel code).May I know how do I the binding between the selected Item in the Host datagrid to the respective details getting displayed in the Log datagrid? Kindly help
May I know how I do the binding between selected Item in grid and controls in the detail UI?

here is the Model of my class

public LogFileModel()
    {
 
    }
    private int _hostID;
    public int HostID
    {
        get { return _hostID; }
        set { _hostID= value; OnpropertyChanged("HostID"); }
    }
 
    private string _logid;
    public string LogID
    {
        get { return _logid; }
        set { _logid= value; OnpropertyChanged("LogID"); }
    }
 
    private string _logpath;
    public string LogPath
    {
        get { return _logPath; }
        set { _logPath = value; OnpropertyChanged("LogPath"); }
    }
 
    private DateTime _date;
    public DateTime Date;
    {
        get { return _date; }
        set { _date= value; OnpropertyChanged("Date"); }
    }
 
    private bool _activity;
    public bool LastActivity
    {
        get { return _activity; }
        set { _activity= value; OnpropertyChanged("LastActivity"); }
    }

ViewModel for my log table

LogModel _myModel = new LogModel();
private ObservableCollection<LogFileModel> _logData = new  ObservableCollection<LogFileModel>();
  public ObservableCollection<LogFileModel> LogData
    {
        get { return _logData; }
        set { _logData = value; OnPropertyChanged("LogData"); }
    }
   public LogViewModel()
    {
        initializeload();
        timer.Tick += new EventHandler(timer_Tick);
        timer.Interval = new TimeSpan(0, 0, 3);
        timer.Start();
    }
 
    ~LogViewModel()
    {
        Dispose(false);
    }
 
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                timer.Stop();
                timer.Tick -= new EventHandler(timer_Tick);
            }
            disposed = true;
        }
    }
 
    private void timer_Tick(object sender, EventArgs e)
    {
        try
        {
            LogData.Clear();
            initializeload();
        }
        catch (Exception ex)
        {
            timer.Stop();
            Console.WriteLine(ex.Message);
 
        }
    }
 
    private void initializeload()
    {
        try
        {
            DataTable table = _myModel.getData();
 
            for (int i = 0; i < table.Rows.Count; ++i)
                LogData.Add(new LogFileModel
                {
                   HostID= Convert.ToInt32(table.Rows[i][0]),
                   LogID = table.Rows[i][1].ToString(),
                   LogPath = table.Rows[i][2].ToString(),
                   Date = Convert.ToDateTime(table.Rows[i][3]),
                   LastAcivity= table.Rows[i][4].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 LogModel
    {
        public DataTable getData()
        {
            DataTable ndt = new DataTable();
            SqlConnection sqlcon = new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
            sqlcon.Open();
            SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [LocalDB].[dbo].[LogFiles]", sqlcon);
            da.Fill(ndt);
            da.Dispose();
            sqlcon.Close();
            return ndt;
        }
    }
}


EDIT
I have added a new MainViewModel to my existing program

public class MainViewModel : ViewModel
{
    private LogViewModel _SubLogViewModel = new LogViewModel();
    public LogViewModel SubLogViewModel
    {
        get
        {
            return _SubLogViewModel;
        }
        set
        {
            if (_SubLogViewModel != value)
            {
                _SubLogViewModel = value;
                OnpropertyChanged("SubLogViewModel");
            }
        }
    }
 
    private HostViewModel _SubHostViewModel = new HostViewModel();
    public HostViewModel SubHostViewModel
    {
        get
        {
            return _SubHostViewModel;
        }
        set
        {
            if (_SubHostViewModel != value)
            {
                _SubHostViewModel = value;
                OnpropertyChanged("SubHostViewModel");
            }
        }
    }
 
    private Host _SelectedHost;
    public Host SelectedHost
    {
        get
        {
            return _SelectedHost;
        }
        set
        {
            if (_SelectedHost!= value)
            {
                _SelectedHost= value;
                OnpropertyChanged("SelectedHost");
                if(this.SelectedHost != null && this.SubLogViewModel != null)
                {
// I dont know how to get the values for Log grid. here Loadlogs is just an imaginary method I have added , it is not defined in my program.
 
                    this.SubLogViewModel.LoadLogs(this.SelectedHost);
                }
            }
        }
    }
}


EDIT : New Xaml

<Grid>
    <Grid.DataContext>
        <host:MainViewModel />
    </Grid.DataContext>
 
    <DataGrid Name="hostDataGrid" DataContext="{Binding SubHostViewModel}" SelectedItem="{Binding SelectedHost, Mode=TwoWay}">
        ...
    </DataGrid>
 
    <DataGrid Name="LogDatagrid" DataContext="{Binding SubLogViewModel}">
 
    </DataGrid>
</Grid>



Kindly help me to proceed further.
Thanks to whom ever it may concern.
Posted 6-Feb-13 10:31am
Edited 19-Feb-13 3:56am
v4
Rate this: bad
 
good
Please Sign up or sign in to vote.

Solution 1

First, if you have viewmodels for each grid, you need to collapse them into one viewmodel. The pattern is one viewmodel per VIEW... not one per model or datacomtext.

Second, both data models should be a property on that view model.

Third, each grid should bind the DATASOURCE to the respective property, not the datacontext.

Fourth, you should create a selected record property on the viewmodel of the type hostIdRecord or whatever you are calling that class. You should then two-way bind the grid's selected (or selectedrow) dependency property to the property on your view model. When a user selects a new row, that property will be set to the object instance backing that row.

When the selected item gets "set", inspect the new record in the property setter in the view model. Use that information to lookup and create the collection of log records. Assign that collection or table to the log data property that the other grid's datasource is bound to. Bang goes the dynamite; it all starts working.

You seem to have some fundamentals but there also seem to be some key misunderstandings. Your model is essentially right but the plumbing is wrong. I strongly recommend the "MVVM Architect's Survival Guide" from Packt Publishing. Great book to walk you through mvvm.
  Permalink  
Comments
BuBa1947 7-Feb-13 4:49am
   
Many Thanks for your reply Jason.
but I have a query, each of the datagrid ( for eg. here is HOST and LOG file) has to select the items from their respective tables from the database. how can I implement it when I have one ViewModel ?
Jason Gleim 7-Feb-13 9:59am
   
Your viewmodel is the datacontext for the entire view... not just the datagrids in the view. The view model needs two properties which expose the data tables... one for the host grid and one for the log grid. You would have one for the log as well that exposes the log model on the view model. I used an Observable collection to hold the host records in this example but you can use any type which implements IEnumerable. (Colection, Array, DataTable, etc)

Note that you can expose as many data models on a single view model class as you need to... in your case it would be one for each grid. But it could be 20 if that is what the view called for.

Since these properties on the view model expose the records (your data models), and the viewmodel is the data context for your view, the grids will bind to the data via the model properties exposed on the view model. Because your view model implements INotifyPropertyChanged, the grids will, behind the scenes, hook the PropertyChanged event on the view model and look for the property they have bound to (hostData or logData for example) to change. When they detect that event, they know to re-populate the grid with the contents of those data objects. You don't have to do a thing... it just happens.

Each grid then, exposes a property called 'SelectedRow' which will be set to the instance of the record which has been selected by the user. You bind that to a property on the viewmodel using a two-way binding (SelectedRow="{binding hostSelectedRow, mode=TwoWay}") which will tie the property on the view model to the selected row on the grid. The selected row on the grid and the property value on the viewmodel automatically stay in sync because of the binding engine. You can, therefore, know that the user selected a new row when the property setter for the selected row property on the view model changes... the grid is the source of that change and it is in response to the user selecting a new row.

You should consider changing the data model as well. I typically use the model to encapsulate everything to do with the data. I usually put a LoadData and a SaveData method on the model (as appropriate) as well as a property that exposes the current collection of data records. The data source for a grid is then bound to that property on the model via a property on the view model. To unwind that, the view model has a public property of type data model. The data model has a public property which is a collection of the data rows. The grid's datasource property is bound to ViewModelDataModelProperty.DataModelDataProperty. Then, in the setter for the selected row property (on the host grid) you would call something like _logDataModel.LoadData(value.uniqueKey).

The event flow then would look something like this:

- View loads and initializes an instance of the view model. The view sets its data context to that instance.
- The view model creates instances of the data models.
- The view model calls LoadData on the host data model.
- The host data model fetches the host list from whatever and sets its hostData property to the resulting data set.
- The host grid's datasource is bound to the host data model's hostData property via the view model. (viewModelHostDataModelProperty.hostData) It detects that the hostData property has changed (it is now populated with the host list) so it updates its visual displaying the list.
- The user clicks/touches a row in the host list.
- The host grid sets its SelectedRow property equal to the instance of the selected host record. The SelectedRow property is bound to a corresponding property on the ViewModel.
- The property setter for the SelectedRow property in the view model class fires. In the setter, you call logDataModel.LoadData passing the unique key from the selected host record.
- The load data method in the log data model takes the unique key and fetches the log records for the selected host. It, in turn, sets the logData property to the resulting data set.
Jason Gleim 7-Feb-13 10:30am
   
- The load data method in the log data model takes the unique key and fetches the log records for the selected host. It, in turn, sets the logData property to the resulting data set.
- The log grid's datasource property is bound to the logData property on the log model via the exposed property on the view model (viewModelLogDataModelProperty.logData). It detects that there is a new set of data exposed on that property and it updates its visual to display the new record set.

It may help too, to visualize it like:

/- HOST MODEL
VIEW <-> VIEWMODEL
\- LOG MODEL

Long answer... took two replies... sorry if you got 20 e-mail updates while I was trying to compose this... I hope it helps!

Jason
BuBa1947 19-Feb-13 9:57am
   
Jason, kindly have a look at my edited question. I have added new code blocks. is this approach ok ?
BuBa1947 19-Feb-13 9:19am
   
I am gonna try and get back to you Jason. Thanks a lot for taking effort to explain.. I actually saw this reply very late, as i got many e-mails and thought it was a spam.
Rate this: bad
 
good
Please Sign up or sign in to vote.

Solution 2

You are getting much closer to getting this to work. The edits are moving in the right direction but lets cover some changes you need to make:

- You created a MainViewModel and set the DataContext of the root grid in your xaml to that ViewModel. That is perfect... exactly what you need to do. Now all the bindings and commands in the xaml will be tied back to that MainViewModel. (and remember that... everything on the view will tie to the ViewModel)

- You added HostViewModel and LogViewModel as public properties on the MainViewModel. You don't actually need these classes. They are adding unnecessary complexity. Rather, you need a HostModel and LogModel that expose the data and methods to get the data... nothing more and nothing less. Here is what the LogModel should look like:
public class LogModel
{
    private ObservableCollection<logfilemodel> _logData = new  ObservableCollection<logfilemodel>();
    public ObservableCollection<logfilemodel> LogData
    {
        get { return _logData; }
        set { _logData = value; OnPropertyChanged("LogData"); }
    }
 
    public void GetData(string hostName)
    {
        try
        {
            DataTable ndt = new DataTable();
            SqlConnection sqlcon = new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
            sqlcon.Open();
            SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [LocalDB].[dbo].[LogFiles]", sqlcon); // Add filter for selected host
            da.Fill(ndt);
            da.Dispose();
            sqlcon.Close();
 
            // Clear the collection before we add rows to it.
            LogData.Clear();
 
            for (int i = 0; i < ndt.Rows.Count; ++i)
                LogData.Add(new LogFileModel
                {
                    HostID = Convert.ToInt32(ndt.Rows[i][0]),
                    LogID = ndt.Rows[i][1].ToString(),
                    LogPath = ndt.Rows[i][2].ToString(),
                    Date = Convert.ToDateTime(ndt.Rows[i][3]),
                    LastAcivity = ndt.Rows[i][4].ToString(),
                });
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    private void OnPropertyChanged(string propertyname)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyname));
    }
}</logfilemodel>

Notice that all we are doing is getting the log entries for the selected host (this is important... you MUST filter by the selected host) and putting them into the collection? We clear the collection of records every time we fetch data. This ensures that only the rows we are interested in are exposed via the LogData property. (You could make this more intelligent by screening for only newer records via a last update timestamp or something similar only adding new records then clearing the collection when you see a different selected host identifier.)

With the LogModel looking like this, you make the MainViewModel.SubLogViewModel property of type LogModel. This, in turn, exposes the data as MainViewModel.SubLogModel.LogData.

- Back in your SelectedHosts property setter, you call _subLogModel.GetData(selectedHostIdentifier). This will make your LogModel fetch the log entries for the selected host. The next line should be OnpropertyChanged("SubLogModel"). Yes... you raise that event from the selected host setter after you have fetched the data. That will raise the property changed event on the log data so that the log grid knows the records have changed.

- Finally, and I've said it before, you MUST change 'DataContext="{Binding SubLogViewModel}"' to 'DataSource="{Binding SubLogModel.LogData}"' on your grids. The DataContext specifies a class which implements INotifyPropertyChanged for the grid to bind it's properties and events to. DataSource specifies a class which implements IEnumerable that holds the data to display. Generally, unless there is a really, really good reason to do it, you should not override the DataContext of any control on a page. For grids, assign the data source to the source of the data... not the data context. (Yes... this is confusing terminology).

Last but not least, you have a timer for updating the log data. This should NOT be in the model but should be in the ViewModel. Put the timer in the MainViewModel and start it in the SelectedHost property setter. When it times out, call _subLogModel.LogData.GetData(selectedHostIdentifier) followed by the OnPropertyChanged method just like in the SelectedHost setter. This will make the log data model re-fetch the data and re-populate the data set (updating the display grid) each time the timer fires. If you don't want to toss the entire data set, you would have to add some logic to catch only new records and simply update them into the collection as I mentioned above.

Good luck.
Jason
  Permalink  
Comments
BuBa1947 20-Feb-13 9:18am
   
Hello Mr.Jason,

First of all, let me thank u.. I tried this above Model and finally I got the output which i needed. I will post the solution down. This time I have used my Person database which has two tables, Persons and Persondetails, where it should display the data in the Persondetails datagrid when a row is selected on the Persons Datagrid.

I have created class for Person and PersonDetail, One ViewModel as MainViewModel and a View which is present already. but I dont know where to place the Timer here ( as you have mentioned above "don't place the timer in the Model, but rather in ViewModel"). But I am afraid to touch my code which is running perfectly now.

the need for timer is to update my grid every 3 seconds because I have many incoming new data in my database. kindly have a look at my updated solution & output. ( this works fine without the Timer). Kindly have a look at Solution 3.
Jason Gleim 21-Feb-13 16:46pm
   
Using your example and extending it to your other problem, the issue that we are facing is that we need to update the data on a regular basis. So let's assume person 1 gets a new address every 2 seconds, you want the lower grid to update every 3 seconds to show the new data. This isn't difficult, you just need to understand the relationship between the selected person in the top grid and the address data in the lower grid. You have correctly bound the selected item (selected person) to a property on your view model. That is where it all starts...

If you set a breakpoint on the setter for the Selectedperson property (on the view model) you will see that the grid passes an object of type Person in as the value when the user selects a row in the top grid. I'm assuming that in that setter you are assigning the results of Person.GetDetails to the Details property on the View Model. This, in turn, would update the rows in the details grid. You say that all of this is working, which is good, so now we simply need to build off of that.

When a user selects a new row, it fires off the method to get the details for the selected person. You essentially want the same thing to happen but on a regular interval. So create a timer in your view model. When the selected row is changed (and the setter is executed), after you assign the details to the Details property, start the timer.

In the timer's callback (for the tick event), disable the timer then simply replicate the code you are already using to set the details. So it would probably be something like:
Details = Selectedperson.GetDetails();
Then, re-start the timer. This will cause the Details property to be updated each time the timer ticks pulling in any new rows that you would have for that person. (... or host) Make sure you check that Selectedperson is not null before you make that call and restart the timer. If it becomes null, you don't want to keep firing the timer.
BuBa1947 26-Feb-13 9:28am
   
Hello Mr.Jason,
i am trying to work on this selecteditem on three datagrid's now. but i am stuck with a silly error. if u have time pls have a look over this post - http://www.codeproject.com/Questions/553025/Selectedplusitemplusforplusmutilpleplusdatagrid-27

Thank you
BuBa1947 25-Feb-13 9:50am
   
Hello Mr.Jason,

actually this explanation is quite confusing. is it possible to explain with some example.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

  Print Answers RSS
Top Experts
Last 24hrsThis month


Advertise | Privacy | Mobile
Web02 | 2.8.160426.1 | Last Updated 20 Feb 2013
Copyright © CodeProject, 1999-2016
All Rights Reserved. Terms of Service
Layout: fixed | fluid

CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100