(Question modified for better clarity)
I've been researching all day and nothing really clears up my issue. I have a combobox that gets its source from a collection of a "Bonus" that has a Name and a Code.
<ComboBox Margin="4"
SelectedItem="{Binding Path=SelectedBonus, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=Bonuses, UpdateSourceTrigger=Explicit}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True">
"Bonuses" is a collection that is retrieved from a file initially. The combobox is properly filled with the collection, and I can select any of the items in the list.
Can't post image, so here is the link to the loaded list on startup: https://www.flickr.com/photos/mav_2007/14059399414/in/set-72157644402662271
Now, the following code is called on a context switch (right click the combo-box and select "edit Bonus"). However, here is where I cannot make any sense of what's going on with WPF and bindings.
In the code below, the method called "EditBonus" resets the combobox list be re-reading the file to make sure nothing has changed (Bonuses.Clear() is called, then rebuilt, and SelectedItem set). But, as soon as I make the call to ShowDialog, the combobox selected item goes blank. If I un-comment the OnPropertyChanged calls below, the problem appears fixed. But, I do not understand why that works, and there is a case I can't fix where an exception in the Save operation of the dialog makes that combo box disappear again.
/// <summary>
/// Handle the "edit bonus" command
/// </summary>
void OnEditBonus()
{
// only edit this is we have a bonus selected
if (bonusEditViewModel.EditBonus())
{
// OnPropertyChanged("SelectedBonus");
var window = new BonusEditDialog(bonusEditViewModel);
window.ShowDialog();
// OnPropertyChanged("SelectedBonus");
}
}
And the method EditBonus():
internal bool EditBonus()
{
bool success = false;
if (SelectedBonus != null)
{
var originalCode = SelectedBonus.Code;
success = UpdateBonuses(originalCode);
if (success)
{
if (SelectedBonus.Code == originalCode)
{
BonusName = SelectedBonus.Type;
originalBonusName = BonusName;
CloseWindow = false;
}
}
}
return success;
}
Now, I've tried changing the UpdateSourceTrigger types and the modes and nothing makes a difference.
I believe it is related to the changing of the datacontext when accessing the dialog, and yet still making modification to the combobox's itemSource. But I'm not sure how I can defer the combobox update until the dialog exits.
This is what it looks like if I try to edit the list without calling that OnPropertyChanged after returning from EditBonus() (note the empty combobox):
https://www.flickr.com/photos/mav_2007/14035815226/in/set-72157644402662271
And this is what it looks like if I make the call just after returning from EditBonus():
And I would love to show you this, but I guess I'm not "reputable" enough :( (You can see all images for this issue on my Flickr page from that link)
Thanks for any help you can give
Additional information (Possible culprit identified):
The DataContext for SelectedBonus is BehaviorViewModel, but there is another SelectedBonusProperty inside BonusEditViewModel. Both BehaviorViewModel and BonusEditViewModel derive from ViewModelBase, which is where the OnPropertyChanged handler actually executes the handler based on ViewModel type. BehaviorViewModel is what is bound to the form. Calling OnPropertyChanged inside BonusEditViewModel has no effect because it is a different context.
Here is the SelectedBonus property inside BehaviorViewMode:
/// <summary>
/// Expose the bonus information from the bonusEditViewModel
/// </summary>
public Bonus SelectedBonus
{
get
{
return bonusEditViewModel.SelectedBonus;
}
set
{
bonusEditViewModel.SelectedBonus = value;
if (behavior != null && bonusEditViewModel.SelectedBonus != null)
{
behavior.Items[BehaviorItem.THEME_ID].Value = bonusEditViewModel.SelectedBonus.Code.ToString();
}
if (bonusEditViewModel.SelectedBonus != null)
{
ThemeShortname = bonusEditViewModel.SelectedBonus.Type;
}
OnPropertyChanged("SelectedBonus");
}
}
And here is the code for the SelectedBonus inside BonusEditViewModel:
/// <summary>
/// The currently selected bonus which will be edited by the dialog
/// </summary>
public Bonus SelectedBonus
{
get { return selectedBonus; }
set
{
selectedBonus = value;
OnPropertyChanged("SelectedBonus");
}
}
The function OnEditBonus() exists inside the BehaviorViewModel, and it calls the method UpdateBonuses() on the BonusEditViewModel object. Now I'm wondering the best way to fix this...
The problem was verified to be with the structure of the code, which had a binding to the property SelectedBonus in the ViewModel class called BehaviorViewModel. However, the property in this class actually gets its data from a property in the ViewModel class called BonusEditViewModel. Calling "OnPropertyChanged("SelectedBonus") inside the BonusEditViewModel class has no effect on the property bound in BehaviorViewModel. The code needs to be restructured so that the SelectedBonus property in BehaviorViewModel does not reference the property in another ViewModel. This is just poorly structured code.
Related
I have two ComboBoxes envelopeList and datasetList. Consider the following line of code:
envelopeList.DataBindings.Add("DataSource", datasetList, "SelectedValue");
The intended functionality is to update envelopeList.DataSource to be datasetList.SelectedValue whenever the selection is changed. However, if datasetList is empty this throws an ArgumentException saying "Additional information: Complex DataBinding accepts as a data source either an IList or an IListSource."
I don't understand why this happens. When datasetList is empty datasetList.SelectedValue returns null and envelopeList.DataSource = null does not throw any exception. This doesn't throw any exceptions either: envelopeList.DataSource = datasetList.SelectedValue;, nor does this: envelopeList.DataSource = new BindingSource(datasetList, "SelectedValue");, even when datasetList is empty.
Doing the binding after datasetList has at least one item works as intended, until it becomes empty in which case envelopeList.DataSource isn't updated. The DataSourceChanged event isn't even fired. (Though in my case that noticed by the user since the DataSource will be emptied when the item in datasetList is deleted).
To make this work I have to execute the following code after datasetList has been populated for the first time:
if(doonce && !(doonce = false))
envelopeList.DataBindings.Add("DataSource", datasetList, "SelectedValue");
It's a very ugly way to do it and I would much rather be able to do this during initialization.
Some potentially important information.
Both ComboBoxes are actually my own inheriting type AdvancedComboBox. This is the relevant functionality within:
public class AdvancedComboBox : ComboBox, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected override void OnSelectedValueChanged(EventArgs e)
{
base.OnSelectedValueChanged(e);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedValue")); //I don't know why, but it works even if I remove this line.
}
}
(I have other uses for the PropertyChanged event, even if I apparently don't actually need it for the SelectedValue property.)
datasetList.DataSource is bound to an IBindingList containing DatasetPresenter objects.
DatasetPresenter has a property Areas that return an IBindingList with the objects that I want envelopeList to show.
I run datasetList.ValueMember = "Areas" before doing the binding.
The question
How do I make envelopeList.DataBindings.Add("DataSource", datasetList, "SelectedValue"); work even when datasetList is empty or achive a similar result?
I prefer solutions that I only have to execute during initialization of the ComboBoxes and/or code that I can put inside the AdvancedComboBox class so that it remains self-containing.
Bonus: Why doesn't it work when datasetList is empty? Even though datasetList.SelectedValue returns null and envelopeList.DataSource = null is okay.
Well, I found a solution, but it's not pretty.
I created this class specifically for this purpose:
private class PlaceHolder
{
public string Name { get; }//This property is the DisplayMember of both ComboBoxes.
public List<PlaceHolder> Areas { get { return new List<PlaceHolder>(0); } }//This property is the ValueMember of datasetList.
}
And during initialization I run this code.
datasetList.DataSource = new List<PlaceHolder>() { new PlaceHolder() };
envelopeList.DataBindings.Add("DataSource", datasetList, "SelectedValue");
datasetList.DataSource = myController.Datasets;//This is the intended data source.
It's ugly and I'm still looking for better solutions.
This question is a result of the fix to this problem. After getting the sort behavior to properly sort the ObservableCollection, I now notice that the first time the collection is added to, the CustomSorter handler fires with only the first item in it, and that same item is passed in as both parameters to the method. That is producing a duplicate item in the list.
Here are the pertinent parts of the view model code:
public ObservableCollection<PermissionItemViewModel> PermissionItems { get; private set; }
private void FetchRoleData()
{
PermissionItems.Clear();
if (SelectedRole != null)
{
using (var context = new myDataContext(new myDbFactory().GetConnectionString()))
{
foreach (PermissionsEnum permission in Enum.GetValues(typeof(PermissionsEnum)))
PermissionItems.Add(new PermissionItemViewModel(permission, SelectedRole[permission]));
}
}
}
All subsequent manipulations of that collection do not do this...it only happens the first time through the FetchRoleData method. Why?
EDIT:
Some additional information. The CustomSort property is set when the CollectionViewSource fires its Filter event (the only event it has AFAIK). I couldn't come up with any better trigger to get it set. The OnAttached override is too soon, as the View member of the CollectionViewSource is not set yet by that point. Catch 22, I guess. That is happening immediately after that first collection item is added. If this is due to the order in which things are being set, then are there any recommendations for a change?
I don't know how or where you're setting up the filter handler. Here's an example of how to set a custom sort on a CollectionViewSource when its View property changes. That's when you want to do it. This assumes that it's in the resources for a Window (or at least someplace where the Window can find it). But once you have cvs, wherever it comes from and however you got your mitts on it, the rest is the same.
public MainWindow()
{
InitializeComponent();
var cvs = FindResource("MyCollectionViewSource1") as CollectionViewSource;
var dpdView = DependencyPropertyDescriptor.FromProperty(
CollectionViewSource.ViewProperty, typeof(CollectionViewSource));
dpdView.AddValueChanged(cvs, (s, e) =>
{
if (cvs.View is ListCollectionView lvc)
{
lvc.CustomSort = new CustomSorter();
}
});
}
I'm baffled by your claim that the first item in the collection is being duplicated. No code you've shown, and no code I've given you, could have that effect. You'll have to share code that demonstrates that issue.
I'm having trouble working with the List View setup to Bind Multiple Selections and remember the selections when I switch the Selection Mode to between Single and Multiple.
Context
I'm creating a Soccer coaching app. The List View will display the Player Roster. On game day, I want an easy way to select the players that showed up to play. I would like to use the Multiple Selection mode to allow the user to select the players that showed up. I will provide a button in the app bar that will control the SelectionMode through a converter. It will switch the List View from Single to Multiple. This part works fine. I can see the List View switch between Single and Multiple.
The part that doesn't work fine is the binding of Selected Items. I must be missing something because it seems extremely difficult to bind to the SelectedItems property reliably. The best thing that has worked so far is the ListViewExtentions from the WinRTXamlToolkit which is what I show in my xaml below. It seems to bind the items to the backing property in the view mode when the selection is made, however, when the SelectionMode is switched back to Single, the back property is cleared out. Also, without modifying the extension code, it broke my SelectionMode code. There is a castastrophic failure in the extension on _listView.SelectedItems.Clear(). If I remove that line, then SelectionMode is back working.
I don't care to use the ListViewExtentions from WinRTXamlToolkit. I only present it here so you know that I have tried it. Ultimately, I'm looking for the right solution for binding SelectedItems.
Here is the List View XAML.
<ListView ItemsSource="{Binding Roster}"
toolkitExt:ListViewExtensions.BindableSelection="{Binding SelectedPlayers, Mode=TwoWay}"
SelectionMode="{Binding IsEditingGameRoster, Converter={StaticResource ListViewSelectionModeFromBooleanNoneOrMultipleConverter}}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- List View Display Not important for describing problem. -->
</DataTemplate>
</ListView.ItemTemplate>
Namespace is: xmlns:toolkitExt="using:WinRTXamlToolkit.Controls.Extensions"
The View Model has the properties:
Roster : ObservableCollection
IsEditingGameRoster : Bool
SelectedPlayers : ObservableCollection
Thanks for any samples that demonstrate binding Multiple Selection especially if it handles switching SelectionMode as well.
Thanks,
Tom
I was facing this problem too. When i have trying to change Selection mode in converter it worked not as expected. Any workaround i have found is to use multiple selection mode, but operate in CollectionChanged event with added/removed items. For example if i need Single selection mode i am rewriting selection to new selected item.
Something like this:
private void OnSelectedPlayersChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (this.SelectionMode == Mode.Once)
{
SynchronizationContextProvider.UIThreadSyncContext.Post((d) =>
{
this.SelectedPlayers.Clear();
this.SelectedPlayers.Add((Players)e.NewItems[0]);
}, null);
}
}
if (e.Action == NotifyCollectionChangedAction.Remove)
{
//some logic
}
}
It is the best option, what i have found so far. If you'll figure out something better i'll happy to use it.
I ended up solving my issue close to what I would like to see but still not perfect. I ended up creating a sub-class of ListView and adding two properties SelectedItems2 and SelectionMode2. If anyone could help adjust the following code so that I could reuse SelectedItems and SelectionMode that would make the solution much closer to perfect.
Sub-Class Code
/// <summary>
/// Enhanced List View
///
/// SeletionMode Switching:
/// This class enhances ListView to support changing the selection mode from None/Single to Multiple and back and having it
/// bind the selected items without forgetting them when changing selection mode. The base ListView will remove the selected items when switching
/// from Multiple SelectionMode to another SelectionMode.
/// 1) Created SelectedItems2 to bind to View Model as SelectedItems will have items removed and SelectedItems is not easy to bind to SelectedItems.
/// 2) On SelectionChanged, remember the selected items in SelectedItems2 how the user selected or unselected them.
/// 3) On SelectionMode set to Multiple, fill the underlying SelectedItems with the values from SelectedItems2 so that the checkboxes are checked on the ListView.
/// 4) Use _isMergingItems flag to ignore selection changed events being fired by merging of the SelectedItem2 and SelectedItems2 collections.
///
/// Doesn't support changing SelectedItems2 bound collection while in Multiple SelectionMode yet.
/// To do so we would need to hook the CollectionChanged event on the SelectedItems2 List object that is bound.
/// and likely handle threading issues with the merging of SelectedItems and SelectedItems2.
/// </summary>
public class MyListView : ListView
{
private bool _isMergingItems;
public MyListView()
{
SelectionChanged += MyListView_SelectionChanged;
}
private void MyListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Ignore selection changed event when selection mode is not multiple.
// This is mainly here because when changing from Multiple to Single, this event is fired and will cause
// the items to be removed from SelectedItems2 which is not what is desired.
if (SelectionMode != ListViewSelectionMode.Multiple) { return; }
if (_isMergingItems) { return; }
try
{
_isMergingItems = true;
var selectedItems2List = SelectedItems2 as IList;
if (selectedItems2List == null) { return; }
foreach (var itemToAdd in e.AddedItems)
{
selectedItems2List.Add(itemToAdd);
}
foreach (var itemToRemove in e.RemovedItems)
{
selectedItems2List.Remove(itemToRemove);
}
}
finally
{
_isMergingItems = false;
}
}
#region --- Dependency Properties ---
private static readonly DependencyProperty SelectionMode2Property = DependencyProperty.Register("SelectionMode2", typeof(ListViewSelectionMode), typeof(MyListView), new PropertyMetadata(ListViewSelectionMode.None, SelectionMode2Changed));
public ListViewSelectionMode SelectionMode2
{
get { return (ListViewSelectionMode)GetValue(SelectionMode2Property); }
set { SetValue(SelectionMode2Property, value); }
}
private static void SelectionMode2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listView = d as MyListView;
var value = (ListViewSelectionMode)e.NewValue;
// Set the underlying selection mode property to cause the control to
// change to and from Multiple selection mode.
listView.SelectionMode = value;
// When Multiple selction mode, we will need to load the underlying SelectedItems of the ListView
// so that the checkboxes are checked appropriatly. When the selection mode is changed the underlying ListView
// removes the selected items.
if (value == ListViewSelectionMode.Multiple)
{
listView.FillUnderlyingSelectedItems();
}
}
private static readonly DependencyProperty SelectedItems2Property = DependencyProperty.Register("SelectedItems2", typeof(object), typeof(MyListView), null);
/// <summary>
/// SelectedItems2 is used as the property to bind the selected items to the ViewModel.
/// Using a List as the data type did not seem to work for binding. Since ItemSource is an object on the base class
/// object was chosen for this property as well.
///
/// However, SelectedItems2 will need to bind to IList in order for it to work properly.
/// </summary>
public object SelectedItems2
{
get { return (object)GetValue(SelectedItems2Property); }
set { SetValue(SelectedItems2Property, value); }
}
#endregion
private void FillUnderlyingSelectedItems()
{
var selectedItems2List = SelectedItems2 as IList;
if (selectedItems2List == null) { return; }
try
{
_isMergingItems = true;
foreach (var item in selectedItems2List)
{
SelectedItems.Add(item);
}
}
finally
{
_isMergingItems = false;
}
}
}
USAGE: XAML
<localUI:MyListView ItemsSource="{Binding Roster}"
SelectedItems2="{Binding SelectedPlayers}"
SelectionMode2="{Binding IsEditingGameRoster, Converter={StaticResource ListViewSelectionModeFromBooleanNoneOrMultipleConverter}}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- List View Display Not important for describing problem. -->
</DataTemplate>
</ListView.ItemTemplate>
</localUI:MyListView>
USAGE: ViewModel
private ObservableCollection<Player> _selectedPlayers;
public ObservableCollection<Player> SelectedPlayers
{
get
{
if (_selectedPlayers == null)
{
_selectedPlayers = new ObservableCollection<Player>();
}
return _selectedPlayers;
}
}
Edit: I have already looked at the Infragistics forums. The code below is based on their samples. The data binding just doesn't seem to work.
I have an Infragistics XamDataGrid in my view.
<DockPanel>
<grid:XamDataGrid x:Name="gridData" DataContext="{Binding Path=DataEditorDataTable}" DataSource="{Binding Path=DataEditorDataTable.DefaultView}"
IsSynchronizedWithCurrentItem="True" Visibility="{Binding DataGridVisible}">
<grid:XamDataGrid.FieldLayoutSettings>
<grid:FieldLayoutSettings AutoGenerateFields="True" AllowAddNew="False" AllowDelete="False" />
</grid:XamDataGrid.FieldLayoutSettings>
</grid:XamDataGrid>
</DockPanel>
I set the data context for the user control in the constructor.
public DataEditor(SomeDataType DataType, IEventAggregator eventaggregator)
{
InitializeComponent();
this.DataContext = new DataEditorViewModel(DataType, eventaggregator);
}
In the dataeditor view model, I subscribe to an event that lets me know when data has changed and I build a datatable and call a method SetData. (I cannot know ahead how many columns of data are going to be shown in the grid, and these columns keep changing with user interaction, so I am hoping to use the data table to bind.)
I assign the properties in a method like so.
/// <summary>
/// Returns the data that the data editor displays.
/// </summary>
public DataTable DataEditorDataTable
{
get
{
return dtDataEditor;
}
set
{
dtDataEditor = value;
OnPropertyChanged("DataEditorDataTable");
}
}
/// <summary>
/// Method to set data on load
/// </summary>
private void SetData(DataTable dtDataEditor)
{
if (!isDataEditorCellEdited)
if (dtDataEditor != null && dtDataEditor.Rows.Count > 0)
{
try
{
//Assign the data to the grid
DataEditorDataTable = dtDataEditor;
DataGridVisible = Visibility.Visible;
}
catch
{
//If any exception occurs, hide the grid
DataGridVisible = Visibility.Collapsed;
}
}
else
//If no data, hide the grid
DataGridVisible = Visibility.Collapsed;
}
The problem is that the binding is simply not happening. Is there anything in particular I have missed with regard to the bindings?
For debugging the binding errors you should look at the output window in visual studio to see if there are any errors.
Reading the code that you have, I assume that the binding is incorrect and should be:
DataContext="{Binding Path=DataEditorDataTable}" DataSource="{Binding Path=DefaultView}"
The change that I made was to remove the property from the table from the path of the DataSource since the table is already the DataContext and you want to bind to the DefaultView off of the table.
I am binding a WPF Datagrid's ItemsSource property to an ObservableCollection. The data gets pulled in fine but I notice strange behavior when I click on a cell (any cell) and start using the keyboard to navigate.
Tab key works as expected (left-to-right and wraps to the next line down).
Up key does nothing (focus stays on the selected cell).
Down key shifts the focus to the top cell in the column. For example, if I'm on row 10 of column B and hit "down" then the focused cell becomes row 0 of column B.
Left or right keys cause an ArgumentOutOfRangeException. "Specified argument was out of the range of valid values. Parameter name: index". There is no InnerException and the Stack Trace isn't much help either.
Double-clicking any of the cells doesn't enable edit mode.
For some reason I get an "extra" row (rowcount=(collection count +1)) at the bottom of the grid that has correct functionality (right/left buttons work, double-click triggers edit mode). When I double click to enter edit mode in this extra row and then click in a bound row above it, an extra row is added to the grid.
I believe I've isolated the incident to the bound ViewModel class (as opposed to the XAML). In preliminary debugging I've stripped the XAML down to the bare minimum (no styles, 2 columns, only binding is the ItemsSource) and still get the weird behavior.
<UserControl x:Name="FuelMileageView" x:Class="TRD.RaceStrategy.Views.FuelMileage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TRD.RaceStrategy"
xmlns:views="clr-namespace:TRD.RaceStrategy.Views"
xmlns:vm="clr-namespace:TRD.RaceStrategy.ViewModels;assembly=RaceStrategy.Support"
xmlns:converters="clr-namespace:TRD.RaceStrategy.Converters;assembly=RaceStrategy.Support"
xmlns:Behaviours="clr-namespace:TRD.RaceStrategy.Behaviours;assembly=RaceStrategy.Support"
mc:Ignorable="d" DataContext="{Binding Path=Properties[PrimaryVM].CarEventVM.FuelMileage.FuelMileageLaps, Source={x:Static local:App.Current}}"
d:DesignHeight="410" d:DesignWidth="485">
<DataGrid x:Name="dgFMLaps" ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Column 1" >
</DataGridTextColumn>
<DataGridTextColumn Header="Column 2" >
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
Aside from the InitializeComponent() call there is no codebehind to speak of which seems to leave the bound FuelMileageLaps collection as the only culprit.
public class FuelMileageLapViewModel : LapViewModel
{
public FuelMileageLapViewModel() { }
}
NOTE: ObservableCollectionEx is an extension of the ObservableCollection class which apparently accounts for threading problems (?) I've used this class with other collections that I've in turn plugged into datagrids that didn't have this keyboard navigation problem.
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
// Override the event so this class can access it
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Be nice - use BlockReentrancy like MSDN said
using (BlockReentrancy())
{
System.Collections.Specialized.NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;
if (eventHandler == null)
return;
Delegate[] delegates = eventHandler.GetInvocationList();
// Walk thru invocation list
foreach (System.Collections.Specialized.NotifyCollectionChangedEventHandler handler in delegates)
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
// If the subscriber is a DispatcherObject and different thread
if (dispatcherObject != null && dispatcherObject.CheckAccess() == false)
{
// Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
}
else // Execute handler as is
handler(this, e);
}
}
}
}
At this point I'm at my wit's end as to the next step for debugging. There's no obvious place to throw in a breakpoint or try/catch block. I've Googled my problem to death and haven't found anything worthwhile. Please help!
Here's the FuelMileageViewModel class where FuelMileageLaps is initialized:
public class FuelMileageViewModel : WorkspaceViewModel
{
/// <summary>
/// View model for the fuel mileage calculator
/// </summary>
/// <param name="car">Car on which to base all calculations</param>
public FuelMileageViewModel()
{}
/// <summary>
/// A separate collection of laps that store the extra fuel mileage data
/// </summary>
public ObservableCollectionEx<FuelMileageLapViewModel> FuelMileageLaps
{
get
{
if (_fuelMileageLaps == null)
{
_fuelMileageLaps = new ObservableCollectionEx<FuelMileageLapViewModel>();
}
return _fuelMileageLaps;
}
set
{
_fuelMileageLaps = value;
OnPropertyChanged("FuelMileageLaps");
}
}
private ObservableCollectionEx<FuelMileageLapViewModel> _fuelMileageLaps;
/// <summary>
/// Number of laps in the race
/// Affects: Laps_G, Laps_Y
/// </summary>
public int NumberOfLaps
{
get
{
return FuelMileageLaps.Count;
}
set
{
int count = FuelMileageLaps.Count;
if (value < 0)
{
throw new ArgumentException("Number of laps must be a positive integer");
}
if (count != value)
{
if( count < value )
{
int diff = value - count;
for (int i = 0; i < diff; i++)
{
FuelMileageLapViewModel lapToAdd = new FuelMileageLapViewModel();
FuelMileageLaps.Add(lapToAdd);
}
}
OnPropertyChanged("NumberOfLaps");
}
}
}
}
I know this is an older question, but I was having VERY similar problems with a DataGrid recently. The arrow key problems were just as you described. However, I used DataGridTemplateColumns in which a TextBox allowed for editing (otherwise, as you noted, I couldn't edit the data). If I edited a cell and then clicked out of it (particularly if I clicked the row above it) the DataGrid would often produce a duplicate row (though the count in the collection remained the same).
In my case, I found a bug in my Equals(object obj) method for the class whose members were in the ObservableCollection being bound to the DataGrid. This prevented calls to Equals from determining the true equality between two items, and somehow this caused big issues. For example, if after editing a row the data grid can't find that same row already in its list (via calls to a buggy Equals method), perhaps it generates another row (assuming it must not have a row yet for that entry).
In any case, I'd suggest checking out the Equals method in your LapViewModel.
Hope that helps.