Does wrapping lists in collections make them read-only? - c#

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?

Related

Losing DataContext when using Datagrid in UserControl

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>

How to get row values with DataGridTemplateColumn connected to different ItemSource

I’m using WPF MVVM DataGrid and one of the columns is the equivalent of DataGridComboBoxColumn, but made of DataGridTemplateColumn .
DataGrid itself is binded to one object, and ComboBox column is binded to the separate one.
The XAML code is:
<DataGrid Grid.Column="0" AutoGenerateColumns="False" CanUserAddRows="False" ItemsSource="{Binding ItemNamesSetting}">
<DataGrid.Columns>
<DataGridTextColumn Header="Item1" Binding="{Binding Path=OriginalItemName}" />
<DataGridTextColumn Header="Item2" Binding="{Binding Path=FinalItemName}" />
<DataGridTemplateColumn Header="Attribute">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AttributesBindingList, ElementName=ThirdStepTab}" DisplayMemberPath="PropName" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Item3" Binding="{Binding Path=Separatopr}" />
</DataGrid.Columns>
</DataGrid>
The question is how can I get the full row data? I’m planning to have one “Save” button that would send data to the database and I need to get data in text columns + data at ComboBox from different ource – row by row. Is there a way to do it?
Thank you.
You need to bind the SelectedItem property of DataGrid to your MVVM view Model. Also, the one of relevant property of view model should bind to Combobox SelectedValue
<DataGrid Grid.Column="0" AutoGenerateColumns="False" CanUserAddRows="False" ItemsSource="{Binding ItemNamesSetting}" SelectedItem="{Binding VMPropertyName}" >
<DataGrid.Columns >
<DataGridTextColumn Header="Item1" Binding="{Binding Path=OriginalItemName}" />
<DataGridTextColumn Header="Item2" Binding="{Binding Path=FinalItemName}" />
<DataGridTemplateColumn Header="Attribute">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AttributesBindingList, ElementName=ThirdStepTab}" DisplayMemberPath="PropName" SelectedValue="{Binding PropertyOfVM}" />
</DataTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Item3" Binding="{Binding Path=Separatopr}" />

DataGridTemplateColumn Header Binding

I have a dynamically created DataGrid with the following XAML:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Cells}" IsReadOnly="True" Visibility="{Binding VisibilityStatus}"
CanUserResizeColumns="False" CanUserAddRows="False" RowBackground="{Binding Color}" Margin="0" RowHeight="25" HeadersVisibility="All">
<DataGrid.Columns>
<DataGridTemplateColumn Header="{Binding Path=Header}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding UniqueValuesToLapMapping.Keys, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedValue, Mode=TwoWay}"
SelectedIndex="0" Width="55"
Background="{Binding Color}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The table is being displayed correctly with all of its content except for the header. No matter in which class I put the Header Property I can't seem to bind to it. I've tried putting it into the ViewModel, putting it into the class that has the "Cells" property, and putting it into the class that has all the Properties that I bind to in the ComboBox.
I have also tried binding without the "Path=" and setting the UpdateSourceTrigger to OnPropertyChanged and changing the properties value on the runtime: nothing seems to work. What am I missing here?
This is the Property I am binding to that I've put in all these classes:
private string _header = "TEST";
public string Header
{
get { return _header; }
set { SetField(ref _header, value, "Header"); }
}
It turns out that the was not in any logical or visual tree. This solution worked for me:
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

DataGrid does not updating with ObservableCollection

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)

Binding list to datagrid

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}" ... />

Categories