I'm using a MVVM approach with WPF to let the user select one item in a combobox. The model contains a set of possible options, the combobox is bound to this set, the current selection is again bound to a property of my model. This part works fine.
Now I'd like to allow the user to enter an arbitrary text into the combobox. If the text doesn't correspond to an existing item the program should ask him if he wants to add a new item. He should also be allowed to cancel the action and select another item.
How would I do that within the MVVM pattern?
You would check the "already existing" status of the text from your ViewModel's bound property setter. At that point, you need a mechanism to raise an event and decide what to do based on what happens.
An example:
enum Outcome { Add, Cancel }
class BlahEventArgs : EventArgs
{
Outcome Outcome { get; set; }
}
class ViewModel
{
private string name;
public EventHandler<BlahEventArgs> NotExistingNameSet;
public Name
{
get { return this.name; }
set
{
if (/* value is existing */) {
this.name = value;
return;
}
var handler = this.NotExistingNameSet;
if (handler == null) {
// you can't just return here, because the UI
// will desync from the data model.
throw new ArgumentOutOfRangeException("value");
}
var e = new BlahEventArgs { Outcome = Outcome.Add };
handler(this, e);
switch (e.Outcome) {
case Outcome.Add:
// Add the new data
this.name = value;
break;
case Outcome.Cancel:
throw new Exception("Cancelled property set");
}
}
}
}
Your View would add an event handler to NotExistingNameSet to present appropriate UI and set the value of e.Outcome accordingly.
Related
I am using vaadin 7.7.7
In a grid i have a combobox as an edited item in one of the columns
as
grid.addColumn("columnProperty").setEditorField(combobox);
I need to update a property/cell in same row based on the combobox selection change
My issue is , the selection change event triggers twice, once when the combobox in clicked and second when the selection value is changed. But the updated value in next cell gets reflected on UI only first time.
Below is the code written . Any solutions?
Combobox.addValueChangeListener(new ValueChangeListener()
#Override
public void valueChange(ValueChangeEvent event) {
// below line works only first time when the combobox is clicked,but i want
//it when the item in the combobox is changed
gridContainer.getContainerProperty(editedRow,"editedColumProperty").setValue("ValueTobeUpdated");}
});
Need to update the unit column on combobox change in edited mode(before saving)
Refer below link for image
example image
You will get value change events even when the field gets value that it should show to the user. In order to get event that indicates that the user has accepted the input you should use field group (setEditorFieldGroup).
From Book of Vaadin example for grid editing:
grid.getColumn("name").setEditorField(nameEditor);
FieldGroup fieldGroup = new FieldGroup();
grid.setEditorFieldGroup(fieldGroup);
fieldGroup.addCommitHandler(new CommitHandler() {
private static final long serialVersionUID = -8378742499490422335L;
#Override
public void preCommit(CommitEvent commitEvent)
throws CommitException {
}
#Override
public void postCommit(CommitEvent commitEvent)
throws CommitException {
Notification.show("Saved successfully");
}
});
Edit
I assume that you want to connect Parameter and Unit comboboxes. I would do that with this kind of value change lister
BeanItemContainer container = new BeanItemContainer<>(
Measurement.class,
measurements);
Grid grid = new Grid(container);
grid.setEditorEnabled(true);
ComboBox parameterComboBox = new ComboBox();
ComboBox unitComboBox = new ComboBox();
parameterComboBox.addItems(Parameter.Pressure, Parameter.Temperature, Parameter.Time);
parameterComboBox.addValueChangeListener(v -> setUnits(parameterComboBox, unitComboBox));
grid.getColumn("parameter").setEditorField(parameterComboBox);
grid.getColumn("unit").setEditorField(unitComboBox);
Units could be updated like this. I think you need to preserve current value and set it back if you replace available items in the combobox.
private void setUnits(ComboBox parameterComboBox, ComboBox unitComboBox) {
Object currentValue = unitComboBox.getValue();
List<String> units = unitsForParameter(parameterComboBox.getValue());
unitComboBox.removeAllItems();
unitComboBox.addItems(units);
if (units.contains(currentValue)) {
unitComboBox.setValue(currentValue);
} else {
unitComboBox.setValue(null);
}
}
private List<String> unitsForParameter(Object value) {
if (value == null) {
return Collections.emptyList();
} else if (value == Parameter.Pressure) {
return asList("Pascal", "Bar");
} else if (value == Parameter.Temperature) {
return asList("Celcius", "Kelvin");
} else if (value == Parameter.Time) {
return asList("Second", "Minute");
} else {
throw new IllegalArgumentException("Unhandled value: " + value);
}
}
I have a DataGrid in a WPF application which has for its ItemsSource a custom collection that I wrote. The collection enforces that all its items satisfy a certain requirement (namely they must be between some minimum and maximum values).
The collection's class signature is:
public class CheckedObservableCollection<T> : IList<T>, ICollection<T>, IList, ICollection,
INotifyCollectionChanged
where T : IComparable<T>, IEditableObject, ICloneable, INotifyPropertyChanged
I want to be able to use the DataGrid feature in which committing an edit on the last row in the DataGrid results in a new item being added to the end of the ItemsSource.
Unfortunately the DataGrid simply adds a new item created using the default constructor. So, when adding a new item, DataGrid indirectly (through its ItemCollection which is a sealed class) declares:
ItemsSource.Add(new T())
where T is the type of elements in the CheckedObservableCollection. I would like for the grid to instead add a different T, one that satisfies the constraints imposed on the collection.
My questions are: Is there a built in way to do this? Has somebody done this already? What's the best (easiest, fastest to code; performance is not an issue) way to do this?
Currently I just derived DataGrid to override the OnExecutedBeginEdit function with my own as follows:
public class CheckedDataGrid<T> : DataGrid where T : IEditableObject, IComparable<T>, INotifyPropertyChanged, ICloneable
{
public CheckedDataGrid() : base() { }
private IEditableCollectionView EditableItems {
get { return (IEditableCollectionView)Items; }
}
protected override void OnExecutedBeginEdit(ExecutedRoutedEventArgs e) {
try {
base.OnExecutedBeginEdit(e);
} catch (ArgumentException) {
var source = ItemsSource as CheckedObservableCollection<T>;
source.Add((T)source.MinValue.Clone());
this.Focus();
}
}
}
Where MinValue is the smallest allowable item in the collection.
I do not like this solution. If any of you have advice I would be very appreciative!
Thanks
This problem is now semi-solvable under 4.5 using the AddingNewItem event of the DataGrid. Here is my answer to a similar question.
I solved the problem by using DataGrid's AddingNewItem event. This almost entirely undocumented event not only tells you a new item is being added, but also [allows lets you choose which item is being added][2]. AddingNewItem fires before anything else; the NewItem property of the EventArgs is simply null.
Even if you provide a handler for the event, DataGrid will refuse to allow the user to add
rows if the class doesn't have a default constructor. However, bizarrely (but thankfully) if you do have one, and set the NewItem property of the AddingNewItemEventArgs, it will never be called.
If you choose to do this, you can make use of attributes such as [Obsolete("Error", true)] and [EditorBrowsable(EditorBrowsableState.Never)] in order to make sure no one ever invokes the constructor. You can also have the constructor body throw an exception
Decompiling the control lets us see what's happening in there...
For anybody interested, I ended up solving the problem by just deriving from BindingList<T> instead of ObservableCollection<T>, using my derived class as the ItemsSource in a regular DataGrid:
public class CheckedBindingList<T> : BindingList<T>, INotifyPropertyChanged where T : IEditableObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Predicate<T> _check;
private DefaultProvider<T> _defaultProvider;
public CheckedBindingList(Predicate<T> check, DefaultProvider<T> defaultProvider) {
if (check == null)
throw new ArgumentNullException("check cannot be null");
if (defaultProvider != null && !check(defaultProvider()))
throw new ArgumentException("defaultProvider does not pass the check");
_check = check;
_defaultProvider = defaultProvider;
}
/// <summary>
/// Predicate the check item in the list against.
/// All items in the list must satisfy Check(item) == true
/// </summary>
public Predicate<T> Check {
get { return _check; }
set {
if (value != _check) {
RaiseListChangedEvents = false;
int i = 0;
while (i < Items.Count)
if (!value(Items[i]))
++i;
else
RemoveAt(i);
RaiseListChangedEvents = true;
SetProperty(ref _check, value, "Check");
ResetBindings();
}
}
}
public DefaultProvider<T> DefaultProvider {
get { return _defaultProvider; }
set {
if (!_check(value()))
throw new ArgumentException("value does not pass the check");
}
}
protected override void OnAddingNew(AddingNewEventArgs e) {
if (e.NewObject != null)
if (!_check((T)e.NewObject)) {
if (_defaultProvider != null)
e.NewObject = _defaultProvider();
else
e.NewObject = default(T);
}
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e) {
switch (e.ListChangedType) {
case (ListChangedType.ItemAdded):
if (!_check(Items[e.NewIndex])) {
RaiseListChangedEvents = false;
RemoveItem(e.NewIndex);
if (_defaultProvider != null)
InsertItem(e.NewIndex, _defaultProvider());
else
InsertItem(e.NewIndex, default(T));
RaiseListChangedEvents = true;
}
break;
case (ListChangedType.ItemChanged):
if (e.NewIndex >= 0 && e.NewIndex < Items.Count) {
if (!_check(Items[e.NewIndex])) {
Items[e.NewIndex].CancelEdit();
throw new ArgumentException("item did not pass the check");
}
}
break;
default:
break;
}
base.OnListChanged(e);
}
protected void SetProperty<K>(ref K field, K value, string name) {
if (!EqualityComparer<K>.Default.Equals(field, value)) {
field = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
This class is incomplete, but the implementation above is enough for validating lists of statically-typed (not built by reflection or with the DLR) objects or value types.
I have a usercontrol bound to a VM. This VM contains a collection property, lets call it "MyCollection" and several regular properties lets call one of them "SomeProperty". As you can see, the get and set logic for this property references the collection in the VM.
The problem is, when I make a change to "MyCollection", this obviously has an impact on the values that are shown in the UI (as they are calculated based on it). However, my UI doesn't appear to be smart enough to update itself whenever "MyCollection" changes.
Here is the VM my usercontrol is bound to:
public class MyVM
{
private ObservableCollection<SomeOtherVM> _myCollection = new ObservableCollection<SomeOtherVM>();
public MyVM()
{
}
public ObservableCollection<SomeOtherVM> MyCollection
{
get { return _myCollection; }
[Notify]
set
{
_myCollection = value;
}
}
public virtual string SomeProperty
{
get
{
if (_myCollection.Count == 1)
return _myCollection[0].SomeProperty;
else
return "More than one "SomeOtherVM" has been selected";
}
[Notify]
set
{
foreach (SomeOtherVM s in _myCollection)
{
s.SomeProperty = value;
}
}
}
}
}
Note that nothing in my usercontrol is directly bound to the collection, it is only bound to other properties that reference the collection in its get; set; methods.
Is there anything I could do in the VM to force the UI to update whenever "MyCollection" is changed? I want to avoid having to put anything in the code behind for the user control.
Subscribe to CollectionChanged of MyCollection and fire PropertyChanged-notifications for the other properties there (no fancy attribute usage for you).
I'm using the MVVM design pattern, with a ListView bound to a ListCollectionView on the ViewModel. I also have several comboboxes that are used to filter the ListView. When the user selects an item from the combobox, the ListView is filtered for the selected item. Whenever I want to filter on top of what is already filtered, it undoes my previous filter like it never happened. The same is also true for removing a filter. Removing a filter for one combobox removes all filters and displays the original list. Is it possible to have multiple, separate filters on the same ListCollectionView?
Am I doing something wrong, or is this simply not supported? You can find a screen capture of my application here to see what I am trying to accomplish. Here's my code for filtering...
/// <summary>
/// Filter the list
/// </summary>
/// <param name="filter">Criteria and Item to filter the list</param>
[MediatorMessageSink("FilterList", ParameterType = typeof(FilterItem))]
public void FilterList(FilterItem filter)
{
// Make sure the list can be filtered...
if (Products.CanFilter)
{
// Now filter the list
Products.Filter = delegate(object obj)
{
Product product = obj as Product;
// Make sure there is an object
if (product != null)
{
bool isFiltered = false;
switch (filter.FilterItemName)
{
case "Category":
isFiltered = (product.Category.IndexOf(filter.Criteria, StringComparison.CurrentCultureIgnoreCase)) != -1 ? true : false;
break;
case "ClothingType":
isFiltered = (product.ClothingType.IndexOf(filter.Criteria, StringComparison.CurrentCultureIgnoreCase)) != -1 ? true : false;
break;
case "ProductName":
isFiltered = (product.ProductName.IndexOf(filter.Criteria, StringComparison.CurrentCultureIgnoreCase)) != -1 ? true : false;
break;
default:
break;
}
return isFiltered;
}
else
return false;
};
}
}
Every time you set Filter property you reset previous filter. This is a fact. Now how can you have multiple filters?
As you know, there are two ways to do filtering: CollectionView and CollectionViewSource. In the first case with CollectionView we filter with delegate, and to do multiple filters I'd create a class to aggregate custom filters and then call them one by one for each filter item. Like in the following code:
public class GroupFilter
{
private List<Predicate<object>> _filters;
public Predicate<object> Filter {get; private set;}
public GroupFilter()
{
_filters = new List<Predicate<object>>();
Filter = InternalFilter;
}
private bool InternalFilter(object o)
{
foreach(var filter in _filters)
{
if (!filter(o))
{
return false;
}
}
return true;
}
public void AddFilter(Predicate<object> filter)
{
_filters.Add(filter);
}
public void RemoveFilter(Predicate<object> filter)
{
if (_filters.Contains(filter))
{
_filters.Remove(filter);
}
}
}
// Somewhere later:
GroupFilter gf = new GroupFilter();
gf.AddFilter(filter1);
listCollectionView.Filter = gf.Filter;
To refresh filtered view you can make a call to ListCollectionView.Refresh() method.
And in the second case with CollectionViewSource you use Filter event to filter collection. You can create multiple event handlers to filter by different criteria. To read more about this approach check this wonderful article by Bea Stollnitz: How do I apply more than one filter? (archive)
Hope this helps.
Cheers, Anvaka.
Every time the user filters, your code is replacing the Filter delegate in your collection view with a new, fresh one. Moreover, the new one only checks the particular criteria the user just selected with a ComboBox.
What you want is a single filter handler that checks all criteria. Something like:
public MyViewModel()
{
products = new ObservableCollection<Product>();
productsView = new ListCollectionView(products);
productsView.Filter += FilterProduct;
}
public Item SelectedItem
{
//get,set omitted. set needs to invalidate filter with refresh call
}
public Type SelectedType
{
//get,set omitted. set needs to invalidate filter with refresh call
}
public Category SelectedCategory
{
//get,set omitted. set needs to invalidate filter with refresh call
}
public ICollection<Product> FilteredProducts
{
get { return productsView; }
}
private bool FilterProduct(object o)
{
var product = o as Product;
if (product == null)
{
return false;
}
if (SelectedItem != null)
{
// filter according to selected item
}
if (SelectedType != null)
{
// filter according to selected type
}
if (SelectedCategory != null)
{
// filter according to selected category
}
return true;
}
Your view can now just bind to the appropriate properties and the filtering will just work.
How can I implement cancelation of editing an object using MVVM.
For example: I have a list of customers. I choose one customer an click the button "Edit", a dialog window(DataContext is binded to CustomerViewModel) opens and I start editing customer's fields. And then I decide to cancel editing, but the fields of the customer have been already changed, so how can I return a customer to its previous state in MVVM?
Check out the IEditableObject interface. Your Customer class should implement that, and your commands can execute BeginEdit / CancelEdit / EndEdit as appropriate.
You can use binding with UpdateSourceTrigger=Explicit. Here you can find more information how this can be implemented.
One super easy way, if your object is already serializable, such as if you are using WCF. You can serialize your original object into an internal field. If, your object isn't serializable, then just use AutoMapper to create a copy of your object with one line of code.
Order backup = Mapper.Map<Order, Order>(order);
When you handle your CancelCommand, just call AutoMapper in reverse. Since your properties already have a change notification everything just works. Its possible you could combine these techniques with IEditableObject, if you need and want to write the extra code.
In this article, Raul just reload the object from the DB. I guess it's less trouble than the solution Kent proposes.
internal void Cancel(CustomerWorkspaceViewModel cvm)
{
Mainardi.Model.ObjectMapping.Individual dc = cvm.DataContext
as Mainardi.Model.ObjectMapping.Individual;
int index = 0;
if (dc.ContactID > 0 && dc.CustomerID > 0)
{
index = _customerCollectionViewModel.List.IndexOf(dc);
_customerCollectionViewModel.List[index] =
_customerBAL.GetCustomerById(dc.CustomerID);
}
Collection.Remove(cvm);
}
Based on Камен Великов's answer:
You can mark your bindings as to be updated manually by defining
<TextBox Name="yourTextBox" Text="{BindingPath=YourBinding, UpdateSourceTrigger=Explicit}" />
in your view (XAML). Then, you have to write the changes from your UI in ViewModel by calling
yourTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
when Save is clicked.
Please note, if there are updated to the binding source triggered from anything else, they are still shown directly in the UI.
I had this problem too. I solved it using "The Memento Pattern Design". With this pattern you could easy save a copy of your original object and, in selectedIndexChange (of a control) or in the Cancel button, you could restore easy the prior version of your object.
An example of use of this pattern is available at How is the Memento Pattern implemented in C#4?
An example of code:
If we have a class User with properties UserName Password and NombrePersona we need to add methods CreateMemento and SetMemento:
public class Usuario : INotifyPropertyChanged
{
#region "Implementación InotifyPropertyChanged"
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private String _UserName = "Capture su UserName";
public String UserName
{
get { return _UserName; }
set { _UserName = value; RaisePropertyChanged("UserName"); }
}
private String _Password = "Capture su contraseña";
public String Password
{
get { return _Password; }
set { _Password = value; RaisePropertyChanged("Password"); }
}
private String _NombrePersona = "Capture su nombre";
public String NombrePersona
{
get { return _NombrePersona; }
set { _NombrePersona = value; RaisePropertyChanged("NombrePersona"); }
}
// Creates memento
public Memento CreateMemento()
{
return (new Memento(this));
}
// Restores original state
public void SetMemento(Memento memento)
{
this.UserName memento.State.UserName ;
this.Password = memento.State.Password ;
this.NombrePersona = memento.State.NombrePersona;
}
Then, we need a class Memento that will contain the "copy" of our object like this:
/// <summary>
/// The 'Memento' class
/// </summary>
public class Memento
{
//private Usuario _UsuarioMemento;
private Usuario UsuarioMemento { get; set; }
// Constructor
public Memento(Usuario state)
{
this.UsuarioMemento = new Usuario();
this.State.UserName = state.UserName ;
this.State.Password = state.Password ;
this.State.NombrePersona = state.NombrePersona ;
}
// Gets or sets state
public Usuario State
{
get { return UsuarioMemento; }
}
}
And we need a class that will generate and contains our memento object:
/// <summary>
/// The 'Caretaker' class
/// </summary>
class Caretaker
{
private Memento _memento;
// Gets or sets memento
public Memento Memento
{
set { _memento = value; }
get { return _memento; }
}
}
Then for implement this pattern we have to create an instance of Caretaker class
Caretaker creadorMemento = new Caretaker();
And create our memento object when a new user was selected for edit, for example in selectedIndexChange after the SelectedUser has been initializing, I use the method for event RaisPropertyChanged like this:
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
if (prop == "RowIndexSelected") // This is my property assigned to SelectedIndex property of my DataGrid
{
if ((this.UserSelected != null) && (creadorMemento .Memento != null))
{
this.UserSelected.SetMemento(creadorMemento .Memento);
}
}
if (prop == "UserSelected") // Property UserSelected changed and if not is null we create the Memento Object
{
if (this.UserSelected != null)
creadorMemento .Memento = new Memento(this.UserSelected);
}
}
An explication for this, when selectedIndexChanged change value we check if UserSelected and our memento object are not null means that our actual item in edit mode has changed then we have to Restore our object with the method SetMemento.
And if our UserSelected property change and is not null we "Create our Memento Object" that we will use when the edit was cancel.
For finish, we have use the SetMemento method in every method that we need to cancel the edition, and when the edit has commited like in the SaveCommand we can set null our memento object like this this.creadorMemento = null.
You could also, in your ViewModel copy the model's state to internal fields, and then expose these and then only set them on the model, when the user actually commits the change.
Problem could be, that on-the-fly validation will be more troublesome if validation relies on the entity being updated - if this is a requirement you could create a clone of the model to work on and then merging the clone with the actual entity when it is saved.