What's the appropriate way to handle running an async operation when an item is selected from a two-way bound control such as a combobox (wpf data binding)?
When I have a two-way binding property (e.g. SelectedValue on ComboBox) I don't think I can use Stephen Cleary's NotifyTaskCompletion because when a user selects a value from the dropdown, the ComboBox itself would need to modify the bound Result property, which is the Task's result.
The only viable solution I've come up with is calling an async Task -method from the databound setter without awaiting the result. This should be fine as long as the async-method triggers a property changed event for whatever ui-related stuff is being done, and that any exceptions are picked up and propagated to the ui accordingly, right?
I assume this would be a common case in async WPF applications. How do you guys approach this?
My solution so far:
<ComboBox
ItemsSource="{Binding PossibleItems}"
DisplayMemberPath="Name"
SelectedValue="{Binding SelectedItem}"/>
...
public Item SelectedItem
{
get { return m_selectedItem; }
set
{
m_selectedItem = value;
OnPropertyChanged();
InitializeAsyncAndFirePropertyChanged(); // async Task method not awaited - gives compiler warning CS4014
}
}
public async Task InitializeAsyncAndFirePropertyChanged(ObservableCollection<RFEnvironment> possibleRfEnvironments)
{
//should check this method for exceptions and propagate them to the UI via databinding
OtherDataBoundProperty = await GetSomeStringFromWebAsync();
}
public string OtherDataBoundProperty
{
get { return m_otherDataBoundProperty; }
set
{
m_otherDataBoundProperty = value;
OnPropertyChanged();
}
}
Note: I have found similar questions asked, but none that addresses two-way bindings on controls such as a Combobox.
If you are using a functional reactive MVVM framework such as ReactiveUI, you would simply observe the SelectedItem property and kick off any operation you want when the property is set. e.g.:
this.WhenAnyValue(x => x.SelectedItem)
.Subscribe(async _ => await InitializeAsyncAndFirePropertyChanged());
A property itself should ne be kicking off background operations, but a view model may do this when a property is set.
Please refer to the docs for more information: https://reactiveui.net/docs/concepts/.
I have a similar issue with async call in property setter when using WCF databinding.
My solution is slightly better, because in your case when an exception occurs in InitializeAsyncAndFirePropertyChanged, no exception is thrown nor caught. The modified code is below. It uses task continuation to throw an exception and raising OnPropertyChanged. OnPropertyChanged call can stay in an original place, it depends on your needs.
public class MyViewModel: INotifyPropertyChanged
{
private readonly TaskScheduler _uiScheduler;
public MyViewModel()
{
_uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
public Item SelectedItem
{
get { return m_selectedItem; }
set
{
m_selectedItem = value;
InitializeAsyncAndFirePropertyChanged()
.ContinueWith(t =>
{
if (t.Exception != null)
{
throw t.Exception;
}
OnPropertyChanged();
}, _uiScheduler);
}
}
public async Task InitializeAsyncAndFirePropertyChanged(ObservableCollection<RFEnvironment> possibleRfEnvironments)
{
//should check this method for exceptions and propagate them to the UI via databinding
OtherDataBoundProperty = await GetSomeStringFromWebAsync();
}
public string OtherDataBoundProperty
{
get { return m_otherDataBoundProperty; }
set
{
m_otherDataBoundProperty = value;
OnPropertyChanged();
}
}
.... other code to support INotifyPropertyChanged
}
Related
I'm using Fluent Ribbon in a MVVM design pattern, with a View/ViewModel. My goal is to change the body according to which tab is selected. I could use either SelectedTabItem or SelectedTabIndex and lookup the corresponding view. However, neither fire when the tab selection changes. In the View I have...
<Fluent:Ribbon Grid.Row="0" SelectedTabItem="{Binding SelectedRibbonTab}" SelectedTabIndex="{Binding SelectedRibbonTabIndex}">
</Fluent:Ribbon>
<ContentControl Grid.Row="1" Content="{Binding RibbonTabContent}"/>
In the ViewModel I have...
// Used both Item and Index for troubleshoothing, but need only one or the other for implementation
private IDictionary<string, FrameworkElement> RibbonTabViews;
private List<FrameworkElement> RibbonTabViewsList;
public RibbonTabItem SelectedRibbonTab
{
get
{
return selectedRibbonTab;
}
set
{
Update(() => SelectedRibbonTab, ref selectedRibbonTab, value, false);
UpdateContentControl();
}
}
public int SelectedRibbonTabIndex
{
get
{
return selectedRibbonTabIndex;
}
set
{
Update(() => SelectedRibbonTabIndex, ref selectedRibbonTabIndex, value, false);
UpdateContentControl(value);
}
}
public FrameworkElement RibbonTabContent
{
get { return ribbonTabContent; }
set { Update(() => RibbonTabContent, ref ribbonTabContent, value, false); }
}
protected void UpdateContentControl()
{
RibbonTabContent = RibbonTabViews[SelectedRibbonTab.Header.ToString()];
}
protected void UpdateContentControl(int index)
{
RibbonTabContent = RibbonTabViewsList[index];
}
I know I don't need both bindings but for the sake of troubleshoot I have both Item and Index. I would think in the ViewModel SelectedRibbonTab and SelectedRibbonTabIndex would be called each time the tab changed. Unfortunately, that doesn't appear to be the case. I have breakpoints at the setters and getters for each and neither are hit when changing tabs. What am I missing? I've been using this approach for years with Microsoft Ribbon but for some reason these don't fire in Fluent Ribbon. Any help would be great, thank you.
You have to set the binding mode to TwoWay to get updated values in your ViewModel.
I tried to use InvokeOperation to get a boolean value from the DomainContext in my ViewModel.
As calls to the server are asynchronous, including invokes. I need to wait for the invoke to complete (i.e. ctx.epidemicContext().Completed += [an event handler]). And in invoke complete event I'm retriving the bool value and setting a property.
_context.IsChangeLogStarted(OnInvokeCompleted, null);
//OnInvoke complete set the property
private void OnInvokeCompleted(InvokeOperation<bool> invOp)
{
IsChangeLogExist = invOp.Value;
}
//Property
public bool IsChangeLogExist
{
get { return this._IsChangeLogExist; }
set { this._IsChangeLogExist = value; }
}
And In my View I'm trying to bind the "IsChangeLogExist" to IsEnabled property of Button.
//View
<Button IsEnabled="{Binding IsChangeLogExist}" Command="{Binding Source={StaticResource ProxyViewModel}, Path=StartChangeLogCommand}" CommandParameter="{Binding}" />
But the problem is when I run the application, Button's IsEnabled property is binding with default value (false) of IsChangeLogExist, With out setting its property value from the domaincontext due the Invoke's asynchronous operation.
before invoke callback calls the OnInvokeCompleted, The button's Isenabled property is already getting the value. value is always false (since we are not setting the value and the invoke call is still at server)
Can some one suggest me how to bind IsEnabled property of button after getting the bool value from server and setting it in OnInvokeCompleted method.
You need to implement INotifyPropertyChanged in your view model and then raise this PopertyChanged event in setter of the property "IsChangeLogExists". This will refresh UI and new value of IsChangeLogExist will be used after callback completed.
public bool IsChangeLogExist
{
get { return this._IsChangeLogExist; }
set
{
this._IsChangeLogExist = value;
if(PropertyChanged != null)
PropertyChanged(this, "IsChangeLogExists")
}
}
How can I know that some property was binding?
For example a property (Class implemeted from NotificationObject):
public string Title
{
set
{
_title=value;
this.RaisePropertyChanged(() => this.Title);
}
get
{
return _title;
}
}
Using:
<TextBlock Text={Binding Title}>
I need to know when a property is not used by anyone to release dispose resources.
There's no easy way to know if a control is bound to a specific property of your ViewModel, but you can know if someone is subscribed to the PropertyChanged event (just check if it's not null). The binding engines subscribes to this event, so if something is bound to at least one property of your ViewModel, the PropertyChanged event handler won't be null.
You can tell if someone has requested your property by setting a flag, although not sure if this will meet your needs:
private bool _isTitleBound = false;
public string Title
{
set
{
_title = value;
this.RaisePropertyChanged(() => Title);
}
get
{
_isTitleBound = true;
return _title;
}
}
You could also consider lazy instantiation, which would result in your disposable objects only being created when the property getter was called. If the property getters are never called, your disposable objects will never be created. Also, if this is a one-time binding consider using lazy instantiation with disposal of your object. For example:
public MyThing Thing
{
get
{
MyThing thing = CreateMyThing();
Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() => thing.Dispose());
return thing;
}
}
private MyThing CreateMyThing()
{
//create and return MyThing instance;
}
How do I add a custom convention to Caliburn.Micro for the IsEnabled property of controls - something like having NameEnabled bound to IsEnabled in parallel to Name bound to Text on a TextBox.
In a way, what I want to achieve is similar to the way that a CanSave property can be used to enable/disable a button bound to a Save method, but generic for all controls.
Caliburn.Micro right now (1.3.1) doesn't really support this "multiple" conventions for the same FrameworkElement, what you have described.
EDIT:
However you can hook into the ViewModelBinder.BindProperties method and there you can implement your own extra convetion.
I went one step further and implemented a prototype which works, but it's not robust, nor elegant and probably not the correct way to do this. But it can be a starting point:
static AppBootstrapper()
{
ConventionManager.AddElementConvention<FrameworkElement>(
UIElement.IsEnabledProperty,
"IsEnabled",
"IsEnabledChanged");
var baseBindProperties = ViewModelBinder.BindProperties;
ViewModelBinder.BindProperties =
(frameWorkElements, viewModels) =>
{
foreach (var frameworkElement in frameWorkElements)
{
var propertyName = frameworkElement.Name + "Enabled";
var property = viewModels
.GetPropertyCaseInsensitive(propertyName);
if (property != null)
{
var convention = ConventionManager
.GetElementConvention(typeof(FrameworkElement));
ConventionManager.SetBindingWithoutBindingOverwrite(
viewModels,
propertyName,
property,
frameworkElement,
convention,
convention.GetBindableProperty(frameworkElement));
}
}
return baseBindProperties(frameWorkElements, viewModels);
};
}
You can enable/disable a control by setting a boolean property in your ViewModel and you just bind to IsEnabled in XAML:
TextBox Name="SerialNumber" IsEnabled="{Binding IsReadOnly}"...
ViewModel:
private bool isReadOnly;
public bool IsReadOnly
{
get { return isReadOnly; }
set
{
this.isReadOnly = value;
NotifyOfPropertyChange( () => IsReadOnly);
}
}
Short Version
If I update the Model object that my ViewModel wraps, what's a good way to fire property-change notifications for all the model's properties that my ViewModel exposes?
Detailed Version
I'm developing a WPF client following the MVVM pattern, and am attempting to handle incoming updates, from a service, to data being displayed in my Views. When the client receives an update, the update appears in the form of a DTO which I use as a Model.
If this model is an update to an existing model being shown in the View, I want the associated ViewModel to update its databound properties so that the View reflects the changes.
Let me illustrate with an example. Consider my Model:
class FooModel
{
public int FooModelProperty { get; set; }
}
Wrapped in a ViewModel:
class FooViewModel
{
private FooModel _model;
public FooModel Model
{
get { return _model; }
set
{
_model = value;
OnPropertyChanged("Model");
}
}
public int FooViewModelProperty
{
get { return Model.FooModelProperty; }
set
{
Model.FooModelProperty = value;
OnPropertyChanged("FooViewModelProperty");
}
}
The Problem:
When an updated model arrives, I set the ViewModel's Model property, like so:
instanceOfFooVM.Model = newModel;
This causes OnPropertyChanged("Model") to fire, but not OnPropertyChanged("FooViewModelProperty"), unless I call the latter explicitly from Model's setter. So a View bound to FooViewModelProperty won't update to display that property's new value when I change the Model.
Explicitly calling OnPropertyChanged for every exposed Model property is obviously not a desirable solution, and neither is taking the newModel and iterating through its properties to update the ViewModel's properties one-by-one.
What's a better approach to this problem of updating a whole model and needing to fire change notifications for all its exposed properties?
According to the docs:
The PropertyChanged event can indicate all properties on the object have changed by using either null or String.Empty as the property name in the PropertyChangedEventArgs.
One option is to listen to your own events, and make a helper routine to raise the other notifications as required.
This can be as simple as adding, in your constructor:
public FooViewModel()
{
this.PropertyChanged += (o,e) =>
{
if (e.PropertyName == "Model")
{
OnPropertyChanged("FooViewModelProperty");
// Add other properties "dependent" on Model here...
}
};
}
Whenever your Model property is set, subscribe to its own PropertyChanged event. When your handler gets called, fire off your own PropertyChanged event. When the Model is set to something else, remove your handler from the old Model.
Example:
class FooViewModel
{
private FooModel _model;
public FooModel Model
{
get { return _model; }
set
{
if (_model != null)
{
_model.PropertyChanged -= ModelPropertyChanged;
}
if (value != null)
{
value.PropertyChanged += ModelPropertyChanged;
}
_model = value;
OnPropertyChanged("Model");
}
}
public int FooViewModelProperty
{
get { return Model.FooModelProperty; }
set
{
Model.FooModelProperty = value;
OnPropertyChanged("FooViewModelProperty");
}
}
private void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Here you will need to translate the property names from those
// present on your Model to those present on your ViewModel.
// For example:
OnPropertyChanged(e.PropertyName.Replace("FooModel", "FooViewModel"));
}
}
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(String.Empty))
For VB.net if anybody else needs it. If you have already implemented "INotifyPropertyChanged" then the last line is all you need.