I have a two datagrids, both using identical xaml (other than one having an extra column). One is in the main window, the other is inside a usercontrol. Both are bound to a collection of the same items (one is currently an ObservableCollection, the other is an array). Project uses MVVM (or a close approximation).
The array/main window one works correctly - the Location combobox loads in the selected value (where it isn't null) and changing that value only affects the row it is changed in.
The ObservableCollection/usercontrol one does not. The values in the Location column do not load when the window opens, until you scroll down then back up again. Changing the Location combobox changes the value displayed in every visible row (or every row if virtualisation is turned off)... including in the disabled comboboxes. The same does not happen for the Bonding Level combobox, which is handled essentially identically. This happens whether the data presented is an array or an ObservableCollection.
Each instance of the class used to produce a row should be entirely separate, there's no codebehind to mess with values, and extremely similar xaml and c# in another file using the exact same type in the collection works. I am lost!
Screenshots are as follows:
Correct behaviour (Main window, array)
https://i.imgur.com/SJIsTOT.png (Loaded immediately, no values in disabled comboboxes)
https://i.imgur.com/cmjaPoR.png (Single row changes)
Broken behaviour (Usercontrol embedded into tabcontrol, ObservableCollection)
https://i.imgur.com/yC3iAas.png (not loading on window opened)
https://i.imgur.com/aQgPMCN.png (loaded after scroll, including invalid values)
https://i.imgur.com/dqo39aB.png (one combobox changed, all rows change)
XAML for the DataGrid and the misbehaving combobox column (no binding errors):
<DataGrid Grid.Row ="1" Width="Auto" Height="Auto" AlternatingRowBackground="#FBE9D9" AlternationCount="2"
AutoGenerateColumns="False" GridLinesVisibility="None"
ItemsSource="{Binding Familiars}" IsSynchronizedWithCurrentItem="True">
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding OpenDataFamiliarWindow}" CommandParameter="{Binding Familiars/}"/>
</DataGrid.InputBindings>
<DataGrid.Resources>
<local_c:OwnedToBoolConverter x:Key="OwnedToBoolConverter"/>
<local_c:EnemyTypeToColourConverter x:Key="EnemyTypeToColour"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
</DataGrid.Resources>
<DataGridTemplateColumn Header="Location" Width="Auto" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding AvailableLocationTypes}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="90"
SelectedItem="{Binding Info.Location, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True" IsEnabled="{Binding Info.Owned, Converter={StaticResource OwnedToBoolConverter}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource DescConverter}, Mode=OneTime}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
The class that the UserControl is designed to bind to (and the only bit that differs from the working use of essentially the same datagrid):
public class ColiseumVenue : INotifyPropertyChanged
{
public BitmapImage HeaderImage { get; private set; }
public string Name { get; private set; }
public ObservableCollection<FamiliarViewModel> Familiars { get; private set; } = new ObservableCollection<FamiliarViewModel>();
private IModel m_Model;
public ColiseumVenue(IModel model, string name, IEnumerable<FamiliarViewModel> familiars)
{
Name = name;
m_Model = model;
HeaderImage = ImageLoader.LoadImage(Path.Combine(ApplicationPaths.GetViewIconDirectory(), name + ".png"));
foreach(var familiar in familiars)
{
Familiars.Add(familiar);
}
}
public ColiseumView Window { get; set; }
private BaseCommand m_openDataFamiliarWindow;
public ICommand OpenDataFamiliarWindow
{
<snip>
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
The public variables of the FamiliarViewModel class are:
public class FamiliarViewModel : INotifyPropertyChanged
{
public FamiliarInfo Info { get; set; }
public LocationTypes[] AvailableLocationTypes { get; private set; }
public OwnershipStatus[] AvailableOwnershipStatuses { get; private set; }
public BondingLevels[] AvailableBondingLevels { get; private set; }
public BookmarkState[] AvailableBookmarkStates { get; private set; }
}
The parts of the FamiliarInfo class that are bound to are:
public ICRUD<OwnedFamiliar> OwnedFamiliar
{
get { return m_OwnedFamiliar; }
set
{
if(m_OwnedFamiliar != value)
{
m_OwnedFamiliar = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Owned"));
}
}
}
public OwnershipStatus Owned => OwnedFamiliar != null ? OwnershipStatus.Owned : OwnershipStatus.NotOwned;
public BondingLevels? BondLevel
{
get
{
return OwnedFamiliar?.Fetch()?.BondingLevel;
}
set
{
if (value.HasValue)
{
OwnedFamiliar?.Update(f => f.BondingLevel = value.Value);
}
}
}
public LocationTypes? Location
{
get
{
return OwnedFamiliar?.Fetch()?.Location;
}
set
{
if (value.HasValue)
{
OwnedFamiliar?.Update(f => f.Location = value.Value);
}
}
}