Share properties between 2 VMs - c#

I'm using Caliburn.Micro and I have the MainWindowViewModel setup with a child VM in a content control. In the Main VM I have a Child VM property which allows me to share properties across the VMs. I have a UserName property on both VMs.
On the Main View I am adding a string to the end of the username to personalize it, while on the child View I'm just displaying the Username as is. The username property is only editable in the child VM, but not the main VM. When I edit it in the child it does not update the parent. I'm not sure how to bind these properties to get the Parent VM in sync with the Child VM.
Main VM:
public string UserName
{
get { return string.Format("{0}'s Diary Log", DiaryViewModel.UserName); }
}
private DiaryViewModel _diaryViewModel;
public DiaryViewModel DiaryViewModel
{
get { return _diaryViewModel; }
set
{
_diaryViewModel = value;
NotifyOfPropertyChange(() => DiaryViewModel);
}
}
Child VM:
private string _userName = "John";
public string UserName
{
get { return _userName; }
set
{
_userName = value;
NotifyOfPropertyChange(() => UserName);
}
}

You could just bind your control in the MainView to DiaryViewModel.UserName and do the string formatting right in the .xaml. Don't forget to escape the apostrophe in the string formatting with a slash.
<TextBlock Text="{Binding Source=DiaryViewModel.UserName, StringFormat='\{0}\'s Diary Log'}" />

Related

MVVM Editable ComboBox without changing the SelectedItem

I have exactly the same requirements as this user: MVVM Editable ComboBox Bindings.
I've tried the accepted answer:
"Bind a property like "EditedServerName" to Combobox.Text. When the
"EditedServerName" is changed you can set the value to the
"ServerName" of your SelectedServer."
But it's not working because when I try to intercept "EditedServerName" the "SelectedServer" is null. I believe it's because the control tries to search the "ServerName" that is being edited, in the collection, and obviously fails to retrieve an element. This is very clear when I start editing and the textblock with the "ServerID" just gets immediately empty.
XAML:
<ComboBox IsEditable= "True"
ItemsSource= "{Binding Servers}"
DisplayMemberPath= "ServerName"
SelectedItem="{Binding SelectedServer}"
Text= "{Binding EditedServerName, UpdateSourceTrigger=LostFocus}" />
<TextBlock Text="{Binding SelectedServer.ServerID}"/>
ViewModel:
public List<Server> Servers { get; set; }
public Server SelectedServer { get; set; }
private string editedServerName;
public string EditedServerName
{
get { return editedServerName; }
set
{
editedServerName = value;
SelectedServer.ServerName = value;
}
}
public MainViewModel()
{
Servers = new List<Server>();
Servers.Add(new Server { ServerID = 0, ServerName = "Local" });
Servers.Add(new Server { ServerID = 1, ServerName = "Remote" });
}
I know I could temporarily store the "SelectedServer" on another object, but I would like a better turn-around if possible.
A couple of things:
Your MainViewModel is not making proper use of the INotifyPropertyChanged interface, which is recommended for data binding back to the view. This is the reason that there's no change to the UI with your currently posted code.
You're correct in saying that once the EditedServerName is changed, the SelectedServer will become null, but with point 1, it's getting to that point either. Even if we fix the code to accommodate data binding, the SelectedServer property is not handling null.
With that in mind, here's what the code might look like if we modify the code to accommodate the above points:
Below code helps notify the view of any changes to our Models / ViewModels
public class BaseNotifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here's what our updated model looks like. Note the use of OnPropertyChanged which is defined in BaseNotifier, as this will update the View with the new server name
public class Server : BaseNotifier
{
private string _serverName;
public string ServerName
{
get { return _serverName; }
set
{
_serverName = value;
OnPropertyChanged();
}
}
private int _serverID;
public int ServerID
{
get { return _serverID; }
set
{
_serverID = value;
OnPropertyChanged();
}
}
}
This is what the ViewModel now looks like. For ViewModels, use ObservableCollection instead of List to allow autmatic view updates when inserting data. You'll also notice that the code checks for null in both the SelectedServer and EditedServerName which should handle your original question.
public class MainViewModel : BaseNotifier
{
private string editedServerName;
private Server selectedServer = null;
public ObservableCollection<Server> Servers { get; set; }
public Server SelectedServer
{
get { return selectedServer; }
set
{
if (value != null)
{
selectedServer = value;
OnPropertyChanged();
}
}
}
public string EditedServerName
{
get { return editedServerName; }
set
{
editedServerName = value;
if (SelectedServer != null)
{
SelectedServer.ServerName = value;
}
OnPropertyChanged();
}
}
public MainViewModel()
{
Servers = new ObservableCollection<Server>();
Servers.Add(new Server { ServerID = 0, ServerName = "Local" });
Servers.Add(new Server { ServerID = 1, ServerName = "Remote" });
}
}

complexed Issue with hiding ListView Xamarin Forms

I have a search bar with the property Text binded to a string property in my ViewModel.
I also have Behaviors within the search bar so that every time the text is changed a search is done within a list of objects using NewTextValue passed to as the query string.
The issue I have is that, I make the ListView invisible until a non-empty string is passed to my Search/Filter command (obviously.. :)). I have tried to enforcing hiding the ListView for a couple scenarios e.g. if all text is removed from the search bar.
When an item is selected from the now visible list view I used that item to populate the Text property of my SearchBar, after which I cannot hide it within code. All attempts have failed and the ListView remains visible. Note: I explicity created a hide button separately and saw it worked so I am wondering if I cannot tie hiding the view with setting the searchbar Text property.
View
<SearchBar Text="{Binding SearchText}">
<SearchBar.Behaviors>
<prismBehaviors:EventToCommandBehavior EventName="TextChanged"
Command="{Binding FilterOccupationsListCommand}"
EventArgsParameterPath="NewTextValue"/>
</SearchBar.Behaviors>
</SearchBar>
<ListView ItemsSource="{Binding FilteredOccupations}" IsVisible="{Binding FilteredOccupationsVisible}" SelectedItem="{Binding Occupation, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Please Note : My ViewModel inherits from BaseViewModel which inherits INotifyPropertyChanged. SetProperty() is what notifies the property. This is quite common with MvvmCross, Prism etc.
ViewModel
public class MyViewModel : BaseViewModel
{
public DelegateCommand<string> FilterOccupationsListCommand { get; }
public MyViewModel()
{
FilterOccupationsListCommand = new DelegateCommand<string>(FilterOccupationsList);
}
private void FilterOccupationsList(string query)
{
if (!string.IsNullOrWhiteSpace(query))
{
FilteredOccupationsVisible = true;
var searchResult = Occupations.Where(x => x.Name.ToLower().Contains(query));
FilteredOccupations = new ObservableCollection<Occupation>(searchResult);
}
else
FilteredOccupationsVisible = false;
}
private Occupation _occupation;
public Occupation Occupation
{
get => _occupation;
set
{
SetProperty(ref _occupation, value);
SearchText = value.Name;
}
}
private string _name;
public string Name { get => _name; set => SetProperty(ref _name, value); }
private string _searchText;
public string SearchText
{
get => _searchText;
set {
SetProperty(ref _searchText, value);
FilteredOccupationsVisible = false;
}
}
private bool _filteredOccupationsVisible;
public bool FilteredOccupationsVisible { get => _filteredOccupationsVisible; set => SetProperty(ref _filteredOccupationsVisible, value); }
public ObservableCollection<Occupation> _filteredOccupations = new ObservableCollection<Occupation>();
public ObservableCollection<Occupation> FilteredOccupations { get => _filteredOccupations; set { SetProperty(ref _filteredOccupations, value); } }
}
If not using Behaviors in SearchBar , you can have a try with TextChanged method of itself.
<SearchBar x:Name="MySearchBar" Text="SearchText" TextChanged="SearchBar_TextChanged" />
In ContentPage , when text cheanged fire here :
MyViewModel myViewModel = new MyViewModel();
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
Console.WriteLine("new -- " + e.NewTextValue + "-- old -- " + e.OldTextValue);
Console.WriteLine("MyEntry --" + MySearchBar.Text);
//Here can invoke FilterOccupationsList of MyViewModel
myViewModel.FilterOccupationsList(MySearchBar.Text);
}
Else if using Command to do , you need to add isntance of ICommand in MyViewModel to invoke FilterOccupationsList.
public class MyViewModel : BaseViewModel
{
public ICommand FilterOccupationsListCommand { private set; get; }
...
public MyViewModel()
{
FilterOccupationsListCommand = new Command<string>((NewTextValue) =>
{
// Pass value to FilterOccupationsList.
Console.WriteLine("SearchBar new text --" + NewTextValue);
FilterOccupationsList(NewTextValue);
});
}
...
}

Variable not retaining value between methods c#

I'm making a WPF Application and I want the Text within my Textbox to change as I change the variable value, however, although I'm setting the variable correctly I can't get it to update the Textbox.
I have this class:
public class UserSettings : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string username;
public string nameuser
{
get { return username;} set { username= value; OnPropertyChanged(nameuser); }
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Which gets called in this class:
public partial class User: Window
{
private UserSettings objsettings = null;
public User()
{
objsettings = new UserSettings();
DataContext = objsettings;
InitializeComponent();
Console.WriteLine("objsettings.username1: " + objsettings.nameuser);
}
public void SetUserSettings(string username)
{
Console.WriteLine("Username: " + username);
objsettings.nameuser= username;
Console.WriteLine("objsettings.username2: " + objsettings.nameuser);
}
and the XAML is:
<TextBox Text="{Binding nameuser, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="210,193,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="30"/>
The console print in the SetUserSettings prints the value however the console print at the top prints the value as nothing. I'm aware that the top value is printing nothing as it gets set AFTER the console print is called (hence it wouldn't contain a value yet), but how do I update it so the window prints the new value, how do I get it to continously loop through User() without opening new windows but just update the values?
You send incorrect Notify:
get { return username;} set { username= value; OnPropertyChanged(nameof(nameuser)); }
You should send not nameuser, but nameof(nameuser).
As for initial username pass it to the User class:
public User(string username)
{
objsettings = new UserSettings();
objsettings.nameuser = username;
DataContext = objsettings;
InitializeComponent();
Console.WriteLine("objsettings.username1: " + objsettings.nameuser);
}
You send an incorrect Notification. You should send nameof(property name) instead the property value.
In addition, to increase the performance, we don't send notifications if there is no change in property value:
public string nameuser
{
get {
return username;
}
set {
if(username == value)
{
return;
}
username= value;
OnPropertyChanged(nameof(nameuser));
}
}
OnPropertyChanged method expects the property name as string. In your case the code compiles since nameuser is also of type string. But essentially you are just telling the UI to listen for changes in property whose name is same as value of nameuser.
Changing it to the correct property name as OnPropertyChanged("nameuser"), will get the results you want.
In C# version 6 you can also use the nameof feature as it ensures that there are no magic strings in the code OnPropertyChange(nameof(IsBarNull));

Forcing all WPF bound controls to update with GetBindingExpression().UpdateTarget()

I have a WPF application that includes ~50 controls that are bound to properties on my business object which implements INotifyPropertyChanged. Here's a quick snippet of my business object:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
// properties begin here
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
// constructor, etc. not shown
}
I also have several validation rules that are used to validate the user input in these controls. I'm using command binding to prevent my user from saving the data if there are any validation errors. My application also includes a "Reset default values" button which, obviously, will reset the default value for all of the properties on my business object. This all works exactly as I'd like it to with one exception. If my user enters invalid data into one or more controls and then clicks the "Reset default values" button, the controls that contain invalid data don't always update as I'd expect. This happens because of the following code in my property setters:
if (_name == value)
{
return;
}
This code exists to prevent unnecessary property changed notifications from occurring when the value entered by my user in the bound UI control is the same value that the property is already set to. As an example, I have an IntegerUpDown control in my UI (this control is part of the Extended WPF Toolkit from Xceed). The default value of the property that my control is bound to is 10. My user deletes the value from the control and my validation rule is triggered which results in a validation error and the UI is updated appropriately with an error adorner, etc. The value of the property that this control is mapped to hasn't been changed so it's still set to 10. Now my user clicks the "Reset default values" button which will result in the default value (10) for the property being reset. However, the value for the property is already set to 10 so the short circuit logic in my setter will return instead of setting the property value.
So now, after my user clicks "Reset default values", I am also forcing an update on my binding target like this:
this.myIntegerUpDown.GetBindingExpression(Xceed.Wpf.Toolkit.IntegerUpDown.ValueProperty).UpdateTarget();
This solves my problem but only for this particular control. Is there any easy way to do this for all of my bound controls without having to specify each one? Thanks.
OnPropertyChanged(new PropertyChangedEventArgs(string.Empty));
This is intended to imply that ALL properties on that object have changed.
Could you do one of the following?
1) Reset the DataContext - Either recreate it, or re-set the property
var context = this.DataContext;
this.DataContext = null;
this.DataContext = context;
2) Loop through all properties programmatically via reflection and manually call OnPropertyChanged with the relevant property names.
var properties = typeof(ViewModel).GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(property.Name));
}
You've mentioned validation and reset values, and of course the obvious one is to persist it.
Why don't you implement IEditableObject Interface on your entity that has three signature methods. BeginEdit(), CancelEdit() and EndEdit()
That way you can easily roll back your entity to the whatever you want, or validate it and lastly persist it. A good example is found here
Sample code
public class Customer : IEditableObject
{
struct CustomerData
{
internal string id ;
internal string firstName ;
internal string lastName ;
}
private CustomersList parent;
private CustomerData custData;
private CustomerData backupData;
private bool inTxn = false;
// Implements IEditableObject
void IEditableObject.BeginEdit()
{
Console.WriteLine("Start BeginEdit");
if (!inTxn)
{
this.backupData = custData;
inTxn = true;
Console.WriteLine("BeginEdit - " + this.backupData.lastName);
}
Console.WriteLine("End BeginEdit");
}
void IEditableObject.CancelEdit()
{
Console.WriteLine("Start CancelEdit");
if (inTxn)
{
this.custData = backupData;
inTxn = false;
Console.WriteLine("CancelEdit - " + this.custData.lastName);
}
Console.WriteLine("End CancelEdit");
}
void IEditableObject.EndEdit()
{
Console.WriteLine("Start EndEdit" + this.custData.id + this.custData.lastName);
if (inTxn)
{
backupData = new CustomerData();
inTxn = false;
Console.WriteLine("Done EndEdit - " + this.custData.id + this.custData.lastName);
}
Console.WriteLine("End EndEdit");
}
public Customer(string ID) : base()
{
this.custData = new CustomerData();
this.custData.id = ID;
this.custData.firstName = "";
this.custData.lastName = "";
}
public string ID
{
get
{
return this.custData.id;
}
}
public string FirstName
{
get
{
return this.custData.firstName;
}
set
{
this.custData.firstName = value;
this.OnCustomerChanged();
}
}
public string LastName
{
get
{
return this.custData.lastName;
}
set
{
this.custData.lastName = value;
this.OnCustomerChanged();
}
}
internal CustomersList Parent
{
get
{
return parent;
}
set
{
parent = value ;
}
}
private void OnCustomerChanged()
{
if (!inTxn && Parent != null)
{
Parent.CustomerChanged(this);
}
}
public override string ToString()
{
StringWriter sb = new StringWriter();
sb.Write(this.FirstName);
sb.Write(" ");
sb.Write(this.LastName);
return sb.ToString();
}
}
Wouldn't it be easier to just always call OnPropertyChanged regardless of whether its the same? How much of a performance boost does that give you?

Why couldn't my Command enable the button?

I am learning to use DelgateCommand from Prism....
In my UI, I have my UserName textbox and PasswordBox:
<TextBox Name="_UserNameTextBox" Text="{Binding UserName, Mode=TwoWay}" />
<PasswordBox Name="_PasswordBox"></PasswordBox>
And my Login Button:
<Button Name="button1" Command="{Binding LoginCommand, Mode=TwoWay}" CommandTarget="{Binding ElementName=_UserNameTextBox, Path=Text}">Login</Button>
Then my ViewModel I have:
string _UserName = string.Empty;
public string UserName
{
get
{
return _UserName;
}
set
{
if (value != _UserName)
{
_UserName = value;
RaisePropertyChanged("UserName");
}
}
}
//For reference the password
PasswordBox _PasswordBox { get; set; }
public DelegateCommand<string> LoginCommand { get; set; }
public LoginViewModel(PasswordBox passwordBox)
{
_PasswordBox = passwordBox;
LoginCommand = new DelegateCommand<string>(
(
//Execute
(str) =>
{
Login(_PasswordBox.Password);
}
),
//CanExecute Delgate
(usr) =>
{
if (string.IsNullOrEmpty(usr) || string.IsNullOrEmpty(_PasswordBox.Password))
return false;
return true;
}
);
}
I can see my UserName is binding properly and I did pass my PasswordBox as referece in ViewModel constructor. When I execute the application the Button is disabled, so I know is binded to the command.
But I never see the CanExecute delgate that I wrote is being check after I type things in UserName and PasswordBox.... And is never enabled...
So what did I done wrong?
EDIT:
=====
So end result is...this?
string _UserName = string.Empty;
public string UserName
{
get
{
return _UserName;
}
set
{
if (value != _UserName)
{
_UserName = value;
RaisePropertyChanged("UserName");
LoginCommand.RaiseCanExecuteChanged();
}
}
}
//For reference the password
PasswordBox _PasswordBox { get; set; }
public DelegateCommand<string> LoginCommand { get; set; }
public LoginViewModel(PasswordBox passwordBox)
{
_PasswordBox = passwordBox;
_PasswordBox.PasswordChanged += delegate(object sender, System.Windows.RoutedEventArgs e)
{
LoginCommand.RaiseCanExecuteChanged();
};
LoginCommand = new DelegateCommand<string>(
(
(str) =>
{
Login(_PasswordBox.Password);
}
),
(usr) =>
{
if (string.IsNullOrEmpty(usr) || string.IsNullOrEmpty(_PasswordBox.Password))
return false;
return true;
}
);
}
Generally speaking, you have to call RaiseCanExecuteChanged whenever the effecting value returned by CanExecute changes. In this specific case you would need to call it whenever the value of the user or password fields changes. But that is exceedingly difficult, because your ViewModel implementation is totally wrong.
Here's what you should do instead:
Expose a Username and a Password property inside your ViewModel. You will need to implement the getters and setters explicitly (i.e. it cannot be an automatic property).
From within your view, bind the contents of the username and password input fields to these properties.
Inside the property setters, call LoginCommand.RaiseCanExecuteChanged.
Here's what will happen when you do this (let's pick the password box for an example):
The user types a character inside the password box.
WPF sets the value of LoginViewModel.Password because of the two-way binding.
The password setter calls RaiseCanExecuteChanged, which raises the CanExecuteChanged event on your command.
The submit button (which has subscribed to that event when you bound it to the command) gets notified.
The button calls CanExecute to see if executing the command is now allowed.
Your delegate runs and returns true, so the button activates itself.
You need to bind the Button.CommandParameter (which will be passed to the Execute and CanExecute), if that binding changes the CanExecute is reevaluted as far as i know.
(I think you are confusing the CommandParameter with the CommandTarget, the CommandTarget is not used inside the command, it is only used to raise a command on a certain element (which can be relevant in terms of command routing and such)

Categories