In my current application, I have a WPF window with a large number of text fields for the user to fill in. To try and avoid loss of data, I've implemented a feature to notify users if they have unsaved changes.
The underlaying data object in the ViewModel implements INotifyPropertyChanged. So on each change I can set a boolean property on my BaseViewModel to indicate that something has changed. However, this notification only happens when the user moves off any given textbox and onto another part of the window.
The BaseViewModel also has an overridable SaveChanges method, in which the property is reset to indicate the data has been saved.
We're using MVVM, but in a nod to practicality, we do have one bit of code-behind. In the underlying view there's an assignment of a function to the Closing event. In the function, we check the boolean and give the user a messagebox to warn of unsaved changes.
This all works fine in the majority of situations. However, if someone alters text in a box and then goes straight for the close button on the WPF window, the property changed event gets fired after the window closes.
Is there any way I can catch this event and stop the window closing in this eventuality?
However, this notification only happens when the user moves off any given textbox and onto another part of the window.
You can control this using the UpdateSourceTrigger property.
<TextBox Text="{Binding Width, UpdateSourceTrigger=PropertyChanged}" Width="50" />
For a TextBox, the default value for this property is LostFocus, please refer to the Remarks section of the link.
Related
Silverlight MVVM. I have a RadCombobox, and for each selection I'm adding a new row in another datagrid. I add necessaries codes in my ViewModel class and this part is working. What I’d like to perform is:
Keep the comboBox open while the control has the focus in order to allow the user repeating selection (I bind IsDropDownOpen to a method and setting in SelectedItem property to true but still it closes after selection )
Unselect the Item selected to allow duplication selection. I added the event SelectionChanged and add code in MainPage.xaml.cs but looking for a solution within my ViewModel.
Lets say,
IsDropDownOpen = {Binding IsDropDownFromViewModel}
Also, assuming that the getter of IsDropDownFromViewModel is encompassing all your conditions for the drop down to be open, and will always return the correct drop down state.
Now all that you will need to do is fire the PropertyChanged event for this property wherever/whenver you think the drop down should have been open, but is closed, or vice versa.
Unfortunately I didn't get your exact scenario, but lets assume this is the case (You should be use a similar approach to fix whatever problem you have).
Example Scenario:
The drop down closes when you select an item, it is intended to stay open
In the above case, one the user selects an item, the setter for the selectedItem's corresponding binding property should be invoked, so that is where we write the notification code
public SelectedItemType SelectedItemInViewModel {
get{
return _selectedItemVM;
},
set{
_selectedItemVM=value;
NotifyPropertyChanged("IsDropDownFromViewModel");
}
}
What this does is, it will tell the radComboBox's IsDropDownOpen property to reevaluate it's binding expression on the RHS and get its new value
Hope you get the gist of the approach, if not leave a comment.
How does one ignore changes to a control when databinding occurs? I tried hooking various events like gotfocus,textchanged,and leavefocus, but if the control already has focus and the user "cancels" their changes, when I reload the record and data binding takes over, textchanged thinks the user still made the change since the focus is on that control. The call stack is empty. Are there any global data binding events like databinding starting and databinding ending? I see where I fire my OnProperyChanged but within that call, databinding does not occur. Looks like it's getting "queued" up and runs at some other point.
At one point, I was going to hook the property change events in our view model , but this means I won't detect and can't VISUALLY display the form is modified till the user leaves the control. I know, I know, I can change all my bindings so that binding occurs immediately on every character change but then this messes with some validation cases as the user hasn't finished typing in their value.
I'd really love some kind of event like TextChangedByUser that would fire whether the user used a key, clipboard, mouse clipboard, anything triggered by the user.
I just can't figure out how to distinguish between user changes and databinding changes.
I'd really love some kind of event like TextChangedByUser that would
fire whether the user used a key, clipboard, mouse clipboard, anything
triggered by the user.
I just can't figure out how to distinguish between user changes and
databinding changes.
Don't use the Text.TextChanged event to detect user input,
use the Binding.SourceUpdated event instead.
Or more general:
Don't use the DPs of your visual elements to detect user updates, use the Binding.SourceUpdated event instead.
This is a RoutedEvent.
At your binding, you have to set NotifyOnSourceUpdated = true. With help of UpdateSourceTrigger you are even able to finetune when you want to be informed.
Your xaml could be sth like this:
<Grid x:Name="LayoutRoot" Binding.SourceUpdated="LayoutRoot_SourceUpdated">
...
<TextBox>
<TextBox.Text>
<Binding NotifyOnSourceUpdated="True" Path="path" UpdateSourceTrigger="PropertyChanged" >
</Binding>
</TextBox.Text>
</Grid>
Your event could be like this:
private void LayoutRoot_SourceUpdated(object sender, DataTransferEventArgs e)
{
// called every time s.th. changed by user
}
(edited due to comment)
Why is this a valid way to detect if an input is triggered in any way by the user?
In the given example, the TextBox's DataContext 'path' property is the source, while the 'TextBox.Text' property is the target.
[Data Binding Overview]http://msdn.microsoft.com/en-us/library/ms752347.aspx
The TextBox.Text property is changed for the first time when the binding initializes and the source-value is written to the 'TextBox.Text' property. Because you do not know when the binding takes place exactly you cannot use the TextBox.Text property or any of its events (e.g. TextChanged) to detect a user input. Hence:
Don't use the Text.TextChanged event to detect user input!!! more general: Don't use the DPs of your visual elements to detect user updates!!!
If the user changes the content of the visual text field by which means whatsoever, the 'TextBox.Text' property changes (your target).After that, the binding updates the source at a time defined by UpdateSourceTrigger.That's when the SourceUpdated event is fired.
I admit not to know the effect of changes to the binding source from outside the binding.
But I have a complete Editor-like Desktop-Application detecting changes by the user that way and it is working very nicely.
You should update your Binding Code to set the following
{Binding Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
EDIT: Sorry, I have overseen the fact that you already know this...In that case, I can't help :(
You can use the UIElement.TextInput event to detect user input.
Note that the event is probably already handled by the input control itself so you might have to use the UIElement.PreviewTextInput event.
I have created a dependency property of type Binary on a RichTextBox which allows me to bind to a FlowDocument which is in binary form (byte[]) within the ViewModel. This works well, the property converts to and back correctly.
Whenever the RichTextBox looses focus then the value of the dependency property is updated with the new binary representation of the FlowDocument.
My problem is that if I have been using the RichTextBox and I close the window, the RichTextBox does not lose focus and hence the dependency property is not updated with the new binary representation of the FlowDocument and therefore new changes are not commited to the database. In my ViewModel I have a method CleanUp which gets called when a ViewModel is getting ready to be disposed, where I can save the updated document.
How can I get the dependency property to update itself as the RichTextBox doesn't lose focus if the user clicks to close the window?
I brainstormed the following:
Tell the dependency property to update itself via a message broadcast. I am not clear on how to register a message listener within the dependency property.
Query the RichTextBox directly, get the Document, convert it to a binary object manually.
Get the view to move focus to a dummy control, so that the dependency property now updates itself.
What do you guys think?
Update: the on changed event for the dependency property adds a event handler which is waiting for the RichTextBox to loose focus. It is this handler that updates the dependency with its new value.
Use an UpdateSourceTrigger of "PropertyChanged"
Something like:
{Binding Path=MyProperty,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}
I had a similar problem once, the solution I used was to move the focus to a different control and I never had any problems with this.
In my case there were several editable controls in the window so I didn't have to use a dummy control.
What's stopping you from handling the closing/closed event of the Window and moving focus or updating the binding?
I've got a WPF app who's menu is using the commanding capabilities. Everything is wired up just fine and when I click the buttons in the menu, the commands run. However I'm having trouble getting the button's IsEnabled status to respect the CanExecute part of my commands.
One challenge is the commands need to see what you're doing in the UI. For instance, one command should only be available when certain items in a ListBox are selected so I need to get a reference to the ListBox... but since the command is exposed in my view model (MVVM pattern), it doesn't have access to the UI (BTW, the menu is in one user control [parent=mainwindow] while the ListBox is in another user control [parent=mainwindow]).
In addition, even when I hard code the command's CanExecute method to return false, the Enabled property of the button doesn't change... the command won't fire, but it won't change... frustrating. I assume I need to modify/raise the CanExecuteChanged event, but I'm not sure when I should be doing that (can't find a good sample).
Feedback?
Try the Messenger class from MVVMLight. It helps in communicating between ViewModels.
And give this a try:
Have a SelectedItem property in your ListBox's ViewModel. Broadcast messages while the property changes. Register for this message in the menu's ViewModel. Use the SelectedItem property in your CanExecute method for your logic.
Normally, you would bind the Command Property of the MenuItem/Button whatever - that means you still have the CommandParameter to work with - this can then be bound to some other control. However, when the two views do not know each-other, you need som kind of mediator between them (ie. a viewmodel that both views can access - have the listbox SelectedItem/SelectedItems bound to a property two-ways - and let the CommandParameter bind to the same property one-way).
As for when to fire the CanExecuteChanged event - you should fire that whenever there is a change in the logic contained in the CanExecute-method. If it is always false, you should never fire the event, as it will read the initalvalue when the Command-parameter is set. '
If your button is behaving oddly - check to see if the IsEnabled property is influenced by Styles or set directly.
Hope this helps.
I have been working with WPF and the MVVM pattern for a while now. I'm having difficulty getting validation working in a "normal" way:
1) I'm implement the IDataErrorInfo interface in my ViewModel. The XAML looks something like:
<TextBox Grid.Column="1"
Grid.Row="1"
Text="{Binding Path=ProjectKey, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
The problem here is that whether LostFocus and PropertyChanged triggers are used, the textbox is validated before the user ever tabs to that control. This means if I'm validating empty fields, the user will see a whole lot of red when they first open the form. Ideally the input would only be validated after the first “lost focus” or “property change”, or once the "Submit" button is clicked.
2) The other issue is validation at the end when the user clicks "Submit". There are certain things you want to validate right before submitting to the database, such as duplicates. I understand I can use UpdateSourceTrigger=Explicit and call the UpdateSource method on all controls. I'm wondering if there is an appropriate way to do this within the MVVM pattern. It seems like such code shouldn't be in the ViewModel since it's very View specific...
Any ideas would be great. I've searched a lot online but can't seem to find the right solution...
For number one your properties on the ViewModel should be initialized with a value before hand in the constructor
public double Property1 {get; set;}
public ViewModel()
{
Property1 = 0;
}
For number two the submit button should not be enabled until all fields pass validation. If you have a field that is unique in the database then validate it on property change and display and error if it doesn't pass. You can have a boolean property that is bound to the button's IsEnabled property and set it to true once all fields pass validation.