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.
Related
I am working on a wpf application and I am dealing with checkboxes now
The problem when I set the Ischecked property to "True" like this:
<CheckBox Visibility="{Binding checkVisibility}" IsChecked="true"
IsEnabled="True"></CheckBox>
I get my checkbox checked
But when I try to bind a booléan property that it's value is "true" i get my checkbox always unchecked
I can't see where is the proplem
this is my xaml
<CheckBox Visibility="{Binding checkVisibility}" IsChecked="{Binding Path=test,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" IsEnabled="True"></CheckBox>
this is my property
public bool _test = true;
public bool test {
get {
return _test;
}
set {
if (_test == value) {
return;
}
_test = value;
RaisePropertyChanged("test");
}
}
I want my checkbox to reflect my property value and vice versa but it's not the case as my checkbox is always unchecked
I am missing something?
Edit
Here is my VM:
namespace X{
public class MyViewModel :
{
public MyViewModel()
{
TestCheckbox();
}
#Region Properties
public bool _test1 = true;
public bool test1
{
get
{
return _test1;
}
set
{
if (_test1 == value)
{
return;
}
_test1 = value;
RaisePropertyChanged("test1");
}
}
ObservableCollection<Output> _output_List;
public ObservableCollection<Output> output_List
{
get { return _output_List; }
set
{
if (_output_List == value) return;
_output_List = value;
RaisePropertyChanged("output_List");
}
}
#EndRegion
#Region ButtonCommand
private RelayCommand _SetOutputPropertiesCommand;
public RelayCommand SetOutputPropertiesCommand => _SetOutputPropertiesCommand
?? (_SetOutputPropertiesCommand = new RelayCommand(
() =>
{
foreach (Output item in output_List)
{
item.myvalue=Test2;
}
}
}));
#EndRegion
#Region Method
public void TestCheckbox()
{
Console.Writeline(Test2);
}
#EndRegion
}
public class Output
{
public string label { get; set; }
public string type { get; set; }
public bool myvalue { get; set; }
public bool Test2 { get; set; }
[System.ComponentModel.DataAnnotations.Schema.NotMapped]
[JsonIgnore]
public string checkVisibility { get; set; }
}
}
My Xaml : my checkboxes are integrated in a DataGrid view
<DataGrid x:Name ="GridO" Style="{x:Null}"
ItemsSource= "{Binding output_List,UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False" CellStyle="{StaticResource Body_Content_DataGrid_Centering}"
Margin="5,0" IsReadOnly="True" SelectionMode="Single" RowHeight="50" Height="Auto">
<DataGrid.Columns>
<DataGridTextColumn Width="40*" Binding="{Binding label}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Input"></TextBlock>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
<DataGridTextColumn Width="40*" Binding="{Binding type}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text = "Type"></TextBlock>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
<DataGridTemplateColumn Width="20*" >
<DataGridTemplateColumn.Header>
<TextBlock Text="Value" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="20,0,0,0" Visibility="{Binding checkVisibility }" IsChecked="{Binding Test2,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" IsEnabled="True"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button x:Name="Valid_output" Cursor="Hand" Background="Transparent" Height="55" Width="140" Margin="0,0,20,0" Command="{Binding SetOutputPropertiesCommand}" >
The bind is Working with "Test2" and not working with "Test1" which not in the same class as Test2
NB: the Button command(which is in the same class as "Test1") is working well
My DataTemplate: are in the App.xaml
xmlns:v="clr-namespace:X.View"
xmlns:vm="clr-namespace:X"
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<v:MyWindow/>
I see the main problem in the DataGrid. You set ItemsSource to a collection, so for every row in the DataGrid the data source is one item in that collection. Not your MyViewModel.
This is the reason, why the Test2 is working - it is in the class which instances are in the collection.
You could try to add CheckBox to the window without DataGrid and bind the Test1 - it should work.
If you really want to use some property from your MyViewModel, you can, but it is not that easy. You have to have there something like:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNamespace"
<!--...something...-->
<DataGrid.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</DataGrid.Resources>
<!--...something...-->
And then your binding in the DataGrid will look like this:
IsChecked="{Binding Data.Test1, Source={StaticResource Proxy}}"
Of course I don't know the exact settings of your application so you will have to complete/change the code according to them.
I suggest you to add a ViewModelBase class, like that
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName =
null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Extends your ViewModel with this ViewModelBase class and implements your property
private bool myProperty;
public bool MyProperty { get; set; OnPropertyChanged(); }
Then you just have to bind on your property
<CheckBox IsChecked="{Binding MyProperty}"></CheckBox>
EDIT: To set your ViewModel as DataContext for your View you can set it from the code
MyView.DataContext = new MyViewModel();
or from the view, in the Window/User Control
<Window x:Class="MyApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApplication"
Title="MainWindow" Height="350" Width="525"
DataContext="local.MyViewModel">
The SelectedItem property binding is not causing its DataGrid row to be highlighted on initial load.
I have a DataGrid with a binding on SelectedItem that is not getting highlighted until I click it again. I think I have an order of operations problem--coming from the fact that all the ViewModel code runs before the Views get rendered. It works fine once I click a row (even the same one that's already in the SelectedAccount prop), but I need to able to highlight a row from the VM.
I can easily verify the SelectedAccount property is not null because there are other ViewModels that display it's values via PubSubEvents.
I've tried several solutions methods, and the only way I've been able to get it to work so far just feels dirty:
using ApplicationName.UI.ViewModels;
public partial class AccountsView : UserControl
{
public AccountsView()
{
InitializeComponent();
Loaded += AccountsView_Loaded;
}
private void AccountsView_Loaded(object sender, RoutedEventArgs e)
{
AccountsViewModel viewModel = (AccountsViewModel)DataContext;
AccountsDataGrid.SelectedItem = viewModel.SelectedAccount;
AccountsDataGrid.Focus();
UpdateLayout();
}
}
I don't like this because it causes the OnPropertyChanged event to fire twice, once before the views load, and and again after the above hack. This triggers a SQL call, so I want to avoid that. I also thought the point of MVVM was to decouple the view from the viewmodel, but maybe I'm not understanding that as well as I thought.
Here's the XAML for the DataGrid:
<ScrollViewer Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="5,0">
<DataGrid Name="AccountsDataGrid"
ItemsSource="{Binding Accounts}"
SelectedItem="{Binding SelectedAccount}"
AutoGenerateColumns="False"
CanUserResizeColumns="True"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Header="ClinicId"
TextBlock.TextAlignment="Center"
Width="75"
Binding="{Binding ClinicId}" />
<DataGridTextColumn Header="Account#"
Width="75"
Binding="{Binding AccountNumber}"/>
<DataGridTextColumn Header="LastName"
Width="1*"
Binding="{Binding LastName}" />
<DataGridTextColumn Header="FirstName"
Width="1*"
Binding="{Binding FirstName}" />
<DataGridTextColumn Header="Balance"
Width="Auto"
Binding="{Binding Balance}" />
<DataGridTextColumn Header="Follow Up"
Width="100"
Binding="{Binding FollowUpDate}"/>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
And the intial Load method in the ViewModel, which is where I want to set the highlighted row.
public void Load()
{
RefreshGrid();
SelectedAccount = Accounts.First();
_accountId = SelectedAccount.Id;
}
EDIT
The issue was subtle, but makes perfect sense now.
private Account _selectedAccount;
public Account SelectedAccount
{
get => _selectedAccount;
set => SetSelectedAccount(value);
}
private void SetSelectedAccount(Account value)
{
_selectedAccount = value;
OnPropertyChanged("_selectedAccount"); // <= whoops
if (_selectedAccount != null)
OnAccountSelected(
_selectedAccount.PrimaryKeyFields);
}
Raising this event for a private property doesn't make sense, as the view cannot see it, and is bound to SelectedAccount. Changing it to OnPropertyChanged("SelectedAccount") did the trick.
Implementing INotifyPropertyChanged should be enough, this code works on my end, I'm using a Command to call the Load() method but it's probably not needed in your code.
ViewModel and C# code :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
Accounts = new List<Account>();
Accounts.AddRange(
Enumerable.Range(0, 10)
.Select(r => new Account
{
AccountNumber = r,
FirstName = $"First{r}",
LastName = $"Last{r}"
}));
LoadedCommand = new WpfCommand((param) => Load());
}
private void Load()
{
SelectedAccount = Accounts.FirstOrDefault(a => a.AccountNumber == 2);
}
public WpfCommand LoadedCommand { get; set; }
public List<Account> Accounts { get; set; }
private Account _selectedAccount = null;
public Account SelectedAccount
{
get { return _selectedAccount; }
set
{
_selectedAccount = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedAccount)));
}
}
}
public class Account
{
public int AccountNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class WpfCommand : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public WpfCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public void Execute(object parameter)
{
_execute?.Invoke(parameter);
}
public bool CanExecute(object parameter)
{
return _canExecute?.Invoke(parameter) ?? true;
}
}
XAML :
<!--System.Windows.Interactivity.WPF nuget package-->
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<DataGrid
ItemsSource="{Binding Accounts}"
SelectedItem="{Binding SelectedAccount}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding AccountNumber}"/>
<DataGridTextColumn Binding="{Binding LastName}" />
<DataGridTextColumn Binding="{Binding FirstName}" />
</DataGrid.Columns>
</DataGrid>
In your view model, use your framework's RaisePropertyChanged(); function (or whatever the equivalent is). In the code-behind of the DataGrid element, try this code:
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
for (int i = 0; i < DataGrid.Items.Count; i++)
{
DataGridRow row = (DataGridRow)DataGrid.ItemContainerGenerator.ContainerFromIndex(i);
TextBlock cellContent = DataGrid.Columns[0].GetCellContent(row) as TextBlock;
if (cellContent != null && cellContent.Text.Equals(DataGrid.SelectedItem))
{
object item = DataGrid.Items[i];
DataGrid.SelectedItem = item;
DataGrid.ScrollIntoView(item);
row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
break;
}
}
}
In my example, I used a generic string list of names, so you may need to alter the line TextBlock cellContent = DataGrid.Columns[0].GetCellContent(row) as TextBlock; and cellContent.Text.Equals(DataGrid.SelectedItem)) to meet your line selection criteria.
The other alternative if you don't want to use the code-behind is an attached behavior that more-or-less does the same thing.
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);
}
The following is a demo project to show our actual problem.
Suppose we have the following data:
public class Person
{
public PersonType PersonType { get; set; }
public Job Job { get; set; }
}
public class PersonType
{
public string Name { get; set; }
public List<Job> Jobs { get; set; }
}
public class Job
{
public string Name { get; set; }
}
and sample data:
public class TestData
{
public static List<PersonType> PersonTypes = new List<PersonType>
{
new PersonType
{
Name = "Person1",
Jobs = new List<Job>
{
new Job{Name = "Development"},
new Job{Name = "Testing"}
}
},
new PersonType
{
Name = "Person2",
Jobs = new List<Job>
{
new Job{Name = "Design"},
new Job{Name = " Analysis"}
}
}
};
}
Now we have a data grid that is used to view and manage data:
<Window.Resources>
<CollectionViewSource x:Key="People" />
<CollectionViewSource x:Key="PersonTypes" />
<CollectionViewSource x:Key="JobsData" />
</Window.Resources>
<DataGrid ItemsSource="{Binding Source={StaticResource People}}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonType.Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox SelectedValue="{Binding PersonType, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Source={StaticResource PersonTypes}}"
DisplayMemberPath="Name" SelectionChanged="PersonTypeChanged" IsSynchronizedWithCurrentItem="False" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Job.Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource JobsData}}"
DisplayMemberPath="Name" SelectedValue="{Binding Job}" IsSynchronizedWithCurrentItem="False" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
People.Source = new List<Person>();
PersonTypes.Source = TestData.PersonTypes;
}
public CollectionViewSource People { get { return Resources["People"] as CollectionViewSource; } }
public CollectionViewSource PersonTypes { get { return Resources["PersonTypes"] as CollectionViewSource; } }
public CollectionViewSource JobsData { get { return Resources["JobsData"] as CollectionViewSource; } }
private void PersonTypeChanged(object sender, SelectionChangedEventArgs e)
{
var personType = (sender as ComboBox).SelectedItem as PersonType;
if (personType == null)
{
JobsData.Source = null;
}
else
{
JobsData.Source = personType.Jobs;
}
}
}
In short, when the PersonType is selected for a Person row, the Job column should be filled with the possible jobs for that PersonType to choose from.
Now the problem: when we change the 'PersonType' in a row, Job of the other rows will be affected (filtered). It seems that CellEditingTemplate not only applied to editing mode, it is still there and functional, but not shown. So when the ItemsSource of the ComboBox is changed, it will affect all rows.
How can we fix this?
This is happening because you use the same JobsData CollectionViewSource for all the rows. If you don't want to change your design I'd suggest caching the cell editing event and update the JobsData there.
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.