Combobox in settingsflyout does not show selected values when opening it again - combobox

This seemed so simple but has turned into a nightmare for me. Everything works great, i can select a value and it's reported back to the view model.
Problem:
User opens the settings flyout and selects a value. User exits the flyout.
User reopens the settings flyout and there is no selected value in the combobox. The value exists in the view model though.
Scenario:
Combobox in a Settingsflyout.
<ComboBox x:Name="defaultComboBox" SelectedItem="{Binding UserSettings.DefaultAccount, Mode=TwoWay}" ItemsSource="{Binding UserAccounts}" DisplayMemberPath="CustomName">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:InvokeCommandAction Command="{Binding UserAccountComboboxLoadedCommand}" CommandParameter="{Binding ElementName=defaultAccountComboBox}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
ViewModelCode:
public void Open(object parameter, Action successAction)
{
logger.LogProgress("Opened UserSettingsFlyoutView.");
UserSettings.DefaultAccount = UserAccounts.FirstOrDefault(u => u.AccountID.ToString().Equals(userSettings.DefaultAccountGuid,StringComparison.CurrentCultureIgnoreCase));
}
public CrossThreadObservableCollection<UserAccount> UserAccounts
{
get
{
try
{
return dbContext.RetrieveAllUserAccounts();
}
catch(Exception e)
{
logger.LogError("Error happened when retrieving user-accounts from secure data store Error: " + e.Message, e.ToString());
return new CrossThreadObservableCollection<UserAccount>();
}
}
}
private IProvideUserSetting userSettings;
public IProvideUserSetting UserSettings
{
get { return userSettings; }
set { userSettings = value; OnPropertyChanged("UserSettings"); }
}
UserSettings class:
private string defaultAccountGuid;
[DataMember]
public string DefaultAccountGuid
{
get { return defaultAccountGuid; }
set { defaultAccountGuid = value; OnPropertyChanged("DefaultAccountGuid"); }
}
private UserAccount defaultAccount;
[IgnoreDataMember]
public UserAccount DefaultAccount
{
get { return defaultAccount; }
set {
defaultAccount = value;
if (defaultAccount != null)
DefaultAccountGuid = defaultAccount.AccountID.ToString();
OnPropertyChanged("DefaultAccount"); }
}

I tried a version of the code and could not reproduce the issue. Could you provide more code? Is there something else setting the selected item?
Anyway, the type of item in the ItemsSource is different than the type of item for selected item. I would try changing the selected item binding to the same class in the items source.
For example, instead of the viewmodel property UserSettings, make that object type UserAccount.
Something like
private UserAccount _selectedUserAccount { get; set; }
public UserAccount SelectedUserAccount
{
get { return _selectedUserAccount; }
set
{
if (_selectedUserAccount != value)
{
_selectedUserAccount = value;
OnPropertyChanged("SelectedUserAccount");
}
}
}
Edit:
You can add a loaded event handler to your combobox, then locate the viewmodel from the code behind and set the selected item property.
private void ComboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
comboBox.SelectedItem =
_viewModel.UserAccounts.Where(x => x.UserAccountString == _viewModel.SelectedUserAccount.UserAccountString);
}

Related

Enable Disable save button during Validation using IDataErrorInfo & Update Button State

I am new to the WPF MVVM and wanted to ask a follow up question to this article:
Enable Disable save button during Validation using IDataErrorInfo
I am trying to enable/disable the button save/update if any of the many controls on the form validation failed/passed.
I have the IsValid method, that checks the validation logic on the Model and returns True/False, that will be passed on to the DelegateCommand as a predicate.
The question is: my button has the following property IsEnabled{binding IsValid}, this should check all fields to make sure that it matches the criteria in the model, returns true/false to the view model and then enables the button if all true. The problem is: Once the view model is instantiated, the DelegateCommand object is created with the validation (IsValid) at at a false state and it stays that way throughout the life of the object even though the user is filling data in the textboxes. How do I turn on the button once all the conditions are met? in other words, how to constantly keep validating and updating the IsValid in order to switch the button on once every textbox validate to true??
Thanks,
I have the following code:
The Model
public class UserModel : ObservePropertyChanged,IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
// if there is an error throw an exception
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Name")
{
if (string.IsNullOrEmpty(Name))
result = "Please enter a Name";
}
return result;
}
}
// the Josh Way
static readonly string[] ValidatedProperties =
{
"Name"
};
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
{
if (GetValidationError(property) != null) // there is an error
return false;
}
return true;
}
}
// a method that checks validation error
private string GetValidationError(string propertyName)
{
string error = null;
switch (propertyName)
{
case "Name":
error = this.ValidateName();
break;
default:
error = null;
throw new Exception("Unexpected property being validated on Service");
}
return error;
}
private string ValidateName()
{
string ErrorMsg = null;
if (string.IsNullOrWhiteSpace(Name))
{
ErrorMsg = "Name can't be empty!";
};
return ErrorMsg;
}
}
** the View Model **
public class UserViewModel:ObservePropertyChanged
{
UserModel model;
public UserViewModel()
{
presentCommand = new DelegateCommand(param => PresentDataMethod(), param => CanSave);
model = new UserModel();
}
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
private string info;
public string Info
{
get { return info; }
set { info = value; OnPropertyChanged("Info"); }
}
private DelegateCommand presentCommand;
public DelegateCommand PresentCommand
{
get
{
if (presentCommand==null)
{
presentCommand = new DelegateCommand(param => PresentDataMethod(), param => CanSave);
}
return presentCommand;
}
}
private void PresentDataMethod()
{
Info = $"Your Name is: {Name}.";
}
// The ViewModel then contains a CanSave Property that reads the IsValid property on the Model:
protected bool CanSave
{
get
{
return model.IsValid;
}
}
}
** The View**
<TextBox x:Name="Name" HorizontalAlignment="Left" Height="34" Margin="285,145,0,0" TextWrapping="Wrap"
VerticalAlignment="Top" Width="248">
<Binding Path="Name"
ValidatesOnDataErrors="True"
UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay">
</Binding>
</TextBox>
<Button Content="Present" FontSize="20" HorizontalAlignment="Left"
Margin="285,184,0,0" VerticalAlignment="Top" Width="248" Height="35"
Command="{Binding Path=PresentCommand}"
IsEnabled="{Binding IsValid}"
>
</Button>
If all you want to do is refresh the button via the IsValid value, all you have to do is listen for any of the OTHER property changes in your ViewModel, and when that happens, tell it to refresh the IsValid binding as well (which is actually your CanSave property). Here is one way to do that:
** The View Model **
// ...
public UserViewModel()
{
// ...
this.PropertyChanged += OnViewModelPropertyChanged;
}
public void OnViewModelPropertyChanged(object sender, PropertyEventArgs e)
{
// On any property that implements "OnPropertyChanged(propname)", refresh the CanSave binding too!
OnPropertyChanged(nameof(this.CanSave));
}
// ...
BTW, it is generally good coding practice to avoid magic words like OnPropertyChanged("Name") or OnPropertyChanged("Info") because you never know when a developer will have to rename their properties, and if that happens, you won't get a compile error here and it might be hard to debug. It is best practice to use the nameof, such as OnPropertyChanged(nameof(Name)) so that you'll know you'll get a compilation error if you ever decide to change the property to, say, FirstName instead of Name.

Databinding not refreshed in Xamarin (WPF)

I have some trouble with the data binding while using MVVM in Xamarin. Let me explain my architecture:
I have a Manager-class, which contains a ObservableCollection with classes of type t, t is my model class. The Manager-class contains also a attribute called activeT, which is the current selected object of my model class. There are 2 UIs, one which shows the current data of t. The viewModel is bound to the attribute t of my Manager-class like that:
public t CurrentT
{
get
{
return _mgr.CurrentT;
}
set
{
_mgr.CurrentT = value;
OnPropertyChanged();
}
}
_mgr is my singleton Manager-Object.
Now there is the other view, which is able to choose the current t out of a combobox. The viewModel of that view is bound to the ObservableCollection of the manager. If I change the selected object, I do it like with the same code like above. The Property of the manager is the following code:
public t CurrentT
{
get
{
return _currentT;
}
set
{
_currentT= value;
OnPropertyChanged());
}
}
The problem is now, that the first view to view the current selected t does not refresh, though I can see in the debugger, that the current t is changed by the other view.
Can someone help me?
Edit:
I provide some more Code:
The Manager-Class:
public class Manager : INotifyPropertyChanged
{
private t _currentConstructionSite;
private ObservableCollection<t> _constructionSites = null;
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public t CurrentConstructionSite
{
get
{
return _currentConstructionSite;
}
set
{
_currentConstructionSite = value;
OnPropertyChanged("CurrentConstructionSite");
}
}
public ObservableCollection<t> ConstructionSites
{
get
{
return _constructionSites;
}
set
{
_constructionSites = value;
}
}
private Manager()
{
ConstructionSites = DataRepository.GenConstructionSites();
_currentConstructionSite = ConstructionSites[0];
}
}
The ViewModels Class A (This is the viewmodel of the view, which shows some data):
public class DashboardViewModel : ViewModelBase
{
private Manager _mgr;
public t CurrentConstructionSite
{
get
{
return _mgr.CurrentConstructionSite;
}
set
{
_mgr.CurrentConstructionSite = value;
OnPropertyChanged();
}
}
public DashboardViewModel()
{
_mgr = Manager.getInstance();
}
}
The View A to show some data:
Binding Setup from XAML:
<ContentPage.BindingContext>
<local:DashboardViewModel x:Name="viewModel"/>
</ContentPage.BindingContext>
Binding a Label to show data:
<Label Text="{Binding CurrentConstructionSite.ConstructionSiteName, Mode=TwoWay}" HorizontalOptions="Center" Font="Bold" FontSize="Large"/>
ViewModel B to choose the current t:
public class ChooseConstructionSiteViewModel : ViewModelBase
{
Manager _mgr = null;
public ObservableCollection<t> ConstructionSites
{
get
{
return _mgr.ConstructionSites;
}
}
public t CurrentConstructionSite
{
get
{
return _mgr.CurrentConstructionSite;
}
set
{
_mgr.CurrentConstructionSite = value;
OnPropertyChanged();
}
}
public ChooseConstructionSiteViewModel()
{
_mgr = Manager.getInstance();
}
}
The View to choose the current t:
<combobox:SfComboBox x:Name="combobox" Grid.Row="0" Grid.Column="1" Margin="8,0,20,0" VerticalOptions="Center" HeightRequest="40" DataSource="{Binding ConstructionSites}" DisplayMemberPath="ConstructionSiteName" SelectionChanged="Handle_SelectionChanged"/>
And if the selection from the combobox changed:
void Handle_SelectionChanged(object sender, Syncfusion.XForms.ComboBox.SelectionChangedEventArgs e)
{
t selectedItem = e.Value as t;
_viewModel.CurrentConstructionSite = selectedItem;
}
The two views are contained as contetPages in a tabbedPage. It works in general, but the changing the selected t in the view B does not update the data in view A. I can see in the debugger that the value of t is changed via view B but when I go back to view A there is the old value. In the debugger I can see that the value is updated.
BTW: ViewModelBase is the class which implements INotifyPropertyChanged
From you second viewmodel, you need to "notify" the first viewmodel that the data has changed.
One of the ways to do that, would be to use a Messenger (MvvmLight has one, so does MvvmCross). You can then use the messenger from your Manager, to notify all the viewmodels that need the info, that CurrentT changed.
In the messenger subscription in your viewmodels, simply call a RaiseNotifyPropertyChanged of your property, and you should be good to go

Why is my Login button always disabled?

I have a popup view with x:Name=This, on it a button delcared as follows:
<Button Content="Log in" Command="{Binding Path=LoginCommand}" CommandParameter="{Binding ElementName=This}" />
This is to gain access to the non-bindable Password property, which is a SecureString type.
In my ctor I initialise the command like so:
public LoginPopupViewModel()
{
LoginCommand = new DelegateCommand<IHavePassword>(
LogUserIn,
p => !string.IsNullOrWhiteSpace(Username));
}
I fully expect that when I type something in the Username, and change focus, the property change notification will help enable the Login button. It doesn't, so I have added the extra code, and the button still remains disabled.
public string Username
{
get { return _username; }
set
{
if (value == _username) return;
_username = value;
OnPropertyChanged();
CommandManager.InvalidateRequerySuggested();
}
}
If I change the CanExecute delegate like below, only then is the button enabled:
public LoginPopupViewModel()
{
LoginCommand = new DelegateCommand<IHavePassword>(
LogUserIn,
p => true);
}
Why does this button remain disabled even when its command can execute?
I have tried a sample program and binding seems to work fine. I don't have your complete source code but you need to use RaiseCanExecuteChanged on the delegate command when you want the command to check if it needs to execute. Have you checked if the binding on the username is correct?
this.loginCommand.RaiseCanExecuteChanged(); is the key to the answer
public LoginPopupViewModel()
{
this.loginCommand = new DelegateCommand(() =>
{
MessageBox.Show("Logged In Click");
}, () =>
{
return !string.IsNullOrEmpty(UserName);
});
}
private DelegateCommand loginCommand;
private string userName;
public ICommand LoginCommand
{
get { return loginCommand; }
}
public string UserName
{
get { return this.userName; }
set
{
if (value == this.userName)
{
return;
}
this.userName = value;
OnPropertyChanged("UserName");
this.loginCommand.RaiseCanExecuteChanged();
}
}
public string Password { get; set; }

WPF ComboBox SelectedItem when bound to an enum

I have a WPF ComboBox bound to a list of a class which contains an enum.
This all works fine, my question is at the end of this post, first the code:
Here is the class:
public class FILTER_TEST
{
public FilterType Filter { get; private set; }
public string Description { get; private set; }
public static List<FILTER_TEST> CreateFilters()
{
var list = new List<FILTER_TEST>();
list.Add(new FILTER_TEST() { Filter = FilterType.CheckNone, Description = "Uncheck all" });
list.Add(new FILTER_TEST() { Filter = FilterType.CheckAll, Description = "Check all" });
list.Add(new FILTER_TEST() { Filter = FilterType.CheckCustom, Description = "Custom check" });
return list;
}
}
Here is the enum FilterType:
public enum FilterType
{
CheckNone,
CheckAll,
CheckCustom
}
In my view model I have the following:
public List<FILTER_TEST> FilterNames { get { return FILTER_TEST.CreateFilters(); } }
public FILTER_TEST SelectedFilter
{
get { return selectedFilter; }
set
{
if (value != selectedFilter)
{
selectedFilter = value;
OnPropertyChanged("SelectedFilter");
}
}
}
Also in the view model, I set the SelectedItem of the ComboBox as follows:
SelectedFilter = FilterNames.Where(x => x.Filter == FilterType.CheckNone).FirstOrDefault();
Here is the xaml putting it all together:
<ComboBox DisplayMemberPath="Description" ItemsSource="{Binding FilterNames}"
SelectedItem="{Binding SelectedFilter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True"/>
My problem is that although the changing of the SelectionItem works, the actual value displayed in the ComboBox doesn’t change.
The initial SelectedItem is “Uncheck all” as, when the window has been loaded, none of the corresponding CheckBox controls (bound to another class which contains a Boolean property) have been checked. What I would like is that when a CheckBox has been checked, then the SelectedItem changes to “Custom check”.
This does indeed change the value of the SelectedItem:
SelectedFilter = FilterNames.Where(x => x.Filter == FilterType.CheckCustom).FirstOrDefault();
But the text shown in the ComboBox is still “Uncheck all”.
Does anyone have an idea as to what I am missing? I am forced to use the 4.0 framework, I don’t know if this is relevant.
I've seen the hint to overwrite Equals() of the type in use as this:
public override bool Equals(object o)
{
if (o is FILTER_TEST)
{
var other = o as FILTER_TEST;
return this.Description == other.Description && this.Filter == other.Filter;
}
else
return false;
}
Now that makes your sample work. Let me come back for a reference on the why.

WPF ListView selection upon focus lost

I am currently learning and working on simple WPF (MVVM pattern) application, which allows to select items from one listview (available items) to another (order) and create an order class instance once 'buy' button is being pressed.
My problem is that once I click on first listview - item is selected and I am not able to deselect it once focus is lost.
I have learnt a lot about event and commands in MVVM, but things a really mixed in my head. Could you please guide me to the simple way how it's possible to 'refresh'/deselect all items once focus is lost on listview?
Thank you.
Here's a quick and dirty example using bindlings. You'll probably have to adjust some it for your needs:
The XAML
<Grid>
<ListBox Name="customerList1"
ItemsSource="{Binding List1Customers}"
SelectedItem="{Binding List1SelectedCustomer, Mode=TwoWay}"/>
<ListBox Name="customerList2"
ItemsSource="{Binding List2Customers}"
SelectedItem="{Binding List2SelectedCustomer, Mode=TwoWay}"/>
</Grid>
The ViewModel
public class Customer
{
public int id { get; set; }
}
public class MyViewModel
{
// This is the collection the first list gets its data from
private ObservableCollection<Customer> list1Customers;
public ObservableCollection<Customer> List1Customers
{
get { return list1Customers; }
set
{
if (list1Customers != value)
{
list1Customers = value;
}
}
}
// This is the customer selected in the first list
private Customer list1SelectedCustomer;
public Customer List1SelectedCustomer
{
get { return list1SelectedCustomer; }
set
{
if (list1SelectedCustomer != value)
{
list1SelectedCustomer = value;
}
}
}
// This is the collection the second list gets its data from
private ObservableCollection<Customer> list2Customers;
public ObservableCollection<Customer> List2Customers
{
get { return list2Customers; }
set
{
if (list2Customers != value)
{
list2Customers = value;
}
}
}
// This is the customer selected in the second list
private Customer list2SelectedCustomer;
public Customer List2SelectedCustomer
{
get { return list2SelectedCustomer; }
set
{
if (list2SelectedCustomer != value)
{
list2SelectedCustomer = value;
}
}
}
public void MoveCustomer(int id)
{
// Find the customer from list 1
var customerToMove = List1Customers.Where(x => x.id == id).FirstOrDefault();
// If it was found...
if (customerToMove != null)
{
// Make the first customer null, which un selects it in the list
List1SelectedCustomer = null;
// Remove the customer from list 1, or comment out if you dont want it removed
List1Customers.Remove(customerToMove);
// Add the customer to list 2
List2Customers.Add(customerToMove);
// Make the newly addec customer in list 2 selected
List2SelectedCustomer = List2Customers.Where(x => x.id == id).FirstOrDefault();
}
}
}

Resources