views:

133

answers:

1

Hi All.

I'm new to WPF/C#/NET but I have been learning by way of coding some small exercises.

Anyway, I'm stuck. I have seached on here and google and can't find the answer or maybe more correctly can't find an answer I can make sense of.

My problem is this... using the Entity Framework I have two tables. One for Employee details and one for Company details. Employees work for 0 or 1 Company's.

I would like to, via WPF/XAML, define a datagrid to navigate Employees. But within each employee row I would like to show the name of the Company they work for (if there is a relationship) or "Unemployed" in the cases where there is no related Company record.

I have not given details of the tables as it really doesnt matter - the problem is displaying concatentated information from parent/child relationships in a single datagrid.

I dont know what the best approach to this kind of problem is, I'm assuming WPF/DataGrid, so I would really appreciate help on how to go about doing it, the binding (assuming WPF) or even an example of the WPF/XAML

Thanks in advance.

+1  A: 

There are many ways to accomplish this - one way you might try is to create a View Model that encapsulates the data you want to display - e.g.

public class EmployeeViewModel
{
    private readonly Employee _employee;

    public EmployeeViewModel(Employee employee)
    {
        _employee = employee;
    }

    public string Name { get { return _employee.Name; } }
    public string CompanyName { get { return _employee.Company == null ? "Unemployed" : _employee.Company.CompanyName; } }
}

Then, given an IEnumerable<Employee> you can project your employee data into this view model and set it as the ItemsSource of your DataGrid - e.g.

IEnumerable<Employee> employees = GetEmployeesFromDatabase();    
DataGrid1.ItemsSource = employees.Select(x => new EmployeeViewModel(x));

You would normally set the ItemsSource via a xaml binding here rather than setting it directly in code but that would involve the use of a parent ViewModel set as the DataContext of the View and I'm trying to keep things simple.

Another way to accomplish this with a DataGrid would be to forgo the use of a View Model, bind directly to an IEnumerable<Employee> collection and set the column bindings explicitly - e.g.

<DataGrid ItemsSource="{Binding Employees}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Employee Name" Binding="{Binding Name}" />
        <DataGridTextColumn Header="Company Name" Binding="{Binding Company.Name}" />
    </DataGrid.Columns>
</DataGrid>

Note that with the second example, you won't get "Unemployed" appearing in the Company Name column where there is no associated company for an employee.

EDIT: To clarify the point about setting the items source for your Grid from a property on a 'main' view model bound to the View, you might create a ViewModel class that represents the whole of the current view. e.g.

public class MainViewModel
{
    private readonly IEnumerable<Employee> _employees;

    public MainViewModel(IEnumerable<Employee> employees)
    {
        _employees = employees;
    }

    public IEnumerable<Employee> Employees
    {
        get { return _employees; }
    }
}

You'd set this as the DataContext for the Window / UserControl.

e.g. in simple cases you could do this in the constructor for your Window (although calls to the database should really be asynchronous for WPF apps where possible).

 public MainWindow()
 {
     InitializeComponent();
     DataContext = new MainViewModel(GetAllEmployees());
 }

Then you can set up the binding on your Grid like this:

<DataGrid ItemsSource="{Binding Employees}" ...

The data context for the main window is then used for all the child controls contained therein except where you explicitly set the DataContext on a child control. This technique becomes useful where you have more complex ViewModels with many properties and commands. If you are interested in finding out more you should take a look at the MVVM pattern and either or both of the Prism / Caliburn frameworks.

Steve Willcock
Thanks Steve.A couple of questions. In my code I'm not setting the grid's ItemsSource like your example. I simply have ItemsSource="{Binding}" and then I set the grids.DataContext to my ObjectSet<Employee> on the Window_Loaded event.Now when I try your second example Company.Name results in nothing being displayed for the Company name. If I just have Company then I get the row id for the correct Company record. Also in my designer Company.Name results in the .Name bit being in red not blue.What am I getting wrong?
Chris
Hi Chris, your ItemsSource="{Binding}" after setting the DataContext of the grid is achieving the same result as binding the whole view to a higher level view model with an Employees property on it, so no problems there. {Binding Company.Name} assumes that you have a property on your company object called Name - if this is not the case you'll need to substitute Company.Name for Company.CompanyName or whatever the required property on the Company object is.
Steve Willcock
Steve, the Company table does have a column of Name. the Company entity has an attribute of Name. But the only thing I'm binding is the collection of Employees. I think I'm missing a fundemental step here...
Chris
You should be able to bind an Employees collection in this way. I'm assuming the employee class has a property called Company? If so then the {Binding Company.Name} property path syntax will navigate to the Company property of the Employee for each row and then get the Name property of the Company. A good way to see if you have any binding errors is to run in Debug mode and take a look at the Output window in Visual Studio (Debug -> Windows -> Output) - any binding errors will normally appear there.
Steve Willcock
One more thing, if you are using an older version of Entity Framework (anything before V4.0) then the Company property is not lazy loaded by default - you may need to use an "Include" statement in your EF query. This is generally a good idea anyway to avoid the Select n+1 problem.
Steve Willcock
Thanks Steve. Ok, I got there in the end on that one. The reason what i thought should work wasnt working turned out to be a case sensitive issue. The following works for me now: Binding="{Binding ecustomer.Name}"One last question. In your OP - can you explain the bit you were avoiding in order to keep things simple? I'd like to understand all the options for this type of situation. Thanks
Chris
Glad you got it to work. I added some explanatory text about binding a single ViewModel to the root of the main view.
Steve Willcock
Thank you Steve, hugely appreciated.
Chris