WPF ListView selection upon focus lost - wpf

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();
}
}
}

Related

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

Wpf MVVM opening 2 or more views to compare

I have a ListView and I want to compare different views to each other. But when I select another object in the ListView it overrides the previous selected object.
See this image:
I'm using MVVM with DataBinding so it has something to do with that I guess, now how can you 'fix' this. Because I have no idea at the moment.
I hope its clear what I try to do or want?
XAML Part
<ListView Grid.Row="3"
ItemsSource="{Binding PersonList}"
SelectedItem="{Binding SelectedPerson}">
ViewModel:
private Person _SelectedPerson;
public Person SelectedPerson
{
get
{
return _SelectedPerson;
}
set
{
_SelectedPerson = value;
Messenger.Default.Send(SelectedPerson);
PersonDetailView personDetailView = new PersonDetailView();
personDetailView.Show();
_SelectedPerson = null;
RaisePropertyChanged("SelectedPerson");
}
}
Do I also need to create new instance of the DetailViewModel?
PersonDetailViewModel:
public PersonDetailViewModel(IPersonDataService iPersonDataService)
{
_IPersonDataService = iPersonDataService;
Messenger.Default.Register<Person>(this, (selectedPerson) =>
{
SelectedPerson = selectedPerson;
});
}
private Person _SelectedPerson;
public Person SelectedPerson
{
get
{
return _SelectedPerson;
}
set
{
_SelectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
Why don't you make something like this? So you always get a refernce back to your object in the ListView and bind the Person to your View.
public class PersonDetailViewModel
{
public Person Person { get; }
public PersonDetailViewModel(Person person)
{
Person = person;
}
}
Create the PersonDetailViewModel.
PersonDetailViewModel personDetailView = new PersonDetailViewModel(value);
personDetailView.Show();
Or (if you subscribe to "DoubleCLick event" for example)
PersonDetailViewModel personDetailView = new PersonDetailViewModel(listBox.SelectedItem as Person);
personDetailView.Show();
I have never used the Messenger before.

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.

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

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);
}

Communication between 2 ListBoxes in WPF

I want to build a WPF backend app for a shop. And one view should contain 2 listboxes. 1 for the items which one can buy and 1 for the categories.
I want to grey out items based on selection. Now more details:
So far my view model has an ObservableCollection<ShopItem>
and the class ShopItem has a price, title and a list of Categories
I want to bind ShopItems to 1 ListBox and the Distinct Category to another 2nd ListBox
Since a ShopItem can have multiple Categories I want to gray out all other categories beside the one that belong to the Selected ShopItem. So selection in my first listbox should control appereance in my 2nd listbox.
On the other hand side, when I select a category I would like to gray out all other ShopItems beside the ones that belong to that category. So again listbox 2 should also affect appereance in listbox 1.
With "grayed out" I mean the items should have another style.
I saw something about MultiTrigger that can swap out Template Styling based on conditions.
I am not sure if I can just bind my ObservableCollection<ShopItem> or would need to have two lists here. Do I need some pub/sub between the two lists. I would like to avoid to foreach over all elements in viewmodel each selection changes, any thoughts here?
I'm scratching my head how to solve this right now. Any suggestions would be great...
I am not sure you can get away with iterating over a collection as to change the list to grey out then each item in the list must notify of the change. The following is an example of how you could do it. Where there is IsSelected you can define a ValueConverter to change the font colour.
class ViewModel : ViewModelBase
{
//displayed on the first list
public ObservableCollection<ShopItemViewModel> Shops { get; private set; }
//displayed on the second list
public ObservableCollection<CategoryViewModel> AllCategories { get; private set; }
//when the user clicks an item on the first list
private ShopItemViewModel _selectedShop;
public ShopItemViewModel SelectedShop
{
get { return _selectedShop; }
set
{
_selectedShop = value;
RaisePropertyChanged("SelectedShop");
foreach (CategoryViewModel cat in AllCategories)
cat.Refresh();
}
}
//when the user clicks an item on the second list
private CategoryViewModel _selectedCat;
public CategoryViewModel SelectedCategory
{
get { return _selectedCat; }
set
{
_selectedCat = value;
RaisePropertyChanged("SelectedCategory");
foreach (ShopItemViewModel shops in Shops)
shops.Refresh();
}
}
}
class ShopItemViewModel : ViewModelBase
{
public ObservableCollection<CategoryViewModel> Categories { get; private set; }
public ShopItemViewModel(ViewModel vm)
{
_vm = vm;
}
private ViewModel _vm;
public void Refresh()
{
RaisePropertyChanged("IsSelected");
}
public bool IsSelected
{
get
{
if (_vm.SelectedCategory != null)
{
return Categories.Contains(_vm.SelectedCategory);
}
return true;
}
}
}
class CategoryViewModel : ViewModelBase
{
public CategoryViewModel(ViewModel vm)
{
_vm = vm;
}
private ViewModel _vm;
public string Title { get; set; }
public void Refresh()
{
RaisePropertyChanged("IsSelected");
}
public bool IsSelected
{
get
{
if (_vm.SelectedShop != null)
{
return _vm.SelectedShop.Categories.Contains(this);
}
return false;
}
}
}

Resources