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}"
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">
My CheckBox Command does not work. I want to pass the SelectedItem of the ListView to the Command when the SelectedItem is checked or unchecked, but the Command does not execute at all. I also suspect my CommandParameter is not configured correctly?
I am pretty sure the problem is because the CheckBox is within a ListView DataTemplate.
Can someone show me how to set this up? I tried to follow examples I found, but nothing seems to work. thanks.
XAML
<ListView x:Name="lvReferralSource" ItemsSource="{Binding ReferralSourceTypeObsCollection}" Style="{StaticResource TypeListViewStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<CheckBox x:Name="ckbReferralIsChecked" Content="{Binding Value}" IsChecked="{Binding Active}" Style="{StaticResource CheckBoxStyleBase2}"
Command="{Binding CheckBoxIsChecked}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=SelectedItem}">
</CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CODE
private ICommand _CheckBoxIsChecked;
public ICommand CheckBoxIsChecked
{
get
{
if (_CheckBoxIsChecked == null)
{
_CheckBoxIsChecked = new RelayCommand<object>(ExecuteCheckBoxIsChecked, CanExecuteCheckBoxIsChecked);
}
return _CheckBoxIsChecked;
}
}
public bool CanExecuteCheckBoxIsChecked(object parameter)
{
return true;
}
public void ExecuteCheckBoxIsChecked(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
if (parameter != null)
{
//Do Stuff...
}
Mouse.OverrideCursor = Cursors.Hand;
}
Your command should get executed provided that the CheckBoxIsChecked property belongs to the data object where the Value and Active properties are defined.
If it belongs to the view model, you could bind to it using a RelativeSource:
<CheckBox x:Name="ckbReferralIsChecked" Content="{Binding Value}" IsChecked="{Binding Active}"
Style="{StaticResource CheckBoxStyleBase2}"
Command="{Binding DataContext.CheckBoxIsChecked, RelativeSource={RelativeSource AncestorType=ListView}}"
CommandParameter="{Binding}">
So I have an application that's setup like this.
my MainView.xaml
<ItemsControl ItemsSource="{Binding CardViewModel.Users}"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultEffectDataTemplate="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserCard/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the ViewModel
class BaseViewModel : ObservableObject
{
public CardViewModel CardViewModel { get; set; } = new CardViewModel();
}
This works fine it displays two UserCards which is my UserControl in the ItemsControl which is exactly what it should be doing and it also binds the Text properties to what it needs to.
<Grid Style="{StaticResource UserCardStyle}">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit"/>
<MenuItem Header="Remove"
Command="{Binding BaseViewModel.CardViewModel.command}"/>
</ContextMenu>
</Grid.ContextMenu>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Width="75"
HorizontalAlignment="Left"
Column="0">
<Ellipse Width="50"
Height="50"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="10">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding Avatar}"/>
</Ellipse.Fill>
</Ellipse>
</Grid>
<Grid Column="1">
<TextBlock Text="{Binding Description}"
TextWrapping="Wrap"
Width="180"
VerticalAlignment="Top"
Margin="10"
FontFamily="Consolas"/>
<TextBlock Width="100"
Height="20"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Margin="5"
Text="{Binding Name}"
FontFamily="Consolas"/>
<TextBlock Width="100"
Height="20"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Text="{Binding Id}"
FontFamily="Consolas"/>
</Grid>
</Grid>
However I wanted to add a DataContext as you can see at the top but I have no idea how to bind the DataContext of the UserControl to something else so that I can create commands for the ContextMenu MenuItems
I did setup a RelayCommand that works on the ViewModel because I have the DataContext set properly there.
Right now the UserCard datacontext inherits from it's parent in the MainWindow view which makes it to where the DataContext for the properties are all from User
public class User : ObservableObject
{
public ImageSource Avatar { get; set; }
public string Description { get; set; }
public int Id { get; set; }
}
I want to be able to either create a ViewModel for that specific control and add commands to that or so that I can bind it to the CardViewModel and still display the data from the Users collection
public CardViewModel()
{
/*
* Commands
*/
command = new RelayCommand(o => LoadImage(), o => true);
AddUser = new RelayCommand(u => DisplayUserBuilder(), u => true);
Users = new ObservableCollection<User>();
Users.Add(new User
{
Name = "User",
Description = "A description",
Id = 0
});
Users.Add(new User
{
Name = "User1",
Description = "Super nice description",
Id = 1
});
If I set the DataContext in the CodeBehind like this, the command works fine but then I can't see any text
public partial class UserCard : UserControl
{
public UserCard()
{
InitializeComponent();
this.DataContext = new BaseViewModel();
}
}
I believe you need something like this :
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit"/>
<MenuItem Header="Remove"
Command="{Binding DataContext.CardViewModel.command, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type MainView}}"/>
</ContextMenu>
</Grid.ContextMenu>
This is what I ended up doing, adding a reference to the ViewModel and then setting it.
<Grid.ContextMenu>
<ContextMenu >
<ContextMenu.DataContext>
<dad:BaseViewModel/>
</ContextMenu.DataContext>
<MenuItem Header="Edit"/>
<MenuItem Header="Remove"
Command="{Binding CardViewModel.command}"/>
</ContextMenu>
</Grid.ContextMenu>
If someone knows a "better" or "cleaner" way of doing the same thing please let me know since I am trying to develop as a developer, no pun intended.
My class is has a ObservableCollection of my viewmodel class and I set the itemsource of the Itemcontrol in xaml as below
<ItemsControl ItemsSource="{Binding ConditionItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Background="#FFD0D7EB">
<StackPanel>
<Button Content="Delete" HorizontalAlignment="Right" Width="180" Margin="0,0,12,10" Command="{Binding DeleteItem}" CommandParameter="{Binding}">
</Button> </StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
For some reason my DeleteItem is never called.
private RelayCommand _DeleteRule;
private void DoDeleteRule(object item)
{
if (item != null)
{
MessageBox.Show("in del");
}
}
public ICommand DeleteItem
{
get
{
if (_DeleteRule == null)
_DeleteRule = new RelayCommand(o => DoDeleteRule(o));
return _DeleteRule;
}
}
Am I doing anything wrong in xaml?
The ItemsControl is bound using {Binding ConditionItems}, so it expects the DeleteItem command to be inside the subitems of that list. I guess this is not the case, the DeleteItem exists on the ViewModel.
You could bind to the DataContext of the Window for example, where you can find the DeleteItem command. Or create a proxy element.
I found it. My xaml should be
<Button Content="Delete" Command="{Binding DataContext.DeleteItem,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}">
</Button>
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);
}