I am having some trouble with my comboBox logic in my viewModel. The viewModel populates the comboBox and the user can select items.
When an item is selected, some editable info appears in the view and they can make changes. Now, I want to prompt the user to save if changes were made and not saved and they are trying to select another item in the drop down (a "Save?" yes/no/cancel messageBox).
Right now I need to remember the previously selected item and handle this myself by resetting the selected item if the user selects cancel. Since the comboBox does not have a PreviewSelectionChanged event, this is the only way I can think of of handling this scenario. It works but it gets a little messy hwen there are multiple comboBoxes, etc.
Am I missing anything or is this just the way it needs to be done?
You essentially have to make a flag in your view model called isDirty or something along those lines.
public class EditorViewModel
{
private bool _isDirty = false;
private long _editableProperty;
public long EditableProperty
{
get { return _editableProperty; }
set
{
_editableProperty = value;
// We've detected a change so mark this view model as dirty.
_isDirty = true;
}
}
}
Note that you will have to have to jump through a few more hoops if you want ensure that the data is in fact different from your original. So say someone accidently adds a space in EditableProperty and removes it your view model will think it's dirty and prompt the user.
The Windows Forms ComboBox provided a SelectionChangeCommitted event, but for the WPF ComboBox control you correct in that there is no event that will notify before the selection change occurs that will provide you with a means of cancelling the event.
If you are going to take a change tracking/editable approach, I would recommend considering implementing IChangeTracking and IEditableObject on the items in your combobox items source.
You will probably have to handle the SelectionChanged event, inspect the removed items to determine if the item that was previously selected was modified and then display a dialog requesting confirmation. If no/cancel was indicated, you can then set the selected index back to that of the previously selected item.
What about making the Editable item a copy of an item instead of the actual item?
So your ViewModel would contain
ObservableCollection<MyModel> ComboBoxItems;
int SelectedComboBoxIndex;
MyModel EditingItem;
Whenever the PropertyChange event occurs on SelectedComboBoxIndex, you check and see if EditingItem is null or not. If it is null, it means you're safe to switch and you set
EditingItem = ComboBoxItem[SelectedComboBoxIndex]).Copy();
If the EditingItem is not null, then you throw up a prompt asking if the user wants to save changes or cancel.
When the user hits Save, it takes the EditingItem and applies the changes to the data store and updates the item in the ComboBoxItems list.
If they hit Cancel, the EditingItem is simply discarded.
Related
I have a Windows form with a series of list boxes
The contents of subsequent lists change based on choices in earlier ones
All the boxes are bound to a BindingList with OnListChanged enabled
I use this to retain user-specified 'checked' items when elements are added or removed
However, I note that if I add an item to the list, it appears to update the UI only after all the other events have been fired
I've looked at the events I would expect to fire on the CheckedListBox, and in the related ViewModel, in order to catch the one which adds an item to the list, but so far without success
Can someone please advise me which event would allow me to call my 'CheckBoxes' method after the UI has been updated, otherwise they all get set to blank again until the form is closed and opened
private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// I've removed the debug statements, but this event when the bound list updates is fired before the UI updates
}
private void teams_checked_list_box_SelectedIndexChanged(object sender, EventArgs e)
{
// same with this event, and the other events like SizeChanged
}
EDITED: On reflection, I realise that the problem occurs because I am not setting the true / false checked flag in the binding, because I couldn't figure out how to do it. If someone could point me in the right direction? Code currently looks like this:
teams_checked_list_box.DataSource = Globals.ThisAddIn.TFSTeamsViewModel.ListOfTeamsFromVM.value;
teams_checked_list_box.DisplayMember = "name";
So basically I am only updating the item name, and the check flag is handled on a later pass
How are you adding items to your list? Are you re-binding the listbox? There are basically two ways you can do this depending on your method of adding items:
If you are re-binding, capture the event BEFORE the item is added. Loop through the CheckedItems property of your list box. Save the values. Add your item. Loop through the NEW values and recheck.
OR
When the user checks the item in the listbox, capture that using the ItemChecked event and then store the value of the item somewhere. (Variable, hidden textbox -- ugly, but it works -- etc.) If you are only adding items to the BOTTOM of your list, you can store the index of the item. If you are re-sorting your list, you'll need to store a unique id to reference the item. (Note: you will also need to REMOVE the checked items from your stored value if the user UN-checks an item in your listbox.) Then, after you add your items to the listbox, loop back through and set the checked value on the appropriate listbox items.
I have created a simple sample with datacontext. Please get the sample from here : source I understand that the uses of DataContext is
If the user change any content in UI, it should be updated in the back end datacontext class without any additional work.
If we make any change in back end datacontext object, it should be updated in UI without any additional work.
In my sample,
If I click "click" button, the textbox value gets updated. - pass
If I click "update" button, the textbox value gets updated. - pass
If I click "clear" button, the textbox value not updated. - fail
After clicked "clear" button, If "click" or "update" button is clicked the textbox value not updated. - fail
Am I doing anything wrong? If yes, how can I initialize some value in the textbox in constructor, and after that if the user make changes, the datacontext object values need to be updated. If I update any value in code behind, the values need to updated in UI. Also if I click "clear" button all the textbox values need to be cleared. After that if user enter value again, the object need to update. How can I achieve it? Please help.
Note: In my sample I have commented some lines. In that i have casted the datacontext to the model class every time and changed it. Its working fine. Do i need to use that method to update values run time. Changing values in object will not update in UI?
The problem is that you consider the variable 'dc' linked to the DataContext, but that is not the case. To fix your code simply set the DataContext again after you change the 'dc' variable.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
dc = new Journal();
myPanel.DataContext = dc;
//myPanel.DataContext = new Journal();
}
Friends,
I have a WPF Combobox. When the Combobox is opened, I have the items dynamically generated based upon environment variables. So basically a combobox that is bound to a list that is dynamically changing.
Everything works as expected until I Exit the combobox with the dropdown open to enter another control(another combobox).
When I reopen the first combobox, the items appear to be frozen and no longer bound to the list when INDEED the list is changing and is still bound. Its almost like the binding broke.
When this event occurs, I have attempted to forcefully add items, and that doesn't work either. I can see in the code behind that the combobox now contains the additional items, yet it doesn't appear that contain them in the UI.
What is this black magic? Any way to prevent it? is this some type of Stuck focus issue? Maybe the dropdown isn't re-sizing?
I think i have narrowed it down to the physical dropdown is not re-sizing to the new items.
EDIT*
The controls are dynamically generating, so I have no real hard code to show you other than this.
private void CBControl_DropDownOpened(object sender, EventArgs e)
{
((ComboBox)sender).Items.Add("Option");
}
On this event, I will add an items to the combobox, although the items ARE being added to the list, they are not displayed in the UI.
EDIT 2*
I figured it out, so i have 2 comboboxes, it appears that the 2nd was steeling and holding the focus INSIDE the dropdown. (odd bug)
in order to fix it i needed to release it bu changing the index of the 2nd combo box WHILE it is open.
int sel = ComboBoxTwo.SelectedIndex;
ComboBoxTwo.IsDropDownOpen = true;
ComboBoxTwo.SelectedIndex = -1;
ComboBoxTwo.IsDropDownOpen = true;
ComboBoxTwo.SelectedIndex = sel;
and I had to manage the unintended recursive call.
So, here it is.
You got 2 ComboBoxes. If you Open one then directly click into another, the focus is moved to the second combobox dropdown.
If you make any changes to the first combobox, the changes will not take effect to the style(dropdown resize) UNTIL you release the focus from the 2nd combo box dropdown item. Although the items change will take effect, the resize wont.
to release the focus, you need to open the second combox and change the selected index like so:
int sel = ComboBoxTwo.SelectedIndex;
ComboBoxTwo.IsDropDownOpen = true;
ComboBoxTwo.SelectedIndex = -1;
ComboBoxTwo.IsDropDownOpen = true;
ComboBoxTwo.SelectedIndex = sel;
10 hours of debugging. 5 hours of research. First solution I have found. It may be dirty, but its all I can find.
How can I reset my viewmodel when the user clicks new button on the view that has the viewmodel as it's datacontext?
For example:
If I have a view NewCustomer and upon save, the data is saved to the DB and the newly created account number is displayed. But when the user clicks the New button in the screen, I want the view (viewmodel) to be reinitialized. Or if the user clicked cancel in the screen to clear all changes.
How can I achieve this? I am using Prism 5.0 and Unity as my container.
If I used IRegionMemberLifetime, I can clear the viewmodel data when I navigate away and navigate again to the view (by setting the KeepAlive as false on clicking New button before navigating away). But I want the form to be cleared without navigating. Can this be done?
You could have a screen/workspaceViewModel, and another ViewModel wrapping your data.
So two classes: CarScreenViewModel and CarViewModel.
The CarScreenViewModel would have a property, say CurrentCar, which reflects what is currently selected in the screen. Then, when clicking the Create button, you simply set:
CurrentCar = new CarViewModel();
Resetting partially loaded data will only lead to behaviour that is hard to reproduce. It is better to start with a fresh instance.
Your standard approach will be something like below
ViewModels
CustomersContainerViewModel which contains
a collection of CustomerViewModel s
and ICommands like
CreateNewCustomer
DeleteExistingCustomer
UpdateExistingCustomer
Your View will contain
the CustomersContainerView which will contain
a collection of Customer Objects in your required UI element
a button to Create new customer (which will launch a new screen which contains the newCustomer fields it can also contain cancel, which will just close the form)
a button to delete (can also be a ContextMenu)
a button to update (can also be a ContextMenu) which will launch a customer form filled with details from DB.
Hopefully this makes some sense... Let me know if you have problem with any of the above
Update - Forgot to add. NewCustomer Command will add a new Customer object to your CustomerCollection and that should open a NewCustomer form (or whatever you chose) for user to input the customer details. Cancel/Delete will just remove that record from the collection. Delete will update the DB as well in addition
In my case
yourViewName.variableName.postValue("")
My screen has a list of objects of type Person, and a form that displays the person properties (name, address, weight, etc.) via databinding.
I want to be able to:
Click in a button "Edit properties", and start modifying the person's properties (I can already do that using Commands in the ViewModel);
If I click "Cancel", the edited info "rollback" to the original unmodified values of that person;
If I click "Save changes", JUST THEN the person's name changes in the person list.
My problem right now is that, as I am editing the forms, the original properties are updated, so to say, in realtime, and if I decide to cancel, I have not the original values to "go back".
I considered to Clone the selected person, but that seemed to be odd. I think a better approach would be to change only the text properties of the field, and update back only when clicking to submit changes, but I don't know how to do it, specially how to preserve databinding consistency.
When you bind, use UpdateSourceTrigger=Explicit. That will tell XAML not to update the source binding until you tell it to. Then, when your Save button is clicked, you can call UpdateSource on the binding to push the contents of your controls back to the Person object, something like:
var nameBinding = nameTextBox.GetBindingExpression( TextBox.TextProperty );
nameBinding.UpdateSource();
To cancel, use UpdateTarget instead, which will push the data in the Person object back to your control.
You can try it with this Undo/Redo approch
http://blog.notifychanged.com/2009/01/30/using-the-viewmodel-pattern-to-provide-undo-redo-in-wpf/
Or
You could have a a Current and Previous property created in View model to which is going to hold the edited values and the other will have original value. The required action of rollback can be performed be reassigning the values
There are also open-source projects available to help with this, including http://undo.codeplex.com/