WPF: bind my Checkbox IsChecked event to my model - wpf

So i have this ListView with ItemSource of my object and ListViewItem:
<GridViewColumn Header="Select" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding SelectedInterfaceCommand}"
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}">
</CheckBox>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
ViewModelBase
public ViewModelBase()
{
SelectInterfaceCommand = new SelectedInterfaceCommand(this);
}
And i try to catch my CheckBox IsChecked event inside my SelectedInterfaceCommand Command:
public class SelectedInterfaceCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public ViewModelBase ViewModel { get; set; }
public SelectedInterfaceCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
}
}
And my CanExecute and Execute never called.
I also try to this approach:
Inside my Object class i have this Property:
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
OnStaticPropertyChanged();
}
}
XAML
<GridViewColumn Header="Select" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox
<CheckBox.IsChecked>
<Binding Path="IsSelected" RelativeSource="{RelativeSource AncestorType={x:Type ListViewItem}}"/>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
And again this IsSelected setter never called.

Related

Bind ListViewItem ContextMenu MenuItem Command to ViewModel of the ListView's ItemsSource

I have a ListView of Expanders. Each Expander (representing a database table) will have items under it, in another ListView. I want to Right-Click and have an "Edit" option on the innermost items, which represent records in the corresponding database table.
There is an ICommand named 'Edit' in my MainEditorViewModel. The Datacontext in which this command resides is the same as that of the outermost ListView named "TestGroupsListView"
Here is the XAML markup for the ListView of Expanders. The outermost ListView I've named for referencing in the binding via ElementName for the MenuItem's Binding:
<ListView Name="TestGroupsListView" ItemsSource="{Binding TestGroups}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Style="{StaticResource MaterialDesignExpander}" >
<Expander.Header>
<Grid MaxHeight="50">
<TextBlock Text="{Binding Name}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Add..." Command="{Binding Add}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</Expander.Header>
<ListView ItemsSource="{Binding Records}" Style="{StaticResource MaterialDesignListView}" Margin="30 0 0 0">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit"
Command="{Binding ElementName=TestGroupsListView, Path=DataContext.Edit}"
CommandParameter="{Binding }"/>
</ContextMenu>
</Grid.ContextMenu>
<Button Content="{Binding RecordName}" Command="{Binding ElementName=TestGroupsListView, Path=DataContext.Edit}"/>
<!--<TextBlock Text="{Binding RecordName}" AllowDrop="True"/>-->
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I am able to bind a button in the DataTemplate to 'Edit' successfully, but when I attempt to bind the MenuItem's Command to 'Edit', nothing happens. Why might this be that the button command binding works using ElementName but the same binding in the ContextMenu doesn't?
I think it will be better to use the context menu globally for ListView and globally for each Child ListView. Ok, here is my solution:
<ListBox ItemsSource="{Binding Groups}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Add..." Command="{Binding Add}"/>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}">
<ListView ItemsSource="{Binding Records}" SelectedItem="{Binding SelectedRecord}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" Command="{Binding Edit}" IsEnabled="{Binding CanEdit}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And for better understanding code behind:
public class GroupsVM : ViewModelBase
{
public ICommand Add
{
get => null; //Command implementation
}
public ObservableCollection<GroupVM> Groups { get; set; } = new ObservableCollection<GroupVM>()
{
new GroupVM { Name = "First" },
new GroupVM { Name = "Second" },
new GroupVM { Name = "Third" }
};
}
public class GroupVM : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public ICommand Edit
{
get => null; //Command implementation
}
public bool CanEdit => SelectedRecord != null;
public ObservableCollection<RecordVM> Records { get; set; } = new ObservableCollection<RecordVM>()
{
new RecordVM { Name="Record1" },
new RecordVM { Name="Record2" },
new RecordVM { Name="Record3" }
};
private RecordVM _selectedRecord = null;
public RecordVM SelectedRecord
{
get => _selectedRecord;
set
{
_selectedRecord = value;
OnPropertyChanged();
OnPropertyChanged("CanEdit");
}
}
}
public class RecordVM : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
}

Cant bind a property as enum to combobox in wpf mvvm

I have the following scenario:
Problem : Cant bind a property as enum to combox in wpf mvvm ?
How can I bind this enum to a combobox?
1.I have an enum.
public enum RankType
{
StringValue1,
StringValue2,
StringValue3
}
2.I Have a property as enum in myclass :
[DefaultValue(RankType.StringValue1)]
[ConvertUsing(typeof(EnumTypeConverter<RankType>))]
public RankType Rank { set; get; }
ConvertUsing Class,It does the conversion for me:
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class ConvertUsingAttribute:Attribute
{
private TypeConverter _converter = null;
public TypeConverter Converter
{
get
{
if (_converter == null)
_converter = (TypeConverter)System.Activator.CreateInstance(TypeOfConverter);
return _converter;
}
}
public Type TypeOfConverter
{
get;
private set;
}
public ConvertUsingAttribute(Type converterType)
{
this.TypeOfConverter = converterType;
}
}
EnumTypeConverter Class:
public class EnumTypeConverter<T>:TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value != null && value is string)
{
return Enum.Parse(typeof(T), (string)value);// int.Parse(((string)value).Trim());
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (value != null && destinationType == typeof(string))
{
return ((T)value).ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
4.In ViewModel
private MilitaryRankType _selectedRankType;
public RankType SelectedRankType
{
get { return _selectedRankType; }
set
{
_selectedRankType = value;
NotifyPropertyChanged(nameof(RankTypes));
}
}
private RankType[] _rankTypes;
public RankType[] RankTypes
{
get
{
return _rankTypes ??
(_rankTypes =Enum.GetValues(typeof(RankType)).Cast<RankType>().ToArray());
}
}
5.In View
<ComboBox ItemsSource="{Binding RankTypes, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" SelectedItem="{Binding SelectedRankType}" />
6.I used the listview in xaml
<ListView ItemsSource="{Binding EmployeesList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="3">
<ListView.View>
<GridView>
<GridViewColumn Width="150" Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=NationalId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="150" Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=CardId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="150" Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
**<ComboBox SelectedItem="{Binding SelectedRankType}" />**
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="150" Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="150" Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=LastName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnValidationError=True,ValidatesOnDataErrors=True,ValidatesOnExceptions=True}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Honestly, I don't really know what went wrong in your code, but I can give you a working example without any fancy converter stuff:
Suppose you have the following window-content:
<Grid x:Name="grid1">
<ComboBox ItemsSource="{Binding SelectableRanks}" SelectedItem="{Binding SelectedRank}"/>
</Grid>
And you initialize the datacontext in the window constructor:
public MainWindow()
{
InitializeComponent();
grid1.DataContext = new RankSelectionVM();
}
And this is your viewmodel:
public class RankSelectionVM
{
private RankType _SelectedRank;
public RankType SelectedRank
{
get { return _SelectedRank; }
set { _SelectedRank = value; }
}
private RankType[] _rankTypes;
public RankType[] SelectableRanks
{
get
{
return _rankTypes ??
(_rankTypes = Enum.GetValues(typeof(RankType)).Cast<RankType>().ToArray());
}
}
}
public enum RankType
{
StringValue1,
StringValue2,
StringValue3
}
It doesn't have any INotifyPropertyChanged or any converters attached, but place a breakpoint in the SelectedRank setter - it should break when you select a value. If this code works for you, you have to find what you did differently to get a not-working code in your project. Otherwise you may have some very strange issue that needs special care.

WPF commands CanExecute validation inside a template

I have a nested datagrid where I have + and - buttons that are bound to RelayCommands that add a new row or delete the current one respectively. The minus button command's CanExecute logic is supposed to disable the current row's minus button if only one item is left in its category.
The problem is that it disables all minus buttons in all categories because of its template nature.
Image
How can this be mitigated?
Here's the code.
XAML
<Grid>
<DataGrid x:Name="dataGrid1"
ItemsSource="{Binding DataCollection}"
SelectedItem="{Binding dataCollectionSelectedItem, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="false" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Item/Price" Width="*">
<DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<DataGrid x:Name="dataGridItem"
ItemsSource="{Binding Items}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.itemsSelectedItem, Mode=TwoWay}"
Background="Transparent"
HeadersVisibility="None"
AutoGenerateColumns="False"
CanUserAddRows="false" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Width="*"/>
<DataGridTextColumn Binding="{Binding Price}" Width="50"/>
<DataGridTemplateColumn Header="Button">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.AddItem }" Width="20" Height="20">+</Button>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteItem }" Width="20" Height="20">-</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Category" Binding="{Binding Category}" Width="Auto"/>
<DataGridTemplateColumn Header="Buttons">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.AddCategory}" Width="20" Height="20">+</Button>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteCategory}" Width="20" Height="20">-</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
C#
public class Item
{
public string Name { get; set; }
public int Price { get; set; }
}
public class DataTable
{
public ObservableCollection<Item> Items { get; set; }
public string Category { get; set; }
}
public class RelayCommand : ICommand
{
private Action<object> executeDelegate;
readonly Predicate<object> canExecuteDelegate;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new NullReferenceException("execute");
executeDelegate = execute;
canExecuteDelegate = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, null) { }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return canExecuteDelegate == null ? true : canExecuteDelegate(parameter);
}
public void Execute(object parameter)
{
executeDelegate.Invoke(parameter);
}
}
public class ViewModel
{
public ObservableCollection<DataTable> DataCollection { get; set; }
public DataTable dataCollectionSelectedItem { get; set; }
public Item itemsSelectedItem { get; set; }
public RelayCommand DeleteCategory { get; private set; }
public RelayCommand AddCategory { get; private set; }
public RelayCommand DeleteItem { get; private set; }
public RelayCommand AddItem { get; private set; }
public ViewModel()
{
DataCollection = new ObservableCollection<DataTable>
{
new DataTable() {
Items = new ObservableCollection<Item> {
new Item { Name = "Phone", Price = 220 },
new Item { Name = "Tablet", Price = 350 },
},
Category = "Electronic gadgets" },
new DataTable() {
Items = new ObservableCollection<Item> {
new Item { Name = "Teddy Bear Deluxe", Price = 2200 },
new Item { Name = "Pokemon", Price = 100 },
},
Category = "Toys" }
};
DeleteItem = new RelayCommand(innerDeleteItem, canUseDeleteItem);
AddItem = new RelayCommand(innerAddItem, canUseAddItem);
}
public void innerDeleteItem(object parameter)
{
var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
if (DataCollection[collectionIndex].Items.Count != 1)
{
DataCollection[collectionIndex].Items.Remove(itemsSelectedItem);
CollectionViewSource.GetDefaultView(DataCollection).Refresh();
}
}
public bool canUseDeleteItem(object parameter)
{
var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
if ((dataCollectionSelectedItem != null) && (DataCollection[collectionIndex].Items.Count == 1))
{
return false;
}
else return true;
}
public void innerAddItem(object parameter)
{
var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
var itemIndex = DataCollection[collectionIndex].Items.IndexOf(itemsSelectedItem);
Item newItem = new Item() { Name = "Item_Name", Price = 0 };
DataCollection[collectionIndex].Items.Insert(itemIndex + 1, newItem);
CollectionViewSource.GetDefaultView(DataCollection).Refresh();
}
public bool canUseAddItem(object parameter)
{
return true;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel newViewModel = new ViewModel();
this.DataContext = newViewModel;
}
}
You're binding your two Commands to Window's Data Context, and it should bind to DataGrid's Data Context.
Change your xaml to:
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.AddItem }" Width="20" Height="20">+</Button>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.DeleteItem }" Width="20" Height="20">-</Button>
</StackPanel>
I have eventually set the button's CanExecute to always return true and styled the button with a custom trigger that disables it when Items.Count turns 1. Perhaps there are more elegant solutions but at least this one works for me.
<Button Content="-"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteItem }"
Width="20" Height="20">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=Items.Count }" Value="1">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

MouseDoubleClick on ListItem with MVVM in WPF

I want to show dialog when i double click any of the list items. But the flow of the program never go in the ShowTextCommand property. I get the list of the names (that works fine) but I can't get the dialog. This is my XAML:
<ListView ItemsSource="{Binding List}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding ShowTextCommand, UpdateSourceTrigger=PropertyChanged}"></MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
this is my Command class:
public class EnterTextCommand : ICommand
{
public EnterTextCommand(TekstoviViewModel vm)
{
ViewModel = vm;
}
private TekstoviViewModel ViewModel;
#region ICommand interface
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
// return ViewModel.CanExecute;
}
public void Execute(object parameter)
{
ViewModel.EnterText();
}
#endregion
}
and the view model
private ICommand command;
public ICommand ShowTextCommand
{
get
{
if (command == null)
command = new EnterTextCommand(this);
return command;
}
internal void EnterText()
{
MessageBox.Show("Event Success");
}
Can someone help ?
Your DataTemplate can't find the command, specify the full path to it using ElementName Binding
<ListView ItemsSource="{Binding List}" x:Name="MainList">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.ShowTextCommand, UpdateSourceTrigger=PropertyChanged,ElementName=MainList}"></MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

SelectAll In Datagrid

I found a nice solution for SelectAll Checkboxes in a datagrid using XAML only:
<DataGrid x:Name="TestGrid" Tag="false">
<DataGrid.Resources>
<DataTemplate x:Key="HeaderCheckbox">
<CheckBox Name="SelectAll" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=Tag, Mode=TwoWay}" />
</DataTemplate>
<DataTemplate x:Key="ItemCheckbox">
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=Tag, Mode=OneWay}" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn HeaderTemplate="{StaticResource HeaderCheckbox}" CellTemplate="{StaticResource ItemCheckbox}" />
<DataGridTextColumn Binding="{Binding FirstName}" />
</DataGrid.Columns>
</DataGrid>
Source: Complete XAML Solution For SelectAll In Datagrid
But it's my question... In above example, the ItemCheckbox is bound to tag property of Datagrid, then how do I to bind the ItemCheckbox to my data field inclusive?
Easiest solution I can see it to use properties in your view model instead of Tag. First in class that holds items create SelectAll property that will update accordingly all items when changed:
public class MyItemCollection : INotifyPropertyChanged
{
private readonly ObservableCollection<MyItem> _items;
public ICollection<MyItem> Items { get { return _items; } }
private bool _selectAll;
public bool SelectAll
{
get { return _selectAll; }
set
{
if (_selectAll != value)
{
_selectAll = value;
OnPropertyChanged("SelectAll");
foreach (var item in _items) item.IsSelected = value;
}
}
}
}
Then add IsSelected property to your item. It will be updated either by SelectAll property or CheckBox in DataGrid
public class MyItem : INotifyPropertyChanged
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
}
and then update your binding to point to new properties:
<DataGrid x:Name="TestGrid" ItemsSource="{Binding Items}">
<DataGrid.Resources>
<DataTemplate x:Key="HeaderCheckbox">
<CheckBox Name="SelectAll" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.SelectAll}" />
</DataTemplate>
<DataTemplate x:Key="ItemCheckbox">
<CheckBox IsChecked="{Binding Path=IsSelected}" />
</DataTemplate>
</DataGrid.Resources>
</DataGrid>

Resources