How to deny rows deletion based on class property value? C# WPF - c#

I want to deny deletion of a row in case some there is a property with specific value, for example if product type is Steel I would like to deny user from deleting that row..
I'm setting source to my datagrid like this:
dataGridSourceList = new ObservableCollection<DatabaseItems>(TempController.Instance.SelectItemsByUserId(Globals.CurrentUser.Id));
dtgMainItems.ItemsSource = dataGridSourceList;
I saw there is a property CanUserDeleteRows
And I've added this to definition of my datagrid in xaml but I'm not sure how to apply this properly..
CanUserDeleteRows="{Binding ElementName=dtgMainItems, Path=SelectedItem.IsDeleteEnabled}"
Any kind of help would be awesome
Thanks

You could handle the CommandManager.PreviewCanExecute attached event:
private void OnPreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Command == DataGrid.DeleteCommand)
{
DatabaseItems selectedItem = dtgMainItems.SelectedItem as DatabaseItems;
if (selectedItem != null && !selectedItem.IsDeleteEnabled)
e.Handled = true;
}
}
XAML:
<DataGrid x:Name="dtgMainItems" CommandManager.PreviewCanExecute="Grid_PreviewCanExecute" />

Related

Hide column in datagrid WPF

I have a table in a database which I want to display in a WPF DataGrid. However I want to hide the first column. This column defines Id's for all items. I need the Id's for further actions, but I don't want to show it in the DataGrid. I've tried the code below, but I do get an error, on the last line, that the index has to be bigger than 0.
DbMainTable.ItemsSource = dataHandler.visibleDatabaseTable.DefaultView;
DbMainTable.Columns[0].Visibility = Visibility.Hidden;
If anyone can help me, let me know.
The problem is that when you are trying to set the visibility of the column it does not exist yet.
Try this:
In constructor:
DbMainTable.ItemsSource = dataHandler.visibleDatabaseTable.DefaultView;
DbMainTable.AutoGeneratedColumns += DbMainTable_OnAutoGeneratedColumns;
below:
private void DbMainTable_OnAutoGeneratedColumns(object? sender, EventArgs e)
{
DbMainTable.AutoGeneratedColumns -= DbMainTable_OnAutoGeneratedColumns;
DbMainTable.Columns[0].Visibility = Visibility.Hidden;
}
Could you provide more information about this issue? It is hard to guess what part of code is not working based on this.
But if I had to guess you use automatically generated columns and you try to hide this column before it is added to array of columns.
I event tried to do this with autogenerated columns and it gives me the same exception as you get.
To resolve this move this part of code somewhere where this datagrid (and its columns) is already loaded - for example to OnLoaded event handler in code behind
To achieve this:
in code behind add this method
private void DbMainTable_OnLoaded(object sender, RoutedEventArgs e)
{
DbMainTable.ItemsSource = dataHandler.visibleDatabaseTable.DefaultView;
DbMainTable.Columns[0].Visibility = Visibility.Hidden;
}
and in XAML:
<DataGrid x:Name="DbMainTable"
Loaded="DbMainTable_OnLoaded"
Grid.Column="0" />
If you need the id but don't want to see the column in the grid then I would think the simplest approach is not to add the column in the first place.
Work with the data which is in visibleDatabaseTable.
private void DbMainTable_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if ((string)e.Column.Header == "Id")
{
e.Cancel = true;
}
}
On your datagrid:
<Datagrid .....
AutoGeneratingColumn="DbMainTable_AutoGeneratingColumn"/>

Remove something added by a particular event - c#

I have a piece of code that will add the selected item of a combobox to a listbox when a checkbox is checked. I would like to remove that selected item to be removed from the listbox when the checkbox is unchecked.
My problem is I cant simply repeat the code for removal to be the same as add because the combobox selection will different or empty when unchecked.
This is how it currently looks:
private void CBwasher_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked == true)
{
listBox2.Items.Add(comboBox1.SelectedItem);
}
if (checkBox1.Checked == false)
{
listBox2.Items.Remove(comboBox1.SelectedItem);
}
So I need a way of removing whatever was added by that check change instead of removing the selected index of the combobox. Please consider that there may be multiple lines in the listbox added by multiple different checkboxes.
You simply need to store the added item and remove it.
private object addedItem;
private void CBwasher_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
addedItem = comboBox1.SelectedItem;
listBox2.Items.Add(addedItem);
}
else
{
listBox2.Items.Remove(addedItem);
}
}
You may also need to check SelectedItem is null before adding/removing the item.
Focusing on the part where you said there might be multiple different checkboxes,
you need to store one item per checkbox.
You can write your own child class of the checbox control to add this feature, or simply use the Tag property.
You can also indicate which checkbox is linked to which combobox in the same way. Either child class or use the Tag property.
In my example, I'll assume you've referenced the combobox from the checkbox using the Tag property.
You can do it manually like this
checkBox1.Tag = comboBox1;
or hopefully you can automate it if you are generating these on the fly.
Here is the general idea of how the checkbox event should look.
The event is is utilising the sender argument, which means you should hook up all your checkboxes CheckedChanged events to this one handler. No need to create separate handlers for each.
private void CBwasher_CheckedChanged(object sender, EventArgs e)
{
var checkBox = (CheckBox)sender;
var comboBox = (ComboBox)checkBox.Tag;
if (checkBox.Checked && comboBox.SelectedItem != null)
{
listBox2.Items.Add(comboBox.SelectedItem);
comboBox.Tag = comboBox.SelectedItem;
}
if (!checkBox.Checked && comboBox.Tag != null)
{
listBox2.Items.Remove(comboBox.Tag);
}
}

Update DataGrid's ItemsSource on SelectionChanged event of a ComboBox

I subscribed to a SelectionChangedEvent on a ComboBox in a DataGrid with the following code:
public static DataGridTemplateColumn CreateComboboxColumn(string colName, Binding textBinding, SelectionChangedEventHandler selChangedHandler = null)
{
var cboColumn = new DataGridTemplateColumn {Header = colName};
...
if (selChangedHandler != null)
cboFactory.AddHandler(Selector.SelectionChangedEvent, selChangedHandler);
...
return cboColumn;
}
The handler I actually register contains:
private void ComboBoxSelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine(#"selectHandler");
var cboBox = sender as ComboBox;
if (cboBox == null)
return;
if (cboBox.IsDropDownOpen) // a selection in combobox was made
{
CommitEdit();
}
else // trigger the combobox to show its list
cboBox.IsDropDownOpen = true;
}
... and is located in my custom DataGrid class.
If I select an item in the ComboBox, e.AddedItems and cboBox.SelectedItem contains the selected value, but nothing is changed on CommitEdit().
What I want is to force a commit to directly update the DataGrid's ItemsSource, when the user selects an item in the drop-down-list. Normally this is raised if the control looses focus...
The link in the solution found in this thread is not available any more and I don't know how to use this code.
I created a tricky, but working, solution for my problem. Here's the modified handler from above:
private void ComboBoxSelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine(#"selectHandler");
var cboBox = sender as ComboBox;
if (cboBox == null)
return;
if (cboBox.IsDropDownOpen) // a selection in combobox was made
{
cboBox.Text = cboBox.SelectedValue as string;
cboBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
}
else // user wants to open the combobox
cboBox.IsDropDownOpen = true;
}
Because my ComboBoxColumn is a custom DataGridTemplateColumn I force it to show its list, when the user first selects the cell.
To change the bound items value I manually overwrite the displayed text with recently selected item and force the UI to select another item (in this case the control to the right) to make an implicit call to CellEditEnding event, which (in my case) commits the whole row:
private bool _isManualEditCommit = false;
private void _CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
// commit a manual edit
// this if-clause prevents double execution of the EditEnding event
if (!_isManualEditCommit)
{
Console.WriteLine(#"_CellEditEnding() manualeditcommit");
_isManualEditCommit = true;
CommitEdit(DataGridEditingUnit.Row, true);
_isManualEditCommit = false;
checkRow(e.Row);
}
}
Maybe I could help somebody with this "dirty" solution ;-)

ScrollIntoView for WPF DataGrid (MVVM)

I'm using the MVVM pattern, and I've created a binding in XAML for the SelectedItem of a DataGrid. I programatically set the SelectedItem, however when I do so the DataGrid does not scroll to the selection. Is there any way I can achieve this without completely breaking the MVVM pattern?
I found the following solution but I get an error when I try to implement the Behavior class, even though I've installed Blend SDK: http://www.codeproject.com/Tips/125583/ScrollIntoView-for-a-DataGrid-when-using-MVVM
This should work. The idea is you have this attached property that you will attach to the DataGrid. In the xaml where you attach it, you'll bind it to a property on your ViewModel. Whenever you want to programmatically assign a value to the SelectedItem, you also set a value to this property, which the attached property is bound to.
I've made the attached property type to be whatever the SelectedItem type is, but honestly it doesn't matter what the type is as long as you set it to something different than what it was before. This attached property is just being used as a means to execute some code on the view control (in this case, a DataGrid) in an MVVM friendly fashion.
So, that said, here's the code for the attached property:
namespace MyAttachedProperties
{
public class SelectingItemAttachedProperty
{
public static readonly DependencyProperty SelectingItemProperty = DependencyProperty.RegisterAttached(
"SelectingItem",
typeof(MySelectionType),
typeof(SelectingItemAttachedProperty),
new PropertyMetadata(default(MySelectionType), OnSelectingItemChanged));
public static MySelectionType GetSelectingItem(DependencyObject target)
{
return (MySelectionType)target.GetValue(SelectingItemProperty);
}
public static void SetSelectingItem(DependencyObject target, MySelectionType value)
{
target.SetValue(SelectingItemProperty, value);
}
static void OnSelectingItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var grid = sender as DataGrid;
if (grid == null || grid.SelectedItem == null)
return;
// Works with .Net 4.5
grid.Dispatcher.InvokeAsync(() =>
{
grid.UpdateLayout();
grid.ScrollIntoView(grid.SelectedItem, null);
});
// Works with .Net 4.0
grid.Dispatcher.BeginInvoke((Action)(() =>
{
grid.UpdateLayout();
grid.ScrollIntoView(grid.SelectedItem, null);
}));
}
}
}
And here's the xaml snippet:
<Window ...
xmlns:attachedProperties="clr-namespace:MyAttachedProperties">
...
<DataGrid
attachedProperties:SelectingItemAttachedProperty.SelectingItem="{Binding MyViewModel.SelectingItem}">
...
</DataGrid>
</Grid>
I am new to MVVM. I understand the idea of MVVM and try to implement everything correctly.
I had a similar problem to above and I ended up with 1 line in XAML and 1 line in code behind. The rest of the code is in the VM.
I did the following in XAML
<ListBox DockPanel.Dock="Top"
Name="Selection1List"
ItemsSource="{Binding SelectedList1ItemsSource}"
SelectedItem="{Binding SelectedList1Item}"
SelectedIndex="{Binding SelectedList1SelectedIndex}"
SelectionChanged="Selection1List_SelectionChanged">
And this in the code behind:
private void Selection1List_SelectionChanged(object sender, SelectionChangedEventArgs e) {
Selection1List.ScrollIntoView(Selection1List.SelectedItem);
}
and this works fine.
I know some people don't want even one line of code in the code behind the window. But I think this 1 line is just for the view. It has nothing to do with the data or with the logic of the data. So I would think this is no violation of the MVVM principle - and so much easier to implement.
Any comments are welcome.
The solution of #Edgar works fine, but in my application I had to check the OriginalSource of the SelectionChangedEventArgs as well.
private void OperatorQualificationsTable_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((OperatorQualificationsTable.SelectedItem != null) && (e.OriginalSource?.Equals(OperatorQualificationsTable) ?? false))
{
OperatorQualificationsTable.ScrollIntoView(OperatorQualificationsTable.SelectedItem);
}
}
My datagrid contains following ComboBoxColumn
<dgx:EnhancedDataGridComboBoxColumn
DisplayMemberPath="DescriptionNL"
Header="{x:Static nl:Strings.Label_Qualification}"
ItemsSource="{Binding Path=QualificationKeysView, Source={StaticResource ViewModel}}"
SelectedValueBinding="{Binding ActivityQualification.QualificationKey}"
SelectedValuePath="QualificationKey"/>
Everytime when I scrolled up or down the selction changed event was called for the Combobox and it was no longer possible to move the selected item out of the view.
This is my solution to get ScrollIntoView working. I perform the operation in the LayoutUpdated() event
public void ManipulateData()
{
// Add a new record or what else is needed;
myItemsSourceCollection.Add(...);
// Not needed when the ItemsSource is a ObervableCollectin
// with correct Binding (ItemsSource="{ Binding myItemsSourceElement }")
myDataGrid.Items.Refresh();
// Goto last Item or where ever
myDataGrid.SelectedIndex = this.myDataGrid.Items.Count - 1;
}
// The LayoutUpdated event for the DataGrid
private void myDataGrid_LayoutUpdated(object sender, EventArgs e)
{
if (myDataGrid.SelectedItem == null)
return;
//<----------
// may become improved to check first if the `ScrollIntoView()` is really needed
// To prevent hanging here the ItemsSource must be
// a) an ObervableCollection with a correct working binding or
// b) myDataGrid.Items.Refresh(); must be called after changing
// the data
myDataGrid.ScrollIntoView(myDataGrid.SelectedItem, null);
}
this works for me:
public class ScrollingDataGrid : DataGrid
{
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
var grid = e.Source as DataGrid;
if(grid.SelectedItem != null)
{
grid.UpdateLayout();
grid.ScrollIntoView(grid.SelectedItem, null);
}
base.OnSelectionChanged(e);
}
}

DataGridViewCheckBoxColumn get state

I've got a DataGridView dgView which I populate with several different Forms, e.g. a DataGridViewCheckBoxColumn. To handle events, I added
private void InitializeComponent()
{
...
this.CellClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgView_CellClick);
...
}
The implementation looks like:
private void dgView_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (Columns[e.ColumnIndex].Name == "Name of CheckBoxColumn") // this is valid and returns true
{
Console.WriteLine("Handle single click!");
// How to get the state of the CheckBoxColumn now ??
}
}
This is where I am stuck. I already tried different approaches but with no success at all:
DataGridViewCheckBoxColumn cbCol = Rows[e.RowIndex].Cells[e.ColumnIndex] as DataGridViewCheckBoxColumn; // does not work
DataGridViewCheckBoxColumn cbCol = (DataGridViewCheckBoxColumn)sender; // nor this
if (bool.TryParse(Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString(), out isBool)) // nor this
{ ... }
Could anybody point out how to retrieve the State of this CheckBoxColumn please? Besides, do any other events exist do address the CheckBoxColumn directly? (such as "ValueChanged" or something)
Update:
The approach
DataGridViewCell dgvCell = Rows[e.RowIndex].Cells[e.ColumnIndex];
Console.WriteLine(dgvCell.Value);
at least returns true / false at the point of time before the value gets changed (or not) by clicking the cell. But all in all there should be a solution to address the CheckBoxColumn directly.
Solution:
Sometimes an answer is too obvious to see. The problem I was facing was that the event "CellClick" triggered when clicking on the cell as well as clicking on the checkbox. The proper handling therefore is to use a "CellValueChanged" event instead:
private void InitializeComponent()
{
...
this.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgView_CellValueChanged);
...
}
To determine the value of the checkbox I use the same way as stated above:
if (e.ColumnIndex != -1 && Columns[e.ColumnIndex].Name == "Name of Checkbox")
{
bool cbVal = Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
}
One way to manipulate UI components is by use of Data Bindings. See for example:
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Online Order?" IsThreeState="True" Binding="{Binding OnlineOrderFlag}" />
</DataGrid.Columns>
This links explains DataGrid usage in detail.
But anyways, have you tried doing QuickWatch the sender to see what is the type of it?
you could cast the cell to a checkbox and check if it's checked there...
CheckBox chkb = (CheckBox)Rows[e.RowIndex].FindControl("NameOfYourControl");
then just do a check against chkb
if (chkb.Checked == true)
{
//do stuff here...
}
You can simply cast the value of the cell to bool
//check if it's the good column
bool result = Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
edit : I'm probably missing something in your question but if you update/add detail I will have a look
edit 2 : After the comment,
If you want to do it in the cellClick just reverse the value it give you.
bool result = !Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
The proper handling is to use a "CellValueChanged" event:
private void InitializeComponent()
{
...
this.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgView_CellValueChanged);
...
}
To determine the value of the checkbox:
private void dgView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex != -1 && Columns[e.ColumnIndex].Name == "Name of Checkbox")
{
Console.WriteLine( Rows[e.RowIndex].Cells[e.ColumnIndex].Value );
}
}

Categories