I'm trying to learn MVVM (Using MVVMLight Toolkit). But i'm stuck.
In ViewModel have an ObservableCollection
private ObservableCollection<Phone> _phoneNumbers;
public ObservableCollection<Phone> PhoneNumbers
{
get { return _phoneNumbers; }
private set
{
_phoneNumbers = value;
}
}
In ViewModel constructor fill it in such way PhoneNumbers = new ObservableCollection<Phone>(Guest.Person.Phones);
In view have
<DataGrid x:Name="PhoneNumbersDataGrid" HorizontalAlignment="Left" Grid.Column="3" Grid.Row="11" VerticalAlignment="Top" Height="86" Width="auto" ItemsSource="{Binding PhoneNumbers, Mode=OneWay}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding PhoneNumber}" Header="Phone Number" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Guest.DeletePhoneCommand, Mode=OneWay, Source={StaticResource Locator}}">Delete</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
In DeletePhoneCommand I'm trying to change PhoneNumbers e.g.
PhoneNumbers = new ObservableCollection<Phone>();
RaisePropertyChanged("PhoneNumbers");
Collection became empty but datagrid displays filled collection without any changes. "get" of Collection fires only when view is loading. Even when i RaisePropertyChanged("PhoneNumbers") it doesn't fire.
What am I doing wrong?
If you want to notify the changes to your collection you should use Mode = TwoWay
Two way will enable any change in object is reflected to the UI and any change in UI is reflected in the object.
<DataGrid x:Name="PhoneNumbersDataGrid" HorizontalAlignment="Left" Grid.Column="3" Grid.Row="11" VerticalAlignment="Top" Height="86" Width="auto" ItemsSource="{Binding PhoneNumbers, Mode=TwoWay}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding PhoneNumber,Mode=TwoWay}" Header="Phone Number" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Guest.DeletePhoneCommand, Mode=OneWay, Source={StaticResource Locator}}">Delete</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
If I'm not mistaken, the binding is broken when you reinitialize the collection. to fix this you will have to work on the already initialized collection. e.g :
clear the list using the
PhoneNumbers.Clear()
to add to the list, using
PhoneNumbers.Add(item)
you don't need to raise the property changed event to update the list in the UI when you're using ObservableCollection. You will need it if you change a property of the item on the list.
at first clear the list with
Clear()
then add item
PhoneNumbers.Add(item)
Related
So, I have this little WPF snippet:
<DataGrid x:Name="lstCategories" AutoGenerateColumns="False" Grid.Row="0"
Margin="5,5,5,5" ScrollViewer.CanContentScroll="True" SelectionMode="Single" CanUserSortColumns="False" CanUserReorderColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<DataGridColumnHeader Tag="All" Click="DataGridColumnHeader_Click">All</DataGridColumnHeader>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding enabled}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Category" Width="Auto" IsReadOnly="True" Binding="{Binding category}" />
</DataGrid.Columns>
</DataGrid>
Which displays data as given in:
lstCategories.ItemsSource = categoryList;
Now, when the user clicks the checkbox, I'd expect the datagrid to write this change to categoryListItem.enabled, but when I check the value, it says true no matter what.
How can I make the DataGrid store changes to the variable it uses as data source?
Additional Info: I've chosen the TemplateColumn-approach with an embedded checkbox because this way the user does not have to click two times to modify the check box.
Update 1:
I've updated the XAML to incorporate TwoWay-Binding as:
<CheckBox IsChecked="{Binding enabled, Mode=TwoWay}"/>
The data source variable still does not get updated.
I see you've already figured out Mode=TwoWay (which isn't necessary anyway, as it is the default if you don't explicitly set it). Now all you have to do is set UpdateSourceTrigger to PropertyChanged.
<DataTemplate>
<CheckBox IsChecked="{Binding enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
Consider this XAML:
<UserControl>
<TextBlock Text="{Binding NestedObject.Name}" TextWrapping="Wrap"/>
<DataGrid DataContext="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding NestedObject.Name}" />
</DataGrid.Columns>
</DataGrid>
</UserControl>
In the case of the TextBlock the object property would be correctly displayed, but when using it in a DataGrid, nothing is displayed.
This is odd since I call it the same way in both cases and I thought that when no data context was specified it was falling back to the parent data context.
Am I missing something in the declaration ?
Note
I'm using the UserControl in the MainWindow and an object with a NestedObject property is assigned to its data context. Also, Name is implementing INotifyPropertyChanged.
You generally bind a column in a DataGrid to a property of an item in the DataGrid's ItemsSource, i.e. your current binding will only work if the ItemsSource property is bound or set to an IEnumerable<T> and the type T has a SomeObject property.
If you want to to bind to a property of another object, you could use a {RelativeSource} to explictly specify the source of the binding:
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.SomeObject.Name, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding DataContext.SomeObject.Name, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Name" Binding="{Binding SomeObject.Name}" />
</DataGrid.Columns>
</DataGrid>
I have a database controller class that creates a couple of table adapters and a dataset. Also, I've created a model for each table row and I create an ObservableCollection<MembersModel> with model instances for each row.
private ObservableCollection<DoubleTeamModel> _doubleTeams = new ObservableCollection<DoubleTeamModel>();
foreach (UsersDataSet.Double_TeamsRow row in ds.Double_Teams)
{
_doubleTeams.Add(new DoubleTeamModel(row));
}
In my viewmodel I have a function that returns a BindingList
public BindingList<TeamMatchModel> TeamQualificationMatches
{
get
{
return new BindingList<TeamMatchModel>(App.Data.GetDoubleTeamMatches().Where(
match => match.TournamentId == CurrentTournament.Id &&
match.Status == MatchStatus.QualificationsPlaying).ToList<TeamMatchModel>());
}
}
This BindingList is then bound to a datagrid
<DataGrid x:Name="dgrTeamQualificationMatches" AutoGenerateColumns="False" Margin="5,10,5,10"
ItemsSource="{Binding TeamQualificationMatches, Mode=TwoWay}"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserSortColumns="False"
SelectionMode="Single" SelectionUnit="FullRow"
RowStyle="{DynamicResource EditableRows}" CellStyle="{DynamicResource EditableTableCells}">
<DataGrid.Columns>
<DataGridTextColumn Header="Team A" Binding="{Binding TeamANames, Mode=OneWay}"
CellStyle="{DynamicResource ScoreATableCells}" Width="Auto" />
<DataGridTemplateColumn Header="Highest shot 1" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TeamANameA}" />
<TextBox Text="{Binding HighestShotTeamAPlayerA, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
// Some other columns
</DataGrid.Columns>
</DataGrid>
This causes a InvalidOperationException with this message:
A TwoWay or OneWayToSource binding cannot work on the read-only property 'TeamQualificationMatches' of type 'CompetitionAgent.ViewModel.TournamentViewModel'
The grid should be writeable (Mode=TwoWay), but each column that's supposed to be read-only has it's mode set to OneWay. When did the entire collection become read-only according to the exception?
I have a WPF application with a datagrid. On load my ViewModel populates a list called HldChangeList. This list is bound to the data grid. The list contains approx. 200 items but at the moment the list shows 10 empty rows but no column headers. I've put a stop in my setter and can see the code is getting there. Not sure what else I am missing.
View Model
private List<HoldingPrePost> _hldChangeList;
public List<HoldingPrePost> HldChangeList
{
get
{
return _hldChangeList;
}
set
{
_hldChangeList = value;
OnPropertyChanged("HldChangeList");
}
}
XAML
<DataGrid x:Name="dataGridHoldings"
DataContext="{Binding HldChangeList}"
AutoGenerateColumns="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Silver"
RowHeaderWidth="30"
ItemsSource="{Binding Path=HldChangeList, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource DataGridTemplate}"
ColumnHeaderStyle="{StaticResource DG_ColumnHeader}"
RowStyle="{StaticResource DG_Row}"
CellStyle="{StaticResource DG_Cell}"
RowHeaderStyle="{StaticResource DG_RowHeader}"
Margin="15,5,20,15" >
<DataGridTextColumn Header="ABC" Binding="{Binding ABC}" IsReadOnly="True"/>
<DataGridTextColumn Header="DEF" Binding="{Binding DEF}" IsReadOnly="True"/>
<DataGridTextColumn Header="GHI" Binding="{Binding GHI}" IsReadOnly="True"/>
</DataGrid>
You're setting both DataContext and ItemsSource to HldChangeList
<DataGrid
DataContext="{Binding HldChangeList}"
ItemsSource="{Binding Path=HldChangeList, UpdateSourceTrigger=PropertyChanged}"/>
WPF will search for HldChangeList items source property in current binding context which you also set to HldChangeList so in your case it will look for HldChangeList.HldChangeList property. If HldChangeList is already part of current binding context then you don't need to change DataContext otherwise you need to set it to something that contains HldChangeList property
EDIT
You forgot to enclose column definitions in DataGrid.Columns tag
<DataGrid x:Name="dataGridHoldings" ... ItemsSource="{Binding Path=HldChangeList}">
<DataGrid.Columns>
<DataGridTextColumn Header="ABC" Binding="{Binding ABC}" IsReadOnly="True"/>
<DataGridTextColumn Header="DEF" Binding="{Binding DEF}" IsReadOnly="True"/>
<DataGridTextColumn Header="GHI" Binding="{Binding GHI}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
As dkozl said,
you need to set ItemsSource of your DataGrid explicitly without setting its DataContext or implicitly by setting the DataContext
Implicit
<DataGrid ...
DataContext="{Binding HldChangeList}" ...
ItemsSource="{Binding}" ... />
Explicit
<DataGrid ...
ItemsSource="{Binding HldChangeList}" ... />
I'm working with a window with several DataGrid's, and I'd like to process deletion event via a single command.
For this, I need to pass to that command the list of records, from which the record has to be deleted.
Here's what I mean:
<DataGrid Margin="0" HeadersVisibility="None"
ItemsSource="{Binding GroupExtednedDataList}"
... >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="delete"
CommandParameter="{Binding:::
How do I bind from here to GroupExtednedDataList from ItemsSoruce?}" >
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
How do I bind from inside <Button Content="delete" to ItemsSource of the DataGrid?
Something like
{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=DataGrid}}