I am building a MVVM - WPF Application.
I have few dataGrids where the CRUD operations work fine.
Now, I want a dataGrid always empty at the beginning and where of course I can add rows. So I can fill it but when I click save, nothing is saved.
Why?
ViewModel:
public class InvoiceViewModel : ViewModelBase
{
public Context ctx = new Context();
public InvoiceViewModel()
{
this.Collection = new ObservableCollection<Invoice>();
}
private ObservableCollection<Invoice> collection;
public ObservableCollection<Invoice> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
}
private Invoice _selected;
public Invoice Selected
{
get
{
return _selected;
}
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
private void Get()
{
ctx.Invoices.ToList().ForEach(invoice => ctx.Invoices.Local.Add(invoice));;
Collection = ctx.Invoices.Local;
}
private void Save()
{
foreach (Invoice item in Collection)
{
if (ctx.Entry(item).State == System.Data.Entity.EntityState.Added)
{
ctx.Invoices.Add(item);
}
}
ctx.SaveChanges();
}
private void Delete()
{
var id = Selected;
var invoice = (from i in ctx.Invoices
where i.idInvoice == id.idInvoice
select i).SingleOrDefault();
Collection.Remove(invoice);
}
#region "Command"
// private ICommand getCommand;
private ICommand saveCommand;
private ICommand removeCommand;
/*public ICommand GetCommand
{
get
{
return getCommand ?? (getCommand = new RelayCommand(p => this.Get(), p => this.CanGet()));
}
}
private bool CanGet()
{
return true;
}*/
public ICommand SaveCommand
{
get
{
return saveCommand ?? (saveCommand = new RelayCommand(p => this.Save(), p => this.CanSave()));
}
}
private bool CanSave()
{
return true;
}
public ICommand DeleteCommand
{
get
{
return removeCommand ?? (removeCommand = new RelayCommand(p => this.Delete(), p => this.CanDelete()));
}
}
public bool CanDelete()
{
if (Selected != null)
return true;
else
return false;
}
#endregion
}
View:
<Page.Resources>
<local:InvoiceViewModel x:Key="invoice" />
<local:ShopViewModel x:Key="shop" />
<local:SupplierViewModel x:Key="supplier" />
<local:ProductViewModel x:Key="product" />
<DataTemplate x:Key="ProductDataTemplate">
<TextBlock Text="{Binding product}" />
</DataTemplate>
</Page.Resources>
<DataGrid x:Name="dataGridInvoice"
Margin="5"
Grid.Row="1"
ItemsSource="{Binding Collection}"
AutoGenerateColumns="False"
SelectedItem="{Binding Selected, Mode=TwoWay}"
SelectionMode="Extended"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn x:Name="dataGridTextColumn"
Header="Supplier Invoice Nb"
Width="*" />
<DataGridComboBoxColumn Header="Ref Supplier"
ItemsSource="{Binding Products, Source={StaticResource supplier}, Mode=OneWay}"
DisplayMemberPath="refsup"
SelectedValueBinding="{Binding refSupp}"
SelectedValuePath="refsup"
Width="*" />
<DataGridTextColumn Header="Unit"
Binding="{Binding unit, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridTextColumn Header="Quantity"
Binding="{Binding quantity, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridTextColumn Header="Prix/MOQ"
Binding="{Binding unitPrice, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridTextColumn Header="Total Price"
Binding="{Binding totalPrice, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Horizontal">
<Button x:Name="BtnDelete"
Content="Delete"
Command="{Binding DeleteCommand}"
HorizontalAlignment="Center"
Margin="100,5,5,5"
Width="85" />
<Button x:Name="BtnAdd"
Content="Save"
Command="{Binding SaveCommand}"
HorizontalAlignment="Center"
Margin="20,5,5,5"
Width="85" />
</StackPanel>
You have this method which actually binds the Collection to the cached local of the entity set. Without binding, it's harder to save the added items:
private void Get() {
ctx.Invoices.ToList().ForEach(invoice => ctx.Invoices.Local.Add(invoice));;
Collection = ctx.Invoices.Local;
}
In fact it can be just like this to load all invoices and cache to Local:
private void Get() {
ctx.Invoices.Load();
Collection = ctx.Invoices.Local;
}
However as you said you want an empty data grid at first (just for adding), then you can refactor the Get method to accept a bool indicating if the data should be loaded from db first or not:
private void Get(bool loadDataFirst) {
if(loadDataFirst) ctx.Invoices.Load();
Collection = ctx.Invoices.Local;
}
Now by using the Get method without loading data first, your Collection is still bound well to the Local cache (which should be empty). After adding new items to Collection, the Local will have those items added automatically and the Save can just call the SaveChanges:
private void Save() {
ctx.SaveChanges();
}
The Get method should be used inside the constructor replacing this.Collection = new ObservableCollection<Invoice>(); like this:
public InvoiceViewModel() {
Get(false);
}
Related
I'm trying to bind a DataGrid with an ObservableCollection list. I basically add a few items to the list (which should add rows to the datagrid) at the start of the form then update the columns dynamically using this code in a different thread:
UserDataGrid.Dispatcher.BeginInvoke(new Action(() =>
{
UserDataGridCollection[m_iID].Status = Log;
UserDataGridCollection[m_iID].ID = m_iID;
UserDataGridCollection[m_iID].User = m_sUser;
}
If I update the DataGrid this way it works but lags the UI thread:
UserDataGrid.ItemsSource = null;
UserDataGrid.ItemsSource = UserDataGridCollection;
I've tried using the PropertyChanged event but the DataGrid isn't populating in the first place so I'm not sure if that's working properly. Here is my data class:
public class UserDataGridCategory : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int id;
private string user, status;
public int ID
{
get { return id; }
set { id = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ID")); }
}
public string User
{
get { return user; }
set { user = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("User")); }
}
public string Status
{
get { return status; }
set { status = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Status")); }
}
}
Here is how i'm creating my ObservableCollection:
static class UserEngine
{
public static ObservableCollection<UserDataGridCategory> UserDataGridCollection { get; set; }
public static object _lock = new object();
public static void RunEngine(DataGrid UserDataGrid)
{
UserDataGridCollection = new ObservableCollection<UserDataGridCategory>();
BindingOperations.EnableCollectionSynchronization(UserDataGridCollection, _lock);
// Some other code
// Spawn thread to invoke dispatcher and do some other stuff
}
}
And here is my xaml:
<DataGrid Name="UserDataGrid" x:FieldModifier="public" ItemsSource="{Binding UserDataGridCollection}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" Margin="10,16,22.6,215" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Resources>
<ContextMenu x:Key="RowMenu" Focusable="False"
DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="View Log" Click="ViewLog" Focusable="False"/>
</ContextMenu>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" >
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ID #" Binding="{Binding ID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True"/>
<DataGridTextColumn Header="User" Binding="{Binding User, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True" Width="150"/>
<DataGridTextColumn Header="Status" Binding="{Binding Status,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True" Width="250"/>
</DataGrid.Columns>
</DataGrid>
Any help would be greatly appreciated, thanks!
maybe it helps to bind your itemsource not to an observable collection but to a CollectionViewSource.
public class UserEngine {
public ICollectionView UserdataCollectionView { get; set; }
public UserEngine() { 'constructor
UserdataCollectionView = CollectionViewSource.GetDefaultView(UserDataGridCollection );
}
public void addnewLinetoOC(){
// after you add a new entry into your observable collection
UserdataCollectionView .Refresh();
}
}
in your xaml you have to set
ItemsSource="{Binding UserdataCollectionView }"
not 100% sure if this solution fits your problem but that's how I solved adding new items to my observable collection in my viewmodel.
In my XAML I have a ComboBox which selects a parent element. Selection of an item in the ComboBox should populate a DataGrid with children elements which belong to the selected parent from the ComboBox (I hope this makes sense).
<!-- Select here to populate datagrid -->
<ComboBox ItemsSource="{Binding ContactGroups}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding ContactGroup}" />
<!-- ComboBox selection loads data to the grid -->
<DataGrid ItemsSource={Binding Contacts}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FirstName}" Header="First Name" />
<DataGridTextColumn Binding="{Binding LastName}" Header="Last Name" />
</DataGrid.Columns>
</DataGrid>
I had wanted to just use databinding and my ViewModel to do this, but I honestly have no idea how to pass the selected ContactGroup Id to my ViewModel which I've set up with the following:
public class ContactsViewModel
{
private readonly ObservableCollection<Contact> _Contacts
= new ObservableCollection<Contact>();
public IEnumerable<Contact> Contacts
{
get { return _Contacts; }
}
private ObservableCollection<ContactGroup> _ContactGroups
= new ObservableCollection<ContactGroup>();
private IEnumerable<ContactGroup> ContactGroups
{
get { return _ContactGroupsViewModel; }
}
// Binding for ContactGroups ComboBox
public ICommand ListContactGroupsCommand
{
get
{
return new MyDelegateCommand(ListContactGroups);
}
}
private void ListContactGroups()
{
using (ApplicationDbContext Context = new ApplicationDbContext())
{
var ContactGroups = Context.ContactGroups.Where
(
x => x.Deleted == false
);
foreach (var c in ContactGroups)
{
AddToContactGroups(c);
}
}
}
private void AddToContactGroups(ContactGroup group)
{
if (!_ContactGroups.Contains(group))
_ContactGroups.Add(group);
}
// Handle selection of a ContactGroup.
public ICommand ListContactsForGroupCommand
{
get
{
return new MyDelegateCommand((ContactGroupId) =>
{ ListContacts((int)ContactGroupId); });
}
}
private void ListContacts(int contactGroupId)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
var Contacts = db.Contacts.Where
(
x => x.ContactGroupId == contactGroupId &&
x.Deleted == false
);
foreach (var c in Contacts)
{
AddToContacts(c);
}
}
}
private void AddToContacts(Contact contact)
{
if (!_Contacts.Contains(contact))
_Contacts.Add(contact);
}
}
How can I execute my ListContactsForGroupCommand on the ComboBox?
I figured I can hook it into the SelectionChanged event, but I see now way to provide a parameter value.
Can I do this from the code-behind?
I would implement it like this:
View Model:
public class ContactsViewModel
{
public IEnumerable<Contact> Contacts
{
get
{
return db.Contacts.Where
(
x => x.ContactGroupId == ContactGroup.Id &&
x.Deleted == false
);
}
}
private ContactGroup _contactGroup = new ContactGroup();
private ContactGroup ContactGroup
{
get { return _contactGroup; }
set
{
_contactGroup = value;
RaisePropertyChangedEvent("ContactGroup");
RaisePropertyChangedEvent("Contacts");
}
}
private ObservableCollection<ContactGroup> _ContactGroups
= new ObservableCollection<ContactGroup>();
private IEnumerable<ContactGroup> ContactGroups
{
get { return _ContactGroupsViewModel; }
}
}
XAML:
<!-- Select here to populate datagrid -->
<ComboBox ItemsSource="{Binding ContactGroups}"
DisplayMemberPath="Name"
SelectedItem="{Binding ContactGroup}" />
<!-- ComboBox selection loads data to the grid -->
<DataGrid ItemsSource={Binding Contacts}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FirstName}" Header="First Name" />
<DataGridTextColumn Binding="{Binding LastName}" Header="Last Name" />
</DataGrid.Columns>
</DataGrid>
Add a ContactGroup property to your view model and execute the command, or call the ListContactGroups() method directly, in the setter of this one:
private int _contactGroup;
public int ContactGroup
{
get { return _contactGroup; }
set
{
_contactGroup = value;
ListContactGroups();
}
}
This should work since you are binding the SelectedValue property of the ComboBox to ContactGroup in your XAML:
SelectedValue="{Binding ContactGroup}" />
This means that this property will be set whenever you select an item in the ComboBox.
I have a MainWindow contain DataGrid and also a Button Edit,
<DataGrid x:Name="EmpDataGrid"
Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding loadDataBinding,Mode=TwoWay}"
SelectedItem="{Binding CurrentCustomer}" Grid.ColumnSpan="2">
<DataGrid.Columns>
<DataGridTextColumn Header="CustmorID" Binding="{Binding ID}" />
<DataGridTextColumn Header="CustmorNom" Binding="{Binding nom}" />
<DataGridTextColumn Header="CustmorPrenom" Binding="{Binding prenom}" />
<DataGridTextColumn Header="CustmorReference" Binding="{Binding reference}" />
</DataGrid.Columns>
<Button
Content="Edit"
Command="{Binding Edit}"
CommandParameter="{Binding CurrentCustomer}" />
then I call the Window Update , XAML Window Update:
<Button Command="{Binding UpdateCustomer}"
<TextBox x:Name="nom" Text="{Binding CustomerToAddObject.nom,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBox x:Name="prenom" Text="{Binding CustomerToAddObject.prenom,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ></TextBox>
<TextBox x:Name="reference" Text="{Binding CustomerToAddObject.reference,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ></TextBox>
My ViewModel:
class ViewModel1 : ViewModelBase
{
Test1Entities context = new Test1Entities();
public ViewModel1()
{ using (Test1Entities context = new Test1Entities())
{
_loadDataBinding = new ObservableCollection<Custmor>(context.Custmor.ToList());
}
edit = new RelayCommand(start);
CustomerToAddObject = new Custmor();
updateCustomer = new RelayCommand(UpdateFunction);
}
private ICommand edit;
public ICommand Edit
{
get
{
return edit;
}
}
//To call Update Window:
private void start(object obj)
{
Update windowUpdate = new Update();
windowUpdate.Show();
}
private ICommand updateCustomer;
public ICommand UpdateCustomer
{
get { return updateCustomer; }
}
private void UpdateFunction(object obj)
{
MessageBox.Show(currentCustomer.nom);
MessageBox.Show(customerToAddObject.nom);
using (Test1Entities context = new Test1Entities())
{
Custmor cus = context.Custmor.Find(currentCustomer.ID);
cus.nom = customerToAddObject.nom;
cus.prenom = customerToAddObject.prenom;
cus.reference = customerToAddObject.reference;
context.SaveChanges();
}
}
// customerToAddObject
private Custmor customerToAddObject;
public Custmor CustomerToAddObject
{
get { return customerToAddObject; }
set { customerToAddObject = value; }
}
//CurrentCustomer
private Custmor currentCustomer;
public Custmor CurrentCustomer
{
get { return currentCustomer; }
set
{ currentCustomer = value;
OnPropertyChanged("CurrentCustomer");
}
}
When I execute, I have this Error:
And this is first execution of my application, and it is correct to display from my database:
Thanks for your help,
Try to set the DataContext of the Update window to the same instance of ViewModel1:
private void start(object obj)
{
Update windowUpdate = new Update();
windowUpdate.DataContext = this; //<--
windowUpdate.Show();
}
If you are creating a new instance you obviously will need to initialize the CurrentCustomer property of this one.
I am still beginner in wpf - mvvm. I have a datagrid binded with a collection.
I need to populate the first column by the content of my textbox.
So each time I add a new row, the first column should already have the content of my textbox.
How can I do that?
View:
<Grid DataContext="{Binding Source={StaticResource invoice}}">
<StackPanel Orientation="Horizontal">
<TextBox Width="71"
Name="InvoiveNumber"
Text="{Binding ??, Mode=OneWay}">
<!-- My textbox -->
</TextBox>
<DatePicker></DatePicker>
<Label Content="Shop" />
<ComboBox Margin="5"
ItemsSource="{Binding Collection, Source={StaticResource shop}}"
DisplayMemberPath="shop1"
Width="53" />
<Label Content="Supplier" />
<ComboBox Margin="5"
ItemsSource="{Binding Collection, Source={StaticResource supplier}}"
DisplayMemberPath="supplier"
SelectedItem="{Binding Selected, Source={StaticResource supplier}, Mode=TwoWay}"
Width="46" />
</StackPanel>
<DataGrid x:Name="dataGridInvoice"
Margin="5"
Grid.Row="1"
ItemsSource="{Binding Collection}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<!-- My column -->
<DataGridTextColumn x:Name="dataGridTextColumn"
Header="Supplier Invoice Nb"
Binding="{Binding suppInvNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridComboBoxColumn Header="Ref Supplier"
ItemsSource="{Binding Products, Source={StaticResource supplier}, Mode=TwoWay}"
DisplayMemberPath="refsup"
SelectedValueBinding="{Binding refSupp}"
SelectedValuePath="refsup"
Width="*" />
<DataGridTextColumn Header="Quantity"
Binding="{Binding quantity, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridTextColumn Header="Prix/MOQ"
Binding="{Binding unitPrice, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridTextColumn Header="Total Price"
Binding="{Binding totalPrice, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
</DataGrid.Columns>
</DataGrid>
<Button x:Name="BtnAdd"
Content="Save"
Command="{Binding SaveCommand}"
Margin="94,0" />
</StackPanel>
</Grid>
ViewModel:
public class InvoiceViewModel : ViewModelBase
{
public Context ctx = new Context();
public InvoiceViewModel()
{
Get(false);
}
private ObservableCollection<Invoice> collection;
public ObservableCollection<Invoice> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
}
private Invoice _selected;
public Invoice Selected
{
get
{
return _selected;
}
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
private void Get(bool loadDataFirst)
{
if (loadDataFirst) ctx.Invoices.Load();
Collection = ctx.Invoices.Local;
}
private void Save()
{
ctx.SaveChanges();
}
private void Delete()
{
var id = Selected;
var invoice = (from i in ctx.Invoices
where i.idInvoice == id.idInvoice
select i).SingleOrDefault();
Collection.Remove(invoice);
}
private Invoice _currentItem;
public Invoice CurrentItem
{
get
{
return _currentItem;
}
set
{
_currentItem = value;
OnPropertyChanged("CurrentItem");
}
}
#region "Command"
private ICommand saveCommand;
private ICommand removeCommand;
public ICommand SaveCommand
{
get
{
return saveCommand ?? (saveCommand = new RelayCommand(p => this.Save(), p => this.CanSave()));
}
}
private bool CanSave()
{
return true;
}
public ICommand DeleteCommand
{
get
{
return removeCommand ?? (removeCommand = new RelayCommand(p => this.Delete(), p => this.CanDelete()));
}
}
public bool CanDelete()
{
if (Selected != null)
return true;
else
return false;
}
#endregion
}
First of all, thanks for such challenging problem. Because of this problem, I heavily worked with DataGrid today.
Problem solved after spending entire day and trying various approaches for perfect solution.
Put some number in the textbox, and click button. This number will appear in any new row automatically. Merely changing this number in the textbox won't bring any changes as I am not using that approach. Clicking the button is must. I could have disabled editing the first column, but left it as it is.
This solution works equally well without any changes with AutoGenerateColumns="True" .
Following code can be used as is :
Window1.xaml
<Window x:Class="WpfDataControls.DataGrid.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="385.714" Width="598.872">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="249*"/>
<RowDefinition Height="79*"/>
</Grid.RowDefinitions>
<TextBox x:Name="tbSupplierInvoiceNumber" HorizontalAlignment="Left" Height="23" Margin="168,10,0,0" Grid.Row="1" TextWrapping="Wrap" Text="1" VerticalAlignment="Top" Width="120"/>
<Button Content="Get invoices" HorizontalAlignment="Left" Margin="306,11,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<DataGrid x:Name="dgrdInvoice" HorizontalAlignment="Left" Margin="10,10,0,0" AutoGenerateColumns="False" CanUserAddRows="True"
VerticalAlignment="Top" Height="229" Width="571"
LoadingRow="dgrdInvoice_LoadingRow">
<DataGrid.Columns>
<!-- My column -->
<DataGridTextColumn x:Name="dataGridTextColumn"
Header="Supplier Invoice Nb"
Binding="{Binding suppInvNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridTextColumn Header="Quantity"
Binding="{Binding quantity, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridTextColumn Header="Prix/MOQ"
Binding="{Binding unitPrice, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
<DataGridTextColumn Header="Total Price"
Binding="{Binding totalPrice, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="*" />
</DataGrid.Columns>
</DataGrid>
<TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Initial suppInvNumber" VerticalAlignment="Top" Width="140"/>
<TextBlock HorizontalAlignment="Left" Margin="111,38,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Change this number and click get invoices to see the change" VerticalAlignment="Top"/>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Diagnostics;
namespace WpfDataControls.DataGrid
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
dgrdInvoice.AddingNewItem += dgrdInvoice_AddingNewItem;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel vm = new ViewModel();
dgrdInvoice.ItemsSource = vm.InvoiceCollection;
}
#region Grid events
void dgrdInvoice_LoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.Loaded += dgrdInvoice_Row_Loaded;
}
void dgrdInvoice_Row_Loaded(object sender, RoutedEventArgs e)
{
DataGridRow newRow = ((DataGridRow)sender);
if (newRow.GetIndex() == dgrdInvoice.Items.Count - 1)
{
newRow.Background = new SolidColorBrush(Colors.BlanchedAlmond);
DependencyObject reference = newRow;
while (true)
{
reference = VisualTreeHelper.GetChild(reference, 0);
if (reference is TextBlock)
{
TextBlock f = (TextBlock)reference;
f.Text = Convert.ToInt32(tbSupplierInvoiceNumber.Text).ToString();
break;
}
}
}
}
void dgrdInvoice_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
DataGridRow newRow = (DataGridRow)dgrdInvoice.ItemContainerGenerator.ContainerFromIndex(dgrdInvoice.Items.Count - 1);
DependencyObject reference = newRow;
int invoiceNumber;
while (true)
{
reference = VisualTreeHelper.GetChild(reference, 0);
if (reference is TextBlock)
{
invoiceNumber = Convert.ToInt32(tbSupplierInvoiceNumber.Text);
TextBlock f = (TextBlock)reference;
f.Text = invoiceNumber.ToString();
break;
}
}
e.NewItem = new Invoice() { suppInvNumber = invoiceNumber };
}
#endregion
}
public class ViewModel
{
ObservableCollection<Invoice> _invoiceCollection;
public ObservableCollection<Invoice> InvoiceCollection { get { return _invoiceCollection; } }
public ViewModel()
{
_invoiceCollection = new ObservableCollection<Invoice>();
_invoiceCollection.Add(new Invoice() { suppInvNumber = 1, quantity=120, unitPrice=23, totalPrice=56 });
_invoiceCollection.Add(new Invoice() { suppInvNumber = 3, quantity = 122, unitPrice = 13, totalPrice = 23 });
_invoiceCollection.Add(new Invoice() { suppInvNumber = 4, quantity = 234, unitPrice = 10, totalPrice = 43 });
_invoiceCollection.Add(new Invoice() { suppInvNumber = 6, quantity = 512, unitPrice = 35, totalPrice = 67 });
_invoiceCollection.Add(new Invoice() { suppInvNumber = 7, quantity = 612, unitPrice = 3, totalPrice = 120 });
}
}
public class Invoice
{
public int suppInvNumber { get; set; }
public int quantity { get; set; }
public int unitPrice { get; set; }
public int totalPrice { get; set; }
}
}
I had to change the previous method because it works only if I populate the first column.
With the following method, I can fill four columns.
private void dataGridInvoice_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
DataGridRow newRow = (DataGridRow)dataGridInvoice.ItemContainerGenerator.ContainerFromIndex(dataGridInvoice.Items.Count - 1);
DependencyObject reference = newRow;
// MessageBox.Show(Convert.ToString(VisualTreeHelper.GetChildrenCount(reference)));
string invoiceNumber = null;
int _supplier = 0;
int _shop = 0;
DateTime _date = DateTime.Now;
if (newRow != null)
{
DataGridCellsPresenter presenter = GetVisualChildHelper<DataGridCellsPresenter>(newRow);
if (presenter == null)
{
dataGridInvoice.ScrollIntoView(newRow, dataGridInvoice.Columns[1]);
presenter = GetVisualChildHelper<DataGridCellsPresenter>(newRow);
}
DataGridCell cell0 = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(0);
DataGridCell cell1 = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(1);
DataGridCell cell2 = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(2);
DataGridCell cell3 = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(3);
invoiceNumber = InvoiceNumber.Text;
int value = Convert.ToInt32(shopComboBox.SelectedValue);
_shop = value;
value = Convert.ToInt32(supplierComboBox.SelectedValue);
_supplier = value;
_date = Convert.ToDateTime(datePicker.Text);
cell0.Content = invoiceNumber;
cell1.Content = _shop;
cell2.Content = _date;
cell3.Content = _supplier;
}
e.NewItem = new Invoice() { suppInvNumber = invoiceNumber, shop = _shop, supplier = _supplier, date = _date };
}
public static T GetVisualChildHelper<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChildHelper<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
I have a DataGrid with one CheckBoxColumn. In the header of that CheckBoxColumn I have added a CheckBox to Select all CheckBoxes of that Datagrid Row.
How can I achieve that?
My XAML Code for WPF dataGrid:
<DataGrid AutoGenerateColumns="False" CanUserAddRows="False" Grid.RowSpan="2" Height="130" HorizontalAlignment="Left" IsReadOnly="False" Margin="189,340,0,0" Name="dgCandidate" TabIndex="7" VerticalAlignment="Top" Width="466" Grid.Row="1" >
<DataGrid.Columns>
<DataGridTextColumn x:Name="colCandidateID" Binding="{Binding CandidateID}" Header="SlNo" MinWidth="20" IsReadOnly="True" />
<DataGridTextColumn x:Name="colRegistraion" Binding="{Binding RegisterNo}" Header="Reg. No." IsReadOnly="True" />
<DataGridTextColumn x:Name="colCandidate" Binding="{Binding CandidateName}" Header="Name" MinWidth="250" IsReadOnly="True" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox Name="chkSelectAll" Checked="chkSelectAll_Checked" Unchecked="chkSelectAll_Unchecked"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<CheckBox x:Name="colchkSelect1" Checked="colchkSelect1_Checked" Unchecked="colchkSelect1_Unchecked" ></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Convert your Candidate class into something like this:
public class Candidate : DependencyObject
{
//CandidateID Dependency Property
public int CandidateID
{
get { return (int)GetValue(CandidateIDProperty); }
set { SetValue(CandidateIDProperty, value); }
}
public static readonly DependencyProperty CandidateIDProperty =
DependencyProperty.Register("CandidateID", typeof(int), typeof(Candidate), new UIPropertyMetadata(0));
//RegisterNo Dependency Property
public int RegisterNo
{
get { return (int)GetValue(RegisterNoProperty); }
set { SetValue(RegisterNoProperty, value); }
}
public static readonly DependencyProperty RegisterNoProperty =
DependencyProperty.Register("RegisterNo", typeof(int), typeof(Candidate), new UIPropertyMetadata(0));
//CandidateName Dependency Property
public string CandidateName
{
get { return (string)GetValue(CandidateNameProperty); }
set { SetValue(CandidateNameProperty, value); }
}
public static readonly DependencyProperty CandidateNameProperty =
DependencyProperty.Register("CandidateName", typeof(string), typeof(Candidate), new UIPropertyMetadata(""));
//BooleanFlag Dependency Property
public bool BooleanFlag
{
get { return (bool)GetValue(BooleanFlagProperty); }
set { SetValue(BooleanFlagProperty, value); }
}
public static readonly DependencyProperty BooleanFlagProperty =
DependencyProperty.Register("BooleanFlag", typeof(bool), typeof(Candidate), new UIPropertyMetadata(false));
}
in MainWindow.xaml:
<DataGrid ItemsSource="{Binding CandidateList}">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding CandidateID}"/>
<DataGridTextColumn Header="RegNr" Binding="{Binding RegisterNo}"/>
<DataGridTextColumn Header="Name" Binding="{Binding CandidateName}"/>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox Checked="CheckBox_Checked" Unchecked="CheckBox_Checked"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<CheckBox IsChecked="{Binding BooleanFlag}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
in MainWindow.xaml.cs:
public MainWindow()
{
DataContext = this;
CandidateList.Add(new Candidate()
{
CandidateID = 1,
CandidateName = "Jack",
RegisterNo = 123,
BooleanFlag = true
});
CandidateList.Add(new Candidate()
{
CandidateID = 2,
CandidateName = "Jim",
RegisterNo = 234,
BooleanFlag = false
});
InitializeComponent();
}
//List Observable Collection
private ObservableCollection<Candidate> _candidateList = new ObservableCollection<Candidate>();
public ObservableCollection<Candidate> CandidateList { get { return _candidateList; } }
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
foreach (var item in CandidateList)
{
item.BooleanFlag = true;
}
}
private void UnheckBox_Checked(object sender, RoutedEventArgs e)
{
foreach (var item in CandidateList)
{
item.BooleanFlag = false;
}
}
Strictly speaking the model should not know about the view and so the solution proposed by blindmeis, where the model change is updating every row in the datagrid, breaks the MVVM/Presentation Design pattern. Remember that in MVVM the dependency flow is View -> ViewModel -> Model so if you are referencing controls in your view model (or control codebehind) then you have effectively broken the pattern and you will probably run into issues further down the track.
I have added CheckBox to Select all CheckBox in Datagrid Row
if you mean select all checkbox in datagrid column, then i would say: simply update your itemssource collection with checked/unchecked.
public bool SelectAll
{
get{return this._selectAll;}
set
{
this._selectAll = value;
this.MyItemsSourceCollection.ForEach(x=>x.MyRowCheckProperty=value);
this.OnPropertyChanged("SelectAll");
}
}
xaml
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox isChecked="{Binding SelectAll}"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<CheckBox IsChecked="{Binding MyRowCheckProperty}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
i dunno if the xaml bindings are right, but i hope you can see my intention
It turns out that this is quite a lot harder to get right than one would hope.
The first problem is that you can't just bind the view model to the column header because it doesn't have the view model as its data context, so you need a binding proxy to correctly route the binding to the view model.
public class BindingProxy : Freezable
{
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
"Data",
typeof(object),
typeof(BindingProxy),
new UIPropertyMetadata(null));
public object Data
{
get { return this.GetValue(DataProperty); }
set { this.SetValue(DataProperty, value); }
}
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
}
Now create a binding proxy in your data grid's resources:
<DataGrid.Resources>
<aon:BindingProxy
x:Key="DataContextProxy"
Data="{Binding}" />
</DataGrid.Resources>
Then the column needs to be defined as:
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox
Command="{Binding
Data.SelectAllCommand,
Source={StaticResource DataContextProxy}}"
IsChecked="{Binding
Data.AreAllSelected,
Mode=OneWay,
Source={StaticResource DataContextProxy},
UpdateSourceTrigger=PropertyChanged}"
IsThreeState="True" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding
Path=IsSelected,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Note that there needs to be a binding to both the check box's IsChecked dependency property and its Command property and the IsChecked binding is OneWay. The IsChecked binding gets the check box to display the current state of the items and the Command binding performs the bulk selection. You need both.
Now in the view model:
public bool? AreAllSelected
{
get
{
return this.Items.All(candidate => candidate.IsSelected)
? true
: this.Items.All(candidate => !candidate.IsSelected)
? (bool?)false
: null;
}
set
{
if (value != null)
{
foreach (var item in this.Items)
{
item.IsSelected = value.Value;
}
}
this.RaisePropertyChanged();
}
}
And the SelectAllCommand property is an implementation of ICommand where the Execute method is:
public void Execute(object parameter)
{
var allSelected = this.AreAllSelected;
switch (allSelected)
{
case true:
this.AreAllSelected = false;
break;
case false:
case null:
this.AreAllSelected = true;
break;
}
}
Finally your row item view models (i.e. the things in Items) need to raise PropertyChanged on the main view model each time the value of IsSelected changes. How you do that is pretty much up to you.