In this listbox i display contact names.
<ListBox x:Name="Items" Margin="36,38,78,131">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="lol" Text="{Binding Path=ContactName}" Style="{StaticResource PhoneTextSmallStyle}"
Width="Auto" TextAlignment="Center" FontWeight="Bold" Foreground="White" VerticalAlignment="Bottom" TextWrapping="Wrap"/>
<Button x:Name="ShowName">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="delete" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I get contacts from local DB
public List<FBContacts> listContactDatas { get; set; }
Items = new BindableCollection<FBContacts>();= new BindableCollection<FBContacts>();
public void GetContacts()
{
using(MyDataContext mydb = new MyDataContext(DBConnectionstring))
{
var items = from ContactsList Name in mydb._contacts select Name;
foreach (var toDoItem in items)
{
Items.Add(new FBContacts()
{
ContactName = toDoItem.Name
});
}
}
}
user can delete any contact if he press button.
public void delete()
{
Items.RemoveAt(/* index*/);
}
so how i can get index of choosen contact?
It is easier if you pass the clicked FBContacts to delete method :
<Button x:Name="ShowName">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="delete">
<cal:Parameter Value="{Binding}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Then you can remove by FBContacts object instead of index :
public void delete(FBContacts item)
{
Items.Remove(item);
}
Bind the currently selected item's index to a separate property:
<ListBox x:Name="Items" SelectedIndex="{Binding SelectedListIndex}" Margin="36,38,78,131">
Of course, SelectedListIndex must be defined as property of type int that fires PropertyChanged in the Viewmodel.
Then, you can easily access the selected item's index everywhere within the Viewmodel:
public void delete()
{
Items.RemoveAt(SelectedListIndex);
}
Related
I am working on ListView in WPF, I want the ListView to get ItemLists from a ViewModel but I am getting the following error
Cannot set properties on property elements
xaml code:
<ListView ItemsSource="{Binding MenuItems}" Name="mainSideMenuList" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Background="#FF284593" Foreground="#FF3457D1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction
Command="{Binding Command}"
CommandParameter="{Binding ElementName=mainSideMenuList, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.ItemTemplate Height="60">
<DataTemplate>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="{Binding IconKind}" Width="25" Height="25" Margin="10" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="10 10" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
private readonly ItemHandler _itemHandler;
private ICommand _command;
public MainWindowViewModel() {
_itemHandler = new ItemHandler();
_itemHandler.Add(new Item("Settings", PackIconKind.Settings));
_itemHandler.Add(new Item("Products", PackIconKind.FoodForkDrink));
_itemHandler.Add(new Item("Tickets", PackIconKind.Ticket));
_itemHandler.Add(new Item("Entities", PackIconKind.Table));
_itemHandler.Add(new Item("Accounts", PackIconKind.Calculator));
_itemHandler.Add(new Item("Inventory", PackIconKind.Database));
_itemHandler.Add(new Item("Printing", PackIconKind.Printer));
_itemHandler.Add(new Item("Reports", PackIconKind.FileAccount));
_itemHandler.Add(new Item("Automation", PackIconKind.Calculator));
_itemHandler.Add(new Item("Users", PackIconKind.User));
}
public List < Item > MenuItems {
get {
return _itemHandler.MenuItems;
}
}
public ICommand Command {
get {
return _command ? ? (_command = new RelayCommand(x => {
DoStuff(x as Item);
}));
}
}
private void DoStuff(Item item) {
MessageBox.Show(item.Name + " element clicked");
}
What am I doing wrong and how can I resolve?
The problem is that the ListView.ItemTemplate is of type DataTemplate which is not a FrameworkElement so it can not have any Height or Widthor anything else which has to do with UI-Elements. It can only have one Content and this is a DataTemplate which can hold one FrameworkElement, mostly a Panel. These element then can have a height or width or any UI-Element property.
Therefore remove the Height of <ListView.ItemTemplate Height="60"> and set it to the <StackPanel Orientation="Horizontal">
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)));
}
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 this goto functionality in my DataGrid. This functionality I would like to keep out of my ViewModel and out of code-behind, so the following attachment could be perfect, however...
The user enters a line(item) number then when the user clicks the GotoButton it brings the item into view.
<Grid>
<TextBox x:Name="GotoTextbox" Text="{Binding GotoLineNumber, UpdateSourceTrigger=PropertyChanged}" />
<Button Name="GotoButton" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<helpers:TargetedTriggerActionGotoButton TargetObject="{Binding ElementName=GenericDataGrid}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
Here is the TargetedTriggerAction class.
public class TargetedTriggerActionGotoButton : TargetedTriggerAction<DataGrid>
{
protected override void Invoke(object parameter)
{
this.Target.SelectedGridItem = GotoLineNumber - 1;
this.Target.SelectedGridIndex = GotoLineNumber.GetValueOrDefault() - 1;
}
}
I would like to somehow pass the text in from GotoTextbox, is there some binding I can do?. How could I achieve this?
As we spoke in comments
to allow parameters to be passed in, one should implement additional property in your TargetedTriggerAction
public class TargetedTriggerActionGotoButton : TargetedTriggerAction<DataGrid>
{
protected override void Invoke()
{
this.Target.SelectedGridItem = GotoLineNumber - 1;
this.Target.SelectedGridIndex = GotoLineNumber.GetValueOrDefault() - 1;
}
//property used as parameter
public object Parameter {get;set;}
}
and then in your xaml
<Grid>
<TextBox x:Name="GotoTextbox" Text="{Binding GotoLineNumber, UpdateSourceTrigger=PropertyChanged}" />
<Button Name="GotoButton" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<helpers:TargetedTriggerActionGotoButton TargetObject="{Binding ElementName=GenericDataGrid}" Parameter="{Binding ElementName="/*desiredName*/",Path="/*neededValue*/"}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
I want to set command parameter to currently selected item on a ListBox.
XAML:
<!--<ListBox ItemsSource="{Binding Places}" SelectedItem="{Binding SelectedPlace, Mode=TwoWay}">-->
<ListBox ItemsSource="{Binding Places}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding ListBoxClick}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
(...)
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C# (part of ViewModel code exposing the ListBoxClick command)
public RelayCommand ListBoxClick { get; set; }
ListBoxClick = new RelayCommand((o) => {
//model is always null
var model = o as BasicModel;
SelectedPlace = model;
});
I added appropriate references, and namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
The problem is that in action called by RelayCommand object the o parameter is always null.
UPDATE
C# code for SelectedPlace property
public BasicModel SelectedPlace {
get {
return _selectedPlace;
}
set {
_selectedPlace = value;
RaisePropertyChanged("SelectedPlace");
}
}
When I use this:
<ListBox ItemsSource="{Binding Places}" SelectedItem="{Binding SelectedPlace, Mode=TwoWay}">
everything works fine if I click ListBoxItem for the first time, but when I click on a selected ListBoxItem nothing happens, because selection doesn't change. I need to be able to detect item click in both situations.
I figured out an ugly way to achieve my goal.
XAML
<ListBox ItemsSource="{Binding Places}" SelectedItem="{Binding SelectedPlace, Mode=TwoWay}">
C# (ViewModel)
private bool _placeSelected;
public BasicModel SelectedPlace {
get {
return _selectedPlace;
}
set {
_placeSelected = true;
_selectedPlace = value;
RaisePropertyChanged("SelectedPlace");
}
}
ListBoxClick = new RelayCommand((o) => {
if (!_placeSelected) {
SelectedPlace = _selectedPlace;
}
else {
_placeSelected = false;
}
});
This way RaisePropertyChanged("SelectedPlace"); will be called in both cases.
Try this:
<ListBox ItemsSource="{Binding Places}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding ListBoxClick}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType={
x:Type ListBox}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
(...)
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If that doesn't work, try changing the Binding to this:
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource Self}}"
UPDATE >>>
Oh sorry, I didn't see that you were using Windows Phone 7. As an alternative, try adding a property into your code behind/view model that binds to the ListBox.SelectedItem property:
<ListBox ItemsSource="{Binding Places}" SelectedItem="{Binding SelectedItem}" ... />
Then you should be able to do this:
ListBoxClick = new RelayCommand(() => {
SelectedPlace = SelectedItem;
});
UPDATE 2 >>>
I don't know if Windows Phone 7 supports the Binding.ElementName property, but if it does, try this:
<ListBox Name="ListBox" ItemsSource="{Binding Places}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding ListBoxClick}"
CommandParameter="{Binding SelectedItem, ElementNameListBox}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
(...)
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>