Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How not to lose binding source updates?

Suppose I have a modal dialog with a textbox and OK/Cancel buttons. And it is built on MVVM - i.e. it has a ViewModel object with a string property that the textbox is bound to.

Say, I enter some text in the textbox and then grab my mouse and click "OK". Everything works fine: at the moment of click, the textbox loses focus, which causes the binding engine to update the ViewModel's property. I get my data, everybody's happy.

Now suppose I don't use my mouse. Instead, I just hit Enter on the keyboard. This also causes the "OK" button to "click", since it is marked as IsDefault="True". But guess what? The textbox doesn not lose focus in this case, and therefore, the binding engine remains innocently ignorant, and I don't get my data. Dang!

Another variation of the same scenario: suppose I have a data entry form right in the main window, enter some data into it, and then hit Ctrl+S for "Save". Guess what? My latest entry doesn't get saved!

This may be somewhat remedied by using UpdateSourceTrigger=PropertyChanged, but that is not always possible.

One obvious case would be the use of StringFormat with binding - the text keeps jumping back into "formatted" state as I'm trying to enter it.

And another case, which I have encountered myself, is when I have some time-consuming processing in the viewmodel's property setter, and I only want to perform it when the user is "done" entering text.

This seems like an eternal problem: I remember trying to solve it systematically from ages ago, ever since I've started working with interactive interfaces, but I've never quite succeeded. In the past, I always ended up using some sort of hacks - like, say, adding an "EnsureDataSaved" method to every "presenter" (as in "MVP") and calling it at "critical" points, or something like that...

But with all the cool technologies, as well as empty hype, of WPF, I expected they'd come up with some good solution.

like image 349
Fyodor Soikin Avatar asked Jan 13 '11 07:01

Fyodor Soikin


1 Answers

At critical points, you can force the binding to push through to your view model:

var textBox = Keyboard.FocusedElement as TextBox;
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();

Edit:

OK, since you don't want hacks we have to face the ugly truth:

  • In order to implement a clean view, the properties exposed by your view model should be friendly to frequent binding updates.

An analogy we can use is a text editor. If the application was a giant text box bound to a file on disk, every keystroke would result in writing the whole file. Even the concept of saving is not needed. That's perversely correct but terribly inefficient. We all immediately see that the view model needs to expose a buffer for the view to bind to and this re-introduces the concept of save and forces state handling in our view model.

Yet, we see this is still not efficient enough. For even medium-sized files the overhead of updating the whole-file buffer on every keystroke becomes unbearable. Next we expose commands in our view model to efficiently manipulate the buffer, never actually exchanging the whole buffer with the view.

So we conclude that in order to achieve efficiency and responsiveness with pure MVVM, we need to expose an efficient view model. That means that all text boxes can be bound through to properties with no ill effects. But, it also means that you have to push state down into the view model to handle that. And that's OK because a view model is not the model; it's job is it to handle the needs of the view.

It's true that we can rapidly prototype user interfaces by taking advantage of shortcuts like binding on focus changes. But binding on focus changes can have negative consequences in real applications and if so then we should simply not use it.

What is the alternative? Expose a property friendly to frequent updates. Call it the same thing as the old inefficient property was called. Implement your fast property using the slow property with logic that depends on the state of the view model. The view model gets the save command. It knows whether the fast property has been pushed through to the slow property. It can decide if when and where the slow property will be synched to the model.

But you say, haven't we just moved the hack from the view to the view model? No, we have lost some elegance and simplicity, but go back to the text editor analogy. We have to solve the problem, and it is the view model's job to solve it.

If we want to use pure MVVM and we want efficiency and responsiveness, then lame heuristics like let's avoid updating the binding source until the element loses focus won't help. They introduce as many problems as they solve. In that case, we should let the view model do its job, even if means adding complexity.

Assuming we accept it, how can we manage the complexity? We can implement a generic wrapper utility class to buffer the slow property and allow the view model to hook its get and set methods. Our utility class can auto-register for save command events to reduce the amount of boilerplate code in our view model.

If we do it right, then all the parts of the view model that were fast enough to be used with property changed binding will all still be the same, and the others that were worthy of asking the question "Is this property too slow?" will have a small amount of code to address the issue, and the view is none the wiser.

like image 109
Rick Sladkey Avatar answered Nov 15 '22 05:11

Rick Sladkey