Where in code and how should I be telling a text box to display a temporary loading message when using MVVM?
I have a WPF application which is intended to get information asynchronously from a database, and when it's finally retrieved it, display it in various text boxes.
I have the asynchonrous lookup working fine, but I'm new to WPF and MVVM. Before, I had no proper separation of my UI and my model, and so it was easy to tell the textboxes to display "Querying..." until the actual data was available.
Now though, I've tried to separate the model from the UI, and I'm no longer sure where to put this.
Is it acceptable (from a MVVM standpoint) to have a UI button update various UI text boxes to say "Querying..." inside its own click event (along with calling a ViewModel method that will eventually update properties those textboxes are bound to, which then should replace them with actual data)?
If not, where -should- this logic go. (I'm also aware that Commands are better than onClick events, but please, one thing at a time :) )
Here's what I'm considering at the moment:
class MainWindow
{
private ViewModel _viewModel;
doStuffButton_Click(object sender, RoutedEventArgs e)
{
textBox1.Text = "Querying..."
textBox2.Text = "Querying..."
// ... and so on in a foreach looop probably.
_viewModel.asyncGoAndLookupStuff(); // starts a Task that
// updates properties that the textBoxes are bound to
// and fires the appropriate PropertyChanged events.
}
}
Is this good practice? If not, how am I supposed to get this sort of functionality into the ViewModel? I've had a go, and I'm running into serious problems setting the ViewModel's properties to various strings while still being able to set them to the appropriate (non-string) values from the Model when they update. Could this be solved with a ValueConverter (and null values)? I've really no idea how to go about doing that but I haven't really looked at converters much yet.
I've seen a lot of stuff about how the UI shouldn't know anything about what's actually going on in the ViewMo开发者_高级运维del, and how it should basically just dumbly sit there and display whatever the ViewModel currently has exposed, but the reason I'm asking this question is I'm not sure how much caution is -too- much.
EDIT:
I didn't think it would be relevant, but from the answers below it might be. Down in the model, I have something much like this (a little bigger/more complicated)
class DataItem
{
// assume a ctor to set it up here.
public int SmallNumber { get; private set; }
public int BigNumber { get; private set;}
}
class Model
{
public DataItem Foo;
public DataItem Bar;
}
and the textboxes are (currently) all bound to things like ViewModel.Model.Foo.SmallNumber, or ViewModel.Model.Bar.BigNumber.
Do I really need to make separate properties in the ViewModel for each sub-element of the model (for example, ViewModel.FooSmallNumber as a string property), so that they can all be string types? I would like to keep this relatively straightforward binding direct to the model if possible, but still have the textboxes display helpful strings at times. (I cannot currently rely on the Model's properties being null when they're querying, they could contain out of date info... I could change it to clear them, I guess.)
Your view model should not be aware of your controls. Therefore, it should not update any of your control properties. Here is how you change the text while doing your querying:
Create a string
dependency property in your view model and bind it to the text of your first text box. Change this string before your async loading and after the async method is completed.
Alternatively, use a BusyControl and bind its Busy
property to a boolean in your view model which you can update before and after your async call.
Finally, following MVVM pattern is not about being cautious, it's about absolute separation of logic and presentation. Ask yourself this: "If I change my view (by renaming a control, removing certain parts, etc), will it affect my view model? If the answer is yes, then you have tightly coupled parts in your system.
If you only query once at the start, you can set the defaultvalue of the Text property like so:
<TextBox Text="{Binding MyProperty, TargetNullValue="Querying...", FallbackValue="Querying..."}" />
If you need the text to go back to 'Querying...' at the next query, set a textbox style on all affected textboxes with a trigger that sets the text as soon as some boolean (IsQuerying) in your VM is set to true. This will overwrite your binding unless it is set to TwoWay (and even then I'm not sure...) so perhaps setting another property is the way to go (like IsEnabled or Opacity).
<Style x:Key="QueryingStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName = ThisVrEditorView, Path=DataContext.IsQuerying}">
<Setter Property="Text" Value="Querying..."/>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
You can and should do all this in your VM.
You add a string property Item1 (with Change notification) to the VM and you just set it to "Querying..." when you start. When the Async method has completed, use Dispatcher.Invoke (or ThreadingContext) to copy the new data to the properties.
精彩评论