I'm struggling to be able to bind a StatusBarItem content element in my view to a subclasses property in my ViewModel, I'm using the MVVM-Light framework/
ViewModel:
public class PageMainViewModel : ViewModelBase
{
LoggedOnUserInfo UserInfo;
public LoggedOnUser UserInfo
{
set
{
_UserInfo = value;
RaisePropertyChanged("UserInfo");
}
}
}
For full clarity the LoggedOnUser Class is defined as follows
public class LoggedOnUser : INotifyPropertyChanged
{
private string _Initials;
public event PropertyChangedEventHandler PropertyChanged;
public LoggedOnUser()
{
}
[DataMember]
public string Initials
{
get { return _Initials; }
set
{
_Initials = value;
OnPropertyChanged("Initials");
}
}
protected void OnPropertyChanged(string propValue)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propValue));
}
}
}
My Views DataContext is being set and is working as I am able to see other bindings working, but my attempts to bind to UserInfo.Initials property in my XAML are producing an empty result.
XAML:
<StatusBarItem Grid.Column="0" Content="{Binding UserInfo.Initials}" Margin="5,0,0,0" VerticalAlignment="Center" Focusable="False" />
The UserInfo property is set after the viewModel is created due to several factors but I thought with my propertychanged events this would be ok.
Any Advice on this would be greatly appreciated.
You do not appear to have a getter on UserInfo, the binding will be out of luck.
(Also check for binding errors when having trouble with bindings, they probably will tell you about all their problems)
add the getter to your userinfo
public class PageMainViewModel : ViewModelBase
{
LoggedOnUserInfo UserInfo;
public LoggedOnUser UserInfo
{
get {return _UserInfo;}
set
{
_UserInfo = value;
RaisePropertyChanged("UserInfo");
}
}
}
and like H.B. said - check your output window for binding errors
I am not quite sure why you have the initials_ attribute bound inside the UserInfo_ attribute.
You can't access the initials_ attribute without a getter of the UserInfo_ attribute.
I would suggest to bind to the latter separately.
Related
I've got a simple LoginView and LoginViewModel.
First, here is the related code of the View:
<StackLayout>
<Entry
Text="{Binding Email, Mode=TwoWay}"
/>
...
And here is the ViewModel:
public class LoginViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _email;
public string Email
{
get => _email;
set
{
_email = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Email)));
}
}
...
}
If I say inside the Constructor something like
Email = "abc";
the Entry for sure displays the value "abc". But if I change the Text inside the Entry, the set {} is not firing so the PropertyChanged() also does not.
Do I miss something here or do I have to use BindableProperties?
Thanks in advance!
Edit 1
For anyone needing the definition of the BindingContext for LoginView, here is the Code-Behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public sealed partial class LoginView
{
public LoginView()
{
InitializeComponent();
// Set the ViewModel
this.BindingContext = new LoginViewModel();
}
}
Edit 2
So I created a Testproject which simply binds the TextProperty of a Entry to a Property. This works! Then I edited my existing Code (removed Baseclasses, simplified everything, etc) to simply do the same basic thing... And it doesn't work. What could this be?
After I updated my Xamarin.Forms version, the bug now is gone!
Suppose we have a Model (class Model) with the following property.
public string InputFileName
{
get { return m_InputFileName; }
set
{
m_InputFileName = value;
RaiseNotifyPropertyChanged("InputFileName");
}
}
The above model implements the INotifyPropertyChanged interface, so we have also the following method and the following event. The RaiseNotifyPropertyChanged method below is used to update the ViewModel.
#region INotifyPropertyChanged Implementation
private void RaiseNotifyPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
The following are the main sections of the class that implements the ViewModel.
public class ViewModel : INotifyPropertyChanged
{
#region Members
private Model m_Model;
private string m_InputFileStr;
private readonly ICommand m_SubmitCommand;
#endregion
#region Constructors
public ViewModel()
{
m_Model = new Model();
m_Model.PropertyChanged += new PropertyChangedEventHandler(this.Model_PropertyChanged);
m_InputFileStr = string.Empty;
// ...
// initialize m_SubmitCommand
}
#endregion
// ...
#region Properties
public string InputFileStr
{
get { return m_InputFileStr; }
set
{
if (value == m_InputFileStr) return;
m_InputFileStr = value;
OnPropertyChanged("InputFileStr");
m_SubmitCommand.RaiseCanExecuteChanged();
}
}
#endregion
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
// This method is called when the model changes, so the Model notified the ViewModel.
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "InputFileName")
{
InputFileStr = m_Model.InputFileName;
}
else if (args.PropertyName == "OutputFileName")
{
OutputFileStr = m_Model.OutputFileName;
}
else if (args.PropertyName == "ReportText")
{
ReportTextStr = m_Model.ReportText;
}
}
}
The following are the main sections of the class that implements the View:
MainWindow.xaml
<TextBox Name="inputfileTextBox"
Text="{Binding Path=InputFileStr, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Button Name="submitButton"
Content="Submit"
Command="{Binding SubmitCommand}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
The above implementation works correctly:
the View and the ViewModel correctly update each other;
the Model correctly updates the ViewModel.
With the aim of enabling the ViewModel to update the Model, I thought I would add the following call inside the set property InputFileStr of ViewModel:
m_Model.InputFileName = value;
However, this solution of updating the Model causes an obvious unintended effect:
The user modified the View.
The ViewModel is automatically modified.
The ViewModel updates the Model (m_Model.InputFileName = value;).
The Model is updated...
... so it notifies the ViewModel about the changes
Is the above behavior a correct behavior? I expect that if the ViewModel updates the Model, then the Model does not have to re-notify the ViewModel about the same change... As an alternative solution I thought I'd add an Update method to the Model: this method should update the Model without using the Model Properties.
public void Update(string inputFileName) // this method does not notifies the ViewModel
{
m_InputFileName = inputFileName;
}
Is this alternative solution a correct solution or are there better solutions?
Depending on what your model is, you will usually just invoke a "Save" method or similar. Most models (say, a database) don't need/want to have every change given to them in real-time.
So in general, the flow would be:
User invokes "save" operation
View model receives this as a command
View model invokes "save" operation on the model with the new data
If your DTO objects are shared between the model and view model, you don't even need to worry about synchronization. Otherwise, this is a good time to sync them.
On a similar note, using PropertyChanged in a model class is usually a bad idea. For starters, its no fun at all to listen to. Instead, if the model receives new data, raise a more semantically clear event to the VM with the new data.
tldr; Basically, don't worry so much about keeping your model and view model in sync. Very often, the model won't be keeping a copy of the current state at all! Even when it is, just update it when the view model is ready to "commit" changes, and notify the View Model of external changes to the model via normal events.
I've added an observable data and bound it to my data grid as follows.
private ObservableCollection<Order> _allOrders;
public ObservableCollection<Order> AllOrders
{
get { return _allOrders;}
set { _allOrders = value; OnPropertyChanged(); }
}
public Presenter() { _allOrders = new ObservableCollection<Order>(...); }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I set breakpoint on the event that is supposed to filter the data, I set the property AllOrder to null. I can verify using the watch that it's set to that. However, the view isn't updated, so I'm guessing that I forgot something. The view model class Presenter implements INotifyPropertyChanged interface, of course.
What's missing?
Edit
The XAML code for the grid looks as follows.
<DataGrid x:Name="dataGrid"
ItemsSource="{Binding AllOrders}"
AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn" ...>
Assuming that you set DataContext accordingly and AllOrders binding works initially if you want to filter items in the UI, without change collection, it's much easier when you use ListCollectionView with a Filter. WPF does not bind directly to collection but to a view - MSDN.
private readonly ObservableCollection<Order> _allOrders;
private readonly ListCollectionView _filteredOrders;
public ICollectionView FilteredOrders
{
get { return _filteredOrders; }
}
public Presenter()
{
_allOrders = new ObservableCollection<Order>(...);
_filteredOrders = new ListCollectionView(_allOrders);
_filteredOrders.Filter = o => ((Order)o).Active;
}
and in XAML
<DataGrid ... ItemsSource="{Binding FilteredOrders}">
when you want to manually refresh UI just call Refresh
_filteredOrders.Refresh();
Apart from that nothing changes in the view model. You still add/remove items to _allItems and changes should be picked up automatically by UI
Do you set the property AllOrders only in the constructor? If so, then do not set the field _allOrders but the property AllOrders. If you set the field then notification is never raised.
Relativly new to the MVVM stuff, i have Trouble with the following:
I have an object "User", this object exposes some properties, like Username, Email, etc..
In the mvvm model i have a property:
private IUser currentUser;
public IUser CurrentUser
{
get
{
return this.currentUser;
}
set
{
this.currentUser = value;
this.OnPropertyChanged("CurrentUser");
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In XAML a TextBox is bound as follows:
Text="{Binding CurrentUser.Email, Mode=TwoWay}"
When changing the Email Address the OnPropertyChanged is not fired and thus other code (as the ICommands) are not "working".
Is there a way that when the user changes the Text in the TextBox the OnPropertyChanged fires??
TIA,
Paul
You are firing PropertyChanged when CurrentUser changes, but current user is not changing you are just changing the Email property on it. A quick fix could be to have the Email property propagate the OnChange event for CurrentUser.
public string Email
{
//get
set
{
this.email = value;
this.OnPropertyChanged("CurrentUser");
}
}
Actually I think this.OnPropertyChanged("Email") would work too, but the setter of CurrentUser is definitely not getting called when you change a property of it.
Where is your INotifyPropertyChanged Interface?
I think it is necessary.
Property Change Notification does not watch the properties of your IUser class.
It is only watching for changes to the ViewModel Property CurrentUser (the reference).
You need
a) make the IUser implement INotifyPropertyChanged
or
b) pull out EmailAddress and add it to the ViewModel like so:
public string Email
{
get
{
return this.CurrentUser.Email;
}
set
{
this.CurrentUser.Email = value;
this.OnPropertyChanged("Email");
}
}
I'm trying to build up a page that will show a progress bar until the data is successfully downloaded from a server.
For this i use a Generic data downloader that will simply populate the Model's properties and set the IsLoading property to true and/or false
The View models look like:
public class GenericPageModel: GenericModel
{
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
_isLoading = value;
OnPropertyChanged("IsLoading");
}
}
}
public class GenericModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
The GenericPageModel is used in a XAML page as a Model and the IsLoading property is used as bellow:
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="boolToVis"/>
</Grid.Resources>
<ProgressBar Height="25" Margin="5"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
Visibility="{Binding IsLoading, Converter={StaticResource boolToVis}}"
IsIndeterminate="True"
/>
</Grid>
The generic data downloader:
...
// Model that it's calling this
public object Model
{ get; set; }
private string _loadingProperty;
...
void _bw_DoWork(object sender, DoWorkEventArgs e)
{
// Set the is loading property
if (null != _loadingProperty)
{
//((Model as GenericModel).Owner as GenericPageModel).IsLoading = true; // Works!!
Model.GetType().GetProperty(_loadingProperty).SetValue(Model, true, null); // Doesn't work
}
}
If i explicitly cast Model to the GenericPageModel and set the IsLoading to true, everything works just fine (see the commented line)
If i use reflection to set the Value of the property, the IsLoading setter is hit correctly, the OnPropertyChanged method is called ok, but the UI doesn't update
Is there something extra that needs to be done when setting a property trough reflection? I 'm guessing the Events aren't raised properly but i can't figure out what to do.
Solved there was an extra model inserted before the downloader call, the line should've said:
object Owner = Model.GetType().GetProperty("Owner").GetValue(Model, null);
Model.GetType().GetProperty(_loadingProperty).SetValue(Owner, true, null);
The following lines
((Model as GenericModel).Owner as GenericPageModel).IsLoading = true; // Works!!
Model.GetType().GetProperty(_loadingProperty).SetValue(Model, true, null); // Doesn't work
are not equivalent. The first affects the IsLoading property on the Model.Owner object, the second on the Model object itself.
Note: your ViewModel base class really shouldn't be called GenericModel as it's the ViewModel, not the Model.