I'm having a TreeView control in WPF which displays a list of tasks on the first level. Each task has a list of persons. Both the tasks and persons are stored in a database. I wrote 2 viewmodel classes which encapsulate the Linq2Sql classes. The TreeView consists of 2 hierarchical DataTemplates which refers to the viewmodel classes. Displaying the data works well and I can add task and persons without any problems.
But now I have the problem that I want to delete a person underneath a task from a contextmenu. My problem is that I am not able to access the parent task and therefore cannot update the persons collection. I know which person to delete but not to which task it belongs.
What is the best way to achieve this?
Thanks!
Gerrit
using System;
class ViewmodelPerson
{
public ViewmodelPerson(LinqPerson P)
{
DBPerson = P;
}
LinqPerson DBPerson;
}
public class ViewmodelTask
{
public ViewmodelTask(LinqTask DBTask)
{
this.DBTask = DBTask;
_Persons = from P in DBTask.Person
select new ViewModelPerson(P);
}
LinqTask DBTask;
List<ViewmodelPerson> _Persons;
List<ViewmodelPerson> Persons
{
get
{
return _Persons;
}
}
public void AddPerson(ViewmodelPerson P)
{
}
}
class BaseViewModel
{
public List<ViewmodelTask> Tasks
{
get
{
// Code to get the tasks from Database via Linq
}
}
}
SOLUTION
Because I was not able to get the parent task to which the person belongs I simply added a member ParentTask to my person class. This member needs to be passed within the constructor. When the DeletePerson method is called on the ViewmodelPerson class the Object is deleted in the database and I have access to the parent Task object and can clean up the List as well. Afterwards ChangedProperty("Persons") of IPropertyChanged is called and the WPF tidies up the UI just like magic!
I was just wondering if this approach has a big impact on memory consumption if there are a lot of persons and tasks.
If I understand you correctly, you have a ContextMenu, but you can't access the DataContext of your view from its scope. This is a common problem in WPF and the solution is to 'put the DataContext' into a Tag property that we can later retrieve from the ContextMenu:
<DataTemplate DataType="{x:Type YourNamespace:YourDataType}">
<Border Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={
x:Type YourViewNamespace:NameOfThisControl}}}" ContextMenu="{StaticResource
YourContextMenu}">
...
</Border>
</DataTemplate>
Then you need to set this Tag property to the DataContext of the ContextMenu using a handy property named PlacementTarget:
<ContextMenu x:Key="YourContextMenu" DataContext="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}">
<MenuItem Header="First Item" Command="{Binding CommandFromYourViewModel}" />
</ContextMenu>
To find out more, please take a look at the Context Menus in WPF post on WPF Tutorial.NET.
Related
I have 2 combobox, which include menus name and its details like (Pie-apple, chocolate), (Juice- apple, orange).
So if I select 'Pie' in the first combobox, second one should have its details-apple and chocolate.
Xaml code is below:
<DockPanel Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" VerticalAlignment="Center" Margin="5">
<TextBlock Text="Menu : " HorizontalAlignment="Left" Height="32"/>
<ComboBox ItemsSource="{Binding LstMenu}" SelectedValue="{Binding SelectedMenu}" HorizontalAlignment="Left" Height="32"/>
<TextBlock Text="Detail: " HorizontalAlignment="Left" Height="32"/>
<ComboBox ItemsSource="{Binding LstDetail}" SelectedValue="{Binding SelectedDetail}" HorizontalAlignment="Left" Height="32" SelectionChanged="combobox_changed"/>
</DockPanel>
and viewmodel is below:
private List<string> lstMenu;
public List<string> LstMenu {
get { return lstMenu; }
set {
lstMenu = value;
RaisePropertyChanged("LstMenu");
}
private string selectedMenu;
public string SelectedMenu {
get { return selectedMenu; }
set {
selectedMenu= value;
RaisePropertyChanged("SelectedMenu");
LstDetails = new ObservableCollection<string>( //get its details list from DB );
}
private ObservableCollection<string> lstDetail;
public ObservableCollection<string> LstDetail {
get { return lstDetail; }
set {
lstDetail= value;
RaisePropertyChanged("LstDetail");
if (lstDetail != null && lstDetail.Count > 0) {
SelectedDetail = lstDetail.FirstOrDefault();
}
}
private string selectedDetail;
public string SelectedDetail {
get { return selectedDetail; }
set {
selectedDetail= value;
RaisePropertyChanged("SelectedDetail");
}
The problem is, if I selected Pie-apple and then changed the first combobox to Juice menu, Juice-apple is automatically selected but RaisePropertyChanged("SelectedDetail"); does not work.
I know that's because the selected two strings apple are same...
However I need to call the event SelectionChanged="combobox_changed" since it contains UI reload event.
Moreover, if I added IsAsync=True into the LstDetail combobox, it definitely works but SelectionChanged="combobox_changed" is called twice. I don't know why.
what should I do?
It's hard to know exactly what the architecture of your program is, without a good Minimal, Reproducible Example. But, from what you've posted, it seems you are trying to do too much of the work, and not letting WPF do its fair share. In particular, you seem to have just one "view model" type, and in that object you attempt to completely define everything that the user sees, and the state of that.
As has been noted in the comments, this has led to a situation where there are pieces of state that relate to each other, but where you haven't done enough work to make sure they stay synchronized with each other. You've delegated some of the work to WPF, but it doesn't have enough context to let you know when something important has changed, and so your UI winds up in a inconsistent state.
It would in fact be possible to fix your program as it stands now, by forcing the SelectedDetail property to refresh itself if the menu combo box changes. But a) I can't tell you exactly how to do that, because you've omitted all of the details that relate to the actual management of that property (such as the combobox_changed() event handler), and b) that's really just too much work anyway.
The first thing you need to get on board with, when writing WPF code, is to put as much of your program specification into declarations, and not procedures. WPF's binding mechanisms do a great job of automatically keeping view model data structures in sync with the actual UI. This means that you can view your program as two completely independent entities: the user interface itself, and the "business logic", i.e. the things your program actually has to do. The "view model" part mediates between these two elements. In the simplest WPF programs, the business logic itself can be entirely encapsulated in the view model data structures; in more complex applications, the view models focus on mapping between the UI ("view") and the business logic ("model").
This has an important implication: if you find yourself writing code that is directly interacting with the view element of your program — i.e. either responding to the UI or modifying it — that code had better be strictly specific to the view. Another way to look at that is, such code should be reusable with any other type of business logic, just as all of the built-in XAML stuff is completely reusable and not at all specific to your business logic.
Conversely, if that code you're writing is fiddling with the view model data structures directly or, even worse, is actually part of the view model data structures, you've gone off into the weeds. This should never happen.
You can use these two metrics to constantly evaluate as you go along whether you're designing the code correctly, and to help guide that design before you actually write the code.
Okay, with that little bit of indoctrination out of the way, here's how I would implement your stated goal:
You need some view models. Not just one, because you have a hierarchy of business logic objects, and the view models need to reflect that. Working from the bottom up:
You need a view model that can represent the detail to be displayed. For example:
class DetailViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => _UpdateField(ref _name, value);
}
private string _description;
public string Description
{
get => _description;
set => _UpdateField(ref _description, value);
}
private decimal _price;
public decimal Price
{
get => _price;
set => _UpdateField(ref _price, value);
}
}
Notes:
The above relies on a base class NotifyPropertyChangedBase I use for all view models, which provides a convenient mechanism to implement observable properties. Code for that is provided below.
The above is strictly a simple data container. For this example, that's all that's needed, because all the example is concerning itself with is how to react to UI input, and WPF is great at managing that already, as long as it has a place to keep everything. A more interesting WPF program would have procedural aspects in the view model for providing commands that operate on the data beyond what the XAML is capable of defining.
Okay, so with a details data structure, we also need a place to keep a list of these objects for each type of menu in your program:
class MenuViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => _UpdateField(ref _name, value);
}
private List<DetailViewModel> _menuItems = new List<DetailViewModel>();
public List<DetailViewModel> MenuItems
{
get => _menuItems;
set => _UpdateField(ref _menuItems, value);
}
private DetailViewModel _selectedItem;
public DetailViewModel SelectedItem
{
get => _selectedItem;
set => _UpdateField(ref _selectedItem, value);
}
}
You'll note that the above two view model data structures have a Name property. This is used to display to the user the name of the item they will be selecting.
The new aspect in this view model is the list of menu item objects, and then a property that keeps track of the currently selected menu item object. This is critical with respect to your question: in your implementation, the only thing you know about the currently selected item is its name. But when the same name appears on two different menus, you've got no way to distinguish the two. The only way out of that dilemma, given the design you chose, is to always refresh the details explicitly when the selected menu changes.
But here, we tie the selected item to the menu itself. This gives us two nice results:
When the menu changes, then whatever's bound to the selected item property will change as well, implicitly updating the displayed details, because WPF's binding engine understands the relationships of the properties involved. In particular, the details aren't just some random string, but rather a specific object that was retrieved from a different specific object. If that latter specific object is no longer the context for the binding (i.e. the user picks a new menu), then WPF knows that the former specific object needs to be re-evaluated.
By default, the user's selection for a given menu is remembered, because each menu has its own SelectedItem property! When the user selects an item from a menu, then selects a different menu, then after they are done with that second menu and go back to the first, the first will still have their previous selection from that menu. Now, this may or may not be the desired behavior. If not, it's reasonably easy in the view model to reset the selected item when the menu changes. But it's usually easier to suppress functionality than to create it, so having the default behavior provide that added functionality is nice.
Finally, of course, we need a place to keep track of the currently selected menu:
class MainViewModel : NotifyPropertyChangedBase
{
private List<MenuViewModel> _menus = new List<MenuViewModel>();
public List<MenuViewModel> Menus
{
get => _menus;
set => _UpdateField(ref _menus, value);
}
private MenuViewModel _selectedMenu;
public MenuViewModel SelectedMenu
{
get => _selectedMenu;
set => _UpdateField(ref _selectedMenu, value);
}
}
Just like with the menu object, this one has both a list of items (menus, in this case) and a property that keeps track of which specific item is selected.
Now that the view model data structures have been correctly designed to reflect the hierarchy of user selection in our user interface, it's a very simple matter to declare the user interface to work with those data structures:
<Window x:Class="TestSO58167153WpfTwoLevelDetail.MainWindow"
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:l="clr-namespace:TestSO58167153WpfTwoLevelDetail"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="comboBoxNameTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:DetailViewModel}">
<StackPanel>
<TextBlock Text="{Binding Description}"/>
<TextBlock Text="{Binding Price, StringFormat={}Price: ${0:0.00}}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Grid.Column="0" Width="100"
ItemsSource="{Binding Menus}" SelectedItem="{Binding SelectedMenu}"
ItemTemplate="{StaticResource comboBoxNameTemplate}"/>
<ComboBox Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Left" Margin="10,0"
ItemsSource="{Binding SelectedMenu.MenuItems}" SelectedItem="{Binding SelectedMenu.SelectedItem}"
ItemTemplate="{StaticResource comboBoxNameTemplate}"/>
<ContentPresenter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Content="{Binding SelectedMenu.SelectedItem}"/>
</Grid>
<Window.DataContext>
<l:MainViewModel>
<l:MainViewModel.Menus>
<l:MenuViewModel Name="Pies">
<l:MenuViewModel.MenuItems>
<l:DetailViewModel Name="Apple" Description="Apple Pie with Pastry Crust" Price="10.50"/>
<l:DetailViewModel Name="Grasshopper" Description="Mint Pie with Oreo Crust" Price="17.95"/>
</l:MenuViewModel.MenuItems>
</l:MenuViewModel>
<l:MenuViewModel Name="Juice">
<l:MenuViewModel.MenuItems>
<l:DetailViewModel Name="Apple" Description="Refreshing Apple Juice" Price="3.70"/>
<l:DetailViewModel Name="Mango" Description="Sweet Mango Juice" Price="4.75"/>
</l:MenuViewModel.MenuItems>
</l:MenuViewModel>
</l:MainViewModel.Menus>
</l:MainViewModel>
</Window.DataContext>
</Window>
There are two main components to the above:
Data templates. These tell WPF how to map the view model data structure to elements in the UI. There are two here: a general-purpose one that just always shows, in a TextBlock element, the Name property of any view model data type; and a template that is specific to the DetailsViewModel object, and which displays just the values we're interested in as details.
The UI itself. This is super-simple: two ComboBox elements, providing the drop-down interface to select both a menu and an item on that menu; one ContentPresenter, a control whose main job is just to provide a place to render a data template for a given object; and a Grid object to organize it all. The ComboBox controls explicitly opt in to the data template that displays the item's Name property value, while the ContentPresenter infers the correct data template from the type of view model being used (but it also allows the template to be set explicitly, if you so desire).
The only other thing up there is the DataContext for the window itself, the content of which I've declared in the XAML here just because it's convenient for the purpose of the sample. In your real-world program, which appears to retrieve data from a database, you'd probably have the top-level view model handle populating itself based on that.
(Speaking of the data context: in the above, all binding paths are relative to the top-level view model. For the purpose of the sample, this is more convenient, but you of course have complete control over the data context for any element in the UI. An alternative way to implement this would be to set the DataContext properties for the controls lower in the dependency hierarchy, so that you don't have to repeat the top-level view model's property names in the binding paths.)
And that's all there is to it. You can compile and run the above code, and it will do just what you're asking for your code to do.
Minor notes:
All of the selections start out blank; you can of course initialize them to non-null values if you want, but doing so in the sample above would just add more code for no useful benefit, at least for the purpose of the sample.)
The view models here all use List<T> for their collections. This is fine for the example, because these collections never change. But as you likely already know, real-world WPF programs usually use ObservableCollection<T>, because generally they are including features that allow for those collections to be modified while the program runs. ObservableCollection<T> implements INotifyCollectionChanged, which in turn allows WPF to keep the UI in sync with the bound data. Feel free to replace List<T> with ObservableCollection<T> here or in any other similar scenario.
As promised, here's the code for the NotifyPropertyChangedBase class. There are lots of different ways to implement a base class like this, and in fact I have a different version with a couple more features that I typically use. But this one works well for a basic WPF example (indeed, for many even this one is too "feature-rich" :) ):
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Basically, I'm not sure how to use MVVM, and/or use commands correctly in my current situation. So, I have a View, containing a list box, and a panel of animation objects, that I created. These animation objects can be animated through a simple public method, Animate(). The goal here, is to associate this Animate() method with buttons inside the list box, like so:
As we can see in the diagram below, both the ListBox items and the visual elements inside of the animation area are associated with the same collection of models from the ViewModel, with the items in each being templated. For example, the ListBox items are simply defined to have some text related to a data item, and the AnimationObjects take on an appearance according to the data. These models, I feel, should not understand that an animation is occurring - they're simple data, and the animation does not change them.
Finally, I show in the below diagram, that I have created two FrameworkElement child types, one for holding animation objects, and another that defines these animation objects.
How can I connect this animation action to the buttons within the list box? It doesn't make sense to me that the models/viewmodels know about the animation, because it doesn't change the state of anything in my application - it's just for visual purposes. I've thought about using a RoutedCommand defined in AnimationObject, and having the buttons bind their command property accordingly, but I worry that will simply make every element animate at the same time.
It is also important for my sake, that I conform to MVVM, as these data will be used in many other situations, perhaps even a different version of this view.
Any advice would be appreciated.
What you can do is call a command in your ViewModel , i.e. the DataContext of your ListBox.
CS :
public class ViewModel
{
public ICommand AnimateObjectCommand { get; }
}
XAML :
<DataTemplate x:Key="AnimationObjectItemTemplate">
<Button Command="{Binding Path=DataContext.AnimateObjectCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}" />
</DataTemplate>
<ListBox ItemsSource="{Binding AnimationObjects}" ItemTemplate="{StaticResource AnimationObjectItemTemplate}"/>
your Command implementation should be one that accepts an argument which would be passed by the CommandParameter .
private ICommand _animateObjectCommand;
public ICommand AnimateObjectCommand
{
get
{
if (_animateObjectCommand == null)
{
_animateObjectCommand = new RelayCommand<AnimationObject>( ao => { ao.Animate(); });
}
return _animateObjectCommand;
}
}
The CommandParameter = {Binding} meaning this.DataContext where this is an Item in your ListBox and it's DataContext is an AnimationObject.
I am new to wpf and this fancy binding stuff, followed these tutorial and got this XAML:
<Button
x:Name="btn"
Content="refresh"
Command="{Binding RefreshCmd}" />
and this code:
public someClass ()
{
InitializeComponent();
CreateRefreshCmd();
btn.DataContext=this; // without this line it will not work !!
}
public ICommand RefreshCmd
{
get;
internal set;
}
private bool CanExecuteRefreshCmd ()
{
return true;
}
private void CreateRefreshCmd ()
{
RefreshCmd=new RelayCommand(e => RefreshExec(), c => this.CanExecuteRefreshCmd());
}
public void RefreshExec ()
{
// do something fancy here !
}
but without the last line in constructor it will not work.
In the tutorial this line does not exist.
How can i avoid this?
EDIT:
I clicked the databinding with visual studio and got this:
Command="{Binding RefreshCmd, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:spielerei}}}"
is this really necessary?
For binding to work, you need to set a data context for bindings to target, so yes, it is necessary. In the Command binding you posted in your edit, the binding is instructed to look for the RefreshCmd property on an ancestor of the Button control of type my:spielerei, which I assume is the containing window type. This is why the explicit setting of DataContext doesn't appear in the tutorial.
Bindings and commands can be used in code-behind, but are much more commonly used with view-models in the MVVM pattern. This involves setting the DataContext of your class to a view-model, which contains the properties and commands you want to bind to. To change your code to follow MVVM, we need a view-model:
public class SomeClassViewModel
{
public SomeClassViewModel()
{
this.RefreshCmd = new RelayCommand(e => RefreshExec(), c => this.CanExecuteRefreshCmd());
}
public ICommand RefreshCmd { get; internal set; }
private bool CanExecuteRefreshCmd()
{
return true;
}
public void RefreshExec()
{
// do something fancy here !
}
}
Then, in the code-behind, create the view-model, and assign it as the data context of the object:
public class SomeClass
{
public SomeClass()
{
InitializeComponent();
this.DataContext = new SomeClassViewModel();
}
}
Notice that all of the code from the SomeClass code-behind file has moved to the view-model - it is now testable, and your XAML controls can communicate with the view-model by binding to properties and executing commands.
Binding will work correctly if there is an object it can bind to. This object is read from DataContext property. If this property is not set there is nothing to bind to. It is why the following line is needed:
btn.DataContext=this;
The tutorial mentioned by you does it in a little bit different way i.e. it sets DataContext in XAML. Please examine MainWindow.xaml file from this tutorial. It contains the following code at the beginning which populates DataContext property:
<Window x:Class="MvvmCommand.MainWindow" DataContext="{Binding Main, Source={StaticResource Locator}}">
When you use a Binding in WPF, by default it sets the binding to the named property on the DataContext of the object that has the property that is bound. So in your example, the DataContext of the button.
This property is inherited down through the tree, so if not set on the Button it will look up the tree all the way to the window that holds the control.
MSDN on binding
Without all your XAML to look through I do have to guess, but I am guessing you haven't set the datacontext of the window that hosts the button. By setting it in the constructor explicitly to this you are setting the source of the binding to the object that has the property, hence why it works.
The normal way to do this is to set the data context to a class that contains the command. The usual design pattern for this is MVVM. The idea of binding is to have separation - it is not like events where you handle them in the code behind, instead it allows you to create a view model or similar class that exposes the commands and bind this to the view. This allows you to do things like unit test the functionality via the view model without having to unit test the view, share view models to multiple views etc.
data context is required to be set so that binding framework can resolve the values
you may have various method of setting the same
first method you've used
another method is to set via xaml
<Window x:Class="Project.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
Idea here is to set the data context to self.
In short, It's not necessary. Do not set datacontext to button, set data context (viemodel) for your page (view) in XAML. Of course your command must be exposed via that viewmodel.
For another question I put up simple example showing command binding and cross viewmodel communication, check it out here https://github.com/mikkoviitala/cross-viewmodel-communication
I am using WPF(4.5) and Caliburn.Micro. I am trying to understand how to have an "event" in my View manipulate other controls in my View.
For instance:
My View has an Expander Control, a Button, and a GridView. The GridView is inside the Expander. When the user clicks the button it calls a method in the VM that populates the gridview with a BindableCollection<>. What I want to have happen is when that collection has more then 1 item I want to Expand the Expander Control automatically.
Ideas?
You can bind to the number of items in a collection:
<Expander IsExpanded="{Binding Path=YourCollection.Length, Converter={StaticResource ResourceName=MyConverter}" />
and then in the window or usercontrol:
<UserControl... xmlns:converters="clr-namespace:My.Namespace.With.Converters">
<UserControl.Resources>
<converters:ItemCountToBooleanConverter x:Key="MyConverter" />
</UserControl.Resources>
</UserControl>
and the converter:
namespace My.Namespace.With.Converters {
public class ItemCountToBooleanConverter : IValueConverter
{
// implementation of IValueConverter here
...
}
}
I wrote this out of my head, so apologies if it contains errors ;)
Also: Make sure your viewModel implements the INotifyPropertyChanged interface, but I assume you already know that.
#cguedel method is completely valid but if you don't want to use Converters (why one more class) then in your view model have another property of type bool maybe called ShouldExpand, well why talk so much, let me show you:
class YourViewModel {
public bool ShouldExpand {
get {
return _theCollectionYouPopulatedTheGridWith.Length() != 0;
// or maybe use a flag, you get the idea !
}
}
public void ButtonPressed() {
// populate the grid with collection
// NOW RAISE PROPERTY CHANGED EVENT FOR THE ShouldExpand property
}
}
Now in your View use this binding instead:
<Expander IsExpanded="{Binding Path=ShouldExpand}" />
As i said before the other solution is well but i like to reduce the number of classes in my solutions. This is just another way of doing it.
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.
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.