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>
Related
I am Building a WPF form in which I want to achieve a cascading dropdown inside an ItemsControl. There would be multiple rows and Dropdown2 source is dependent on Dropdown1.
The row gets added when i add an item to FilterData, and the dropdown also populates, but i am not sure how to make a relational database with multiple rows
Here is what I have tried so far
<ItemsControl Grid.Row="1" x:Name="Filter" ItemsSource="{Binding FilterData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10" Orientation="Horizontal" >
<CheckBox IsChecked="{Binding Group}"/>
<ComboBox x:Name="cmbCondition" ItemsSource="{Binding ConditionList, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
DisplayMemberPath="Name" SelectedValuePath="Name" SelectedItem="{Binding ConditionList}" Width="80" Height="23" />
<ComboBox x:Name="cmbType" ItemsSource="{Binding TypeList, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
DisplayMemberPath="Name" SelectedValuePath="Name" SelectedItem="{Binding TypeList}" Width="80" Height="23" />
<ComboBox x:Name="cmbOperator" ItemsSource="{Binding OperatorList, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
DisplayMemberPath="Name" SelectedValuePath="Name" SelectedItem="{Binding OperatorList}" Width="80" Height="23" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public ObservableCollection<FilterData> _FilterData { get; set; }
public ObservableCollection<ConditionList> _ConditionList { get; set; }
public ObservableCollection<TypeList> _TypeList { get; set; }
public IEnumerable<FilterData> FilterData
{
get { return _FilterData; }
}
public IEnumerable<ConditionList> ConditionList
{
get { return _ConditionList; }
}
public IEnumerable<TypeList> TypeList
{
get { return _TypeList; }
}
//Form Load event
Filter.DataContext = this;
AddRow();
private void AddRow()
{
_ConditionList = new ObservableCollection<ConditionList>()
{
new ConditionList() { Name = "AND" },
new ConditionList() { Name = "OR" }
};
_FilterData.Add(new FilterData
{
Group = true,
// Condition = _ConditionList
});
}
// Modal
public class TypeList
{
public string Name { get; set; }
}
public class ConditionList
{
public string Name { get; set; }
}
public class FilterData
{
public bool Group { get; set; }
public ConditionList Condition { get; set; }
}
If you bind the SelectedItem property of the first ComboBox to a source property of type ConditionList, you could populate the source collection for the second ComboBox (TypeList) in the setter of this one.
Make sure that you implement the INotifyPropertyChanged interface and raise the PropertyChanged event for the source collection property that is being set to a new value, e.g.:
private ConditionList _selectedCondition;
public ConditionList SelectedCondition
{
get { return _selectedCondition; }
set
{
_selectedCondition = value;
NotifyPropertyChanged();
//populate the list...
TypeList = new List<TypeList> { ... };
}
}
public IEnumerable<TypeList> TypeList
{
get { return _TypeList; }
private set { _TypeList = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
XAML:
<ComboBox x:Name="cmbCondition" ItemsSource="{Binding ConditionList, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCondition, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Width="80" Height="23" />
<ComboBox x:Name="cmbType" ItemsSource="{Binding TypeList, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" ... />
I need to present a WPF GridView where one column is a Combobox, The user can select one value from the list or enter a new value so I set the IsComboBoxEditable to true but the problem is that if the user types a value that is not in the ItemsSource the Text is blank when the Combobox looses the focus.
Note : I don't want, when a new value is typed , this value to be
added to the ItemsSource. I only need to save it's string value in row
that bounded to it.
I also need DropDownOpened event, to populate it's ItemsSource.
Here is my code:
<telerik:GridViewDataColumn Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<telerik:RadComboBox IsEditable="True" ItemsSource="{Binding Descriptions}" Text="{Binding Description1,Mode=TwoWay}" DropDownOpened="descriptionRadComboBox_DropDownOpened"/>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
</telerik:GridViewDataColumn>
Description1 is string property, and Descriptions is List of string that populate in runtime.(When DropDownOpened Event occurred)
Like you mentioned, your goal is, simply, to "Editable ComboBox".
(And, of course, you don't want to add new Item to ItemsSource)
<telerik:GridViewDataColumn UniqueName="description1" Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description1}"></TextBlock>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
<telerik:GridViewDataColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadComboBox Name="SLStandardDescriptionsRadComboBox" IsEditable="True"
ItemsSource="{Binding DataContext.SLStandardDescriptions, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
DisplayMemberPath="SLStandardDescriptionTitle" DropDownOpened="Description_DropDownOpened">
</telerik:RadComboBox>
</DataTemplate>
</telerik:GridViewDataColumn.CellEditTemplate>
</telerik:GridViewDataColumn>
Codebehinde :
private void RadGridView_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == "description1")
{
RadComboBox combo = e.Cell.ChildrenOfType<RadComboBox>().FirstOrDefault();
if (combo != null)
{
List<Description> comboItems = combo.ItemsSource as List<Description>;
string textEntered = e.Cell.ChildrenOfType<RadComboBox>().First().Text;
bool result = comboItems.Contains(comboItems.Where(x => x.DescriptionTitle == textEntered).FirstOrDefault());
if (!result)
{
comboItems.Add(new Description { DescriptionTitle = textEntered });
combo.SelectedItem = new Description { DescriptionTitle = textEntered };
}
if (_viewModel.AccDocumentItem != null)
{
if (e.Cell.Column.UniqueName == "description1")
_viewModel.AccDocumentItem.Description1 = textEntered;
}
}
}
}
Here is the solution for .net DataGrid control:
<DataGrid ItemsSource="{Binding Path=Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Title}" ></DataGridTextColumn>
<DataGridComboBoxColumn SelectedValueBinding="{Binding ComboItem.ID}" DisplayMemberPath="ComboTitle" SelectedValuePath="ID">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<Setter Property="IsEditable" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Surely you can do this using Telerik DataGrid control as well.
And here is my ViewModel:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
ComboItems = new ObservableCollection<ComboItem>()
{
new ComboItem(){ID=1,ComboTitle="ComboItem1"},
new ComboItem(){ID=2,ComboTitle="ComboItem2"},
new ComboItem(){ID=3,ComboTitle="ComboItem3"}
};
Items = new ObservableCollection<Item>()
{
new Item(){ID=1,Title="Item1",ComboItem=ComboItems[0]},
new Item(){ID=2,Title="Item2",ComboItem=ComboItems[1]},
new Item(){ID=3,Title="Item3",ComboItem=ComboItems[2]}
};
}
public ObservableCollection<Item> Items { get; set; }
public ObservableCollection<ComboItem> ComboItems { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
public ComboItem ComboItem { get; set; }
}
public class ComboItem
{
public int ID { get; set; }
public string ComboTitle { get; set; }
}
I have a table Called IGdaily with Field Trans_Category. I want to bind a DataGridComboBoxColumn displaying enum and binds its int value to datagrid cell Trans_Category.
My Enum
public enum Enm_Purch_Ret : short
{
Purchase = 1,
Sale = 2,
Return = 3
}
Viewmodel Vm_Purchase
public class Vm_Purchase : INotifyPropertyChanged
{
private IGoldEntities db = new IGoldEntities();
public ObservableCollection<IGdaily> Vm_IGdaily { get; set; }
public ObservableCollection<Enm_Purch_Ret> Vm_Enum_P_R { get; set; }
public Vm_Purchase()
{
Vm_IGdaily = new ObservableCollection<IGdaily>();
Vm_Enum_P_R = new ObservableCollection<Enm_Purch_Ret>(Enum.GetValues(typeof(Enm_Purch_Ret)).Cast<Enm_Purch_Ret>().ToList());
}
public ObservableCollection<IGdaily> IGDailys
{
get { return Vm_IGdaily; }
set { Vm_IGdaily = value; NotifyPropertyChanged(); }
}
public ObservableCollection<Enm_Purch_Ret> Enm_Purch_Rets
{
get { return Vm_Enum_P_R; }
set { Vm_Enum_P_R = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In XAML
<Window.DataContext>
<local:Vm_Purchase/>
</Window.DataContext>
<DataGrid x:Name="DG" ItemsSource="{Binding IGDailys}" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="Cell" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding GroupName}" Header="Name" Width="200"/>
<DataGridComboBoxColumn Header="Item/Metal" SelectedValueBinding="{Binding Trans_Category}" SelectedValuePath="{Binding Path=Enm_Purch_Rets, StringFormat='\{0:D\}'}" DisplayMemberPath="Enm_Purch_Ret">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Enm_Purch_Rets, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Enm_Purch_Rets , RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridTextColumn Binding="{Binding Trans_Category}" ClipboardContentBinding="{x:Null}" FontSize="14" Header="Metal Id" Width="100"/>
</DataGrid.Columns>
</DataGrid>
public partial class IGdaily
{
public int GDaily_Id { get; set; }
public int DailyMast_Id { get; set; }
public int ItemGroup_Id { get; set; }
public int Item_Id { get; set; }
public int Trans_Category { get; set; }
}
Please help what is my mistake in binding. I am new to MVVM pattern.
please explain
Thanks
You can't set an int property to a Enm_Purch_Ret value because there is no implicit conversion between these two types.
But you could use a converter class that peforms the conversion for you:
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Enm_Purch_Ret e = (Enm_Purch_Ret)value;
return (int)e;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int i = (int)value;
return (Enm_Purch_Ret)i;
}
}
Usage:
<DataGrid x:Name="DG" ItemsSource="{Binding IGDailys}" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="Cell" >
<DataGrid.Resources>
<local:EnumConverter x:Key="conv" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding GroupName}" Header="Name" Width="200"/>
<DataGridComboBoxColumn Header="Item/Metal"
SelectedItemBinding="{Binding Trans_Category, Converter={StaticResource conv}}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Enm_Purch_Rets, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Enm_Purch_Rets , RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridTextColumn Binding="{Binding Trans_Category}" ClipboardContentBinding="{x:Null}" FontSize="14" Header="Metal Id" Width="100"/>
</DataGrid.Columns>
</DataGrid>
I have a Datagrid in WPF in which first column has checkbox column and last column has buttons.
Initially, I want to make all the buttons disabled and whenever any checkbox is checked then button of that row should get enabled.
checkbox is unchecked then button should be disabled.
Searched a Lot but could not find anything related to this.
I am not using MVVM.. How to do this on the code behind?
Thanks
This is my Xaml Code and I am simply assigning my itemsource on the code behind
<Grid Grid.Row="2" Grid.ColumnSpan="2" Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center" >
<Border BorderThickness="0" Margin="10" CornerRadius="15">
<Border.BitmapEffect>
<DropShadowBitmapEffect />
</Border.BitmapEffect>
<Grid>
<Border x:Name="BDRounded" BorderThickness="0" CornerRadius="15" Background="White"/>
<DataGrid HorizontalAlignment="Left" x:Name="dgrdActors" RowHeight="74" AutoGenerateColumns="False" CanUserAddRows="False"
BorderThickness="1,0,0,0" BorderBrush="#FFD1A251" FontSize="28" Foreground="#DCA566" FontFamily="Helvetica Neue"
CanUserResizeRows="False" AlternatingRowBackground="Linen" AlternationCount="2" Background="#DCA566"
RowHeaderWidth="0" CanUserResizeColumns="False" CanUserSortColumns="False" CanUserReorderColumns="False"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible"
HorizontalGridLinesBrush="#FFD1A251" VerticalGridLinesBrush="#FFD1A251" Height="326"
SelectionMode="Extended" SelectionUnit="FullRow" VirtualizingStackPanel.VirtualizationMode="Standard"
Style="{StaticResource DatagridStyle}">
<DataGrid.Columns>
<DataGridTemplateColumn Width="70" CanUserReorder="False" CanUserResize="False" CanUserSort="False" CellStyle="{StaticResource HitVisibilityCellStyle}" HeaderStyle="{StaticResource HeaderStyle}" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Viewbox Margin="-1">
<!--<CheckBox x:Name="chkboxactors" HorizontalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding IsActorChecked, UpdateSourceTrigger=PropertyChanged}"></CheckBox>-->
<CheckBox x:Name="chkboxActors" HorizontalAlignment="Center" VerticalAlignment="Center"></CheckBox>
</Viewbox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Actor Name(s)" Width="300" Binding="{Binding ActorName}" CanUserReorder="False" CellStyle="{StaticResource CellStyle}" CanUserResize="False" CanUserSort="False" HeaderStyle="{StaticResource HeaderStyle}" IsReadOnly="True">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="HorizontalAlignment" Value="Center"></Setter>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Role(s)" Width="300" Binding="{Binding Role}" CanUserReorder="False" CellStyle="{StaticResource CellStyle}" CanUserResize="False" CanUserSort="False" HeaderStyle="{StaticResource HeaderStyle}" IsReadOnly="True">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="HorizontalAlignment" Value="Center"></Setter>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Width="250" CanUserReorder="False" CanUserResize="False" HeaderStyle="{StaticResource HeaderStyle}" CellStyle="{StaticResource HitVisibilityCellStyle}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button x:Name="btnSelectRole" Content="Select Role" Style="{StaticResource DatagridButtonStyle}"></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=BDRounded}"/>
</DataGrid.OpacityMask>
</DataGrid>
</Grid>
</Border>
</Grid>
Here you go!
There's no need to do the enabling/disabling in the ViewModel, as this can all be done in XAML.
XAML:
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="100">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="false"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked}" Value="true">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel:
public class ViewModel
{
public List<Data> Items { get; private set; }
public ViewModel()
{
Items = new List<Data>
{
new Data(),
new Data(),
new Data()
};
}
}
public class Data : INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get {return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
Edit:
Since you've requested a code-behind implementation, here you go. This works by traversing the visual tree based on the current row that the checkbox was clicked from.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DataGrid AutoGenerateColumns="False" x:Name="MyDataGrid">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Click="CheckBox_Clicked"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="100" x:Name="Button" IsEnabled="false" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Window>
XAML.CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyDataGrid.ItemsSource = new List<string>
{
"test",
"test1",
"test2",
"test3"
};
}
private void CheckBox_Clicked(object sender, RoutedEventArgs e)
{
var checkBox = sender as CheckBox;
if (checkBox != null)
{
var associatedRow = VisualTreeHelper.GetParent(checkBox);
while ((associatedRow != null) && (associatedRow.GetType() != typeof(DataGridRow)))
{
associatedRow = VisualTreeHelper.GetParent(associatedRow);
}
var dataGridRow = associatedRow as DataGridRow;
if (dataGridRow != null)
{
var associatedButton = FindChild(dataGridRow, "Button");
if (associatedButton != null)
{
associatedButton.IsEnabled = checkBox.IsChecked.HasValue ? checkBox.IsChecked.Value : false;
}
}
}
}
public static Button FindChild(DependencyObject parent, string childName)
{
if (parent == null) return null;
Button foundChild = null;
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
var childType = child is Button;
if (!childType)
{
foundChild = FindChild(child, childName);
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
if (frameworkElement != null && frameworkElement.Name == childName)
{
foundChild = (Button)child;
break;
}
}
else
{
foundChild = (Button)child;
break;
}
}
return foundChild;
}
}
If the datagrid is bound to a collection of objects you own (ideally in this case a Facade of a model), then add a IsSelected property to the object that makes up the collection. You can databind your checkbox to that property.
To enable/disable the button, have the model/facade in the collection implement ICommand. You can then use the CanExecute method to enable/disable the button based on the value of IsSelected.
public class User : ICommand, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool IsSelected
{
get
{
return this.isSelected;
}
set
{
this.isSelected = value;
CommandManager.InvalidateRequerySuggested();
this.OnPropertyChanged("IsSelected");
}
}
public bool CanExecute(object parameter)
{
return this.IsSelected;
}
public void Execute(object parameter)
{
// ... Do stuff ...
}
private void RaiseCanExecuteChanged()
{
var handler = this.CanExecuteChanged;
if (handler == null)
{
return;
}
handler(this, new PropertyChangedEventArgs(property));
}
private void OnPropertyChanged(string property)
{
var handler = this.PropertyChanged;
if (handler == null)
{
return;
}
handler(this, new PropertyChangedEventArgs(property));
}
}
Now you bind your checkbox to the IsSelected property. Anytime that the checkbox is selected, the CanExecute method will fire on the class.
Ideally you would use a DelegateCommand class from either MVVMLight or Prism, which have a RaiseCanExecuteChanged() method. This lets you avoid using the CommandManager to requery it.
This can be your DataGrid definition:
<DataGrid x:Name="TestDataGrid" ItemsSource="{Binding source}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="TestBox" Content="Test" IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Click" IsEnabled="{Binding IsChecked}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Pretty simple code behind:
public partial class MainWindow : Window
{
public ObservableCollection<Model> source { get; set; }
public MainWindow()
{
InitializeComponent();
source = new ObservableCollection<Model>();
source.Add(new Model());
source.Add(new Model());
this.DataContext = this;
}
}
This could be your model:
public class Model : DependencyObject
{
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsChecked. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(Model), new PropertyMetadata(false));
}
Or implement INPC inteface:
public class Model : INotifyPropertyChanged
{
private bool _IsChecked;
public bool IsChecked
{
get { return _IsChecked; }
set
{
_IsChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
< DataGridTemplateColumn Header="{ Loc CellSettings_Min}" >
<DataGridTemplateColumn.CellTemplate >
< DataTemplate >
< TextBox Width="100" Text="{ Binding Interval }" >
<TextBox.Style>
< Style TargetType = "TextBox" >
< Setter Property="IsEnabled" Value="false" />
< Style.Triggers >
< DataTrigger Binding = "{ Binding AutoScale }" Value= "True" >
< Setter Property = "IsEnabled" Value = "False" />
< /DataTrigger >
< DataTrigger Binding = "{ Binding AutoScale }" Value="False" >
< Setter Property = "IsEnabled" Value = "True" />
</ DataTrigger >
</ Style.Triggers >
</ Style >
</ TextBox.Style >
</ TextBox >
</ DataTemplate >
</ DataGridTemplateColumn.CellTemplate >
</ DataGridTemplateColumn >
You should use MVVM.DataBinding is convenient .
Xaml
<CheckBox Name="checkbox" IsChecked="{Binding Checked, Mode = TwoWay}" />
<Button IsEnabled="{Binding ButtonEnabled , Mode = TwoWay}" />
C#
In ViewModel
public class ViewMode : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _buttonChecked = false;
public bool ButtonChecked
{
get
{
return _buttonChecked;
}
set
{
if(value == true)
{
_buttonChecked = value;
OnPropertyChanged("ButtonChecked");
}
}
}
private bool _checked;
public bool Checked
{
get
{
return _checked;
}
set
{
if(value == true)
{
_checked= value;
ButtonChecked = value;
OnPropertyChanged("Checked");
}
}
}
[NotifyPropertyChangedInvocator]
private virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I got a mvvm problem with calling methods from data grid in silverlight.
I would like to register a trigger on the property changed event for each row.
Problems:
- Binding a method to another data context (myMVVM) and not to the MyEntity object
- Get as much information as possible like dataItem, call and property and pass it to OnPropertyChanged
Any idea?
This is what i would like to have:
<Grid DataContext="{Binding myMVVM}">
<data:DataGrid ItemsSource="{Binding MyCollection}">
<data:DataGrid.Columns>
<data:DataGridTextColumn Binding="{Binding Text}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PropertyChanged">
<i:CallMethodAction TargetObject="{Binding}" Method="OnPropertyChanged"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</data:DataGridTextColumn>
<data:DataGridTextColumn Binding="{Binding Text2}" />
<data:DataGridTextColumn Binding="{Binding Text3}" />
<data:DataGridTextColumn Binding="{Binding Text4}" />
<data:DataGridTextColumn Binding="{Binding Text5}" />
</data:DataGrid.Columns>
</data:DataGrid>
</Grid>
public class MyMVVM {
public System.Collections.Generic.List<MyEntry> MyCollection { get; set; }
public void OnPropertyChanged(object sender, MyEventArgs ea) {
DataGrid mySender = (DataGrid)sender;
MyEntry dataItem = ea.DataItem;
string propertyName = ea.PropertyName;
}
}
public class MyEntry : System.ComponentModel.INotifyPropertyChanged {
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text {
get { return _text; }
set {
_text = value;
RaisePropertyChangedEvent("Text");
}
}
public string Text2 { get; set; }
public string Text3 { get; set; }
public string Text4 { get; set; }
public string Text5 { get; set; }
public void RaisePropertyChangedEvent(string propname) {
if (PropertyChanged != null) {
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propname));
}
}
}
In XAML, you want to respond to the PropertyChanged event at the DataGridTextColumn.
Unfortunately, the DataGridTextColumn doesn´t have events.
If you want to respond to changes in a cell, it is recommended to set the Behavior directly to the DataGrid. This provides the CellEditEnded event.
here the solution:
<UserControl.Resources>
<ViewModel:MyViewModel x:Key="myViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource SampleDataSource}}">
<sdk:DataGrid d:LayoutOverrides="Width" AutoGenerateColumns="False" ItemsSource="{Binding Collection}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="CellEditEnded">
<ei:CallMethodAction TargetObject="{StaticResource myViewModel}" MethodName="OnPropertyChanged" />
</i:EventTrigger>
</i:Interaction.Triggers>
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding Text}" Header="Text"/>
<sdk:DataGridTextColumn Binding="{Binding Text2}" Header="Text2"/>
<sdk:DataGridTextColumn Binding="{Binding Text3}" Header="Text3"/>
<sdk:DataGridTextColumn Binding="{Binding Text4}" Header="Text4"/>
<sdk:DataGridTextColumn Binding="{Binding Text5}" Header="Text5"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>