问题
I have a property(in viewmodel) bound to a combobox. When the viewmodel property changes, it uses the Messenger to tell another viewmodel about this.
This other viewmodel then decides if this is ok, if not i want to cancel and send the old value back up to the view.
I guess i can do this by setting the value to the new one first, then set it back. But is there a more elegant soulution?
Failing code
public DeckType SelectedDeckType
{
get { return _selectedDeckType; }
set
{
DeckTypeMessage deckTypeMessage = new DeckTypeMessage(value);
Messenger.Default.Send(deckTypeMessage);
if (deckTypeMessage.IsCancel)
{
//Some background magic finds out the value of this property is still the same?
//So the combobox does not revert!
//I can hack this but is there some way to force this?
RaisePropertyChanged();
return;
}
_selectedDeckType = value;
RaisePropertyChanged();
}
}
I managed to fix it with this workaround, but i dont like it :( At first glance it seams to be incorrect, but the call stack makes it this way
Using oneway binding on SelectedItem and Interaction Trigger with command
Hacky workaround
public DeckType SelectedDeckType
{
get { return _selectedDeckType; }
set
{
_selectedDeckType = value;
RaisePropertyChanged();
}
}
public ICommand SelectedDeckTypeChangedCommand { get; private set; }
private void ExecuteSelectedItemChangedCommand(DeckType aDeckType)
{
try
{
if (_previousSelectedDeckType == aDeckType)
{
return;
}
_previousSelectedDeckType = aDeckType;
DeckTypeMessage deckTypeMessage = new DeckTypeMessage(this, aDeckType);
Messenger.Default.Send(deckTypeMessage);
if (deckTypeMessage.IsCancel)
{
SelectedDeckType = _selectedDeckType;
_previousSelectedDeckType = _selectedDeckType;
return;
}
SelectedDeckType = aDeckType;
}
catch (Exception ex)
{
NotifyMediator.NotifiyException(new NotifyMediator.NotifyInformation(NotifyMediator.NotificationLevel.Error, ex));
}
}
Kind Regards
回答1:
You need to use Dispatcher.BeginInvoke
to perform the reversal of the user action.
Basically, when the user selects the item on the combo box, any attempt to reject that value will be ignored by WPF. However, if you wait until all the code relating to data binding finishes, then you can basically start a new binding activity. This is what Dispatcher.BeginInvoke
does. It allows your reset of the selected item to be postponed until the binding engine has finished its work.
Example:
public class MainViewModel : ViewModelBase
{
private string _selectedItem;
public List<string> Items { get; private set; }
public string SelectedItem
{
get { return _selectedItem; }
set
{
if (value == _selectedItem) return;
var previousItem = _selectedItem;
_selectedItem = value;
var isInvalid = value == "Bus"; // replace w/ your messenger code
if (isInvalid)
{
Application.Current.Dispatcher.BeginInvoke(
new Action(() => ResetSelectedItem(previousItem)),
DispatcherPriority.ContextIdle,
null);
return;
}
RaisePropertyChanged();
}
}
public MainViewModel()
{
Items = new[] { "Car", "Bus", "Train", "Airplane" }.ToList();
_selectedItem = "Airplane";
}
private void ResetSelectedItem(string previousItem)
{
_selectedItem = previousItem;
RaisePropertyChanged(() => SelectedItem);
}
}
来源:https://stackoverflow.com/questions/27449797/wpf-mvvm-light-cancel-property-change-in-viewmodel-raisepropertychanged-even