I have a View where I bind some deep properties of a Model (using the naming convention of Caliburn.Micro):
View:
<UserControl x:Class="TOP.SomeView"
(...)
<TextBox x:Name="NewFooModel_Foo" .../>
Then I need to catch the firing of the INPC of that property in the ViewModel:
Model:
public class FooModel{
private string _foo;
(...)
public int Foo {
get { return _foo; }
set {
if (_foo != value) {
_foo = value;
NotifyOfPropertyChange(() => Foo);
}
}
}
}
From that point, the property of the model is binded correctly. So, I need that change to be notified to the CanCreateFoo and I don't know how:
ViewModel:
public class SomeViewModel{
(...)
public FooModel NewFooModel {
get { return _newFooModel; }
set {
if (_newFooModel != value) {
_newFooModel = value;
NotifyOfPropertyChange(() => Foo);
//HERE I NEED TO NOTIFY TO CANCREATEFOOMODEL THAT A PROPERTY OF THE MODEL IS CHANGED
}
}
}
public bool CanCreateFooModel{
get{
return NewFooModel.Foo != null;
}
}
}
Please, can someone help me? Thanks in advance.
You could use EventAggregator to publish a message when the Property changes (and also NotifyOfPropertyChange for your current VM).
Each Model which is interested can subscribe to this message and handle it.
Related
I have a simple ViewModel:
public class MeetingPageViewModel : ReactiveObject, IRoutableViewModel
{
public MeetingPageViewModel(IScreen hs, IMeetingRef mRef)
{
HostScreen = hs;
_backing = "hi there";
}
public IScreen HostScreen { get; private set; }
public string MeetingTitle
{
get { return _backing; }
set { this.RaiseAndSetIfChanged(ref _backing, value); }
}
string _backing;
public string UrlPathSegment
{
get { return "/meeting"; }
}
}
And I bind to the MeetingTitle a TextBlock:
public sealed partial class MeetingPage : Page, IViewFor<MeetingPageViewModel>
{
public MeetingPage()
{
this.InitializeComponent();
// Bind everything together we need.
this.OneWayBind(ViewModel, x => x.MeetingTitle, y => y.MeetingTitle.Text);
}
/// <summary>
/// Stash the view model
/// </summary>
public MeetingPageViewModel ViewModel
{
get { return (MeetingPageViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(MeetingPageViewModel), typeof(MeetingPage), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MeetingPageViewModel)value; }
}
}
After navigating back to the previous screen MeetingPageViewModel isn't garbage collected. I'm using 6.4.1 of RxUI and VS2013 (and the memory analysis analyze tool). If I dispose of the return value from OneWayBind then everything is cleaned up properly - but of course, I no longer have the bindings.
It turns out the problem is the DependencyProperty ViewModel. Its lifetime is forever (note the "static" in its declaration). As a result, the binding that is attached to it is never garbage collected, since it never goes away, and that binding then holds a reference to both the view and viewmodel, and so they never go away.
The only way to break this is explicitly clean up the bindings. RxUI provides the WhenActivated method to help with this. Surround the bindings in a lambda, and use the provided function to track the IDisposals. When the view goes away this will then be cleaned up.
this.WhenActivated(disposeOfMe => {
disposeOfMe (this.OneWayBind(ViewModel, x => x.MeetingTitle, y => y.MeetingTitle.Text));
});
I have a problem using the IDataErrorInfo in combination with IReactiveBinding.Bind(). I hope someone here can help me.
I have a ViewModel that is inherited from ReactiveObject and implements the IDataErrorInfo interface.
public class MainWindowViewModel : ReactiveUI.ReactiveObject, IDataErrorInfo
{
private string username = string.Empty;
public string Username
{
get { return this.username; }
set { this.RaiseAndSetIfChanged(ref this.username, value); }
}
public MainWindowViewModel()
{
this.Validator = new MainWindowViewModelValidator();
}
public AbstractValidator<MainWindowViewModel> Validator { get; set; }
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get
{
return Validator != null ? string.Join(Environment.NewLine, Validator.Validate(this).Errors.Select(x => x.ErrorMessage).ToArray())
: string.Empty;
}
}
string IDataErrorInfo.this[string propertyName]
{
get
{
if (Validator != null)
{
var results = Validator.Validate(this, propertyName);
if (results != null
&& results.Errors.Count() > 0)
{
var errors = string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage).ToArray());
return errors;
}
}
return string.Empty;
}
}
#endregion
}
The MainWindowViewValidator ensures that the Usernameproperty is not empty.
The ViewModel is connected to the View in the code behind of the XAML-File:
public partial class MainWindow : IViewFor<MainWindowViewModel>
{
public MainWindow()
{
InitializeComponent();
this.ViewModel = new MainWindowViewModel();
this.Bind(this.ViewModel, viewmodel => viewmodel.Username, view => view.Username.Text);
}
public MainWindowViewModel ViewModel
{
get { return (MainWindowViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MainWindowViewModel), typeof(MainWindow), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MainWindowViewModel)value; }
}
}
The problem is now that the model validation is not called, as I don't specify the databinding in the XAML file directly.
Does anybody has a neat solution for this problem?
The problem is now that the model validation is not called, as I don't specify the databinding in the XAML file directly.
ReactiveUI doesn't participate in IDataErrorInfo and friends for binding. It used to, but really if you think about it, Validation itself is a derived property of the form.
ReactiveUI is already really good at describing how properties are related to each other (via WhenAny/ToProperty), so you should just construct an ValidationError property that displays the error message.
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
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);
}
}
I want to do some simple textbox validation in WPF, but I just realized that IDataErrorInfo relies on raising the PropertyChanged event in order to trigger the validation, which means that the invalid value is applied to my bound object before validation occurs. Is there a way to change this so the validation happens first (and prevents binding on invalid data), or is there another solution that works this way?
Trimmed down code looks like this:
<TextBox>
<TextBox.Text>
<Binding Path="MyProperty" ValidatesOnDataErrors="True" />
</TextBox.Text>
</TextBox>
public class MyViewModel : IDataErrorInfo
{
public string MyProperty
{
get { return _myProperty; }
set
{
if (_myProperty != value)
{
_myProperty = value;
NotifyPropertyChanged(() => MyProperty);
SaveSettings();
}
}
}
public string Error
{
get { return string.Empty; }
}
public string this[string columnName]
{
get
{
if (columnName == "MyProperty")
return "ERROR";
return string.Empty;
}
}
}
The better interface and validation method to use (if using .net 4.5) is INotifyDataErrorInfo. It's main advantage is allowing you to control when and how the validation occurs. One good overview:
http://anthymecaillard.wordpress.com/2012/03/26/wpf-4-5-validation-asynchrone/
I don't think you need to call SaveSettings() method every time property changed. I think it should be called when user click on "Save" button, but not when property changed. However if you still would like to save changes on property changed, you should only do it if there are no validation errors available. For instance:
public class MyViewModel : IDataErrorInfo
{
public string MyProperty
{
get { return _myProperty; }
set
{
if (_myProperty != value)
{
_myProperty = value;
NotifyPropertyChanged(() => MyProperty);
if (string.IsNullOrEmpty(this["MyProperty"]))
{
SaveSettings();
}
}
}
}
public string Error
{
get { return string.Empty; }
}
public string this[string columnName]
{
get
{
if (columnName == "MyProperty")
return "ERROR";
return string.Empty;
}
}
}
I'm trying to design a CustomUserControl which consits of an TextEditor and a PopUp...
So the Popup control should be binded to a list...
I called it BindingList. This Property should accept any types like ObservableCollection, List, Ienumerable for example(Collections)...
<my:CustomControl BindingList="{Binding Path=Collection}"
public IEnumerable<object> BindingList
{
get { return (IEnumerable<object>)GetValue(BindingListProp); }
set { SetValue(BindingListProp, value); }
}
The BindinglistProp
public static readonly DependencyProperty BindingListProp = DependencyProperty.Register(??????
I have no clue how it should look like that it can accept a binding.
And how should i deal with the Collection which is passed? when it is of a type which i don`t know
like
class Person
{
private string _Name;
private string _forename;
public string Name
{
get { return _Name; }
set
{
_Name = value;
}
}
public string Forename
{
get { return _forename; }
set
{
_forename = value;
}
}
}
Thanks for any hints, tutorials or code snippets.
sincerely
Mark
public IObservable<object> BindingList
{
get { return (IObservable<object>)base.GetValue(BindingListProperty); }
set { base.SetValue(BindingListProperty, value); }
}
public static DependencyProperty BindingListProperty =
DependencyProperty.Register(
"BindingList",
typeof(IObservable<object>),
typeof(CustomControl),
new PropertyMetadata(null));
Look to the CollectionViewSource.GetDefaultView to work with any collection in common way.