Below I have a ListView with cities, when city is checked then the second list (with persons) must displays persons only from this city.
<ListView ItemsSource="{Binding Cities}">
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<CheckBox IsChecked="{Bidning IsChecked}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the second list view with persons:
<ListView ItemsSource="{Binding Persons}">
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<Label Content="{Bidning Surname}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Below you can see view model with the lists which are binded with ListViews.
public ListCollectionView Cities
{
get { return _cities; }
set
{
_cities = value;
RaisePropertyChanged();
}
}
public ListCollectionView Persons
{
get { return _persons; }
set
{
_persons= value;
RaisePropertyChanged();
}
}
The question is how to handle when checkbox which is inside Cities ListView is checked by the user, so I can refresh the Persons list and return Persons only from checked City? I do not know what should I write in View Model, so I do not know how to handle when ListView children property has changed changed.
you have to add public City SelectedCity property to the view model and bind the selected city value in the view
<ListView ItemsSource="{Binding Cities}" SelectedItem="{Binding SelectedCity">
in the SelectedCity setter you can update the Persons collection like
class PersonsByCitiesViewModel
{
public City SelectedCity
{
get{}
set
{
Persons = LoadPersons(value);
}
}
public ListCollectionView LoadPersons(City selected)
{
var persons = LoadAll();
if (selected != null)
persons = persons.Where(_ => _.CityId = selected.CityId);
return new ListCollectionView(persons);
}
}
Handling SelectedItem is good but only if I want to select just one City from the list.
What If I want to select more than one?
I have tried to handle property IsChecked inside Cities class, but in Cities class I do not have an access to other elements which are in View Model where Cities List and Person list are.
I recommend handling the Checked and Unchecked event of the CheckBox elements.
View:
<ListView ItemsSource="{Binding Cities}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding CityName}" />
<CheckBox IsChecked="{Binding IsChecked}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding DataContext.SelectedCitiesChangedCommand, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding DataContext.SelectedCitiesChangedCommand, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{Binding ShownPeople}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
public List<City> Cities { get; } = new List<City>();
public List<Person> Persons { get; } = new List<Person>();
public ObservableCollection<Person> ShownPeople { get; } = new ObservableCollection<Person>();
public ICommand SelectedCitiesChangedCommand { get; }
public ViewModel()
{
SelectedCitiesChangedCommand = new DelegateCommand(SelectedCitiesChanged);
}
private void SelectedCitiesChanged()
{
var checkedCities = Cities.Where(city => city.IsChecked);
ShownPeople.Clear();
ShownPeople.AddRange(Persons.Where(person => checkedCities.Any(city => city.CityName == person.City)));
}
Related
i have 2 questions
How can i bind a command from MenuItem using Mvvm?
<StackLayout>
<Label Text="{Binding TestText}"/>
<SearchBar TextColor="Black" SearchCommand="{Binding SearchBar}" Text="{Binding TextChanging}"/>
<ListView ItemsSource="{Binding ListOfItems, Mode=TwoWay}" RefreshCommand="{Binding RefreshData}" IsPullToRefreshEnabled="True" IsRefreshing="{Binding IsRefreshing}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextCell TextColor="Black" Text="{Binding MainText}" Detail="{Binding SecondText}" >
<TextCell.ContextActions>
<MenuItem Command="{Binding Path=BindingContext.DeleteCommand}" CommandParameter="{Binding .}" Text="Delete"/>
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public Command DeleteCommand(object sender, EventArgs e)
{
return new Command(() =>
{
});
}
I can't really binding this DeleteCommand using just MVVM.
Question2:
In runtime, my MenuItem look like this:
enter image description here
and i want to look like this:enter image description here
How can i do that?
When you use DeleteCommand to delete a selected item, you need get it and then update the itemsource for the listview.
Here's the code snippet for your reference:
Mode:
public class MyModel
{
public string MainText { get; set; }
}
ViewModel:
public class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<MyModel> ListOfItems { get; set; }
public ICommand DeleteCommand => new Command(Delete);
public MainPageViewModel()
{
ListOfItems = new ObservableCollection<MyModel>()
{
new MyModel(){ MainText = "test1"},
new MyModel(){ MainText = "test2"},
new MyModel(){ MainText = "test3"},
new MyModel(){ MainText = "test4"},
};
}
private void Delete(object obj)
{
//get the item and cast it to Model
var content = obj as MyModel;
ListOfItems.Remove(content);
}
}
Xaml Code:
<StackLayout>
<ListView x:Name="items" ItemsSource="{Binding ListOfItems, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell TextColor="Black" Text="{Binding MainText}" >
<TextCell.ContextActions>
<MenuItem Command="{Binding Path=BindingContext.DeleteCommand , Source={x:Reference Name=items }}" CommandParameter="{Binding .}" Text="Delete"/>
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Tips: Don't forget to add Source={x:Reference Name=items } for the menu item.
Last but not least, bind the View and Model in Code behind or in Xaml:
BindingContext = new MainPageViewModel();
I have a Listbox which is bound to a DataTemplate that has another Listbox on it.
On DataTemplate there is a button that I want to use for adding items to DataTemplate ListBox, but I can't find a solution to do this.
Here is my listbox:
<Button Width="200" Content="Add Question" x:Name="btnAddQuestion" Click="btnAddQuestion_Click"/>
<StackPanel Orientation="Horizontal">
<ListBox Margin="5" x:Name="lvQuestions" ItemTemplate="{StaticResource TemplateQuestionTitle}">
</ListBox>
</StackPanel>
And this is DataTemplate:
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox materialDesign:HintAssist.Hint="Enter question" MinWidth="200" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/>
<Button Content="+" Command="{Binding Source={x:Reference ThisPage},Path=DataContext.Command}" />
</StackPanel>
<ListBox ItemsSource="{Binding MyItems}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
This is code behind on my page:
public partial class UIBuilder:Window
{
private CommandVm _commandVm;
public UIBuilder()
{
InitializeComponent();
_commandVm = new CommandVm();
DataContext = _commandVm;
}
private void btnAddQuestion_Click(object sender, RoutedEventArgs e)
{
lvQuestions.Items.Add(null);
}
}
I have implemented this code on my ViewModel in order to add items to datatemplate ListBox:
public class CommandVm
{
public ObservableCollection<TextBox> MyItems { get; set; }
public CommandVm()
{
MyItems = new ObservableCollection<TextBox>();
Command = new RelayCommand<TextBox>(Execute);
}
private void Execute(TextBox textBox)
{
MyItems .Add(textBox);
}
public ICommand Command { get; set; }
}
I use to catch the Execute() function on button "+" click command, but my code doesn't add any ListBox item.
MyItems is a property of the parent view model which means that you should bind to it like this:
<ListBox ItemsSource="{Binding DataContext.MyItems,
RelativeSource={RelativeSource AncestorType=Window}}" MinHeight="50">
This also means that you are using one single collection of items for all questions. Besides this obvious design flaw, a view model should not contain any TextBox elements. This basically breaks what the MVVM pattern is all about.
What you should do to make this example MVVM compliant is to create a Question class that has a collection of items, e.g.:
public class Question
{
public Question()
{
AddAnswerCommand = new RelayCommand<object>(Execute);
}
private void Execute(object obj)
{
Items.Add(new Answer());
}
public ObservableCollection<Answer> Items { get; }
= new ObservableCollection<Answer>();
public ICommand AddAnswerCommand { get; }
}
public class Answer { }
The window's view model should then have a collection of questions:
public class CommandVm
{
public CommandVm()
{
AddQuestionCommand = new RelayCommand<object>(Execute);
}
public ObservableCollection<Question> Questions { get; }
= new ObservableCollection<Question>();
public ICommand AddQuestionCommand { get; }
private void Execute(object obj)
{
Questions.Add(new Question());
}
}
The view and the bindings could then be defined like this:
<Window.Resources>
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox MinWidth="200" />
<Button Content="+" Command="{Binding AddAnswerCommand}" />
</StackPanel>
<ListBox ItemsSource="{Binding Items}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Width="200" Content="Add Question" Command="{Binding AddQuestionCommand}"/>
<ListBox Margin="5"
ItemsSource="{Binding Questions}"
ItemTemplate="{StaticResource TemplateQuestionTitle}" />
</StackPanel>
This setup lets you add individual elements to each separate question.
I have buttons "ADD" and "DEL", but "DEL" does not work. What is wrong?
count in my ObservableCollection<User> was changed but ListBox does not
sample project: https://github.com/Veselov-Dmitry/MyQuestion
view:
<StackPanel>
<Button Content="ADD"
Command="{Binding AddUsers_OASUCommand}"
CommandParameter="">
</Button>
<ListBox ItemsSource="{Binding Users_OASU}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Login}" />
<Button Content="DEL"
Command="{Binding DelUsers_OASUCommand}"
CommandParameter="{Binding Path=Content,
RelativeSource={RelativeSource Mode=FindAncestor ,
AncestorType={x:Type ListBoxItem}}}">
<Button.DataContext>
<local:ViewModel />
</Button.DataContext>
</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
I set datacontext in constructor MainView
viewvmodel:
class ViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<User> Users_OASU{get; set;}
public ICommand AddUsers_OASUCommand{get; set;}
public ICommand DelUsers_OASUCommand{get; set;}
public ViewModel()
{
Users_OASU = new ObservableCollection<User>(GetUsers());
AddUsers_OASUCommand = new Command<object>(arg => AddUsers_OASUMethod());
DelUsers_OASUCommand = new Command<object>(arg => DelUsers_OASUMethod(arg));
}
private void DelUsers_OASUMethod(object arg)
{
User find = Users_OASU.Where(x => x.Login == (arg as User).Login).FirstOrDefault();
Users_OASU.Remove(find);
}
private void AddUsers_OASUMethod()
{
Users_OASU.Add(new User("52221", "John X."));
}
private List<User> GetUsers()
{
List<User> list = new List<User>();
list.Add(new User("52222", "John W."));
list.Add(new User("52223", "John Z."));
list.Add(new User("52224", "John A."));
list.Add(new User("52225", "John M."));
return list;
}
}
"count in my ObservableCollection was changed but ListBox does not" - you have multiple instances of ViewModel, count was changed, but not in the collection which is displayed
you need to setup DataTemplate correctly to avoid that
first, each Button will get User object for DataContext (it will be provided by ListBox from ItemsSource). You mustn't declare new <Button.DataContext>
second, DelUsers_OASUCommand is declared in a ViewModel class, it is accessible on ListBox level, from DataContext. Change binding path accordingly.
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Login}" />
<Button Command="{Binding DataContext.DelUsers_OASUCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding Path=Content,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Content="DEL" />
</WrapPanel>
</DataTemplate>
additionally I would change DelUsers_OASUMethod to accept User as argument
private void DelUsers_OASUMethod(object arg)
{
Users_OASU.Remove(arg as User);
}
and pass CommandParameter like this:
CommandParameter="{Binding Path=.}"
or the same, but shorter:
CommandParameter="{Binding}"
I've got a question, I have a list of checkbox in combobox box and it looks like this:
<StackPanel Orientation="Vertical" DataContext="{Binding CandidateEntity}">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding DataContext.SkillSetEntities, ElementName=CrudCandidate }"
IsEditable="True" IsReadOnly="True" Text="Umiejętności">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
Now I also have a collection of skillset objects in binded item source (CandidateEntity.SkillSets), now how can I check those checkboxes that are in my collection of skillset objects?
I want to create a edition for CandidateEntity object in form and part of that edition is list of skillset that is represented in combobox.
EDIT:
I have solved problem by adding to skillset model prop:
private bool _isSelected = false;
[NotMapped]
public bool IsSelected
{
get
{
return this._isSelected;
}
set
{
_isSelected = value;
}
}
And then in view model:
private List<SkillSet> GetSkillSets()
{
var skillsetList = this._catalog.SkillSets.ToList();
var candidateSkillsetList = this.CandidateEntity.SkillSets.ToList();
foreach (SkillSet skillset in skillsetList)
{
foreach (SkillSet candidateSkillset in candidateSkillsetList)
{
if (skillset.id == candidateSkillset.id)
{
skillset.IsSelected = true;
}
}
}
return skillsetList;
}
and in checkbox in wpf:
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}"/>
BUT I am pretty sure there must be easier way to handle that, is there?
I am trying to do ListView with ItemTemplate with CheckBox and TextBlock control and bind data to it. However, the list does not display added items.
Here is my XAML code:
<ListView x:Name="WarrantyCategory_ListView" HorizontalAlignment="Stretch" Grid.Row="3" VerticalAlignment="Center" SelectionMode="Single" Header="Category:" FontSize="16" Foreground="#FFC7C7C7" Margin="10,0" ItemsSource="{Binding WarrantyCategories}" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<CheckBox x:Name="Description_CheckBox" IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
<TextBlock x:Name="DescriptionItem_TextBlock" Text="{Binding Category, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is my WarrantyCategory class:
public class WarrantyCategory
{
public bool IsChecked { get; set; }
public string Category { get; set; }
public WarrantyCategory(bool isChecked, string category)
{
IsChecked = isChecked;
Category = category;
}
}
and my Page code behind:
public AddWarrantyDescriptionPage()
{
this.InitializeComponent();
DisplayInformation.AutoRotationPreferences = DisplayOrientations.Portrait;
InitializeCategoryList();
}
private void InitializeCategoryList()
{
WarrantyCategories.Add(new WarrantyCategory(false, "Computers"));
WarrantyCategories.Add(new WarrantyCategory(false, "Mobile"));
WarrantyCategories.Add(new WarrantyCategory(false, "Music"));
WarrantyCategories.Add(new WarrantyCategory(false, "Video"));
}