I work on a WPF project build in MVVM architecture.
My ViewModel class contains ObservableCollection of SentenceVM class which is also a ViewModel class (implements INotifyPropertyChanged).
This ObservableCollection bound to a DataGrid and I would like to allow adding new records to the collection via the DataGrid build in function.
ObservableCollection<SentenceVM> observe = new ObservableCollection<SentenceVM>()
The problem is that the CollectionChanged event fires at the beginning of the adding row procedure. Thus, I don't know when to refer to the database to submit the new data.
I need this event to be fired when the user has finished adding the new data, not at the beginning.
I know it could be done by generating a command which executes on END inserting or on Enter key but i'm looking for a practical way to do so with this ObservableCollection.
Thank you !
I found out there is no way to do so via ObservableCollection. The only way to do so in practical way is to define an 'EventToCommand' trigger which executes on CellEditEnding event and execute the bounded command, like that:
1) Define the trigger class which inherits from the TriggerAction class and define the invoke method:
public class EventToCommandTrigger : TriggerAction<FrameworkElement>
2) Define the ICommand (dependency property) to bound to:
public ICommand CommandToExecute
{
get { return (ICommand)GetValue(CommandToExecuteProperty); }
set { SetValue(CommandToExecuteProperty, value); }
}
public static readonly DependencyProperty CommandToExecuteProperty =
DependencyProperty.Register("CommandToExecute", typeof(ICommand), typeof(EventToCommandTrigger), new FrameworkPropertyMetadata(null));
3) Implement the abstract Invoke method like that:
protected override void Invoke(object parameter)
{
if (CommandToExecute == null)
return;
if (CommandToExecute.CanExecute(parameter))
CommandToExecute.Execute(parameter);
}
4) Then in the xaml code, connect the CellEditEnding event to the above trigger, like that:
<i:Interaction.Triggers>
<i:EventTrigger EventName="CellEditEnding">
<triggers:EventToCommandTrigger CommandToExecute="{Binding Path=DeleteCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
The problem is that the CollectionChanged event fires at the beginning of the adding row procedure. Thus, I don't know when to refer to the database to submit the new data.
In my opinion, there's nothing wrong with inserting the data into the database inside the CollectionChanged event. In fact, the NotifyCollectionChangedEventArgs gives you everything you need in order to perform this action.
Consider looking at e.NewItems, and e.OldItems. The NewItems is the data that is being inserted into the collection, you can simply iterate through this list and act accordingly.
Related
In à program written in C# and Xamarin Forms (but this question is more based on MVVM), I have an ObservableCollection of Items:
ObserbableCollection<Item> Items { get; set; }
When this collection changes (an item is added or removed to/from the collection), I need to notify other properties and commands because they are all binded to controls on a XAML screen (mostly buttons and labels).
The only solution I found was something like this:
Items.CollectionChanged += (sender, args) =>
{
((Command)OnHoldCommand).ChangeCanExecute();
((Command)CancelSaleCommand).ChangeCanExecute();
((Command)ValidateTakeAwayCommand).ChangeCanExecute();
((Command)ValidateEatInCommand).ChangeCanExecute();
RaisePropertyChanged(() => TotalItems);
RaisePropertyChanged(() => TotalAmount);
};
Do you think there is another solution? Maybe using Fody.PropertyChanged?
I use FreshMvvm as Mvvm framework on top of Xamarin Forms.
Your ViewModels usually implement the INotifyPropertyChanged interface. If it does, then you can subscribe to the PropertyChanged event and listen for changes of the collection, as the view would do. In the event handler, one can notify other properties if necessary.
Another more direct way is, when your ViewModel has control about inserts/changes, then you can notify other properties directly when e.g. the new item is inserted (calling the NotifyPropertyChanged() method).
I’m developing a WPF application in MVVM Patern. The application has a command bar and buttons for Save and Delete records.
The application also has a Master Detail form. It’s a User control and a DataGrid.
Master block : Customer Order
Detail block: Customer Order Lines
(one to many relationship).
Problem:
When clicking a button in command bar, different actions need to be performed depending on the focused item.
For an example if I click the Delete button
It should delete the records only in the DataGrid row, when DataGrid has
focus and row(s) selected.
E.g. DeleteRows() Method should be called.
It should delete the entire record if the master block has focus and not datagrid focused.
E.g. DeleteRecord() Method should be called.
As far as I know I can achieve this using Keyboard focus and Logical focus manager.
But I was unable to find out a proper solution. I should consider that, when clicking the delete button I should ignore the focus of the Delete button.
Please help me to overcome this issue with a sample code.
Since you're using the MVVM pattern, I assume that your buttons in the command bar have corresponding ICommands in the view model.
You can bind your DataGrid's SelectedItem property to a view model property (of course, with a two-way binding) and make that decision according to this property value. If it is null, so there's no item currently selected in the DataGrid, and you can delete the whole record. If it is set to an instance, then a row is selected in the DataGrid, and you can delete only one row.
If you need to exactly know which was the last focused element, you can use the Keyboard.PreviewLostKeyboardFocus attached event in your code behind. Or even better, create your own Behavior with a dependency propery that you can bind to your view model.
enum LastFocusedEntityType { None, Record, Row }
class LastFocusedEntityTrackingBehavior : Behavior<UIElement>
{
public static readonly LastFocusedEntityProperty = DependencyProperty.Register(
"LastFocusedEntity",
typeof(LastFocusedEntityType),
typeof(LastFocusedEntityTrackingBehavior),
LastFocusedEntityType.None);
public LastFocusedEntityType LastFocusedEntity
{
get { return (LastFocusedEntityType)this.GetValue(LastFocusedEntityProperty); }
set { this.Setvalue(LastFocusedEntityProperty, value); }
}
protected override void OnAttached()
{
Keyboard.AddPreviewLostKeyboardFocusHandler(this.AssociatedObject, this.PreviewLostKeyboardFocusHandler);
}
private void PreviewLostKeyboardFocusHandler(object sender, KeyboardFocusChangedEventArgs e)
{
if (e.OldFocus is DataGrid)
{
this.LastFocusedEntity = LastFocusedEntityType.Row;
}
else
{
this.LastFocusedEntity = LastFocusedEntityType.Record;
}
}
}
Then you can apply this behavior to your master block container:
<UserControl>
<i:Interaction.Behaviors>
<local:LastFocusedEntityTrackingBehavior LastFocusedEntity="{Binding LastFocusedEntity, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
</UserControl>
In your view model, your ICommand's Execute() method should then look at the LastFocusedEntity property value and decide what to do next.
Note: I didn't check this code whether it compiles.
Thanks to the advice of tencntraze this question has been solved.
See the bottom of this question and tencntraze's answer for the solution.
I have a rather strange bug in my program that I'm currently wrestling with.
I'm developing an MVVM application, and I have a simple ListBox in my View with its ItemsSource bound to an ObservableCollection of strings. Whenever I add an item to this collection, it /does/ fire an event to notify that the collection has changed, yet this isn't reflected on the GUI during run-time unless I attempt to manually resize the window as the program is running, almost as if a resizing the window forces the GUI to refresh.
The XAML for the relevant control is as follows
<ListBox ItemsSource="{Binding Path=LogWindow}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0,0,0,0" />
Which as you can see is perfectly normal. In my ViewModel for this view, I have a public property called LogWindow defined as follows
public ObservableCollection<string> LogWindow
{
get
{
return _logWindow;
}
set
{
_logWindow = value;
OnPropertyChanged("LogWindow");
}
}
The private member for this is as follows
private ObservableCollection<string> _logWindow;
In the ViewModel constructor I initialize _logWindow and wire up LogWindow CollectionChanged event to a handler which in turn fires a PropertyChanged notification.
My code for this is as follows
public MainWindowViewModel(IDialogService dialogService)
{
... skipped for brevity
LogWindow = new ObservableCollection<string>();
LogWindow.CollectionChanged += LogWindow_CollectionChanged;
...
}
The CollectionChanged event handler is as follows
void LogWindow_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("LogWindow");
}
And finally when my View receives notification that it has received a new message from the Model, I add a new string to the observable collection as follows
LogWindow.Add(_vehicleWeightManager.SerialMessage);
When LogWindow.Add happens, my event listener fires and my breakpoint is hit to confirm the code gets that far as illustrated below
After this my code calls the OnPropertyChanged function which is inherited from my ViewModelBase, this functions fine for my other GUI elements such as labels and the like, so that almost certainly isn't the issue.
I applied a watch to LogWindow to keep track that the collection was indeed being added to, and it is as illustrated below
So at this stage I'm at a loss. My GUI will only update if I resize the window in some way, otherwise there is no visual update. The data is present in the bound property, and afaik the GUI is alerted when a new item is added to the property... Does anyone have any insight that may help?
Thanks to the advice of tencntraze and kallocain this has been solved.
The problem was caused by another thread attempting to add to the LogWindow collection which was causing erroneous behaviour. Rather than use Dispatcher as suggested, I used a TaskFactory code as follows.
TaskFactory uiFactory;
...
...
public MainWindowViewModel(IDialogService dialogService)
{
...
_logWindow = new ObservableCollection<string>();
uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}
...
...
void _vehicleWeightManager_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
if (e.PropertyName == "VehicleWeight")
{
// Marshal the SerialMessage property to the GUI thread.
uiFactory.StartNew(() => LogWindow.Add(_vehicleWeightManager.SerialMessage.Replace("\n", ""))).Wait();
}
}
Further reading for the above method can be found here
If you're receiving the message on another thread, you'll need to invoke it back to the UI thread, otherwise there can be unpredictable behavior. Sometimes this is an exception, other times it works out to do nothing.
this.Dispatcher.BeginInvoke(new Action(() =>
{
LogWindow.Add(_vehicleWeightManager.SerialMessage);
}));
I am having a problem with DomainContext.RejectChanges() and reflecting the rollback in the UI. Here is my scenario.
I have a Model (Entity) generated for use with RIA services (I'll call it Foo)
I have a ViewModel that wraps Foo and extends it (I'll call it FooViewModel)
I have a View that is using Binding to display and update data using the FooViewModel
I have an "outer" ViewModel that holds an ObservableCollection of FooViewModels
The "outer" View has a list box bound to the ObservableCollection
So essentially there is a listbox of FooViewModels on one screen...when you select an item a childwindow is displayed to edit that particular FooViewModel. The FooViewModel is serving both the listbox and the childwindow.
Editing works just fine. A change in the childwindow reflects in the listbox immediately because I am calling RaisePropertyChanged() when the viewmodel properties are updated.
However, If I perform a DomainContext.RejectChanges()...the underlying entity gets rolled back (all changes reverted as expected)...however the FooViewModel isn't aware that this change has occurred and thus the UI isn't updated. If I reselect the item in the listbox on the first screen, the childwindow is displayed with the rolled back changes (which is what I want). The listbox still isn't updated though.
When I reject changes, if I kludge a RaiseProperyChanged() for the field that I changed...the UI listbox does update.
How do I get the UI to update when the underlying entity is rejected?? And how do I do it without tracking what properties of the viewmodel were rolledback? There has to be an easy way to accomplish this that I am just missing.
Something you could try is use the PropertyChanged event on the underlying entity Foo to trigger a RaisePropertyChanged pass on the FooViewModel properties.
so making some assumptions (so this code make sense):
You have a private variables in your FooViewModel
private Foo _foo;
private DomainContext _context;
You have a method on your FooViewModel that is calling RejectChanges() on your domain context.
Like so:
public void RejectChanges()
{
_context.RejectChanges();
}
We have a method that raises the PropertyChanged event on our FooViewModel
Like so:
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(propertyName);
}
Ok, now we have that established, lets have a look at exactly what happens when you call RejectChanges() on a domain context.
When you call RejectChanges() this bubbles down through the DomainContext to its EntityContainer, then to each EntitySet in that container and then to each Entity in the set.
Once there (and in the EntitySet), it reapplies the original values if there was any, removes the entity if it was added, or adds it if it was deleted. If there was changes to the values, then it applies them back to the properties.
So theoretically, all the RaisePropertyChanged(), that are generated in the entity properties, should be triggered.
NOTE: I haven't actually tested this. If this isn't the case, then none of this works :P
So we can hook into PropertyChanged event of the Foo entity, and raise the PropertyChanged event on our FooViewModel.
so our RejectChanges() method might look like this:
public void RejectChanges()
{
Func<object, PropertyChangedEventArgs> handler = (sender, e) =>
{
RaisePropertyChanged(e.PropertyName);
};
_foo.PropertyChanged += handler;
_context.RejectChanges();
_foo.PropertyChanged -= handler;
}
So we hook up an event handler to our Foo entity, which calls the FooViewModel.RaisePropertyChanged method with the property name that is changing on the Foo entity.
Then we reject changes (which triggers the property changes),
then we unhook the event handler.
Pretty long winded, but I hope this helps :)
I presume that the call to DomainContext.RejectChanges() is happening within the ViewModel as you probably bound that to some command or method called from the parent ViewModel. Since all your bindings to the data is done on the ViewModel properties you will have to raise the property change event on them when you directly manipulate the model outside of those properties. Which you probably doing already.
public void RejectChanges()
{
DomainContext.RejectChanges();
RaisePropertyChangeOnAll();
}
How you implement RaisePropertyChangeOnAll() can be done simply with a list of RaisePropertyChange("...") for each property or you could do it through reflection (if Silverlight permissions allow, not too sure about it) by adding an Attribute on each property you want to raise. Find all the properties that are tagged with it and call RaisePropertyChanged on the MemberInfo.Name value.
[Raiseable]
public string SomeValue
{
...
}
Just an idea but may not be a perfect solution.
This question is the result of my previous question DataGrid - grid selection is reset when new Data is arrived
=====================================================================
I have such DataGrid
<DataGrid AutoGenerateColumns="True" HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" ItemsSource="{Binding DataList}" IsReadOnly="True"/>
In my ViewModel I have such field:
public ObservableCollection<ConsoleData> DataList { get; set; }
And such method which is called every second:
private void model_DataArrived(List<ConsoleData> dataList)
{
DataList.Clear();
dataList.ForEach(x => DataList.Add(x));
}
=====================================================================
We have figured out that because I call DataList.Clear the selection in the UI control is cleared as well and I don't want this to happen. So likely I should not replace instances of ConsoleData in ViewModel, instead of that I should update these instances.
But ObservableCollection observes for add/remove I guess and doesn't observe for update isn't? So if I will update instances DataBinding will not work?
Another problem with the current application is that dataList.ForEach(x => DataList.Add(x)); forces databinding to execute on each iteration instead of executing only at the end?
Overall what is the right way to do what I want to do because current application doesn't work and has too many problems...
It's not clear how you are planning on updating an item in your ObservableCollection. There are at least two ways to do this. One way is to update all the properties that are changed in a ConsoleData object. In this case, you would have ConsoleData implement INotifyPropertyChanged. Another way is a direct update of item in the ObservableCollection. To do this, you could use the SetItem method of the ObservableCollection. This will raise the CollectionChanged event as the MSDN documentation for SetItem indicates.
Since you have indicated that you are using MVVM, the generally accepted thing to do would be to make your ObservableCollection be a collection of ConsoleDataViewModel instead of ConsoleData.
Another problem with the current application is that dataList.ForEach(x => DataList.Add(x)); forces databinding to execute on each iteration instead of executing only at the end?
I don't think you will have the refresh problem if you modify your model_DataArrived method to update instead of clear/add as indicated above.
The problem is the ObservableCollection not notifying when an item is changed; it notifies only when items are added and removed, as you say. To solve this problem I created a class I call VeryObservableCollection. For each object added, it hooks the object's NotifyPropertyChanged event to a handler that triggers a CollectionChanged event. For each object removed, it removes the handler. Very simple and should solve your issue. You just need to make sure you implement NotifyPropertyChanged on the objects held in the collection. For example:
public class VeryObservableCollection<T> : ObservableCollection<T>
/// <summary>
/// Override for setting item
/// </summary>
/// <param name="index">Index</param>
/// <param name="item">Item</param>
protected override void SetItem(int index, T item)
{
try
{
INotifyPropertyChanged propOld = Items[index] as INotifyPropertyChanged;
if (propOld != null)
propOld.PropertyChanged -= new PropertyChangedEventHandler(Affecting_PropertyChanged);
}
catch (Exception ex)
{
Exception ex2 = ex.InnerException;
}
INotifyPropertyChanged propNew = item as INotifyPropertyChanged;
if (propNew != null)
propNew.PropertyChanged += new PropertyChangedEventHandler(Affecting_PropertyChanged);
base.SetItem(index, item);
}
I think you can do something like this
private ObservableCollection<ConsoleData> dataList;
public ObservableCollection<ConsoleData> DataList
{
get {return dataList; }
set {dataList = value;}
}
And your data manipulations access only the field dataList. One time fiished manipulation force DataBinding to update, or reassign it, forcing in this way Biding to raise notification to WPF.
Should work.
if your ConsoleData implements INotifyPropertyChanged you can simply update these and you will see the changes in your ui - without selection lost.
OK first we need to understand that an item which was the source of selection on any Selector controls such as ListBox, DataGrid etc., if removed, will loose the selection.
For this please use a bulk adding and single notifying FastObservableCollection.
But to mainain such selection, you will have to set the selected item / value back to the DataGrid once Clear and AddRange takes place.