I have a DataGrid that has a Checkbox in every first cell of a row. I want to loop through my DataGrid rows and get the rows if the Checkbox in that cell is checked.
This is my code in checking the DataGrid:
Edited based on Ilan's answer:
Private Sub ApproveBtn_Clicked(sender As Object, e As RoutedEventArgs)
For i As Integer = 0 To TimeSheetAppDGrid.Items.Count - 1
TimeSheetAppDGrid.SelectedItem = TimeSheetAppDGrid.Items(i)
Dim row As Row = DirectCast(TimeSheetAppDGrid.SelectedItems(0), Row)
Dim mycheckbox = TryCast(TimeSheetAppDGrid.Columns(0).GetCellContent(TimeSheetAppDGrid.Items(i)), ContentPresenter)
If mycheckbox Is Nothing Then
Return
End If
Dim checkBox = GetAllVisualChildren(mycheckbox).OfType(Of CheckBox)().FirstOrDefault(Function(box) box.Name = "chkSelectAll")
If checkBox Is Nothing Then
Return
End If
Dim isChecked = checkBox.Checked
If isChecked = True Then
MsgBox(row.Mark)
End If
Next
End Sub
The code below doesn't seem to be accepted by VB.NET causing the compilation error that says "Value of type 'FrameworkElement' cannot be converted to 'CheckBox'." After searching the web, almost all of the answers have this code in it but in C# so I converted it:
Dim mycheckbox As CheckBox = TryCast(TimeSheetAppDGrid.Columns(0).GetCellContent(TimeSheetAppDGrid.SelectedItems(0)), CheckBox)
This link is where I got the code. Below is the original line of code from C#:
CheckBox mycheckbox = myGrid.Columns[5].GetCellContent(myGrid.Items[i]) as CheckBox;
This is my xaml for the DataGrid:
<DataGrid Name ="TimeSheetAppDGrid" Margin="16,200,48,0" CanUserSortColumns="False" CanUserReorderColumns="False" CanUserAddRows="False" AutoGenerateColumns="False" SelectionUnit="FullRow"
materialDesign:DataGridAssist.CellPadding="13 8 8 8" materialDesign:DataGridAssist.ColumnHeaderPadding="8" IsReadOnly="True" SelectionMode="Single" >
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource MaterialDesignDataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="Row_DoubleClick"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox Name="AllChkBox" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chkSelectAll" Margin="3.5 0 0 0"
IsChecked="{Binding IsChecked, ElementName=AllChkBox,
Mode=OneWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Path=Mark}" Header="Mark" Width="120" MaxWidth="120"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnEditingStyle}"/>
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Path=ProjectCode}" Header="Project Code" Width="130" MaxWidth="130"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Path=Project}" Header="Project" Width="250" MaxWidth="250"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Path=PM}" Header="Project Manager" Width="230" MaxWidth="230"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Path=Activity}" Header="Activity" Width="600" MaxWidth="600"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Path=Today}" Header="Date" Width="200" MaxWidth="200"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Path=Regular}" Header="Regular" Width="130" MaxWidth="130"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Path=Overtime}" Header="Overtime" Width="130" MaxWidth="130"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource MaterialDesignDataGridColumnHeader}">
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</DataGridTextColumn.HeaderStyle>
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</DataGridTextColumn.ElementStyle>
</materialDesign:MaterialDataGridTextColumn>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource MaterialDesignDataGridCell}">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
Any suggestions?
You have to take look at the visual tree generated using Snoop, so that your cast works properly.
Simplest approach is to keep updating a Dictionary<int, DataGridRow> upon Checked/Unchecked events of the CheckBox.
Sorry the code is in C#.
XAML :
<CheckBox Name="AllChkBox" Checked="AllChkBox_Checked" Unchecked="AllChkBox_Unchecked" />
<CheckBox Name="chkSelectAll" Margin="3.5 0 0 0"
Checked="chkSelectAll_Checked" Unchecked="chkSelectAll_Unchecked"
IsChecked="{Binding IsChecked, ElementName=AllChkBox, Mode=OneWay}"/>
Code :
Dictionary<int, DataGridRow> rows = new Dictionary<int, DataGridRow>();
private void chkSelectAll_Checked(object sender, RoutedEventArgs e)
{
var cur_item = DGrid.CurrentItem;
int cur_index = DGrid.Items.IndexOf(cur_item);
if (cur_index == -1) // header checkbox is checked
return;
DataGridRow row = (DataGridRow)DGrid.ItemContainerGenerator.ContainerFromItem(cur_item);
rows.Add(DGrid.Items.IndexOf(cur_item), row);
}
private void chkSelectAll_Unchecked(object sender, RoutedEventArgs e)
{
var cur_item = DGrid.CurrentItem;
rows.Remove(DGrid.Items.IndexOf(cur_item));
}
private void AllChkBox_Checked(object sender, RoutedEventArgs e)
{
foreach (var item in DGrid.Items)
{
DataGridRow row = (DataGridRow)DGrid.ItemContainerGenerator.ContainerFromItem(item);
rows.Add(DGrid.Items.IndexOf(item), row);
}
}
private void AllChkBox_Unchecked(object sender, RoutedEventArgs e)
{
rows.Clear();
}
EDIT
ok, here's a more refined way of achieving what you need without having to search the visual tree looking for the checkbox (again in c# but hopefully shouldn't be difficult to translate).
Firstly, i've created a Student class to bind to which implements INotifyPropertyChanged :
public class Student:INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set { _isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public string Mark { get; set; }
public string ProjectCode { get; set; }
public string Project { get; set; }
public string PM { get; set; }
public string Activity { get; set; }
public DateTime Today { get; set; }
public string Regular { get; set; }
public string Overtime { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I then add a row to my data grid with test data :
TimeSheetAppDGrid.ItemsSource = new List<Student>()
{
new Student { ProjectCode="Project1",Mark = "A",Today = DateTime.Today}
};
In the XAML for the data template i've changed the binding to not bind to the header check box but bind to the student IsChecked property and i've added a clicked handler for AllChkBox checkbox in the header cell:
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox Click="AllChkBox_OnClick" Name="AllChkBox" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chkSelectAll" Margin="3.5 0 0 0"
IsChecked="{Binding Path=IsChecked,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
For the AllChkBox clicked handler (AllChkBox_OnClick) i iterate through each data source (i.e. Student) and toggle the IsChecked property to the AllChkBox checked value:
private void AllChkBox_OnClick(object sender, RoutedEventArgs e)
{
foreach (var s in TimeSheetAppDGrid.ItemsSource)
{
Student st = s as Student;
st.IsChecked = (bool) AllChkBox.IsChecked;
}
}
Now each item source (i.e. Student) can be interrogated for their IsChecked property (which is bound to the corresponding checkbox cell) without the need to fish around checking for the checkbox (excuse the pun) i.e.
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
foreach (var source in TimeSheetAppDGrid.ItemsSource)
{
Student st = source as Student;
if (st.IsChecked)
MessageBox.Show("Is Checked");
}
}
in my opinion there is some conceptual error in your adopted solution:
CheckBox mycheckbox = myGrid.Columns[5].GetCellContent(myGrid.Items[i]) as CheckBox;
I've checked it and when we use the DataGridTemplateColumn.CellTemplate, like in your case, the value that is accepted(mycheckbox) is the ContentPresenter because there is the DataTemplate behind the cell(because you are using the DataTemplate). That is why the code is actually isn't working.
The solution that I'm suggesting you is to get the check box object from the ContentPresenter(the code is in C# you will have to convert it to VB), since it is the ContentPresenter's visual child(you can se this in Snoop).
UpdateHere is the GetAllVisualChildren code:
public static class VisualTreeHelperExtensions
{
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
while (true)
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
child = parentObject;
}
}
public static List<DependencyObject> GetAllVisualChildren(this DependencyObject parent)
{
var resultedList = new List<DependencyObject>();
var visualQueue = new Queue<DependencyObject>();
visualQueue.Enqueue(parent);
do
{
var depObj = visualQueue.Dequeue();
var childrenCount = VisualTreeHelper.GetChildrenCount(depObj);
for (int i = 0; i < childrenCount; i++)
{
var v = VisualTreeHelper.GetChild(depObj, i);
visualQueue.Enqueue(v);
}
resultedList.Add(depObj);
} while (visualQueue.Count > 0);
resultedList.RemoveAt(0);
return resultedList;
}
}
Do next things in order to import the VisualTreeHelperExtensions class to VB
Create a C# class library as the part of your solution.
Add the VisualTreeHelperExtensions class into a new created project.
Compile your C# class library.
Add the new create(and compiled) class library into your vb.net project as a reference(dll).
Update #3 - changed vb code
Dim mycheckbox = TryCast(TimeSheetAppDGrid.Columns(0).GetCellContent(TimeSheetAppDGrid.Items(0)), ContentPresenter)
If mycheckbox Is Nothing Then
Return
End If
Dim checkBox = mycheckbox.GetAllVisualChildren().OfType(Of CheckBox)().FirstOrDefault(Function(box) box.Name = "chkSelectAll")
If checkBox Is Nothing Then
Return
End If
Dim isChecked = checkBox.IsChecked
If isChecked = True Then
MsgBox("Checked")
End If
VB.net CheckBox example
CheckBox Control WPF In VB.NET.
CheckBox Class in .Net Framework(no any restriction to vb)
Since all that the .net CheckBox has its IsChecked boolean state presenter.
Regards.
Related
I use IDataErrorInfo and DataAnnotations in my ViewModels to take care of validation and I want to use them for validation in my DataGrid. The behaviour I want for my cells can be simulated easily in a TextBox:
<TextBox Name="TestBox"
Text="{Binding TextProperty, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
However, in my DataGrid, the columns are automatically generated, and I can't set the ValidatesOnDataErrors binding option as I could if they were defined manually.
What I would like to do is something along these lines in a Style, since I don't want to alter the Binding's value, only its Binding Options:
<Style TargetType="DataGridCell">
<Setter Property="Content" Value="{Binding Path=., UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
</Style>
But this doesn't work. I am not sure what Property to use in the setter, as the DataGridCell has an internal TextBox or TextBlock, and what exactly handles the cell's validation.
Any ideas?
On your datagrid, hook the "AutoGeneratingColumn" event.
Inside the event handler, you can use e.Column to get to the binding and adjust it. You'll have to cast e.Column to the correct type first though (DataGridTextColumn, for example).
<DataGrid AutoGenerateColumns="True" Name="dg" AutoGeneratingColumn="dg_AutoGeneratingColumn" />
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
dg.ItemsSource = new List<MyItem>() { new MyItem() { Item1 = "Item 1", Item2 = "Item 2" } };
}
private void dg_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var tc = e.Column as System.Windows.Controls.DataGridTextColumn;
var b = tc.Binding as System.Windows.Data.Binding;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b.ValidatesOnDataErrors = true;
b.NotifyOnValidationError = true;
}
}
public class MyItem
{
public string Item1 { get; set; }
public string Item2 { get; set; }
}
I'm new in WPF and i'm exploring listbox control.
I created a listbox, items represent image plus text.
Xaml code:
<ListBox x:Name="LstB_Checklist" HorizontalAlignment="Left" Height="139" Margin="48,61,0,0" VerticalAlignment="Top" Width="220">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Checked}" Value="false">
<Setter Property="Source" Value="pack://application:,,,/listbox;component/Pictures/BulletOff.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Checked}" Value="true">
<Setter Property="Source" Value="pack://application:,,,/listbox;component/Pictures/BulletOn.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
the binding allows to properly set items and image at startup.
Code:
public MainWindow()
{
InitializeComponent();
List<LstB_Item> items = new List<LstB_Item>();
items.Add(new LstB_Item() { Title = "Item1", Checked = "false" });
items.Add(new LstB_Item() { Title = "Item2", Checked = "false" });
LstB_Checklist.ItemsSource = items;
}
public class LstB_Item
{
public string Title { get; set; }
public string Checked { get; set; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//
}
I would like to know how to change the image according to some conditions, when i clik on a button (e.g selected item image turn to "bulletOn" instead of "bulltOff" according to external condition, not based on "onselect" trigger)
Many thanks
As Clemens suggests the class should implement the INotifyPropertyChanged interface and raise change notifications whenever the Checked property is set to a new value:
public class LstB_Item : INotifyPropertyChanged
{
public string Title { get; set; }
private string _checked;
public string Checked
{
get { return _checked; }
set { _checked = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You should then be able to simply change the value of the Checked property for the item that you want to change the image for:
private void Button_Click(object sender, RoutedEventArgs e)
{
List<LstB_Item> items = LstB_Checklist.ItemsSource as List<LstB_Item>;
items[0].Checked = "true";
}
Please refer to MSDN for more information about the INotifyPropertyChanged interface: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx
You should also consider changing the type of your Checked property from string to bool:
private bool _checked;
public bool Checked
{
get { return _checked; }
set { _checked = value; NotifyPropertyChanged(); }
}
I seem to have a problem with adding rows to a DataGridthrough the interface itself.
Here is a screenshot of the UI:
As you can see, 0 rows were found in the database so nothing shows up in the DataGrid on the right side.
But id like there to be one empty row there, for manually adding rows.
The DataGrid.CanUserAddRows is set to True but has no effect.
Here is the xaml for the DataGrid, I have taken the liberty of removing some of the code to make it smaller.
PrivilegeDetailsView.xaml
<UserControl ...
d:DataContext="{d:DesignInstance impl:PrivilegeDetailsViewModel}">
<DataGrid ...
ItemsSource="{Binding RolesHasPrivilegesOnObjects}"
AutoGenerateColumns="False"
CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" CanUserSort="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type int:IRoleHasPrivilegeOnObjectListItemViewModel}">
<Image Source="{Binding Icon}" ToolTip="{Binding ToolTip}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Header="Name" Binding="{Binding Name}"/>
<DataGridCheckBoxColumn Header="Select" Binding="{Binding HasSelect, UpdateSourceTrigger=PropertyChanged}">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding CanHaveSelect}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</DataTrigger>
<DataTrigger Binding="{Binding CanHaveSelect}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
...
</DataGrid.Columns>
</DataGrid>
</UserControl>
PrivilegeDetailsView.xaml.cs
public partial class PrivilegeDetailsView : IPrivilegeDetailsView
{
public PrivilegeDetailsView() { InitializeComponent(); }
public DataGrid PrivilegesOnObjectsDataGrid { get { return PrivilegeDataGrid; } }
public IViewModel ViewModel
{
get { return (IViewModel)DataContext; }
set { DataContext = value; }
}
}
Here is the ViewModel (VM) for the xaml View above:
PrivilegeDetailsViewModel.cs
public class PrivilegeDetailsViewModel : ViewModelBase, IPrivilegeDetailsViewModel
{
private readonly IEventAggregator _eventAggregator;
private readonly IPrivilegeViewModel _privilegeViewModel;
private readonly IRoleHasPrivilegeOnObjectViewModelAdapterRepository _roleHasPrivilegeOnObjectViewModelAdapterRepository;
private ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> _rolesHasPrivilegesOnObjects;
public PrivilegeDetailsViewModel(IPrivilegeDetailsView view,
IRoleHasPrivilegeOnObjectViewModelAdapterRepository roleHasPrivilegeOnObjectViewModelAdapterRepository,
IPrivilegeViewModel privilegeViewModel,
IEventAggregator eventAggregator) : base(view)
{
_roleHasPrivilegeOnObjectViewModelAdapterRepository = roleHasPrivilegeOnObjectViewModelAdapterRepository;
_privilegeViewModel = privilegeViewModel;
_eventAggregator = eventAggregator;
Initialize();
}
protected override sealed void Initialize()
{
_privilegeViewModel.PropertyChanged += PrivilegeViewModelOnPropertyChanged;
_eventAggregator.GetEvent<ToggleSelectPrivilegeEvent>().Subscribe(ToggleSelectPrivilege);
...
}
public new IPrivilegeDetailsView View
{
get { return (IPrivilegeDetailsView)base.View; }
}
public ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> RolesHasPrivilegesOnObjects
{
get { return _rolesHasPrivilegesOnObjects; }
set
{
_rolesHasPrivilegesOnObjects = value;
OnPropertyChanged();
}
}
public void Save()
{
if(RolesHasPrivilegesOnObjects == null) return;
_roleHasPrivilegeOnObjectViewModelAdapterRepository.SaveChanges(RolesHasPrivilegesOnObjects);
}
private void ToggleExecutePrivilege(object obj)
{
var toggle = !View.PrivilegesOnObjectsDataGrid.SelectedItems.Cast<IRoleHasPrivilegeOnObjectListItemViewModel>()
.All(x => x.HasExecute);
foreach(var selectedItem in View.PrivilegesOnObjectsDataGrid
.SelectedItems
.Cast<IRoleHasPrivilegeOnObjectListItemViewModel>()
.Where(selectedItem => selectedItem.Object
.CanHavePrivilege("EXECUTE"))) {
selectedItem.HasExecute = toggle;
}
}
...
private void PrivilegeViewModelOnPropertyChanged(object s, PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
//When the SelectedSchema changes in the parent VM, I get the new rows to be shown in the DataGrid.
case "SelectedSchema":
RolesHasPrivilegesOnObjects = _roleHasPrivilegeOnObjectViewModelAdapterRepository
.GetPrivilegesOnObjectsAssociatedWith((IRoleEntityViewModel)_privilegeViewModel.SelectedRole,
(IContainerEntityViewModel)_privilegeViewModel.SelectedSchema);
break;
}
}
}
This is the VM for each row in the DataGrid
RoleHasPrivilegeOnObjectEntityViewModel.cs
public class RoleHasPrivilegeOnObjectEntityViewModel : EntityViewModelBase<RoleHasPrivilegeOnObjectEntityViewModel,
RoleHasPrivilegesOnObject>,
IRoleHasPrivilegeOnObjectListItemViewModel
{
private readonly RoleHasPrivilegesOnObject _roleHasPrivilegesOnObject;
public RoleHasPrivilegeOnObjectEntityViewModel(RoleHasPrivilegesOnObject roleHasPrivilegesOnObject)
{
_roleHasPrivilegesOnObject = roleHasPrivilegesOnObject;
Role = new RoleEntityViewModel(_roleHasPrivilegesOnObject.Role);
Object = new ObjectEntityViewModel(_roleHasPrivilegesOnObject.Object);
}
public override EntityType EntityType { get { return EntityType.NONE; } }
public override RoleHasPrivilegesOnObject OriginalEntity { get { return _roleHasPrivilegesOnObject; } }
public IRoleEntityViewModel Role { get; set; }
public IObjectEntityViewModel Object { get; set; }
public string ToolTip { get { return _roleHasPrivilegesOnObject.ToolTip; } }
public bool HasExecute
{
get { return _roleHasPrivilegesOnObject.HasExecute; }
set
{
_roleHasPrivilegesOnObject.HasExecute = value;
OnPropertyChanged();
}
}
public bool CanHaveExecute { get { return _roleHasPrivilegesOnObject.CanHaveExecute; } }
public override string Icon { get { return Object != null ? Object.Icon : string.Empty; } }
public override string NAME
{
get { return _roleHasPrivilegesOnObject.NAME; }
set
{
_roleHasPrivilegesOnObject.NAME = value;
OnPropertyChanged();
}
}
...
}
I know this is a lot of code, I have stripped away a lot and put in place a few dots (...) to show that more code exist. NOTE: Im using EF5 and PRISM
How can I make the DataGrid accept new rows through the GUI?
I believe your problem is using ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> as ItemsSource. In order for DataGrid to be able to create a new row, there has to be a type that can be constructed with an empty constructor.
If you changed it to say ObservableCollection<RoleHasPrivilegeOnObjectEntityViewModel> instead, i'm pretty sure your rows will start getting added.
What I ended up doing was partially/Mostly what Maverik suggested.
I changed ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> to be ObservableCollection<RoleHasPrivilegeOnObjectEntityViewModel> and created a default constructor, which it didn't previously have.
The issue then was that the RoleHasPrivilegeOnObjectEntityViewModel needs some fields and properties set in order to function, so I created a public Initialize function to provided the necessary parameters.
I added an event handler to the DataGrid's InitializingNewItem event, where i called the Initialize function.
private void PrivilegesOnObjectsDataGridOnInitializingNewItem(object s, InitializingNewItemEventArgs e)
{
var newItem = e.NewItem as RoleHasPrivilegeOnObjectEntityViewModel;
if (newItem == null) return;
var role = _privilegeViewModel.SelectedRole;
var schema = _privilegeViewModel.SelectedSchema;
newItem.Initialize(role.OriginalEntity, schema.OriginalEntity);
}
When trying to adda new row, clicking the ComboBox didn't fire off the InitializeNewItem event. But clicking any other column fired off the InitializeNewItem event, and since at first each Row's VM had it's own AvailableObjectTypes property, the ComboBox ItemSource was not set if the ComboBox was selected before any other column, thus making it empty.
That was not an acceptable behaviour so moving AvailableObjectTypes to the PrivilegeDetailsViewModel and changing the ComboBox's ItemSource binding to this helped
ItemsSource="{Binding DataContext.AvailableObjectTypes, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
EDITED: As comments have suggested, I should implement the MVVM pattern, I have done exactly that. However the same problem still persists. So I have altered the question accordingly:
I have a datagrid containing two columns bound to an observable collection (in the MyNotes class). One column contains a combobox and the other a textbox. The collection stores references to Note objects that contain an enumeration variable (displayed by the combobox) and a string (displayed by the textbox). All works fine except for the SelectedItems (and therefore the SelectedItem). When the program is built and run, you can add new rows to the datagrid (using the add/remove buttons) but after you attempt an edit (by entering the datagrid's textbox or combobox) then the datagrid's selectedItems and selectedItem fail. This can be seen by the use of the add/remove buttons: the selected row is not deleted and a new row is not added above the selected row respectively. This is a result of a symptom regarding the SelectedNote property losing its binding (I don't know why this happens and when I attempt to hack a rebind, the rebind fails?). Another symptom relates to the selected items property not reflecting what the datagrid is actually showing as selected (when viewing it in debug mode).
I am sure this problem relates to an issue with the datagrid, which makes it unusable for my case.
Here is the new XAML (its datacontext, the viewmodel, is set in the XAML and ParaTypes and headerText are both XAML static resources):
<DataGrid x:Name ="dgdNoteLimits"
ItemsSource ="{Binding ParagraphCollection}"
SelectedItem ="{Binding Path=SelectedNote, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AllowDrop ="True"
HeadersVisibility ="Column"
AutoGenerateColumns ="False"
CanUserAddRows ="False"
CanUserReorderColumns ="False"
CanUserSortColumns ="False"
BorderThickness ="0"
VerticalGridLinesBrush ="DarkGray"
HorizontalGridLinesBrush="DarkGray"
SelectionMode ="Extended"
SelectionUnit ="FullRow"
ColumnHeaderStyle ="{StaticResource headerText}">
<DataGrid.ItemContainerStyle>
<Style>
<Style.Resources>
<!-- SelectedItem's background color when focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Blue"/>
<!-- SelectedItem's background color when NOT focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Blue" />
</Style.Resources>
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.Columns>
<DataGridComboBoxColumn Header = "Note Type"
ItemsSource = "{Binding Source={StaticResource ParaTypes}}"
SelectedValueBinding= "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBinding = "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth = "115"
Width = "Auto">
</DataGridComboBoxColumn>
<DataGridTextColumn Header ="Description"
Binding="{Binding Path=NoteText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width ="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="SpellCheck.IsEnabled"
Value ="true" />
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1"
Margin="0, 0, 0, 16"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Add"
Width="72"
Margin="16,8,8,8"
Command="{Binding AddClickCommand}"/>
<Button Content="Remove"
Width="72"
Margin="16,8,8,8"
Command="{Binding RemoveClickCommand}"/>
</StackPanel>
Here is the view model:
class MainWindowViewModel : INotifyPropertyChanged
{
MyNotes NotesCollection;
private bool canExecute;
private ICommand clickCommand;
public MainWindowViewModel()
{
this.NotesCollection = new MyNotes();
this.ParagraphCollection = this.NotesCollection.Notes;
this.canExecute = true;
}
private ObservableCollection<Note> paragraphCollection;
public ObservableCollection<Note> ParagraphCollection
{
get { return this.paragraphCollection; }
set
{
this.paragraphCollection = value;
RaisePropertyChanged(() => this.ParagraphCollection);
}
}
private Note selectedNote;
public Note SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
RaisePropertyChanged(() => this.SelectedNote);
}
}
public ICommand AddClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => AddButtonHandler(), canExecute));
}
}
public void AddButtonHandler()
{
int noteIndex = 0;
Note aNote;
// what to do if a note is either selected or unselected...
if (this.SelectedNote != null)
{
// if a row is selected then add row above it.
if (this.SelectedNote.NoteIndex != null)
noteIndex = (int)this.SelectedNote.NoteIndex;
else
noteIndex = 0;
//create note and insert it into collection.
aNote = new Note(noteIndex);
ParagraphCollection.Insert(noteIndex, aNote);
// Note index gives sequential order of collection
// (this allows two row entries to have same NoteType
// and NoteText values but still note equate).
int counter = noteIndex;
// reset collection index so they are sequential
for (int i = noteIndex; i < this.NotesCollection.Notes.Count; i++)
{
this.NotesCollection.Notes[i].NoteIndex = counter++;
}
}
else
{
//if a row is not selected add it to the bottom.
aNote = new Note(this.NotesCollection.Count);
this.ParagraphCollection.Add(aNote);
}
}
public ICommand RemoveClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => RemoveButtonHandler(), canExecute));
}
}
public void RemoveButtonHandler()
{
//delete selected note.
this.ParagraphCollection.Remove(selectedNote);
}
//boiler plate INotifyPropertyChanged implementation!
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged<T>(Expression<System.Func<T>> propertyExpression)
{
var memberExpr = propertyExpression.Body as MemberExpression;
if (memberExpr == null)
throw new ArgumentException("propertyExpression should represent access to a member");
string memberName = memberExpr.Member.Name;
RaisePropertyChanged(memberName);
}
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and the model:
class MyNotes
{
public ObservableCollection<Note> Notes;
public MyNotes()
{
this.Notes = new ObservableCollection<Note>();
}
}
public enum NoteTypes
{
Header, Limitation, Warning, Caution, Note
}
public class Note
{
public int? NoteIndex { get; set; }
public NoteTypes NoteType { get; set; }
public string NoteText { get; set; }
public Note()
{
this.NoteIndex = null;
this.NoteType = NoteTypes.Note;
this.NoteText = "";
}
public Note(int? noteIndex): this()
{
this.NoteIndex = noteIndex;
}
public override string ToString()
{
return this.NoteType + ": " + this.NoteText;
}
public override bool Equals(object obj)
{
Note other = obj as Note;
if (other == null)
return false;
if (this.NoteIndex != other.NoteIndex)
return false;
if (this.NoteType != other.NoteType)
return false;
if (this.NoteText != other.NoteText)
return false;
return true;
}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 23 + this.NoteIndex.GetHashCode();
hash = hash * 23 + this.NoteType.GetHashCode();
hash = hash * 23 + this.NoteText.GetHashCode();
return hash;
}
}
Existing comments were greatly appreciated (I have learnt a lot and see the value of MVVM). It is just a shame they have not resolved the problem. But thank you.
So if anyone knows how I can resolve this issue, then that would be greatly appreciated.
I want to put a red-border around a DataGrid when it has no rows (Im doing the binding to ItemsSource).
So i was following this guide for WPF Validation:
http://www.codeproject.com/Articles/15239/Validation-in-Windows-Presentation-Foundation
Anyhow, it is simple enough to make a textbox have such an error when the Text = "":
and even customize the error:
I tried debugging and the ValidationRules that are bound within my ItemsSource are never invoked.
<DataGrid ...>
<DataGrid.ItemsSource>
<Binding Path="Lines" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataGridValidationRule
MiniumRows="1"
MaximumRows="100"
ErrorMessage="must have between 1 and 100 rows">
</DataGridValidationRule>
</Binding.ValidationRules>
</Binding>
</DataGrid.ItemsSource>
</DataGrid>
And then the DataGridValidtionRule class looks like this:
public class public class StringRangeValidationRule : ValidationRule
{
private int _minimumRows = -1;
private int _maximumRows = -1;
private string _errorMessage;
public int MinimumRows
{
get { return _minimumRows ; }
set { _minimumRows = value; }
}
public int MaximumRows
{
get { return _maximumLength; }
set { _maximumLength = value; }
}
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
ValidationResult result = new ValidationResult(true, null);
ObservableCollection<Lines> lines = (ObservableCollection<Lines>) value;
if (lines.Count < this.MinimumRows||
(this.MaximumRows> 0 &&
lines.Count > this.MaximumRows))
{
result = new ValidationResult(false, this.ErrorMessage);
}
return result;
}
And the "Lines" class
public class Line
{
public Line(string f, string b)
{
foo = f;
bar = b;
}
public string Foo {get; set;}
public string Bar {get; set;}
}
EDIT:
It turns out that my "delete row" button for my datagrid was removing from the ObservableCollection but not through the actual DataGrid (it was removing at the ViewModel)... and for some reason this prevents the Validation call from being invoked.
So again the View:
<DataGrid Name="mygrid">
<DataGrid.ItemsSource>
<Binding Path="Lines" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataGridValidationRule
MiniumRows="1"
MaximumRows="100"
ErrorMessage="must have between 1 and 100 rows">
</DataGridValidationRule>
</Binding.ValidationRules>
</Binding>
</DataGrid.ItemsSource>
</DataGrid>
so if i had in the ViewModel:
void delete(Line l)
{
Lines.Remove(l); //if you delete everything (grid empty) there won't be any error shown.
}
The error border & icon wouldn't show up around the datagrid.
But if instead i put a event that directly changed the ItemsSource like this:
void delete(Line l)
{
Lines.Remove(l);
myView.mygrid.ItemsSource = Lines; // this magically fixes everything... even though it was already bound to Lines... though i hate to directly access the View from within the ViewModel.
}
I'm not sure why exactly... but that fixed it.
Any ideas on how i could separate the view from the VM? I don't really like this fix.
Try to put the datagrid in a border and attach a trigger to it to make it red once datagrid is empty
<Border Margin="20" >
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ISEmpty}" Value="True">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<DataGrid Name="myGrid" AutoGenerateColumns="True" ItemsSource="{Binding Path=Lecturers}" >
</DataGrid>
</Border>
Set IsEmpty property to true when you clear the grid
private bool iSEmpty;
public bool ISEmpty
{
get { return iSEmpty; }
set
{
iSEmpty = value;
NotifyPropertyChanged("ISEmpty");
}
}
Clear your item source collection
viewmodel.Lecturers.Clear();
this.viewmodel.ISEmpty = true;
Future work. You can bind trigger to datagrid control.