How to bind an Entity of another Entity - c#

I have two classes, Customer and Order. The entity, VM and xaml codes are below. What I'm trying to achieve is when I select a customer in datagrid on left, I want to see his/her orders on datagrid on the right. I can see the customers on the left datagrid, however I can't see their orders. I'm using Entity framework and MVVMLight. I'd appreciate if anyone can say what I'm missing. Thanks.
Here is base class :
public class BaseEntity : INotifyPropertyChanged
{
public virtual int Id { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Here is Customer class :
public class Customer : BaseEntity
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private ICollection<Order> _orders;
public ICollection<Order> Orders
{
get { return _orders; }
set
{
_orders = value;
OnPropertyChanged("Orders");
}
}
}
Here is Order Class:
public class Order : BaseEntity
{
private string _explanation;
public string Explanation
{
get { return _explanation; }
set
{
_explanation = value;
OnPropertyChanged("Explanation");
}
}
private Customer _customer;
public Customer Customer
{
get { return _customer; }
set
{
_customer = value;
OnPropertyChanged("Customer");
}
}
}
And in ViewModel I have only Customer VM :
public class CustomerVM : ViewModelBase
{
protected CustomerOrderContext _context;
protected ObservableCollection<Customer> _entities;
protected Customer _selectedentity;
public ObservableCollection<Customer> Entities
{
get { return _entities; }
set
{
_entities = value;
RaisePropertyChanged(() => this.Entities);
DisplayEntityInfo();
}
}
public Customer SelectedEntity
{
get { return _selectedentity; }
set
{
_selectedentity = value;
RaisePropertyChanged(() => this.SelectedEntity);
DisplayEntityInfo();
}
}
public CustomerVM()
{
_context = new CustomerOrderContext();
Entities = GetEntities();
SelectedEntity = Entities.FirstOrDefault();
}
public ObservableCollection<Customer> GetEntities()
{
ObservableCollection<Customer> entities;
entities = new ObservableCollection<Customer>(_context.Set<Customer>());
return entities;
}
}
And this is my .xaml file :
<Window x:Class="CustomerOrder.App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cm="clr-namespace:System.ComponentModel;assembly=System"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:CustomerOrder.App.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=CustomerView}"
Title="MainWindow" Height="500" Width="900">
<Grid>
<Canvas>
<DataGrid x:Name="maingrid" ItemsSource="{Binding Entities}" SelectedItem="{Binding SelectedEntity}" AutoGenerateColumns="False" Canvas.Left="10" Canvas.Top="265">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="200"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid x:Name="ordergrid" ItemsSource="{Binding ElementName=maingrid, Path=SelectedItem.Orders}" AutoGenerateColumns="True" CanUserDeleteRows="True" Canvas.Top="265" Canvas.Left="597">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Explanation}" Header="Orders" Width="200"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Canvas>
</Grid>
</Window>

The first DataGrid has SelectedItem bound to 'SelectedEntity'. You should bind the second DataGrid's ItemSource property to the same 'SelectedEntity' property on your ViewModel
<DataGrid x:Name="ordergrid" ItemsSource="{Binding SelectedEntity.Orders}" AutoGenerateColumns="True" CanUserDeleteRows="True" Canvas.Top="265" Canvas.Left="597">

Related

How do I use MVVM with EF 6 database first?

I have a WPF project which reads Database entries. I built the model with EF 6 from the database.
Now I am a little bit confused how to apply the MVVM Pattern.
This is the model class generated from EF 6:
public partial class Folder
{
public string FolderPath { get; set; }
public string UserGroup { get; set; }
public string Permission { get; set; }
public string AccessControllType { get; set; }
public string IsInherited { get; set; }
public int FolderId { get; set; }
}
This is the MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private ADAccessRightsEntities _context = new ADAccessRightsEntities();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
System.Windows.Data.CollectionViewSource folderViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("folderViewSource")));
folderViewSource.Source = _context.Folders.ToList();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
this._context.Dispose();
}
}
This is my xaml code:
<Window x:Class="PermissoinViewer.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:local="clr-namespace:PermissoinViewer"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
<Window.Resources>
<CollectionViewSource x:Key="folderViewSource" d:DesignSource="{d:DesignInstance {x:Type local:Folder}, CreateList=True}"/>
<CollectionViewSource x:Key="userGroupViewSource" d:DesignSource="{d:DesignInstance {x:Type local:UserGroup}, CreateList=True}"/>
</Window.Resources>
<Grid DataContext="{StaticResource folderViewSource}">
<DataGrid x:Name="folderDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding}" Margin="10,10,313,209" RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.Columns>
<DataGridTextColumn x:Name="accessControllTypeColumn" Binding="{Binding AccessControllType}" Header="Access Controll Type" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="folderIdColumn" Binding="{Binding FolderId}" Header="Folder Id" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="folderPathColumn" Binding="{Binding FolderPath}" Header="Folder Path" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="isInheritedColumn" Binding="{Binding IsInherited}" Header="Is Inherited" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="permissionColumn" Binding="{Binding Permission}" Header="Permission" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="userGroupColumn" Binding="{Binding UserGroup}" Header="User Group" Width="SizeToHeader"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
How do I include a VM class now?
Edit1:
I created a FolderVM class.
class FolderVM : AViewModel
{
private Folder folder;
public FolderVM(Folder folder)
{
this.folder = folder;
}
public string FolderPath
{
get => folder.FolderPath;
set
{
folder.FolderPath = value;
CallPropertyChanged();
}
}
public string UserGroup
{
get => folder.UserGroup;
set
{
folder.UserGroup = value;
CallPropertyChanged();
}
}
public string Permission
{
get => folder.Permission;
set
{
folder.Permission = value;
CallPropertyChanged();
}
}
public string AccessControllType
{
get => folder.AccessControllType;
set
{
folder.AccessControllType = value;
CallPropertyChanged();
}
}
public string IsInherited
{
get => folder.IsInherited;
set
{
folder.IsInherited = value;
CallPropertyChanged();
}
}
public int FolderId
{
get => folder.FolderId;
set
{
folder.FolderId = value;
CallPropertyChanged();
}
}
}
AView Model:
abstract class AViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void CallPropertyChanged([CallerMemberName] string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
I want to filter them that only a specific UserGroup is displayed in the view. How should I continue?

How to add textbox values to list using MVVM?

Models
public class EmployeeDetails
{
public string Name { get; set; }
public int Age {get;set;}
}
public class AddressDetails
{
public EmployeeDetails EmployeeName { get; set; }
public string City { get; set; }
}
View
<Window x:Class="ClassCollection.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:local="clr-namespace:ClassCollection"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={StaticResource loc},Path=ViewModel}"
>
<Grid>
<StackPanel Margin="0 20 0 0">
<TextBox x:Name="txt1" Width="90" Height="20" Text="{Binding Details.EmployeeName}"/>
<TextBox x:Name="txt2" Width="90" Height="20" Text="{Binding Details.City}" Margin="0 20 0 0"/>
</StackPanel>
<Button x:Name="btn" Width="90" Height="25" Content="Add" Command=" {Binding AddCommand}"/>
</Grid>
</Window>
ViewModel
public class Viewmodel
{
public ObservableCollection<AddressDetails> EmployeeList;
public Viewmodel()
{
EmployeeList = new ObservableCollection<AddressDetails>();
LoadCommand();
}
private AddressDetails _details;
public AddressDetails Details
{
get { return _details; }
set
{
_details = value;
}
}
// Commands
public ICommand AddCommand { get; set; }
private void LoadCommand()
{
AddCommand = new CustomCommand(Add, CanAdd);
}
private bool CanAdd(object obj)
{
return true;
}
private void Add(object obj)
{
EmployeeList.Add(new AddressDetails { EmployeeName = Details.EmployeeName, City = Details.City });
}
}
Locator
public class Locator
{
private static Viewmodel viewmodel = new Viewmodel();
public static Viewmodel ViewModel
{
get { return viewmodel; }
}
}
How to add TextBox value to collection list using MVVM?
The Above is my code that I have tried. It shows null reference exception if I do like above. What would be the problem?
Update
I have two fields in EmployeeDetails class. So I must give input for these two field when add to collection. But I need only one field Name to insert to the collection. How to do it?
Analysis
It seems the _details field is not «initialized».
Solution
Please consider introducing the appropriate field initialization, for example:
private readonly AddressDetails _details = new AddressDetails
{
EmployeeName = new EmployeeDetails()
};

How to update a CheckedListItem using WPF

One of my classes
public class CheckedListItem<T> : INotifyPropertyChanged
has the method
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
My XAML looks like
<ListBox ItemsSource="{Binding Employee}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Path=Item.Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My employee.cs file
public class Employee
{
public string Name { get; set; }
}
I cant figure out how I can write back to the database when a item is checked or unchecked.
I would appreciate any help and would be more than happy to post the entire code for any files you need to see.
You should be able to get what you want by adding TwoWay for mode and PropertyChanged on UpdateSourceTrigger.
Final XAML will then be:
<ListBox ItemsSource="{Binding Employee}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="{Binding Path=Item.Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EDIT:
By the way, I think you might be setting your ItemsSource incorrectly. I'm sure what you want is to bind that listbox to a collection of Employees?
Something like:
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set
{
_employees = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}
}
}
I misunderstood the question so here comes second edit:
Your class files would instead look like this:
public partial class MainWindow : INotifyPropertyChanged
{
private ObservableCollection<MutableKeyValuePair<Employee, bool>> _employees;
public MainWindow()
{
InitializeComponent();
Employees = new ObservableCollection<MutableKeyValuePair<Employee, bool>>
{
new MutableKeyValuePair<Employee, bool>(new Employee("Joakim"), false),
new MutableKeyValuePair<Employee, bool>(new Employee("SoftwareIsFun"), false),
};
}
public ObservableCollection<MutableKeyValuePair<Employee, bool>> Employees
{
get { return _employees; }
set
{
_employees = value;
OnPropertyChanged(nameof(Employees));
}
}
public void UpdateDatabase()
{
foreach (var employee in Employees)
{
if (employee.Value)
{
//DATABASE LOGIC HERE
}
}
}
}
public class MutableKeyValuePair<TKey, TValue>
{
public TKey Key { get; set; }
public TValue Value { get; set; }
public MutableKeyValuePair()
{
}
public MutableKeyValuePair(TKey key, TValue value)
{
Key = key;
Value = value;
}
}
public class Employee
{
public Employee(string name)
{
Name = name;
}
public string Name { get; set; }
}
XAML:
<DataGrid ItemsSource="{Binding Employees}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Key.Name}"/>
<DataGridCheckBoxColumn Binding="{Binding Value}"/>
</DataGrid.Columns>
</DataGrid>

Binding not working properly with ListBox

I'm binding a List of a custom class (which implements the INotifyPropertyChanged) into a ListBox.
When I add any item to the List, the ListBox doesn't update, but if I scroll, the ListBox gets updated.
The class
class MyClass : INotifyPropertyChanged
{
private string name = string.Empty;
public string Name { /* INotifyPropertyChanged */ }
}
The property
private List<MyClass> loaded;
public List<MyClass> Loaded { /* INorutyPropertyChanged */ }
The ListBox
<ListBox ItemsSource={Binding Loaded} />
If I force override the List property, it works fine:
Loaded = new List<MyClass>() { new MyClass { Name = "test"; } }
Update to:
public ObservableCollection<MyClass> Loaded { get; private set; }
and
<ListBox ItemsSource={Binding Loaded, UpdateSourceTrigger=PropertyChanged} />
Also, you do not need to use INotiftyPropertyChanged for your Loaded property. If the binding happens once, and the data-source does not change, there's no need.
Edit:
Here's a working example.
MainWindow.xaml
<Window x:Class="WpfApplication3.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"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Width="200" ItemsSource="{Binding Items, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Width="100" Height="75" HorizontalAlignment="Right" Content="Add" Command="{Binding AddItem}"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new DataContext();
}
}
DataContext.cs
namespace WpfApplication3
{
public class DataContext
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public DataContext()
{
Items = new ObservableCollection<Item>
{
new Item
{
Value = "test"
}
};
AddItem = new RelayCommand(() =>
{
Items.Add(new Item
{
Value = "new item"
});
}, () => true);
}
}
public class Item
{
public string Value { get; set; }
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
Let me know if you have any questions.

wpf mvvm binding to sub viewmodel.property

I made an example as simple as possible.
I have a class ViewModelMain whose will implement several viewmodels.
I am trying to bind my slider value on a viewmodel in my ViewModelMain.
Here my code:
MainWindow.xaml.cs
I set the datacontext here, don't know if it is realy a good idea.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
VMMain vm = new VMMain();
this.DataContext = vm;
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Slider Height="23" Name="page_slider" Width="100" Value="{Binding Path=p.NbrLine}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Minimum="0" Maximum="10"/>
<TextBox Text="{Binding Value, ElementName=page_slider, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="28" HorizontalAlignment="Stretch" Name="Voiture1Label" VerticalAlignment="Stretch" Margin="0,110,0,172"></TextBox>
</Grid></Window>
ViewModelMain.cs
ViewModelBase : the class which implement the INotifyPropertyChanged
ModelPage : my model
MyPage : my sub viewmode which is the viewmodel of ModelPage
ViewModelMain : my final viewmodel which will implement more viewmodel
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public class ModelPage
{
public int NbrLine { get; set; }
public int NbrCapsLock { get; set; }
}
public class MyPage : ViewModelBase
{
private ModelPage _model;
public MyPage(ModelPage m)
{
_model = m;
}
public int NbrLine
{
get { return (_model.NbrLine); }
set
{
if (_model.NbrLine == value) return;
_model.NbrLine = value;
OnPropertyChanged("NbrLine");
}
}
public int NbrCapsLock
{
get { return (_model.NbrCapsLock); }
set
{
if (_model.NbrCapsLock == value) return;
_model.NbrCapsLock = value;
OnPropertyChanged("NbrCapsLock");
}
}
}
public class ViewModelMain
{
public MyPage p;
public ViewModelMain()
{
p = new MyPage(new ModelPage(){NbrLine = 5, NbrCapsLock = 1});
}
}
when i launch it, my slider is still on 0 doesn't understand why it is not on 5.
p is a field, not a property. You should only bind to properties:
public MyPage p { get; set; }
Actually you chould transform p into property like that. WPF can not bind to simple attributes.
public class ViewModelMain
{
public MyPage p { get; set; }
public ViewModelMain()
{
p = new MyPage(new ModelPage() { NbrLine = 5, NbrCapsLock = 1 });
}
}

Categories

Resources