This question already has answers here:
List<> collection does not update the View in MVVM
(4 answers)
Closed 1 year ago.
public class MainWindowVM : ViewModel
{
#region public members
public User LocalUser
{
get => Settings.LocalUser;
set
{
Settings.LocalUser = value;
OnPropertyChanged();
}
}
public WebUser SelectedUser
{
get => selectedUser;
set
{
selectedUser = value;
OnPropertyChanged();
}
}
public string InputMessage
{
get => inputMessage;
set
{
inputMessage = value;
OnPropertyChanged();
}
}
public List<Message> MessageList
{
get
{
if (selectedUser == null)
return null;
return SelectedUser.MessageThread;
}
set
{
SelectedUser.MessageThread = value;
OnPropertyChanged();
}
}
public RelayCommand SendMessageCommand
{
get
{
return sendMessageCommand ??
(sendMessageCommand = new RelayCommand(obj =>
{
SendMessage();
}));
}
}
#endregion
#region public methods
public MainWindowVM(Window window)
{
Window MainWindow = window;
MawLib.WindowResizer windowResizer = new MawLib.WindowResizer(window);
Settings.InitializeInstance();
SelUser = SelectUser;
UserListRaw = new List<WebUser>
{
new WebUser(new UserId("SomeTestUser23")),
new WebUser(new UserId("NoName"))
};
WebClient.Connect();
}
public void SelectUser(WebUser user)
{
SelectedUser = user;
MessageList = SelectedUser.MessageThread;
}
#endregion
#region private members
private WebUser selectedUser;
private string inputMessage;
#endregion
#region private methods
private void SendMessage()
{
if (InputMessage != "" && InputMessage != null)
{
MessageList.Add(new Message(InputMessage, LocalUser, SelectedUser));
WebClient.SendRequest(new Message(InputMessage, Settings.LocalUser, selectedUser), Request.RequestType.Message);
InputMessage = "";
}
}
#endregion
}
Need to have a Message List.Add interacted via the Message List property, but for some reason it bypasses it. I also tried to access the accessor via assignment, but this also bypassed the property. I was looking for information about this, but I didn't find anything suitable, maybe I was looking badly?
As suggested by Dzianis Karpuk, need to use ObservableCollection instead of List, everything works as it should.
Related
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 5 years ago.
i'm a newbie in MVVM, I have a Model.cs which contains a few property of 'ID' and 'PositionName' and i got View.cs which contains DataGrid with SelectedItems={Binding Items} and ItemSource={Binding Position} and a button with a Command={Binding SHowEdits} after clicking that i encountered a NullReference error at 'Items.PositionName == null '.
here's my code.
ViewModel.cs
class PositionVM : INotifyPropertyChanged
{
private ObservableCollection<PositionModel> _position;
private PositionModel _items;
private ICommand _showedits;
public ObservableCollection<PositionModel> Position
{
get
{
return _position;
}
set
{
_position = value;
NotifyProperty("Position");
}
}
public PositionModel Items
{
get
{
return _items;
}
set
{
_items = value;
NotifyProperty("Items");
}
}
public ICommand ShowEdits
{
get
{
if (_showedits == null)
_showedits = new ShowEdit();
return _showedits;
}
set
{
_showedits = value;
}
}
public PositionVM()
{
Position = new ObservableCollection<PositionModel>();
Position.Add(new PositionModel()
{
ID = 1,
PositionName = "asd"
});
}
public void ShowEditDialog()
{
if (Items.PositionName == null)
{
MessageBox.Show("ERROR");
}
else
{
PositionView view = new PositionView();
Data.ID = view.txtid.Text;
var z = new PositionView();
z.ShowDialog();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyProperty(String info)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Why am i getting this error? and How can i avoid it? Thanksss
I think your problem with ICommand. Try to do it as property:
public ICommand ShowEditsCommand { get; set; };
And of course you need create it. I use my Command class for it, you can use it too on first time, it's pretty simple:
/// <summary>
/// Relay implementation of ICommand.
/// </summary>
public class RelayCommand : ICommand
{
private Action execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
public RelayCommand(Action execute)
: this(execute, DefaultCanExecute)
{
}
public RelayCommand(Action execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
this.CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
this.CanExecuteChangedInternal -= value;
}
}
public bool CanExecute(object parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute();
}
public void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChangedInternal;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
this.canExecute = _ => false;
this.execute = () => { return; };
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
}
After this you need to initialize it in your viewmodel constructor and bind it to method:
ShowEditsCommand = new RelayCommand(ShowEdits);
Where ShowEdits is your methods that you need run when command call:
public void ShowEdits()
{
// do something here
}
Since this line
Items.PositionName == null
throws a null reference exception, it's clear that Items is null.
Why Items is null?
The Items property is defined as below:
public PositionModel Items
{
get
{
return _items;
}
set
{
_items = value;
NotifyProperty("Items");
}
}
When you try to set the value of PositionModel a call to the getter (get { return _items; })is done, in order you get a reference to the object that _items point to. The value you get is null. That means that either the setter has not been called, in order _items to be initialized with a value or it has been initialized but later on another part of your code has set it to null.
Looking through your code, we see the constructor of your class:
public PositionVM()
{
Position = new ObservableCollection<PositionModel>();
Position.Add(new PositionModel()
{
ID = 1,
PositionName = "asd"
});
}
Apparently, the Items are not initialized there. So Items has the default value for a reference type, null...
I am new to WPF, threading and this concept of separation of UI.
So here I have a well designed project in which all calculations are done inside a method (e.g. Command class and Execute method); and the User Interface comprises several User Controls.
Since the calculation take a couple of minutes I think I should use a progress bar which is on MainControl user control class.
So I created this method inside MainControl to update progress bar:
public void SetProgressBar(int Value, int Min = 0, int Max = 100)
{
if (Value <= Max && Min <= Value)
{
StatusProgress.Minimum = Min;
StatusProgress.Maximum = Max;
StatusProgress.Dispatcher.Invoke(
new Action(() => this.StatusProgress.Value = Value),
System.Windows.Threading.DispatcherPriority.Background);
}
}
Then I created a reference to MainControl inside Command class:
private MainControl MainControlRef;
So in different stage of my calculations I can report the progress using:
MainControlRef.SetProgressBar(10);
This is what I could come up with. I think this just doesn't feel right. I would appreciate if you could give me some feed backs.
Beside that I noticed that the progress bar sometime updates and sometime doesn't.
Easy using the MVVM pattern, your VM would typically contain the Command Object(research relay command), and other data needed by the view such as an int to hold progress bar value.
Your view then binds to this VM so once your command is executing and updating the progress bar value, your view would be updating as well
Sample from an old project below, hope it helps
public class LoginViewModel : ViewModelBase
{
private static ILog Logger = LogManager.GetLogger(typeof(LoginViewModel));
#region Properties
private String _title;
private String _login;
private String _password;
private String _validationMessage;
private bool _displayLoginButton;
private bool _displayLoginProgressRing;
private bool _enableInput;
private bool _displayValidationMessage;
private ICommand _loginCommand;
public LoginViewModel()
{
DisplayLoginButton = EnableInput = true;
DisplayLoginProgressRing = DisplayValidationMessage = false;
}
public bool DisplayValidationMessage
{
get { return _displayValidationMessage; }
set
{
_displayValidationMessage = value;
RaisePropertyChanged(() => this.DisplayValidationMessage);
}
}
public bool DisplayLoginButton
{
get { return _displayLoginButton; }
set
{
_displayLoginButton = value;
RaisePropertyChanged(() => this.DisplayLoginButton);
}
}
public bool EnableInput
{
get { return _enableInput; }
set
{
_enableInput = value;
RaisePropertyChanged(() => this.EnableInput);
}
}
public bool DisplayLoginProgressRing
{
get { return _displayLoginProgressRing; }
set
{
_displayLoginProgressRing = value;
RaisePropertyChanged(() => this.DisplayLoginProgressRing);
}
}
public String Title
{
get
{
return _title;
}
set
{
_title = value;
RaisePropertyChanged(() => this.Title);
}
}
public String Login
{
get
{
return _login;
}
set
{
_login = value;
RaisePropertyChanged(() => this.Login);
}
}
public String Password
{
get
{
return _password;
}
set
{
_password = value;
RaisePropertyChanged(() => this.Password);
}
}
public String ValidationMessage
{
get
{
return _validationMessage;
}
set
{
_validationMessage = value;
RaisePropertyChanged(() => this.ValidationMessage);
}
}
#endregion
#region Commands
public ICommand LoginCommand
{
get
{
return _loginCommand ?? (_loginCommand =
new RelayCommand<object>((param) => ExecuteLoginCommand(param), (param) => CanExecuteLoginCommand(param)));
}
}
private bool CanExecuteLoginCommand(object parameter)
{
var paramArray = (object[])parameter;
if (paramArray == null) return false;
PasswordBox pb = paramArray[0] as PasswordBox;
if (pb == null) return false;
var pwdText = pb.Password;
return !(String.IsNullOrEmpty(pwdText) && Login != null);
}
private void ExecuteLoginCommand(object parameter)
{
Logger.InfoFormat("User [{0}] attempting to login", this.Login);
DisplayLoginButton = false;
DisplayLoginProgressRing = true;
EnableInput = false;
var paramArray = (object[])parameter;
var pb = paramArray[0] as PasswordBox;
var loginWindow = paramArray[1] as LoginView;
if (pb != null)
Password = pb.Password;
if (ValidateInput())
{
if (SynchronizationContext.Current == null)
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
var curSyncContext = SynchronizationContext.Current;
bool authenticated = false;
Task.Factory.StartNew(() =>
{
authenticated = CredentialService.Instance.Store(int.Parse(Login), Password);
Thread.Sleep(1000);
}).ContinueWith((task) =>
{
curSyncContext.Send((param) =>
{
if (authenticated)
{
Logger.InfoFormat("User [{0}] Authenticated Successfully, Logging In", this.Login);
var mainWindow = new MainWindow();
mainWindow.Show();
loginWindow.Close();
}
else
{
Logger.InfoFormat("User [{0}] Failed to Authenticate", this.Login);
DisplayValidationMessage = true;
ValidationMessage = INVALID_CREDENTIALS;
DisplayLoginButton = true;
DisplayLoginProgressRing = false;
EnableInput = true;
}
}, null);
});
}
else
{
Logger.InfoFormat("User [{0}] failed input validation", this.Login);
DisplayValidationMessage = true;
ValidationMessage = INVALID_INPUT;
DisplayLoginButton = true;
DisplayLoginProgressRing = false;
EnableInput = true;
}
}
#endregion
My Class relation is like this Model:
Class MainModel
{
data data1 = new data();
public override string LFE
{
get { return data1.lnf.ToString(); }
set { data1.lnf = Convert.ToByte(value); }
}
public override UInt16 GetRsBValue(int index)
{
return (byte)this.data1.CConfig[index].Bline;
}
public override void SetRsBValue(UInt16 value, int index)
{
byte[] arr = BitConverter.GetBytes(value);
this.data1.CConfig[index].Bline = arr[0];
}
}
Class data
{
public byte Bline
{
get { return this.bline; }
set { this.bline = value; }
}
public byte lnf
{
get { return this.ln_frequency; }
set { this.ln_frequency = value; }
}
}
Class ViewModel : INotifyPropertyChange
{
public UInt16 Rschange
{
get
{
return this.eObj.GetRsBValue(this.index);
}
set
{
this.eObj.SetRsBValue(value, this.Index);
this.OnPropertyChanged(new PropertyChangedEventArgs("Rschange"));
this.eObj.writedata(DefaultFileName);
}
}
public string LF
{
get
{
return this.eObj.LFE;
}
set
{
this.eObj.LFE = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(" LF"));
}
}
}
In Model side I have created instance of data class in main Model.
I'm getting data from other application to my application. I'm getting that updated value till data class but it's not showing that value in MainModel. So It's not updating mu UI at all. Please tell me how can I update my UI when I'm getting value from other application.
P.S: I don't want to create Model class instance in ViewModel side and I have 10 properties and 10 method like this in my class.
You have a typo in your call to property changed in your LF property.
// note the extra space V
this.OnPropertyChanged(new PropertyChangedEventArgs(" LF"));
If you create your property changed handler like this:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You can prevent such typos by using the following syntax:
public string LF
{
get
{
return this.eObj.LFE;
}
set
{
this.eObj.LFE = value;
this.OnPropertyChanged();
}
}
I have currently the problem that my UI doesnt update as I like to do so, hope you can help me out.
I have a simulated "2 class inheritance" as recommended in this page
http://www.codeproject.com/Articles/10072/Simulated-Multiple-Inheritance-Pattern-for-C
My real life app looks like the following:
public class Item : INotifyPropertyChanged
{
private bool _isVisible;
public bool IsVisible
{
get
{
return _isVisible;
}
set
{
if (_isVisible == value)
return;
_isVisible = value;
OnPropertyChanged("IsVisible");
}
}
//NotifyPropertyChanged Implementation removed so the focus stays on problem...
}
public class ObjectItem : INotifyPropertyChanged
{
private bool _isExpanded;
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
if (_isExpanded== value)
return;
_isExpanded= value;
OnPropertyChanged("IsExpanded");
}
}
//NotifyPropertyChanged Implementation removed so the focus stays on problem...
}
public class CombinedItem : Item
{
private readonly ObjectItem _objectItem = new ObjectItem();
public bool IsExpanded
{
get { return _objectItem.IsExpanded; }
set { _objectItem.IsExpanded = value; }
}
public static implicit operator ObjectItem(CombinedItem combinedItem)
{
return combinedItem._objectItem;
}
}
I am now facing the problem that the Property IsExpanded doesnt get Notified to the UI correctly when I have a CominedItem as the DataContext, the IsVisible Property works as expected.
To overcome the problem I have changed the CominedItem to the following:
public class CombinedItem : Item
{
private readonly ObjectItem _objectItem = new ObjectItem();
public bool IsExpanded
{
get { return _objectItem.IsExpanded; }
set
{
if (_objectItem.IsExpanded == value)
return;
_objectItem.IsExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
public static implicit operator ObjectItem(CombinedItem combinedItem)
{
return combinedItem._objectItem;
}
}
Is there a way to avoid writing the OnPropertyChanged("IsExpanded") again, with this approach.
(I know there are libaries/tools, where you dont need to write it at all and just have to declare a attribute, pls dont suggest those)
Actually you should subscribe to ObjectItem PropertyChanged and raise the matching event on CombinedItem.
If _objectItem.IsExpanded is modified without using CombinedItem.IsExpanded, your UI will not see the change.
Without some magic attribute/tool if you want to wrap a property, you will have to handle changes notification.
public class CombinedItem : Item
{
private readonly ObjectItem _objectItem = new ObjectItem();
public CombinedItem()
{
_objectItem.PropertyChanged += (s, e) =>
{
if (e.PropertyName == "IsExpanded")
OnPropertyChanged("IsExpanded");
}
}
public bool IsExpanded
{
get { return _objectItem.IsExpanded; }
set { _objectItem.IsExpanded = value; }
}
public static implicit operator ObjectItem(CombinedItem combinedItem)
{
return combinedItem._objectItem;
}
}
You could expose ObjectItem as a property and bind to that
public class CombinedItem : Item
{
private readonly ObjectItem _objectItem = new ObjectItem();
public bool IsExpanded
{
get { return _objectItem.IsExpanded; }
set { _objectItem.IsExpanded = value; }
}
public ObjectItem ObjectItem
{
get { return _objectItem; }
}
public static implicit operator ObjectItem(CombinedItem combinedItem)
{
return combinedItem._objectItem;
}
}
<Expander IsExpanded={Binding ObjectItem.IsExpanded}/>
How can I refresh the following ObservableCollection?
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties;
public ObservableCollection<ServerProperties> Properties
{
get
{
properties = new ObservableCollection<ServerProperties>();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
return properties;
}
}
}
public class ServerProperties
{
private string property;
private string value;
public ServerProperties()
{
}
public ServerProperties(string property, string value)
{
Property = property;
Value = value;
}
public string Property
{
get
{
return this.property;
}
set
{
this.property = value;
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
}
}
public override string ToString()
{
return string.Format("[Property : {0}]", Value);
}
}
I changed the value of textProperties[,] and now I'd like to overwrite the previous content of the collection with the current content of textProperties[,].
What would be the simplest way to do this?
Any help would be appreciated.
Start off by implementing INotifyPropertyChanged in your ViewModel as well as in the ServerProperties object. This way you can raise the PropetyChanged event which will pass back to the user interface.
ViewModel
public class ViewModelProperties : INotifyPropertyChanged {
public event ProeprtyChangedEventHandler PropertyChanged;
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties {
get { return properties;}
set {
properties = value;
this.RaisePropertyChangedEvent("Properties");
}
}
private void RaisePropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Implementing this on the ServerProperties object as well will allow you to change the objects, at any level, and have it bubble up to the interface.
Lastly look into your population code and in order to get the property to update successfully first populate it to a List then re-initialise the ObservableCollection using the List.
Properties = new ObservableCollection<ServerProperties>(propertiesList);
This also allows you to better handle the creation of your ObservableCollection and perform tests before posting the output to the interface. Hope it helps.
For example, one of the simpler solutions could be
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties
{
get
{
return properties;
}
}
public void SetProperties()
{
properties.Clear();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
}
}
Any time you wish to add new items to OC, just call the SetProperties method.