Wpf Datagrid Max Rows - wpf

I am currently working with data grid where I only want to allow the user to enter UP TO 20 rows of data before making CanUserAddRows to false.
I made a dependency property on my own datagrid (that derives from the original one). I tried using the event "ItemContainerGenerator.ItemsChanged" and check for row count in there and if row count = max rows, then make can user add row = false (I get an exception for that saying i'm not allow to change it during "Add Row".
I was wondering if there is a good way on implementing this ....
Thanks and Regards,
Kevin

Here's a subclassed DataGrid with a MaxRows Dependency Property. The things to note about the implementation is if the DataGrid is in edit mode and CanUserAddRows is changed an InvalidOperationException will occur (like you mentioned in the question).
InvalidOperationException
'NewItemPlaceholderPosition' is not
allowed during a transaction begun by
'AddNew'.
To workaround this, a method called IsInEditMode is called in OnItemsChanged and if it returns true we subscribe to the event LayoutUpdated to check IsInEditMode again.
Also, if MaxRows is set to 20, it must allow 20 rows when CanUserAddRows is True, and 19 rows when False (to make place for the NewItemPlaceHolder which isn't present on False).
It can be used like this
<local:MaxRowsDataGrid MaxRows="20"
CanUserAddRows="True"
...>
MaxRowsDataGrid
public class MaxRowsDataGrid : DataGrid
{
public static readonly DependencyProperty MaxRowsProperty =
DependencyProperty.Register("MaxRows",
typeof(int),
typeof(MaxRowsDataGrid),
new UIPropertyMetadata(0, MaxRowsPropertyChanged));
private static void MaxRowsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MaxRowsDataGrid maxRowsDataGrid = source as MaxRowsDataGrid;
maxRowsDataGrid.SetCanUserAddRowsState();
}
public int MaxRows
{
get { return (int)GetValue(MaxRowsProperty); }
set { SetValue(MaxRowsProperty, value); }
}
private bool m_changingState;
public MaxRowsDataGrid()
{
m_changingState = false;
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (IsInEditMode() == true)
{
EventHandler eventHandler = null;
eventHandler += new EventHandler(delegate
{
if (IsInEditMode() == false)
{
SetCanUserAddRowsState();
LayoutUpdated -= eventHandler;
}
});
LayoutUpdated += eventHandler;
}
else
{
SetCanUserAddRowsState();
}
}
private bool IsInEditMode()
{
IEditableCollectionView itemsView = Items;
if (itemsView.IsAddingNew == false && itemsView.IsEditingItem == false)
{
return false;
}
return true;
}
// This method will raise OnItemsChanged again
// because a NewItemPlaceHolder will be added or removed
// so to avoid infinite recursion a bool flag is added
private void SetCanUserAddRowsState()
{
if (m_changingState == false)
{
m_changingState = true;
int maxRows = (CanUserAddRows == true) ? MaxRows : MaxRows-1;
if (Items.Count > maxRows)
{
CanUserAddRows = false;
}
else
{
CanUserAddRows = true;
}
m_changingState = false;
}
}
}

I am an adept of K.I.S.S. To reduce the amount of lines required to do the job and keep it simple, use the following:
private void MyDataGrid_LoadingRow( object sender, DataGridRowEventArgs e )
{
// The user cannot add more rows than allowed
IEditableCollectionView itemsView = this.myDataGrid.Items;
if( this.myDataGrid.Items.Count == max_RowCount + 1 && itemsView.IsAddingNew == true )
{
// Commit the current one added by the user
itemsView.CommitNew();
// Once the adding transaction is commit the user cannot add an other one
this.myDataGrid.CanUserAddRows = false;
}
}
Simple and compact ;0)

Related

Searching In DataGrid

I want to search in a data grid via typing in a textbox, but I am unable to find solution.
Do I need to do any binding? If so, then how do I do it?
If you want filter text in your Datagrid i.e by Name, try this...
private bool DataMatchesFilterText(User user, string filterText)
{
return user.Name.ToString() == filterText;
}
Yeah you will require your data grid to be bound to a Property that contains all your data.
Then add a event handler to your Textbox to act on one of the key events, e.g.
Xaml:
<TextBox x:Name="SearchBox" KeyUp="FilterTextBox_TextChanged" />
Then in the code behind you need to act on that event. Here you need to extract the filter text, get the rows in your DataGrid and then perform some method to determine if it should be visible or not. You will need to implement your own DataMatchesFilterText method.
Codebehind:
private void FilterTextBox_TextChanged(object sender, KeyEventArgs e)
{
var filterTextBox = (TextBox)sender;
var filterText = filterTextBox.Text;
SetRowVisibilityByFilterText(filterText);
}
private void SetRowVisibilityByFilterText(string filterText)
{
GetVisibleRows(yourGrid)
.ToList()
.ForEach(
x =>
{
if (x == null) return;
x.Visibility =
DataMatchesFilterText(x.Item as YourRowProperty, filterText) ? Visibility.Visible : Visibility.Collapsed;
});
}
public static IEnumerable<DataGridRow> GetVisibleRows(DataGrid grid)
{
if (grid == null || grid.Items == null) yield break;
int count = grid.ItemsSource == null
? grid.Items.Count
: grid.ItemsSource.Cast<object>().Count();
for (int i = 0; i < count; i++)
{
yield return (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(i);
}
}

Bindings seems broken while using observable collection

I have a ViewModel with an Observable Collection Property
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get
{
return currentSensorAreasList;
}
set
{
if (currentSensorAreasList != value)
{
currentSensorAreasList = value;
OnPropertyChanged(PROPERTY_NAME_CURRENT_SENSOR_AREAS_LIST);
}
}
}
Then in my xaml i have a binding
ItemsSource="{Binding CurrentSensorAreasList}">
This Observable Collection is updated trought a method that can be call in the viewModel constructor or when a collectionchanged handler from another list gets called.
I just clear the list and then add a fewer new items. While debugging i see all my new items updated on the list. But the UI does not get updated.
When i regenerate the viewModel and then this update method gets call in the constructor the list gets updated in the UI.
Any ideas?? I don't know if the problem comes when i call the method from a handler.....
UPDATE #1
As requested i'm going the code when I update the list
I have tested two ways to do this update
private void UpdateList1()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList.Clear();
CurrentSensorAreasList.AddRange(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList));
//AddRange is an extension method.
}
}
private void UpdateList2()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList = new ObservableCollection<GeographicArea>(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList))
}
}
Both cases works when i call it from the constructor. Then I have Other Lists where the Areas changes, and i get notified via CollectionChanged Handlers.
private void globalAreaManagerList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (AreaManager newItem in e.NewItems)
{
newItem.AreaList.CollectionChanged += AreaList_CollectionChanged;
}
}
if (e.OldItems != null)
{
foreach (AreaManager oldItem in e.OldItems)
{
oldItem.AreaList.CollectionChanged -= AreaList_CollectionChanged;
}
}
UpdateList();
}
private void AreaList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdateList();
}
So when i use UpdateList1 seems to work more times but suddenly the Binding is broken and then this update does not show in the UI.
If you wish change exactly an instance of collection I recomend using DependecyProperty for that case.
Here is:
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get { return (ObservableCollection<GeographicArea>)GetValue(CurrentSensorAreasListProperty); }
set { SetValue(CurrentSensorAreasListProperty, value); }
}
public static readonly DependencyProperty CurrentSensorAreasListProperty =
DependencyProperty.Register("CurrentSensorAreasList", typeof(ObservableCollection<GeographicArea>), typeof(ownerclass));
Where ownerclass - a name of class where you put this property.
But the better way is create only one instance of ObservaleCollection and then just change its items. I mean Add, Remove, and Clear methods.

RepositoryItemCheckEdit doesn't stay checked

I try to add a RepositoryItemCheckEdit to my GridView using devexpress and Winforms. However, I can get only one checkbox be checked. If I check another one, the checkbox I checked before becomes unchecked. I followed everything I can find on the net, but couldn't make this work. What am I missing?
The code part I insert the column:
gcIsEmirleri.DataSource = (from i in isemirleri
select new
{
ID = i.isEmriId,
// other attributes
}).ToList();
GridColumn column = gvIsEmirleri.Columns["Sec"];
if (column == null)
{
gvIsEmirleri.BeginUpdate();
DataColumn col = new DataColumn("Sec", typeof(bool));
column = gvIsEmirleri.Columns.AddVisible("Sec");
col.VisibleIndex = 0;
col.Caption = "Sec";
col.Name = "Sec";
col.OptionsColumn.AllowEdit = true;
gvIsEmirleri.EndUpdate();
gvIsEmirleri.Columns["Sec"].UnboundType = DevExpress.Data.UnboundColumnType.Boolean;
RepositoryItemCheckEdit chk = new RepositoryItemCheckEdit();
chk.ValueChecked = true;
chk.ValueUnchecked = false;
gvIsEmirleri.Columns["Sec"].ColumnEdit = chk;
chk.QueryCheckStateByValue += chk_QueryCheckStateByValue;
}
The code part I make the checkbox two-stated instead of three:
private void chk_QueryCheckStateByValue(object sender, DevExpress.XtraEditors.Controls.QueryCheckStateByValueEventArgs e)
{
if (e.Value == null)
{
e.CheckState = CheckState.Unchecked;
e.Handled = true;
}
}
EDIT: I created a List<bool> chkList; and do the following operations:
This function is added to checkedits' CheckStateChanged:
private void chk_CheckStateChanged(object sender, EventArgs e)
{
CheckEdit chk = sender as CheckEdit;
if (chk.Checked)
chkList[gvIsEmirleri.FocusedRowHandle] = true;
else
chkList[gvIsEmirleri.FocusedRowHandle] = false;
FillBindingSource();
}
In FillBindingSource I added the lines:
for (int i = 0; i < chkList.Count; i++)
{
if (chkList[i])
gvIsEmirleri.SetRowCellValue(i, "Sec", true);
}
I debug these lines, I see that List has correct bool values and gvIsEmirleri.SetRowCellValue(i, "Sec", true); is operated when it has to. However, it still doesn't work.
My guess is : You are using an unbound Column, and you are not saving the checked / unckecked info, so, after the selected row is left, the checkBox get it's initial value (unckecked).
For this, I suggest you handle the CustomUnboundColumnData event of your view. Here is a simple :
readonly Dictionary<object, bool> checkedMap = new Dictionary<object, bool>();
private void viewScales_CustomUnboundColumnData(object sender, CustomColumnDataEventArgs e)
{
// Check what column
if (e.Column != gvIsEmirleri.Columns["Sec"])
return;
if (e.IsGetData)
{
// check if the row has been checked and set it's value using e.Value
bool checked;
if (checkedMap.TryGetValue(e.Row, out checked))
e.Value = checked;
}
if (e.IsSetData)
{
var checked = Convert.ToBoolean(e.Value);
// Check if the key already exist
if (checkedMap.ContainsKey(e.Row))
scaleMap.Remove(e.Row);
checkedMap.Add(e.Row, checked);
}
}
Note : This is the way I resolved a similar problem, but I did not test the code I just wrote.

TextBox resets text when tabbing out ot other controls

I have a form with several controls. There a situtions where 'textBoxOtherRelationship' is disable and the text is set to string.empty. But when I then got to another control and tab out the data appears again,while the control remains disabled.
textBoxOtherRelationship.DataBindings.Add(new Binding("Text", _binder, "RelationshipNotes"));
private void ComboBoxRelationShipSelectedValueChanged(object sender, EventArgs e)
{
if ((Constants.Relationship)comboBoxRelationShip.SelectedItem.DataValue == Constants.Relationship.Other)
{
textBoxOtherRelationship.Enabled = true;
if (_formMode != ActionMode.ReadOnly)
{
textBoxFirstName.BackColor = Color.White;
}
}
else
{
textBoxOtherRelationship.Enabled = false;
_model.RelationshipNotes = null;
textBoxOtherRelationship.Text = string.Empty;
if (_formMode != ActionMode.ReadOnly)
{
textBoxFirstName.BackColor = Color.LightYellow;
}
}
}
Hmm.. so I see this line here:
textBoxOtherRelationship.DataBindings.Add(
new Binding("Text", _binder, "RelationshipNotes"));
which tells me that you've got binding set up between the Text property on the textBoxOtherRelationship and a property called "RelationshipNotes" on the datasource _binder.
Great.
So, I'm assuming that the two-way binding works just fine and that when you type something into the textBoxOtherRelationship and that control loses focus the underlying RelationshipNotes property is getting updated as well, right?
Now, looking at your code there, I don't think the underlying datasource is being updated when you set the Text property to string.Empty because that usually doesn't happen until the textbox loses focus and you've disabled the control.
If you add:
textBoxOtherRelationship.DataBindings[0].WriteValue();
after you set the value to string.Empty that string.Empty value will get stored back to the datasource because the databinding will know there is something to update. Programmatically, it doesn't.
I see you have this line:
textBoxOtherRelationship.Enabled = false;
_model.RelationshipNotes = null; <<<----------------------
textBoxOtherRelationship.Text = string.Empty;
Is _model.RelationshipNotes what is ultimately supposed to be bound to that textbox?
The SelectedIndexChanged event doesn't commit the databindings until after the control loses focus, so the quick fix is to write the value first in your event:
private void ComboBoxRelationShipSelectedValueChanged(object sender, EventArgs e)
{
if (comboBoxRelationShip.DataBindings.Count > 0) {
comboBoxRelationShip.DataBindings[0].WriteValue();
if ((Constants.Relationship)comboBoxRelationShip.SelectedItem.DataValue ==
Constants.Relationship.Other) {
textBoxOtherRelationship.Enabled = true;
if (_formMode != ActionMode.ReadOnly) {
textBoxFirstName.BackColor = Color.White;
}
} else {
textBoxOtherRelationship.Enabled = false;
_model.RelationshipNotes = null;
textBoxOtherRelationship.Text = string.Empty;
if (_formMode != ActionMode.ReadOnly) {
textBoxFirstName.BackColor = Color.LightYellow;
}
}
}
}

Winforms DataBind to Control's Visible Property

Are there any known issues when databinding to a control's visible property?
The control is always NOT visible regardless of what my property is.
Public ReadOnly Property IsRibbonCategory() As Boolean
Get
Return True
End Get
End Property
I tried the control's text property and other properties and they seem to work correctly.
I am trying to set a Panel's visible property.
I've found that life is better if you assume that binding to a control's Visible property is broken, despite the fact that it sometimes works. See http://support.microsoft.com/kb/327305, which says as much (and while the KB article applies to .NET 1.0 and 1.1, it still seems to be a problem in at least 2.0).
I created a utility class for creating bindings which, among other things, gave me a centralized place to add a work-around. Instead of actually creating a binding on Visible it does two things:
It subscribes to the data source's INotifyPropertyChanged.PropertyChanged event and sets the Visible value as appropriate when the event is raised.
It sets the initial value of Visible according to the current data source value.
This required a little reflection code, but wasn't too bad. It is critical that you don't bind the Visible property and do the work-around or it won't work.
Workaround: Set the Visible property on the BindingComplete event.
I had same issue setting a label's Visible property - always stays false, even though setting the Enabled property works fine.
I just hit this issue in .NET 4.7.1 and Visual Studio 2017. To fix it, I changed the Visible property on my control to be initially set to True, as I had it as False previously.
Things to check:
Be sure you've instantiated the class that has the IsRibbonCategory property
Did you set the datasource of property of the binding source to the instance of the class
The datasource update mode should be on "on validation"
Make sure you didn't set the visible property manually to false on the control
Hope that helps. Can you post more code?
A workaround would be to use a Component to databind to a control's visiblity property instead of directly binding to the control's visibility property.
See below code:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public class ControlVisibilityBinding : Component
{
private static readonly object EventControlChanged = new object();
private static readonly object EventVisibleChanged = new object();
private System.Windows.Forms.Control _control;
private bool _visible = true;
public event EventHandler VisibleChanged
{
add { Events.AddHandler(EventVisibleChanged, value); }
remove { Events.RemoveHandler(EventVisibleChanged, value); }
}
public event EventHandler ControlChanged
{
add { Events.AddHandler(EventControlChanged, value); }
remove { Events.RemoveHandler(EventControlChanged, value); }
}
public ControlVisibilityBinding()
{
}
public ControlVisibilityBinding(IContainer container)
{
container.Add(this);
}
[DefaultValue(null)]
public System.Windows.Forms.Control Control
{
get { return _control; }
set
{
if(_control == value)
{
return;
}
WireControl(_control, false);
_control = value;
if(_control != null)
{
_control.Visible = _visible;
}
WireControl(_control, true);
OnControlChanged(EventArgs.Empty);
OnVisibleChanged(EventArgs.Empty);
}
}
[DefaultValue(true)]
public bool Visible
{
get { return _visible; }
set
{
if(_visible != value)
{
_visible = value;
}
if(Control != null)
{
Control.Visible = _visible;
}
OnVisibleChanged(EventArgs.Empty);
}
}
private void WireControl(Control control, bool subscribe)
{
if(control == null)
{
return;
}
if(subscribe)
{
control.VisibleChanged += Control_VisibleChanged;
}
else
{
control.VisibleChanged -= Control_VisibleChanged;
}
}
private void Control_VisibleChanged(object sender, EventArgs e)
{
OnVisibleChanged(EventArgs.Empty);
}
protected virtual void OnVisibleChanged(EventArgs e)
{
EventHandler subscribers = (EventHandler)Events[EventVisibleChanged];
if(subscribers != null)
{
subscribers(this, e);
}
}
protected virtual void OnControlChanged(EventArgs e)
{
EventHandler subscribers = (EventHandler)Events[EventControlChanged];
if(subscribers != null)
{
subscribers(this, e);
}
}
}
static class Program
{
[STAThread]
static void Main()
{
using(Form form = new Form())
using(FlowLayoutPanel groupBoxLayoutPanel = new FlowLayoutPanel())
using(RadioButton visibleButton = new RadioButton())
using(RadioButton hiddenButton = new RadioButton())
using(GroupBox groupBox = new GroupBox())
using(Label text = new Label())
using(ControlVisibilityBinding visibilityBinding = new ControlVisibilityBinding())
using(TextBox inputTextBox = new TextBox())
{
groupBoxLayoutPanel.Dock = DockStyle.Fill;
groupBoxLayoutPanel.FlowDirection = FlowDirection.LeftToRight;
groupBoxLayoutPanel.AutoSize = true;
groupBoxLayoutPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
visibleButton.Text = "Show Label";
visibleButton.AutoSize = true;
hiddenButton.Text = "Hide Label";
hiddenButton.AutoSize = true;
groupBoxLayoutPanel.Controls.Add(visibleButton);
groupBoxLayoutPanel.Controls.Add(hiddenButton);
inputTextBox.Text = "Enter Label Text Here";
inputTextBox.Dock = DockStyle.Top;
groupBox.AutoSize = true;
groupBox.AutoSizeMode = AutoSizeMode.GrowAndShrink;
groupBox.Controls.Add(groupBoxLayoutPanel);
groupBox.Dock = DockStyle.Fill;
text.AutoSize = true;
text.ForeColor = Color.Red;
text.Dock = DockStyle.Bottom;
text.BorderStyle = BorderStyle.FixedSingle;
text.Font = new Font(text.Font.FontFamily, text.Font.Size * 1.25f, FontStyle.Bold | FontStyle.Italic);
text.DataBindings.Add("Text", inputTextBox, "Text", true, DataSourceUpdateMode.Never);
visibilityBinding.Control = text;
visibleButton.DataBindings.Add("Checked", visibilityBinding, "Visible", true, DataSourceUpdateMode.OnPropertyChanged);
Binding binding = hiddenButton.DataBindings.Add("Checked", visibilityBinding, "Visible", true, DataSourceUpdateMode.OnPropertyChanged);
ConvertEventHandler invertConverter = (sender, e) => e.Value = !((bool)e.Value);
binding.Format += invertConverter;
binding.Parse += invertConverter;
form.Controls.Add(inputTextBox);
form.Controls.Add(text);
form.Controls.Add(groupBox);
Application.Run(form);
}
}
}
}
Here is my turn around, it may be stupid but it worked many times.
I put one Panel control in my form, I make it to Fill my form and I put everything in that Panel. All the controls I bind the Visible property see their visibility change according to the objects in my DataGridView.

Resources